1 #include "game.h" 2 namespace physics 3 { 4 FVAR(IDF_WORLD, stairheight, 0, 4.1f, 1000); 5 FVAR(IDF_WORLD, floorz, 0, 0.867f, 1); 6 FVAR(IDF_WORLD, slopez, 0, 0.5f, 1); 7 FVAR(IDF_WORLD, wallz, 0, 0.2f, 1); 8 FVAR(IDF_WORLD, stepspeed, 1e-4f, 1.f, 1000); 9 10 FVAR(IDF_PERSIST, floatspeed, FVAR_NONZERO, 200, FVAR_MAX); 11 FVAR(IDF_PERSIST, floatcoast, 0, 3.f, FVAR_MAX); 12 13 VAR(IDF_PERSIST, physframetime, 5, 5, 20); 14 VAR(IDF_PERSIST, physinterp, 0, 1, 1); 15 16 FVAR(IDF_PERSIST, impulseparkouryaw, 0, 150, 180); // determines the minimum yaw angle to switch between parkour climb and run 17 VAR(IDF_PERSIST, impulsemethod, 0, 3, 3); // determines which impulse method to use, 0 = none, 1 = launch, 2 = slide, 3 = both 18 VAR(IDF_PERSIST, impulseaction, 0, 3, 3); // determines how impulse action works, 0 = off, 1 = impulse jump, 2 = impulse boost, 3 = both 19 20 VAR(IDF_PERSIST, crouchstyle, 0, 0, 2); // 0 = press and hold, 1 = double-tap toggle, 2 = toggle 21 VAR(IDF_PERSIST, walkstyle, 0, 0, 2); // 0 = press and hold, 1 = double-tap toggle, 2 = toggle 22 VAR(IDF_PERSIST, grabstyle, 0, 2, 2); // 0 = up=up down=down, 1 = up=down down=up, 2 = up=up, down=up 23 VAR(IDF_PERSIST, grabplayerstyle, 0, 3, 3); // 0 = up=up down=down, 1 = up=down down=up, 2 = up=up, down=up, 3 = directly toward player 24 25 int physsteps = 0, lastphysframe = 0, lastmove = 0, lastdirmove = 0, laststrafe = 0, lastdirstrafe = 0, lastcrouch = 0, lastwalk = 0; 26 allowimpulse(physent * d,int type)27 bool allowimpulse(physent *d, int type) 28 { 29 if(d && gameent::is(d)) 30 return (!type || AA(((gameent *)d)->actortype, abilities)&(1<<type)) && (impulsestyle || PHYS(gravity) == 0); 31 return false; 32 } 33 canimpulse(physent * d,int type,bool touch)34 bool canimpulse(physent *d, int type, bool touch) 35 { 36 if(!gameent::is(d) || !allowimpulse(d, type)) return false; 37 gameent *e = (gameent *)d; 38 if(e->impulse[IM_TYPE] == IM_T_PUSHER && e->impulsetime[IM_T_PUSHER] > lastmillis) return false; 39 if(!touch && impulsestyle == 1 && e->impulse[IM_TYPE] > IM_T_JUMP && e->impulse[IM_TYPE] < IM_T_TOUCH) return false; 40 if(impulsestyle <= 2 && e->impulse[IM_COUNT] >= impulsecount) return false; 41 int time = 0, delay = 0; 42 switch(type) 43 { 44 case A_A_PARKOUR: 45 time = max(max(e->impulsetime[IM_T_PARKOUR], e->impulsetime[IM_T_MELEE]), e->impulsetime[IM_T_GRAB]); 46 delay = impulseparkourdelay; 47 break; 48 case A_A_SLIDE: 49 time = e->impulsetime[IM_T_SLIDE]; 50 delay = impulseslidedelay; 51 break; 52 case A_A_POUND: 53 time = e->impulsetime[IM_T_POUND]; 54 delay = impulsepounddelay; 55 break; 56 case A_A_BOOST: default: 57 time = max(e->impulsetime[IM_T_JUMP], max(e->impulsetime[IM_T_BOOST], e->impulsetime[IM_T_KICK])); 58 delay = e->impulse[IM_TYPE] == IM_T_JUMP ? impulsejumpdelay : impulseboostdelay; 59 break; 60 } 61 if(time && delay && lastmillis-time <= delay) return false; 62 return true; 63 } 64 65 #define imov(name,v,u,d,s,os) \ 66 void do##name(bool down) \ 67 { \ 68 game::player1->s = down; \ 69 int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \ 70 game::player1->v = dir; \ 71 if(down) \ 72 { \ 73 last##v = lastmillis; \ 74 lastdir##v = dir; \ 75 last##u = lastdir##u = 0; \ 76 } \ 77 } \ 78 ICOMMAND(0, name, "D", (int *down), { do##name(*down!=0); }); 79 80 imov(backward, move, strafe, -1, k_down, k_up); 81 imov(forward, move, strafe, 1, k_up, k_down); 82 imov(left, strafe, move, 1, k_left, k_right); 83 imov(right, strafe, move, -1, k_right, k_left); 84 85 // inputs doaction(int type,bool down)86 void doaction(int type, bool down) 87 { 88 if(type >= AC_MAX || type < 0) return; 89 if(!game::allowmove(game::player1)) 90 { 91 game::player1->action[type] = false; 92 if((type == AC_PRIMARY || type == AC_JUMP) && down) game::respawn(game::player1); 93 return; 94 } 95 96 int style = 0, *last = NULL; 97 switch(type) 98 { 99 case AC_CROUCH: style = crouchstyle; last = &lastcrouch; break; 100 case AC_WALK: style = walkstyle; last = &lastwalk; break; 101 default: break; 102 } 103 if(last != NULL) switch(style) 104 { 105 case 1: 106 { 107 if(!down && game::player1->action[type]) 108 { 109 if(*last && lastmillis-*last < PHYSMILLIS) return; 110 *last = lastmillis; 111 } 112 break; 113 } 114 case 2: 115 { 116 if(!down) 117 { 118 if(*last) 119 { 120 *last = 0; 121 return; 122 } 123 *last = lastmillis; 124 } 125 break; 126 } 127 default: break; 128 } 129 if(down) game::player1->actiontime[type] = lastmillis; 130 else if(type == AC_CROUCH || type == AC_JUMP) game::player1->actiontime[type] = -lastmillis; 131 game::player1->action[type] = down; 132 } 133 134 ICOMMAND(0, primary, "D", (int *n), doaction(AC_PRIMARY, *n!=0)); 135 ICOMMAND(0, secondary, "D", (int *n), doaction(AC_SECONDARY, *n!=0)); 136 ICOMMAND(0, reload, "D", (int *n), doaction(AC_RELOAD, *n!=0)); 137 ICOMMAND(0, use, "D", (int *n), doaction(AC_USE, *n!=0)); 138 ICOMMAND(0, jump, "D", (int *n), doaction(AC_JUMP, *n!=0)); 139 ICOMMAND(0, walk, "D", (int *n), doaction(AC_WALK, *n!=0)); 140 ICOMMAND(0, crouch, "D", (int *n), doaction(AC_CROUCH, *n!=0)); 141 ICOMMAND(0, special, "D", (int *n), doaction(AC_SPECIAL, *n!=0)); 142 ICOMMAND(0, drop, "D", (int *n), doaction(AC_DROP, *n!=0)); 143 ICOMMAND(0, affinity, "D", (int *n), doaction(AC_AFFINITY, *n!=0)); 144 carryaffinity(gameent * d)145 int carryaffinity(gameent *d) 146 { 147 if(m_capture(game::gamemode)) return capture::carryaffinity(d); 148 if(m_bomber(game::gamemode)) return bomber::carryaffinity(d); 149 return 0; 150 } 151 CLCOMMAND(carryaffinity, intret(carryaffinity(d))); 152 secondaryweap(gameent * d)153 bool secondaryweap(gameent *d) 154 { 155 if(!isweap(d->weapselect)) return false; 156 if(d->action[AC_SECONDARY] && (!d->action[AC_PRIMARY] || d->actiontime[AC_SECONDARY] > d->actiontime[AC_PRIMARY])) return true; 157 if(d->actiontime[AC_SECONDARY] > d->actiontime[AC_PRIMARY] && d->weapstate[d->weapselect] == W_S_POWER) return true; 158 return false; 159 } 160 isghost(gameent * d,gameent * e,bool proj)161 bool isghost(gameent *d, gameent *e, bool proj) 162 { // d is target, e is from 163 if(!e || (d == e && !proj)) return false; 164 if(d->actortype < A_ENEMY && e->actortype < A_ENEMY && m_ghost(game::gamemode, game::mutators)) return true; 165 switch(d->actortype) 166 { 167 case A_PLAYER: if(!(AA(e->actortype, collide)&(1<<A_C_PLAYERS))) return true; break; 168 case A_BOT: if(!(AA(e->actortype, collide)&(1<<A_C_BOTS))) return true; break; 169 default: if(!(AA(e->actortype, collide)&(1<<A_C_ENEMIES))) return true; break; 170 } 171 if(m_team(game::gamemode, game::mutators) && d->team == e->team && (proj || AA(e->actortype, teamdamage)&(1<<A_T_GHOST))) switch(d->actortype) 172 { 173 case A_PLAYER: if(!(AA(e->actortype, teamdamage)&(1<<A_T_PLAYERS))) return true; break; 174 case A_BOT: if(!(AA(e->actortype, teamdamage)&(1<<A_T_BOTS))) return true; break; 175 default: if(!(AA(e->actortype, teamdamage)&(1<<A_T_ENEMIES))) return true; break; 176 } 177 return false; 178 } 179 issolid(physent * d,physent * e,bool esc,bool impact,bool reverse)180 bool issolid(physent *d, physent *e, bool esc, bool impact, bool reverse) 181 { // d is target, e is from 182 if(!e || d == e) return false; // don't collide with themself 183 if(projent::is(e)) 184 { 185 projent *p = (projent *)e; 186 if(gameent::is(d)) 187 { 188 gameent *g = (gameent *)d; 189 if(g->state != CS_ALIVE) return false; 190 if(g->protect(lastmillis, m_protect(game::gamemode, game::mutators))) return false; 191 if(p->stick == d || isghost(g, p->owner, true)) return false; 192 if(impact && (p->hit == g || !(p->projcollide&COLLIDE_PLAYER))) return false; 193 if(p->owner == d && (!(p->projcollide&COLLIDE_OWNER) || (esc && !p->escaped))) return false; 194 } 195 else if(projent::is(d)) 196 { 197 projent *q = (projent *)d; 198 if(p->projtype == PRJ_SHOT && q->projtype == PRJ_SHOT) 199 { 200 if((p->projcollide&IMPACT_SHOTS || p->projcollide&BOUNCE_SHOTS) && q->projcollide&COLLIDE_PROJ) return true; 201 } 202 return false; 203 } 204 else return false; 205 } 206 if(gameent::is(d)) 207 { 208 gameent *g = (gameent *)d; 209 if(g->state != CS_ALIVE) return false; 210 if(gameent::is(e) && isghost(g, (gameent *)e)) return false; 211 return true; 212 } 213 else if(projent::is(d) && !reverse) return issolid(e, d, esc, impact, true); 214 return false; 215 } 216 liquidcheck(physent * d)217 bool liquidcheck(physent *d) { return d->inliquid && !d->onladder && d->submerged >= PHYS(liquidsubmerge); } 218 liquidmerge(physent * d,float from,float to)219 float liquidmerge(physent *d, float from, float to) 220 { 221 if(d->inliquid) 222 { 223 if(d->physstate >= PHYS_SLIDE && d->submerged < 1.f) 224 return from-((from-to)*d->submerged); 225 else return to; 226 } 227 return from; 228 } 229 jumpvel(physent * d,bool liquid=true)230 float jumpvel(physent *d, bool liquid = true) 231 { 232 float vel = d->jumpspeed; 233 if(liquid && d->inliquid) vel *= liquidmerge(d, 1.f, PHYS(liquidspeed)); 234 if(gameent::is(d)) 235 { 236 gameent *e = (gameent *)d; 237 vel *= e->stunscale; 238 } 239 return vel; 240 } 241 gravityvel(physent * d)242 float gravityvel(physent *d) 243 { 244 float vel = PHYS(gravity)*(d->weight/100.f); 245 if(gameent::is(d)) 246 { 247 gameent *e = (gameent *)d; 248 if(e->vel.z+e->falling.z <= gravitycutoff) vel *= e->crouching(true) ? gravityfallcrouch : gravityfall; 249 else if(e->actiontime[AC_JUMP] >= 0) vel *= e->crouching(true) ? gravityjumpcrouch : gravityjump; 250 else if(e->crouching(true)) vel *= gravitycrouch; 251 vel *= e->stungravity; 252 } 253 return vel; 254 } 255 sticktofloor(physent * d)256 bool sticktofloor(physent *d) 257 { 258 if(!d->onladder) 259 { 260 if(liquidcheck(d)) return false; 261 if(gameent::is(d)) 262 { 263 gameent *e = (gameent *)d; 264 if(e->sliding()) return false; 265 } 266 } 267 return true; 268 } 269 sticktospecial(physent * d)270 bool sticktospecial(physent *d) 271 { 272 if(gameent::is(d) && ((gameent *)d)->impulse[IM_TYPE] == IM_T_PARKOUR) return true; 273 return false; 274 } 275 isfloating(physent * d)276 bool isfloating(physent *d) 277 { 278 return d->type == ENT_CAMERA || (d->type == ENT_PLAYER && d->state == CS_EDITING); 279 } 280 movevelocity(physent * d,bool floating)281 float movevelocity(physent *d, bool floating) 282 { 283 physent *pl = d->type == ENT_CAMERA ? game::player1 : d; 284 float vel = pl->speed*movespeed; 285 if(floating) vel *= floatspeed/100.0f; 286 else if(gameent::is(pl)) 287 { 288 gameent *e = (gameent *)pl; 289 vel *= e->stunscale; 290 if((d->physstate >= PHYS_SLOPE || d->onladder) && !e->sliding(true) && e->crouching()) vel *= movecrawl; 291 else if(isweap(e->weapselect) && e->weapstate[e->weapselect] == W_S_ZOOM) vel *= movecrawl; 292 if(e->move >= 0) vel *= e->strafe ? movestrafe : movestraight; 293 if(e->running()) vel *= moverun; 294 switch(e->physstate) 295 { 296 case PHYS_FALL: if(PHYS(gravity) > 0) vel *= moveinair; break; 297 case PHYS_STEP_DOWN: vel *= movestepdown; break; 298 case PHYS_STEP_UP: vel *= movestepup; break; 299 default: break; 300 } 301 } 302 return vel; 303 } 304 impulsevelocity(physent * d,float amt,int & cost,int type,float redir,vec & keep)305 float impulsevelocity(physent *d, float amt, int &cost, int type, float redir, vec &keep) 306 { 307 float scale = 1.f; 308 if(gameent::is(d)) 309 { 310 gameent *e = (gameent *)d; 311 scale *= e->stunscale; 312 if(impulsemeter && cost) 313 { 314 if(impulsecostscale > 0) cost = int(cost*scale); 315 int diff = impulsemeter-e->impulse[IM_METER]; 316 if(cost > diff) 317 { 318 if(type >= A_A_IMFIRST && impulsecostrelax&(1<<(type-A_A_IMFIRST))) 319 { 320 scale *= float(diff)/float(cost); 321 cost = diff; 322 } 323 else return 0.f; 324 } 325 } 326 else cost = 0; 327 } 328 float speed = (d->impulsespeed*amt*scale)+(keep.magnitude()*redir); 329 keep.mul(1-min(redir, 1.f)); 330 return speed; 331 } 332 movepitch(physent * d)333 bool movepitch(physent *d) 334 { 335 if(d->type == ENT_CAMERA || d->state == CS_EDITING || d->state == CS_SPECTATOR) return true; 336 if(d->onladder || (d->inliquid && (liquidcheck(d) || d->pitch < 0.f)) || PHYS(gravity) == 0) return true; 337 return false; 338 } 339 recalcdir(physent * d,const vec & oldvel,vec & dir)340 void recalcdir(physent *d, const vec &oldvel, vec &dir) 341 { 342 float speed = oldvel.magnitude(); 343 if(speed > 1e-6f) 344 { 345 float step = dir.magnitude(); 346 dir = d->vel; 347 dir.add(d->falling); 348 dir.mul(step/speed); 349 } 350 } 351 slideagainst(physent * d,vec & dir,const vec & obstacle,bool foundfloor,bool slidecollide)352 void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide) 353 { 354 vec wall(obstacle); 355 if(foundfloor ? wall.z > 0 : slidecollide) 356 { 357 wall.z = 0; 358 if(!wall.iszero()) wall.normalize(); 359 } 360 vec oldvel(d->vel); 361 oldvel.add(d->falling); 362 d->vel.project(wall); 363 d->falling.project(wall); 364 recalcdir(d, oldvel, dir); 365 } 366 switchfloor(physent * d,vec & dir,const vec & floor)367 void switchfloor(physent *d, vec &dir, const vec &floor) 368 { 369 if(floor.z >= floorz) d->falling = vec(0, 0, 0); 370 371 vec oldvel(d->vel); 372 oldvel.add(d->falling); 373 if(dir.dot(floor) >= 0) 374 { 375 if(d->physstate < PHYS_SLIDE || fabs(dir.dot(d->floor)) > 0.01f*dir.magnitude()) return; 376 d->vel.projectxy(floor, 0.0f); 377 } 378 else d->vel.projectxy(floor); 379 d->falling.project(floor); 380 recalcdir(d, oldvel, dir); 381 } 382 trystepup(physent * d,vec & dir,const vec & obstacle,float maxstep,const vec & floor)383 bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor) 384 { 385 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); 386 bool cansmooth = true; 387 /* check if there is space atop the stair to move to */ 388 if(d->physstate != PHYS_STEP_UP) 389 { 390 vec checkdir = stairdir; 391 checkdir.mul(0.1f); 392 checkdir.z += maxstep + 0.1f; 393 d->o.add(checkdir); 394 if(collide(d)) 395 { 396 d->o = old; 397 if(!collide(d, vec(0, 0, -1), slopez)) return false; 398 cansmooth = false; 399 } 400 } 401 402 if(cansmooth) 403 { 404 vec checkdir = stairdir; 405 checkdir.z += 1; 406 checkdir.mul(maxstep); 407 d->o = old; 408 d->o.add(checkdir); 409 int scale = 2; 410 if(collide(d, checkdir)) 411 { 412 if(!collide(d, vec(0, 0, -1), slopez)) 413 { 414 d->o = old; 415 return false; 416 } 417 d->o.add(checkdir); 418 if(collide(d, vec(0, 0, -1), slopez)) scale = 1; 419 } 420 if(scale != 1) 421 { 422 d->o = old; 423 d->o.add(checkdir.mul2(2)); 424 if(!collide(d, vec(0, 0, -1), slopez)) scale = 1; 425 } 426 427 d->o = old; 428 vec smoothdir = vec(dir.x, dir.y, 0); 429 float magxy = smoothdir.magnitude(); 430 if(magxy > 1e-9f) 431 { 432 if(magxy > scale*dir.z) 433 { 434 smoothdir.mul(1/magxy); 435 smoothdir.z = 1.0f/scale; 436 smoothdir.mul(dir.magnitude()/smoothdir.magnitude()); 437 } 438 else smoothdir.z = dir.z; 439 d->o.add(smoothdir.mul(stepspeed)); 440 float margin = (maxstep + 0.1f)*ceil(stepspeed); 441 d->o.z += margin; 442 if(!collide(d, smoothdir)) 443 { 444 d->o.z -= margin; 445 if(d->physstate == PHYS_FALL || d->floor != floor) 446 { 447 d->airmillis = 0; 448 d->floor = floor; 449 switchfloor(d, dir, d->floor); 450 } 451 d->physstate = PHYS_STEP_UP; 452 return true; 453 } 454 } 455 } 456 457 /* try stepping up */ 458 d->o = old; 459 d->o.z += dir.magnitude()*stepspeed; 460 if(!collide(d, vec(0, 0, 1))) 461 { 462 if(d->physstate == PHYS_FALL || d->floor != floor) 463 { 464 d->airmillis = 0; 465 d->floor = floor; 466 switchfloor(d, dir, d->floor); 467 } 468 if(cansmooth) d->physstate = PHYS_STEP_UP; 469 return true; 470 } 471 d->o = old; 472 return false; 473 } 474 trystepdown(physent * d,vec & dir,float step,float xy,float z,bool init=false)475 bool trystepdown(physent *d, vec &dir, float step, float xy, float z, bool init = false) 476 { 477 vec stepdir(dir.x, dir.y, 0); 478 stepdir.z = -stepdir.magnitude2()*z/xy; 479 if(!stepdir.z) return false; 480 stepdir.normalize(); 481 482 vec old(d->o); 483 d->o.add(vec(stepdir).mul(stairheight/fabs(stepdir.z))).z -= stairheight; 484 d->zmargin = -stairheight; 485 if(collide(d, vec(0, 0, -1), slopez)) 486 { 487 d->o = old; 488 d->o.add(vec(stepdir).mul(step)); 489 d->zmargin = 0; 490 if(!collide(d, vec(0, 0, -1))) 491 { 492 vec stepfloor(stepdir); 493 stepfloor.mul(-stepfloor.z).z += 1; 494 stepfloor.normalize(); 495 if(d->physstate >= PHYS_SLOPE && d->floor != stepfloor) 496 { 497 // prevent alternating step-down/step-up states if player would keep bumping into the same floor 498 vec stepped(d->o); 499 d->o.z -= 0.5f; 500 d->zmargin = -0.5f; 501 if(collide(d, stepdir) && collidewall == d->floor) 502 { 503 d->o = old; 504 if(!init) 505 { 506 d->o.x += dir.x; 507 d->o.y += dir.y; 508 if(dir.z <= 0 || collide(d, dir)) 509 d->o.z += dir.z; 510 } 511 d->zmargin = 0; 512 d->physstate = PHYS_STEP_DOWN; 513 return true; 514 } 515 d->o = init ? old : stepped; 516 d->zmargin = 0; 517 } 518 else if(init) d->o = old; 519 switchfloor(d, dir, stepfloor); 520 d->floor = stepfloor; 521 if(init) 522 { 523 d->airmillis = 0; 524 d->physstate = PHYS_STEP_DOWN; 525 } 526 return true; 527 } 528 } 529 d->o = old; 530 d->zmargin = 0; 531 return false; 532 } 533 trystepdown(physent * d,vec & dir,bool init=false)534 bool trystepdown(physent *d, vec &dir, bool init = false) 535 { 536 if(!sticktofloor(d)) return false; 537 vec old(d->o); 538 d->o.z -= 0.1f; 539 if(collide(d, vec(0, 0, -1)) || collideinside) 540 { 541 d->o = old; 542 return false; 543 } 544 d->o.z -= stairheight; 545 d->zmargin = -stairheight; 546 if(!collide(d, vec(0, 0, -1), slopez)) 547 { 548 d->o = old; 549 d->zmargin = 0; 550 return false; 551 } 552 d->o = old; 553 d->zmargin = 0; 554 float step = dir.magnitude(); 555 if(trystepdown(d, dir, step, 2, 1, init)) return true; 556 if(trystepdown(d, dir, step, 1, 1, init)) return true; 557 if(trystepdown(d, dir, step, 1, 2, init)) return true; 558 return false; 559 } 560 falling(physent * d,vec & dir,const vec & floor)561 void falling(physent *d, vec &dir, const vec &floor) 562 { 563 if(sticktospecial(d)) 564 { 565 d->airmillis = 0; 566 d->physstate = PHYS_FLOOR; 567 d->floor = vec(0, 0, 1); 568 return; 569 } 570 if(floor.z > 0.0f && floor.z < slopez) 571 { 572 if(floor.z >= wallz) switchfloor(d, dir, floor); 573 d->airmillis = 0; 574 d->physstate = PHYS_SLIDE; 575 d->floor = floor; 576 } 577 else if(d->physstate < PHYS_SLOPE || dir.dot(d->floor) > 0.01f*dir.magnitude() || (floor.z != 0.0f && floor.z != 1.0f) || !trystepdown(d, dir, true)) 578 d->physstate = PHYS_FALL; 579 } 580 landing(physent * d,vec & dir,const vec & floor,bool collided)581 void landing(physent *d, vec &dir, const vec &floor, bool collided) 582 { 583 switchfloor(d, dir, floor); 584 d->airmillis = 0; 585 if((d->physstate != PHYS_STEP_UP && d->physstate != PHYS_STEP_DOWN) || !collided) 586 d->physstate = floor.z >= floorz ? PHYS_FLOOR : PHYS_SLOPE; 587 d->floor = floor; 588 } 589 findfloor(physent * d,bool collided,const vec & obstacle,bool & slide,vec & floor)590 bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec &floor) 591 { 592 bool found = false; 593 vec moved(d->o); 594 d->o.z -= 0.1f; 595 if(sticktospecial(d)) 596 { 597 floor = vec(0, 0, 1); 598 found = true; 599 } 600 else if(d->physstate != PHYS_FALL && collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz)) 601 { 602 floor = collidewall; 603 found = true; 604 } 605 else if(collided && obstacle.z >= slopez) 606 { 607 floor = obstacle; 608 found = true; 609 slide = false; 610 } 611 else if(d->physstate == PHYS_STEP_UP || d->physstate == PHYS_SLIDE) 612 { 613 if(collide(d, vec(0, 0, -1)) && collidewall.z > 0.0f) 614 { 615 floor = collidewall; 616 if(floor.z >= slopez) found = true; 617 } 618 } 619 else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f) 620 { 621 if(collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1))) 622 { 623 floor = collidewall; 624 if(floor.z >= slopez && floor.z < 1.0f) found = true; 625 } 626 } 627 if(collided && (!found || obstacle.z > floor.z)) 628 { 629 floor = obstacle; 630 slide = !found && (floor.z < wallz || floor.z >= slopez); 631 } 632 d->o = moved; 633 return found; 634 } 635 move(physent * d,vec & dir)636 bool move(physent *d, vec &dir) 637 { 638 vec old(d->o), obstacle; 639 d->o.add(dir); 640 bool collided = false, slidecollide = false; 641 if(gameent::is(d)) 642 { 643 if(collide(d, dir)) 644 { 645 obstacle = collidewall; 646 /* check to see if there is an obstacle that would prevent this one from being used as a floor */ 647 if(((collidewall.z >= slopez && dir.z < 0) || (collidewall.z <= -slopez && dir.z > 0)) && (dir.x || dir.y) && collide(d, vec(dir.x, dir.y, 0))) 648 { 649 if(collidewall.dot(dir) >= 0) slidecollide = true; 650 obstacle = collidewall; 651 } 652 653 d->o = old; 654 d->o.z -= stairheight; 655 d->zmargin = -stairheight; 656 if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || (collide(d, vec(0, 0, -1), slopez) && (d->physstate == PHYS_STEP_UP || d->physstate == PHYS_STEP_DOWN || collidewall.z >= floorz))) 657 { 658 d->o = old; 659 d->zmargin = 0; 660 if(trystepup(d, dir, obstacle, stairheight, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor : vec(collidewall))) 661 return true; 662 } 663 else 664 { 665 d->o = old; 666 d->zmargin = 0; 667 } 668 collided = true; // can't step over the obstacle, so just slide against it 669 } 670 else if(d->physstate == PHYS_STEP_UP) 671 { 672 if(collide(d, vec(0, 0, -1), slopez)) 673 { 674 d->o = old; 675 if(trystepup(d, dir, vec(0, 0, 1), stairheight, vec(collidewall))) return true; 676 d->o.add(dir); 677 } 678 } 679 else if(!sticktospecial(d) && d->physstate == PHYS_STEP_DOWN && dir.dot(d->floor) <= 1e-6f) 680 { 681 vec moved(d->o); 682 d->o = old; 683 if(trystepdown(d, dir)) return true; 684 d->o = moved; 685 } 686 } 687 else if(collide(d, dir)) 688 { 689 obstacle = collidewall; 690 d->o = old; 691 collided = true; 692 } 693 vec floor(0, 0, 0); 694 bool slide = collided, found = findfloor(d, collided, obstacle, slide, floor); 695 if(slide || (!collided && floor.z > 0 && floor.z < wallz)) 696 { 697 slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide); 698 d->blocked = true; 699 } 700 if(found) landing(d, dir, floor, collided); 701 else falling(d, dir, floor); 702 return !collided; 703 } 704 impulseplayer(gameent * d,bool onfloor,const vec & inertia,bool melee=false,bool slide=false)705 bool impulseplayer(gameent *d, bool onfloor, const vec &inertia, bool melee = false, bool slide = false) 706 { 707 bool launch = !melee && !slide && onfloor && impulsemethod&1 && d->sliding(true) && d->action[AC_JUMP], 708 mchk = !melee || onfloor, action = mchk && (d->actortype >= A_BOT || melee || impulseaction&2); 709 int move = action ? d->move : 0, strafe = action ? d->strafe : 0; 710 bool moving = mchk && (move || strafe), pound = !melee && !launch && !slide && !onfloor && (impulsepoundstyle || !moving) && d->action[AC_CROUCH]; 711 if(d->actortype < A_BOT && !launch && !melee && !slide && !impulseaction) return false; 712 int type = melee ? A_A_PARKOUR : (slide ? A_A_SLIDE : (pound ? A_A_POUND : A_A_BOOST)); 713 bool pulse = melee ? !onfloor : (!launch && !onfloor && (d->actortype >= A_BOT || impulseaction&1) && d->action[AC_JUMP]); 714 if((!launch && !melee && !slide && !pulse) || !canimpulse(d, type, melee || slide)) return false; 715 vec keepvel = inertia; 716 int cost = int(impulsecost*(melee ? impulsecostmelee : (pound ? impulsecostpound : impulsecostboost))); 717 float skew = melee ? impulsemelee : (slide ? impulseslide : (launch ? impulselaunch : (pound ? impulsepound : (moving ? impulseboost : impulsejump)))), 718 redir = melee ? impulsemeleeredir : (slide ? impulseslideredir : (launch ? impulselaunchredir : (pound ? impulsepoundredir : (moving ? impulseboostredir : impulsejumpredir)))), 719 force = impulsevelocity(d, skew, cost, type, redir, keepvel); 720 if(force <= 0) return false; 721 vec dir(0, 0, pound ? -1 : 1); 722 if(!pound && (launch || slide || moving || onfloor)) 723 { 724 float yaw = d->yaw, pitch = moving && (launch || pulse) ? d->pitch : 0; 725 if(launch) pitch = clamp(pitch, impulselaunchpitchmin, impulselaunchpitchmax); 726 else if(moving && pulse) pitch = clamp(pitch, impulseboostpitchmin, impulseboostpitchmax); 727 vecfromyawpitch(yaw, pitch, moving ? move : 1, strafe, dir); 728 if(!launch && slide && !d->floor.iszero() && !dir.iszero()) 729 { 730 dir.project(d->floor).normalize(); 731 if(dir.z < 0) force += -dir.z*force; 732 } 733 } 734 d->vel = vec(dir).mul(force).add(keepvel); 735 if(launch) d->vel.z += jumpvel(d); 736 d->doimpulse(melee ? IM_T_MELEE : (slide ? IM_T_SLIDE : (pound ? IM_T_POUND : IM_T_BOOST)), lastmillis, cost); 737 d->action[AC_JUMP] = false; 738 client::addmsg(N_SPHY, "ri2", d->clientnum, melee ? SPHY_MELEE : (slide ? SPHY_SLIDE : (pound ? SPHY_POUND: SPHY_BOOST))); 739 game::impulseeffect(d); 740 return true; 741 } 742 modifyinput(gameent * d,vec & m,bool wantsmove,int millis)743 void modifyinput(gameent *d, vec &m, bool wantsmove, int millis) 744 { 745 bool onfloor = d->physstate >= PHYS_SLOPE || d->onladder || liquidcheck(d); 746 if(d->impulse[IM_TYPE] == IM_T_PARKOUR && (!allowimpulse(d, A_A_PARKOUR) || (impulseparkourlen && lastmillis-d->impulsetime[IM_T_PARKOUR] > impulseparkourlen) || d->vel.iszero())) 747 { 748 d->doimpulse(IM_T_AFTER, lastmillis); 749 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_AFTER); 750 d->resetphys(true); 751 onfloor = false; 752 } 753 if(d->impulse[IM_TYPE] == IM_T_PARKOUR) 754 { 755 if(d->action[AC_JUMP] && canimpulse(d, A_A_BOOST, true)) 756 { 757 int cost = int(impulsecost*impulsecostkick); 758 vec keepvel = vec(d->vel).add(d->falling); 759 float mag = impulsevelocity(d, impulsekick, cost, A_A_BOOST, impulsekickredir, keepvel); 760 if(mag > 0) 761 { 762 vec rft; 763 float pitch = clamp(d->pitch, impulsekickpitchmin, impulsekickpitchmax); 764 vecfromyawpitch(d->yaw, pitch, d->move || d->strafe ? d->move : 1, d->strafe, rft); 765 d->vel = vec(rft).mul(mag).add(keepvel); 766 d->doimpulse(IM_T_KICK, lastmillis, cost); 767 d->action[AC_JUMP] = onfloor = false; 768 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_KICK); 769 game::impulseeffect(d); 770 game::footstep(d); 771 } 772 } 773 } 774 else if(!impulseplayer(d, onfloor, vec(d->vel).add(d->falling)) && onfloor && d->action[AC_JUMP] && AA(d->actortype, abilities)&(1<<A_A_JUMP)) 775 { 776 float force = jumpvel(d); 777 if(force > 0) 778 { 779 d->vel.z += force; 780 if(d->inliquid) 781 { 782 float scale = liquidmerge(d, 1.f, PHYS(liquidspeed)); 783 d->vel.x *= scale; 784 d->vel.y *= scale; 785 } 786 d->doimpulse(IM_T_JUMP, lastmillis); 787 d->action[AC_JUMP] = onfloor = false; 788 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_JUMP); 789 playsound(S_JUMP, d->o, d); 790 createshape(PART_SMOKE, int(d->radius), 0x222222, 21, 20, 250, d->feetpos(), 1, 1, -10, 0, 10.f); 791 } 792 } 793 bool found = false; 794 if(d->impulse[IM_TYPE] == IM_T_PARKOUR || d->action[AC_SPECIAL]) 795 { 796 vec oldpos = d->o, dir; 797 const int movements[8][2] = { { 2, 2 }, { 1, 2 }, { 1, 0 }, { 1, -1 }, { 1, 1 }, { 0, 1 }, { 0, -1 }, { -1, 0 } }; 798 loopi(d->impulse[IM_TYPE] == IM_T_PARKOUR ? 8 : 2) // we do these insane checks so that running along walls works at all times 799 { 800 int move = movements[i][0], strafe = movements[i][1]; 801 if(move == 2) move = d->move > 0 ? d->move : 0; 802 if(strafe == 2) strafe = d->turnside ? d->turnside : d->strafe; 803 if(!move && !strafe) continue; 804 vecfromyawpitch(d->yaw, 0, move, strafe, dir); 805 bool foundwall = false; 806 loopk(d->impulse[IM_TYPE] == IM_T_PARKOUR ? 4 : 1) 807 { 808 d->o.add(dir); 809 bool collided = collide(d); 810 if(!collided || collideplayer || collidewall.iszero()) continue; 811 foundwall = true; 812 break; 813 } 814 d->o = oldpos; 815 if(!foundwall) continue; 816 vec face = vec(collidewall).normalize(); 817 if(fabs(face.z) <= impulseparkournorm) 818 { 819 bool canspec = d->action[AC_SPECIAL] && canimpulse(d, A_A_PARKOUR, true), parkour = canspec && !onfloor && !d->onladder; 820 float yaw = 0, pitch = 0; 821 vectoyawpitch(face, yaw, pitch); 822 float off = yaw-d->yaw; 823 if(off > 180) off -= 360; 824 else if(off < -180) off += 360; 825 bool isclimb = fabs(off) >= impulseparkouryaw, vault = false; 826 if(impulseclimbstyle && canspec && isclimb && !parkour && d->impulse[IM_TYPE] != IM_T_PARKOUR) 827 { 828 float space = d->height+d->aboveeye, m = min(impulseclimbvaultmin, impulseclimbvaultmax), n = max(impulseclimbvaultmin, impulseclimbvaultmax); 829 d->o.add(dir); 830 if(onfloor) 831 { 832 d->o.z += space*m; 833 if(collide(d)) 834 { 835 d->o.z += space*n-space*m; 836 if(!collide(d) || collideplayer) vault = true; 837 } 838 } 839 else 840 { 841 d->o.z += space*n; 842 if(!collide(d) || collideplayer) vault = true; 843 } 844 d->o = oldpos; 845 } 846 if(d->impulse[IM_TYPE] == IM_T_PARKOUR || parkour || vault) 847 { 848 int side = isclimb ? 0 : (off < 0 ? -1 : 1); 849 if(isclimb) 850 { 851 if(pitch > 0) 852 { 853 yaw += 180; 854 pitch = 90-pitch; 855 } 856 else pitch += 90; 857 } 858 else 859 { 860 if(off < 0) yaw += 90; 861 else yaw -= 90; 862 pitch = 0; 863 } 864 while(yaw >= 360) yaw -= 360; 865 while(yaw < 0) yaw += 360; 866 vec rft; 867 vecfromyawpitch(yaw, pitch, 1, isclimb ? -d->strafe : 0, rft); 868 d->o.add(rft); 869 bool collided = collide(d, rft); 870 d->o = oldpos; 871 if(collided || collideplayer) continue; // we might find a better vector 872 if(d->impulse[IM_TYPE] != IM_T_PARKOUR) 873 { 874 int cost = int(impulsecost*(isclimb ? impulsecostclimb : impulsecostparkour)); 875 vec keepvel = vec(d->vel).add(d->falling); 876 float mag = impulsevelocity(d, isclimb ? impulseclimb : impulseparkour, cost, A_A_PARKOUR, isclimb ? impulseclimbredir : impulseparkourredir, keepvel); 877 if(mag > 0) 878 { 879 d->vel = vec(rft).mul(mag).add(keepvel); 880 d->doimpulse(IM_T_PARKOUR, lastmillis, cost, side); 881 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_PARKOUR); 882 game::impulseeffect(d); 883 game::footstep(d); 884 m = rft; // re-project and override 885 found = true; 886 } 887 } 888 else 889 { 890 d->turnside = side; 891 m = rft; // re-project and override 892 found = true; 893 } 894 break; 895 } 896 } 897 } 898 } 899 if(!found && d->impulse[IM_TYPE] == IM_T_PARKOUR) 900 { 901 if(!d->turnside && impulseclimbendstyle) 902 { 903 float mag = vec(d->vel).magnitude(); 904 if(mag > 0) 905 { 906 vec rft; 907 vecfromyawpitch(d->yaw, impulseclimbendstyle == 2 ? max(d->pitch, impulseclimbendmin) : impulseclimbendmin, 1, 0, rft); 908 d->vel = rft.mul(mag); 909 } 910 } 911 d->doimpulse(IM_T_AFTER, lastmillis); 912 client::addmsg(N_SPHY, "ri2", d->clientnum, SPHY_AFTER); 913 } 914 bool sliding = d->sliding(true), pounding = !sliding && !onfloor && d->impulse[IM_TYPE] == IM_T_POUND && d->impulsetime[IM_T_POUND] != 0, kicking = !sliding && !pounding && !onfloor && d->action[AC_SPECIAL]; 915 if((sliding || pounding || kicking) && d->canmelee(m_weapon(d->actortype, game::gamemode, game::mutators), lastmillis, sliding)) 916 { 917 vec oldpos = d->o, dir(d->yaw*RAD, 0.f); 918 loopi(2) 919 { 920 d->o.add(dir); 921 bool collided = collide(d, dir, 0, true, true); 922 d->o = oldpos; 923 if(collided && collideplayer && gameent::is(collideplayer)) 924 { 925 vec pos = collideplayer->headpos(); 926 if(weapons::doshot(d, pos, W_MELEE, true, sliding, 0, (gameent *)collideplayer)) 927 impulseplayer(d, onfloor, vec(d->vel).add(d->falling), true); 928 break; 929 } 930 if(sliding) break; 931 dir = vec(0, 0, -1); // try straight down 932 } 933 } 934 } 935 modifymovement(gameent * d,vec & m,bool local,bool wantsmove,int millis)936 void modifymovement(gameent *d, vec &m, bool local, bool wantsmove, int millis) 937 { 938 if(local && game::allowmove(d)) modifyinput(d, m, wantsmove, millis); 939 if(wantsmove && !sticktospecial(d) && d->physstate >= PHYS_SLOPE) 940 { // move up or down slopes in air but only move up slopes in liquid 941 float dz = -(m.x*d->floor.x + m.y*d->floor.y)/d->floor.z; 942 m.z = liquidcheck(d) ? max(m.z, dz) : dz; 943 if(!m.iszero()) m.normalize(); 944 } 945 if(d->physstate == PHYS_FALL && !d->onladder && d->impulse[IM_TYPE] != IM_T_PARKOUR) 946 { 947 if(!d->airmillis) d->airmillis = lastmillis ? lastmillis : 1; 948 d->floormillis = 0; 949 } 950 else 951 { 952 d->airmillis = 0; 953 if(!d->floormillis) d->floormillis = lastmillis ? lastmillis : 1; 954 } 955 if(d->impulse[IM_TYPE] != IM_T_PARKOUR && d->onladder && !m.iszero()) m.add(vec(0, 0, m.z >= 0 ? 1 : -1)).normalize(); 956 } 957 coastscale(const vec & o)958 float coastscale(const vec &o) 959 { 960 return lookupvslot(lookupcube(ivec(o)).texture[O_TOP], false).coastscale; 961 } 962 modifyvelocity(physent * pl,bool local,bool floating,int millis)963 void modifyvelocity(physent *pl, bool local, bool floating, int millis) 964 { 965 vec m(0, 0, 0); 966 bool wantsmove = game::allowmove(pl) && (pl->move || pl->strafe); 967 if(wantsmove) vecfromyawpitch(pl->yaw, movepitch(pl) ? pl->pitch : 0, pl->move, pl->strafe, m); 968 if(!floating && gameent::is(pl)) modifymovement((gameent *)pl, m, local, wantsmove, millis); 969 else if(pl->physstate == PHYS_FALL && !pl->onladder) 970 { 971 if(!pl->airmillis) pl->airmillis = lastmillis ? lastmillis : 1; 972 pl->floormillis = 0; 973 } 974 else 975 { 976 pl->airmillis = 0; 977 if(!pl->floormillis) pl->floormillis = lastmillis ? lastmillis : 1; 978 } 979 980 m.mul(movevelocity(pl, floating)); 981 float coast = PHYS(floorcoast); 982 if(floating || pl->type == ENT_CAMERA) coast = floatcoast; 983 else 984 { 985 bool slide = gameent::is(pl) && ((gameent *)pl)->sliding(); 986 float c = sticktospecial(pl) || pl->physstate >= PHYS_SLOPE || pl->onladder ? (slide ? PHYS(slidecoast) : PHYS(floorcoast))*coastscale(pl->feetpos(-1)) : PHYS(aircoast); 987 coast = pl->inliquid ? liquidmerge(pl, c, PHYS(liquidcoast)) : c; 988 } 989 pl->vel.lerp(m, pl->vel, pow(max(1.0f - 1.0f/coast, 0.0f), millis/20.0f)); 990 } 991 modifygravity(physent * pl,int curtime)992 void modifygravity(physent *pl, int curtime) 993 { 994 if(PHYS(gravity) > 0) 995 { 996 vec g(0, 0, 0); 997 float secs = curtime/1000.0f; 998 if(pl->physstate == PHYS_FALL) g.z -= gravityvel(pl)*secs; 999 else if(pl->floor.z > 0 && pl->floor.z < floorz) 1000 { 1001 g.z = -1; 1002 g.project(pl->floor); 1003 g.normalize(); 1004 g.mul(gravityvel(pl)*secs); 1005 } 1006 bool liquid = liquidcheck(pl); 1007 if(!liquid || (!pl->move && !pl->strafe) || (gameent::is(pl) && ((gameent *)pl)->crouching())) 1008 pl->falling.add(g); 1009 if(liquid || pl->physstate >= PHYS_SLOPE) 1010 { 1011 float coast = liquid ? liquidmerge(pl, PHYS(aircoast), PHYS(liquidcoast)) : PHYS(floorcoast)*coastscale(pl->feetpos(-1)), 1012 c = liquid ? 1.0f : clamp((pl->floor.z-slopez)/(floorz-slopez), 0.0f, 1.0f); 1013 pl->falling.mul(pow(max(1.0f - c/coast, 0.0f), curtime/20.0f)); 1014 } 1015 } 1016 else pl->falling = vec(0, 0, 0); 1017 } 1018 updatematerial(physent * pl,const vec & center,const vec & bottom,bool local)1019 void updatematerial(physent *pl, const vec ¢er, const vec &bottom, bool local) 1020 { 1021 float radius = center.z-bottom.z, height = radius*2, submerged = pl->submerged; 1022 int matid = lookupmaterial(bottom), oldmatid = pl->inmaterial, oldmat = oldmatid&MATF_VOLUME, liquid = 0, iters = int(ceilf(height)); 1023 if(iters > 0) 1024 { 1025 float frac = height/float(iters); // guard against rounding errors 1026 vec tmp = bottom; 1027 loopi(iters) 1028 { 1029 tmp.z += frac; 1030 int chkmat = lookupmaterial(tmp); 1031 if(!liquid && isliquid(matid&MATF_VOLUME) && !isliquid(chkmat&MATF_VOLUME)) liquid = i+1; 1032 matid |= chkmat; 1033 } 1034 } 1035 int curmat = matid&MATF_VOLUME; 1036 if(curmat != oldmat) 1037 { 1038 #define mattrig(mo,mcol,ms,mt,mz,mq,mp,mw) \ 1039 { \ 1040 int col = (int(mcol[2]*(mq)) + (int(mcol[1]*(mq)) << 8) + (int(mcol[0]*(mq)) << 16)); \ 1041 regularshape(mp, mt, col, 21, 20, mz, mo, ms, 1, 10, 0, 20); \ 1042 if((mw) >= 0) playsound(mw, mo, pl); \ 1043 } 1044 if(curmat == MAT_WATER || oldmat == MAT_WATER) 1045 { 1046 const bvec &watercol = getwatercolour((curmat == MAT_WATER ? matid : pl->inmaterial)&MATF_INDEX); 1047 mattrig(bottom, watercol, 0.5f, int(radius), PHYSMILLIS, 0.25f, PART_SPARK, curmat != MAT_WATER ? S_SPLASH2 : S_SPLASH1); 1048 } 1049 if(curmat == MAT_LAVA) 1050 { 1051 const bvec &lavacol = getlavacolour(matid&MATF_INDEX); 1052 mattrig(center, lavacol, 2.f, int(radius), PHYSMILLIS*2, 1.f, PART_FIREBALL, S_BURNLAVA); 1053 } 1054 } 1055 pl->inmaterial = matid; 1056 pl->inliquid = isliquid(curmat); 1057 pl->onladder = (matid&MATF_FLAGS)&MAT_LADDER; 1058 pl->submerged = clamp(pl->inliquid ? (liquid ? liquid/float(iters) : 1.f) : 0.f, 0.f, 1.f); 1059 if(local && pl->physstate < PHYS_SLIDE && submerged >= 0.5f && pl->submerged < 0.5f && pl->vel.z > 1e-3f) 1060 pl->vel.z = max(pl->vel.z, max(jumpvel(pl, false), gravityvel(pl))); 1061 if(pl->onladder && pl->physstate < PHYS_SLIDE) pl->floor = vec(0, 0, 1); 1062 if(local && gameent::is(pl) && (pl->inmaterial != oldmatid || pl->submerged != submerged)) 1063 client::addmsg(N_SPHY, "ri3f", ((gameent *)pl)->clientnum, SPHY_MATERIAL, pl->inmaterial, pl->submerged); 1064 } 1065 1066 // main physics routine, moves an actor for a time step 1067 // moveres indicates the physics precision (which is lower for monsters and multiplayer prediction) 1068 // local is false for multiplayer prediction 1069 moveplayer(physent * pl,int moveres,bool local,int millis)1070 bool moveplayer(physent *pl, int moveres, bool local, int millis) 1071 { 1072 bool floating = isfloating(pl), player = !floating && gameent::is(pl); 1073 float secs = millis/1000.f; 1074 1075 pl->blocked = false; 1076 if(player) 1077 { 1078 updatematerial(pl, pl->center(), pl->feetpos(), local); 1079 modifyvelocity(pl, local, false, millis); 1080 if(!sticktospecial(pl) && !pl->onladder) modifygravity(pl, millis); // apply gravity 1081 else pl->resetphys(false); 1082 } 1083 else 1084 { 1085 pl->inliquid = pl->onladder = false; 1086 pl->submerged = 0; 1087 modifyvelocity(pl, local, floating, millis); 1088 } 1089 1090 vec vel(pl->vel); 1091 if(player && pl->inliquid) vel.mul(liquidmerge(pl, 1.f, PHYS(liquidspeed))); 1092 vel.add(pl->falling); 1093 vel.mul(secs); 1094 1095 if(floating) // just apply velocity 1096 { 1097 if(pl->physstate != PHYS_FLOAT) 1098 { 1099 pl->physstate = PHYS_FLOAT; 1100 pl->airmillis = pl->floormillis = 0; 1101 pl->falling = vec(0, 0, 0); 1102 } 1103 pl->o.add(vel); 1104 } 1105 else // apply velocity with collision 1106 { 1107 vec prevel = vec(pl->vel).add(pl->falling); 1108 float mag = prevel.magnitude(); 1109 int collisions = 0, timeinair = pl->airtime(lastmillis); 1110 vel.mul(1.0f/moveres); 1111 loopi(moveres) if(!move(pl, vel)) { if(++collisions < 5) i--; } // discrete steps collision detection & sliding 1112 if(player && timeinair) 1113 { 1114 gameent *d = (gameent *)pl; 1115 if(!d->airmillis && !sticktospecial(d)) 1116 { 1117 if(local && impulsemethod&2 && timeinair >= impulseslideinair && (d->move == 1 || d->strafe) && d->action[AC_CROUCH] && allowimpulse(d, A_A_SLIDE)) 1118 impulseplayer(d, true, prevel, false, true); 1119 if(timeinair >= PHYSMILLIS) 1120 { 1121 if(mag >= 20) 1122 { 1123 int vol = min(int(mag*1.25f), 255); 1124 if(d->inliquid) vol *= 0.5f; 1125 playsound(S_LAND, d->o, d, 0, vol); 1126 } 1127 else game::footstep(d); 1128 } 1129 d->resetjump(); 1130 } 1131 } 1132 } 1133 1134 if(gameent::is(pl)) 1135 { 1136 if(pl->state == CS_ALIVE) updatedynentcache(pl); 1137 if(local) 1138 { 1139 gameent *d = (gameent *)pl; 1140 if(d->state == CS_ALIVE && !floating) 1141 { 1142 if(d->o.z < 0) 1143 { 1144 game::suicide(d, HIT(LOST)); 1145 return false; 1146 } 1147 if(d->roll != 0) adjustscaled(d->roll, PHYSMILLIS); 1148 } 1149 else d->roll = 0; 1150 } 1151 } 1152 1153 return true; 1154 } 1155 interppos(physent * d)1156 void interppos(physent *d) 1157 { 1158 d->o = d->newpos; 1159 d->o.z += d->height; 1160 1161 int diff = lastphysframe - lastmillis; 1162 if(diff <= 0 || !physinterp) return; 1163 1164 vec deltapos(d->deltapos); 1165 deltapos.mul(min(diff, physframetime)/float(physframetime)); 1166 d->o.add(deltapos); 1167 } 1168 movecamera(physent * pl,const vec & dir,float dist,float stepdist)1169 bool movecamera(physent *pl, const vec &dir, float dist, float stepdist) 1170 { 1171 int steps = (int)ceil(dist/stepdist); 1172 if(steps <= 0) return true; 1173 1174 vec d(dir); 1175 d.mul(dist/steps); 1176 loopi(steps) 1177 { 1178 vec oldpos(pl->o); 1179 pl->o.add(d); 1180 if(collide(pl, vec(0, 0, 0), 0, false)) 1181 { 1182 pl->o = oldpos; 1183 return false; 1184 } 1185 } 1186 return true; 1187 } 1188 move(physent * d,int moveres,bool local)1189 void move(physent *d, int moveres, bool local) 1190 { 1191 if(physsteps <= 0) 1192 { 1193 if(local) interppos(d); 1194 return; 1195 } 1196 1197 if(local) 1198 { 1199 d->o = d->newpos; 1200 d->o.z += d->height; 1201 } 1202 loopi(physsteps-1) moveplayer(d, moveres, local, physframetime); 1203 if(local) d->deltapos = d->o; 1204 moveplayer(d, moveres, local, physframetime); 1205 if(local) 1206 { 1207 d->newpos = d->o; 1208 d->deltapos.sub(d->newpos); 1209 d->newpos.z -= d->height; 1210 interppos(d); 1211 } 1212 } 1213 updatephysstate(physent * d)1214 void updatephysstate(physent *d) 1215 { 1216 if(d->physstate == PHYS_FALL && !d->onladder) return; 1217 vec old(d->o); 1218 /* Attempt to reconstruct the floor state. 1219 * May be inaccurate since movement collisions are not considered. 1220 * If good floor is not found, just keep the old floor and hope it's correct enough. 1221 */ 1222 bool foundfloor = false; 1223 switch(d->physstate) 1224 { 1225 case PHYS_SLOPE: 1226 case PHYS_FLOOR: 1227 case PHYS_STEP_DOWN: 1228 d->o.z -= 0.15f; 1229 if(collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE || d->physstate == PHYS_STEP_DOWN ? slopez : floorz)) 1230 { 1231 d->floor = collidewall; 1232 foundfloor = true; 1233 } 1234 break; 1235 1236 case PHYS_STEP_UP: 1237 d->o.z -= stairheight+0.15f; 1238 if(collide(d, vec(0, 0, -1), slopez)) 1239 { 1240 d->floor = collidewall; 1241 foundfloor = true; 1242 } 1243 break; 1244 1245 case PHYS_SLIDE: 1246 d->o.z -= 0.15f; 1247 if(collide(d, vec(0, 0, -1)) && collidewall.z < slopez) 1248 { 1249 d->floor = collidewall; 1250 foundfloor = true; 1251 } 1252 break; 1253 default: break; 1254 } 1255 if((d->physstate > PHYS_FALL && d->floor.z <= 0) || (d->onladder && !foundfloor)) d->floor = vec(0, 0, 1); 1256 d->o = old; 1257 } 1258 hitzonecollide(gameent * e,const vec & o,const vec & ray,float & dist)1259 bool hitzonecollide(gameent *e, const vec &o, const vec &ray, float &dist) 1260 { 1261 modelstate mdl; 1262 modelattach mdlattach[ATTACHMENTMAX]; 1263 const char *mdlname = game::getplayerstate(e, mdl, 1, e->curscale, 0, mdlattach); 1264 dist = 1e16f; 1265 int zone = intersectmodel(mdlname, mdl, o, ray, dist, 0, e); 1266 switch(zone) 1267 { 1268 case -1: return false; 1269 case 0: collidezones = CLZ_HEAD; break; 1270 case 1: collidezones = CLZ_TORSO; break; 1271 default: collidezones = CLZ_LIMB; break; 1272 } 1273 dist *= ray.magnitude(); 1274 return true; 1275 } 1276 checkcollide(physent * d,const vec & dir,physent * o)1277 bool checkcollide(physent *d, const vec &dir, physent *o) 1278 { 1279 collidezones = CLZ_HEAD; 1280 if(!d || !projent::shot(d) || !gameent::is(o)) return true; 1281 gameent *e = (gameent *)o; 1282 if(!actors[e->actortype].hitboxes) return true; 1283 collidezones = CLZ_NONE; 1284 if(!d->o.reject(e->limbstag(), d->radius+max(e->limbsbox().x, e->limbsbox().y)) && ellipsecollide(d, dir, e->limbstag(), vec(0, 0, 0), e->yaw, e->limbsbox().x, e->limbsbox().y, e->limbsbox().z, e->limbsbox().z)) 1285 collidezones |= CLZ_LIMB; 1286 if(!d->o.reject(e->torsotag(), d->radius+max(e->torsobox().x, e->torsobox().y)) && ellipsecollide(d, dir, e->torsotag(), vec(0, 0, 0), e->yaw, e->torsobox().x, e->torsobox().y, e->torsobox().z, e->torsobox().z)) 1287 collidezones |= CLZ_TORSO; 1288 if(!d->o.reject(e->headtag(), d->radius+max(e->headbox().x, e->headbox().y)) && ellipsecollide(d, dir, e->headtag(), vec(0, 0, 0), e->yaw, e->headbox().x, e->headbox().y, e->headbox().z, e->headbox().z)) 1289 collidezones |= CLZ_HEAD; 1290 return collidezones != CLZ_NONE; 1291 } 1292 checktracecollide(physent * d,const vec & from,const vec & to,float & dist,physent * o,float x1,float x2,float y1,float y2)1293 bool checktracecollide(physent *d, const vec &from, const vec &to, float &dist, physent *o, float x1, float x2, float y1, float y2) 1294 { 1295 collidezones = CLZ_HEAD; 1296 if(!d || !projent::shot(d) || !gameent::is(o)) return true; 1297 gameent *e = (gameent *)o; 1298 if(!actors[e->actortype].hitboxes) return true; 1299 collidezones = CLZ_NONE; 1300 float bestdist = 1e16f; 1301 if(e->limbstag().x+e->limbsbox().x >= x1 && e->limbstag().y+e->limbsbox().y >= y1 && e->limbstag().x-e->limbsbox().x <= x2 && e->limbstag().y-e->limbsbox().y <= y2) 1302 { 1303 vec bottom(e->limbstag()), top(e->limbstag()); 1304 bottom.z -= e->limbsbox().z; 1305 top.z += e->limbsbox().z; 1306 float t = 1e16f; 1307 if(linecylinderintersect(from, to, bottom, top, max(e->limbsbox().x, e->limbsbox().y), t)) 1308 { 1309 collidezones |= CLZ_LIMB; 1310 bestdist = min(bestdist, t); 1311 } 1312 } 1313 if(e->torsotag().x+e->torsobox().x >= x1 && e->torsotag().y+e->torsobox().y >= y1 && e->torsotag().x-e->torsobox().x <= x2 && e->torsotag().y-e->torsobox().y <= y2) 1314 { 1315 vec bottom(e->torsotag()), top(e->torsotag()); 1316 bottom.z -= e->torsobox().z; 1317 top.z += e->torsobox().z; 1318 float t = 1e16f; 1319 if(linecylinderintersect(from, to, bottom, top, max(e->torsobox().x, e->torsobox().y), t)) 1320 { 1321 collidezones |= CLZ_TORSO; 1322 bestdist = min(bestdist, t); 1323 } 1324 } 1325 if(e->headtag().x+e->headbox().x >= x1 && e->headtag().y+e->headbox().y >= y1 && e->headtag().x-e->headbox().x <= x2 && e->headtag().y-e->headbox().y <= y2) 1326 { 1327 vec bottom(e->headtag()), top(e->headtag()); 1328 bottom.z -= e->headbox().z; 1329 top.z += e->headbox().z; 1330 float t = 1e16f; 1331 if(linecylinderintersect(from, to, bottom, top, max(e->headbox().x, e->headbox().y), t)) 1332 { 1333 collidezones |= CLZ_HEAD; 1334 bestdist = min(bestdist, t); 1335 } 1336 } 1337 if(collidezones == CLZ_NONE) return false; 1338 dist = bestdist*from.dist(to); 1339 return true; 1340 } 1341 renderboundboxes(physent * d,const vec & rad,const vec & o)1342 void renderboundboxes(physent *d, const vec &rad, const vec &o) 1343 { 1344 if(!gameent::is(d)) return; 1345 vec pos = vec(o).sub(GUARDRADIUS), radius = vec(rad).add(GUARDRADIUS*2); 1346 loopj(6) boxs(j, pos, radius); 1347 gameent *e = (gameent *)d; 1348 if(!actors[e->actortype].hitboxes || (e == game::focus && !game::thirdpersonview())) return; 1349 vec headpos = vec(e->headtag()).sub(e->headbox()), headbox = vec(e->headbox()).mul(2), 1350 torsopos = vec(e->torsotag()).sub(e->torsobox()), torsobox = vec(e->torsobox()).mul(2), 1351 limbspos = vec(e->limbstag()).sub(e->limbsbox()), limbsbox = vec(e->limbsbox()).mul(2); 1352 loopj(6) 1353 { 1354 boxs(j, headpos, headbox); 1355 boxs(j, torsopos, torsobox); 1356 boxs(j, limbspos, limbsbox); 1357 } 1358 } 1359 entinmap(physent * d,bool avoidplayers)1360 bool entinmap(physent *d, bool avoidplayers) 1361 { 1362 if(d->state != CS_ALIVE) 1363 { 1364 d->resetinterp(); 1365 return insideworld(d->o); 1366 } 1367 vec orig = d->o; 1368 float maxrad = max(d->radius, max(d->xradius, d->yradius))+1; 1369 #define doposchk \ 1370 if(insideworld(d->o) && !collide(d, vec(0, 0, 0), 0, avoidplayers)) \ 1371 { \ 1372 d->resetinterp(); \ 1373 return true; \ 1374 } \ 1375 else d->o = orig; 1376 #define inmapchk(x,y) \ 1377 loopi(x) \ 1378 { \ 1379 int n = i+1; \ 1380 y; \ 1381 doposchk; \ 1382 } 1383 doposchk; 1384 if(gameent::is(d)) loopk(18) 1385 { 1386 vec dir = vec(d->yaw*RAD, d->pitch*RAD).rotate_around_z(k*20.f*RAD); 1387 if(!dir.iszero()) inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad))); 1388 } 1389 if(!d->vel.iszero()) loopk(18) 1390 { 1391 vec dir = vec(d->vel).normalize().rotate_around_z(k*20.f*RAD); 1392 inmapchk(100, d->o.add(vec(dir).mul(n/10.f+maxrad))); 1393 } 1394 inmapchk(100, d->o.add(vec((rnd(21)-10)/10.f, (rnd(21)-10)/10.f, (rnd(21)-10)/10.f).normalize().mul(vec(n/10.f+maxrad, n/10.f+maxrad, n/25.f+maxrad)))); 1395 inmapchk(20, d->o.z += (d->height+d->aboveeye)*n/10.f); 1396 d->o = orig; 1397 d->resetinterp(); 1398 return false; 1399 } 1400 1401 VAR(IDF_PERSIST, smoothmove, 0, 100, 200); 1402 VAR(IDF_PERSIST, smoothdist, 0, 64, 1024); 1403 predictplayer(gameent * d,bool domove,int res=0,bool local=false)1404 void predictplayer(gameent *d, bool domove, int res = 0, bool local = false) 1405 { 1406 d->o = d->newpos; 1407 d->o.z += d->height; 1408 1409 d->yaw = d->newyaw; 1410 d->pitch = d->newpitch; 1411 1412 if(domove) 1413 { 1414 move(d, res, local); 1415 d->newpos = d->o; 1416 d->newpos.z -= d->height; 1417 } 1418 1419 float k = 1.0f - float(lastmillis - d->smoothmillis)/float(smoothmove); 1420 if(k > 0) 1421 { 1422 d->o.add(vec(d->deltapos).mul(k)); 1423 d->yaw += d->deltayaw*k; 1424 if(d->yaw < 0) d->yaw += 360; 1425 else if(d->yaw >= 360) d->yaw -= 360; 1426 d->pitch += d->deltapitch*k; 1427 } 1428 } 1429 smoothplayer(gameent * d,int res,bool local)1430 void smoothplayer(gameent *d, int res, bool local) 1431 { 1432 if(d->state == CS_ALIVE || d->state == CS_EDITING) 1433 { 1434 if(smoothmove && d->smoothmillis > 0) predictplayer(d, true, res, local); 1435 else move(d, res, local); 1436 } 1437 else if(d->state == CS_DEAD || d->state == CS_WAITING) 1438 { 1439 if(d->ragdoll) moveragdoll(d); 1440 else if(lastmillis-d->lastpain < 2000) move(d, res, local); 1441 } 1442 } 1443 droptofloor(vec & o,int type,float radius,float height)1444 bool droptofloor(vec &o, int type, float radius, float height) 1445 { 1446 static struct dropent : physent 1447 { 1448 dropent() 1449 { 1450 physent::reset(); 1451 radius = xradius = yradius = height = aboveeye = 1; 1452 type = ENT_DUMMY; 1453 vel = vec(0, 0, -1); 1454 } 1455 } d; 1456 d.o = o; 1457 d.type = type; 1458 if(!insideworld(d.o)) 1459 { 1460 if(d.o.z < worldsize) return false; 1461 d.o.z = worldsize - 1e-3f; 1462 if(!insideworld(d.o)) return false; 1463 } 1464 vec v(0.0001f, 0.0001f, -1); 1465 v.normalize(); 1466 if(raycube(d.o, v, worldsize) >= worldsize) return false; 1467 d.radius = d.xradius = d.yradius = radius; 1468 d.height = height; 1469 d.aboveeye = radius; 1470 if(!movecamera(&d, vec(0, 0, -1), worldsize, 1)) 1471 { 1472 o = d.o; 1473 return true; 1474 } 1475 return false; 1476 } 1477 reset()1478 void reset() 1479 { 1480 lastphysframe = 0; 1481 } 1482 update()1483 void update() 1484 { 1485 if(!lastphysframe) lastphysframe = lastmillis; 1486 int diff = lastmillis - lastphysframe; 1487 if(diff <= 0) physsteps = 0; 1488 else 1489 { 1490 physsteps = (diff + physframetime - 1)/physframetime; 1491 lastphysframe += physsteps * physframetime; 1492 } 1493 cleardynentcache(); 1494 } 1495 } 1496