1 #define GAMEPHYSICS 1 2 #include "game.h" 3 namespace physics 4 { 5 FVARW(gravity, 0, 50.f, 10000); // gravity 6 FVARW(jumpspeed, 0, 50.f, 10000); // extra velocity to add when jumping 7 FVARW(movespeed, 0, 50.f, 10000); // speed 8 FVARW(movecrawl, 0, 0.5f, 10000); // crawl modifier 9 FVARW(impulsespeed, 0, 50.f, 10000); // extra velocity to add when impulsing 10 11 VARW(impulsestyle, 0, 1, 3); // impulse style; 0 = off, 1 = touch and count, 2 = count only, 3 = freestyle 12 VARW(impulsemeter, 0, 30000, INT_MAX-1); // impulse dash length; 0 = unlimited, anything else = timer 13 VARW(impulsecost, 0, 1000, INT_MAX-1); // cost of impulse jump 14 VARW(impulsecount, 0, 5, INT_MAX-1); // number of impulse actions per air transit 15 VARW(impulseskate, 0, 1000, INT_MAX-1); // length of time a run along a wall can last 16 FVARW(impulseregen, 0, 5, 10000); // impulse regen multiplier 17 18 FVARW(liquidspeed, 0, 0.85f, 1); 19 FVARW(liquidcurb, 0, 10.f, 10000); 20 FVARW(floorcurb, 0, 5.f, 10000); 21 FVARW(aircurb, 0, 25.f, 10000); 22 23 FVARW(stairheight, 0, 4.1f, 10000); 24 FVARW(floorz, 0, 0.867f, 1); 25 FVARW(slopez, 0, 0.5f, 1); 26 FVARW(wallz, 0, 0.2f, 1); 27 FVARW(stepspeed, 1e-3f, 1.f, 10000); 28 FVARW(ladderspeed, 1e-3f, 1.f, 10000); 29 30 FVARP(floatspeed, 1e-3f, 75, 10000); 31 FVARP(floatcurb, 0, 1.f, 10000); 32 33 FVARP(impulseroll, 0, 10, 90); 34 FVARP(impulsereflect, 0, 155, 360); 35 36 VARP(physframetime, 5, 5, 20); 37 VARP(physinterp, 0, 1, 1); 38 39 VARP(impulsedash, 0, 1, 3); // determines how impulsedash works, 0 = off, 1 = double jump, 2 = double tap, 3 = double jump only 40 41 int physsteps = 0, lastphysframe = 0, lastmove = 0, lastdirmove = 0, laststrafe = 0, lastdirstrafe = 0; 42 43 #define imov(name,v,u,d,s,os) \ 44 void do##name(bool down) \ 45 { \ 46 game::player1->s = down; \ 47 int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \ 48 game::player1->v = dir; \ 49 if(down) \ 50 { \ 51 if(FWV(impulsestyle) && impulsedash == 2 && last##v && lastdir##v && dir == lastdir##v && lastmillis-last##v < PHYSMILLIS) \ 52 { \ 53 game::player1->action[AC_DASH] = true; \ 54 game::player1->actiontime[AC_DASH] = lastmillis; \ 55 } \ 56 last##v = lastmillis; lastdir##v = dir; \ 57 last##u = lastdir##u = 0; \ 58 } \ 59 } \ 60 ICOMMAND(name, "D", (int *down), { do##name(*down!=0); }); 61 62 imov(backward, move, strafe, -1, k_down, k_up); 63 imov(forward, move, strafe, 1, k_up, k_down); 64 imov(left, strafe, move, 1, k_left, k_right); 65 imov(right, strafe, move, -1, k_right, k_left); 66 67 // inputs doaction(int type,bool down)68 void doaction(int type, bool down) 69 { 70 if(type < AC_TOTAL && type > -1) 71 { 72 if(game::allowmove(game::player1)) 73 { 74 if(type == AC_CROUCH) 75 { 76 if(game::player1->action[type] != down) 77 { 78 if(game::player1->actiontime[type] >= 0) game::player1->actiontime[type] = lastmillis-max(PHYSMILLIS-(lastmillis-game::player1->actiontime[type]), 0); 79 else if(down) game::player1->actiontime[type] = -game::player1->actiontime[type]; 80 } 81 } 82 else if(down) game::player1->actiontime[type] = lastmillis; 83 game::player1->action[type] = down; 84 } 85 else 86 { 87 game::player1->action[type] = false; 88 if(type == AC_ATTACK && down) game::respawn(game::player1); 89 } 90 } 91 } 92 ICOMMAND(action, "Di", (int *n, int *i), { doaction(*i, *n!=0); }); 93 issolid(physent * d,physent * e)94 bool issolid(physent *d, physent *e) 95 { 96 if(e && e->type == ENT_PROJ) 97 { 98 projent *p = (projent *)e; 99 if(p->hit == d || !(p->projcollide&COLLIDE_PLAYER)) return false; 100 if(p->owner == d && (!(p->projcollide&COLLIDE_OWNER) || !p->escaped)) return false; 101 } 102 if(d->state == CS_ALIVE) 103 { 104 if(d->type == ENT_PLAYER && ((gameent *)d)->protect(lastmillis, m_protect(game::gamemode, game::mutators))) 105 return false; 106 return true; 107 } 108 return d->state == CS_DEAD || d->state == CS_WAITING; 109 } 110 iscrouching(physent * d)111 bool iscrouching(physent *d) 112 { 113 if(d->type == ENT_PLAYER || d->type == ENT_AI) 114 { 115 gameent *e = (gameent *)d; 116 return e->action[AC_CROUCH] || e->actiontime[AC_CROUCH] < 0 || lastmillis-e->actiontime[AC_CROUCH] <= PHYSMILLIS; 117 } 118 return false; 119 } liquidcheck(physent * d)120 bool liquidcheck(physent *d) { return d->inliquid && d->submerged > 0.8f; } 121 liquidmerge(physent * d,float from,float to)122 float liquidmerge(physent *d, float from, float to) 123 { 124 if(d->inliquid) 125 { 126 if(d->physstate >= PHYS_SLIDE && d->submerged < 1.f) 127 return from-((from-to)*d->submerged); 128 else return to; 129 } 130 return from; 131 } 132 jumpforce(physent * d,bool liquid)133 float jumpforce(physent *d, bool liquid) { return FWV(jumpspeed)*(d->weight/100.f)*(liquid ? liquidmerge(d, 1.f, FWV(liquidspeed)) : 1.f); } impulseforce(physent * d)134 float impulseforce(physent *d) { return FWV(impulsespeed)*(d->weight/100.f); } gravityforce(physent * d)135 float gravityforce(physent *d) { return FWV(gravity)*(d->weight/100.f); } 136 stepforce(physent * d,bool up)137 float stepforce(physent *d, bool up) 138 { 139 if(up && d->onladder) return ladderspeed; 140 if(d->physstate > PHYS_FALL) return stepspeed; 141 return 1.f; 142 } 143 sticktofloor(physent * d)144 bool sticktofloor(physent *d) 145 { 146 if(!d->onladder && !liquidcheck(d) && (d->type == ENT_PLAYER || d->type == ENT_AI) && FWV(gravity) > 0) 147 { 148 gameent *e = (gameent *)d; 149 if(e->turnside || (e->lastpush && lastmillis-e->lastpush <= PHYSMILLIS) || (e->actiontime[AC_JUMP] && lastmillis-e->actiontime[AC_JUMP] <= PHYSMILLIS)) 150 return false; 151 return true; 152 } 153 return false; 154 } 155 sticktospecial(physent * d)156 bool sticktospecial(physent *d) 157 { 158 if(d->onladder) return true; 159 if(d->type == ENT_PLAYER || d->type == ENT_AI) { if(((gameent *)d)->turnside) return true; } 160 return false; 161 } 162 canimpulse(physent * d,int cost)163 bool canimpulse(physent *d, int cost) 164 { 165 if((d->type == ENT_PLAYER || d->type == ENT_AI) && FWV(impulsestyle)) 166 { 167 gameent *e = (gameent *)d; 168 if(FWV(impulsemeter) && e->impulse[IM_METER]+(cost > 0 ? cost : FWV(impulsecost)) > FWV(impulsemeter)) return false; 169 if(cost <= 0) 170 { 171 if(e->impulse[IM_TIME] && lastmillis-e->impulse[IM_TIME] <= PHYSMILLIS) return false; 172 if(FWV(gravity) > 0) 173 { 174 if(FWV(impulsestyle) <= 2 && e->impulse[IM_COUNT] >= FWV(impulsecount)) return false; 175 if(cost == 0 && FWV(impulsestyle) == 1 && e->impulse[IM_TYPE] > IM_T_NONE && e->impulse[IM_TYPE] < IM_T_WALL) return false; 176 } 177 } 178 return true; 179 } 180 return false; 181 } 182 movevelocity(physent * d)183 float movevelocity(physent *d) 184 { 185 if(d->type == ENT_CAMERA) return game::player1->maxspeed*(game::player1->weight/100.f)*(floatspeed/100.0f); 186 else if(d->type == ENT_PLAYER || d->type == ENT_AI) 187 { 188 if(d->state == CS_EDITING || d->state == CS_SPECTATOR) return d->maxspeed*(d->weight/100.f)*(floatspeed/100.0f); 189 else 190 { 191 float speed = FWV(movespeed); 192 if(iscrouching(d) || (d == game::player1 && game::inzoom())) speed *= FWV(movecrawl); 193 if(FWV(impulsestyle) && ((gameent *)d)->action[AC_IMPULSE] && (d->move || d->strafe) && (!FWV(impulsemeter) || ((gameent *)d)->impulse[IM_METER] < FWV(impulsemeter))) 194 speed += FWV(impulsespeed)*(d->move < 0 ? 0.5f : 1); 195 return max(d->maxspeed,1.f)*(d->weight/100.f)*(speed/100.f); 196 } 197 } 198 return max(d->maxspeed,1.f); 199 } 200 movepitch(physent * d)201 bool movepitch(physent *d) 202 { 203 if(d->type == ENT_CAMERA || d->state == CS_EDITING || d->state == CS_SPECTATOR) return true; 204 if(d->state == CS_ALIVE && FWV(gravity) <= 0 && d->physstate == PHYS_FALL) return true; 205 return false; 206 } 207 recalcdir(physent * d,const vec & oldvel,vec & dir)208 void recalcdir(physent *d, const vec &oldvel, vec &dir) 209 { 210 float speed = oldvel.magnitude(); 211 if(speed > 1e-6f) 212 { 213 float step = dir.magnitude(); 214 dir = d->vel; 215 dir.add(d->falling); 216 dir.mul(step/speed); 217 } 218 } 219 slideagainst(physent * d,vec & dir,const vec & obstacle,bool foundfloor,bool slidecollide)220 void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide) 221 { 222 vec wall(obstacle); 223 if(foundfloor ? wall.z > 0 : slidecollide) 224 { 225 wall.z = 0; 226 if(!wall.iszero()) wall.normalize(); 227 } 228 vec oldvel(d->vel); 229 oldvel.add(d->falling); 230 d->vel.project(wall); 231 d->falling.project(wall); 232 recalcdir(d, oldvel, dir); 233 } 234 switchfloor(physent * d,vec & dir,const vec & floor)235 void switchfloor(physent *d, vec &dir, const vec &floor) 236 { 237 if(floor.z >= floorz) d->falling = vec(0, 0, 0); 238 239 vec oldvel(d->vel); 240 oldvel.add(d->falling); 241 if(dir.dot(floor) >= 0) 242 { 243 if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return; 244 d->vel.projectxy(floor, 0.0f); 245 } 246 else d->vel.projectxy(floor); 247 d->falling.project(floor); 248 recalcdir(d, oldvel, dir); 249 } 250 trystepup(physent * d,vec & dir,const vec & obstacle,float maxstep,const vec & floor)251 bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor) 252 { 253 vec old(d->o), stairdir = (obstacle.z >= 0 && obstacle.z < slopez ? vec(-obstacle.x, -obstacle.y, 0) : vec(dir.x, dir.y, 0)).rescale(1); 254 float force = stepforce(d, true); 255 if(d->onladder) 256 { 257 vec laddir = vec(stairdir).add(vec(0, 0, maxstep)).mul(0.1f*force); 258 loopi(2) 259 { 260 d->o.add(laddir); 261 if(collide(d)) 262 { 263 if(d->physstate == PHYS_FALL || d->floor != floor) 264 { 265 d->timeinair = 0; 266 d->floor = vec(0, 0, 1); 267 switchfloor(d, dir, d->floor); 268 } 269 d->physstate = PHYS_STEP_UP; 270 return true; 271 } 272 d->o = old; // try again, but only up 273 laddir.x = laddir.y = 0; 274 } 275 return false; 276 } 277 bool cansmooth = true; 278 d->o = old; 279 /* check if there is space atop the stair to move to */ 280 if(d->physstate != PHYS_STEP_UP) 281 { 282 vec checkdir = stairdir; 283 checkdir.mul(0.1f); 284 checkdir.z += maxstep + 0.1f; 285 checkdir.mul(force); 286 d->o.add(checkdir); 287 if(!collide(d)) 288 { 289 d->o = old; 290 if(collide(d, vec(0, 0, -1), slopez)) return false; 291 cansmooth = false; 292 } 293 } 294 295 if(cansmooth) 296 { 297 d->o = old; 298 vec checkdir = stairdir; 299 checkdir.z += 1; 300 checkdir.mul(maxstep*force); 301 d->o.add(checkdir); 302 if(!collide(d, checkdir)) 303 { 304 if(collide(d, vec(0, 0, -1), slopez)) 305 { 306 d->o = old; 307 return false; 308 } 309 } 310 /* try stepping up half as much as forward */ 311 d->o = old; 312 vec smoothdir = vec(dir.x, dir.y, 0).mul(force); 313 float magxy = smoothdir.magnitude(); 314 if(magxy > 1e-9f) 315 { 316 if(magxy > 2*dir.z) 317 { 318 smoothdir.mul(1/magxy); 319 smoothdir.z = 0.5f; 320 smoothdir.mul(dir.magnitude()*force/smoothdir.magnitude()); 321 } 322 else smoothdir.z = dir.z; 323 d->o.add(smoothdir); 324 d->o.z += maxstep*force + 0.1f*force; 325 if(collide(d, smoothdir)) 326 { 327 d->o.z -= maxstep*force + 0.1f*force; 328 if(d->physstate == PHYS_FALL || d->floor != floor) 329 { 330 d->timeinair = 0; 331 d->floor = floor; 332 switchfloor(d, dir, d->floor); 333 } 334 d->physstate = PHYS_STEP_UP; 335 return true; 336 } 337 } 338 } 339 340 /* try stepping up */ 341 d->o = old; 342 d->o.z += dir.magnitude()*force; 343 if(collide(d, vec(0, 0, 1))) 344 { 345 if(d->physstate == PHYS_FALL || d->floor != floor) 346 { 347 d->timeinair = 0; 348 d->floor = floor; 349 switchfloor(d, dir, d->floor); 350 } 351 if(cansmooth) d->physstate = PHYS_STEP_UP; 352 return true; 353 } 354 d->o = old; 355 return false; 356 } 357 trystepdown(physent * d,vec & dir,float step,float xy,float z)358 bool trystepdown(physent *d, vec &dir, float step, float xy, float z) 359 { 360 vec stepdir(dir.x, dir.y, 0); 361 stepdir.z = -stepdir.magnitude2()*z/xy; 362 if(!stepdir.z) return false; 363 stepdir.normalize(); 364 365 vec old(d->o); 366 d->o.add(vec(stepdir).mul(stairheight/fabs(stepdir.z))).z -= stairheight; 367 if(!collide(d, vec(0, 0, -1), slopez)) 368 { 369 d->o = old; 370 d->o.add(vec(stepdir).mul(step)); 371 if(collide(d, vec(0, 0, -1))) 372 { 373 stepdir.mul(-stepdir.z).z += 1; 374 stepdir.normalize(); 375 switchfloor(d, dir, stepdir); 376 d->floor = stepdir; 377 return true; 378 } 379 } 380 d->o = old; 381 return false; 382 } 383 384 falling(physent * d,vec & dir,const vec & floor)385 void falling(physent *d, vec &dir, const vec &floor) 386 { 387 if(d->physstate >= PHYS_FLOOR && (d->physstate != PHYS_STEP_DOWN || dir.z < -0.25f*dir.magnitude2()) && sticktofloor(d)) 388 { 389 vec moved(d->o); 390 d->o.z -= stairheight+0.1f; 391 if(!collide(d, vec(0, 0, -1), slopez)) 392 { 393 d->o = moved; 394 d->timeinair = 0; 395 d->physstate = PHYS_STEP_DOWN; 396 return; 397 } 398 else d->o = moved; 399 } 400 401 if(floor.z > 0.0f && floor.z < slopez) 402 { 403 if(floor.z >= wallz) switchfloor(d, dir, floor); 404 d->timeinair = 0; 405 d->physstate = PHYS_SLIDE; 406 d->floor = floor; 407 } 408 else if(sticktospecial(d)) 409 { 410 d->timeinair = 0; 411 d->physstate = PHYS_FLOOR; 412 d->floor = vec(0, 0, 1); 413 } 414 else d->physstate = PHYS_FALL; 415 } 416 landing(physent * d,vec & dir,const vec & floor,bool collided)417 void landing(physent *d, vec &dir, const vec &floor, bool collided) 418 { 419 switchfloor(d, dir, floor); 420 d->timeinair = 0; 421 if((d->physstate!=PHYS_STEP_UP && d->physstate!=PHYS_STEP_DOWN) || !collided) 422 d->physstate = floor.z >= floorz ? PHYS_FLOOR : PHYS_SLOPE; 423 d->floor = floor; 424 } 425 findfloor(physent * d,bool collided,const vec & obstacle,bool & slide,vec & floor)426 bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor) 427 { 428 bool found = false; 429 vec moved(d->o); 430 d->o.z -= 0.1f; 431 if(sticktospecial(d)) 432 { 433 floor = vec(0, 0, 1); 434 found = true; 435 } 436 else if(d->physstate != PHYS_FALL && !collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz)) 437 { 438 floor = wall; 439 found = true; 440 } 441 else if(collided && obstacle.z >= slopez) 442 { 443 floor = obstacle; 444 found = true; 445 slide = false; 446 } 447 else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE) 448 { 449 if(!collide(d, vec(0, 0, -1)) && wall.z > 0.0f) 450 { 451 floor = wall; 452 if(floor.z >= slopez) found = true; 453 } 454 } 455 else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f) 456 { 457 if(!collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1))) 458 { 459 floor = wall; 460 if(floor.z >= slopez && floor.z < 1.0f) found = true; 461 } 462 } 463 if(collided && (!found || obstacle.z > floor.z)) 464 { 465 floor = obstacle; 466 slide = !found && (floor.z < wallz || floor.z >= slopez); 467 } 468 d->o = moved; 469 return found; 470 } 471 move(physent * d,vec & dir)472 bool move(physent *d, vec &dir) 473 { 474 vec old(d->o), obstacle; d->o.add(dir); 475 bool collided = false, slidecollide = false; 476 if(!collide(d, dir)) 477 { 478 obstacle = wall; 479 /* check to see if there is an obstacle that would prevent this one from being used as a floor */ 480 if((d->type==ENT_PLAYER || d->type==ENT_AI) && ((wall.z>=slopez && dir.z<0) || (wall.z<=-slopez && dir.z>0)) && (dir.x || dir.y) && !collide(d, vec(dir.x, dir.y, 0))) 481 { 482 if(wall.dot(dir) >= 0) slidecollide = true; 483 obstacle = wall; 484 } 485 486 d->o = old; 487 d->o.z -= stairheight; 488 d->zmargin = -stairheight; 489 if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || d->physstate == PHYS_STEP_DOWN || (!collide(d, vec(0, 0, -1), slopez) && (d->physstate == PHYS_STEP_UP || wall.z >= floorz || d->onladder))) 490 { 491 d->o = old; 492 d->zmargin = 0; 493 if(trystepup(d, dir, obstacle, stairheight, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || d->physstate == PHYS_STEP_DOWN || d->onladder ? d->floor : vec(wall))) 494 return true; 495 } 496 else 497 { 498 d->o = old; 499 d->zmargin = 0; 500 } 501 collided = true; // can't step over the obstacle, so just slide against it 502 } 503 else if(d->physstate == PHYS_STEP_UP || d->onladder) 504 { 505 if(!collide(d, vec(0, 0, -1), slopez)) 506 { 507 d->o = old; 508 if(trystepup(d, dir, vec(0, 0, 1), stairheight, d->onladder ? d->floor : vec(wall))) return true; 509 d->o.add(dir); 510 } 511 } 512 else if((d->type == ENT_PLAYER || d->type == ENT_AI) && d->physstate == PHYS_STEP_DOWN && sticktofloor(d)) 513 { 514 d->o = old; 515 float step = dir.magnitude(); 516 if(trystepdown(d, dir, step, 2, 1)) return true; 517 else if(trystepdown(d, dir, step, 1, 1)) return true; 518 else if(trystepdown(d, dir, step, 1, 2)) return true; 519 d->o = old; d->o.add(dir); 520 } 521 vec floor(0, 0, 0); 522 bool slide = collided, found = findfloor(d, collided, obstacle, slide, floor); 523 if(slide || (!collided && floor.z > 0 && floor.z < wallz)) 524 { 525 slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide); 526 d->blocked = true; 527 } 528 if(found) landing(d, dir, floor, collided); 529 else falling(d, dir, floor); 530 return !collided; 531 } 532 modifyinput(gameent * d,vec & m,bool wantsmove,bool floating,int millis)533 void modifyinput(gameent *d, vec &m, bool wantsmove, bool floating, int millis) 534 { 535 if(floating) 536 { 537 if(game::allowmove(d) && d->action[AC_JUMP]) d->vel.z += jumpforce(d, false); 538 } 539 else if(game::allowmove(d)) 540 { 541 bool onfloor = d->physstate >= PHYS_SLOPE || d->onladder || liquidcheck(d); 542 if(millis && FWV(impulsestyle)) 543 { 544 if(d->action[AC_IMPULSE] && (d->move || d->strafe)) 545 { 546 if(canimpulse(d, millis)) d->impulse[IM_METER] += millis; 547 else d->action[AC_IMPULSE] = false; 548 } 549 else if(d->impulse[IM_METER] > 0 && FWV(impulseregen) > 0) 550 { 551 int timeslice = max(int(millis*FWV(impulseregen)), 1); 552 if(iscrouching(d)) timeslice += timeslice; 553 if(d->move || d->strafe) timeslice -= timeslice/2; 554 if(d->physstate == PHYS_FALL && !d->onladder) timeslice -= timeslice/2; 555 if((d->impulse[IM_METER] -= timeslice) < 0) d->impulse[IM_METER] = 0; 556 } 557 } 558 559 if(d->turnside && (!FWV(impulsestyle) || d->impulse[IM_TYPE] != IM_T_SKATE || lastmillis-d->impulse[IM_TIME] > FWV(impulseskate))) 560 { 561 d->turnside = 0; 562 d->resetphys(); 563 } 564 565 if(FWV(impulsestyle) && (d->ai || (impulsedash > 0 && impulsedash < 3)) && canimpulse(d) && (d->move || d->strafe) && (!d->ai && impulsedash == 2 ? d->action[AC_DASH] : d->action[AC_JUMP] && !onfloor)) 566 { 567 float mag = impulseforce(d)+max(d->vel.magnitude(), 1.f); 568 vecfromyawpitch(d->aimyaw, !d->ai && impulsedash == 2 ? max(d->aimpitch, 10.f) : d->aimpitch, d->move, d->strafe, d->vel); 569 d->vel.normalize().mul(mag); d->vel.z += mag/4; 570 d->doimpulse(FWV(impulsecost), IM_T_DASH, lastmillis); 571 playsound(S_IMPULSE, d->o, d); game::impulseeffect(d, true); 572 client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE); 573 } 574 if(!d->turnside && onfloor) 575 { 576 if(d->action[AC_JUMP]) 577 { 578 d->vel.z += jumpforce(d, true); 579 if(d->inliquid) 580 { 581 float scale = liquidmerge(d, 1.f, FWV(liquidspeed)); 582 d->vel.x *= scale; 583 d->vel.y *= scale; 584 } 585 d->resetphys(); 586 playsound(S_JUMP, d->o, d); 587 regularshape(PART_SMOKE, int(d->radius), 0x111111, 21, 20, 100, d->feetpos(), 1, 1, -10, 0, 10.f); 588 client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_JUMP); 589 } 590 } 591 else 592 { 593 if(FWV(impulsestyle) && !d->turnside && !onfloor && canimpulse(d) && d->action[AC_JUMP]) 594 { 595 d->vel.z += impulseforce(d)*1.5f; 596 d->doimpulse(FWV(impulsecost), IM_T_BOOST, lastmillis); 597 playsound(S_IMPULSE, d->o, d); 598 game::impulseeffect(d, true); 599 client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE); 600 } 601 if(FWV(impulsestyle) && (d->turnside || (canimpulse(d, -1) && d->action[AC_SPECIAL])) && !d->inliquid && !d->onladder) 602 { 603 loopi(d->turnside ? 3 : 1) 604 { 605 vec oldpos = d->o, dir; 606 int move = i ? (i%2 ? 1 : -1) : d->move, strafe = i >= 2 ? d->turnside : d->strafe; 607 if(!move && !strafe) continue; 608 vecfromyawpitch(d->aimyaw, 0, move, strafe, dir); 609 d->o.add(dir); 610 if(collide(d, dir) || wall.iszero()) 611 { 612 d->o = oldpos; 613 if(i >= (d->turnside ? 2 : 0)) { if(d->turnside) { d->turnside = 0; d->resetphys(); } break; } 614 continue; 615 } 616 d->o = oldpos; 617 wall.normalize(); 618 float yaw = 0, pitch = 0; 619 vectoyawpitch(wall, yaw, pitch); 620 float off = yaw-d->aimyaw; 621 if(off > 180) off -= 360; 622 else if(off < -180) off += 360; 623 int key = (d->turnside && d->action[AC_JUMP]) ? AC_JUMP : ((!d->turnside && d->action[AC_SPECIAL] && fabs(off) >= impulsereflect) ? AC_SPECIAL : -1); 624 if(key >= 0 && canimpulse(d, -1)) 625 { 626 float mag = (impulseforce(d)+max(d->vel.magnitude(), 1.f))/2; 627 d->vel = vec(d->turnside ? wall : vec(dir).reflect(wall)).add(vec(d->vel).reflect(wall).rescale(1)).mul(mag/2); 628 vectoyawpitch(d->vel, yaw, pitch); 629 d->vel.z += d->turnside ? mag : mag/2; 630 off = yaw-d->aimyaw; 631 if(off > 180) off -= 360; 632 else if(off < -180) off += 360; 633 d->doimpulse(FWV(impulsecost), IM_T_KICK, lastmillis); 634 d->action[key] = false; 635 d->turnmillis = PHYSMILLIS; 636 d->turnside = (off < 0 ? -1 : 1)*(move ? move : 1); 637 d->turnyaw = off; 638 d->turnroll = 0; 639 playsound(S_IMPULSE, d->o, d); 640 game::impulseeffect(d, true); 641 client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_IMPULSE); 642 } 643 else if(d->turnside || (d->action[AC_SPECIAL] && canimpulse(d, -1))) 644 { 645 if(off < 0) yaw += 90; 646 else yaw -= 90; 647 while(yaw >= 360) yaw -= 360; 648 while(yaw < 0) yaw += 360; 649 vec rft; vecfromyawpitch(yaw, 0, 1, 0, rft); 650 if(!d->turnside) 651 { 652 float mag = max(d->vel.magnitude(), 3.f); 653 d->vel = vec(rft).mul(mag); 654 off = yaw-d->aimyaw; 655 if(off > 180) off -= 360; 656 else if(off < -180) off += 360; 657 d->doimpulse(FWV(impulsecost), IM_T_SKATE, lastmillis); 658 d->action[AC_SPECIAL] = false; 659 d->turnmillis = PHYSMILLIS; 660 d->turnside = (off < 0 ? -1 : 1)*(move ? move : 1); 661 d->turnyaw = off; 662 d->turnroll = (impulseroll*d->turnside)-d->roll; 663 } 664 else if(d->vel.magnitude() >= 3) m = rft; // re-project and override 665 else { d->turnside = 0; d->resetphys(); break; } 666 } 667 break; 668 } 669 } 670 else if(d->turnside) { d->turnside = 0; d->resetphys(); } 671 } 672 } 673 d->action[AC_JUMP] = d->action[AC_DASH] = false; 674 if((d->physstate == PHYS_FALL && !d->onladder) || d->turnside) d->timeinair += millis; 675 else d->dojumpreset(); 676 } 677 modifyvelocity(physent * pl,bool local,bool floating,int millis)678 void modifyvelocity(physent *pl, bool local, bool floating, int millis) 679 { 680 vec m(0, 0, 0); 681 bool wantsmove = game::allowmove(pl) && (pl->move || pl->strafe); 682 if(wantsmove) 683 { 684 vecfromyawpitch(pl->aimyaw, floating || (pl->inliquid && (liquidcheck(pl) || pl->aimpitch < 0.f)) || movepitch(pl) ? pl->aimpitch : 0, pl->move, pl->strafe, m); 685 if((pl->type == ENT_PLAYER || pl->type == ENT_AI) && !floating && pl->physstate >= PHYS_SLOPE) 686 { // move up or down slopes in air but only move up slopes in liquid 687 float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z; 688 m.z = liquidcheck(pl) ? max(m.z, dz) : dz; 689 } 690 m.normalize(); 691 } 692 if(local && (pl->type == ENT_PLAYER || pl->type == ENT_AI)) modifyinput((gameent *)pl, m, wantsmove, floating, millis); 693 else if(pl->physstate == PHYS_FALL && !pl->onladder) pl->timeinair += millis; 694 else pl->timeinair = 0; 695 vec d = vec(m).mul(movevelocity(pl)); 696 if((pl->type == ENT_PLAYER || pl->type == ENT_AI) && !floating && !pl->inliquid) 697 d.mul(pl->move || pl->strafe || pl->physstate == PHYS_FALL || pl->physstate == PHYS_STEP_DOWN ? (pl->strafe || pl->move <= 0 ? 1.25f : 1.5f) : 1.0f); 698 if(floating || pl->type==ENT_CAMERA) pl->vel.lerp(d, pl->vel, pow(max(1.0f - 1.0f/floatcurb, 0.0f), millis/20.0f)); 699 else 700 { 701 bool floor = pl->physstate >= PHYS_SLOPE; 702 if(floor && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && ((FWV(impulsemeter) && ((gameent *)pl)->action[AC_IMPULSE] && ((gameent *)pl)->impulse[IM_METER] < FWV(impulsemeter)))) 703 floor = false; 704 float curb = floor ? FWV(floorcurb) : FWV(aircurb), fric = pl->inliquid ? liquidmerge(pl, curb, FWV(liquidcurb)) : curb; 705 pl->vel.lerp(d, pl->vel, pow(max(1.0f - 1.0f/fric, 0.0f), millis/20.0f)); 706 } 707 } 708 modifygravity(physent * pl,int curtime)709 void modifygravity(physent *pl, int curtime) 710 { 711 float secs = curtime/1000.0f; 712 vec g(0, 0, 0); 713 if(FWV(gravity) > 0) 714 { 715 if(pl->physstate == PHYS_FALL) g.z -= gravityforce(pl)*secs; 716 else if(pl->floor.z > 0 && pl->floor.z < floorz) 717 { 718 g.z = -1; 719 g.project(pl->floor); 720 g.normalize(); 721 g.mul(gravityforce(pl)*secs); 722 } 723 if(!liquidcheck(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g); 724 } 725 else pl->falling = g; 726 if(liquidcheck(pl) || pl->physstate >= PHYS_SLOPE) 727 { 728 float fric = liquidcheck(pl) ? liquidmerge(pl, FWV(aircurb), FWV(liquidcurb)) : FWV(floorcurb), 729 c = liquidcheck(pl) ? 1.0f : clamp((pl->floor.z - slopez)/(floorz-slopez), 0.0f, 1.0f); 730 pl->falling.mul(pow(max(1.0f - c/fric, 0.0f), curtime/20.0f)); 731 } 732 } 733 updatematerial(physent * pl,const vec & center,float radius,const vec & bottom,bool local,bool floating)734 void updatematerial(physent *pl, const vec ¢er, float radius, const vec &bottom, bool local, bool floating) 735 { 736 int matid = lookupmaterial(bottom), curmat = matid&MATF_VOLUME, flagmat = matid&MATF_FLAGS, 737 oldmat = pl->inmaterial&MATF_VOLUME; 738 739 if(!floating && curmat != oldmat) 740 { 741 #define mattrig(mo,mcol,ms,mt,mz,mq,mp,mw) \ 742 { \ 743 int col = (int(mcol[2]*mq) + (int(mcol[1]*mq) << 8) + (int(mcol[0]*mq) << 16)); \ 744 regularshape(mp, mt, col, 21, 20, mz, mo, ms, 1, 10, 0, 20); \ 745 if(mw >= 0) playsound(mw, mo, pl); \ 746 } 747 if(curmat == MAT_WATER || oldmat == MAT_WATER) 748 mattrig(bottom, watercol, 0.5f, int(radius), PHYSMILLIS, 0.25f, PART_SPARK, curmat != MAT_WATER ? S_SPLASH2 : S_SPLASH1); 749 if(curmat == MAT_LAVA) mattrig(vec(bottom).add(vec(0, 0, radius)), lavacol, 2.f, int(radius), PHYSMILLIS*2, 1.f, PART_FIREBALL, S_BURNING); 750 } 751 if(local && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && pl->state == CS_ALIVE && flagmat == MAT_DEATH) 752 game::suicide((gameent *)pl, (curmat == MAT_LAVA ? HIT_MELT : (curmat == MAT_WATER ? HIT_WATER : HIT_DEATH))|HIT_FULL); 753 pl->inmaterial = matid; 754 if((pl->inliquid = !floating && isliquid(curmat)) != false) 755 { 756 float frac = float(center.z-bottom.z)/10.f, sub = pl->submerged; 757 vec tmp = bottom; 758 int found = 0; 759 loopi(10) 760 { 761 tmp.z += frac; 762 if(!isliquid(lookupmaterial(tmp)&MATF_VOLUME)) 763 { 764 found = i+1; 765 break; 766 } 767 } 768 pl->submerged = found ? found/10.f : 1.f; 769 if(local) 770 { 771 if(curmat == MAT_WATER && (pl->type == ENT_PLAYER || pl->type == ENT_AI) && pl->submerged >= 0.5f && ((gameent *)pl)->lastfire) 772 { 773 gameent *d = (gameent *)pl; 774 if(issound(d->fschan)) removesound(d->fschan); 775 d->fschan = -1; d->lastfire = 0; 776 playsound(S_EXTINGUISH, d->o, d, 0, d != game::player1 ? 128 : 224, -1, -1); 777 client::addmsg(SV_PHYS, "ri2", d->clientnum, SPHY_EXTINGUISH); 778 } 779 if(pl->physstate < PHYS_SLIDE && sub >= 0.5f && pl->submerged < 0.5f && pl->vel.z > 1e-16f) 780 pl->vel.z = max(pl->vel.z, max(jumpforce(pl, false), max(gravityforce(pl), 50.f))); 781 } 782 } 783 else pl->submerged = 0; 784 pl->onladder = !floating && flagmat == MAT_LADDER; 785 if(pl->onladder && pl->physstate < PHYS_SLIDE) pl->floor = vec(0, 0, 1); 786 } 787 updatematerial(physent * pl,bool local,bool floating)788 void updatematerial(physent *pl, bool local, bool floating) 789 { 790 updatematerial(pl, pl->o, pl->height/2.f, (pl->type == ENT_PLAYER || pl->type == ENT_AI) ? pl->feetpos(1) : pl->o, local, floating); 791 } 792 updateragdoll(dynent * d,const vec & center,float radius)793 void updateragdoll(dynent *d, const vec ¢er, float radius) 794 { 795 vec bottom(center); 796 bottom.z -= radius/2.f; 797 updatematerial(d, center, radius, bottom, false, false); 798 } 799 800 // main physics routine, moves a player/monster for a time step 801 // moveres indicated the physics precision (which is lower for monsters and multiplayer prediction) 802 // local is false for multiplayer prediction 803 moveplayer(physent * pl,int moveres,bool local,int millis)804 bool moveplayer(physent *pl, int moveres, bool local, int millis) 805 { 806 bool floating = pl->type == ENT_CAMERA || (pl->type == ENT_PLAYER && pl->state == CS_EDITING); 807 float secs = millis/1000.f; 808 809 pl->blocked = false; 810 if(pl->type==ENT_PLAYER || pl->type==ENT_AI) 811 { 812 updatematerial(pl, local, floating); 813 if(!floating && !sticktospecial(pl)) modifygravity(pl, millis); // apply gravity 814 } 815 modifyvelocity(pl, local, floating, millis); // apply any player generated changes in velocity 816 817 vec d(pl->vel); 818 if((pl->type==ENT_PLAYER || pl->type==ENT_AI) && !floating && pl->inliquid) d.mul(liquidmerge(pl, 1.f, FWV(liquidspeed))); 819 d.add(pl->falling); 820 d.mul(secs); 821 822 if(floating) // just apply velocity 823 { 824 if(pl->physstate != PHYS_FLOAT) 825 { 826 pl->physstate = PHYS_FLOAT; 827 pl->timeinair = 0; 828 pl->falling = vec(0, 0, 0); 829 } 830 pl->o.add(d); 831 } 832 else // apply velocity with collision 833 { 834 const float f = 1.0f/moveres; 835 int collisions = 0, timeinair = pl->timeinair; 836 vec vel(pl->vel); 837 838 d.mul(f); 839 loopi(moveres) if(!move(pl, d)) { if(++collisions<5) i--; } // discrete steps collision detection & sliding 840 if(pl->type == ENT_PLAYER && !pl->timeinair && timeinair > PHYSMILLIS*4) // if we land after long time must have been a high jump, make thud sound 841 playsound(S_LAND, pl->o, pl); 842 } 843 844 if(pl->type == ENT_PLAYER || pl->type == ENT_AI) 845 { 846 if(pl->state == CS_ALIVE) updatedynentcache(pl); 847 if(local) 848 { 849 gameent *d = (gameent *)pl; 850 if(d->state == CS_ALIVE) 851 { 852 if(d->o.z < 0) 853 { 854 game::suicide(d, HIT_DEATH|HIT_FULL); 855 return false; 856 } 857 if(d->turnmillis > 0) 858 { 859 float amt = float(millis)/float(PHYSMILLIS), yaw = d->turnyaw*amt, roll = d->turnroll*amt; 860 if(yaw != 0) { d->aimyaw += yaw; d->yaw += yaw; } 861 if(roll != 0) d->roll += roll; 862 d->turnmillis -= millis; 863 } 864 else 865 { 866 d->turnmillis = 0; 867 if(d->roll != 0 && !d->turnside) adjustscaled(float, d->roll, PHYSMILLIS); 868 } 869 } 870 else 871 { 872 d->turnmillis = d->turnside = 0; 873 d->roll = 0; 874 } 875 } 876 } 877 878 return true; 879 } 880 movecamera(physent * pl,const vec & dir,float dist,float stepdist)881 bool movecamera(physent *pl, const vec &dir, float dist, float stepdist) 882 { 883 int steps = (int)ceil(dist/stepdist); 884 if(steps <= 0) return true; 885 886 vec d(dir); 887 d.mul(dist/steps); 888 loopi(steps) 889 { 890 vec oldpos(pl->o); 891 pl->o.add(d); 892 if(!collide(pl, vec(0, 0, 0), 0, false)) 893 { 894 pl->o = oldpos; 895 return false; 896 } 897 } 898 return true; 899 } 900 interppos(physent * d)901 void interppos(physent *d) 902 { 903 d->o = d->newpos; 904 d->o.z += d->height; 905 906 int diff = lastphysframe - lastmillis; 907 if(diff <= 0 || !physinterp) return; 908 909 vec deltapos(d->deltapos); 910 deltapos.mul(min(diff, physframetime)/float(physframetime)); 911 d->o.add(deltapos); 912 } 913 move(physent * d,int moveres,bool local)914 void move(physent *d, int moveres, bool local) 915 { 916 if(physsteps <= 0) 917 { 918 if(local) interppos(d); 919 return; 920 } 921 922 if(local) 923 { 924 d->o = d->newpos; 925 d->o.z += d->height; 926 } 927 loopi(physsteps-1) moveplayer(d, moveres, local, physframetime); 928 if(local) d->deltapos = d->o; 929 moveplayer(d, moveres, local, physframetime); 930 if(local) 931 { 932 d->newpos = d->o; 933 d->deltapos.sub(d->newpos); 934 d->newpos.z -= d->height; 935 interppos(d); 936 } 937 } 938 avoidcollision(physent * d,const vec & dir,physent * obstacle,float space)939 void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space) 940 { 941 float rad = obstacle->radius+d->radius; 942 vec bbmin(obstacle->o); 943 bbmin.x -= rad; 944 bbmin.y -= rad; 945 bbmin.z -= obstacle->height+d->aboveeye; 946 bbmin.sub(space); 947 vec bbmax(obstacle->o); 948 bbmax.x += rad; 949 bbmax.y += rad; 950 bbmax.z += obstacle->aboveeye+d->height; 951 bbmax.add(space); 952 953 loopi(3) if(d->o[i] <= bbmin[i] || d->o[i] >= bbmax[i]) return; 954 955 float mindist = 1e16f; 956 loopi(3) if(dir[i] != 0) 957 { 958 float dist = ((dir[i] > 0 ? bbmax[i] : bbmin[i]) - d->o[i]) / dir[i]; 959 mindist = min(mindist, dist); 960 } 961 if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist)); 962 } 963 updatephysstate(physent * d)964 void updatephysstate(physent *d) 965 { 966 if(d->physstate == PHYS_FALL && !d->onladder) return; 967 d->timeinair = 0; 968 vec old(d->o); 969 /* Attempt to reconstruct the floor state. 970 * May be inaccurate since movement collisions are not considered. 971 * If good floor is not found, just keep the old floor and hope it's correct enough. 972 */ 973 bool foundfloor = false; 974 switch(d->physstate) 975 { 976 case PHYS_SLOPE: 977 case PHYS_FLOOR: 978 case PHYS_STEP_DOWN: 979 d->o.z -= 0.15f; 980 if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz)) 981 { 982 d->floor = wall; 983 foundfloor = true; 984 } 985 break; 986 987 case PHYS_STEP_UP: 988 d->o.z -= stairheight+0.15f; 989 if(!collide(d, vec(0, 0, -1), slopez)) 990 { 991 d->floor = wall; 992 foundfloor = true; 993 } 994 break; 995 996 case PHYS_SLIDE: 997 d->o.z -= 0.15f; 998 if(!collide(d, vec(0, 0, -1)) && wall.z < slopez) 999 { 1000 d->floor = wall; 1001 foundfloor = true; 1002 } 1003 break; 1004 default: break; 1005 } 1006 if((d->physstate > PHYS_FALL && d->floor.z <= 0) || (d->onladder && !foundfloor)) d->floor = vec(0, 0, 1); 1007 d->o = old; 1008 } 1009 xcollide(physent * d,const vec & dir,physent * o)1010 bool xcollide(physent *d, const vec &dir, physent *o) 1011 { 1012 hitflags = HITFLAG_NONE; 1013 if(d->type == ENT_PROJ && (o->type == ENT_PLAYER || (o->type == ENT_AI && (!isaitype(((gameent *)o)->aitype) || aistyle[((gameent *)o)->aitype].maxspeed)))) 1014 { 1015 gameent *e = (gameent *)o; 1016 if(!d->o.reject(e->legs, d->radius+max(e->lrad.x, e->lrad.y)) && !ellipsecollide(d, dir, e->legs, vec(0, 0, 0), e->yaw, e->lrad.x, e->lrad.y, e->lrad.z, e->lrad.z)) 1017 hitflags |= HITFLAG_LEGS; 1018 if(!d->o.reject(e->torso, d->radius+max(e->trad.x, e->trad.y)) && !ellipsecollide(d, dir, e->torso, vec(0, 0, 0), e->yaw, e->trad.x, e->trad.y, e->trad.z, e->trad.z)) 1019 hitflags |= HITFLAG_TORSO; 1020 if(!d->o.reject(e->head, d->radius+max(e->hrad.x, e->hrad.y)) && !ellipsecollide(d, dir, e->head, vec(0, 0, 0), e->yaw, e->hrad.x, e->hrad.y, e->hrad.z, e->hrad.z)) 1021 hitflags |= HITFLAG_HEAD; 1022 return hitflags == HITFLAG_NONE; 1023 } 1024 if(!plcollide(d, dir, o)) 1025 { 1026 hitflags |= HITFLAG_TORSO; 1027 return false; 1028 } 1029 return true; 1030 } 1031 xtracecollide(physent * d,const vec & from,const vec & to,float x1,float x2,float y1,float y2,float maxdist,float & dist,physent * o)1032 bool xtracecollide(physent *d, const vec &from, const vec &to, float x1, float x2, float y1, float y2, float maxdist, float &dist, physent *o) 1033 { 1034 hitflags = HITFLAG_NONE; 1035 if(d && d->type == ENT_PROJ && (o->type == ENT_PLAYER || (o->type == ENT_AI && (!isaitype(((gameent *)o)->aitype) || aistyle[((gameent *)o)->aitype].maxspeed)))) 1036 { 1037 gameent *e = (gameent *)o; 1038 float bestdist = 1e16f; 1039 if(e->legs.x+e->lrad.x >= x1 && e->legs.y+e->lrad.y >= y1 && e->legs.x-e->lrad.x <= x2 && e->legs.y-e->lrad.y <= y2) 1040 { 1041 vec bottom(e->legs), top(e->legs); bottom.z -= e->lrad.z; top.z += e->lrad.z; float d = 1e16f; 1042 if(linecylinderintersect(from, to, bottom, top, max(e->lrad.x, e->lrad.y), d)) { hitflags |= HITFLAG_LEGS; bestdist = min(bestdist, d); } 1043 } 1044 if(e->torso.x+e->trad.x >= x1 && e->torso.y+e->trad.y >= y1 && e->torso.x-e->trad.x <= x2 && e->torso.y-e->trad.y <= y2) 1045 { 1046 vec bottom(e->torso), top(e->torso); bottom.z -= e->trad.z; top.z += e->trad.z; float d = 1e16f; 1047 if(linecylinderintersect(from, to, bottom, top, max(e->trad.x, e->trad.y), d)) { hitflags |= HITFLAG_TORSO; bestdist = min(bestdist, d); } 1048 } 1049 if(e->head.x+e->hrad.x >= x1 && e->head.y+e->hrad.y >= y1 && e->head.x-e->hrad.x <= x2 && e->head.y-e->hrad.y <= y2) 1050 { 1051 vec bottom(e->head), top(e->head); bottom.z -= e->hrad.z; top.z += e->hrad.z; float d = 1e16f; 1052 if(linecylinderintersect(from, to, bottom, top, max(e->hrad.x, e->hrad.y), d)) { hitflags |= HITFLAG_HEAD; bestdist = min(bestdist, d); } 1053 } 1054 if(hitflags == HITFLAG_NONE) return true; 1055 dist = bestdist*from.dist(to); 1056 return false; 1057 } 1058 if(o->o.x+o->radius >= x1 && o->o.y+o->radius >= y1 && o->o.x-o->radius <= x2 && o->o.y-o->radius <= y2 && intersect(o, from, to, dist)) 1059 { 1060 hitflags |= HITFLAG_TORSO; 1061 return false; 1062 } 1063 return true; 1064 } 1065 complexboundbox(physent * d)1066 void complexboundbox(physent *d) 1067 { 1068 render3dbox(d->o, d->height, d->aboveeye, d->radius); 1069 renderellipse(d->o, d->xradius, d->yradius, d->yaw); 1070 if(d->type == ENT_PLAYER || (d->type == ENT_AI && (!isaitype(((gameent *)d)->aitype) || aistyle[((gameent *)d)->aitype].maxspeed))) 1071 { 1072 gameent *e = (gameent *)d; 1073 render3dbox(e->head, e->hrad.z, e->hrad.z, max(e->hrad.x, e->hrad.y)); 1074 renderellipse(e->head, e->hrad.x, e->hrad.y, e->yaw); 1075 render3dbox(e->torso, e->trad.z, e->trad.z, max(e->trad.x, e->trad.y)); 1076 renderellipse(e->torso, e->trad.x, e->trad.y, e->yaw); 1077 render3dbox(e->legs, e->lrad.z, e->lrad.z, max(e->lrad.x, e->lrad.y)); 1078 renderellipse(e->legs, e->lrad.x, e->lrad.y, e->yaw); 1079 render3dbox(e->waist, 0.25f, 0.25f, 0.25f); 1080 render3dbox(e->lfoot, 1, 1, 1); render3dbox(e->rfoot, 1, 1, 1); 1081 } 1082 } 1083 entinmap(physent * d,bool avoidplayers)1084 bool entinmap(physent *d, bool avoidplayers) 1085 { 1086 if(d->state != CS_ALIVE) { d->resetinterp(); return insideworld(d->o); } 1087 vec orig = d->o; 1088 #define inmapchk(x,y) \ 1089 { \ 1090 loopi(x) \ 1091 { \ 1092 if(i) { y; } \ 1093 if(collide(d) && !inside) \ 1094 { \ 1095 if(avoidplayers && hitplayer && issolid(hitplayer, d)) continue; \ 1096 d->resetinterp(); \ 1097 return true; \ 1098 } \ 1099 d->o = orig; \ 1100 } \ 1101 } 1102 if(d->type == ENT_PLAYER || (d->type == ENT_AI && (!isaitype(((gameent *)d)->aitype) || aistyle[((gameent *)d)->aitype].maxspeed))) 1103 { 1104 vec dir; vecfromyawpitch(d->yaw, d->pitch, 1, 0, dir); 1105 inmapchk(100, d->o.add(vec(dir).mul(i/10.f))); 1106 } 1107 inmapchk(100, d->o.add(vec((rnd(21)-10)*i/10.f, (rnd(21)-10)*i/10.f, (rnd(21)-10)*i/10.f))); 1108 d->o = orig; 1109 d->resetinterp(); 1110 return false; 1111 } 1112 1113 VARP(smoothmove, 0, 90, 1000); 1114 VARP(smoothdist, 0, 64, 1024); 1115 predictplayer(gameent * d,bool domove,int res=0,bool local=false)1116 void predictplayer(gameent *d, bool domove, int res = 0, bool local = false) 1117 { 1118 d->o = d->newpos; 1119 d->o.z += d->height; 1120 1121 d->yaw = d->newyaw; 1122 d->pitch = d->newpitch; 1123 1124 d->aimyaw = d->newaimyaw; 1125 d->aimpitch = d->newaimpitch; 1126 1127 if(domove) 1128 { 1129 move(d, res, local); 1130 d->newpos = d->o; 1131 d->newpos.z -= d->height; 1132 } 1133 1134 float k = 1.0f - float(lastmillis - d->smoothmillis)/float(smoothmove); 1135 if(k>0) 1136 { 1137 d->o.add(vec(d->deltapos).mul(k)); 1138 1139 d->yaw += d->deltayaw*k; 1140 if(d->yaw<0) d->yaw += 360; 1141 else if(d->yaw>=360) d->yaw -= 360; 1142 d->pitch += d->deltapitch*k; 1143 1144 d->aimyaw += d->deltaaimyaw*k; 1145 if(d->aimyaw<0) d->aimyaw += 360; 1146 else if(d->aimyaw>=360) d->aimyaw -= 360; 1147 d->aimpitch += d->deltaaimpitch*k; 1148 } 1149 } 1150 smoothplayer(gameent * d,int res,bool local)1151 void smoothplayer(gameent *d, int res, bool local) 1152 { 1153 if(d->state == CS_ALIVE || d->state == CS_EDITING) 1154 { 1155 if(smoothmove && d->smoothmillis>0) predictplayer(d, true, res, local); 1156 else move(d, res, local); 1157 } 1158 else if(d->state==CS_DEAD || d->state == CS_WAITING) 1159 { 1160 if(d->ragdoll) moveragdoll(d, true); 1161 else if(lastmillis-d->lastpain<2000) move(d, res, local); 1162 } 1163 } 1164 droptofloor(vec & o,float radius,float height)1165 bool droptofloor(vec &o, float radius, float height) 1166 { 1167 if(!insideworld(o)) return false; 1168 vec v(0.0001f, 0.0001f, -1); 1169 v.normalize(); 1170 if(raycube(o, v, hdr.worldsize) >= hdr.worldsize) return false; 1171 physent d; 1172 d.type = ENT_DUMMY; 1173 d.collidetype = COLLIDE_AABB; 1174 d.o = o; 1175 d.radius = radius; 1176 d.height = height; 1177 d.aboveeye = radius; 1178 if(!movecamera(&d, vec(0, 0, -1), hdr.worldsize, 1)) 1179 { 1180 o = d.o; 1181 return true; 1182 } 1183 return false; 1184 } 1185 update()1186 void update() 1187 { 1188 int diff = lastmillis - lastphysframe; 1189 if(diff <= 0) physsteps = 0; 1190 else 1191 { 1192 physsteps = (diff + physframetime - 1)/physframetime; 1193 lastphysframe += physsteps * physframetime; 1194 } 1195 cleardynentcache(); 1196 } 1197 } 1198 #undef GAMEPHYSICS 1199