diff --git a/gamestate.c b/gamestate.c index fc582cb..a7eaa9f 100644 --- a/gamestate.c +++ b/gamestate.c @@ -471,11 +471,18 @@ static cpBool on_damage(cpArbiter* arb, cpSpace* space, cpDataPointer userData) entity_b = cp_shape_entity(b); float damage = V2length(cp_to_v2(cpArbiterTotalImpulse(arb))) * COLLISION_DAMAGE_SCALING; + + if (entity_a->is_box && entity_a->box_type == BoxExplosive) + entity_a->damage += 2.0f*EXPLOSION_DAMAGE_THRESHOLD; + if (entity_b->is_box && entity_b->box_type == BoxExplosive) + entity_b->damage += 2.0f*EXPLOSION_DAMAGE_THRESHOLD; + if (damage > 0.05f) { // Log("Collision with damage %f\n", 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 @@ -515,12 +522,6 @@ V2 grid_com(Entity* grid) return cp_to_v2(cpBodyLocalToWorld(grid->body, cpBodyGetCenterOfGravity(grid->body))); } -V2 entity_pos(Entity* e) -{ - assert(!e->is_box); - // @Robust merge entity_pos with box_pos - return cp_to_v2(cpBodyGetPosition(e->body)); -} V2 grid_vel(Entity* grid) { return cp_to_v2(cpBodyGetVelocity(grid->body)); @@ -568,15 +569,20 @@ float entity_shape_mass(Entity* box) assert(box->shape != NULL); return (float)cpShapeGetMass(box->shape); } -V2 box_pos(Entity* 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(Entity* box) { return (float)cpBodyGetAngle(cpShapeGetBody(box->shape)); } +V2 entity_pos(Entity* e) +{ + if (e->is_box) { + return V2add(entity_pos(box_grid(e)), V2rotate(entity_shape_pos(e), entity_rotation(box_grid(e)))); + } + else { + assert(e->body != NULL); + return cp_to_v2(cpBodyGetPosition(e->body)); + } +} struct BodyData { @@ -711,6 +717,7 @@ void ser_player(SerState* ser, Player* p) SER_VAR(&p->connected); if (p->connected) { + SER_VAR(&p->unlocked_bombs); ser_entityid(ser, &p->entity); ser_inputframe(ser, &p->input); } @@ -779,6 +786,14 @@ void ser_entity(SerState* ser, GameState* gs, Entity* e) SER_VAR(&e->goldness); } + SER_VAR(&e->is_explosion); + if (e->is_explosion) + { + ser_V2(ser, &e->explosion_pos); + ser_V2(ser, &e->explosion_vel); + SER_VAR(&e->explosion_progresss); + } + SER_VAR(&e->is_grid); if (e->is_grid) { @@ -955,6 +970,34 @@ Entity* closest_to_point_in_radius(GameState* gs, V2 point, float radius) return NULL; } +static float cur_explosion_damage = 0.0f; +static V2 explosion_origin = { 0 }; +static void explosion_callback_func(cpShape* shape, cpContactPointSet* points, void* data) +{ + GameState* gs = (GameState*)data; + cp_shape_entity(shape)->damage += cur_explosion_damage; + Entity* parent = get_entity(gs, cp_shape_entity(shape)->shape_parent_entity); + V2 from_pos = entity_pos(cp_shape_entity(shape)); + V2 impulse = V2scale(V2normalize(V2sub(from_pos, explosion_origin)), EXPLOSION_PUSH_STRENGTH); + assert(parent->body != NULL); + cpBodyApplyImpulseAtWorldPoint(parent->body, v2_to_cp(impulse), v2_to_cp(from_pos)); + +} + +static void do_explosion(GameState* gs, Entity* explosion, float dt) +{ + cur_explosion_damage = dt * EXPLOSION_DAMAGE_PER_SEC; + explosion_origin = explosion->explosion_pos; + + cpBody* tmpbody = cpBodyNew(0.0f, 0.0f); + cpShape* circle = cpCircleShapeNew(tmpbody, EXPLOSION_RADIUS, v2_to_cp(explosion_origin)); + + cpSpaceShapeQuery(gs->space, circle, explosion_callback_func, (void*)gs); + + cpShapeFree(circle); + cpBodyFree(tmpbody); +} + V2 box_facing_vector(Entity* box) { assert(box->is_box); @@ -1029,7 +1072,7 @@ void process(GameState* gs, float dt) assert(p->is_player); #ifdef INFINITE_RESOURCES - p->spice_taken_away = 0.0f; + p->damage = 0.0f; #endif // update gold win condition if (V2length(V2sub(cp_to_v2(cpBodyGetPosition(p->body)), gs->goldpos)) < GOLD_COLLECT_RADIUS) @@ -1066,7 +1109,7 @@ void process(GameState* gs, float dt) } else { - V2 pilot_seat_exit_spot = V2add(box_pos(the_seat), V2scale(box_facing_vector(the_seat), BOX_SIZE)); + V2 pilot_seat_exit_spot = V2add(entity_pos(the_seat), V2scale(box_facing_vector(the_seat), BOX_SIZE)); cpBodySetPosition(p->body, v2_to_cp(pilot_seat_exit_spot)); cpBodySetVelocity(p->body, v2_to_cp(player_vel(gs, p))); the_seat->player_who_is_inside_of_me = (EntityID){ 0 }; @@ -1095,10 +1138,10 @@ void process(GameState* gs, float dt) { assert(seat_inside_of->is_box); cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while in a seat - cpBodySetPosition(p->body, v2_to_cp(box_pos(seat_inside_of))); + cpBodySetPosition(p->body, v2_to_cp(entity_pos(seat_inside_of))); // set thruster thrust from movement - if(seat_inside_of->box_type == BoxCockpit) { + if (seat_inside_of->box_type == BoxCockpit) { Entity* g = get_entity(gs, seat_inside_of->shape_parent_entity); V2 target_direction = { 0 }; @@ -1133,7 +1176,7 @@ void process(GameState* gs, float dt) { Entity* cur_box = cp_shape_entity(nearest); Entity* cur_grid = cp_body_entity(cpShapeGetBody(nearest)); - p->damage -= DAMAGE_TO_PLAYER_PER_BLOCK*((BATTERY_CAPACITY - cur_box->energy_used)/BATTERY_CAPACITY); + p->damage -= DAMAGE_TO_PLAYER_PER_BLOCK * ((BATTERY_CAPACITY - cur_box->energy_used) / BATTERY_CAPACITY); grid_remove_box(gs, cur_grid, cur_box); } else if (target_grid == NULL) @@ -1179,7 +1222,7 @@ void process(GameState* gs, float dt) { cpVect p = cpvsub(cpBodyGetPosition(e->body), v2_to_cp(SUN_POS)); cpFloat sqdist = cpvlengthsq(p); - if(sqdist > (INSTANT_DEATH_DISTANCE_FROM_SUN*INSTANT_DEATH_DISTANCE_FROM_SUN)) + if (sqdist > (INSTANT_DEATH_DISTANCE_FROM_SUN * INSTANT_DEATH_DISTANCE_FROM_SUN)) { entity_destroy(gs, e); continue; @@ -1194,8 +1237,27 @@ void process(GameState* gs, float dt) cpBodyUpdateVelocity(e->body, g, 1.0f, dt); } + if (e->is_explosion) + { + e->explosion_progresss += dt; + e->explosion_pos = V2add(e->explosion_pos, V2scale(e->explosion_vel, dt)); + do_explosion(gs, e, dt); + if (e->explosion_progresss >= EXPLOSION_TIME) + { + entity_destroy(gs, e); + } + } + if (e->is_box) { + if (e->box_type == BoxExplosive && e->damage >= EXPLOSION_DAMAGE_THRESHOLD) + { + Entity* explosion = new_entity(gs); + explosion->is_explosion = true; + explosion->explosion_pos = entity_pos(e); + explosion->explosion_vel = grid_vel(box_grid(e)); + grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); + } if (e->damage >= 1.0f) { grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); @@ -1208,7 +1270,7 @@ void process(GameState* gs, float dt) BOXES_ITER(gs, cur, e) { if (cur->box_type == BoxSolarPanel) { - cur->sun_amount = clamp01(V2dot(box_facing_vector(cur), V2normalize(V2sub(SUN_POS, box_pos(cur))))); + cur->sun_amount = clamp01(V2dot(box_facing_vector(cur), V2normalize(V2sub(SUN_POS, entity_pos(cur))))); energy_to_add += cur->sun_amount * SOLAR_ENERGY_PER_SECOND * dt; } } @@ -1237,7 +1299,7 @@ void process(GameState* gs, float dt) if (possibly_use_energy(gs, e, energy_to_consume)) { cur->thrust = cur->wanted_thrust; - cpBodyApplyForceAtWorldPoint(e->body, v2_to_cp(thruster_force(cur)), v2_to_cp(box_pos(cur))); + cpBodyApplyForceAtWorldPoint(e->body, v2_to_cp(thruster_force(cur)), v2_to_cp(entity_pos(cur))); } } if (cur->box_type == BoxMedbay) diff --git a/loaded/explosion.png b/loaded/explosion.png new file mode 100644 index 0000000..528b051 Binary files /dev/null and b/loaded/explosion.png differ diff --git a/loaded/explosive.png b/loaded/explosive.png new file mode 100644 index 0000000..6d2cc4e Binary files /dev/null and b/loaded/explosive.png differ diff --git a/loaded/mystery.png b/loaded/mystery.png new file mode 100644 index 0000000..1614132 Binary files /dev/null and b/loaded/mystery.png differ diff --git a/main.c b/main.c index 4681b29..5082733 100644 --- a/main.c +++ b/main.c @@ -54,6 +54,8 @@ static sg_image image_stars; static sg_image image_stars2; static sg_image image_sun; static sg_image image_medbay_used; +static sg_image image_mystery; +static sg_image image_explosion; static int cur_editing_boxtype = -1; static int cur_editing_rotation = 0; @@ -61,6 +63,7 @@ static struct BoxInfo { enum BoxType type; const char* image_path; sg_image image; + bool needs_tobe_unlocked; } boxes[] = { // if added to here will show up in toolbar, is placeable { @@ -75,10 +78,6 @@ static struct BoxInfo { .type = BoxBattery, .image_path = "loaded/battery.png", }, - { - .type = BoxSolarPanel, - .image_path = "loaded/solarpanel.png", - }, { .type = BoxCockpit, .image_path = "loaded/cockpit.png", @@ -87,6 +86,15 @@ static struct BoxInfo { .type = BoxMedbay, .image_path = "loaded/medbay.png", }, + { + .type = BoxSolarPanel, + .image_path = "loaded/solarpanel.png", + }, + { + .type = BoxExplosive, + .image_path = "loaded/explosive.png", + .needs_tobe_unlocked = true, + }, }; const int boxes_len = sizeof(boxes) / sizeof(*boxes); @@ -171,6 +179,8 @@ init(void) image_stars2 = load_image("loaded/stars2.png"); image_sun = load_image("loaded/sun.png"); image_medbay_used = load_image("loaded/medbay_used.png"); + image_mystery = load_image("loaded/mystery.png"); + image_explosion = load_image("loaded/explosion.png"); } // socket initialization @@ -273,14 +283,30 @@ myentity() return to_return; } +bool can_build(int i) +{ + bool allow_building = true; + if (boxinfo((enum BoxType)i).needs_tobe_unlocked) + { + allow_building = gs.players[myplayer].unlocked_bombs; + } + return allow_building; +} + +void attempt_to_build(int i) +{ + if (can_build(i)) + cur_editing_boxtype = i; +} + 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; - } + cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f); + if (cur_opacity <= 0.01f) { + return; + } if (draw) sgp_push_transform(); @@ -326,8 +352,8 @@ ui(bool draw, float dt, float width, float height) mouse_pos) && mouse_pressed) { // "handle" mouse pressed + attempt_to_build(i); mouse_pressed = false; - cur_editing_boxtype = i; } if (draw) { @@ -339,7 +365,14 @@ ui(bool draw, float dt, float width, float height) 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); + struct BoxInfo info = boxinfo((enum BoxType)i); + if (can_build(i)) + { + sgp_set_image(0, info.image); + } + else { + sgp_set_image(0, image_mystery); + } transform_scope { float item_x = x + item_offset_x; @@ -370,8 +403,8 @@ static void draw_dots(V2 camera_pos, float gap) if (fabsf(star.y - camera_pos.y) > VISION_RADIUS) continue; - star.x += hash11(star.x * 100.0f + star.y * 67.0f)*gap; - star.y += hash11(star.y * 93.0f + star.x * 53.0f)*gap; + star.x += hash11(star.x * 100.0f + star.y * 67.0f) * gap; + star.y += hash11(star.y * 93.0f + star.x * 53.0f) * gap; sgp_draw_point(star.x, star.y); } } @@ -706,8 +739,8 @@ frame(void) { if (b->type == BoxThruster) { - dbg_rect(box_pos(b)); - dbg_line(box_pos(b), V2add(box_pos(b), V2scale(thruster_force(b), -1.0f))); + dbg_rect(entity_pos(b)); + dbg_line(entity_pos(b), V2add(entity_pos(b), V2scale(thruster_force(b), -1.0f))); } } #endif @@ -715,14 +748,14 @@ frame(void) float cur_alpha = sgp_get_color().a; Color from = WHITE; Color to = colhex(255, 0, 0); - Color result = Collerp(from, to, b->energy_used/BATTERY_CAPACITY); + Color result = Collerp(from, to, b->energy_used / BATTERY_CAPACITY); sgp_set_color(result.r, result.g, result.b, cur_alpha); } transform_scope { sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), - box_pos(b).x, - box_pos(b).y); + entity_pos(b).x, + entity_pos(b).y); if (b->box_type == BoxThruster) { transform_scope @@ -735,8 +768,8 @@ frame(void) // float scaling = 1.1; // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0); // sgp_scale(scaling, 1.0); - sgp_scale_at(scaling, 1.0f, box_pos(b).x, box_pos(b).y); - draw_texture_centered(box_pos(b), BOX_SIZE); + sgp_scale_at(scaling, 1.0f, entity_pos(b).x, entity_pos(b).y); + draw_texture_centered(entity_pos(b), BOX_SIZE); sgp_reset_image(0); } } @@ -752,19 +785,19 @@ frame(void) img = image_medbay_used; } sgp_set_image(0, img); - draw_texture_centered(box_pos(b), BOX_SIZE); + draw_texture_centered(entity_pos(b), BOX_SIZE); sgp_reset_image(0); if (b->box_type == BoxSolarPanel) { Color to_set = colhexcode(0xeb9834); - to_set.a = b->sun_amount*0.5f; + to_set.a = b->sun_amount * 0.5f; set_color(to_set); - draw_color_rect_centered(box_pos(b), BOX_SIZE); + draw_color_rect_centered(entity_pos(b), BOX_SIZE); } sgp_set_color(0.5f, 0.1f, 0.1f, b->damage); - draw_color_rect_centered(box_pos(b), BOX_SIZE); + draw_color_rect_centered(entity_pos(b), BOX_SIZE); } } @@ -784,7 +817,15 @@ frame(void) sgp_reset_image(0); } } + if (e->is_explosion) + { + sgp_set_image(0, image_explosion); + sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f - (e->explosion_progresss / EXPLOSION_TIME)); + draw_texture_centered(e->explosion_pos, EXPLOSION_RADIUS*2.0f); + sgp_reset_image(0); + } } + // gold target set_color(GOLD); sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f); @@ -807,7 +848,7 @@ frame(void) dbg_drawall(); } // world space transform end - } +} // UI drawn in screen space ui(true, dt, width, height); @@ -843,7 +884,7 @@ void event(const sapp_event* e) int key_num = e->key_code - SAPP_KEYCODE_0; int target_box = key_num - 1; if (target_box < BoxLast) { - cur_editing_boxtype = target_box; + attempt_to_build(target_box); } if (!mouse_frozen) { diff --git a/server.c b/server.c index e58b6fe..c6193ea 100644 --- a/server.c +++ b/server.c @@ -21,6 +21,11 @@ void server(void* data) initialize(&gs, entity_data, entities_size); Log("Allocated %zu bytes for entities\n", entities_size); + // unlock the explosive + if (true) + { + } + // one box policy if (false) { @@ -122,6 +127,9 @@ void server(void* data) event.peer->data = (void*)player_slot; gs.players[player_slot] = (struct Player){ 0 }; gs.players[player_slot].connected = true; +#ifdef UNLOCK_ALL + gs.players[player_slot].unlocked_bombs = true; +#endif } break; diff --git a/types.h b/types.h index f759206..3e8489e 100644 --- a/types.h +++ b/types.h @@ -6,13 +6,12 @@ #define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE}) #define PLAYER_MASS 0.5f #define PLAYER_JETPACK_FORCE 2.0f -//#define PLAYER_JETPACK_SPICE_PER_SECOND 0.3f -#define PLAYER_JETPACK_SPICE_PER_SECOND 0.0f +#define PLAYER_JETPACK_SPICE_PER_SECOND 0.3f #define MAX_HAND_REACH 1.0f #define GOLD_COLLECT_RADIUS 0.3f #define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f #define BOX_MASS 1.0f -#define COLLISION_DAMAGE_SCALING 0.1f +#define COLLISION_DAMAGE_SCALING 0.15f #define THRUSTER_FORCE 4.0f #define THRUSTER_ENERGY_USED_PER_SECOND 0.05f #define VISION_RADIUS 16.0f @@ -25,6 +24,11 @@ #define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f #define BATTERY_CAPACITY DAMAGE_TO_PLAYER_PER_BLOCK #define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.1f +#define EXPLOSION_TIME 0.5f +#define EXPLOSION_PUSH_STRENGTH 5.0f +#define EXPLOSION_DAMAGE_PER_SEC 10.0f +#define EXPLOSION_RADIUS 1.0f +#define EXPLOSION_DAMAGE_THRESHOLD 0.2f // how much damage until it explodes #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) @@ -86,6 +90,7 @@ enum BoxType BoxCockpit, BoxMedbay, BoxSolarPanel, + BoxExplosive, BoxLast, }; @@ -150,6 +155,12 @@ typedef struct Entity EntityID currently_inside_of_box; float goldness; // how much the player is a winner + // explosion + bool is_explosion; + V2 explosion_pos; + V2 explosion_vel; + float explosion_progresss; // in seconds + // grids bool is_grid; float total_energy_capacity; @@ -171,6 +182,7 @@ typedef struct Entity typedef struct Player { bool connected; + bool unlocked_bombs; EntityID entity; InputFrame input; } Player; @@ -269,7 +281,6 @@ V2 grid_world_to_local(Entity* grid, V2 world); V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords float entity_angular_velocity(Entity* grid); V2 entity_shape_pos(Entity* box); -V2 box_pos(Entity* box); // returns in world coords float box_rotation(Entity* box); // thruster