|
|
|
@ -430,6 +430,19 @@ void destroy_child_shape(cpBody *body, cpShape *shape, void *data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void entity_free_allocated(Entity *e)
|
|
|
|
|
{
|
|
|
|
|
#define MAYBE_FREE(variable, free_call) \
|
|
|
|
|
if (variable != NULL) \
|
|
|
|
|
{ \
|
|
|
|
|
free_call(variable); \
|
|
|
|
|
variable = NULL; \
|
|
|
|
|
}
|
|
|
|
|
MAYBE_FREE(e->shape, cpShapeFree);
|
|
|
|
|
MAYBE_FREE(e->body, cpBodyFree);
|
|
|
|
|
MAYBE_FREE(e->landed_constraint, cpConstraintFree);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Destroys the entity and puts its slot back into the free list. Doesn't obey game rules
|
|
|
|
|
// like making sure grids don't have holes in them, for that you want entity_destroy.
|
|
|
|
|
// *Does* free all owned memory/entities though, e.g grids free the boxes they own.
|
|
|
|
@ -451,14 +464,10 @@ void entity_memory_free(GameState *gs, Entity *e)
|
|
|
|
|
if (e->shape != NULL)
|
|
|
|
|
{
|
|
|
|
|
cpSpaceRemoveShape(gs->space, e->shape);
|
|
|
|
|
cpShapeFree(e->shape);
|
|
|
|
|
e->shape = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (e->landed_constraint != NULL)
|
|
|
|
|
{
|
|
|
|
|
cpSpaceRemoveConstraint(gs->space, e->landed_constraint);
|
|
|
|
|
cpConstraintFree(e->landed_constraint);
|
|
|
|
|
e->landed_constraint = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (e->body != NULL)
|
|
|
|
|
{
|
|
|
|
@ -467,9 +476,8 @@ void entity_memory_free(GameState *gs, Entity *e)
|
|
|
|
|
cpBodyEachConstraint(e->body, destroy_constraints, NULL);
|
|
|
|
|
cpBodyEachShape(e->body, destroy_child_shape, (void *)gs);
|
|
|
|
|
cpSpaceRemoveBody(gs->space, e->body);
|
|
|
|
|
cpBodyFree(e->body);
|
|
|
|
|
e->body = NULL;
|
|
|
|
|
}
|
|
|
|
|
entity_free_allocated(e);
|
|
|
|
|
Entity *front_of_free_list = get_entity(gs, gs->free_list);
|
|
|
|
|
if (front_of_free_list != NULL)
|
|
|
|
|
flight_assert(!front_of_free_list->exists);
|
|
|
|
@ -595,13 +603,18 @@ static const cpShapeFilter FILTER_DEFAULT = {CP_NO_GROUP, DEFAULT, CP_ALL_CATEGO
|
|
|
|
|
// size is (1/2 the width, 1/2 the height)
|
|
|
|
|
void create_rectangle_shape(GameState *gs, Entity *e, Entity *parent, cpVect pos, cpVect size, double mass)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE("Create rectangle shape")
|
|
|
|
|
{
|
|
|
|
|
// @Robust remove this garbage
|
|
|
|
|
if (e->shape != NULL)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE("Freeing shape")
|
|
|
|
|
{
|
|
|
|
|
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] = {
|
|
|
|
@ -615,9 +628,16 @@ void create_rectangle_shape(GameState *gs, Entity *e, Entity *parent, cpVect pos
|
|
|
|
|
e->shape_parent_entity = get_id(gs, parent);
|
|
|
|
|
e->shape = (cpShape *)cpPolyShapeInitRaw(cpPolyShapeAlloc(), parent->body, 4, verts, 0.0); // this cast is done in chipmunk, not sure why it works
|
|
|
|
|
cpShapeSetUserData(e->shape, (void *)e);
|
|
|
|
|
PROFILE_SCOPE("Setting mass")
|
|
|
|
|
{
|
|
|
|
|
cpShapeSetMass(e->shape, mass);
|
|
|
|
|
}
|
|
|
|
|
PROFILE_SCOPE("Adding shape")
|
|
|
|
|
{
|
|
|
|
|
cpSpaceAddShape(gs->space, e->shape);
|
|
|
|
|
}
|
|
|
|
|
cpShapeSetFilter(e->shape, FILTER_DEFAULT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void create_circle_shape(GameState *gs, Entity *e, double radius)
|
|
|
|
|
{
|
|
|
|
@ -1016,9 +1036,6 @@ static void on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if(entity_a->is_missile) {getPointFunc = cpArbiterGetPointA;
|
|
|
|
|
// if(entity_b->is_missile) getPointFunc = cpArbiterGetPointB;
|
|
|
|
|
|
|
|
|
|
double damage = cpvlength((cpArbiterTotalImpulse(arb))) * COLLISION_DAMAGE_SCALING;
|
|
|
|
|
|
|
|
|
|
if (entity_a->is_box && entity_a->box_type == BoxExplosive)
|
|
|
|
@ -1028,22 +1045,18 @@ static void on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData)
|
|
|
|
|
|
|
|
|
|
if (damage > 0.05)
|
|
|
|
|
{
|
|
|
|
|
// 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
|
|
|
|
|
// cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL);
|
|
|
|
|
// cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// must be called with zero initialized game state, because copies the server side computing!
|
|
|
|
|
void initialize(GameState *gs, void *entity_arena, size_t entity_arena_size)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE("Initialize")
|
|
|
|
|
{
|
|
|
|
|
bool is_server_side = gs->server_side_computing;
|
|
|
|
|
*gs = (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 = (unsigned int)(entity_arena_size / sizeof(Entity));
|
|
|
|
|
gs->space = cpSpaceNew();
|
|
|
|
@ -1051,22 +1064,26 @@ void initialize(GameState *gs, void *entity_arena, size_t entity_arena_size)
|
|
|
|
|
cpCollisionHandler *handler = cpSpaceAddCollisionHandler(gs->space, 0, 0);
|
|
|
|
|
handler->postSolveFunc = on_damage;
|
|
|
|
|
gs->server_side_computing = is_server_side;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void destroy(GameState *gs)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE("Destroy")
|
|
|
|
|
{
|
|
|
|
|
// can't zero out gs data because the entity memory arena is reused
|
|
|
|
|
// on deserialization
|
|
|
|
|
for (size_t i = 0; i < gs->cur_next_entity; i++)
|
|
|
|
|
{
|
|
|
|
|
if (gs->entities[i].exists)
|
|
|
|
|
{
|
|
|
|
|
entity_memory_free(gs, &gs->entities[i]);
|
|
|
|
|
gs->entities[i] = (Entity){0};
|
|
|
|
|
entity_free_allocated(&gs->entities[i]);
|
|
|
|
|
gs->entities[i] = (Entity){0}; // IMPORTANT expects zeroed for initialize
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cpSpaceFree(gs->space);
|
|
|
|
|
gs->space = NULL;
|
|
|
|
|
gs->cur_next_entity = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// center of mass, not the literal position
|
|
|
|
|
cpVect grid_com(Entity *grid)
|
|
|
|
@ -1435,6 +1452,8 @@ SerMaybeFailure ser_player(SerState *ser, Player *p)
|
|
|
|
|
|
|
|
|
|
SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE("Ser entity")
|
|
|
|
|
{
|
|
|
|
|
SER_VAR(&e->no_save_to_disk);
|
|
|
|
|
SER_VAR(&e->always_visible);
|
|
|
|
|
SER_VAR(&e->generation);
|
|
|
|
@ -1626,44 +1645,14 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
|
|
|
|
|
break;
|
|
|
|
|
case BoxLandingGear:
|
|
|
|
|
{
|
|
|
|
|
bool is_null = e->landed_constraint == NULL;
|
|
|
|
|
SER_VAR(&is_null);
|
|
|
|
|
if (!is_null)
|
|
|
|
|
{
|
|
|
|
|
EntityID from = {0};
|
|
|
|
|
EntityID to = {0};
|
|
|
|
|
cpVect pin = {0};
|
|
|
|
|
if (ser->serializing)
|
|
|
|
|
{
|
|
|
|
|
from = get_id(gs, cp_body_entity(cpConstraintGetBodyA(e->landed_constraint)));
|
|
|
|
|
to = get_id(gs, cp_body_entity(cpConstraintGetBodyB(e->landed_constraint)));
|
|
|
|
|
pin = cpPivotJointGetAnchorA(e->landed_constraint);
|
|
|
|
|
}
|
|
|
|
|
SER_MAYBE_RETURN(ser_entityid(ser, &from));
|
|
|
|
|
SER_MAYBE_RETURN(ser_entityid(ser, &to));
|
|
|
|
|
SER_MAYBE_RETURN(ser_V2(ser, &pin));
|
|
|
|
|
if (!ser->serializing)
|
|
|
|
|
{
|
|
|
|
|
Entity *from_entity = get_entity(gs, from);
|
|
|
|
|
Entity *to_entity = get_entity(gs, to);
|
|
|
|
|
if (from_entity == NULL || to_entity == NULL)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
e->landed_constraint = cpPivotJointNew(from_entity->body, to_entity->body, pin);
|
|
|
|
|
cpSpaceAddConstraint(gs->space, e->landed_constraint);
|
|
|
|
|
on_create_constraint(e, e->landed_constraint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SER_MAYBE_RETURN(ser_entityid(ser, &e->shape_to_land_on));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return ser_ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -3210,6 +3199,8 @@ void process(struct GameState *gs, double dt)
|
|
|
|
|
// PROFILE_SCOPE("Grid processing")
|
|
|
|
|
{
|
|
|
|
|
Entity *grid = e;
|
|
|
|
|
float e; // turn all references to e into errors
|
|
|
|
|
(void)e;
|
|
|
|
|
// calculate how much energy solar panels provide
|
|
|
|
|
double energy_to_add = 0.0;
|
|
|
|
|
BOXES_ITER(gs, cur_box, grid)
|
|
|
|
@ -3251,6 +3242,7 @@ void process(struct GameState *gs, double dt)
|
|
|
|
|
// use the energy, stored in the batteries, in various boxes
|
|
|
|
|
BOXES_ITER(gs, cur_box, grid)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (cur_box->box_type == BoxThruster)
|
|
|
|
|
{
|
|
|
|
|
cur_box->energy_effectiveness = batteries_use_energy(gs, grid, &non_battery_energy_left_over, cur_box->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt);
|
|
|
|
@ -3506,8 +3498,49 @@ void process(struct GameState *gs, double dt)
|
|
|
|
|
}
|
|
|
|
|
if (cur_box->box_type == BoxLandingGear)
|
|
|
|
|
{
|
|
|
|
|
cpVect landing_point = cpvadd(entity_pos(cur_box), cpvmult(box_facing_vector(cur_box), BOX_SIZE / 2.0));
|
|
|
|
|
Entity *must_have_shape = get_entity(gs, cur_box->shape_to_land_on);
|
|
|
|
|
bool want_have_constraint = true;
|
|
|
|
|
if (want_have_constraint)
|
|
|
|
|
want_have_constraint &= must_have_shape != NULL;
|
|
|
|
|
if (want_have_constraint)
|
|
|
|
|
want_have_constraint &= must_have_shape->shape != NULL;
|
|
|
|
|
if (want_have_constraint)
|
|
|
|
|
want_have_constraint &= cpShapeGetBody(must_have_shape->shape) != NULL;
|
|
|
|
|
if (want_have_constraint)
|
|
|
|
|
want_have_constraint &= cpvdist(entity_pos(must_have_shape), landing_point) < BOX_SIZE + LANDING_GEAR_MAX_DIST;
|
|
|
|
|
|
|
|
|
|
#define DELETE_CONSTRAINT(constraint) \
|
|
|
|
|
{ \
|
|
|
|
|
cpSpaceRemoveConstraint(gs->space, constraint); \
|
|
|
|
|
cpConstraintFree(constraint); \
|
|
|
|
|
constraint = NULL; \
|
|
|
|
|
}
|
|
|
|
|
if (want_have_constraint)
|
|
|
|
|
{
|
|
|
|
|
flight_assert(must_have_shape != NULL);
|
|
|
|
|
cpBody *body_a = box_grid(cur_box)->body;
|
|
|
|
|
cpBody *body_b = cpShapeGetBody(must_have_shape->shape);
|
|
|
|
|
if (cur_box->landed_constraint != NULL && (cpConstraintGetBodyA(cur_box->landed_constraint) != body_a || cpConstraintGetBodyB(cur_box->landed_constraint) != body_b))
|
|
|
|
|
{
|
|
|
|
|
DELETE_CONSTRAINT(cur_box->landed_constraint);
|
|
|
|
|
}
|
|
|
|
|
if (cur_box->landed_constraint == NULL)
|
|
|
|
|
{
|
|
|
|
|
cur_box->landed_constraint = cpPivotJointNew(body_a, body_b, landing_point);
|
|
|
|
|
cpSpaceAddConstraint(gs->space, cur_box->landed_constraint);
|
|
|
|
|
on_create_constraint(cur_box, cur_box->landed_constraint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (cur_box->landed_constraint != NULL)
|
|
|
|
|
{
|
|
|
|
|
DELETE_CONSTRAINT(cur_box->landed_constraint);
|
|
|
|
|
}
|
|
|
|
|
cur_box->shape_to_land_on = (EntityID){0};
|
|
|
|
|
|
|
|
|
|
// maybe see something to land on
|
|
|
|
|
cpVect along = box_facing_vector(cur_box);
|
|
|
|
|
cpVect from = cpvadd(entity_pos(cur_box), cpvmult(along, BOX_SIZE / 2.0 + 0.03));
|
|
|
|
|
cpVect to = cpvadd(from, cpvmult(along, LANDING_GEAR_MAX_DIST));
|
|
|
|
@ -3516,12 +3549,7 @@ void process(struct GameState *gs, double dt)
|
|
|
|
|
cpShape *found = cpSpaceSegmentQueryFirst(gs->space, from, to, 0.0, FILTER_DEFAULT, &query_result);
|
|
|
|
|
if (found != NULL && cpShapeGetBody(found) != box_grid(cur_box)->body)
|
|
|
|
|
{
|
|
|
|
|
cpVect anchor = cpvadd(entity_pos(cur_box), cpvmult(along, BOX_SIZE / 2.0));
|
|
|
|
|
cpBody *a = box_grid(cur_box)->body;
|
|
|
|
|
cpBody *b = cpShapeGetBody(found);
|
|
|
|
|
cur_box->landed_constraint = cpPivotJointNew(a, b, anchor);
|
|
|
|
|
cpSpaceAddConstraint(gs->space, cur_box->landed_constraint);
|
|
|
|
|
on_create_constraint(cur_box, cur_box->landed_constraint);
|
|
|
|
|
cur_box->shape_to_land_on = get_id(gs, cp_shape_entity(found));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|