Building and destruction of blocks

main
Cameron Murphy Reikes 2 years ago
parent f2ef6d8531
commit b61ec16309

@ -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);

145
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);

@ -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);
}
}
}

@ -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

Loading…
Cancel
Save