Thruster placing/rotate, toolbar UI

main
Cameron Murphy Reikes 2 years ago
parent 4d147e77de
commit 547727c7f3

@ -367,6 +367,30 @@ void des_grid(char **in, struct Grid *g, struct GameState *gs)
}
}
void ser_inputframe(char **out, struct InputFrame *i)
{
ser_V2(out, i->movement);
ser_bool(out, i->inhabit);
ser_V2(out, i->build);
ser_bool(out, i->dobuild);
ser_int(out, i->build_type);
ser_int(out, i->build_rotation);
ser_int(out, i->grid_index);
}
void des_inputframe(char **in, struct InputFrame *i)
{
des_V2(in, &i->movement);
des_bool(in, &i->inhabit);
des_V2(in, &i->build);
des_bool(in, &i->dobuild);
des_int(in, (int *)&i->build_type);
des_int(in, (int *)&i->build_rotation);
des_int(in, &i->grid_index);
}
void ser_player(char **out, struct Player *p)
{
ser_bool(out, p->connected);
@ -378,13 +402,7 @@ void ser_player(char **out, struct Player *p)
ser_float(out, p->spice_taken_away);
ser_float(out, p->goldness);
// 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);
ser_inputframe(out, &p->input);
}
}
@ -399,13 +417,7 @@ void des_player(char **in, struct Player *p, struct GameState *gs)
des_float(in, &p->spice_taken_away);
des_float(in, &p->goldness);
// 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);
des_inputframe(in, &p->input);
}
}
@ -586,9 +598,9 @@ void process(struct GameState *gs, float dt)
p->currently_inhabiting_index = -1;
}
if (p->inhabit)
if (p->input.inhabit)
{
p->inhabit = false; // "handle" the input
p->input.inhabit = false; // "handle" the input
if (p->currently_inhabiting_index == -1)
{
@ -644,15 +656,15 @@ void process(struct GameState *gs, float dt)
// process movement
{
// no cheating by making movement bigger than length 1
if (V2length(p->movement) != 0.0f)
if (V2length(p->input.movement) != 0.0f)
{
p->movement = V2scale(V2normalize(p->movement), clamp(V2length(p->movement), 0.0f, 1.0f));
p->input.movement = V2scale(V2normalize(p->input.movement), clamp(V2length(p->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->movement, dt * 0.5f));
p->spice_taken_away += dt * 0.15f * V2length(p->movement);
p->vel = V2add(p->vel, V2scale(p->input.movement, dt * 0.5f));
p->spice_taken_away += dt * 0.15f * V2length(p->input.movement);
}
else
{
@ -664,9 +676,9 @@ void process(struct GameState *gs, float dt)
float thruster_spice_consumption = 0.0f;
{
V2 target_direction = {0};
if (V2length(p->movement) > 0.0f)
if (V2length(p->input.movement) > 0.0f)
{
target_direction = V2normalize(p->movement);
target_direction = V2normalize(p->input.movement);
}
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
{
@ -681,23 +693,23 @@ void process(struct GameState *gs, float dt)
}
}
// cpBodyApplyForceAtWorldPoint(g->body, v2_to_cp(V2scale(p->movement, 5.0f)), v2_to_cp(grid_com(g)));
// 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->spice_taken_away += dt * thruster_spice_consumption * THRUSTER_SPICE_PER_SECOND;
}
p->pos = V2add(p->pos, V2scale(p->vel, dt));
}
if (p->dobuild)
if (p->input.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...
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...
cpPointQueryInfo info = {0};
// @Robust make sure to query only against boxes...
V2 world_build = p->build;
if (p->grid_index != -1)
V2 world_build = p->input.build;
if (p->input.grid_index != -1)
{
world_build = grid_local_to_world(&gs->grids[p->grid_index], p->build);
world_build = grid_local_to_world(&gs->grids[p->input.grid_index], p->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);
if (nearest != NULL)
@ -707,7 +719,7 @@ void process(struct GameState *gs, float dt)
grid_remove_box(gs->space, cur_grid, cur_box);
p->spice_taken_away -= 0.1f;
}
else if (p->grid_index == -1)
else if (p->input.grid_index == -1)
{
// @Robust better memory mgmt
struct Grid *empty_grid = NULL;
@ -719,15 +731,18 @@ void process(struct GameState *gs, float dt)
break;
}
}
// @Robust cleanly fail when not enough grids
assert(empty_grid != NULL);
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].rotation = p->input.build_rotation;
cpBodySetVelocity(empty_grid->body, v2_to_cp(p->vel));
}
else
{
struct Grid *g = &gs->grids[p->grid_index];
struct Grid *g = &gs->grids[p->input.grid_index];
struct Box *empty_box = NULL;
for (int ii = 0; ii < MAX_BOXES_PER_GRID; ii++)
@ -742,6 +757,8 @@ void process(struct GameState *gs, float dt)
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->rotation = p->input.build_rotation;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

165
main.c

@ -39,8 +39,37 @@ static ENetHost *client;
static ENetPeer *peer;
static float zoom_target = 300.0f;
static float zoom = 300.0f;
static sg_image image_hullpiece;
static sg_image image_thruster;
static sg_image image_itemframe;
static sg_image image_itemframe_selected;
static int cur_editing_boxtype = -1;
static int cur_editing_rotation = 0;
static struct BoxInfo
{
enum BoxType type;
const char *image_path;
sg_image image;
} boxes[] = { // if added to here will show up in toolbar, is placeable
{
.type = BoxHullpiece,
.image_path = "loaded/hullpiece.png",
},
{
.type = BoxThruster,
.image_path = "loaded/thruster.png",
}};
const int boxes_len = sizeof(boxes) / sizeof(*boxes);
struct BoxInfo boxinfo(enum BoxType type)
{
for (int i = 0; i < boxes_len; i++)
{
if (boxes[i].type == type)
return boxes[i];
}
Log("No box info found for type %d\n", type);
return (struct BoxInfo){0};
}
static sg_image load_image(const char *path)
{
@ -96,8 +125,12 @@ static void init(void)
// image loading
{
image_hullpiece = load_image("loaded/hullpiece.png");
image_thruster = load_image("loaded/thruster.png");
for (int i = 0; i < boxes_len; i++)
{
boxes[i].image = load_image(boxes[i].image_path);
}
image_itemframe = load_image("loaded/itemframe.png");
image_itemframe_selected = load_image("loaded/itemframe_selected.png");
}
// socket initialization
@ -154,19 +187,8 @@ static void drawbox(V2 boxpos, float rot, float damage, enum BoxType type, enum
sgp_push_transform();
sgp_rotate_at(rot, boxpos.x, boxpos.y);
switch(type)
{
case BoxHullpiece:
sgp_set_image(0, image_hullpiece);
break;
case BoxThruster:
sgp_set_image(0, image_thruster);
break;
default:
Log("Unknown image for box type of type %d\n", type);
break;
}
sgp_rotate_at( rotangle(rotation),boxpos.x,boxpos.y);
sgp_set_image(0, boxinfo(type).image);
sgp_rotate_at(rotangle(rotation), boxpos.x, boxpos.y);
sgp_draw_textured_rect(boxpos.x - halfbox, boxpos.y - halfbox, BOX_SIZE, BOX_SIZE);
sgp_reset_image(0);
@ -202,6 +224,69 @@ static void draw_circle(V2 point, float radius)
sgp_draw_lines(lines, POINTS);
}
static void ui(bool draw, float width, float height)
{
// draw spice bar
if (draw && myplayer != -1)
{
sgp_set_color(0.5f, 0.5f, 0.5f, 1.0f);
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);
}
// draw item toolbar
{
int itemframe_width = sg_query_image_info(image_itemframe).width * 2.0f;
int itemframe_height = sg_query_image_info(image_itemframe).height * 2.0f;
int total_width = itemframe_width * boxes_len;
float item_width = itemframe_width * 0.75;
float item_height = itemframe_height * 0.75;
float item_offset_x = (itemframe_width - item_width) / 2.0f;
float item_offset_y = (itemframe_height - item_height) / 2.0f;
float x = width / 2.0 - total_width / 2.0;
float y = height - itemframe_height * 1.5;
for (int i = 0; i < boxes_len; i++)
{
if (has_point((AABB){
.x = x,
.y = y,
.width = itemframe_width,
.height = itemframe_height,
},
mouse_pos) &&
mouse_pressed)
{
// "handle" mouse pressed
mouse_pressed = false;
cur_editing_boxtype = i;
}
if (draw)
{
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
if (cur_editing_boxtype == i)
{
sgp_set_image(0, image_itemframe_selected);
}
else
{
sgp_set_image(0, image_itemframe);
}
sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height);
sgp_set_image(0, boxinfo((enum BoxType)i).image);
sgp_draw_textured_rect(x + item_offset_x, y + item_offset_y, item_width, item_height);
sgp_reset_image(0);
}
x += itemframe_width;
}
}
}
static void frame(void)
{
int width = sapp_width(), height = sapp_height();
@ -269,6 +354,7 @@ static void frame(void)
}
// gameplay
ui(false, width, height); // handle events
V2 build_target_pos = {0};
float build_target_rotation = 0.0f;
V2 camera_pos = {0};
@ -356,6 +442,8 @@ static void frame(void)
cur_input_frame.grid_index = grid_index;
if (cur_input_frame.dobuild)
{
cur_input_frame.build_type = cur_editing_boxtype;
cur_input_frame.build_rotation = cur_editing_rotation;
if (grid_index != -1)
{
cur_input_frame.build = grid_world_to_local(&gs.grids[cur_input_frame.grid_index], build_preview.pos);
@ -410,28 +498,17 @@ static void frame(void)
sgp_set_color(0.1f, 0.1f, 0.1f, 1.0f);
sgp_clear();
// draw spice bar
if (myplayer != -1)
{
sgp_set_color(0.5f, 0.5f, 0.5f, 1.0f);
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);
}
// sokol drawing library draw in world space
// world space coordinates are +Y up, -Y down. Like normal cartesian coords
{
sgp_push_transform();
sgp_translate(width / 2, height / 2);
sgp_scale_at(zoom, -zoom, 0.0f, 0.0f);
// camera go to player
sgp_translate(-camera_pos.x, -camera_pos.y);
}
// hand reached limit circle
if (myplayer != -1)
{
static float hand_reach_alpha = 1.0f;
@ -461,9 +538,9 @@ static void frame(void)
}
// building preview
{
if(cur_editing_boxtype != -1){
sgp_set_color(0.5f, 0.5f, 0.5f, (sin(time * 9.0f) + 1.0) / 3.0f + 0.2);
drawbox(build_preview.pos, build_preview.grid_rotation, 0.0f, BoxHullpiece, Right);
drawbox(build_preview.pos, build_preview.grid_rotation, 0.0f, cur_editing_boxtype, cur_editing_rotation);
}
// grids
@ -478,8 +555,10 @@ static void frame(void)
struct Box *b = &g->boxes[ii];
sgp_set_color(0.5f, 0.5f, 0.5f, 1.0f);
// debug draw force vectors for thrusters
if (false){
if(b->type == BoxThruster) {
if (false)
{
if (b->type == BoxThruster)
{
dbg_rect(box_pos(b));
dbg_line(box_pos(b), V2add(box_pos(b), V2scale(thruster_force(b), -1.0f)));
}
@ -527,6 +606,12 @@ static void frame(void)
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);
sg_pass_action pass_action = {0};
sg_begin_default_pass(&pass_action, width, height);
sgp_flush();
@ -553,6 +638,18 @@ void event(const sapp_event *e)
{
mouse_frozen = !mouse_frozen;
}
if (e->key_code == SAPP_KEYCODE_R)
{
cur_editing_rotation += 1;
cur_editing_rotation %= RotationLast;
}
int key_num = e->key_code - SAPP_KEYCODE_0;
int target_box = key_num - 1;
if(target_box < BoxLast)
{
cur_editing_boxtype = target_box;
}
if (!mouse_frozen)
{
keydown[e->key_code] = true;

@ -157,20 +157,22 @@ void server(void *data)
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].movement = cur_input.movement;
gs.players[player_slot].grid_index = cur_input.grid_index;
gs.players[player_slot].input.movement = cur_input.movement;
gs.players[player_slot].input.grid_index = cur_input.grid_index;
// for these "event" inputs, only modify the game state if the event is true.
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
if (cur_input.inhabit)
{
gs.players[player_slot].inhabit = cur_input.inhabit;
gs.players[player_slot].input.inhabit = cur_input.inhabit;
}
if (cur_input.dobuild)
{
gs.players[player_slot].build = cur_input.build;
gs.players[player_slot].dobuild = cur_input.dobuild;
gs.players[player_slot].input.build = cur_input.build;
gs.players[player_slot].input.dobuild = cur_input.dobuild;
gs.players[player_slot].input.build_type = cur_input.build_type;
gs.players[player_slot].input.build_rotation = cur_input.build_rotation;
}
}
player_to_latest_tick_processed[player_slot] = received.inputs[0].tick;

@ -8,8 +8,8 @@
#define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f
#define MAX_BOXES_PER_GRID 32
#define BOX_MASS 1.0f
#define THRUSTER_FORCE 2.0f
#define THRUSTER_SPICE_PER_SECOND 0.1f
#define THRUSTER_FORCE 4.0f
#define THRUSTER_SPICE_PER_SECOND 0.02f
#define TIMESTEP (1.0f / 60.0f) // not required to simulate at this, but this defines what tick the game is on
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
@ -63,6 +63,36 @@ typedef sgp_point P2;
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__)
enum BoxType
{
BoxHullpiece,
BoxThruster,
BoxLast
};
enum Rotation
{
Right,
Down,
Left,
Up,
RotationLast,
};
struct InputFrame
{
uint64_t tick;
V2 movement;
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool dobuild;
enum BoxType build_type;
enum Rotation build_rotation;
int grid_index;
};
// gotta update the serialization functions when this changes
struct GameState
{
@ -86,13 +116,7 @@ struct GameState
// input
// @Cleanup make this a frameinput struct instead of copying over all the fields like this
V2 movement; // can be at maximum length 1.0
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid
V2 build; // @Robust this is messy, clean up?
bool dobuild;
int grid_index;
struct InputFrame input;
} players[MAX_PLAYERS];
// if body or shape is null, then that grid/box has been freed
@ -104,18 +128,8 @@ struct GameState
struct Box
{
enum BoxType
{
BoxHullpiece,
BoxThruster,
} type;
enum Rotation
{
Right,
Down,
Left,
Up,
} rotation;
enum BoxType type;
enum Rotation rotation;
// thruster
float thrust; // must be between 0 and 1
@ -131,19 +145,19 @@ struct GameState
// returns in radians
static float rotangle(enum Rotation rot)
{
switch(rot)
switch (rot)
{
case Right:
return 0.0f;
break;
case Down:
return -PI/2.0f;
return -PI / 2.0f;
break;
case Left:
return -PI;
break;
case Up:
return -3.0f * PI/2.0f;
return -3.0f * PI / 2.0f;
break;
default:
Log("Unknown rotation %d\n", rot);
@ -160,17 +174,7 @@ struct ServerToClient
struct ClientToServer
{
struct InputFrame
{
uint64_t tick;
V2 movement;
bool inhabit;
// if grid_index != -1, this is in local coordinates to the grid
V2 build;
bool dobuild;
int grid_index;
} inputs[INPUT_BUFFER];
struct InputFrame inputs[INPUT_BUFFER];
};
// server
@ -218,6 +222,15 @@ void dbg_rect(V2 center);
// all the math is static so that it can be defined in each compilation unit its included in
typedef struct AABB
{
float x, y, width, height;
} AABB;
static bool has_point(AABB aabb, V2 point)
{
return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height;
}
static V2 V2add(V2 a, V2 b)
{

Loading…
Cancel
Save