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