1 /* 2 Copyright (c) 2013 yvt 3 based on code of pysnip (c) Mathias Kaerlev 2011-2012. 4 5 This file is part of OpenSpades. 6 7 OpenSpades is free software: you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation, either version 3 of the License, or 10 (at your option) any later version. 11 12 OpenSpades is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with OpenSpades. If not, see <http://www.gnu.org/licenses/>. 19 20 */ 21 22 #include <cmath> 23 #include <cstdlib> 24 #include <iterator> 25 26 #include "Client.h" 27 28 #include <Core/ConcurrentDispatch.h> 29 #include <Core/Settings.h> 30 #include <Core/Strings.h> 31 32 #include "IAudioChunk.h" 33 #include "IAudioDevice.h" 34 35 #include "CenterMessageView.h" 36 #include "ChatWindow.h" 37 #include "ClientPlayer.h" 38 #include "ClientUI.h" 39 #include "Corpse.h" 40 #include "FallingBlock.h" 41 #include "HurtRingView.h" 42 #include "ILocalEntity.h" 43 #include "LimboView.h" 44 #include "MapView.h" 45 #include "PaletteView.h" 46 #include "ParticleSpriteEntity.h" 47 #include "SmokeSpriteEntity.h" 48 49 #include "GameMap.h" 50 #include "Grenade.h" 51 #include "Weapon.h" 52 #include "World.h" 53 54 #include "NetClient.h" 55 56 DEFINE_SPADES_SETTING(cg_blood, "1"); 57 DEFINE_SPADES_SETTING(cg_particles, "2"); 58 DEFINE_SPADES_SETTING(cg_waterImpact, "1"); 59 SPADES_SETTING(cg_manualFocus); 60 DEFINE_SPADES_SETTING(cg_autoFocusSpeed, "0.4"); 61 62 namespace spades { 63 namespace client { 64 65 #pragma mark - Local Entities / Effects 66 RemoveAllCorpses()67 void Client::RemoveAllCorpses() { 68 SPADES_MARK_FUNCTION(); 69 70 corpses.clear(); 71 lastMyCorpse = nullptr; 72 } 73 RemoveAllLocalEntities()74 void Client::RemoveAllLocalEntities() { 75 SPADES_MARK_FUNCTION(); 76 77 localEntities.clear(); 78 } 79 RemoveInvisibleCorpses()80 void Client::RemoveInvisibleCorpses() { 81 SPADES_MARK_FUNCTION(); 82 83 decltype(corpses)::iterator it; 84 std::vector<decltype(it)> its; 85 int cnt = (int)corpses.size() - corpseSoftLimit; 86 for (it = corpses.begin(); it != corpses.end(); it++) { 87 if (cnt <= 0) 88 break; 89 auto &c = *it; 90 if (!c->IsVisibleFrom(lastSceneDef.viewOrigin)) { 91 if (c.get() == lastMyCorpse) 92 lastMyCorpse = nullptr; 93 its.push_back(it); 94 } 95 cnt--; 96 } 97 98 for (size_t i = 0; i < its.size(); i++) 99 corpses.erase(its[i]); 100 } 101 RemoveCorpseForPlayer(int playerId)102 void Client::RemoveCorpseForPlayer(int playerId) { 103 for (auto it = corpses.begin(); it != corpses.end();) { 104 auto cur = it; 105 ++it; 106 107 auto &c = *cur; 108 if (c->GetPlayerId() == playerId) { 109 corpses.erase(cur); 110 } 111 } 112 } 113 HotTrackedPlayer(hitTag_t * hitFlag)114 Player *Client::HotTrackedPlayer(hitTag_t *hitFlag) { 115 if (!IsFirstPerson(GetCameraMode())) 116 return nullptr; 117 118 auto &p = GetCameraTargetPlayer(); 119 120 Vector3 origin = p.GetEye(); 121 Vector3 dir = p.GetFront(); 122 World::WeaponRayCastResult result = world->WeaponRayCast(origin, dir, &p); 123 124 if (result.hit == false || result.player == nullptr) 125 return nullptr; 126 127 // don't hot track enemies (non-spectator only) 128 if (result.player->GetTeamId() != p.GetTeamId() && p.GetTeamId() < 2) 129 return nullptr; 130 if (hitFlag) { 131 *hitFlag = result.hitFlag; 132 } 133 return result.player; 134 } 135 IsMuted()136 bool Client::IsMuted() { 137 // prevent to play loud sound at connection 138 // caused by saved packets 139 return time < worldSetTime + .05f; 140 } 141 Bleed(spades::Vector3 v)142 void Client::Bleed(spades::Vector3 v) { 143 SPADES_MARK_FUNCTION(); 144 145 if (!cg_blood) 146 return; 147 148 // distance cull 149 if ((v - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f) 150 return; 151 152 if ((int)cg_particles < 1) 153 return; 154 155 Handle<IImage> img = renderer->RegisterImage("Gfx/White.tga"); 156 Vector4 color = {0.5f, 0.02f, 0.04f, 1.f}; 157 for (int i = 0; i < 10; i++) { 158 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 159 ent->SetTrajectory(v, 160 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 161 SampleRandomFloat() - SampleRandomFloat(), 162 SampleRandomFloat() - SampleRandomFloat()) * 163 10.f, 164 1.f, 0.7f); 165 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 166 ent->SetRadius(0.1f + SampleRandomFloat() * SampleRandomFloat() * 0.2f); 167 ent->SetLifeTime(3.f, 0.f, 1.f); 168 localEntities.emplace_back(ent); 169 } 170 171 if ((int)cg_particles < 2) 172 return; 173 174 color = MakeVector4(.7f, .35f, .37f, .6f); 175 for (int i = 0; i < 2; i++) { 176 ParticleSpriteEntity *ent = 177 new SmokeSpriteEntity(this, color, 100.f, SmokeSpriteEntity::Type::Explosion); 178 ent->SetTrajectory(v, 179 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 180 SampleRandomFloat() - SampleRandomFloat(), 181 SampleRandomFloat() - SampleRandomFloat()) * 182 .7f, 183 .8f, 0.f); 184 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 185 ent->SetRadius(.5f + SampleRandomFloat() * SampleRandomFloat() * 0.2f, 2.f); 186 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 187 ent->SetLifeTime(.20f + SampleRandomFloat() * .2f, 0.06f, .20f); 188 localEntities.emplace_back(ent); 189 } 190 191 color.w *= .1f; 192 for (int i = 0; i < 1; i++) { 193 ParticleSpriteEntity *ent = 194 new SmokeSpriteEntity(this, color, 40.f, SmokeSpriteEntity::Type::Steady); 195 ent->SetTrajectory(v, 196 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 197 SampleRandomFloat() - SampleRandomFloat(), 198 SampleRandomFloat() - SampleRandomFloat()) * 199 .7f, 200 .8f, 0.f); 201 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 202 ent->SetRadius(.7f + SampleRandomFloat() * SampleRandomFloat() * 0.2f, 2.f, 0.1f); 203 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 204 ent->SetLifeTime(.80f + SampleRandomFloat() * 0.4f, 0.06f, 1.0f); 205 localEntities.emplace_back(ent); 206 } 207 } 208 EmitBlockFragments(Vector3 origin,IntVector3 c)209 void Client::EmitBlockFragments(Vector3 origin, IntVector3 c) { 210 SPADES_MARK_FUNCTION(); 211 212 // distance cull 213 float distPowered = (origin - lastSceneDef.viewOrigin).GetPoweredLength(); 214 if (distPowered > 150.f * 150.f) 215 return; 216 217 if ((int)cg_particles < 1) 218 return; 219 220 Handle<IImage> img = renderer->RegisterImage("Gfx/White.tga"); 221 Vector4 color = {c.x / 255.f, c.y / 255.f, c.z / 255.f, 1.f}; 222 for (int i = 0; i < 7; i++) { 223 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 224 ent->SetTrajectory(origin, 225 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 226 SampleRandomFloat() - SampleRandomFloat(), 227 SampleRandomFloat() - SampleRandomFloat()) * 228 7.f, 229 1.f, .9f); 230 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 231 ent->SetRadius(0.2f + SampleRandomFloat() * SampleRandomFloat() * 0.1f); 232 ent->SetLifeTime(2.f, 0.f, 1.f); 233 if (distPowered < 16.f * 16.f) 234 ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); 235 localEntities.emplace_back(ent); 236 } 237 238 if ((int)cg_particles < 2) 239 return; 240 241 if (distPowered < 32.f * 32.f) { 242 for (int i = 0; i < 16; i++) { 243 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 244 ent->SetTrajectory(origin, MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 245 SampleRandomFloat() - SampleRandomFloat(), 246 SampleRandomFloat() - SampleRandomFloat()) * 247 12.f, 248 1.f, .9f); 249 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 250 ent->SetRadius(0.1f + SampleRandomFloat() * SampleRandomFloat() * 0.14f); 251 ent->SetLifeTime(2.f, 0.f, 1.f); 252 if (distPowered < 16.f * 16.f) 253 ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); 254 localEntities.emplace_back(ent); 255 } 256 } 257 258 color += (MakeVector4(1, 1, 1, 1) - color) * .2f; 259 color.w *= .2f; 260 for (int i = 0; i < 2; i++) { 261 ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 100.f); 262 ent->SetTrajectory(origin, 263 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 264 SampleRandomFloat() - SampleRandomFloat(), 265 SampleRandomFloat() - SampleRandomFloat()) * 266 .7f, 267 1.f, 0.f); 268 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 269 ent->SetRadius(.6f + SampleRandomFloat() * SampleRandomFloat() * 0.2f, 0.8f); 270 ent->SetLifeTime(.3f + SampleRandomFloat() * .3f, 0.06f, .4f); 271 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 272 localEntities.emplace_back(ent); 273 } 274 } 275 EmitBlockDestroyFragments(IntVector3 blk,IntVector3 c)276 void Client::EmitBlockDestroyFragments(IntVector3 blk, IntVector3 c) { 277 SPADES_MARK_FUNCTION(); 278 279 Vector3 origin = {blk.x + .5f, blk.y + .5f, blk.z + .5f}; 280 281 // distance cull 282 if ((origin - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f) 283 return; 284 285 if ((int)cg_particles < 1) 286 return; 287 288 Handle<IImage> img = renderer->RegisterImage("Gfx/White.tga"); 289 Vector4 color = {c.x / 255.f, c.y / 255.f, c.z / 255.f, 1.f}; 290 for (int i = 0; i < 8; i++) { 291 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 292 ent->SetTrajectory(origin, 293 MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 294 SampleRandomFloat() - SampleRandomFloat(), 295 SampleRandomFloat() - SampleRandomFloat()) * 296 7.f, 297 1.f, 1.f); 298 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 299 ent->SetRadius(0.3f + SampleRandomFloat() * SampleRandomFloat() * 0.2f); 300 ent->SetLifeTime(2.f, 0.f, 1.f); 301 ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); 302 localEntities.emplace_back(ent); 303 } 304 } 305 MuzzleFire(spades::Vector3 origin,spades::Vector3 dir,bool local)306 void Client::MuzzleFire(spades::Vector3 origin, spades::Vector3 dir, bool local) { 307 DynamicLightParam l; 308 l.origin = origin; 309 l.radius = 5.f; 310 l.type = DynamicLightTypePoint; 311 l.color = MakeVector3(3.f, 1.6f, 0.5f); 312 flashDlights.push_back(l); 313 314 if ((int)cg_particles < 1) 315 return; 316 317 Vector4 color; 318 Vector3 velBias = {0, 0, -0.5f}; 319 color = MakeVector4(.8f, .8f, .8f, .3f); 320 321 // rapid smoke 322 for (int i = 0; i < 2; i++) { 323 ParticleSpriteEntity *ent = 324 new SmokeSpriteEntity(this, color, 120.f, SmokeSpriteEntity::Type::Explosion); 325 ent->SetTrajectory( 326 origin, (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 327 SampleRandomFloat() - SampleRandomFloat(), 328 SampleRandomFloat() - SampleRandomFloat()) + 329 velBias * .5f) * 330 0.3f, 331 1.f, 0.f); 332 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 333 ent->SetRadius(.4f, 3.f, 0.0000005f); 334 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 335 ent->SetLifeTime(0.2f + SampleRandomFloat() * 0.1f, 0.f, .30f); 336 localEntities.emplace_back(ent); 337 } 338 } 339 KickCamera(float strength)340 void Client::KickCamera(float strength) { 341 grenadeVibration = std::min(grenadeVibration + strength, 0.4f); 342 grenadeVibrationSlow = std::min(grenadeVibrationSlow + strength * 5.f, 0.4f); 343 } 344 GrenadeExplosion(spades::Vector3 origin)345 void Client::GrenadeExplosion(spades::Vector3 origin) { 346 float dist = (origin - lastSceneDef.viewOrigin).GetLength(); 347 if (dist > 170.f) 348 return; 349 KickCamera(2.f / (dist + 5.f)); 350 351 DynamicLightParam l; 352 l.origin = origin; 353 l.radius = 16.f; 354 l.type = DynamicLightTypePoint; 355 l.color = MakeVector3(3.f, 1.6f, 0.5f); 356 l.useLensFlare = true; 357 flashDlights.push_back(l); 358 359 if ((int)cg_particles < 1) 360 return; 361 362 Vector3 velBias = {0, 0, 0}; 363 if (!map->ClipBox(origin.x, origin.y, origin.z)) { 364 if (map->ClipBox(origin.x + 1.f, origin.y, origin.z)) { 365 velBias.x -= 1.f; 366 } 367 if (map->ClipBox(origin.x - 1.f, origin.y, origin.z)) { 368 velBias.x += 1.f; 369 } 370 if (map->ClipBox(origin.x, origin.y + 1.f, origin.z)) { 371 velBias.y -= 1.f; 372 } 373 if (map->ClipBox(origin.x, origin.y - 1.f, origin.z)) { 374 velBias.y += 1.f; 375 } 376 if (map->ClipBox(origin.x, origin.y, origin.z + 1.f)) { 377 velBias.z -= 1.f; 378 } 379 if (map->ClipBox(origin.x, origin.y, origin.z - 1.f)) { 380 velBias.z += 1.f; 381 } 382 } 383 384 Vector4 color; 385 color = MakeVector4(.6f, .6f, .6f, 1.f); 386 // rapid smoke 387 for (int i = 0; i < 4; i++) { 388 ParticleSpriteEntity *ent = 389 new SmokeSpriteEntity(this, color, 60.f, SmokeSpriteEntity::Type::Explosion); 390 ent->SetTrajectory( 391 origin, (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 392 SampleRandomFloat() - SampleRandomFloat(), 393 SampleRandomFloat() - SampleRandomFloat()) + 394 velBias * .5f) * 395 2.f, 396 1.f, 0.f); 397 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 398 ent->SetRadius(.6f + SampleRandomFloat() * SampleRandomFloat() * 0.4f, 2.f, .2f); 399 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 400 ent->SetLifeTime(1.8f + SampleRandomFloat() * 0.1f, 0.f, .20f); 401 localEntities.emplace_back(ent); 402 } 403 404 // slow smoke 405 color.w = .25f; 406 for (int i = 0; i < 8; i++) { 407 ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 20.f); 408 ent->SetTrajectory( 409 origin, (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 410 SampleRandomFloat() - SampleRandomFloat(), 411 (SampleRandomFloat() - SampleRandomFloat()) * .2f)) * 412 2.f, 413 1.f, 0.f); 414 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 415 ent->SetRadius(1.5f + SampleRandomFloat() * SampleRandomFloat() * 0.8f, 0.2f); 416 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 417 switch ((int)cg_particles) { 418 case 1: ent->SetLifeTime(0.8f + SampleRandomFloat() * 1.f, 0.1f, 8.f); break; 419 case 2: ent->SetLifeTime(1.5f + SampleRandomFloat() * 2.f, 0.1f, 8.f); break; 420 case 3: 421 default: ent->SetLifeTime(2.f + SampleRandomFloat() * 5.f, 0.1f, 8.f); break; 422 } 423 localEntities.emplace_back(ent); 424 } 425 426 // fragments 427 Handle<IImage> img = renderer->RegisterImage("Gfx/White.tga"); 428 color = MakeVector4(0.01, 0.03, 0, 1.f); 429 for (int i = 0; i < 42; i++) { 430 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 431 Vector3 dir = MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 432 SampleRandomFloat() - SampleRandomFloat(), 433 SampleRandomFloat() - SampleRandomFloat()); 434 dir += velBias * .5f; 435 float radius = 0.1f + SampleRandomFloat() * SampleRandomFloat() * 0.2f; 436 ent->SetTrajectory(origin + dir * .2f, dir * 20.f, .1f + radius * 3.f, 1.f); 437 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 438 ent->SetRadius(radius); 439 ent->SetLifeTime(3.5f + SampleRandomFloat() * 2.f, 0.f, 1.f); 440 ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); 441 localEntities.emplace_back(ent); 442 } 443 444 // fire smoke 445 color = MakeVector4(1.f, .7f, .4f, .2f) * 5.f; 446 for (int i = 0; i < 4; i++) { 447 ParticleSpriteEntity *ent = 448 new SmokeSpriteEntity(this, color, 120.f, SmokeSpriteEntity::Type::Explosion); 449 ent->SetTrajectory( 450 origin, (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), SampleRandomFloat() - SampleRandomFloat(), 451 SampleRandomFloat() - SampleRandomFloat()) + 452 velBias) * 453 6.f, 454 1.f, 0.f); 455 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 456 ent->SetRadius(.3f + SampleRandomFloat() * SampleRandomFloat() * 0.4f, 3.f, .1f); 457 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 458 ent->SetLifeTime(.18f + SampleRandomFloat() * 0.03f, 0.f, .10f); 459 // ent->SetAdditive(true); 460 localEntities.emplace_back(ent); 461 } 462 } 463 GrenadeExplosionUnderwater(spades::Vector3 origin)464 void Client::GrenadeExplosionUnderwater(spades::Vector3 origin) { 465 float dist = (origin - lastSceneDef.viewOrigin).GetLength(); 466 if (dist > 170.f) 467 return; 468 KickCamera(1.5f / (dist + 5.f)); 469 470 if ((int)cg_particles < 1) 471 return; 472 473 Vector3 velBias = {0, 0, 0}; 474 475 Vector4 color; 476 color = MakeVector4(.95f, .95f, .95f, .6f); 477 // water1 478 Handle<IImage> img = renderer->RegisterImage("Textures/WaterExpl.png"); 479 if ((int)cg_particles < 2) 480 color.w = .3f; 481 for (int i = 0; i < 7; i++) { 482 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 483 ent->SetTrajectory(origin, 484 (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 485 SampleRandomFloat() - SampleRandomFloat(), -SampleRandomFloat() * 7.f)) * 486 2.5f, 487 .3f, .6f); 488 ent->SetRotation(0.f); 489 ent->SetRadius(1.5f + SampleRandomFloat() * SampleRandomFloat() * 0.4f, 1.3f); 490 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 491 ent->SetLifeTime(3.f + SampleRandomFloat() * 0.3f, 0.f, .60f); 492 localEntities.emplace_back(ent); 493 } 494 495 // water2 496 img = renderer->RegisterImage("Textures/Fluid.png"); 497 color.w = .9f; 498 if ((int)cg_particles < 2) 499 color.w = .4f; 500 for (int i = 0; i < 16; i++) { 501 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 502 ent->SetTrajectory(origin, 503 (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 504 SampleRandomFloat() - SampleRandomFloat(), -SampleRandomFloat() * 10.f)) * 505 3.5f, 506 1.f, 1.f); 507 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 508 ent->SetRadius(0.9f + SampleRandomFloat() * SampleRandomFloat() * 0.4f, 0.7f); 509 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 510 ent->SetLifeTime(3.f + SampleRandomFloat() * 0.3f, .7f, .60f); 511 localEntities.emplace_back(ent); 512 } 513 514 // slow smoke 515 color.w = .4f; 516 if ((int)cg_particles < 2) 517 color.w = .2f; 518 for (int i = 0; i < 8; i++) { 519 ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 20.f); 520 ent->SetTrajectory( 521 origin, (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), SampleRandomFloat() - SampleRandomFloat(), 522 (SampleRandomFloat() - SampleRandomFloat()) * .2f)) * 523 2.f, 524 1.f, 0.f); 525 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 526 ent->SetRadius(1.4f + SampleRandomFloat() * SampleRandomFloat() * 0.8f, 0.2f); 527 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 528 switch ((int)cg_particles) { 529 case 1: ent->SetLifeTime(3.f + SampleRandomFloat() * 5.f, 0.1f, 8.f); break; 530 case 2: 531 case 3: 532 default: ent->SetLifeTime(6.f + SampleRandomFloat() * 5.f, 0.1f, 8.f); break; 533 } 534 localEntities.emplace_back(ent); 535 } 536 537 // fragments 538 img = renderer->RegisterImage("Gfx/White.tga"); 539 color = MakeVector4(1, 1, 1, 0.7f); 540 for (int i = 0; i < 42; i++) { 541 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 542 Vector3 dir = MakeVector3(SampleRandomFloat() - SampleRandomFloat(), SampleRandomFloat() - SampleRandomFloat(), 543 -SampleRandomFloat() * 3.f); 544 dir += velBias * .5f; 545 float radius = 0.1f + SampleRandomFloat() * SampleRandomFloat() * 0.2f; 546 ent->SetTrajectory(origin + dir * .2f + MakeVector3(0, 0, -1.2f), dir * 13.f, 547 .1f + radius * 3.f, 1.f); 548 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 549 ent->SetRadius(radius); 550 ent->SetLifeTime(3.5f + SampleRandomFloat() * 2.f, 0.f, 1.f); 551 ent->SetBlockHitAction(ParticleSpriteEntity::Delete); 552 localEntities.emplace_back(ent); 553 } 554 555 // TODO: wave? 556 } 557 BulletHitWaterSurface(spades::Vector3 origin)558 void Client::BulletHitWaterSurface(spades::Vector3 origin) { 559 float dist = (origin - lastSceneDef.viewOrigin).GetLength(); 560 if (dist > 150.f) 561 return; 562 if (!cg_waterImpact) 563 return; 564 565 if ((int)cg_particles < 1) 566 return; 567 568 Vector4 color; 569 color = MakeVector4(.95f, .95f, .95f, .3f); 570 // water1 571 Handle<IImage> img = renderer->RegisterImage("Textures/WaterExpl.png"); 572 if ((int)cg_particles < 2) 573 color.w = .2f; 574 for (int i = 0; i < 2; i++) { 575 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 576 ent->SetTrajectory(origin, 577 (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 578 SampleRandomFloat() - SampleRandomFloat(), -SampleRandomFloat() * 7.f)) * 579 1.f, 580 .3f, .6f); 581 ent->SetRotation(0.f); 582 ent->SetRadius(0.6f + SampleRandomFloat() * SampleRandomFloat() * 0.4f, .7f); 583 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 584 ent->SetLifeTime(3.f + SampleRandomFloat() * 0.3f, 0.1f, .60f); 585 localEntities.emplace_back(ent); 586 } 587 588 // water2 589 img = renderer->RegisterImage("Textures/Fluid.png"); 590 color.w = .9f; 591 if ((int)cg_particles < 2) 592 color.w = .4f; 593 for (int i = 0; i < 6; i++) { 594 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 595 ent->SetTrajectory(origin, 596 (MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 597 SampleRandomFloat() - SampleRandomFloat(), -SampleRandomFloat() * 10.f)) * 598 2.f, 599 1.f, 1.f); 600 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 601 ent->SetRadius(0.6f + SampleRandomFloat() * SampleRandomFloat() * 0.6f, 0.6f); 602 ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); 603 ent->SetLifeTime(3.f + SampleRandomFloat() * 0.3f, SampleRandomFloat() * 0.3f, .60f); 604 localEntities.emplace_back(ent); 605 } 606 607 // fragments 608 img = renderer->RegisterImage("Gfx/White.tga"); 609 color = MakeVector4(1, 1, 1, 0.7f); 610 for (int i = 0; i < 10; i++) { 611 ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); 612 Vector3 dir = MakeVector3(SampleRandomFloat() - SampleRandomFloat(), 613 SampleRandomFloat() - SampleRandomFloat(), 614 -SampleRandomFloat() * 3.f); 615 float radius = 0.03f + SampleRandomFloat() * SampleRandomFloat() * 0.05f; 616 ent->SetTrajectory(origin + dir * .2f + MakeVector3(0, 0, -1.2f), dir * 5.f, 617 .1f + radius * 3.f, 1.f); 618 ent->SetRotation(SampleRandomFloat() * (float)M_PI * 2.f); 619 ent->SetRadius(radius); 620 ent->SetLifeTime(3.5f + SampleRandomFloat() * 2.f, 0.f, 1.f); 621 ent->SetBlockHitAction(ParticleSpriteEntity::Delete); 622 localEntities.emplace_back(ent); 623 } 624 625 // TODO: wave? 626 } 627 628 #pragma mark - Camera Control 629 630 enum { AutoFocusPoints = 4 }; UpdateAutoFocus(float dt)631 void Client::UpdateAutoFocus(float dt) { 632 if (autoFocusEnabled && world && (int)cg_manualFocus) { 633 // Compute focal length 634 float measureRange = tanf(lastSceneDef.fovY * .5f) * .2f; 635 const Vector3 camOrigin = lastSceneDef.viewOrigin; 636 const float lenScale = 1.f / lastSceneDef.viewAxis[2].GetLength(); 637 const Vector3 camDir = lastSceneDef.viewAxis[2].Normalize(); 638 const Vector3 camX = lastSceneDef.viewAxis[0].Normalize() * measureRange; 639 const Vector3 camY = lastSceneDef.viewAxis[1].Normalize() * measureRange; 640 641 float distances[AutoFocusPoints * AutoFocusPoints]; 642 std::size_t numValidDistances = 0; 643 Vector3 camDir1 = camDir - camX - camY; 644 const Vector3 camDX = camX * (2.f / (AutoFocusPoints - 1)); 645 const Vector3 camDY = camY * (2.f / (AutoFocusPoints - 1)); 646 for (int x = 0; x < AutoFocusPoints; ++x) { 647 Vector3 camDir2 = camDir1; 648 for (int y = 0; y < AutoFocusPoints; ++y) { 649 float dist = RayCastForAutoFocus(camOrigin, camDir2); 650 651 dist *= lenScale; 652 653 if (std::isfinite(dist) && dist > 0.8f) { 654 distances[numValidDistances++] = dist; 655 } 656 657 camDir2 += camDY; 658 } 659 camDir1 += camDX; 660 } 661 662 if (numValidDistances > 0) { 663 // Take median 664 std::sort(distances, distances + numValidDistances); 665 666 float dist = (numValidDistances & 1) 667 ? distances[numValidDistances >> 1] 668 : (distances[numValidDistances >> 1] + 669 distances[(numValidDistances >> 1) - 1]) * 670 0.5f; 671 672 targetFocalLength = dist; 673 } 674 } 675 676 // Change the actual focal length slowly 677 { 678 float dist = 1.f / targetFocalLength; 679 float curDist = 1.f / focalLength; 680 const float maxSpeed = cg_autoFocusSpeed; 681 682 if (dist > curDist) { 683 curDist = std::min(dist, curDist + maxSpeed * dt); 684 } else { 685 curDist = std::max(dist, curDist - maxSpeed * dt); 686 } 687 688 focalLength = 1.f / curDist; 689 } 690 } RayCastForAutoFocus(const Vector3 & origin,const Vector3 & direction)691 float Client::RayCastForAutoFocus(const Vector3 &origin, const Vector3 &direction) { 692 SPAssert(world); 693 694 const auto &lastSceneDef = this->lastSceneDef; 695 World::WeaponRayCastResult result = world->WeaponRayCast(origin, direction, nullptr); 696 if (result.hit) { 697 return Vector3::Dot(result.hitPos - origin, lastSceneDef.viewAxis[2]); 698 } 699 700 return std::nan(nullptr); 701 } 702 } 703 } 704