@@ -10,15 +10,31 @@ const uint8_t NUM_NEAR_PICKUPS = 1;
1010const float WALL_CHECK_DISTANCE_SQUARED = SQUARED (6.0f );
1111const float WALL_AVOID_DISTANCE = 4.0f ;
1212const float WALL_DANGER_DISTANCE = 3.0f ;
13- const float WALL_BRAKE_DISTANCE = 20.0f ;
14- const float WALL_BRAKE_SPEED = 12.5f ;
15- const float WALL_BRAKE_TIME = 0.5f ;
13+ const float WALL_BURST_CHECK_DISTANCE = 5.0f ;
14+ const float WALL_BURST_CHECK_SPEED = 20.0f ;
1615const float BURST_MIN_RADIUS_SQUARED = SQUARED (DRONE_BURST_RADIUS_MIN );
17- const float MOVE_SPEED_SQUARED = SQUARED (5.0f );
16+ const float STABILIZE_MOVE_SPEED = 5.0f ;
17+ const float SHOTGUN_DANGER_DISTANCE = 4.0f ;
18+
19+ void addDebugPoint (iwEnv * e , b2Vec2 pos , float size , Color color ) {
20+ #ifndef NDEBUG
21+ debugPoint * point = fastCalloc (1 , sizeof (debugPoint ));
22+ point -> pos = pos ;
23+ point -> size = size ;
24+ point -> color = color ;
25+ cc_array_add (e -> debugPoints , point );
26+ #else
27+ MAYBE_UNUSED (e );
28+ MAYBE_UNUSED (pos );
29+ MAYBE_UNUSED (size );
30+ MAYBE_UNUSED (color );
31+ #endif
32+ }
1833
1934typedef struct castCircleCtx {
2035 bool hit ;
2136 b2ShapeId shapeID ;
37+ b2Vec2 point ;
2238} castCircleCtx ;
2339
2440float castCircleCallback (b2ShapeId shapeId , b2Vec2 point , b2Vec2 normal , float fraction , void * context ) {
@@ -33,6 +49,7 @@ float castCircleCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float f
3349 castCircleCtx * ctx = context ;
3450 ctx -> hit = true;
3551 ctx -> shapeID = shapeId ;
52+ ctx -> point = point ;
3653
3754 return 0.0f ;
3855}
@@ -122,52 +139,79 @@ void pathfindBFS(const iwEnv *e, uint8_t *flatPaths, uint16_t destCellIdx) {
122139 }
123140}
124141
125- float distanceWithDamping (const iwEnv * e , const droneEntity * drone , const b2Vec2 direction , const float linearDamping , const float steps ) {
126- float speed = drone -> weaponInfo -> recoilMagnitude * DRONE_INV_MASS ;
127- if (!b2VecEqual (drone -> velocity , b2Vec2_zero )) {
128- speed = b2Length (b2MulAdd (drone -> velocity , speed , direction ));
129- }
130-
131- const float damping = 1.0f + linearDamping * e -> deltaTime ;
142+ float distanceWithDamping (const iwEnv * e , const float speed , const float linearDamping , const float steps ) {
143+ const float damping = 1.0f + (linearDamping * e -> deltaTime );
132144 return speed * (damping / linearDamping ) * (1.0f - powf (1.0f / damping , steps ));
133145}
134146
147+ b2Vec2 positionWithDamping (const iwEnv * e , const droneEntity * drone , const b2Vec2 impulse , const float linearDamping , const float steps ) {
148+ const b2Vec2 newVel = b2Add (drone -> velocity , impulse );
149+ const float speed = b2Length (newVel );
150+ const float distance = distanceWithDamping (e , speed , linearDamping , steps );
151+ const b2Vec2 direction = b2Normalize (newVel );
152+ return b2MulAdd (drone -> pos , distance , direction );
153+ }
154+
155+ // returns true if drone can fire in the given direction without hitting
156+ // or getting too close to a death wall before it can fire again
135157bool safeToFire (iwEnv * e , const droneEntity * drone , const b2Vec2 direction ) {
158+ // don't shoot shotgun point blank at walls, the shots will immediately
159+ // bounce back and send the drone flying uncontrollably
160+ if (drone -> weaponInfo -> type == SHOTGUN_WEAPON ) {
161+ const b2Vec2 rayEnd = b2MulAdd (drone -> pos , SHOTGUN_DANGER_DISTANCE , direction );
162+ const b2Vec2 translation = b2Sub (rayEnd , drone -> pos );
163+ const b2QueryFilter filter = {.categoryBits = PROJECTILE_SHAPE , .maskBits = WALL_SHAPE | FLOATING_WALL_SHAPE };
164+ const b2RayResult rayRes = b2World_CastRayClosest (e -> worldID , drone -> pos , translation , filter );
165+ if (rayRes .hit ) {
166+ return false;
167+ }
168+ }
169+
136170 float shotWait ;
137171 if (drone -> ammo > 1 ) {
138172 shotWait = ((drone -> weaponInfo -> coolDown + drone -> weaponInfo -> charge ) / e -> deltaTime ) * 1.5f ;
139173 } else {
140174 shotWait = ((e -> defaultWeapon -> coolDown + e -> defaultWeapon -> charge ) / e -> deltaTime ) * 1.5f ;
141175 }
142- const b2Vec2 invDirection = b2MulSV (-1.0f , direction );
143- const float recoilDistance = distanceWithDamping (e , drone , invDirection , DRONE_LINEAR_DAMPING , shotWait );
176+ const float recoilSpeed = - drone -> weaponInfo -> recoilMagnitude * DRONE_INV_MASS ;
177+ const b2Vec2 recoil = b2MulSV (recoilSpeed , direction );
178+ const b2Vec2 recoilPos = positionWithDamping (e , drone , recoil , DRONE_LINEAR_DAMPING , shotWait );
144179
145- // e->debugPoint = b2MulAdd(drone->pos, recoilDistance, invDirection );
180+ addDebugPoint ( e , b2MulAdd (drone -> pos , 2.0f * DRONE_RADIUS , direction ), 0.5f , WHITE );
146181
147182 const b2Vec2 pos = drone -> pos ;
148- const b2Vec2 rayEnd = b2MulAdd ( pos , recoilDistance , invDirection ) ;
183+ const b2Vec2 rayEnd = recoilPos ;
149184 const b2Vec2 translation = b2Sub (rayEnd , pos );
150- const b2ShapeProxy cirProxy = b2MakeProxy (& pos , 1 , DRONE_RADIUS );
185+ float radius = DRONE_RADIUS ;
186+ if (drone -> shield != NULL ) {
187+ radius = DRONE_SHIELD_RADIUS ;
188+ }
189+ const b2ShapeProxy cirProxy = b2MakeProxy (& pos , 1 , radius );
151190 const b2QueryFilter filter = {.categoryBits = DRONE_SHAPE , .maskBits = WALL_SHAPE | FLOATING_WALL_SHAPE | DRONE_SHAPE };
152191
153192 castCircleCtx ctx = {0 };
154193 b2World_CastShape (e -> worldID , & cirProxy , translation , filter , castCircleCallback , & ctx );
155194 if (!ctx .hit ) {
195+ addDebugPoint (e , recoilPos , 0.5f , LIME );
156196 return true;
157197 } else {
158198 const entity * ent = b2Shape_GetUserData (ctx .shapeID );
159- if (ent -> type == STANDARD_WALL_ENTITY || ent -> type == BOUNCY_WALL_ENTITY || ent -> type == DRONE_ENTITY ) {
199+ if (entityTypeIsWall (ent -> type ) && ent -> type != DEATH_WALL_ENTITY ) {
200+ addDebugPoint (e , recoilPos , 0.5f , LIME );
160201 return true;
161202 }
162203 }
163204
205+ addDebugPoint (e , recoilPos , 0.5f , MAROON );
164206 return false;
165207}
166208
167209bool weaponSafeForMovement (const droneEntity * drone ) {
168210 switch (drone -> weaponInfo -> type ) {
169211 case IMPLODER_WEAPON :
170212 case MINE_LAUNCHER_WEAPON :
213+ case BLACK_HOLE_WEAPON :
214+ case NUKE_WEAPON :
171215 return false;
172216 default :
173217 return true;
@@ -236,6 +280,11 @@ float weaponIdealRangeSquared(const droneEntity *drone) {
236280}
237281
238282bool shouldShootAtEnemy (iwEnv * e , const droneEntity * drone , const droneEntity * enemyDrone , const b2Vec2 enemyDroneDirection ) {
283+ // don't shoot at shielded enemies with railguns since it will likely
284+ // bounce back and hit us
285+ if (drone -> weaponInfo -> type == SNIPER_WEAPON && enemyDrone -> shield != NULL ) {
286+ return false;
287+ }
239288 if (!safeToFire (e , drone , enemyDroneDirection )) {
240289 return false;
241290 }
@@ -257,6 +306,10 @@ bool shouldShootAtEnemy(iwEnv *e, const droneEntity *drone, const droneEntity *e
257306 if (ent == NULL || ent -> type != DRONE_ENTITY ) {
258307 return false;
259308 }
309+ const droneEntity * hitDrone = ent -> entity ;
310+ if (hitDrone -> idx != enemyDrone -> idx ) {
311+ return false;
312+ }
260313
261314 return true;
262315}
@@ -267,42 +320,42 @@ b2Vec2 predictiveAim(const droneEntity *drone, const droneEntity *enemyDrone, co
267320 return b2Normalize (b2Sub (predictedPos , drone -> pos ));
268321}
269322
270- void handleWallProximity ( iwEnv * e , const droneEntity * drone , const wallEntity * wall , const float distance , agentActions * actions ) {
271- if (distance > WALL_BRAKE_DISTANCE ) {
323+ void scriptedAgentBurst ( const droneEntity * drone , agentActions * actions ) {
324+ if (drone -> chargingBurst ) {
272325 return ;
326+ } else {
327+ actions -> chargingBurst = true;
273328 }
329+ }
274330
331+ void handleWallProximity (iwEnv * e , const droneEntity * drone , const wallEntity * wall , const float distance , agentActions * actions ) {
332+ // shoot to move away faster from a death wall if we're too close and it's safe
275333 const b2Vec2 wallDirection = b2Normalize (b2Sub (wall -> pos , drone -> pos ));
276- const float speedToWall = b2Dot (drone -> velocity , wallDirection );
277- if (speedToWall > WALL_BRAKE_SPEED ) {
278- float damping = DRONE_LINEAR_DAMPING ;
279- if (drone -> braking ) {
280- damping *= DRONE_BRAKE_DAMPING_COEF ;
281- }
282- const float travelDistance = distanceWithDamping (e , drone , wallDirection , damping , WALL_BRAKE_TIME / e -> deltaTime );
283- if (travelDistance >= distance ) {
284- actions -> brake = true;
285- }
334+ if (distance <= WALL_DANGER_DISTANCE && weaponSafeForMovement (drone ) && safeToFire (e , drone , wallDirection )) {
335+ actions -> aim = b2MulAdd (actions -> aim , distance , wallDirection );
336+ scriptedAgentShoot (drone , actions );
286337 }
287- if ( actions -> brake || distance <= WALL_AVOID_DISTANCE ) {
288- const b2Vec2 invWallDirection = b2MulSV ( -1.0f , wallDirection );
289- actions -> move = b2MulAdd (actions -> move , distance , invWallDirection );
338+ // move away from the wall if we're too close
339+ if ( distance <= WALL_AVOID_DISTANCE ) {
340+ actions -> move = b2MulAdd (actions -> move , - distance , wallDirection );
290341 }
342+ }
291343
292- if (distance > WALL_DANGER_DISTANCE ) {
344+ // charge burst until we're close enough to a death wall to burst off
345+ // of it
346+ void wallBurst (iwEnv * e , const droneEntity * drone , const float speed , const float distance , agentActions * actions ) {
347+ if (distance < DRONE_BURST_RADIUS_MIN ) {
348+ scriptedAgentBurst (drone , actions );
293349 return ;
294350 }
295351
296- // shoot to move away faster from a death wall if we're too close and it's safe
297- if (weaponSafeForMovement (drone ) && safeToFire (e , drone , wallDirection )) {
298- actions -> aim = wallDirection ;
299- scriptedAgentShoot (drone , actions );
352+ float damping = DRONE_LINEAR_DAMPING ;
353+ if (drone -> braking || actions -> brake ) {
354+ damping *= DRONE_BRAKE_DAMPING_COEF ;
300355 }
301- }
302-
303- void scriptedAgentBurst (const droneEntity * drone , agentActions * actions ) {
304- if (drone -> chargingBurst ) {
305- return ;
356+ const float travelDistance = distanceWithDamping (e , b2Length (drone -> velocity ), damping , e -> frameSkip );
357+ if (travelDistance > distance ) {
358+ scriptedAgentBurst (drone , actions );
306359 } else {
307360 actions -> chargingBurst = true;
308361 }
@@ -314,24 +367,26 @@ agentActions scriptedAgentActions(iwEnv *e, droneEntity *drone) {
314367 return actions ;
315368 }
316369
317- // keep the weapon charged and ready if it needs it
370+ // keep the weapon charged and ready
318371 if (drone -> weaponInfo -> charge != 0.0f ) {
319372 actions .chargingWeapon = true;
320373 actions .shoot = true;
321374 }
322375
323- // find the nearest death wall or floating wall
376+ // find the N nearest death walls or floating walls
324377 nearEntity nearWalls [MAX_NEAREST_WALLS ] = {0 };
325378 findNearWalls (e , drone , nearWalls , NUM_NEAR_WALLS );
326379
327- // find the distance between the closest points on the drone and the nearest wall
380+ // move away from and shoot at death walls if we're too close
381+ float closestWallDistance = FLT_MAX ;
328382 for (uint8_t i = 0 ; i < NUM_NEAR_WALLS ; i ++ ) {
329383 const wallEntity * wall = nearWalls [i ].entity ;
330384 if (wall -> type != DEATH_WALL_ENTITY ) {
331385 continue ;
332386 }
333387
334388 const b2DistanceOutput output = closestPoint (drone -> ent , wall -> ent );
389+ closestWallDistance = min (closestWallDistance , output .distance );
335390 handleWallProximity (e , drone , wall , output .distance , & actions );
336391 }
337392
@@ -345,9 +400,60 @@ agentActions scriptedAgentActions(iwEnv *e, droneEntity *drone) {
345400 }
346401
347402 const b2DistanceOutput output = closestPoint (drone -> ent , floatingWall -> ent );
403+ closestWallDistance = min (closestWallDistance , output .distance );
348404 handleWallProximity (e , drone , floatingWall , output .distance , & actions );
349405 }
350406
407+ // TODO: shoot mines around enemy, only if not too close
408+
409+ // if we are moving towards a death wall at a high speed, do everything
410+ // possible to avoid hitting it
411+ const float droneSpeed = b2Length (drone -> velocity );
412+ if (drone -> braking || drone -> chargingBurst || closestWallDistance <= WALL_BURST_CHECK_DISTANCE || droneSpeed >= WALL_BURST_CHECK_SPEED ) {
413+ float damping = DRONE_LINEAR_DAMPING ;
414+ if (drone -> braking || actions .brake ) {
415+ damping *= DRONE_BRAKE_DAMPING_COEF ;
416+ }
417+ const b2Vec2 recoilPos = positionWithDamping (e , drone , b2Vec2_zero , damping , 0.5f / e -> deltaTime );
418+ const b2Vec2 pos = drone -> pos ;
419+ const b2Vec2 rayEnd = recoilPos ;
420+ const b2Vec2 translation = b2Sub (rayEnd , pos );
421+ float radius = DRONE_RADIUS ;
422+ if (drone -> shield != NULL ) {
423+ radius = DRONE_SHIELD_RADIUS ;
424+ }
425+ const b2ShapeProxy cirProxy = b2MakeProxy (& pos , 1 , radius );
426+ const b2QueryFilter filter = {.categoryBits = DRONE_SHAPE , .maskBits = WALL_SHAPE | FLOATING_WALL_SHAPE };
427+
428+ castCircleCtx ctx = {0 };
429+ b2World_CastShape (e -> worldID , & cirProxy , translation , filter , castCircleCallback , & ctx );
430+ if (ctx .hit ) {
431+ const entity * ent = b2Shape_GetUserData (ctx .shapeID );
432+ if (entityTypeIsWall (ent -> type ) && ent -> type == DEATH_WALL_ENTITY ) {
433+ actions .brake = true;
434+ if (drone -> shield == NULL ) {
435+ wallBurst (e , drone , droneSpeed , b2Distance (drone -> pos , ctx .point ), & actions );
436+ }
437+
438+ const b2Vec2 droneDirection = b2Normalize (drone -> velocity );
439+ if (b2VecEqual (actions .move , b2Vec2_zero )) {
440+ actions .move = b2MulSV (-1.0f , droneDirection );
441+ }
442+ if (b2VecEqual (actions .aim , b2Vec2_zero ) && weaponSafeForMovement (drone )) {
443+ actions .aim = droneDirection ;
444+ scriptedAgentShoot (drone , & actions );
445+ }
446+ return actions ;
447+ }
448+ }
449+ }
450+
451+ // if we're close enough to a wall to need to shoot at it, don't
452+ // worry about enemies
453+ if (!b2VecEqual (actions .aim , b2Vec2_zero )) {
454+ return actions ;
455+ }
456+
351457 // get a weapon if the standard weapon is active
352458 if (drone -> weaponInfo -> type == STANDARD_WEAPON && cc_array_size (e -> pickups ) != 0 ) {
353459 nearEntity nearPickups [MAX_WEAPON_PICKUPS ] = {0 };
@@ -389,12 +495,10 @@ agentActions scriptedAgentActions(iwEnv *e, droneEntity *drone) {
389495 }
390496 }
391497 if (enemyDrone == NULL ) {
392- return actions ;
393- }
394-
395- // if we're close enough to a wall to need to shoot at it, don't
396- // worry about enemies
397- if (!b2VecEqual (actions .aim , b2Vec2_zero )) {
498+ // fight recoil if we're not otherwise moving
499+ if (b2VecEqual (actions .move , b2Vec2_zero ) && droneSpeed >= STABILIZE_MOVE_SPEED ) {
500+ actions .move = b2MulSV (-1.0f , b2Normalize (drone -> velocity ));
501+ }
398502 return actions ;
399503 }
400504
@@ -423,11 +527,8 @@ agentActions scriptedAgentActions(iwEnv *e, droneEntity *drone) {
423527 }
424528
425529 // fight recoil if we're not otherwise moving
426- if (b2VecEqual (actions .move , b2Vec2_zero )) {
427- const float speedSquared = b2LengthSquared (drone -> velocity );
428- if (speedSquared > MOVE_SPEED_SQUARED ) {
429- actions .move = b2MulSV (-1.0f , b2Normalize (drone -> velocity ));
430- }
530+ if (b2VecEqual (actions .move , b2Vec2_zero ) && droneSpeed >= STABILIZE_MOVE_SPEED ) {
531+ actions .move = b2MulSV (-1.0f , b2Normalize (drone -> velocity ));
431532 }
432533
433534 return actions ;
0 commit comments