From b61ec16309bf5b4ebb89888c09b51401e1d69cc2 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sun, 23 Oct 2022 05:46:22 -0700 Subject: [PATCH] Building and destruction of blocks --- gamestate.c | 84 ++++++++++++++++++++++++++---- main.c | 145 +++++++++++++++++++++++++++++++++++++--------------- server.c | 61 ++++++++++++---------- types.h | 7 +++ 4 files changed, 220 insertions(+), 77 deletions(-) diff --git a/gamestate.c b/gamestate.c index 5d35929..178da2d 100644 --- a/gamestate.c +++ b/gamestate.c @@ -96,7 +96,7 @@ static cpBool on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) float damage = V2length(cp_to_v2(cpArbiterTotalImpulse(arb))) * 0.25f; if (damage > 0.05f) { - Log("Collision with damage %f\n", damage); + // Log("Collision with damage %f\n", damage); getbox(a)->damage += damage; getbox(b)->damage += damage; } @@ -139,9 +139,10 @@ void reset_player(struct Player *p) } // box must be passed as a parameter as the box added to chipmunk uses this pointer in its -// user data +// 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] = { @@ -184,9 +185,14 @@ V2 grid_vel(struct Grid *grid) { return cp_to_v2(cpBodyGetVelocity(grid->body)); } +V2 grid_world_to_local(struct Grid *grid, V2 world) +{ + return cp_to_v2(cpBodyWorldToLocal(grid->body, v2_to_cp(world))); +} +// returned snapped position is in world coordinates V2 grid_snapped_box_pos(struct Grid *grid, V2 world) { - V2 local = cp_to_v2(cpBodyWorldToLocal(grid->body, v2_to_cp(world))); + V2 local = grid_world_to_local(grid, world); local.x /= BOX_SIZE; local.y /= BOX_SIZE; local.x = roundf(local.x); @@ -336,8 +342,14 @@ void ser_player(char **out, struct Player *p) ser_int(out, p->currently_inhabiting_index); ser_V2(out, p->pos); ser_V2(out, p->vel); + + // input ser_V2(out, p->movement); ser_bool(out, p->inhabit); + + ser_V2(out, p->build); + ser_bool(out, p->dobuild); + ser_int(out, p->grid_index); } } @@ -349,8 +361,14 @@ void des_player(char **in, struct Player *p, struct GameState *gs) des_int(in, &p->currently_inhabiting_index); des_V2(in, &p->pos); des_V2(in, &p->vel); + + // input des_V2(in, &p->movement); des_bool(in, &p->inhabit); + + des_V2(in, &p->build); + des_bool(in, &p->dobuild); + des_int(in, &p->grid_index); } } @@ -430,7 +448,7 @@ static void closest_point_callback_func(cpShape *shape, cpContactPointSet *point assert(points->count == 1); 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) + if (dist > closest_to_point_in_radius_result_largest_dist) { closest_to_point_in_radius_result_largest_dist = dist; closest_to_point_in_radius_result = shape; @@ -441,8 +459,8 @@ struct Grid *closest_to_point_in_radius(struct GameState *gs, V2 point, float ra { closest_to_point_in_radius_result = NULL; closest_to_point_in_radius_result_largest_dist = 0.0f; - - cpBody * tmpbody = cpBodyNew(0.0f, 0.0f); + + cpBody *tmpbody = cpBodyNew(0.0f, 0.0f); cpShape *circle = cpCircleShapeNew(tmpbody, radius, v2_to_cp(point)); cpSpaceShapeQuery(gs->space, circle, closest_point_callback_func, NULL); @@ -454,7 +472,7 @@ struct Grid *closest_to_point_in_radius(struct GameState *gs, V2 point, float ra // @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 NULL; } @@ -469,6 +487,53 @@ void process(struct GameState *gs, float dt) if (!p->connected) continue; + if (p->dobuild) + { + p->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... + cpShape *nearest = cpSpacePointQueryNearest(gs->space, v2_to_cp(p->build), 0.01f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES), &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); + } + else if(p->grid_index == -1) + { + // @Robust better memory mgmt + struct Grid *empty_grid = NULL; + for (int ii = 0; ii < MAX_GRIDS; ii++) + { + if (gs->grids[ii].body == NULL) + { + empty_grid = &gs->grids[ii]; + break; + } + } + grid_new(empty_grid, gs, p->build); + box_new(&empty_grid->boxes[0], gs, empty_grid, (V2){0}); + } + else + { + struct Grid *g = &gs->grids[p->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); + box_new(empty_box, gs, g, grid_world_to_local(g, p->build)); + } + } + if (gs->grids[p->currently_inhabiting_index].body == NULL) { p->currently_inhabiting_index = -1; @@ -482,7 +547,7 @@ void process(struct GameState *gs, float dt) // @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.1, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES), &query_info); + cpShape *result = cpSpacePointQueryNearest(gs->space, v2_to_cp(p->pos), 0.1f, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES), &query_info); if (result != NULL) { // result is assumed to be a box shape @@ -515,7 +580,7 @@ void process(struct GameState *gs, float dt) if (p->currently_inhabiting_index == -1) { - p->vel = V2lerp(p->vel, p->movement, dt * 5.0f); + p->vel = V2add(p->vel, V2scale(p->movement, dt*3.0f)); p->pos = V2add(p->pos, V2scale(p->vel, dt)); } else @@ -524,7 +589,6 @@ void process(struct GameState *gs, float dt) p->pos = V2lerp(p->pos, grid_com(g), dt * 20.0f); cpBodyApplyForceAtWorldPoint(g->body, v2_to_cp(V2scale(p->movement, 5.0f)), v2_to_cp(grid_com(g))); } - // cpBodyApplyForceAtWorldPoint(p->box.body, v2_to_cp(V2scale(p->input, 5.0f)), v2_to_cp(box_pos(p->box))); } cpSpaceStep(gs->space, dt); diff --git a/main.c b/main.c index a651b5b..bf507b8 100644 --- a/main.c +++ b/main.c @@ -16,7 +16,7 @@ static struct GameState gs = {0}; static int myplayer = -1; -static bool mouse_down = false; +static bool right_mouse_down = false; static bool keydown[SAPP_KEYCODE_MENU] = {0}; typedef struct KeyPressed { @@ -25,6 +25,8 @@ typedef struct KeyPressed } KeyPressed; static KeyPressed keypressed[SAPP_KEYCODE_MENU] = {0}; 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 ENetHost *client; @@ -134,11 +136,18 @@ static void frame(void) float time = sapp_frame_count() * sapp_frame_duration(); float dt = sapp_frame_duration(); - for (int i = 0; i < SAPP_KEYCODE_MENU; i++) + // pressed input management { - if (keypressed[i].frame < sapp_frame_count()) + for (int i = 0; i < SAPP_KEYCODE_MENU; i++) { - keypressed[i].pressed = false; + if (keypressed[i].frame < sapp_frame_count()) + { + keypressed[i].pressed = false; + } + } + if (mouse_pressed_frame < sapp_frame_count()) + { + mouse_pressed = false; } } @@ -189,19 +198,76 @@ static void frame(void) // gameplay V2 build_target_pos = {0}; float build_target_rotation = 0.0f; + V2 camera_pos = {0}; + V2 world_mouse_pos = mouse_pos; + float zoom = 300.0f + funval; + struct BuildPreviewInfo + { + V2 grid_pos; + float grid_rotation; + V2 pos; + } build_preview = {0}; { - // @Robust accumulate total time and send input at rate like 20 hz, not every frame - struct ClientToServer curmsg = {0}; - V2 input = (V2){ - .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], - .y = (float)keydown[SAPP_KEYCODE_S] - (float)keydown[SAPP_KEYCODE_W], - }; - curmsg.movement = input; - curmsg.inhabit = keypressed[SAPP_KEYCODE_G].pressed; - - // @BeforeShip figure out why tf the possess ship key is so unreliable - ENetPacket *packet = enet_packet_create((void *)&curmsg, sizeof(curmsg), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); - enet_peer_send(peer, 0, packet); + // calculate world position and camera + { + if (myplayer != -1) + { + camera_pos = gs.players[myplayer].pos; + } + world_mouse_pos = V2sub(world_mouse_pos, (V2){.x = width / 2.0f, .y = height / 2.0f}); + world_mouse_pos.x /= zoom; + world_mouse_pos.y /= zoom; + world_mouse_pos = V2add(world_mouse_pos, (V2){.x = camera_pos.x, .y = camera_pos.y}); + } + + // calculate build preview stuff + int grid_index = -1; + { + struct Grid *placing_grid = closest_to_point_in_radius(&gs, world_mouse_pos, 0.35f); + if (placing_grid == NULL) + { + build_preview = (struct BuildPreviewInfo){ + .grid_pos = world_mouse_pos, + .grid_rotation = 0.0f, + .pos = world_mouse_pos, + }; + } + else + { + for (int i = 0; i < MAX_GRIDS; i++) + { + if (&gs.grids[i] == placing_grid) + { + grid_index = i; + break; + } + } + V2 pos = grid_snapped_box_pos(placing_grid, world_mouse_pos); + build_preview = (struct BuildPreviewInfo){ + .grid_pos = grid_pos(placing_grid), + .grid_rotation = grid_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 + struct ClientToServer curmsg = {0}; + V2 input = (V2){ + .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A], + .y = (float)keydown[SAPP_KEYCODE_S] - (float)keydown[SAPP_KEYCODE_W], + }; + curmsg.movement = input; + curmsg.inhabit = keypressed[SAPP_KEYCODE_G].pressed; + curmsg.build = build_preview.pos; + curmsg.dobuild = mouse_pressed; + curmsg.grid_index = grid_index; + + // @BeforeShip figure out why tf the possess ship key is so unreliable + ENetPacket *packet = enet_packet_create((void *)&curmsg, sizeof(curmsg), ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); + enet_peer_send(peer, 0, packet); + } // @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! @@ -220,21 +286,13 @@ static void frame(void) sgp_clear(); // sokol drawing library draw in world space - V2 world_mouse_pos = mouse_pos; { - float zoom = 300.0f + funval; sgp_translate(width / 2, height / 2); - world_mouse_pos = V2sub(world_mouse_pos, (V2){.x = width / 2.0f, .y = height / 2.0f}); sgp_scale_at(zoom, zoom, 0.0f, 0.0f); - world_mouse_pos.x /= zoom; - world_mouse_pos.y /= zoom; + // camera go to player - if (myplayer != -1) - { - V2 pos = gs.players[myplayer].pos; - sgp_translate(-pos.x, -pos.y); - world_mouse_pos = V2add(world_mouse_pos, (V2){.x = pos.x, .y = pos.y}); - } + + sgp_translate(-camera_pos.x, -camera_pos.y); } sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); @@ -281,13 +339,11 @@ static void frame(void) 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); } - struct Grid *placing_grid = closest_to_point_in_radius(&gs, world_mouse_pos, 0.35f); - if (placing_grid != NULL) + + // building preview { - V2 pos = grid_snapped_box_pos(placing_grid, world_mouse_pos); sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2); - drawbox(grid_pos(placing_grid), grid_rotation(placing_grid), pos, 0.0f, false); - // sgp_draw_filled_rect(pos.x, pos.y, 0.1f, 0.1f); + drawbox(build_preview.grid_pos, build_preview.grid_rotation, build_preview.pos, 0.0f, false); } // grids @@ -317,11 +373,6 @@ static void frame(void) sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f); dbg_drawall(); - // sgp_draw_line(5.0f, 5.0f, 5.0f, 10.0f); - // sgp_draw_line() - // sgp_rotate_at(time, 0.0f, 0.0f); - - // Begin a render pass. sg_pass_action pass_action = {0}; sg_begin_default_pass(&pass_action, width, height); sgp_flush(); @@ -362,18 +413,32 @@ void event(const sapp_event *e) break; case SAPP_EVENTTYPE_MOUSE_DOWN: if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - mouse_down = true; + { + mouse_pressed = true; + mouse_pressed_frame = e->frame_count; + } + if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) + { + right_mouse_down = true; + } break; case SAPP_EVENTTYPE_MOUSE_UP: if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) - mouse_down = false; + { + mouse_pressed = false; + mouse_pressed_frame = 0; + } + if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) + { + right_mouse_down = false; + } break; case SAPP_EVENTTYPE_MOUSE_MOVE: if (!mouse_frozen) { mouse_pos = (V2){.x = e->mouse_x, .y = e->mouse_y}; } - if (mouse_down) + if (right_mouse_down) { funval += e->mouse_dx; Log("Funval %f\n", funval); diff --git a/server.c b/server.c index 15b36a1..1cc3e83 100644 --- a/server.c +++ b/server.c @@ -17,37 +17,36 @@ void server(void *data) initialize(&gs); // box haven - if(true) + if (true) { 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}); - box_new(&gs.grids[0].boxes[1],&gs, &gs.grids[0], (V2){0, 0.5f}); - box_new(&gs.grids[0].boxes[2],&gs, &gs.grids[0], (V2){0, 1.0f}); - box_new(&gs.grids[0].boxes[3],&gs, &gs.grids[0], (V2){0.5f, 1.0f}); + 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, 0.5f}); + box_new(&gs.grids[0].boxes[2], &gs, &gs.grids[0], (V2){0, 1.0f}); + box_new(&gs.grids[0].boxes[3], &gs, &gs.grids[0], (V2){0.5f, 1.0f}); grid_new(&gs.grids[1], &gs, (V2){.x = -0.75f, .y = 0.0}); - box_new(&gs.grids[1].boxes[0],&gs, &gs.grids[1], (V2){0}); + box_new(&gs.grids[1].boxes[0], &gs, &gs.grids[1], (V2){0}); grid_new(&gs.grids[2], &gs, (V2){.x = -0.75f, .y = 0.5}); - box_new(&gs.grids[2].boxes[0],&gs, &gs.grids[2], (V2){0}); + box_new(&gs.grids[2].boxes[0], &gs, &gs.grids[2], (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}); - + 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}); + 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}); + box_new(&gs.grids[0].boxes[0], &gs, &gs.grids[0], (V2){0}); } if (enet_initialize() != 0) @@ -82,7 +81,7 @@ void server(void *data) // @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this while (true) { - int ret = enet_host_service(server, &event, 16); + int ret = enet_host_service(server, &event, 0); if (ret == 0) break; if (ret < 0) @@ -141,6 +140,9 @@ void server(void *data) int64_t player_slot = (int64_t)event.peer->data; gs.players[player_slot].movement = received.movement; gs.players[player_slot].inhabit = received.inhabit; + gs.players[player_slot].build = received.build; + gs.players[player_slot].dobuild = received.dobuild; + gs.players[player_slot].grid_index = received.grid_index; } /* Clean up the packet now that we're done using it. */ @@ -161,30 +163,35 @@ void server(void *data) total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time)); last_processed_time = stm_now(); // @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this... + bool processed = false; while (total_time > TIMESTEP) { + processed = true; process(&gs, TIMESTEP); total_time -= TIMESTEP; } -#define MAX_BYTES_SIZE 2048 * 2 - static char bytes_buffer[MAX_BYTES_SIZE] = {0}; - for (int i = 0; i < server->peerCount; i++) + if (processed) { - // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again - if (server->peers[i].state != ENET_PEER_STATE_CONNECTED) +#define MAX_BYTES_SIZE 2048 * 2 + static char bytes_buffer[MAX_BYTES_SIZE] = {0}; + for (int i = 0; i < server->peerCount; i++) { - continue; - } - struct ServerToClient to_send; - to_send.cur_gs = &gs; - to_send.your_player = (int)(int64_t)server->peers[i].data; + // @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again + if (server->peers[i].state != ENET_PEER_STATE_CONNECTED) + { + continue; + } + struct ServerToClient to_send; + to_send.cur_gs = &gs; + to_send.your_player = (int)(int64_t)server->peers[i].data; - int len = 0; - into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE); + int len = 0; + into_bytes(&to_send, bytes_buffer, &len, MAX_BYTES_SIZE); - ENetPacket *gamestate_packet = enet_packet_create((void *)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); - enet_peer_send(&server->peers[i], 0, gamestate_packet); + ENetPacket *gamestate_packet = enet_packet_create((void *)bytes_buffer, len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT); + enet_peer_send(&server->peers[i], 0, gamestate_packet); + } } } diff --git a/types.h b/types.h index a962103..03c8a9a 100644 --- a/types.h +++ b/types.h @@ -60,6 +60,10 @@ struct GameState // input V2 movement; bool inhabit; + + V2 build; // @Robust this is messy, clean up? + bool dobuild; + int grid_index; } players[MAX_PLAYERS]; // if body or shape is null, then that grid/box has been freed @@ -87,6 +91,9 @@ struct ClientToServer { V2 movement; bool inhabit; + V2 build; + bool dobuild; + int grid_index; }; // server