diff --git a/.vscode/settings.json b/.vscode/settings.json index ce4d2e0..7059129 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -51,6 +51,7 @@ "xmemory": "c", "xtr1common": "c", "xtree": "c", - "stdint.h": "c" + "stdint.h": "c", + "ipsettings.h": "c" } } \ No newline at end of file diff --git a/build_debug.bat b/build_debug.bat index 13f8efb..4abda12 100644 --- a/build_debug.bat +++ b/build_debug.bat @@ -13,6 +13,7 @@ pushd thirdparty\Chipmunk2D\src for %%x in (*.c) do set MUNKSRC=!MUNKSRC! thirdparty\Chipmunk2D\src\%%x popd +@REM /DENET_DEBUG=1^ cl /MP /Zi /Fd"flight.pdb" /Fe"flight"^ /I"thirdparty" /I"thirdparty\enet\include" /I"thirdparty\Chipmunk2D\include\chipmunk" /I"thirdparty\Chipmunk2D\include"^ main.c gamestate.c server.c debugdraw.c^ diff --git a/gamestate.c b/gamestate.c index 7970ad0..bbe111a 100644 --- a/gamestate.c +++ b/gamestate.c @@ -1,7 +1,8 @@ #include #include "types.h" -#include // assert logging +#include // assert logging +#include // memset // do not use any global variables to process gamestate @@ -10,6 +11,12 @@ // - debug.c for debug drawing // - chipmunk +enum +{ + PLAYERS = 1 << 0, + BOXES = 1 << 1, +}; + void __assert(bool cond, const char *file, int line, const char *cond_string) { if (!cond) @@ -30,82 +37,246 @@ static cpVect v2_to_cp(V2 v) return cpv(v.x, v.y); } -static struct Box *getbox(cpShape *shape) +bool was_entity_deleted(struct GameState *gs, EntityID id) { - return (struct Box *)cpShapeGetUserData(shape); + if(id.generation == 0) return false; // generation 0 means null entity ID, not a deleted entity + Entity *the_entity = &gs->entities[id.index]; + return (!the_entity->exists || the_entity->generation != id.generation); } -static struct Grid *find_empty_grid(struct GameState *gs) +// may return null if it doesn't exist anymore +Entity *get_entity(struct GameState *gs, EntityID id) { - // @Robust better memory mgmt - struct Grid *empty_grid = NULL; - for (int ii = 0; ii < MAX_GRIDS; ii++) + if (id.generation == 0) { - if (gs->grids[ii].body == NULL) - { - empty_grid = &gs->grids[ii]; - break; - } + return NULL; } - // @Robust cleanly fail when not enough grids - assert(empty_grid != NULL); - return empty_grid; + assert(id.index < gs->cur_next_entity); + assert(id.index < gs->max_entities); + Entity *to_return = &gs->entities[id.index]; + if (was_entity_deleted(gs, id)) + return NULL; + return to_return; +} + +EntityID get_id(struct GameState *gs, Entity *e) +{ + if (e == NULL) + return (EntityID){0}; + + int index = e - gs->entities; + assert(index >= 0); + assert(index < gs->cur_next_entity); + + return (EntityID){ + .generation = e->generation, + .index = index, + }; +} + +static Entity *cp_shape_entity(cpShape *shape) +{ + return (Entity *)cpShapeGetUserData(shape); +} + +static Entity *cp_body_entity(cpBody *body) +{ + return (Entity *)cpBodyGetUserData(body); } -static int grid_num_boxes(struct Grid *g) +static struct GameState *cp_space_gs(cpSpace *space) { + return (struct GameState *)cpSpaceGetUserData(space); +} + +int grid_num_boxes(struct GameState *gs, Entity *e) +{ + assert(e->is_grid); int to_return = 0; - for (int i = 0; i < MAX_BOXES_PER_GRID; i++) + + BOXES_ITER(gs, cur, e) + to_return++; + + return to_return; +} + +void box_remove_from_boxes(GameState *gs, Entity *box) +{ + assert(box->is_box); + Entity *prev_box = get_entity(gs, box->prev_box); + Entity *next_box = get_entity(gs, box->next_box); + if (prev_box != NULL) { - SKIPNULL(g->boxes[i].shape); - to_return++; + if (prev_box->is_box) + prev_box->next_box = get_id(gs, next_box); + else if (prev_box->is_grid) + prev_box->boxes = get_id(gs, next_box); } - return to_return; + if (next_box != NULL) + { + assert(next_box->is_box); + next_box->prev_box = get_id(gs, prev_box); + } + box->next_box = (EntityID){0}; + box->prev_box = (EntityID){0}; } -static void box_destroy(cpSpace *space, struct Box *box) +void entity_destroy(GameState *gs, Entity *e) { - cpSpaceRemoveShape(space, box->shape); - cpShapeFree(box->shape); - box->shape = NULL; + assert(e->exists); + + if (e->is_grid) + { + BOXES_ITER(gs, cur, e) + entity_destroy(gs, cur); + } + if (e->is_box) + { + box_remove_from_boxes(gs, e); + } + + if (e->body != NULL) + { + cpSpaceRemoveBody(gs->space, e->body); + cpBodyFree(e->body); + } + if (e->shape != NULL) + { + cpSpaceRemoveShape(gs->space, e->shape); + cpShapeFree(e->shape); + } + e->body = NULL; + e->shape = NULL; + + Entity *front_of_free_list = get_entity(gs, gs->free_list); + if (front_of_free_list != NULL) + assert(!front_of_free_list->exists); + int gen = e->generation; + *e = (Entity){0}; + e->generation = gen; + e->next_free_entity = gs->free_list; + gs->free_list = get_id(gs, e); } -// space should be from gamestate, doesn't accept gamestate parameter so collision -// callbacks can use it -void grid_destroy(cpSpace *space, struct Grid *grid) +Entity *new_entity(struct GameState *gs) { - for (int i = 0; i < MAX_BOXES_PER_GRID; i++) + Entity *to_return = NULL; + if (get_entity(gs, gs->free_list) != NULL) { - SKIPNULL(grid->boxes[i].shape); + to_return = get_entity(gs, gs->free_list); + assert(!to_return->exists); + gs->free_list = to_return->next_free_entity; + } + else + { + assert(gs->cur_next_entity < gs->max_entities); // too many entities if fails + to_return = &gs->entities[gs->cur_next_entity]; + gs->cur_next_entity++; + } - box_destroy(space, &grid->boxes[i]); + to_return->generation++; + to_return->exists = true; + return to_return; +} + +void create_body(struct GameState *gs, Entity *e) +{ + assert(gs->space != NULL); + + if (e->body != NULL) + { + cpSpaceRemoveBody(gs->space, e->body); + cpBodyFree(e->body); + e->body = NULL; } - cpSpaceRemoveBody(space, grid->body); - cpBodyFree(grid->body); - grid->body = NULL; + cpBody *body = cpSpaceAddBody(gs->space, cpBodyNew(0.0, 0.0)); // zeros for mass/moment of inertia means automatically calculated from its collision shapes + e->body = body; + cpBodySetUserData(e->body, (void *)e); } -// removes boxe from grid, then ensures that the rule that grids must not have -// holes in them is applied. -// uses these forward declared serialization functions to duplicate a box -typedef struct SerState +void grid_create(struct GameState *gs, Entity *e) { - char *bytes; - bool serializing; - int cursor; // points to next available byte, is the size of current message after serializing something - int max_size; -} SerState; -void ser_box(SerState *ser, struct Box *var, struct GameState *gs, struct Grid *g); -static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) + e->is_grid = true; + create_body(gs, e); +} + +void entity_set_pos(Entity *e, V2 pos) +{ + assert(e->is_grid); + assert(e->body != NULL); + cpBodySetPosition(e->body, v2_to_cp(pos)); +} + +// size is (1/2 the width, 1/2 the height) +void create_rectangle_shape(GameState *gs, Entity *e, Entity *parent, V2 pos, V2 size, float mass) +{ + if (e->shape != NULL) + { + cpSpaceRemoveShape(gs->space, e->shape); + cpShapeFree(e->shape); + e->shape = NULL; + } + + cpBB box = cpBBNew(-size.x + pos.x, -size.y + pos.y, size.x + pos.x, size.y + pos.y); + cpVect verts[4] = { + cpv(box.r, box.b), + cpv(box.r, box.t), + cpv(box.l, box.t), + cpv(box.l, box.b), + }; + + e->shape_size = size; + e->shape_parent_entity = get_id(gs, parent); + e->shape = (cpShape *)cpPolyShapeInitRaw(cpPolyShapeAlloc(), parent->body, 4, verts, 0.0f); // this cast is done in chipmunk, not sure why it works + cpShapeSetUserData(e->shape, (void *)e); + cpShapeSetMass(e->shape, mass); + cpSpaceAddShape(gs->space, e->shape); +} + +void create_player(struct GameState *gs, Entity *e) +{ + e->is_player = true; + create_body(gs, e); + create_rectangle_shape(gs, e, e, (V2){0}, V2scale(PLAYER_SIZE, 0.5f), PLAYER_MASS); + cpShapeSetFilter(e->shape, cpShapeFilterNew(CP_NO_GROUP, PLAYERS, CP_ALL_CATEGORIES)); +} + +// 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) +{ + new_box->is_box = true; + assert(gs->space != NULL); + assert(grid->is_grid); + + float halfbox = BOX_SIZE / 2.0f; + + create_rectangle_shape(gs, new_box, grid, pos, (V2){halfbox, halfbox}, 1.0f); + + cpShapeSetFilter(new_box->shape, cpShapeFilterNew(CP_NO_GROUP, BOXES, CP_ALL_CATEGORIES)); + + new_box->next_box = get_id(gs, get_entity(gs, grid->boxes)); + new_box->prev_box = get_id(gs, grid); + if (get_entity(gs, new_box->next_box) != NULL) + { + get_entity(gs, new_box->next_box)->prev_box = get_id(gs, new_box); + } + grid->boxes = get_id(gs, new_box); +} + +// 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) { - box_destroy(space, box); + assert(grid->is_grid); + assert(box->is_box); + entity_destroy(gs, box); - struct GameState *gs = (struct GameState *)cpSpaceGetUserData(space); - int num_boxes = grid_num_boxes(grid); + int num_boxes = grid_num_boxes(gs, grid); if (num_boxes == 0) { - grid_destroy(space, grid); + entity_destroy(gs, grid); return; } if (num_boxes == 1) @@ -120,68 +291,45 @@ static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) // The other "real grids" are allocated as new grids #define MAX_SEPARATE_GRIDS 8 - struct Box *separate_grids[MAX_SEPARATE_GRIDS][MAX_BOXES_PER_GRID] = {0}; + EntityID separate_grids[MAX_SEPARATE_GRIDS] = {0}; int cur_separate_grid_index = 0; + int cur_separate_grid_size = 0; int processed_boxes = 0; - struct Box **biggest_separate_grid = separate_grids[0]; + int biggest_separate_grid_index = 0; int biggest_separate_grid_length = 0; // process all boxes into separate, but correctly connected, grids while (processed_boxes < num_boxes) { // grab an unprocessed box, one not in separate_grids, to start the flood fill - struct Box *unprocessed = NULL; - for (int i = 0; i < MAX_BOXES_PER_GRID; i++) - { - SKIPNULL(grid->boxes[i].shape); - struct Box *cur = &grid->boxes[i]; - bool cur_has_been_processed = false; - for (int sep_i = 0; sep_i < MAX_SEPARATE_GRIDS; sep_i++) - { - for (int sep_box_i = 0; sep_box_i < MAX_BOXES_PER_GRID; sep_box_i++) - { - if (cur == separate_grids[sep_i][sep_box_i]) - { - cur_has_been_processed = true; - break; - } - } - if (cur_has_been_processed) - break; - } - if (!cur_has_been_processed) - { - unprocessed = cur; - break; - } - } + Entity *unprocessed = get_entity(gs, grid->boxes); assert(unprocessed != NULL); + assert(unprocessed->is_box); + box_remove_from_boxes(gs, unprocessed); // no longer in the boxes list of the grid - // flood fill from this unprocessed box, adding each result to a new separate grid + // flood fill from this unprocessed box, adding each result to cur_separate_grid_index, + // removing each block from the grid // https://en.wikipedia.org/wiki/Flood_fill - struct Box **cur_separate_grid = separate_grids[cur_separate_grid_index]; - cur_separate_grid_index++; - int separate_grid_i = 0; { // queue stuff @Robust use factored datastructure - struct Box *Q[MAX_BOXES_PER_GRID] = {0}; - int Q_i = 0; - - Q[Q_i] = unprocessed; - Q_i++; - struct Box *N = NULL; - while (Q_i > 0) + EntityID Q = get_id(gs, unprocessed); + Entity *N = NULL; + while (true) { - N = Q[Q_i - 1]; - Q_i--; + assert(!was_entity_deleted(gs, Q)); + N = get_entity(gs, Q); + if (N == NULL) // must mean that the queue is empty + break; + Q = N->next_box; if (true) // if node "inside", this is always true { - cur_separate_grid[separate_grid_i] = N; - separate_grid_i++; + N->next_box = separate_grids[cur_separate_grid_index]; + separate_grids[cur_separate_grid_index] = get_id(gs, N); + cur_separate_grid_size++; processed_boxes++; - V2 cur_local_pos = box_local_pos(N); + V2 cur_local_pos = entity_shape_pos(N); const V2 dirs[] = { (V2){.x = -1.0f, .y = 0.0f}, (V2){.x = 1.0f, .y = 0.0f}, @@ -193,49 +341,39 @@ static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) for (int ii = 0; ii < num_dirs; ii++) { V2 dir = dirs[ii]; - // @Robust faster method, not O(N^2), of getting the box + // @Robust @Speed faster method, not O(N^2), of getting the box // in the direction currently needed V2 wanted_local_pos = V2add(cur_local_pos, V2scale(dir, BOX_SIZE)); - struct Box *box_in_direction = NULL; - for (int iii = 0; iii < MAX_BOXES_PER_GRID; iii++) + EntityID box_in_direction = (EntityID){0}; + BOXES_ITER(gs, cur, grid) { - SKIPNULL(grid->boxes[iii].shape); - if (V2cmp(box_local_pos(&grid->boxes[iii]), wanted_local_pos, 0.01f)) + if (V2cmp(entity_shape_pos(cur), wanted_local_pos, 0.01f)) { - box_in_direction = &grid->boxes[iii]; + box_in_direction = get_id(gs, cur); break; } } - if (box_in_direction != NULL) + + Entity *newbox = get_entity(gs, box_in_direction); + if (newbox != NULL) { - // make sure not already added to the separate grid - bool already_in_separate_grid = false; - for (int sepgrid_i = 0; sepgrid_i < MAX_BOXES_PER_GRID; sepgrid_i++) - { - if (cur_separate_grid[sepgrid_i] == NULL) - break; // assumed to be end of the current separate grid list - if (cur_separate_grid[sepgrid_i] == box_in_direction) - { - already_in_separate_grid = true; - break; - } - } - if (!already_in_separate_grid) - { - Q[Q_i] = box_in_direction; - Q_i++; - } + box_remove_from_boxes(gs, newbox); + newbox->next_box = Q; + Q = box_in_direction; } } } } } - if (separate_grid_i > biggest_separate_grid_length) + if (cur_separate_grid_size > biggest_separate_grid_length) { - biggest_separate_grid_length = separate_grid_i; - biggest_separate_grid = cur_separate_grid; + biggest_separate_grid_length = cur_separate_grid_size; + biggest_separate_grid_index = cur_separate_grid_index; } + cur_separate_grid_index++; + assert(cur_separate_grid_index < MAX_SEPARATE_GRIDS); + cur_separate_grid_size = 0; } // create new grids for all lists of boxes except for the biggest one. @@ -243,56 +381,45 @@ static void grid_remove_box(cpSpace *space, struct Grid *grid, struct Box *box) // which are no longer connected for (int sepgrid_i = 0; sepgrid_i < MAX_SEPARATE_GRIDS; sepgrid_i++) { - if (separate_grids[sepgrid_i] == biggest_separate_grid) - continue; // leave the boxes of the biggest separate untouched - struct Box **cur_separate_grid = separate_grids[sepgrid_i]; - int cur_sepgrid_i = 0; - if (cur_separate_grid[cur_sepgrid_i] == NULL) + EntityID cur_separate_grid = separate_grids[sepgrid_i]; + if (get_entity(gs, cur_separate_grid) == NULL) continue; // this separate grid is empty + + Entity *new_grid; + if (sepgrid_i == biggest_separate_grid_index) + { + new_grid = grid; + } + else + { + new_grid = new_entity(gs); + grid_create(gs, new_grid); + cpBodySetPosition(new_grid->body, cpBodyGetPosition(grid->body)); + cpBodySetAngle(new_grid->body, cpBodyGetAngle(grid->body)); + } - struct Grid *new_grid = find_empty_grid(gs); - grid_new(new_grid, gs, grid_pos(grid)); // all grids have same pos but different center of mass (com) - cpBodySetAngle(new_grid->body, grid_rotation(grid)); - - int new_grid_box_i = 0; - while (cur_separate_grid[cur_sepgrid_i] != NULL) + Entity *cur = get_entity(gs, cur_separate_grid); + while (cur != NULL) { - char box_bytes[128]; - - char *cur = box_bytes; - // duplicate the box by serializing it then deserializing it - SerState ser = (SerState){ - .bytes = cur, - .cursor = 0, - .max_size = 128, - .serializing = true, - }; - ser_box(&ser, cur_separate_grid[cur_sepgrid_i], gs, grid); - ser.cursor = 0; - ser.serializing = false; - ser_box(&ser, &new_grid->boxes[new_grid_box_i], gs, new_grid); - - cur_sepgrid_i++; - new_grid_box_i++; + Entity *next = get_entity(gs, cur->next_box); + box_create(gs, cur, new_grid, entity_shape_pos(cur)); // destroys next/prev fields on cur + cur = next; } cpBodySetVelocity(new_grid->body, cpBodyGetVelocityAtWorldPoint(grid->body, v2_to_cp(grid_com(new_grid)))); cpBodySetAngularVelocity(new_grid->body, grid_angular_velocity(grid)); - cur_sepgrid_i = 0; - while (cur_separate_grid[cur_sepgrid_i] != NULL) - { - box_destroy(space, cur_separate_grid[cur_sepgrid_i]); - cur_sepgrid_i++; - } } } static void postStepRemove(cpSpace *space, void *key, void *data) { cpShape *b = (cpShape *)key; - if (getbox(b)->damage > 1.0f) + 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) { - grid_remove_box(space, (struct Grid *)cpBodyGetUserData(cpShapeGetBody(b)), getbox(b)); + if (e->is_box) + grid_remove_box(cp_space_gs(space), cp_body_entity(cpShapeGetBody(b)), e); } } @@ -301,12 +428,16 @@ static cpBool on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) cpShape *a, *b; cpArbiterGetShapes(arb, &a, &b); + Entity *entity_a, *entity_b; + entity_a = cp_shape_entity(a); + entity_b = cp_shape_entity(b); + float damage = V2length(cp_to_v2(cpArbiterTotalImpulse(arb))) * 0.25f; if (damage > 0.05f) { // Log("Collision with damage %f\n", damage); - getbox(a)->damage += damage; - getbox(b)->damage += damage; + entity_a->damage += damage; + entity_b->damage += damage; } // b must be the key passed into the post step removed, the key is cast into its shape @@ -316,94 +447,54 @@ static cpBool on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) return true; // keep colliding } -void initialize(struct GameState *gs) +void initialize(struct GameState *gs, void *entity_arena, int entity_arena_size) { + *gs = (struct 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->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->beginFunc = begin; handler->postSolveFunc = on_damage; - // handler->postSolveFunc = postStepRemove; - for (int i = 0; i < MAX_PLAYERS; i++) - { - reset_player(&gs->players[i]); - } } void destroy(struct GameState *gs) { - for (int i = 0; i < MAX_GRIDS; i++) + // can't zero out gs data because the entity memory arena is reused + // on deserialization + for (int i = 0; i < gs->max_entities; i++) { - SKIPNULL(gs->grids[i].body); - grid_destroy(gs->space, &gs->grids[i]); + if (gs->entities[i].exists) + entity_destroy(gs, &gs->entities[i]); } - cpSpaceFree(gs->space); gs->space = NULL; + gs->cur_next_entity = 0; } - -void reset_player(struct Player *p) -{ - *p = (struct Player){0}; - p->currently_inhabiting_index = -1; -} - -// 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 -void box_new(struct Box *to_modify, struct GameState *gs, struct Grid *grid, V2 pos) -{ - *to_modify = (struct Box){0}; - float halfbox = BOX_SIZE / 2.0f; - cpBB box = cpBBNew(-halfbox + pos.x, -halfbox + pos.y, halfbox + pos.x, halfbox + pos.y); - cpVect verts[4] = { - cpv(box.r, box.b), - cpv(box.r, box.t), - cpv(box.l, box.t), - cpv(box.l, box.b), - }; - - to_modify->shape = (cpShape *)cpPolyShapeInitRaw(cpPolyShapeAlloc(), grid->body, 4, verts, 0.0f); // this cast is done in chipmunk, not sure why it works - - // assumed to be grid in inhabit code as well - cpShapeSetUserData(to_modify->shape, (void *)to_modify); - cpShapeSetMass(to_modify->shape, BOX_MASS); - cpSpaceAddShape(gs->space, to_modify->shape); -} - -// the grid pointer passed gets referenced by the body -void grid_new(struct Grid *to_modify, struct GameState *gs, V2 pos) -{ - assert(gs->space != NULL); - float halfbox = BOX_SIZE / 2.0f; - - cpBody *body = cpSpaceAddBody(gs->space, cpBodyNew(0.0, 0.0)); // zeros for mass/moment of inertia means automatically calculated from its collision shapes - to_modify->body = body; - cpBodySetPosition(body, v2_to_cp(pos)); - cpBodySetUserData(to_modify->body, (void *)to_modify); -} - // center of mass, not the literal position -V2 grid_com(struct Grid *grid) +V2 grid_com(Entity *grid) { return cp_to_v2(cpBodyLocalToWorld(grid->body, cpBodyGetCenterOfGravity(grid->body))); } -V2 grid_pos(struct Grid *grid) + +V2 entity_pos(Entity *grid) { return cp_to_v2(cpBodyGetPosition(grid->body)); } -V2 grid_vel(struct Grid *grid) +V2 grid_vel(Entity *grid) { return cp_to_v2(cpBodyGetVelocity(grid->body)); } -V2 grid_world_to_local(struct Grid *grid, V2 world) +V2 grid_world_to_local(Entity *grid, V2 world) { return cp_to_v2(cpBodyWorldToLocal(grid->body, v2_to_cp(world))); } -V2 grid_local_to_world(struct Grid *grid, V2 local) +V2 grid_local_to_world(Entity *grid, V2 local) { return cp_to_v2(cpBodyLocalToWorld(grid->body, v2_to_cp(local))); } // returned snapped position is in world coordinates -V2 grid_snapped_box_pos(struct Grid *grid, V2 world) +V2 grid_snapped_box_pos(Entity *grid, V2 world) { V2 local = grid_world_to_local(grid, world); local.x /= BOX_SIZE; @@ -415,37 +506,73 @@ V2 grid_snapped_box_pos(struct Grid *grid, V2 world) return cp_to_v2(cpBodyLocalToWorld(grid->body, v2_to_cp(local))); } -float grid_rotation(struct Grid *grid) +float entity_rotation(Entity *grid) { return cpBodyGetAngle(grid->body); } -float grid_angular_velocity(struct Grid *grid) +float grid_angular_velocity(Entity *grid) { return cpBodyGetAngularVelocity(grid->body); } -struct Grid *box_grid(struct Box *box) +Entity *box_grid(Entity *box) { - return (struct Grid *)cpBodyGetUserData(cpShapeGetBody(box->shape)); + return (Entity *)cpBodyGetUserData(cpShapeGetBody(box->shape)); } -V2 box_local_pos(struct Box *box) +// in local space +V2 entity_shape_pos(Entity *box) { return cp_to_v2(cpShapeGetCenterOfGravity(box->shape)); } -V2 box_pos(struct Box *box) +float entity_shape_mass(Entity *box) +{ + assert(box->shape != NULL); + return cpShapeGetMass(box->shape); +} +V2 box_pos(Entity *box) { - return V2add(grid_pos(box_grid(box)), V2rotate(box_local_pos(box), grid_rotation(box_grid(box)))); + assert(box->is_box); + return V2add(entity_pos(box_grid(box)), V2rotate(entity_shape_pos(box), entity_rotation(box_grid(box)))); } -float box_rotation(struct Box *box) +float box_rotation(Entity *box) { return cpBodyGetAngle(cpShapeGetBody(box->shape)); } -#define WRITE_VARNAMES false // good for debugging -#include -// assumes SerState *var defined -#define SER_VAR(var_pointer) \ +struct BodyData +{ + V2 pos; + V2 vel; + float rotation; + float angular_velocity; +}; + +void populate(cpBody *body, struct BodyData *data) +{ + data->pos = cp_to_v2(cpBodyGetPosition(body)); + data->vel = cp_to_v2(cpBodyGetVelocity(body)); + data->rotation = (float)cpBodyGetAngle(body); + data->angular_velocity = (float)cpBodyGetAngularVelocity(body); +} + +void update_from(cpBody *body, struct BodyData *data) +{ + cpBodySetPosition(body, v2_to_cp(data->pos)); + cpBodySetVelocity(body, v2_to_cp(data->vel)); + cpBodySetAngle(body, data->rotation); + cpBodySetAngularVelocity(body, data->angular_velocity); +} + +#define WRITE_VARNAMES true // debugging feature horrible for network +typedef struct SerState +{ + char *bytes; + bool serializing; + int cursor; // points to next available byte, is the size of current message after serializing something + int max_size; +} SerState; +#define SER_VAR_NAME(var_pointer, name) \ { \ - const char *var_name = #var_pointer; \ + const char *var_name = name; \ size_t var_name_len = 0; \ if (WRITE_VARNAMES) \ { \ @@ -491,6 +618,7 @@ float box_rotation(struct Box *box) } \ } \ } +#define SER_VAR(var_pointer) SER_VAR_NAME(var_pointer, #var_pointer) void ser_V2(SerState *ser, V2 *var) { @@ -498,97 +626,116 @@ void ser_V2(SerState *ser, V2 *var) SER_VAR(&var->y); } -void ser_box(SerState *ser, struct Box *var, struct GameState *gs, struct Grid *g) +void ser_bodydata(SerState *ser, struct BodyData *data) { + ser_V2(ser, &data->pos); + ser_V2(ser, &data->vel); + SER_VAR(&data->rotation); + SER_VAR(&data->angular_velocity); +} + +void ser_entityid(SerState *ser, EntityID *id) +{ + SER_VAR(&id->generation); + SER_VAR(&id->index); +} + +void ser_inputframe(SerState *ser, struct InputFrame *i) +{ + SER_VAR(&i->movement); + SER_VAR(&i->inhabit); + SER_VAR(&i->build); + 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) +{ + SER_VAR(&p->connected); + if (p->connected) { - V2 pos; - if (ser->serializing) - { - pos = cp_to_v2(cpShapeGetCenterOfGravity(var->shape)); - } - ser_V2(ser, &pos); - if (!ser->serializing) - { - box_new(var, gs, g, pos); - } + ser_entityid(ser, &p->entity); + ser_inputframe(ser, &p->input); } - - SER_VAR(&var->type); // @Rovarust separate enum serialization that checks for out of varounds enum - SER_VAR(&var->compass_rotation); - SER_VAR(&var->thrust); - SER_VAR(&var->energy_used); - SER_VAR(&var->damage); } -void ser_grid(SerState *ser, struct GameState *gs, struct Grid *g) +void ser_entity(SerState *ser, struct GameState *gs, Entity *e) { - if (ser->serializing) - assert(g->body != NULL); - else - assert(g->body == NULL); + SER_VAR(&e->generation); + SER_VAR(&e->damage); + + bool has_body = ser->serializing && e->body != NULL; + SER_VAR(&has_body); + if (has_body) { - V2 pos = {0}; - V2 vel = {0}; - float rot = 0.0f; - float angular_vel = 0.0f; + struct BodyData body_data; if (ser->serializing) - { - pos = grid_pos(g); - vel = grid_vel(g); - rot = grid_rotation(g); - angular_vel = grid_angular_velocity(g); - } - SER_VAR(&pos); - SER_VAR(&vel); - SER_VAR(&rot); - SER_VAR(&angular_vel); + populate(e->body, &body_data); + ser_bodydata(ser, &body_data); if (!ser->serializing) { - grid_new(g, gs, pos); - cpBodySetVelocity(g->body, v2_to_cp(vel)); - cpBodySetAngle(g->body, rot); - cpBodySetAngularVelocity(g->body, angular_vel); + create_body(gs, e); + update_from(e->body, &body_data); } } - SER_VAR(&g->total_energy_capacity); + bool has_shape = ser->serializing && e->shape != NULL; + SER_VAR(&has_shape); - for (int i = 0; i < MAX_BOXES_PER_GRID; i++) + if (has_shape) { - bool exists; + SER_VAR(&e->shape_size); + ser_entityid(ser, &e->shape_parent_entity); + + + V2 shape_pos; if (ser->serializing) - exists = g->boxes[i].shape != NULL; - SER_VAR(&exists); - if (exists) + shape_pos = entity_shape_pos(e); + SER_VAR(&shape_pos); + + float shape_mass; + if (ser->serializing) + shape_mass = entity_shape_mass(e); + SER_VAR(&shape_mass) + + Entity * parent = get_entity(gs, e->shape_parent_entity); + if(parent == NULL) { - ser_box(ser, &g->boxes[i], gs, g); + printf("Null shape parent\n"); + } + if (!ser->serializing) + { + create_rectangle_shape(gs, e, parent , shape_pos, e->shape_size, shape_mass); } } -} -void ser_inputframe(SerState *ser, struct InputFrame *i) -{ - SER_VAR(&i->movement); - SER_VAR(&i->inhabit); - SER_VAR(&i->build); - SER_VAR(&i->dobuild); - SER_VAR(&i->build_type); - SER_VAR(&i->build_rotation); - SER_VAR(&i->grid_index); -} + SER_VAR(&e->is_player); + if (e->is_player) + { + ser_entityid(ser, &e->currently_piloting_seat); + SER_VAR(&e->spice_taken_away); + SER_VAR(&e->goldness); + } -void ser_player(SerState *ser, struct Player *p) -{ - SER_VAR(&p->connected); - if (p->connected) + SER_VAR(&e->is_grid); + if (e->is_grid) { - SER_VAR(&p->currently_inhabiting_index); - ser_V2(ser, &p->pos); - ser_V2(ser, &p->vel); - SER_VAR(&p->spice_taken_away); - SER_VAR(&p->goldness); - ser_inputframe(ser, &p->input); + SER_VAR(&e->total_energy_capacity); + ser_entityid(ser, &e->boxes); + } + + SER_VAR(&e->is_box); + if (e->is_box) + { + SER_VAR(&e->box_type); + ser_entityid(ser, &e->next_box); + ser_entityid(ser, &e->prev_box); + SER_VAR(&e->compass_rotation); + SER_VAR(&e->thrust); + SER_VAR(&e->energy_used); } } @@ -596,14 +743,20 @@ void ser_server_to_client(SerState *ser, ServerToClient *s) { struct GameState *gs = s->cur_gs; + int cur_next_entity = 0; + if (ser->serializing) + cur_next_entity = gs->cur_next_entity; + SER_VAR(&cur_next_entity); + if (!ser->serializing) { destroy(gs); - initialize(gs); + memset((void *)gs->entities, 0, sizeof(*gs->entities) * gs->max_entities); + initialize(gs, gs->entities, gs->max_entities * sizeof(*gs->entities)); + gs->cur_next_entity = cur_next_entity; } SER_VAR(&s->your_player); - SER_VAR(&gs->tick); SER_VAR(&gs->time); ser_V2(ser, &gs->goldpos); @@ -613,15 +766,58 @@ void ser_server_to_client(SerState *ser, ServerToClient *s) ser_player(ser, &gs->players[i]); } - for (int i = 0; i < MAX_GRIDS; i++) + if (ser->serializing) { - bool exists; - if (ser->serializing) - exists = gs->grids[i].body != NULL; - SER_VAR(&exists); - if (exists) + for (int i = 0; i < gs->cur_next_entity; i++) { - ser_grid(ser, gs, &gs->grids[i]); + Entity *e = &gs->entities[i]; + if (e->exists) + { + if(e->is_player) + { + SER_VAR(&i); + ser_entity(ser, gs, e); + } + else if(e->is_grid) + { + // 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(&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"); + ser_entity(ser, gs, cur); + } + } + } + } + int end_of_entities = -1; + SER_VAR_NAME(&end_of_entities, "&i"); + } + else + { + while (true) + { + int next_index; + SER_VAR_NAME(&next_index, "&i"); + if (next_index == -1) + break; + 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); + } + for (int i = 0; i < gs->cur_next_entity; i++) + { + Entity *e = &gs->entities[i]; + if (!e->exists) + { + e->next_free_entity = gs->free_list; + gs->free_list = get_id(gs, e); + } } } } @@ -663,6 +859,8 @@ static float closest_to_point_in_radius_result_largest_dist = 0.0f; static void closest_point_callback_func(cpShape *shape, cpContactPointSet *points, void *data) { assert(points->count == 1); + if (!cp_shape_entity(shape)->is_box) + return; float dist = V2length(cp_to_v2(cpvsub(points->points[0].pointA, points->points[0].pointB))); // float dist = -points->points[0].distance; if (dist > closest_to_point_in_radius_result_largest_dist) @@ -672,7 +870,7 @@ static void closest_point_callback_func(cpShape *shape, cpContactPointSet *point } } -struct Grid *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) { closest_to_point_in_radius_result = NULL; closest_to_point_in_radius_result_largest_dist = 0.0f; @@ -687,15 +885,15 @@ struct Grid *closest_to_point_in_radius(struct GameState *gs, V2 point, float ra if (closest_to_point_in_radius_result != NULL) { // @Robust query here for only boxes that are part of ships, could get nasty... - return (struct Grid *)cpBodyGetUserData(cpShapeGetBody(closest_to_point_in_radius_result)); + return cp_body_entity(cpShapeGetBody(closest_to_point_in_radius_result)); } return NULL; } -V2 thruster_direction(struct Box *box) +V2 thruster_direction(Entity *box) { - assert(box->type == BoxThruster); + assert(box->is_box && box->box_type == BoxThruster); V2 to_return = (V2){.x = 1.0f, .y = 0.0f}; to_return = V2rotate(to_return, rotangle(box->compass_rotation)); @@ -704,7 +902,7 @@ V2 thruster_direction(struct Box *box) return to_return; } -V2 thruster_force(struct Box *box) +V2 thruster_force(Entity *box) { return V2scale(thruster_direction(box), -box->thrust * THRUSTER_FORCE); } @@ -719,29 +917,38 @@ void process(struct GameState *gs, float dt) assert(gs->space != NULL); assert(dt == TIMESTEP); // @TODO fix tick being incremented every time - gs->tick += 1; gs->time += dt; // process input for (int i = 0; i < MAX_PLAYERS; i++) { - struct Player *p = &gs->players[i]; - if (!p->connected) + struct Player *player = &gs->players[i]; + if (!player->connected) continue; + Entity *p = get_entity(gs, player->entity); + if (p == NULL) + { + p = new_entity(gs); + create_player(gs, p); + player->entity = get_id(gs, p); + } + assert(p->is_player); // update gold win condition - if (V2length(V2sub(p->pos, gs->goldpos)) < GOLD_COLLECT_RADIUS) + if (V2length(V2sub(cp_to_v2(cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS) { p->goldness += 0.1; p->spice_taken_away = 0.0f; gs->goldpos = (V2){.x = hash11(gs->time) * 20.0f, .y = hash11(gs->time - 13.6f) * 20.0f}; } - if (gs->grids[p->currently_inhabiting_index].body == NULL) + if (get_entity(gs, p->currently_piloting_seat) == NULL) { - p->currently_inhabiting_index = -1; + p->currently_piloting_seat = (EntityID){0}; } +// @Todo do getting inside pilot seat +#if 0 if (p->input.inhabit) { p->input.inhabit = false; // "handle" the input @@ -796,21 +1003,20 @@ void process(struct GameState *gs, float dt) p->currently_inhabiting_index = -1; } } +#endif // process movement { // no cheating by making movement bigger than length 1 - if (V2length(p->input.movement) != 0.0f) + float movement_strength = V2length(player->input.movement); + if (movement_strength != 0.0f) { - p->input.movement = V2scale(V2normalize(p->input.movement), clamp(V2length(p->input.movement), 0.0f, 1.0f)); + player->input.movement = V2scale(V2normalize(player->input.movement), clamp(V2length(player->input.movement), 0.0f, 1.0f)); } - if (p->currently_inhabiting_index == -1) - { - // @Robust make sure movement vector is normalized so player can't cheat - p->vel = V2add(p->vel, V2scale(p->input.movement, dt * 0.5f)); - p->spice_taken_away += dt * 0.15f * V2length(p->input.movement); - } - else + 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 { struct Grid *g = &gs->grids[p->currently_inhabiting_index]; V2 target_new_pos = V2lerp(p->pos, grid_com(g), dt * 20.0f); @@ -846,70 +1052,64 @@ void process(struct GameState *gs, float dt) // 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 } - p->pos = V2add(p->pos, V2scale(p->vel, dt)); +#endif } - if (p->input.dobuild) +#if 1 // building + if (player->input.dobuild) { - p->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}; - // @Robust make sure to query only against boxes... - V2 world_build = p->input.build; - if (p->input.grid_index != -1) + V2 world_build = player->input.build; + + // @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(&gs->grids[p->input.grid_index], p->input.build); + 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, CP_ALL_CATEGORIES), &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) { - struct Box *cur_box = (struct Box *)cpShapeGetUserData(nearest); - struct Grid *cur_grid = (struct Grid *)cpBodyGetUserData(cpShapeGetBody(nearest)); - grid_remove_box(gs->space, cur_grid, cur_box); + Entity *cur_box = cp_shape_entity(nearest); + Entity *cur_grid = cp_body_entity(cpShapeGetBody(nearest)); + grid_remove_box(gs, cur_grid, cur_box); p->spice_taken_away -= 0.1f; } - else if (p->input.grid_index == -1) + else if (target_grid == NULL) { - struct Grid *empty_grid = find_empty_grid(gs); + Entity *new_grid = new_entity(gs); + grid_create(gs, new_grid); p->spice_taken_away += 0.2f; - grid_new(empty_grid, gs, world_build); - box_new(&empty_grid->boxes[0], gs, empty_grid, (V2){0}); - empty_grid->boxes[0].type = p->input.build_type; - empty_grid->boxes[0].compass_rotation = p->input.build_rotation; - cpBodySetVelocity(empty_grid->body, v2_to_cp(p->vel)); + entity_set_pos(new_grid, world_build); + + Entity *new_box = new_entity(gs); + 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)); } else { - struct Grid *g = &gs->grids[p->input.grid_index]; - - struct Box *empty_box = NULL; - for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) - { - if (g->boxes[ii].shape == NULL) - { - empty_box = &g->boxes[ii]; - break; - } - } - // @Robust cleanly fail when not enough boxes - assert(empty_box != NULL); - p->spice_taken_away += 0.1f; - box_new(empty_box, gs, g, grid_world_to_local(g, world_build)); - empty_box->type = p->input.build_type; - empty_box->compass_rotation = p->input.build_rotation; + Entity *new_box = new_entity(gs); + box_create(gs, new_box, target_grid, grid_world_to_local(target_grid, world_build)); + new_box->box_type = player->input.build_type; + new_box->compass_rotation = player->input.build_rotation; } } - +#endif if (p->spice_taken_away >= 1.0f) { - reset_player(p); - p->connected = true; + entity_destroy(gs, p); + player->entity = (EntityID){0}; } p->spice_taken_away = clamp01(p->spice_taken_away); } - // add thrust from thruster blocks +// @Todo add thrust from thruster blocks +#if 0 for (int i = 0; i < MAX_GRIDS; i++) { SKIPNULL(gs->grids[i].body); @@ -960,6 +1160,7 @@ void process(struct GameState *gs, float dt) gs->grids[i].total_energy_capacity += 1.0f - batteries[ii]->energy_used; } } +#endif cpSpaceStep(gs->space, dt); } \ No newline at end of file diff --git a/loaded/player.png b/loaded/player.png new file mode 100644 index 0000000..40132f0 Binary files /dev/null and b/loaded/player.png differ diff --git a/main.c b/main.c index 28b52ae..409fad6 100644 --- a/main.c +++ b/main.c @@ -42,6 +42,7 @@ static float zoom = 300.0f; static sg_image image_itemframe; static sg_image image_itemframe_selected; static sg_image image_thrusterburn; +static sg_image image_player; static int cur_editing_boxtype = -1; static int cur_editing_rotation = 0; @@ -110,7 +111,8 @@ 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 - initialize(&gs); + 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); @@ -137,6 +139,7 @@ static void init(void) 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"); } // socket initialization @@ -187,25 +190,8 @@ static void init(void) } } -// static void drawbox(V2 boxpos, float rot, float damage, enum BoxType type, enum Rotation rotation) -// { - -// sgp_push_transform(); -// sgp_rotate_at(rot, boxpos.x, boxpos.y); - -// sgp_set_image(0, boxinfo(type).image); -// sgp_rotate_at(rotangle(rotation), boxpos.x, boxpos.y); - -// sgp_reset_image(0); - -// if (damage > 0.0f) -// { -// sgp_set_color(0.5f, 0.1f, 0.1f, damage); - -// } - -// sgp_pop_transform(); -// } +#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) { @@ -214,10 +200,15 @@ static void draw_color_rect_centered(V2 center, float size) sgp_draw_filled_rect(center.x - halfbox, center.y - halfbox, size, size); } +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); +} + static void draw_texture_centered(V2 center, float size) { - float halfbox = size / 2.0f; - sgp_draw_textured_rect(center.x - halfbox, center.y - halfbox, BOX_SIZE, BOX_SIZE); + draw_texture_rectangle_centered(center, (V2){size, size}); } static void draw_circle(V2 point, float radius) @@ -236,22 +227,45 @@ static void draw_circle(V2 point, float radius) sgp_draw_lines(lines, POINTS); } -static void ui(bool draw, float width, float height) +static Entity *myentity() +{ + if (myplayer == -1) + return NULL; + Entity *to_return = get_entity(&gs, gs.players[myplayer].entity); + if (to_return != NULL) + assert(to_return->is_player); + return to_return; +} + +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) + { + return; + } + // draw spice bar - if (draw && myplayer != -1) + if (draw) { - sgp_set_color(0.5f, 0.5f, 0.5f, 1.0f); + static float spice_taken_away = 0.5f; + + 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 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, 1.0f); - sgp_draw_filled_rect(margin, 80.0f, bar_width * (1.0f - gs.players[myplayer].spice_taken_away), 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); } // 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; @@ -279,7 +293,7 @@ static void ui(bool draw, float width, float height) } if (draw) { - sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); + sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity); if (cur_editing_boxtype == i) { sgp_set_image(0, image_itemframe_selected); @@ -366,10 +380,10 @@ static void frame(void) } // gameplay - ui(false, width, height); // handle events + ui(false, dt, width, height); // handle events V2 build_target_pos = {0}; float build_target_rotation = 0.0f; - V2 camera_pos = {0}; + static V2 camera_pos = {0}; // keeps camera at same position after player death V2 world_mouse_pos = mouse_pos; struct BuildPreviewInfo { @@ -384,9 +398,10 @@ static void frame(void) // calculate world position and camera { - if (myplayer != -1) + if (myentity() != NULL) { - camera_pos = gs.players[myplayer].pos; + + camera_pos = entity_pos(myentity()); } world_mouse_pos = V2sub(world_mouse_pos, (V2){.x = width / 2.0f, .y = height / 2.0f}); world_mouse_pos.x /= zoom; @@ -395,10 +410,10 @@ static void frame(void) } // calculate build preview stuff - int grid_index = -1; - if (myplayer != -1) + EntityID grid_to_build_on = (EntityID){0}; + if (myentity() != NULL) { - V2 hand_pos = V2sub(world_mouse_pos, gs.players[myplayer].pos); + V2 hand_pos = V2sub(world_mouse_pos, entity_pos(myentity())); float hand_len = V2length(hand_pos); if (hand_len > MAX_HAND_REACH) { @@ -410,9 +425,9 @@ static void frame(void) hand_at_arms_length = false; } hand_pos = V2scale(V2normalize(hand_pos), hand_len); - hand_pos = V2add(hand_pos, gs.players[myplayer].pos); + hand_pos = V2add(hand_pos, entity_pos(myentity())); - struct Grid *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) { build_preview = (struct BuildPreviewInfo){ @@ -423,18 +438,11 @@ static void frame(void) } else { - for (int i = 0; i < MAX_GRIDS; i++) - { - if (&gs.grids[i] == placing_grid) - { - grid_index = i; - break; - } - } + 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 = grid_pos(placing_grid), - .grid_rotation = grid_rotation(placing_grid), + .grid_pos = entity_pos(placing_grid), + .grid_rotation = entity_rotation(placing_grid), .pos = pos}; } } @@ -455,12 +463,11 @@ static void frame(void) 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_index = grid_index; - if (grid_index != -1) + 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(&gs.grids[cur_input_frame.grid_index], build_preview.pos); - V2 untransformed = grid_local_to_world(&gs.grids[cur_input_frame.grid_index], cur_input_frame.build); - untransformed.x += 5.0f; + cur_input_frame.build = grid_world_to_local(grid, build_preview.pos); } else { @@ -474,14 +481,15 @@ static void frame(void) !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_index != latest.grid_index || + 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]; } - cur_input_frame.tick = gs.tick; + cur_input_frame.tick = tick(&gs); client_to_server.inputs[0] = cur_input_frame; } @@ -512,8 +520,7 @@ static void frame(void) // sokol drawing library draw in world space // world space coordinates are +Y up, -Y down. Like normal cartesian coords - { - sgp_push_transform(); + transform_scope { sgp_translate(width / 2, height / 2); sgp_scale_at(zoom, -zoom, 0.0f, 0.0f); @@ -521,17 +528,17 @@ static void frame(void) sgp_translate(-camera_pos.x, -camera_pos.y); // hand reached limit circle - if (myplayer != -1) + 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); sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha); - draw_circle(gs.players[myplayer].pos, MAX_HAND_REACH); + draw_circle(entity_pos(myentity()), MAX_HAND_REACH); } // stars sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); - const int num = 50; + const int num = 30; for (int x = -num; x < num; x++) { for (int y = -num; y < num; y++) @@ -554,30 +561,30 @@ static void frame(void) { sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2); - sgp_push_transform(); - - 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_reset_image(0); + transform_scope + { - sgp_pop_transform(); + 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_reset_image(0); + } } - // grids + for (int i = 0; i < gs.cur_next_entity; i++) { - for (int i = 0; i < MAX_GRIDS; i++) + Entity *e = &gs.entities[i]; + if (!e->exists) + continue; + if (e->is_grid) // grid drawing { - SKIPNULL(gs.grids[i].body); - struct Grid *g = &gs.grids[i]; - for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++) + Entity *g = e; + BOXES_ITER(&gs, b, g) { - SKIPNULL(g->boxes[ii].shape); - struct Box *b = &g->boxes[ii]; sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); - // debug draw force vectors for thrusters - if (false) +// debug draw force vectors for thrusters +#if 0 { if (b->type == BoxThruster) { @@ -585,7 +592,8 @@ static void frame(void) dbg_line(box_pos(b), V2add(box_pos(b), V2scale(thruster_force(b), -1.0f))); } } - if (b->type == BoxBattery) +#endif + if (b->box_type == BoxBattery) { float cur_alpha = sgp_get_color().a; Color from = WHITE; @@ -593,95 +601,105 @@ static void frame(void) Color result = Collerp(from, to, b->energy_used); sgp_set_color(result.r, result.g, result.b, cur_alpha); } - sgp_push_transform(); + transform_scope { - sgp_rotate_at(grid_rotation(g) + rotangle(b->compass_rotation), box_pos(b).x, box_pos(b).y); + sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), box_pos(b).x, box_pos(b).y); - if (b->type == BoxThruster) - { - sgp_push_transform(); - 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.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); + 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.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); + draw_texture_centered(box_pos(b), BOX_SIZE); + sgp_reset_image(0); + } + } + + sgp_set_image(0, boxinfo(b->box_type).image); draw_texture_centered(box_pos(b), BOX_SIZE); sgp_reset_image(0); - sgp_pop_transform(); - } - - sgp_set_image(0, boxinfo(b->type).image); - draw_texture_centered(box_pos(b), BOX_SIZE); - sgp_reset_image(0); - - sgp_set_color(0.5f, 0.1f, 0.1f, b->damage); - draw_color_rect_centered(box_pos(b), BOX_SIZE); - - sgp_pop_transform(); + sgp_set_color(0.5f, 0.1f, 0.1f, b->damage); + draw_color_rect_centered(box_pos(b), BOX_SIZE); + } } sgp_set_color(1.0f, 0.0f, 0.0f, 1.0f); - V2 vel = grid_vel(&gs.grids[i]); + V2 vel = grid_vel(g); 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) + { + 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_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++) + // 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) { - struct Player *p = &gs.players[i]; - if (!p->connected) - continue; - static float opacities[MAX_PLAYERS] = {1.0f}; - opacities[i] = lerp(opacities[i], p->currently_inhabiting_index == -1 ? 1.0f : 0.1f, dt * 7.0f); - Color col_to_draw = Collerp(WHITE, GOLD, p->goldness); - col_to_draw.a = opacities[i]; + positions[i] = entity_pos(p); + } - set_color(col_to_draw); - sgp_push_transform(); + set_color(col_to_draw); + transform_scope + { float psize = 0.1f; - sgp_draw_filled_rect(p->pos.x - psize / 2.0f, p->pos.y - psize / 2.0f, psize, psize); - sgp_pop_transform(); - // sgp_rotate_at(grid_rotation(p->grid), grid_pos(p->grid).x, grid_pos(p->grid).y); - // V2 bpos = grid_pos(p->grid); - // sgp_draw_filled_rect(grid_pos(p->grid).x - halfbox, grid_pos(p->grid).y - halfbox, BOX_SIZE, BOX_SIZE); - // sgp_pop_transform(); - - // sgp_set_color(1.0f, 0.0f, 0.0f, 1.0f); - // V2 vel = grid_vel(p->grid); - // V2 to = V2add(grid_pos(p->grid), vel); - // sgp_draw_line(grid_pos(p->grid).x, grid_pos(p->grid).y, to.x, to.y); + 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(); - - sgp_pop_transform(); } - // UI drawn in screen space - ui(true, width, height); + // gold target + set_color(GOLD); + sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); - sg_pass_action pass_action = {0}; - sg_begin_default_pass(&pass_action, width, height); - sgp_flush(); - sgp_end(); - sg_end_pass(); - sg_commit(); + sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); + dbg_drawall(); } + + // UI drawn in screen space + ui(true, dt, width, height); + + sg_pass_action pass_action = {0}; + sg_begin_default_pass(&pass_action, width, height); + sgp_flush(); + sgp_end(); + sg_end_pass(); + sg_commit(); } void cleanup(void) { + free(gs.entities); destroy(&gs); sgp_shutdown(); sg_shutdown(); diff --git a/server.c b/server.c index adf6be2..730bd76 100644 --- a/server.c +++ b/server.c @@ -3,6 +3,7 @@ #include #include #include // int64 printing +#include // started in a thread from host void server(void *data) @@ -12,48 +13,20 @@ void server(void *data) stm_setup(); struct GameState gs = {0}; - initialize(&gs); + size_t entities_size = (sizeof(Entity) * MAX_ENTITIES); + Entity *entity_data = malloc(entities_size); + initialize(&gs, entity_data, entities_size); + Log("Allocated %zu bytes for entities\n", entities_size); - // box haven + // one box policy if (true) { - grid_new(&gs.grids[0], &gs, (V2){.x = BOX_SIZE*3.0, .y = 0.0}); - box_new(&gs.grids[0].boxes[0], &gs, &gs.grids[0], (V2){0}); - box_new(&gs.grids[0].boxes[1], &gs, &gs.grids[0], (V2){0, BOX_SIZE}); - box_new(&gs.grids[0].boxes[2], &gs, &gs.grids[0], (V2){0, BOX_SIZE*2.0}); - gs.grids[0].boxes[2].type = BoxBattery; - - box_new(&gs.grids[0].boxes[3], &gs, &gs.grids[0], (V2){BOX_SIZE, BOX_SIZE*2.0}); - gs.grids[0].boxes[3].type = BoxThruster; - gs.grids[0].boxes[3].compass_rotation = Right; - - box_new(&gs.grids[0].boxes[4], &gs, &gs.grids[0], (V2){0, BOX_SIZE*3.0}); - gs.grids[0].boxes[4].type = BoxThruster; - gs.grids[0].boxes[4].compass_rotation = Up; - - - grid_new(&gs.grids[1], &gs, (V2){.x = -BOX_SIZE*1.5, .y = 0.0}); - box_new(&gs.grids[1].boxes[0], &gs, &gs.grids[1], (V2){0}); - - grid_new(&gs.grids[2], &gs, (V2){.x = -BOX_SIZE*1.5, .y = BOX_SIZE}); - box_new(&gs.grids[2].boxes[0], &gs, &gs.grids[2], (V2){0}); - } + Entity * grid = new_entity(&gs); + grid_create(&gs, grid); + entity_set_pos(grid, (V2){-BOX_SIZE*2, 0.0f}); + Entity * box = new_entity(&gs); + box_create(&gs, box, grid, (V2){0}); - // two boxes - if (false) - { - grid_new(&gs.grids[0], &gs, (V2){.x = 0.75f, .y = 0.0}); - box_new(&gs.grids[0].boxes[0], &gs, &gs.grids[0], (V2){0}); - - grid_new(&gs.grids[1], &gs, (V2){.x = -1.75f, .y = 0.0}); - box_new(&gs.grids[1].boxes[1], &gs, &gs.grids[1], (V2){1}); - } - - // one box policy - if (false) - { - grid_new(&gs.grids[0], &gs, (V2){.x = 0.75f, .y = 0.0}); - box_new(&gs.grids[0].boxes[0], &gs, &gs.grids[0], (V2){0}); } if (enet_initialize() != 0) @@ -126,11 +99,7 @@ void server(void *data) else { event.peer->data = (void *)player_slot; - reset_player(&gs.players[player_slot]); - // gs.players[player_slot].box = box_new(&gs, (V2){ - // .x = 0.0f, - // .y = 1.0f * (float)player_slot, - // }); + gs.players[player_slot] = (struct Player){0}; gs.players[player_slot].connected = true; } @@ -158,7 +127,7 @@ void server(void *data) { if (received.inputs[i].tick == 0) // empty input continue; - if(received.inputs[i].tick <= latest_tick) + if (received.inputs[i].tick <= latest_tick) continue; // don't reprocess inputs already processed struct InputFrame cur_input = received.inputs[i]; gs.players[player_slot].input.movement = cur_input.movement; @@ -172,7 +141,7 @@ void server(void *data) } if (cur_input.dobuild) { - gs.players[player_slot].input.grid_index = cur_input.grid_index; + 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; @@ -234,6 +203,7 @@ void server(void *data) } destroy(&gs); + free(entity_data); enet_host_destroy(server); enet_deinitialize(); } \ No newline at end of file diff --git a/tooling.ahk b/tooling.ahk index 6a991a1..a0c4a52 100644 --- a/tooling.ahk +++ b/tooling.ahk @@ -4,6 +4,9 @@ SendMode, Input SetBatchLines, -1 SetWorkingDir, %A_ScriptDir% +; Fuck windows for having this hardcoded +^Esc::return + ^b:: WinKill, Flight Sleep, 20 diff --git a/types.h b/types.h index a3fbfa6..bfe9e0d 100644 --- a/types.h +++ b/types.h @@ -1,12 +1,15 @@ #pragma once #define MAX_PLAYERS 4 +#define MAX_ENTITIES 1024 #define BOX_SIZE 0.25f +#define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE}) +#define PLAYER_MASS 0.5f +#define PLAYER_JETPACK_FORCE 1.0f +#define PLAYER_JETPACK_SPICE_PER_SECOND 0.1f #define MAX_HAND_REACH 1.0f #define GOLD_COLLECT_RADIUS 0.3f -#define MAX_GRIDS 32 #define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f -#define MAX_BOXES_PER_GRID 32 #define BOX_MASS 1.0f #define THRUSTER_FORCE 4.0f #define THRUSTER_ENERGY_USED_PER_SECOND 0.05f @@ -68,6 +71,7 @@ enum BoxType BoxHullpiece, BoxThruster, BoxBattery, + BoxCockpit, BoxLast, }; @@ -80,6 +84,13 @@ enum CompassRotation RotationLast, }; +// when generation is 0, invalid ID +typedef struct +{ + unsigned int generation; // if 0 then EntityID points to nothing, generation >= 1 + unsigned int index; // index into the entity arena +} EntityID; + struct InputFrame { uint64_t tick; @@ -91,15 +102,49 @@ struct InputFrame bool dobuild; enum BoxType build_type; enum CompassRotation build_rotation; - int grid_index; + EntityID grid_to_build_on; }; +typedef struct Entity +{ + bool exists; + EntityID next_free_entity; + unsigned int generation; + + float damage; // used by box and player + cpBody *body; // used by grid, player, and box + cpShape *shape; // must be a box so shape_size can be set appropriately, and serialized + + // for serializing the shape + EntityID shape_parent_entity; // can't be zero if shape is nonzero + V2 shape_size; + + // player + bool is_player; + EntityID currently_piloting_seat; + float spice_taken_away; // at 1.0, out of spice + float goldness; // how much the player is a winner + + // grids + bool is_grid; + float total_energy_capacity; + EntityID boxes; + + // boxes + bool is_box; + enum BoxType box_type; + EntityID next_box; + EntityID prev_box; // doubly linked so can remove in middle of chain + enum CompassRotation compass_rotation; + float thrust; // must be between 0 and 1 + float energy_used; // battery +} Entity; + // gotta update the serialization functions when this changes -struct GameState +typedef struct GameState { cpSpace *space; - uint64_t tick; double time; V2 goldpos; @@ -107,43 +152,19 @@ struct GameState struct Player { bool connected; - - int currently_inhabiting_index; // is equal to -1 when not inhabiting a grid - V2 pos; - V2 vel; - float spice_taken_away; // at 1.0, out of spice - float goldness; // how much the player is a winner - - // input - // @Cleanup make this a frameinput struct instead of copying over all the fields like this - + EntityID entity; struct InputFrame input; } players[MAX_PLAYERS]; - // if body or shape is null, then that grid/box has been freed - - // important that this memory does not move around, each box shape in it has a pointer to its grid struct, stored in the box's shapes user_data - struct Grid - { - cpBody *body; - float total_energy_capacity; // updated every frame by thruster logic - - struct Box - { - enum BoxType type; - enum CompassRotation compass_rotation; // @Robust rename to compass_rotation - - // thruster - float thrust; // must be between 0 and 1 - - // battery - float energy_used; // must be between 0 and 1 - - cpShape *shape; - float damage; - } boxes[MAX_BOXES_PER_GRID]; // @Robust this needs to be dynamically allocated, huge disparity in how many blocks a body can have... - } grids[MAX_GRIDS]; -}; + // Entity arena + // ent:ity pointers can't move around because of how the physics engine handles user data. + // if you really need this, potentially refactor to store entity IDs instead of pointers + // in the shapes and bodies of chipmunk. Would require editing the library I think + Entity *entities; + int max_entities; // maximum number of entities possible in the entities list + int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty + EntityID free_list; +} GameState; #define PI 3.14159f @@ -186,35 +207,44 @@ struct ClientToServer void server(void *data); // data parameter required from thread api... // gamestate -void initialize(struct GameState *gs); // must do this to place boxes into it and process +void initialize(struct GameState *gs, void *entity_arena, int entity_arena_size); void destroy(struct GameState *gs); void process(struct GameState *gs, float dt); // does in place -struct Grid *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); void into_bytes(struct ServerToClient *gs, char *out_bytes, int *out_len, int max_len); void from_bytes(struct ServerToClient *gs, char *bytes, int max_len); +// entities +Entity *get_entity(struct GameState *gs, EntityID id); +Entity *new_entity(struct GameState *gs); +EntityID get_id(struct GameState *gs, Entity *e); +V2 entity_pos(Entity *e); +void entity_set_pos(Entity *e, V2 pos); +float entity_rotation(Entity *e); +#define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box)) +#define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes) + // player -void reset_player(struct Player *p); +void player_destroy(struct Player *p); +void player_new(struct Player *p); // grid -void grid_new(struct Grid *to_modify, struct GameState *gs, V2 pos); -V2 grid_com(struct Grid *grid); -V2 grid_pos(struct Grid *grid); -V2 grid_vel(struct Grid *grid); -V2 grid_local_to_world(struct Grid *grid, V2 local); -V2 grid_world_to_local(struct Grid *grid, V2 world); -V2 grid_snapped_box_pos(struct Grid *grid, V2 world); // returns the snapped pos in world coords -float grid_rotation(struct Grid *grid); -float grid_angular_velocity(struct Grid *grid); -void box_new(struct Box *to_modify, struct GameState *gs, struct Grid *grid, V2 pos); -V2 box_local_pos(struct Box *box); -V2 box_pos(struct Box *box); -float box_rotation(struct Box *box); +void grid_create(struct GameState *gs, Entity *e); +void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos); +V2 grid_com(Entity *grid); +V2 grid_vel(Entity *grid); +V2 grid_local_to_world(Entity *grid, V2 local); +V2 grid_world_to_local(Entity *grid, V2 world); +V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords +float grid_angular_velocity(Entity *grid); +V2 entity_shape_pos(Entity *box); +V2 box_pos(Entity *box); +float box_rotation(Entity *box); // thruster -V2 thruster_direction(struct Box *box); -V2 thruster_force(struct Box *box); +V2 thruster_direction(Entity *box); +V2 thruster_force(Entity *box); // debug draw void dbg_drawall();