/* Copyright (c) 2013 yvt This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include "Client.h" #include "IRenderer.h" #include "GameMap.h" #include "SceneDefinition.h" #include "../Core/FileManager.h" #include "../Core/IStream.h" #include "GameMapWrapper.h" #include "World.h" #include "Player.h" #include "IAudioChunk.h" #include "IAudioDevice.h" #include "Weapon.h" #include "Grenade.h" #include "../Core/Debug.h" #include "NetClient.h" #include "FontData.h" #include "Quake3Font.h" #include "../Core/Exception.h" #include "ChatWindow.h" #include "Corpse.h" #include "CenterMessageView.h" #include "HurtRingView.h" #include "CTFGameMode.h" #include "MapView.h" #include "ScoreboardView.h" #include "LimboView.h" #include "ILocalEntity.h" #include "ParticleSpriteEntity.h" #include "SmokeSpriteEntity.h" #include "GameMapWrapper.h" #include "../Core/Settings.h" #include "../Core/Bitmap.h" #include #include "FallingBlock.h" #include "GunCasing.h" #include "../Core/ConcurrentDispatch.h" #include "PaletteView.h" #include "TCGameMode.h" #include "TCProgressView.h" #include "../Core/IStream.h" #include #include #include "Tracer.h" static float nextRandom() { return (float)rand() / (float)RAND_MAX; } SPADES_SETTING(cg_ragdoll, "1"); SPADES_SETTING(cg_blood, "1"); SPADES_SETTING(cg_ejectBrass, "1"); SPADES_SETTING(cg_mouseSensitivity, "1"); SPADES_SETTING(cg_zoomedMouseSensScale, "0.6"); SPADES_SETTING(cg_keyAttack, "LeftMouseButton"); SPADES_SETTING(cg_keyAltAttack, "RightMouseButton"); SPADES_SETTING(cg_keyToolSpade, "1"); SPADES_SETTING(cg_keyToolBlock, "2"); SPADES_SETTING(cg_keyToolWeapon, "3"); SPADES_SETTING(cg_keyToolGrenade, "4"); SPADES_SETTING(cg_keyReloadWeapon, "r"); SPADES_SETTING(cg_keyFlashlight, "f"); SPADES_SETTING(cg_keyMoveLeft, "a"); SPADES_SETTING(cg_keyMoveRight, "d"); SPADES_SETTING(cg_keyMoveForward, "w"); SPADES_SETTING(cg_keyMoveBackward, "s"); SPADES_SETTING(cg_keyJump, "Space"); SPADES_SETTING(cg_keyCrouch, "Control"); SPADES_SETTING(cg_keySprint, "Shift"); SPADES_SETTING(cg_keySneak, "v"); SPADES_SETTING(cg_keyCaptureColor, "e"); SPADES_SETTING(cg_keyGlobalChat, "t"); SPADES_SETTING(cg_keyTeamChat, "y"); SPADES_SETTING(cg_keyChangeMapScale, "m"); SPADES_SETTING(cg_keyToggleMapZoom, "n"); SPADES_SETTING(cg_keyScoreboard, "Tab"); SPADES_SETTING(cg_keyLimbo, "l"); SPADES_SETTING(cg_keyScreenshot, "0"); SPADES_SETTING(cg_keySceneshot, "9"); SPADES_SETTING(cg_switchToolByWheel, "1"); namespace spades { namespace client { Client::Client(IRenderer *r, IAudioDevice *audioDev, std::string host, std::string playerName): renderer(r), audioDevice(audioDev), playerName(playerName) { SPADES_MARK_FUNCTION(); SPLog("Initializing..."); /* designFont = new Quake3Font(renderer, renderer->RegisterImage("Gfx/Fonts/Orbitron.tga"), (const int*)OrbitronMap, 30, 18); */ designFont = new Quake3Font(renderer, renderer->RegisterImage("Gfx/Fonts/UnsteadyOversteer.tga"), (const int *)UnsteadyOversteerMap, 30, 18); SPLog("Font 'Unsteady Oversteer' Loaded"); textFont = new Quake3Font(renderer, renderer->RegisterImage("Gfx/Fonts/UbuntuCondensed.tga"), (const int*)UbuntuCondensedMap, 24, 4); SPLog("Font 'Ubuntu Condensed' Loaded"); bigTextFont = new Quake3Font(renderer, renderer->RegisterImage("Gfx/Fonts/UbuntuCondensedBig.tga"), (const int*)UbuntuCondensedBigMap, 48, 8); SPLog("Font 'Ubuntu Condensed (Large)' Loaded"); world = NULL; // preferences? corpseSoftTimeLimit = 30.f; // TODO: this is not used corpseSoftLimit = 6; corpseHardLimit = 16; lastMyCorpse = NULL; renderer->SetFogDistance(128.f); renderer->SetFogColor(MakeVector3(.8f, 1.f, 1.f)); chatWindow = new ChatWindow(this, textFont, false); killfeedWindow = new ChatWindow(this, textFont, true); chatEditing = false; hurtRingView = new HurtRingView(this); centerMessageView = new CenterMessageView(this, bigTextFont); mapView = new MapView(this, false); largeMapView = new MapView(this, true); scoreboard = new ScoreboardView(this); limbo = new LimboView(this); paletteView = new PaletteView(this); tcView = new TCProgressView(this); time = 0.f; lastAliveTime = 0.f; readyToClose = false; scoreboardVisible = false; flashlightOn = false; lastKills = 0; localFireVibrationTime = -1.f; viewWeaponOffset = MakeVector3(0, 0, 0); lastPosSentTime = 0.f; worldSubFrame = 0.f; grenadeVibration = 0.f; inGameLimbo = false; nextScreenShotIndex = 0; // preload SmokeSpriteEntity(this, Vector4(), 20.f); SPLog("Started connecting to '%s'", host.c_str()); net = new NetClient(this); net->Connect(host); //net->Connect("192.168.24.24"); //net->Connect("127.0.0.1"); // decide log file name std::string fn = host; if(fn.find("aos:///") == 0) fn = fn.substr(7); if(fn.find("aos://") == 0) fn = fn.substr(6); std::string fn2; { time_t t; struct tm tm; ::time(&t); tm = *localtime(&t); char buf[256]; sprintf(buf, "%04d%02d%02d%02d%02d%02d_", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); fn2 = buf; } for(size_t i = 0; i < fn.size(); i++){ char c = fn[i]; if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { fn2 += c; }else{ fn2 += '_'; } } fn2 = "NetLogs/" + fn2 + ".log"; try{ logStream = FileManager::OpenForWriting(fn2.c_str()); SPLog("Netlog Started at '%s'", fn2.c_str()); }catch(const std::exception& ex){ SPLog("Failed to open netlog file '%s'", fn2.c_str()); } } void Client::SetWorld(spades::client::World *w) { SPADES_MARK_FUNCTION(); if(world == w){ return; } RemoveAllCorpses(); lastHealth = 0; lastHurtTime = -100.f; hurtRingView->ClearAll(); scoreboardVisible = false; flashlightOn = false; if(world){ world->SetListener(NULL); renderer->SetGameMap(NULL); audioDevice->SetGameMap(NULL); delete world; world = NULL; map = NULL; } world = w; if(world){ SPLog("World set"); world->SetListener(this); map = world->GetMap(); renderer->SetGameMap(map); audioDevice->SetGameMap(map); NetLog("------ World Loaded ------"); }else{ SPLog("World removed"); NetLog("------ World Unloaded ------"); } limbo->SetSelectedTeam(2); limbo->SetSelectedWeapon(RIFLE_WEAPON); RemoveAllLocalEntities(); worldSubFrame = 0.f; aimDownState = 0.f; sprintState = 0.f; inGameLimbo = false; worldSetTime = time; } Client::~Client() { SPADES_MARK_FUNCTION(); NetLog("Disconnecting"); if(logStream) { SPLog("Closing netlog"); delete logStream; } SPLog("Disconnecting"); net->Disconnect(); delete net; SPLog("Disconnected"); RemoveAllLocalEntities(); RemoveAllCorpses(); // do before deleting world renderer->SetGameMap(NULL); if(world) delete world; delete tcView; delete limbo; delete scoreboard; delete mapView; delete largeMapView; delete chatWindow; delete killfeedWindow; delete paletteView; delete centerMessageView; delete hurtRingView; delete designFont; delete textFont; delete bigTextFont; } bool Client::WantsToBeClosed() { return readyToClose; } void Client::RunFrame(float dt) { SPADES_MARK_FUNCTION(); try{ if(net->GetStatus() == NetClientStatusConnected) net->DoEvents(0); else net->DoEvents(30); }catch(const std::exception& ex){ if(net->GetStatus() == NetClientStatusNotConnected){ SPLog("Disconnected because of error:\n%s", ex.what()); NetLog("Disconnected because of error:\n%s", ex.what()); throw; }else{ SPLog("Exception while processing network packets (ignored):\n%s", ex.what()); } } hurtRingView->Update(dt); centerMessageView->Update(dt); mapView->Update(dt); largeMapView->Update(dt); //puts(net->GetStatusString().c_str()); if(world){ Player* player = world->GetLocalPlayer(); if(player && player->GetTeamId() >= 2){ // spectating Vector3 lastPos = followPos; followVel *= powf(.3f, dt); followPos += followVel * dt; GameMap::RayCastResult minResult; float minDist = 1.e+10f; Vector3 minShift; if(followVel.GetLength() < .01){ followPos = lastPos; followVel *= 0.f; }else{ for(int sx = -1; sx <= 1; sx ++) for(int sy = -1; sy <= 1; sy++) for(int sz = -1; sz <= 1; sz++){ GameMap::RayCastResult result; Vector3 shift = {sx*.1f, sy*.1f,sz*.1f}; result = map->CastRay2(lastPos+shift, followPos - lastPos, 256); if(result.hit && !result.startSolid && Vector3::Dot(result.hitPos - followPos - shift, followPos - lastPos) < 0.f){ float dist = Vector3::Dot(result.hitPos - followPos - shift, (followPos - lastPos).Normalize()); if(dist < minDist){ minResult = result; minDist = dist; minShift = shift; } } } } if(minDist < 1.e+9f){ GameMap::RayCastResult result = minResult; Vector3 shift = minShift; followPos = result.hitPos - shift; followPos.x += result.normal.x * .02f; followPos.y += result.normal.y * .02f; followPos.z += result.normal.z * .02f; // reflect Vector3 norm = {(float)result.normal.x, (float)result.normal.y, (float)result.normal.z}; float dot = Vector3::Dot(followVel, norm); followVel -= norm * (dot * 1.2f); /* int link = world->mapWrapper->GetLink(result.hitBlock.x, result.hitBlock.y, result.hitBlock.z); printf("hit block: link = %d (", link); switch(link){ case 0: puts("Invalid)"); break; case 1: puts("Root)"); break; case 2: puts("Neg X)"); break; case 3: puts("Pos X)"); break; case 4: puts("Neg Y)"); break; case 5: puts("Pos Y)"); break; case 6: puts("Neg Z)"); break; case 7: puts("Pos Z)"); break; }*/ } Vector3 front; Vector3 up = {0, 0, -1}; front.x = -cosf(followYaw) * cosf(followPitch); front.y = -sinf(followYaw) * cosf(followPitch); front.z = sinf(followPitch); Vector3 right = -Vector3::Cross(up, front).Normalize(); Vector3 up2 = Vector3::Cross(right, front).Normalize(); float scale = 10.f * dt; if(playerInput.sprint){ scale *= 3.f; } front *= scale; right *= scale; up2 *= scale; if(playerInput.moveForward){ followVel += front; }else if(playerInput.moveBackward){ followVel -= front; } if(playerInput.moveLeft){ followVel -= right; }else if(playerInput.moveRight){ followVel += right; } if(playerInput.jump){ followVel += up2; }else if(playerInput.crouch){ followVel -= up2; } SPAssert(followVel.GetLength() < 100.f); }else if(player){ // joined in a team PlayerInput inp = playerInput; WeaponInput winp = weapInput; if(inp.crouch == false){ if(player->GetInput().crouch){ if(!player->TryUncrouch(false)){ inp.crouch = true; } } } if(inp.jump){ if(!player->IsOnGroundOrWade()) inp.jump = false; } if(selectedTool != player->GetTool() || toolRaiseState < .999f) { winp.primary = false; winp.secondary = false; } if(player->GetTool() == Player::ToolWeapon && player->IsAwaitingReloadCompletion()) { winp.primary = false; } player->SetInput(inp); player->SetWeaponInput(winp); //send player input // FIXME: send only there are any changed net->SendPlayerInput(inp); net->SendWeaponInput(weapInput); PlayerInput actualInput = player->GetInput(); WeaponInput actualWeapInput = player->GetWeaponInput(); if(actualWeapInput.secondary && player->IsToolWeapon() && player->IsAlive()){ aimDownState += dt * 6.f; if(aimDownState > 1.f) aimDownState = 1.f; }else{ aimDownState -= dt * 3.f; if(aimDownState < 0.f) aimDownState = 0.f; if(player->IsToolWeapon()){ // there is a possibility that player has respawned or something. // stop aiming down weapInput.secondary = false; } } Vector3 vel = player->GetVelocty(); vel.z = 0.f; if(actualInput.sprint && player->IsAlive() && vel.GetLength() > .1f){ sprintState += dt * 4.f; if(sprintState > 1.f) sprintState = 1.f; }else{ sprintState -= dt * 3.f; if(sprintState < 0.f) sprintState = 0.f; } if(!player->IsToolSelectable(selectedTool)) { // select another tool Player::ToolType t = selectedTool; do{ switch(t){ case Player::ToolSpade: t = Player::ToolGrenade; break; case Player::ToolBlock: t = Player::ToolSpade; break; case Player::ToolWeapon: t = Player::ToolBlock; break; case Player::ToolGrenade: t = Player::ToolWeapon; break; } }while(!world->GetLocalPlayer()->IsToolSelectable(t)); SetSelectedTool(t); } if(selectedTool == player->GetTool()) { toolRaiseState += dt * 4.f; if(toolRaiseState > 1.f) toolRaiseState = 1.f; }else{ toolRaiseState -= dt * 4.f; if(toolRaiseState < 0.f){ toolRaiseState = 0.f; player->SetTool(selectedTool); net->SendTool(); // TODO: play tool raise sound } } Vector3 curFront = player->GetFront(); if(curFront.x != lastFront.x || curFront.y != lastFront.y || curFront.z != lastFront.z) { lastFront = curFront; net->SendOrientation(curFront); } lastKills = world->GetPlayerPersistent(player->GetId()).kills; if(player->IsAlive()) lastAliveTime = time; if(player->GetHealth() < lastHealth){ // hurt! lastHealth = player->GetHealth(); lastHurtTime = world->GetTime(); IAudioChunk *c; switch((rand() >> 3) & 3){ case 0: c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal1.wav"); break; case 1: c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal2.wav"); break; case 2: c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal3.wav"); break; case 3: c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal4.wav"); break; } audioDevice->PlayLocal(c, AudioParam()); }else{ lastHealth = player->GetHealth(); } inp.jump = false; } #if 0 world->Advance(dt); #else // accurately resembles server's physics // but not smooth if(dt > 0.f) worldSubFrame += dt; float frameStep = 1.f / 60.f; while(worldSubFrame >= frameStep){ world->Advance(frameStep); worldSubFrame -= frameStep; } #endif // corpse never accesses audio nor renderer class CorpseUpdateDispatch: public ConcurrentDispatch{ Client *client; float dt; public: CorpseUpdateDispatch(Client *c, float dt): client(c), dt(dt){} virtual void Run(){ std::list::iterator it; for(it = client->corpses.begin(); it != client->corpses.end(); it++){ for(int i = 0; i < 4; i++) (*it)->Update(dt / 4.f); } } }; CorpseUpdateDispatch corpseDispatch(this, dt); corpseDispatch.Start(); { std::list::iterator it; std::vector::iterator> its; for(it = localEntities.begin(); it != localEntities.end(); it++){ if(!(*it)->Update(dt)) its.push_back(it); } for(size_t i = 0; i < its.size(); i++){ delete *(its[i]); localEntities.erase(its[i]); } } corpseDispatch.Join(); if(grenadeVibration > 0.f){ grenadeVibration -= dt; if(grenadeVibration < 0.f) grenadeVibration = 0.f; } if(time > lastPosSentTime + 1.f && world->GetLocalPlayer()){ Player *p = world->GetLocalPlayer(); if(p->IsAlive()){ net->SendPosition(); lastPosSentTime = time; } } }else{ renderer->SetFogColor(MakeVector3(0.f, 0.f, 0.f)); } chatWindow->Update(dt); killfeedWindow->Update(dt); limbo->Update(dt); // SceneDef also can be used for sounds SceneDefinition sceneDef = SceneDef(); lastSceneDef = sceneDef; // Update sounds try{ audioDevice->Respatialize(sceneDef.viewOrigin, sceneDef.viewAxis[2], sceneDef.viewAxis[1]); }catch(const std::exception& ex){ SPLog("Audio subsystem returned error (ignored):\n%s", ex.what()); } // render scene DrawScene(); // draw 2d Draw2D(); // Well done! renderer->FrameDone(); renderer->Flip(); if(world){ Player* player = world->GetLocalPlayer(); // view effects if(player){ float scale = dt; Vector3 vel = player->GetVelocty(); Vector3 front = player->GetFront(); Vector3 right = player->GetRight(); Vector3 up = player->GetUp(); viewWeaponOffset.x += Vector3::Dot(vel, right) * scale; viewWeaponOffset.y -= Vector3::Dot(vel, front) * scale; viewWeaponOffset.z += Vector3::Dot(vel, up) * scale; if(dt > 0.f) viewWeaponOffset *= powf(.02f, dt); if(player->GetTool() == Player::ToolWeapon && player->GetWeaponInput().secondary) { if(dt > 0.f) viewWeaponOffset *= powf(.01f, dt); const float limitX = .01f; const float limitY = .01f; if(viewWeaponOffset.x < -limitX) viewWeaponOffset.x = Mix(viewWeaponOffset.x, -limitX, .5f); if(viewWeaponOffset.x > limitX) viewWeaponOffset.x = Mix(viewWeaponOffset.x, limitX, .5f); if(viewWeaponOffset.z < 0.f) viewWeaponOffset.z = Mix(viewWeaponOffset.z, 0.f, .5f); if(viewWeaponOffset.z > limitY) viewWeaponOffset.z = Mix(viewWeaponOffset.z, limitY, .5f); } }else{ viewWeaponOffset = MakeVector3(0,0,0); } } time += dt; } void Client::Closing() { SPADES_MARK_FUNCTION(); } void Client::MouseEvent(float x, float y) { SPADES_MARK_FUNCTION(); if(IsLimboViewActive()){ limbo->MouseEvent(x, y); return; } if(IsFollowing()){ SPAssert(world != NULL); if(world->GetLocalPlayer() && world->GetLocalPlayer()->GetTeamId() >= 2 && followingPlayerId == world->GetLocalPlayerIndex()){ // invert dir x = -x; y = -y; } followYaw -= x * 0.003f; followPitch -= y * 0.003f; if(followPitch < -M_PI*.45f) followPitch = -M_PI*.45f; if(followPitch > M_PI*.45f) followPitch = M_PI * .45f; followYaw = fmodf(followYaw, M_PI*2.f); }else if(world && world->GetLocalPlayer()){ Player *p = world->GetLocalPlayer(); if(p->IsAlive()){ x /= GetAimDownZoomScale(); y /= GetAimDownZoomScale(); if(aimDownState > 0.f) { float scale = cg_zoomedMouseSensScale; scale = powf(scale, aimDownState); x *= scale; y *= scale; } x *= (float)cg_mouseSensitivity; y *= (float)cg_mouseSensitivity; p->Turn(x * 0.003f, y * 0.003f); } } } void Client::CharEvent(const std::string &ch){ SPADES_MARK_FUNCTION(); if(chatEditing){ ChatCharEvent(ch); return; } if(ch == "/"){ ActivateChatTextEditor(false); ChatCharEvent("/"); } } // TODO: this might not be a fast way static bool CheckKey(const std::string& cfg, const std::string& input) { if(cfg.empty()) return false; std::vector keys = Split(cfg,","); static const std::string space1("space"); static const std::string space2("spacebar"); static const std::string space3("spacekey"); for(size_t i = 0; i < keys.size(); i++) { std::string key = keys[i]; if(EqualsIgnoringCase(key, space1) || EqualsIgnoringCase(key, space2) || EqualsIgnoringCase(key, space3)) { if(input == " ") return true; }else{ if(EqualsIgnoringCase(key, input)) return true; } } return false; } void Client::KeyEvent(const std::string& name, bool down){ SPADES_MARK_FUNCTION(); if(chatEditing){ if(down) ChatKeyEvent(name); return; } if(name == "Escape"){ if(down){ if(inGameLimbo){ inGameLimbo = false; }else{ readyToClose = true; } } }else if(world){ if(IsLimboViewActive()){ if(down){ limbo->KeyEvent(name); } return; } if(IsFollowing()){ if(CheckKey(cg_keyAttack, name)){ if(down){ if(world->GetLocalPlayer()->GetTeamId() >= 2 || time > lastAliveTime + 1.3f) FollowNextPlayer(); } return; }else if(CheckKey(cg_keyAltAttack, name)){ if(down){ if(world->GetLocalPlayer()){ followingPlayerId = world->GetLocalPlayerIndex(); } } return; } } if(world->GetLocalPlayer()){ Player *p = world->GetLocalPlayer(); if(p->IsAlive() && p->GetTool() == Player::ToolBlock && down) { if(paletteView->KeyInput(name)){ return; } } if(CheckKey(cg_keyMoveLeft, name)){ playerInput.moveLeft = down; keypadInput.left = down; if(down) playerInput.moveRight = false; else playerInput.moveRight = keypadInput.right; }else if(CheckKey(cg_keyMoveRight, name)){ playerInput.moveRight = down; keypadInput.right = down; if(down) playerInput.moveLeft = false; else playerInput.moveLeft = keypadInput.left; }else if(CheckKey(cg_keyMoveForward, name)){ playerInput.moveForward = down; keypadInput.forward = down; if(down) playerInput.moveBackward = false; else playerInput.moveBackward = keypadInput.backward; }else if(CheckKey(cg_keyMoveBackward, name)){ playerInput.moveBackward = down; keypadInput.backward = down; if(down) playerInput.moveForward = false; else playerInput.moveForward = keypadInput.forward; }else if(CheckKey(cg_keyCrouch, name)){ playerInput.crouch = down; }else if(CheckKey(cg_keySprint, name)){ playerInput.sprint = down; if(down){ if(world->GetLocalPlayer()->IsToolWeapon()){ weapInput.secondary = false; } } }else if(CheckKey(cg_keySneak, name)){ playerInput.sneak = down; }else if(CheckKey(cg_keyJump, name)){ playerInput.jump = down; }else if(CheckKey(cg_keyAttack, name)){ weapInput.primary = down; }else if(CheckKey(cg_keyAltAttack, name)){ if(world->GetLocalPlayer()->IsToolWeapon()){ if(down && !playerInput.sprint){ weapInput.secondary = !weapInput.secondary; } }else{ weapInput.secondary = down; } }else if(CheckKey(cg_keyReloadWeapon, name) && down){ Weapon *w = world->GetLocalPlayer()->GetWeapon(); if(w->GetAmmo() < w->GetClipSize() && w->GetStock() > 0 && (!world->GetLocalPlayer()->IsAwaitingReloadCompletion()) && (!w->IsReloading()) && world->GetLocalPlayer()->GetTool() == Player::ToolWeapon){ world->GetLocalPlayer()->Reload(); if(world->GetLocalPlayer()->IsToolWeapon()){ weapInput.secondary = false; } net->SendReload(); } }else if(CheckKey(cg_keyToolSpade, name) && down){ if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive() && world->GetLocalPlayer()->IsToolSelectable(Player::ToolSpade)){ SetSelectedTool(Player::ToolSpade); } }else if(CheckKey(cg_keyToolBlock, name) && down){ if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive() && world->GetLocalPlayer()->IsToolSelectable(Player::ToolBlock)){ SetSelectedTool(Player::ToolBlock); } }else if(CheckKey(cg_keyToolWeapon, name) && down){ if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive() && world->GetLocalPlayer()->IsToolSelectable(Player::ToolWeapon)){ SetSelectedTool(Player::ToolWeapon); } }else if(CheckKey(cg_keyToolGrenade, name) && down){ if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive() && world->GetLocalPlayer()->IsToolSelectable(Player::ToolGrenade)){ SetSelectedTool(Player::ToolGrenade); } }else if(CheckKey(cg_keyGlobalChat, name) && down){ // global chat ActivateChatTextEditor(true); }else if(CheckKey(cg_keyTeamChat, name) && down){ // team chat ActivateChatTextEditor(false); }else if(CheckKey(cg_keyCaptureColor, name) && down){ CaptureColor(); }else if(CheckKey(cg_keyChangeMapScale, name) && down){ mapView->SwitchScale(); IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Misc/SwitchMapZoom.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else if(CheckKey(cg_keyToggleMapZoom, name) && down){ if(largeMapView->ToggleZoom()){ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Misc/OpenMap.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else{ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Misc/CloseMap.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } }else if(CheckKey(cg_keyScoreboard, name)){ scoreboardVisible = down; }else if(CheckKey(cg_keyLimbo, name) && down){ limbo->SetSelectedTeam(world->GetLocalPlayer()->GetTeamId()); limbo->SetSelectedWeapon(world->GetLocalPlayer()->GetWeapon()->GetWeaponType()); inGameLimbo = true; }else if(CheckKey(cg_keySceneshot, name) && down){ TakeScreenShot(true); }else if(CheckKey(cg_keyScreenshot, name) && down){ TakeScreenShot(false); }else if(CheckKey(cg_keyFlashlight, name) && down){ flashlightOn = !flashlightOn; flashlightOnTime = time; IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Player/Flashlight.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else if(cg_switchToolByWheel && down) { bool rev = (int)cg_switchToolByWheel > 0; if(name == (rev ? "WheelDown":"WheelUp")) { if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive()){ Player::ToolType t = selectedTool; do{ switch(t){ case Player::ToolSpade: t = Player::ToolGrenade; break; case Player::ToolBlock: t = Player::ToolSpade; break; case Player::ToolWeapon: t = Player::ToolBlock; break; case Player::ToolGrenade: t = Player::ToolWeapon; break; } }while(!world->GetLocalPlayer()->IsToolSelectable(t)); SetSelectedTool(t); } }else if(name == (rev ? "WheelUp":"WheelDown")) { if(world->GetLocalPlayer()->GetTeamId() < 2 && world->GetLocalPlayer()->IsAlive()){ Player::ToolType t = selectedTool; do{ switch(t){ case Player::ToolSpade: t = Player::ToolBlock; break; case Player::ToolBlock: t = Player::ToolWeapon; break; case Player::ToolWeapon: t = Player::ToolGrenade; break; case Player::ToolGrenade: t = Player::ToolSpade; break; } }while(!world->GetLocalPlayer()->IsToolSelectable(t)); SetSelectedTool(t); } } } }else{ // limbo } } } void Client::CaptureColor() { if(!world) return; Player *p = world->GetLocalPlayer(); if(!p) return; if(!p->IsAlive()) return; IntVector3 outBlockCoord; if(!world->GetMap()->CastRay(p->GetEye(), p->GetFront(), 256.f, outBlockCoord)){ return; } uint32_t col = world->GetMap()->GetColorWrapped(outBlockCoord.x, outBlockCoord.y, outBlockCoord.z); IntVector3 colV; colV.x = (uint8_t)(col); colV.y = (uint8_t)(col >> 8); colV.z = (uint8_t)(col >> 16); p->SetHeldBlockColor(colV); net->SendHeldBlockColor(); } bool Client::IsLimboViewActive(){ if(world){ if(!world->GetLocalPlayer()){ return true; }else if(inGameLimbo){ return true; } } // TODO: anytime limbo return false; } void Client::SpawnPressed() { WeaponType weap = limbo->GetSelectedWeapon(); int team = limbo->GetSelectedTeam(); inGameLimbo = false; if(team == 2) team = 255; if(!world->GetLocalPlayer()){ // join net->SendJoin(team, weap, playerName, lastKills); }else{ Player *p = world->GetLocalPlayer(); if(p->GetTeamId() != team){ net->SendTeamChange(team); } if(team != 2 && p->GetWeapon()->GetWeaponType() != weap){ net->SendWeaponChange(weap); } } } void Client::NetLog(const char *format, ...) { char buf[4096]; va_list va; va_start(va, format); vsprintf(buf, format, va); va_end(va); std::string str = buf; time_t t; struct tm tm; ::time(&t); tm = *localtime(&t); std::string timeStr = asctime(&tm); // remove '\n' in the end of the result of asctime(). timeStr.resize(timeStr.size()-1); sprintf(buf, "%s %s\n", timeStr.c_str(), str.c_str()); printf("%s", buf); if(logStream) { logStream->Write(buf); logStream->Flush(); } } void Client::SetSelectedTool(Player::ToolType type, bool quiet) { if(type == selectedTool) return; selectedTool = type; if(!quiet) { IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/SwitchLocal.wav"); audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), AudioParam()); } } #pragma mark - Drawing void Client::TakeScreenShot(bool sceneOnly){ SceneDefinition sceneDef = SceneDef(); lastSceneDef = sceneDef; // render scene flashDlights = flashDlightsOld; DrawScene(); // draw 2d if(!sceneOnly) Draw2D(); // Well done! renderer->FrameDone(); Bitmap *bmp = renderer->ReadBitmap(); try{ std::string name = ScreenShotPath(); bmp->Save(name); std::string msg; if(sceneOnly) msg = "Sceneshot saved: "; else msg = "Screenshot saved: "; msg += name; msg = ChatWindow::ColoredMessage(msg, MsgColorSysInfo); chatWindow->AddMessage(msg); delete bmp; }catch(const std::exception& ex){ std::string msg; msg = "Screenshot failed: "; std::vector lines = SplitIntoLines(ex.what()); msg += lines[0]; msg = ChatWindow::ColoredMessage(msg, MsgColorRed); chatWindow->AddMessage(msg); delete bmp; } } std::string Client::ScreenShotPath() { char buf[256]; for(int i = 0; i < 10000;i++){ sprintf(buf, "Screenshots/shot%04d.tga", nextScreenShotIndex); if(FileManager::FileExists(buf)){ nextScreenShotIndex++; if(nextScreenShotIndex >= 10000) nextScreenShotIndex = 0; continue; } return buf; } SPRaise("No free file name"); } bool Client::ShouldRenderInThirdPersonView() { //^return true; if(world && world->GetLocalPlayer()){ if(!world->GetLocalPlayer()->IsAlive()) return true; } return false; } float Client::GetLocalFireVibration() { float localFireVibration = 0.f; localFireVibration = time - localFireVibrationTime; localFireVibration = 1.f - localFireVibration / 0.1f; if(localFireVibration < 0.f) localFireVibration = 0.f; return localFireVibration; } float Client::GetAimDownZoomScale(){ if(world == NULL || world->GetLocalPlayer() == NULL || world->GetLocalPlayer()->IsToolWeapon() == false || world->GetLocalPlayer()->IsAlive() == false) return 1.f; float delta = .8f; switch(world->GetLocalPlayer()->GetWeapon()->GetWeaponType()) { case SMG_WEAPON: delta = .8f; break; case RIFLE_WEAPON: delta = 1.4f; break; case SHOTGUN_WEAPON: delta = .4f; break; } return 1.f + powf(aimDownState, 5.f) * delta; } SceneDefinition Client::SceneDef() { SPADES_MARK_FUNCTION(); SceneDefinition def; def.time = (unsigned int)(time * 1000.f); def.denyCameraBlur = true; if(world){ IntVector3 fogColor = world->GetFogColor(); renderer->SetFogColor(MakeVector3(fogColor.x / 255.f, fogColor.y / 255.f, fogColor.z / 255.f)); Player *player = world->GetLocalPlayer(); if(IsFollowing()){ int limit = 100; // if current following player has left, // or removed, // choose next player. while(!world->GetPlayer(followingPlayerId) || world->GetPlayer(followingPlayerId)->GetFront().GetPoweredLength() < .01f){ FollowNextPlayer(); if((limit--) <= 0){ break; } } player = world->GetPlayer(followingPlayerId); } if(player){ float roll = 0.f; float scale = 1.f; float vibPitch = 0.f; float vibYaw = 0.f; if(ShouldRenderInThirdPersonView() || (IsFollowing() && player != world->GetLocalPlayer())){ Vector3 center = player->GetEye(); Vector3 playerFront = player->GetFront2D(); Vector3 up = MakeVector3(0,0,-1); if((!player->IsAlive()) && lastMyCorpse && player == world->GetLocalPlayer()){ center = lastMyCorpse->GetCenter(); } if(map->IsSolidWrapped((int)floorf(center.x), (int)floorf(center.y), (int)floorf(center.z))){ float z = center.z; while(z > center.z - 5.f){ if(!map->IsSolidWrapped((int)floorf(center.x), (int)floorf(center.y), (int)floorf(z))){ center.z = z; break; }else{ z -= 1.f; } } } float distance = 5.f; if(player == world->GetLocalPlayer() && world->GetLocalPlayer()->GetTeamId() < 2 && !world->GetLocalPlayer()->IsAlive()){ // deathcam. float elapsedTime = time - lastAliveTime; distance -= 3.f * expf(-elapsedTime * 1.f); } Vector3 eye = center; //eye -= playerFront * 5.f; //eye += up * 2.0f; eye.x += cosf(followYaw) * cosf(followPitch) * distance; eye.y += sinf(followYaw) * cosf(followPitch) * distance; eye.z -= sinf(followPitch) * distance; if(false){ // settings for making limbo stuff eye = center; eye += playerFront * 3.f; eye += up * -.1f; eye += player->GetRight() *2.f; scale *= .6f; } // try ray casting GameMap::RayCastResult result; result = map->CastRay2(center, (eye - center).Normalize(), 256); if(result.hit) { float dist = (result.hitPos - center).GetLength(); float curDist = (eye - center).GetLength(); dist -= 0.3f; // near clip plane if(curDist > dist){ float diff = curDist - dist; eye += (center - eye).Normalize() * diff; } } Vector3 front = center - eye; front = front.Normalize(); def.viewOrigin = eye; def.viewAxis[0] = -Vector3::Cross(up, front).Normalize(); def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize(); def.viewAxis[2] = front; def.fovX = 90.f * M_PI /180.f; def.fovY = atanf(tanf(def.fovX * .5f) * renderer->ScreenHeight() / renderer->ScreenWidth()) * 2.f; // update initial spectate pos // this is not used now, but if the local player is // is spectating, this is used when he/she's no // longer following followPos = def.viewOrigin; followVel = MakeVector3(0, 0, 0); }else if(player->GetTeamId() >= 2){ // spectator view (noclip view) Vector3 center = followPos; Vector3 front; Vector3 up = {0, 0, -1}; front.x = -cosf(followYaw) * cosf(followPitch); front.y = -sinf(followYaw) * cosf(followPitch); front.z = sinf(followPitch); def.viewOrigin = center; def.viewAxis[0] = -Vector3::Cross(up, front).Normalize(); def.viewAxis[1] = -Vector3::Cross(front, def.viewAxis[0]).Normalize(); def.viewAxis[2] = front; def.fovY = 60.f * M_PI /180.f; def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) * 2.f; // for 1st view, camera blur can be used def.denyCameraBlur = false; }else{ Vector3 front = player->GetFront(); Vector3 right = player->GetRight(); Vector3 up = player->GetUp(); float localFireVibration = GetLocalFireVibration(); localFireVibration *= localFireVibration; roll += (nextRandom() - nextRandom()) * 0.03f * localFireVibration; scale += nextRandom() * 0.04f * localFireVibration; vibPitch += localFireVibration * (1.f - localFireVibration) * 0.01f; vibYaw += sinf(localFireVibration * (float)M_PI * 2.f) * 0.001f; // sprint bob if(sprintState > 0.f){ float sp = SmoothStep(sprintState); vibYaw += sinf(player->GetWalkAnimationProgress() * M_PI * 2.f) * 0.01f * sp; roll -= sinf(player->GetWalkAnimationProgress() * M_PI * 2.f) * 0.005f * sp; float p = cosf(player->GetWalkAnimationProgress() * M_PI * 2.f); p = p * p; p *= p; p *= p; p *= p; vibPitch += p * 0.01f * sp; } scale /= GetAimDownZoomScale(); def.viewOrigin = player->GetEye(); def.viewAxis[0] = right; def.viewAxis[1] = up; def.viewAxis[2] = front; def.fovY = 60.f * M_PI /180.f; def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) * 2.f; // for 1st view, camera blur can be used def.denyCameraBlur = false; } // add vibration for both 1st/3rd view { // add grenade vibration float grenVib = grenadeVibration; if(grenVib > 0.f){ grenVib *= 10.f; if(grenVib > 1.f) grenVib = 1.f; roll += (nextRandom() - nextRandom()) * 0.2f * grenVib; vibPitch += (nextRandom() - nextRandom()) * 0.1f * grenVib; vibYaw += (nextRandom() - nextRandom()) * 0.1f * grenVib; scale -= (nextRandom()-nextRandom()) * 0.1f * grenVib; } } // add roll / scale { Vector3 right = def.viewAxis[0]; Vector3 up = def.viewAxis[1]; def.viewAxis[0] = right * cosf(roll) - up * sinf(roll); def.viewAxis[1] = up * cosf(roll) + right * sinf(roll); def.fovX *= scale; def.fovY *= scale; } { Vector3 u = def.viewAxis[1]; Vector3 v = def.viewAxis[2]; def.viewAxis[1] = u * cosf(vibPitch) - v * sinf(vibPitch); def.viewAxis[2] = v * cosf(vibPitch) + u * sinf(vibPitch); } { Vector3 u = def.viewAxis[0]; Vector3 v = def.viewAxis[2]; def.viewAxis[0] = u * cosf(vibYaw) - v * sinf(vibYaw); def.viewAxis[2] = v * cosf(vibYaw) + u * sinf(vibYaw); } def.zNear = 0.05f; def.zFar = 130.f; def.skipWorld = false; }else{ def.viewOrigin = MakeVector3(256, 256, 4); def.viewAxis[0] = MakeVector3(-1, 0, 0); def.viewAxis[1] = MakeVector3(0, 1, 0); def.viewAxis[2] = MakeVector3(0, 0, 1); def.fovY = 60.f * M_PI /180.f; def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) * 2.f; def.zNear = 0.05f; def.zFar = 130.f; def.skipWorld = false; } }else{ def.viewOrigin = MakeVector3(0, 0, 0); def.viewAxis[0] = MakeVector3(1, 0, 0); def.viewAxis[1] = MakeVector3(0, 0, -1); def.viewAxis[2] = MakeVector3(0, 0, 1); def.fovY = 60.f * M_PI /180.f; def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() / renderer->ScreenHeight()) * 2.f; def.zNear = 0.05f; def.zFar = 130.f; def.skipWorld = true; renderer->SetFogColor(MakeVector3(0,0,0)); } SPAssert(!isnan(def.viewOrigin.x)); SPAssert(!isnan(def.viewOrigin.y)); SPAssert(!isnan(def.viewOrigin.z)); return def; } std::string Client::GetWeaponPrefix(std::string fold, WeaponType type){ SPADES_MARK_FUNCTION_DEBUG(); std::string weapPrefix; switch(type){ case RIFLE_WEAPON: weapPrefix = fold + "/Weapons/Rifle"; break; case SMG_WEAPON: weapPrefix = fold + "/Weapons/SMG"; break; case SHOTGUN_WEAPON: weapPrefix = fold + "/Weapons/Shotgun"; break; } return weapPrefix; } void Client::AddGrenadeToScene(spades::client::Grenade *g) { SPADES_MARK_FUNCTION(); IModel *model; model = renderer->RegisterModel("Models/Weapons/Grenade/Grenade.kv6"); ModelRenderParam param; Matrix4 mat = Matrix4::Scale(0.03f); mat = Matrix4::Translate(g->GetPosition()) * mat; param.matrix = mat; renderer->RenderModel(model, param); } void Client::AddPlayerToScene(spades::client::Player *p) { SPADES_MARK_FUNCTION(); if(p->GetTeamId() >= 2){ // spectator, or dummy player return; } // debug if(false){ IImage *img = renderer->RegisterImage("Gfx/Ball.png"); renderer->SetColor(MakeVector4(1, 0, 0, 0)); renderer->AddLongSprite(img, lastSceneDef.viewOrigin + MakeVector3(0, 0, 1), p->GetOrigin(), 0.5f); } float distancePowered = (p->GetOrigin() - lastSceneDef.viewOrigin).GetPoweredLength(); if(distancePowered > 140.f * 140.f){ return; } if(!p->IsAlive()){ if(!cg_ragdoll){ ModelRenderParam param; param.matrix = Matrix4::Translate(p->GetOrigin()+ MakeVector3(0,0,1)); param.matrix = param.matrix * Matrix4::Scale(.1f); IntVector3 col = p->GetColor(); param.customColor = MakeVector3(col.x/255.f, col.y/255.f, col.z/255.f); IModel *model = renderer->RegisterModel("Models/Player/Dead.kv6"); renderer->RenderModel(model, param); } return; } std::string weapPrefix = GetWeaponPrefix("Models", p->GetWeapon()->GetWeaponType()); Matrix4 eyeMatrix = Matrix4::FromAxis(-p->GetRight(), p->GetFront(), -p->GetUp(), p->GetEye()); if(p == world->GetLocalPlayer() && !ShouldRenderInThirdPersonView()){ if(flashlightOn){ float brightness; brightness = time - flashlightOnTime; brightness = 1.f - expf(-brightness * 5.f); // add flash light DynamicLightParam light; light.origin = (eyeMatrix * MakeVector3(0, -0.05f, -0.1f)).GetXYZ(); light.color = MakeVector3(1, .7f, .5f) * 1.5f * brightness; light.radius = 40.f; light.type = DynamicLightTypeSpotlight; light.spotAngle = 30.f * M_PI / 180.f; light.spotAxis[0] = p->GetRight(); light.spotAxis[1] = p->GetUp(); light.spotAxis[2] = p->GetFront(); light.image = renderer->RegisterImage("Gfx/Spotlight.tga"); renderer->AddLight(light); light.color *= .3f; light.radius = 10.f; light.type = DynamicLightTypePoint; light.image = NULL; renderer->AddLight(light); // add glare renderer->SetColor(MakeVector4(1, .7f, .5f, 0) * brightness * .3f); renderer->AddSprite(renderer->RegisterImage("Gfx/Glare.tga"), (eyeMatrix * MakeVector3(0, 0.3f, -0.3f)).GetXYZ(), .8f, 0.f); } Vector3 leftHand, rightHand; leftHand = MakeVector3(0, 0, 0); rightHand = MakeVector3(0, 0, 0); // view weapon float sprint = SmoothStep(sprintState); float putdown = 1.f - toolRaiseState; putdown *= putdown; putdown = std::min(1.f, putdown * 1.5f); if(p->GetTool() == Player::ToolSpade){ WeaponInput inp = p->GetWeaponInput(); Matrix4 mat = Matrix4::Scale(0.033f); if(inp.primary) { float per = p->GetSpadeAnimationProgress(); per = 1.f - per; mat = Matrix4::Rotate(MakeVector3(1, 0, 0), per * 1.7f) * mat; mat = Matrix4::Translate(MakeVector3(0, per*0.3f, 0)) * mat; }else if(inp.secondary) { float per = p->GetDigAnimationProgress(); bool first = p->IsFirstDig(); float ang; const float readyFront = -0.8f; float front = readyFront; float side = 1.f; const float digAngle = .6f; const float readyAngle = 0.6f; if(per < .5f) { if(first) { // bringing to the position per *= 2.f; per *= per; ang = per * readyAngle; side = per; front = per * readyFront; }else{ // digged! ang = readyAngle; per = (.5f - per) / .5f; per *= per; per *= per; ang += per * digAngle; front += per * 2.f; } }else{ per = (per - .5f) / .5f; per = 1.f - (1.f-per)*(1.f-per); ang = readyAngle + per * digAngle; front += per * 2.f; } mat = Matrix4::Rotate(MakeVector3(1, 0, 0), ang) * mat; mat = Matrix4::Rotate(MakeVector3(0, 0, 1), front * .15f) * mat; side *= .3f; front *= .1f; mat = Matrix4::Translate(MakeVector3(side, front, front * .2f)) * mat; } if(sprint > 0.f || putdown > 0.f){ float per = std::max(sprint, putdown); mat = Matrix4::Rotate(MakeVector3(0, 1, 0), per * 1.3f) * mat; mat = Matrix4::Translate(MakeVector3(0.3f, -0.4f, -0.1f) * per) * mat; } mat = Matrix4::Translate(0.f, putdown * -.3f, 0.f) * mat; mat = Matrix4::Translate(-0.3f, .7f, 0.3f) * mat; mat = Matrix4::Translate(viewWeaponOffset) * mat; leftHand = (mat * MakeVector3(0.0f, 0.0f, 7.f)).GetXYZ(); rightHand = (mat * MakeVector3(0.0f, 0.0f, -2.f)).GetXYZ(); mat = eyeMatrix * mat; ModelRenderParam param; param.matrix = mat; param.depthHack = true; IModel *model = renderer->RegisterModel("Models/Weapons/Spade/Spade.kv6"); renderer->RenderModel(model, param); }else if(p->GetTool() == Player::ToolBlock){ if(p->IsReadyToUseTool()){ Matrix4 mat = Matrix4::Scale(0.033f); if(sprint > 0.f){ mat = Matrix4::Rotate(MakeVector3(0, 0, 1), sprint * -0.3f) * mat; mat = Matrix4::Translate(MakeVector3(0.1f, -0.4f, -0.05f) * sprint) * mat; } mat = Matrix4::Translate(-0.3f, .7f, 0.3f) * mat; mat = Matrix4::Translate(viewWeaponOffset) * mat; mat = Matrix4::Translate(putdown * -.1f, putdown * -.3f, putdown * .2f) * mat; leftHand = (mat * MakeVector3(5.0f, -1.0f, 4.f)).GetXYZ(); rightHand = (mat * MakeVector3(-5.5f, 3.0f, -5.f)).GetXYZ(); mat = eyeMatrix * mat; ModelRenderParam param; param.matrix = mat; param.depthHack = true; param.customColor = MakeVector3(p->GetBlockColor()) / 255.f; IModel *model = renderer->RegisterModel("Models/Weapons/Block/Block2.kv6"); renderer->RenderModel(model, param); } }else if(p->GetTool() == Player::ToolGrenade){ float tim = p->GetTimeToNextGrenade(); if(p->IsReadyToUseTool()){ WeaponInput inp = p->GetWeaponInput(); float bring = 0.f; float pin = 0.f; float side = 0.f; if(tim < 0.f) { bring = std::min(1.f, -tim * 5.f); bring = 1.f - bring; bring = 1.f - bring * bring; } if(inp.primary) { pin = p->GetGrenadeCookTime() * 8.f; if(pin > 2.f)pin = 2.f; if(pin > 1.f){ side += pin - 1.f; bring -= (pin - 1.f) * 2.f; } } Matrix4 mat = Matrix4::Scale(0.033f); if(sprint > 0.f){ mat = Matrix4::Rotate(MakeVector3(0, 0, 1), sprint * -0.3f) * mat; mat = Matrix4::Translate(MakeVector3(0.1f, -0.4f, -0.05f) * sprint) * mat; } mat = Matrix4::Translate(-0.3f - side * .8f, .8 - bring * .1f, 0.45f - bring * .15f) * mat; mat = Matrix4::Translate(putdown * -.1f, putdown * -.3f, putdown * .1f) * mat; mat = Matrix4::Translate(viewWeaponOffset) * mat; leftHand = (mat * MakeVector3(10.0f, -1.0f, 10.f)).GetXYZ(); rightHand = (mat * MakeVector3(-3.f, 1.0f, 5.f)).GetXYZ(); Vector3 leftHand2 = (mat * MakeVector3(2.f, 1.0f, -2.f)).GetXYZ(); Vector3 leftHand3 = (mat * MakeVector3(8.0f, -1.0f, 10.f)).GetXYZ(); if(pin < 1.f){ leftHand = Mix(leftHand, leftHand2, pin); }else{ leftHand = Mix(leftHand2, leftHand3, pin - 1.f); } mat = eyeMatrix * mat; ModelRenderParam param; param.matrix = mat; param.depthHack = true; IModel *model = renderer->RegisterModel("Models/Weapons/Grenade/Grenade.kv6"); renderer->RenderModel(model, param); }else{ // throwing float per = .5f - p->GetTimeToNextGrenade(); per *= 6.f; if(per > 1.f) per = 1.f; leftHand = MakeVector3(0.5f, 0.5f, 0.6f); float p2 = per - .6f; p2 = .9f - p2 * p2 * 2.5f; rightHand = MakeVector3(-0.2f, p2, -.9f + per * 1.8f); } }else if(p->GetTool() == Player::ToolWeapon){ Matrix4 mat = Matrix4::Scale(0.033f); float vib = GetLocalFireVibration(); float aDown = SmoothStep(aimDownState); float motion = 1.f - aDown * 0.4f; float weapUp = aDown; switch(p->GetWeapon()->GetWeaponType()){ case RIFLE_WEAPON: weapUp *= 0.05f; break; case SMG_WEAPON: weapUp *= 0.028f; break; case SHOTGUN_WEAPON: weapUp *= 0.05f; break; default: SPInvalidEnum("p->GetWeapon()->GetWeaponType()", p->GetWeapon()->GetWeaponType()); } if(sprint > 0.f){ mat = Matrix4::Rotate(MakeVector3(0, 0, 1), sprint * -1.3f) * mat; mat = Matrix4::Rotate(MakeVector3(0, 1, 0), sprint * 0.2f) * mat; mat = Matrix4::Translate(MakeVector3(0.2f, -0.2f, 0.05f) * sprint) * mat; } if(putdown > 0.f){ mat = Matrix4::Rotate(MakeVector3(0, 0, 1), putdown * -1.3f) * mat; mat = Matrix4::Rotate(MakeVector3(0, 1, 0), putdown * 0.2f) * mat; mat = Matrix4::Translate(MakeVector3(0.1f, -0.3f, 0.1f) * putdown) * mat; } mat = Matrix4::Translate(-0.13f * (1.f - aDown), .5f, 0.2f - weapUp) * mat; mat = Matrix4::Translate(viewWeaponOffset * motion) * mat; mat = Matrix4::Translate(sinf(vib*M_PI*2.f)*0.008f * motion, vib*(vib-1.f)*0.14f * motion, vib*(1.f-vib)*0.03f * motion) * mat; bool reloading = p->GetWeapon()->IsReloading(); float reload = p->GetWeapon()->GetReloadProgress(); switch(p->GetWeapon()->GetWeaponType()){ case SMG_WEAPON: { leftHand = (mat * MakeVector3(1.0f, 6.0f, 1.f)).GetXYZ(); rightHand = (mat * MakeVector3(0.f, -8.0f, 2.f)).GetXYZ(); Vector3 leftHand2 = (mat * MakeVector3(5.0f, -10.0f, 4.f)).GetXYZ(); Vector3 leftHand3 = (mat * MakeVector3(1.0f, 6.0f, -4.f)).GetXYZ(); Vector3 leftHand4 = (mat * MakeVector3(1.0f, 9.0f, -6.f)).GetXYZ(); ModelRenderParam param; param.depthHack = true; param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/WeaponNoMagazine.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } if(false){ // just debug IImage *img = renderer->RegisterImage("Gfx/Ball.png"); renderer->SetColor(MakeVector4(1, 0.3f, 0.1f, 0)); renderer->AddLongSprite(img, (param.matrix * MakeVector3(0, 10, -2)).GetXYZ(), (param.matrix * MakeVector3(0, 1000, -2)).GetXYZ(), 0.03f); } mat = mat * Matrix4::Translate(0.f, 3.f, 1.f); reload *= 2.5f; if(reloading) { if(reload < 0.7f){ float per = reload / .7f; mat = mat * Matrix4::Translate(0.f, 0.f, per * per * 50.f); leftHand = Mix(leftHand, leftHand2, SmoothStep(per)); }else if(reload < 1.4f){ float per = (1.4f - reload) / .7f; if(per < .3f) { per *= 4.f; per -= .4f; per = std::max(std::min(per, .3f), 0.f); } mat = mat * Matrix4::Translate(0.f, 0.f, per * per * 10.f); leftHand = (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(); }else if(reload < 1.9f){ float per = (reload - 1.4f) / .5f; leftHand = (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(); leftHand = Mix(leftHand, leftHand3, SmoothStep(per)); }else if(reload < 2.2f){ float per = (reload - 1.9f) / .3f; leftHand = Mix(leftHand3, leftHand4, SmoothStep(per)); }else { float per = (reload - 2.2f) / .3f; leftHand = Mix(leftHand4, leftHand, SmoothStep(per)); } } param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/Magazine.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } } break; case RIFLE_WEAPON: { leftHand = (mat * MakeVector3(1.0f, 6.0f, 1.f)).GetXYZ(); rightHand = (mat * MakeVector3(0.f, -8.0f, 2.f)).GetXYZ(); Vector3 leftHand2 = (mat * MakeVector3(5.0f, -10.0f, 4.f)).GetXYZ(); Vector3 rightHand3 = (mat * MakeVector3(-2.0f, -7.0f, -4.f)).GetXYZ(); Vector3 rightHand4 = (mat * MakeVector3(-3.0f, -4.0f, -6.f)).GetXYZ(); ModelRenderParam param; param.depthHack = true; param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/WeaponNoMagazine.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } mat = mat * Matrix4::Translate(0.f, 1.f, 1.f); reload *= 2.5f; if(reloading) { if(reload < 0.1f){ float per = reload / .1f; leftHand = Mix(leftHand, (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(), SmoothStep(per)); }else if(reload < 0.7f){ float per = (reload-.1f) / .6f; if(per < .2f) { per *= 4.f; per -= .4f; per = std::max(std::min(per, .2f), 0.f); } if(per > .5f){ per += per - .5f; } mat = mat * Matrix4::Translate(0.f, 0.f, per * per * 10.f); leftHand = (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(); if(per > .5f){ per = (per - .5f); leftHand = Mix(leftHand, leftHand2, SmoothStep(per)); } }else if(reload < 1.4f){ float per = (1.4f - reload) / .7f; if(per < .3f) { per *= 4.f; per -= .4f; per = std::max(std::min(per, .3f), 0.f); } mat = mat * Matrix4::Translate(0.f, 0.f, per * per * 10.f); leftHand = (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(); }else if(reload < 1.9f){ float per = (reload - 1.4f) / .5f; Vector3 orig = leftHand; leftHand = (mat * MakeVector3(0.f, 0.f, 4.f)).GetXYZ(); leftHand = Mix(leftHand, orig, SmoothStep(per)); rightHand = Mix(rightHand, rightHand3, SmoothStep(per)); }else if(reload < 2.2f){ float per = (reload - 1.9f) / .3f; rightHand = Mix(rightHand3, rightHand4, SmoothStep(per)); }else { float per = (reload - 2.2f) / .3f; rightHand = Mix(rightHand4, rightHand, SmoothStep(per)); } } param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/Magazine.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } } break; case SHOTGUN_WEAPON: { rightHand = (mat * MakeVector3(0.f, -8.0f, 2.f)).GetXYZ(); Vector3 leftHand2 = (mat * MakeVector3(5.0f, -10.0f, 4.f)).GetXYZ(); Vector3 leftHand3 = (mat * MakeVector3(1.0f, 1.0f, 2.f)).GetXYZ(); ModelRenderParam param; param.depthHack = true; param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/WeaponNoPump.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } //mat = mat * Matrix4::Translate(0.f, 0.f, 0.f); reload *= .5f; leftHand = (mat * MakeVector3(0.f, 4.f, 2.f)).GetXYZ(); if(reloading) { if(reload < 0.2f){ float per = reload / .2f; leftHand = Mix(leftHand, leftHand2, SmoothStep(per)); }else if(reload < 0.35f){ float per = (reload-.2f) / .15f; leftHand = Mix(leftHand2, leftHand3, SmoothStep(per)); }else if(reload < .5f){ float per = (reload-.35f) / .15f; leftHand = Mix(leftHand3, leftHand, SmoothStep(per)); } } float cockFade = 1.f; if(reloading) { if(reload < .25f || p->GetWeapon()->GetAmmo() < p->GetWeapon()->GetClipSize() - 1){ cockFade = 0.f; }else{ cockFade = (reload - .25f) * 10.f; if(cockFade > 1.f) cockFade = 1.f; } } if(cockFade > 0.f) { float cock = 0.f; float tim = p->GetWeapon()->TimeToNextFire(); if(tim < 0.f){ // might be right after reloading... if(p->GetWeapon()->GetAmmo() == p->GetWeapon()->GetClipSize() && reload > .5f && reload < 1.f) { tim = reload - .4f; if(tim < .05f) { cock = 0.f; }else if(tim < .12f){ cock = (tim - .05f) / .07f; }else if(tim < .26f) { cock = 1.f; }else if(tim < .36f){ cock = 1.f - (tim - .26f) / .1f; } } }if(tim < .2f) { cock = 0.f; }else if(tim < .3f) { cock = (tim - .2f) / .1f; }else if(tim < .42f) { cock = 1.f; }else if(tim < .52f) { cock = 1.f - (tim - .42f) / .1f; }else{ cock = 0.f; } cock *= cockFade; mat = mat * Matrix4::Translate(0.f, -cock * 1.5f, 0.f); leftHand = Mix(leftHand, (mat * MakeVector3(0.f, 4.f, 2.f)).GetXYZ(), cockFade); } param.matrix = eyeMatrix * mat; { std::string path = weapPrefix + "/Pump.kv6"; IModel *model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } } break; default: abort(); } } // view hands if(leftHand.GetPoweredLength() > 0.001f && rightHand.GetPoweredLength() > 0.001f){ ModelRenderParam param; param.depthHack = true; IModel *model = renderer->RegisterModel ("Models/Player/Arm.kv6"); IModel *model2 = renderer->RegisterModel ("Models/Player/UpperArm.kv6"); IntVector3 col = p->GetColor(); param.customColor = MakeVector3(col.x/255.f, col.y/255.f, col.z/255.f); const float armlen = 0.5f; Vector3 shoulders[] = {{0.4f, 0.0f, 0.25f}, {-0.4f, 0.0f, 0.25f}}; Vector3 hands[] = {leftHand, rightHand}; Vector3 benddirs[] = {0.5f, 0.2f, 0.f, -0.5f, 0.2f, 0.f}; for(int i = 0; i < 2; i++){ Vector3 shoulder = shoulders[i]; Vector3 hand = hands[i]; Vector3 benddir = benddirs[i]; float len2 = (hand - shoulder).GetPoweredLength(); // len2/4 + x^2 = armlen^2 float bendlen = sqrtf(std::max(armlen*armlen - len2*.25f, 0.f)); Vector3 bend = Vector3::Cross(benddir, hand-shoulder); bend = bend.Normalize(); if(bend.z < 0.f) bend.z = -bend.z; Vector3 elbow = (hand + shoulder) * .5f; elbow += bend * bendlen; { Vector3 axises[3]; axises[2] = (hand - elbow).Normalize(); axises[0] = MakeVector3(0, 0, 1); axises[1] = Vector3::Cross(axises[2], axises[0]).Normalize(); axises[0] = Vector3::Cross(axises[1], axises[2]).Normalize(); Matrix4 mat = Matrix4::Scale(.05f); mat = Matrix4::FromAxis(axises[0], axises[1], axises[2], elbow) * mat; mat = eyeMatrix * mat; param.matrix = mat; renderer->RenderModel(model, param); } { Vector3 axises[3]; axises[2] = (elbow - shoulder).Normalize(); axises[0] = MakeVector3(0, 0, 1); axises[1] = Vector3::Cross(axises[2], axises[0]).Normalize(); axises[0] = Vector3::Cross(axises[1], axises[2]).Normalize(); Matrix4 mat = Matrix4::Scale(.05f); mat = Matrix4::FromAxis(axises[0], axises[1], axises[2], shoulder) * mat; mat = eyeMatrix * mat; param.matrix = mat; renderer->RenderModel(model2, param); } } } // --- local view ends } else { ModelRenderParam param; IModel *model; Vector3 front = p->GetFront(); IntVector3 col = p->GetColor(); param.customColor = MakeVector3(col.x/255.f, col.y/255.f, col.z/255.f); float yaw = atan2(front.y, front.x) + M_PI * .5f; float pitch = -atan2(front.z, sqrt(front.x * front.x + front.y * front.y)); // lower axis Matrix4 lower = Matrix4::Translate(p->GetOrigin()); lower = lower * Matrix4::Rotate(MakeVector3(0,0,1), yaw); Matrix4 scaler = Matrix4::Scale(0.1f); scaler = scaler * Matrix4::Scale(-1,-1,1); PlayerInput inp = p->GetInput(); // lower Matrix4 torso, head, arms; if(inp.crouch){ Matrix4 leg1 = Matrix4::Translate(-0.25f, 0.2f, -0.1f); Matrix4 leg2 = Matrix4::Translate( 0.25f, 0.2f, -0.1f); leg1 = lower * leg1; leg2 = lower * leg2; model = renderer->RegisterModel ("Models/Player/LegCrouch.kv6"); param.matrix = leg1 * scaler; renderer->RenderModel(model, param); param.matrix = leg2 * scaler; renderer->RenderModel(model, param); torso = Matrix4::Translate(0.f,0.f,-0.55f); torso = lower * torso; model = renderer->RegisterModel ("Models/Player/TorsoCrouch.kv6"); param.matrix = torso * scaler; renderer->RenderModel(model, param); head = Matrix4::Translate(0.f,0.f,-0.0f); head = torso * head; arms = Matrix4::Translate(0.f,0.f,-0.0f); arms = torso * arms; }else{ Matrix4 leg1 = Matrix4::Translate(-0.25f, 0.f, -0.1f); Matrix4 leg2 = Matrix4::Translate( 0.25f, 0.f, -0.1f); float ang = sinf(p->GetWalkAnimationProgress() * M_PI * 2.f) * 0.6f; float walkVel = Vector3::Dot(p->GetVelocty(), p->GetFront2D()) * 2.f; ang *= walkVel; leg1 = leg1 * Matrix4::Rotate(MakeVector3(1,0,0), ang); leg2 = leg2 * Matrix4::Rotate(MakeVector3(1,0,0), -ang); leg1 = lower * leg1; leg2 = lower * leg2; model = renderer->RegisterModel ("Models/Player/Leg.kv6"); param.matrix = leg1 * scaler; renderer->RenderModel(model, param); param.matrix = leg2 * scaler; renderer->RenderModel(model, param); torso = Matrix4::Translate(0.f,0.f,-1.0f); torso = lower * torso; model = renderer->RegisterModel ("Models/Player/Torso.kv6"); param.matrix = torso * scaler; renderer->RenderModel(model, param); head = Matrix4::Translate(0.f,0.f,-0.0f); head = torso * head; arms = Matrix4::Translate(0.f,0.f,0.1f); arms = torso * arms; } arms = arms * Matrix4::Rotate(MakeVector3(1,0,0), pitch); model = renderer->RegisterModel ("Models/Player/Arms.kv6"); param.matrix = arms * scaler; renderer->RenderModel(model, param); head = head * Matrix4::Rotate(MakeVector3(1,0,0), pitch); model = renderer->RegisterModel ("Models/Player/Head.kv6"); param.matrix = head * scaler; renderer->RenderModel(model, param); // draw tool if(p->GetTool() == Player::ToolSpade){ WeaponInput inp = p->GetWeaponInput(); Matrix4 mat = Matrix4::Scale(0.05f); if(inp.primary) { float per = p->GetSpadeAnimationProgress(); per = 1.f - per; mat = Matrix4::Rotate(MakeVector3(1, 0, 0), -per * 0.7f) * mat; } mat = Matrix4::Translate(0.35f, -1.0f, 0.0f) * mat; mat = arms * mat; param.matrix = mat; model = renderer->RegisterModel("Models/Weapons/Spade/Spade.kv6"); renderer->RenderModel(model, param); }else if(p->GetTool() == Player::ToolBlock){ Matrix4 mat = Matrix4::Scale(0.05f); mat = Matrix4::Translate(0.35f, -1.0f, 0.0f) * mat; mat = arms * mat; param.matrix = mat; param.customColor = MakeVector3(p->GetBlockColor()) / 255.f; model = renderer->RegisterModel("Models/Weapons/Block/Block2.kv6"); renderer->RenderModel(model, param); }else if(p->GetTool() == Player::ToolGrenade){ Matrix4 mat = Matrix4::Scale(0.05f); mat = Matrix4::Translate(0.35f, -1.0f, 0.0f) * mat; mat = arms * mat; param.matrix = mat; model = renderer->RegisterModel("Models/Weapons/Grenade/Grenade.kv6"); renderer->RenderModel(model, param); }else if(p->GetTool() == Player::ToolWeapon){ Matrix4 mat = Matrix4::Scale(0.05f); mat = mat * Matrix4::Scale(-1, -1, 1); mat = Matrix4::Translate(0.35f, -1.0f, 0.0f) * mat; mat = arms * mat; ModelRenderParam param; param.matrix = mat; std::string path = weapPrefix + "/Weapon.kv6"; model = renderer->RegisterModel(path.c_str()); renderer->RenderModel(model, param); } // draw intel in ctf CTFGameMode *ctfMode = dynamic_cast(world->GetMode()); if(ctfMode){ int tId = p->GetTeamId(); if(tId < 3){ CTFGameMode::Team& team = ctfMode->GetTeam(p->GetTeamId()); if(team.hasIntel && team.carrier == p->GetId()){ IntVector3 col2 = world->GetTeam(1-p->GetTeamId()).color; param.customColor = MakeVector3(col2.x/255.f, col2.y/255.f, col2.z/255.f); Matrix4 mIntel = torso * Matrix4::Translate(0,0.6f,0.5f); model = renderer->RegisterModel ("Models/MapObjects/Intel.kv6"); param.matrix = mIntel * scaler; renderer->RenderModel(model, param); param.customColor = MakeVector3(col.x/255.f, col.y/255.f, col.z/255.f); } } } if(false){ // draw hitbox AddDebugObjectToScene(p->GetHitBoxes().torso); AddDebugObjectToScene(p->GetHitBoxes().head); AddDebugObjectToScene(p->GetHitBoxes().limbs[0]); AddDebugObjectToScene(p->GetHitBoxes().limbs[1]); AddDebugObjectToScene(p->GetHitBoxes().limbs[2]); } // third person player rendering, done } } void Client::AddDebugObjectToScene(const spades::OBB3 &obb, const Vector4& color) { const Matrix4& mat = obb.m; Vector3 v[2][2][2]; v[0][0][0] = (mat * MakeVector3(0,0,0)).GetXYZ(); v[0][0][1] = (mat * MakeVector3(0,0,1)).GetXYZ(); v[0][1][0] = (mat * MakeVector3(0,1,0)).GetXYZ(); v[0][1][1] = (mat * MakeVector3(0,1,1)).GetXYZ(); v[1][0][0] = (mat * MakeVector3(1,0,0)).GetXYZ(); v[1][0][1] = (mat * MakeVector3(1,0,1)).GetXYZ(); v[1][1][0] = (mat * MakeVector3(1,1,0)).GetXYZ(); v[1][1][1] = (mat * MakeVector3(1,1,1)).GetXYZ(); renderer->AddDebugLine(v[0][0][0], v[1][0][0], color); renderer->AddDebugLine(v[0][0][1], v[1][0][1], color); renderer->AddDebugLine(v[0][1][0], v[1][1][0], color); renderer->AddDebugLine(v[0][1][1], v[1][1][1], color); renderer->AddDebugLine(v[0][0][0], v[0][1][0], color); renderer->AddDebugLine(v[0][0][1], v[0][1][1], color); renderer->AddDebugLine(v[1][0][0], v[1][1][0], color); renderer->AddDebugLine(v[1][0][1], v[1][1][1], color); renderer->AddDebugLine(v[0][0][0], v[0][0][1], color); renderer->AddDebugLine(v[0][1][0], v[0][1][1], color); renderer->AddDebugLine(v[1][0][0], v[1][0][1], color); renderer->AddDebugLine(v[1][1][0], v[1][1][1], color); } void Client::DrawCTFObjects() { SPADES_MARK_FUNCTION(); CTFGameMode *mode = dynamic_cast(world->GetMode()); int tId; IModel *base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6"); IModel *intel = renderer->RegisterModel("Models/MapObjects/Intel.kv6"); for(tId = 0; tId < 2; tId++){ CTFGameMode::Team& team = mode->GetTeam(tId); IntVector3 col = world->GetTeam(tId).color; Vector3 color = { col.x / 255.f, col.y / 255.f, col.z / 255.f }; ModelRenderParam param; param.customColor = color; // draw base param.matrix = Matrix4::Translate(team.basePos); param.matrix = param.matrix * Matrix4::Scale(.3f); renderer->RenderModel(base, param); // draw flag if(!mode->GetTeam(1-tId).hasIntel){ param.matrix = Matrix4::Translate(team.flagPos); param.matrix = param.matrix * Matrix4::Scale(.1f); renderer->RenderModel(intel, param); } } } void Client::DrawTCObjects() { SPADES_MARK_FUNCTION(); TCGameMode *mode = dynamic_cast(world->GetMode()); int tId; IModel *base = renderer->RegisterModel("Models/MapObjects/CheckPoint.kv6"); int cnt = mode->GetNumTerritories(); for(tId = 0; tId < cnt; tId++){ TCGameMode::Territory *t = mode->GetTerritory(tId); IntVector3 col; if(t->ownerTeamId == 2){ col = IntVector3::Make(255, 255, 255); }else{ col = world->GetTeam(t->ownerTeamId).color; } Vector3 color = { col.x / 255.f, col.y / 255.f, col.z / 255.f }; ModelRenderParam param; param.customColor = color; // draw base param.matrix = Matrix4::Translate(t->pos); param.matrix = param.matrix * Matrix4::Scale(.3f); renderer->RenderModel(base, param); } } void Client::RemoveAllCorpses(){ SPADES_MARK_FUNCTION(); std::list::iterator it; for(it = corpses.begin(); it != corpses.end(); it++) delete *it; corpses.clear(); lastMyCorpse = NULL; } void Client::RemoveAllLocalEntities(){ SPADES_MARK_FUNCTION(); std::list::iterator it; for(it = localEntities.begin(); it != localEntities.end(); it++) delete *it; localEntities.clear(); } void Client::RemoveInvisibleCorpses(){ SPADES_MARK_FUNCTION(); std::vector::iterator> its; std::list::iterator it; int cnt = (int)corpses.size() - corpseSoftLimit; for(it = corpses.begin(); it != corpses.end(); it++){ if(cnt <= 0) break; Corpse *c = *it; if(!c->IsVisibleFrom(lastSceneDef.viewOrigin)){ if(c == lastMyCorpse) lastMyCorpse = NULL; delete c; its.push_back(it); } cnt--; } for(size_t i = 0; i < its.size(); i++) corpses.erase(its[i]); } void Client::DrawScene(){ SPADES_MARK_FUNCTION(); renderer->StartScene(lastSceneDef); if(world){ Player *p = world->GetLocalPlayer(); for(int i = 0; i < world->GetNumPlayerSlots(); i++) if(world->GetPlayer(i)) AddPlayerToScene(world->GetPlayer(i)); std::vector nades = world->GetAllGrenades(); for(size_t i = 0; i < nades.size(); i++){ AddGrenadeToScene(nades[i]); } { std::list::iterator it; for(it = corpses.begin(); it != corpses.end(); it++){ Vector3 center = (*it)->GetCenter(); if((center - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f) continue; (*it)->AddToScene(); } } if(dynamic_cast(world->GetMode())){ DrawCTFObjects(); } if(dynamic_cast(world->GetMode())){ DrawTCObjects(); } { std::list::iterator it; for(it = localEntities.begin(); it != localEntities.end(); it++){ (*it)->Render3D(); } } // draw block cursor // FIXME: don't use debug line if(p){ if(p->IsBlockCursorActive()){ std::vector blocks; if(p->IsBlockCursorDragging()){ blocks = world->CubeLine(p->GetBlockCursorDragPos(), p->GetBlockCursorPos(), 256); }else{ blocks.push_back(p->GetBlockCursorPos()); } Vector4 color = {1,1,1,1}; if(blocks.size() > (int)p->GetNumBlocks()) color = MakeVector4(1,0,0,1); for(size_t i = 0; i < blocks.size(); i++){ IntVector3& v = blocks[i]; renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z), MakeVector3(v.x+1, v.y, v.z), color); renderer->AddDebugLine(MakeVector3(v.x, v.y+1, v.z), MakeVector3(v.x+1, v.y+1, v.z), color); renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z+1), MakeVector3(v.x+1, v.y, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x, v.y+1, v.z+1), MakeVector3(v.x+1, v.y+1, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z), MakeVector3(v.x+1, v.y, v.z), color); renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z), MakeVector3(v.x, v.y+1, v.z), color); renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z+1), MakeVector3(v.x, v.y+1, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x+1, v.y, v.z), MakeVector3(v.x+1, v.y+1, v.z), color); renderer->AddDebugLine(MakeVector3(v.x+1, v.y, v.z+1), MakeVector3(v.x+1, v.y+1, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x, v.y, v.z), MakeVector3(v.x, v.y, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x, v.y+1, v.z), MakeVector3(v.x, v.y+1, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x+1, v.y, v.z), MakeVector3(v.x+1, v.y, v.z+1), color); renderer->AddDebugLine(MakeVector3(v.x+1, v.y+1, v.z), MakeVector3(v.x+1, v.y+1, v.z+1), color); } } } } for(size_t i = 0; i < flashDlights.size(); i++) renderer->AddLight(flashDlights[i]); flashDlightsOld.clear(); flashDlightsOld.swap(flashDlights); // draw player hottrack // FIXME: don't use debug line { Player *hottracked = HotTrackedPlayer(); if(hottracked){ IntVector3 col = world->GetTeam(hottracked->GetTeamId()).color; Vector4 color; color.x = col.x / 255.f; color.y = col.y / 255.f; color.z = col.z / 255.f; color.w = 1.f; Player::HitBoxes hb = hottracked->GetHitBoxes(); AddDebugObjectToScene(hb.head, color); AddDebugObjectToScene(hb.torso, color); AddDebugObjectToScene(hb.limbs[0], color); AddDebugObjectToScene(hb.limbs[1], color); AddDebugObjectToScene(hb.limbs[2], color); } } renderer->EndScene(); } Vector3 Client::Project(spades::Vector3 v){ v -= lastSceneDef.viewOrigin; // transform to NDC Vector3 v2; v2.x = Vector3::Dot(v, lastSceneDef.viewAxis[0]); v2.y = Vector3::Dot(v, lastSceneDef.viewAxis[1]); v2.z = Vector3::Dot(v, lastSceneDef.viewAxis[2]); float tanX = tanf(lastSceneDef.fovX * .5f); float tanY = tanf(lastSceneDef.fovY * .5f); v2.x /= tanX * v2.z; v2.y /= tanY * v2.z; // transform to IRenderer 2D coord v2.x = (v2.x + 1.f) / 2.f * renderer->ScreenWidth(); v2.y = (-v2.y + 1.f) / 2.f * renderer->ScreenHeight(); return v2; } void Client::Draw2D(){ SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); IFont *font; if(GetWorld()){ float wTime = world->GetTime(); { std::list::iterator it; for(it = localEntities.begin(); it != localEntities.end(); it++){ (*it)->Render2D(); } } Player *p = GetWorld()->GetLocalPlayer(); if(p){ if(wTime < lastHurtTime + .2f){ float per = (wTime - lastHurtTime) / .2f; Vector3 color = {1.f, per, per}; renderer->MultiplyScreenColor(color); } Player *hottracked = HotTrackedPlayer(); if(hottracked){ Vector3 posxyz = Project(hottracked->GetEye()); Vector2 pos = {posxyz.x, posxyz.y}; float dist = (hottracked->GetEye() - p->GetEye()).GetLength(); int idist = (int)floorf(dist + .5f); char buf[64]; sprintf(buf, "%s [%d%s]", hottracked->GetName().c_str(), idist, (idist == 1) ? "block":"blocks"); font = textFont; Vector2 size = font->Measure(buf); pos.x -= size.x * .5f; pos.y -= size.y; font->Draw(buf, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(buf, pos, 1.f, MakeVector4(1,1,1,1)); } tcView->Draw(); if(p->IsAlive() && p->GetTeamId() < 2){ // draw damage ring hurtRingView->Draw(); // draw sight IImage *sight = renderer->RegisterImage("Gfx/Sight.tga"); renderer->SetColor(MakeVector4(1,1,1,1)); renderer->DrawImage(sight, MakeVector2((scrWidth - sight->GetWidth()) * .5f, (scrHeight - sight->GetHeight()) * .5f)); // draw ammo Weapon *weap = p->GetWeapon(); IImage *ammoIcon; float iconWidth, iconHeight; float spacing = 2.f; int stockNum; int warnLevel; if(p->IsToolWeapon()){ switch(weap->GetWeaponType()){ case RIFLE_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/7.62mm.tga"); iconWidth = 6.f; iconHeight = iconWidth * 4.f; break; case SMG_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/9mm.tga"); iconWidth = 4.f; iconHeight = iconWidth * 4.f; break; case SHOTGUN_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/12gauge.tga"); iconWidth = 30.f; iconHeight = iconWidth / 4.f; spacing = -6.f; break; default: SPInvalidEnum("weap->GetWeaponType()", weap->GetWeaponType()); } int clipSize = weap->GetClipSize(); int clip = weap->GetAmmo(); clipSize = std::max(clipSize, clip); for(int i = 0; i < clipSize; i++){ float x = scrWidth - 16.f - (float)(i+1) * (iconWidth + spacing); float y = scrHeight - 16.f - iconHeight; if(clip >= i + 1){ renderer->SetColor(MakeVector4(1,1,1,1)); }else{ renderer->SetColor(MakeVector4(0.4,0.4,0.4,1)); } renderer->DrawImage(ammoIcon, AABB2(x,y,iconWidth,iconHeight)); } stockNum = weap->GetStock(); warnLevel = weap->GetMaxStock() / 3; }else{ iconHeight = 0.f; warnLevel = 0; switch(p->GetTool()){ case Player::ToolSpade: case Player::ToolBlock: stockNum = p->GetNumBlocks(); break; case Player::ToolGrenade: stockNum = p->GetNumGrenades(); break; default: SPInvalidEnum("p->GetTool()", p->GetTool()); } } Vector4 numberColor = {1, 1, 1, 1}; if(stockNum == 0){ numberColor.y = 0.3f; numberColor.z = 0.3f; }else if(stockNum <= warnLevel){ numberColor.z = 0.3f; } char buf[64]; sprintf(buf, "%d", stockNum); font = designFont; std::string stockStr = buf; Vector2 size = font->Measure(stockStr); Vector2 pos = MakeVector2(scrWidth - 16.f, scrHeight - 16.f - iconHeight); pos -= size; font->Draw(stockStr, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(stockStr, pos, 1.f, numberColor); // draw "press ... to reload" { std::string msg = ""; switch(p->GetTool()){ case Player::ToolBlock: if(p->GetNumBlocks() == 0){ msg = "Out of Block"; } break; case Player::ToolGrenade: if(p->GetNumGrenades() == 0){ msg = "Out of Grenade"; } break; case Player::ToolWeapon: { Weapon *weap = p->GetWeapon(); if(weap->IsReloading() || p->IsAwaitingReloadCompletion()){ msg = "Reloading"; }else if(weap->GetAmmo() == 0 && weap->GetStock() == 0){ msg = "Out of Ammo"; }else if(weap->GetStock() > 0 && weap->GetAmmo() < weap->GetClipSize() / 4){ msg = "Press [" + (std::string)cg_keyReloadWeapon + "] to Reload"; } } break; default:; // no message } if(!msg.empty()){ font = textFont; Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight * 2.f / 3.f); font->Draw(msg, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(msg, pos, 1.f, MakeVector4(1,1,1,1)); } } if(p->GetTool() == Player::ToolBlock) { paletteView->Draw(); } // draw map mapView->Draw(); // --- end of "player is alive" drawing }else { // draw respawn tme if(!p->IsAlive()){ std::string msg; float secs = p->GetRespawnTime() - world->GetTime(); char buf[64]; if(secs > 0.f) sprintf(buf, "You will respawn in: %d", (int)ceilf(secs)); else strcpy(buf, "Waiting for respawn"); msg = buf; if(!msg.empty()){ font = textFont; Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight / 3.f); font->Draw(msg, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(msg, pos, 1.f, MakeVector4(1,1,1,1)); } } // draw map mapView->Draw(); } // draw health if(p->GetTeamId() < 2){ char buf[64]; sprintf(buf, "%d", p->GetHealth()); Vector4 numberColor = {1, 1, 1, 1}; if(p->GetHealth() == 0){ numberColor.y = 0.3f; numberColor.z = 0.3f; }else if(p->GetHealth() <= 50){ numberColor.z = 0.3f; } font = designFont; std::string stockStr = buf; Vector2 size = font->Measure(stockStr); Vector2 pos = MakeVector2(16.f, scrHeight - 16.f); pos.y -= size.y; font->Draw(stockStr, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(stockStr, pos, 1.f, numberColor); } if(IsFollowing()){ if(followingPlayerId == p->GetId()){ // just spectating }else{ font = textFont; std::string msg; msg = "Following " + world->GetPlayerPersistent(followingPlayerId).name; Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2(scrWidth - 8.f, 256.f + 32.f); pos.x -= size.x; font->Draw(msg, pos + MakeVector2(1,1), 1.f, MakeVector4(0,0,0,0.5)); font->Draw(msg, pos, 1.f, MakeVector4(1, 1, 1, 1)); } } chatWindow->Draw(); killfeedWindow->Draw(); if(p->IsAlive()) { // large map view should come in front largeMapView->Draw(); } if(scoreboardVisible) scoreboard->Draw(); // --- end "player is there" render }else{ // world exists, but no local player: not joined scoreboard->Draw(); } if(IsLimboViewActive()) limbo->Draw(); // FIXME: when to draw chat editor? if(chatEditing){ Vector2 pos; pos.x = 8.f; pos.y = scrHeight - 24.f; std::string str; if(chatGlobal) str = "Global Chat: "; else str = "Team Chat: "; str += chatText; str += "_"; textFont->Draw(str, pos+MakeVector2(1, 1), 1.f, MakeVector4(0,0,0,0.5)); textFont->Draw(str, pos, 1.f, MakeVector4(1,1,1,1)); } }else{ // no world; loading? // background IImage *img; float bgSize = std::max(scrWidth, scrHeight); renderer->SetColor(MakeVector4(1, 1, 1, .1)); img = renderer->RegisterImage("Gfx/CircleGradient.png"); renderer->DrawImage(img, AABB2((scrWidth - bgSize) * .5f, (scrHeight - bgSize) * .5f, bgSize, bgSize)); renderer->SetColor(MakeVector4(1, 1, 1, .1)); img = renderer->RegisterImage("Gfx/White.tga"); renderer->DrawImage(img, AABB2(0,0,scrWidth,scrHeight)); // loading window renderer->SetColor(MakeVector4(1, 1, 1, 1)); float wndX, wndY; img = renderer->RegisterImage("Gfx/LoadingWindow.png"); wndX = (scrWidth - img->GetWidth()) * .5f; wndY = (scrHeight - img->GetHeight()) * .5f; wndX = floorf(wndX); wndY = floorf(wndY); renderer->DrawImage(img, MakeVector2(wndX, wndY)); renderer->SetColor(MakeVector4(1, 1, 1, .2)); img = renderer->RegisterImage("Gfx/LoadingWindowGlow.png"); renderer->DrawImage(img, AABB2((scrWidth - 512.f) * .5f, (scrHeight - 256.f) * .5f, 512.f, 256.f)); renderer->SetColor(MakeVector4(1, 1, 1, 1)); img = renderer->RegisterImage("Gfx/LoadingStripe.png"); float scrX = time * 32.f; scrX = fmodf(scrX, 16.f); renderer->DrawImage(img, MakeVector2(wndX+11, wndY+37), AABB2(-scrX, 0, 218,14)); std::string msg = net->GetStatusString(); font = textFont; font->Draw(msg, MakeVector2(wndX + 8.f, wndY + 8.f), 1.f, MakeVector4(0,0,0,1.f)); } centerMessageView->Draw(); } #pragma mark - Chat Messages void Client::PlayerSentChatMessage(spades::client::Player *p, bool global, const std::string &msg){ std::string s; if(global) s = "[Global] "; s += ChatWindow::TeamColorMessage(p->GetName(), p->GetTeamId()); s += ": "; s += msg; chatWindow->AddMessage(s); if(global) NetLog("[Global] %s (%s): %s", p->GetName().c_str(), world->GetTeam(p->GetTeamId()).name.c_str(), msg.c_str()); else NetLog("[Team] %s (%s): %s", p->GetName().c_str(), world->GetTeam(p->GetTeamId()).name.c_str(), msg.c_str()); if(!IsMuted()) { IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/Chat.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } } void Client::ServerSentMessage(const std::string &msg) { chatWindow->AddMessage(msg); NetLog("%s", msg.c_str()); if(msg.find(playerName) != std::string::npos){ printf("Mention: %s\n", msg.c_str()); } } void Client::ActivateChatTextEditor(bool global) { chatEditing = true; chatGlobal = global; playerInput = PlayerInput(); weapInput = WeaponInput(); keypadInput = KeypadInput(); } void Client::CloseChatTextEditor() { chatEditing = false; chatText.clear(); } void Client::ChatCharEvent(const std::string& ch){ SPAssert(chatEditing); chatText += ch; } void Client::ChatKeyEvent(const std::string &key) { SPAssert(chatEditing); if(key == "BackSpace"){ if(!chatText.empty()) chatText = chatText.substr(0, chatText.size() - 1); }else if(key == "Enter"){ if(!chatText.empty()){ net->SendChat(chatText, chatGlobal); } CloseChatTextEditor(); }else if(key == "Left"){ // TODO: cursor }else if(key == "Right"){ // TODO: cursor }else if(key == "Escape"){ CloseChatTextEditor(); } } #pragma mark - Follow / Spectate void Client::FollowNextPlayer() { int myTeam = 2; if(world->GetLocalPlayer()){ myTeam = world->GetLocalPlayer()->GetTeamId(); } int nextId = followingPlayerId; do{ nextId++; if(nextId >= world->GetNumPlayerSlots()) nextId = 0; Player *p = world->GetPlayer(nextId); if(p == NULL) continue; if(myTeam < 2 && p->GetTeamId() != myTeam) continue; if(p->GetFront().GetPoweredLength() < .01f) continue; break; }while(nextId != followingPlayerId); followingPlayerId = nextId; } bool Client::IsFollowing() { if(!world) return false; if(!world->GetLocalPlayer()) return false; Player *p = world->GetLocalPlayer(); if(p->GetTeamId() >= 2) return true; if(p->IsAlive()) return false; else return true; } #pragma mark - Effects Player *Client::HotTrackedPlayer(){ if(!world) return NULL; Player *p = world->GetLocalPlayer(); if(!p || !p->IsAlive()) return NULL; if(ShouldRenderInThirdPersonView()) return NULL; Vector3 origin = p->GetEye(); Vector3 dir = p->GetFront(); World::WeaponRayCastResult result = world->WeaponRayCast(origin, dir, p); if(result.hit == false || result.player == NULL) return NULL; // don't hot track enemies (non-spectator only) if(result.player->GetTeamId() != p->GetTeamId() && p->GetTeamId() < 2) return NULL; return result.player; } bool Client::IsMuted() { // prevent to play loud sound at connection return time < worldSetTime + .05f; } void Client::Bleed(spades::Vector3 v){ SPADES_MARK_FUNCTION(); if(!cg_blood) return; // distance cull if((v - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f) return; //IImage *img = renderer->RegisterImage("Textures/SoftBall.tga"); IImage *img = renderer->RegisterImage("Gfx/White.tga"); Vector4 color = {0.5f, 0.02f, 0.04f, 1.f}; for(int i = 0; i < 10; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(v, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * 10.f, 1.f, 0.7f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(0.1f + GetRandom()*GetRandom()*0.2f); ent->SetLifeTime(3.f, 0.f, 1.f); localEntities.push_back(ent); } color = MakeVector4(.7f, .35f, .37f, .6f); for(int i = 0; i < 2; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 100.f); ent->SetTrajectory(v, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * .7f, .8f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(.5f + GetRandom()*GetRandom()*0.2f, 2.f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(.20f + GetRandom() * .2f, 0.06f, .20f); localEntities.push_back(ent); } } void Client::EmitBlockFragments(Vector3 origin, IntVector3 c){ SPADES_MARK_FUNCTION(); // distance cull float distPowered = (origin - lastSceneDef.viewOrigin).GetPoweredLength(); if(distPowered > 150.f * 150.f) return; IImage *img = renderer->RegisterImage("Gfx/White.tga"); Vector4 color = {c.x / 255.f, c.y / 255.f, c.z / 255.f, 1.f}; for(int i = 0; i < 7; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(origin, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * 7.f, 1.f, .9f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(0.2f + GetRandom()*GetRandom()*0.1f); ent->SetLifeTime(2.f, 0.f, 1.f); if(distPowered < 16.f * 16.f) ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); localEntities.push_back(ent); } if(distPowered < 32.f * 32.f){ for(int i = 0; i < 16; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(origin, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * 12.f, 1.f, .9f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(0.1f + GetRandom()*GetRandom()*0.14f); ent->SetLifeTime(2.f, 0.f, 1.f); if(distPowered < 16.f * 16.f) ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); localEntities.push_back(ent); } } color += (MakeVector4(1, 1, 1, 1) - color) * .2f; color.w *= .2f; for(int i = 0; i < 2; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 100.f); ent->SetTrajectory(origin, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * .7f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(.6f + GetRandom()*GetRandom()*0.2f, 0.8f); ent->SetLifeTime(.3f + GetRandom() * .3f, 0.06f, .4f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); localEntities.push_back(ent); } } void Client::EmitBlockDestroyFragments(IntVector3 blk, IntVector3 c){ SPADES_MARK_FUNCTION(); Vector3 origin = {blk.x + .5f, blk.y + .5f, blk.z + .5f}; // distance cull if((origin - lastSceneDef.viewOrigin).GetPoweredLength() > 150.f * 150.f) return; IImage *img = renderer->RegisterImage("Gfx/White.tga"); Vector4 color = {c.x / 255.f, c.y / 255.f, c.z / 255.f, 1.f}; for(int i = 0; i < 8; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(origin, MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()) * 7.f, 1.f, 1.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(0.3f + GetRandom()*GetRandom()*0.2f); ent->SetLifeTime(2.f, 0.f, 1.f); ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); localEntities.push_back(ent); } } void Client::MuzzleFire(spades::Vector3 origin, spades::Vector3 dir, bool local) { DynamicLightParam l; l.origin = origin; l.radius = 5.f; l.type = DynamicLightTypePoint; l.color = MakeVector3(3.f, 1.6f, 0.5f); flashDlights.push_back(l); Vector4 color; Vector3 velBias = {0, 0, -0.5f}; color = MakeVector4( .8f, .8f, .8f, .3f); // rapid smoke for(int i = 0; i < 2; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 120.f); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom())+velBias*.5f) * 0.3f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(.2f, 7.f, 0.0000005f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(0.2f + GetRandom()*0.1f, 0.f, .30f); localEntities.push_back(ent); } } void Client::GrenadeExplosion(spades::Vector3 origin){ float dist = (origin - lastSceneDef.viewOrigin).GetLength(); if(dist > 170.f) return; grenadeVibration += 2.f / (dist + 5.f); if(grenadeVibration > 1.f) grenadeVibration = 1.f; DynamicLightParam l; l.origin = origin; l.radius = 16.f; l.type = DynamicLightTypePoint; l.color = MakeVector3(3.f, 1.6f, 0.5f); flashDlights.push_back(l); Vector3 velBias = {0,0,0}; if(!map->ClipBox(origin.x, origin.y, origin.z)){ if(map->ClipBox(origin.x + 1.f, origin.y, origin.z)){ velBias.x -= 1.f; } if(map->ClipBox(origin.x - 1.f, origin.y, origin.z)){ velBias.x += 1.f; } if(map->ClipBox(origin.x, origin.y + 1.f, origin.z)){ velBias.y -= 1.f; } if(map->ClipBox(origin.x, origin.y - 1.f, origin.z)){ velBias.y += 1.f; } if(map->ClipBox(origin.x, origin.y , origin.z + 1.f)){ velBias.z -= 1.f; } if(map->ClipBox(origin.x, origin.y , origin.z - 1.f)){ velBias.z += 1.f; } } Vector4 color; color = MakeVector4( .8f, .8f, .8f, .6f); // rapid smoke for(int i = 0; i < 4; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 60.f); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom())+velBias*.5f) * 4.f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(1.f + GetRandom()*GetRandom()*0.4f, 10.f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(.1f + GetRandom()*0.02f, 0.f, .10f); localEntities.push_back(ent); } // slow smoke color.w = .25f; for(int i = 0; i < 8; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 20.f); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), (GetRandom()-GetRandom()) * .2f)) * 2.f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(1.4f + GetRandom()*GetRandom()*0.8f, 0.2f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(6.f + GetRandom() * 5.f, 0.1f, 8.f); localEntities.push_back(ent); } // fragments IImage *img = renderer->RegisterImage("Gfx/White.tga"); color = MakeVector4(0.01, 0.03, 0, 1.f); for(int i = 0; i < 42; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); Vector3 dir = MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom()); dir += velBias * .5f; float radius = 0.1f + GetRandom()*GetRandom()*0.2f; ent->SetTrajectory(origin + dir * .2f, dir * 20.f, .1f + radius * 3.f, 1.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(radius); ent->SetLifeTime(3.5f + GetRandom() * 2.f, 0.f, 1.f); ent->SetBlockHitAction(ParticleSpriteEntity::BounceWeak); localEntities.push_back(ent); } // fire smoke color= MakeVector4(1.f, .6f, .2f, 1.f); for(int i = 0; i < 4; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 60.f); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), GetRandom()-GetRandom())+velBias) * 12.f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(1.f + GetRandom()*GetRandom()*0.4f, 6.f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(.08f + GetRandom()*0.03f, 0.f, .10f); ent->SetAdditive(true); localEntities.push_back(ent); } } void Client::GrenadeExplosionUnderwater(spades::Vector3 origin){ float dist = (origin - lastSceneDef.viewOrigin).GetLength(); if(dist > 170.f) return; grenadeVibration += 1.5f / (dist + 5.f); if(grenadeVibration > 1.f) grenadeVibration = 1.f; Vector3 velBias = {0,0,0}; Vector4 color; color = MakeVector4( .95f, .95f, .95f, .6f); // water1 IImage *img = renderer->RegisterImage("Textures/WaterExpl.png"); for(int i = 0; i < 7; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), -GetRandom()*7.f)) * 2.5f, .3f, .6f); ent->SetRotation(0.f); ent->SetRadius(1.5f + GetRandom()*GetRandom()*0.4f, 1.3f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(3.f + GetRandom()*0.3f, 0.f, .60f); localEntities.push_back(ent); } // water2 img = renderer->RegisterImage("Textures/Fluid.png"); color.w = .9f; for(int i = 0; i < 16; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), -GetRandom()*10.f)) * 3.5f, 1.f, 1.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(0.9f + GetRandom()*GetRandom()*0.4f, 0.7f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(3.f + GetRandom()*0.3f, .7f, .60f); localEntities.push_back(ent); } // slow smoke color.w = .4f; for(int i = 0; i < 8; i++){ ParticleSpriteEntity *ent = new SmokeSpriteEntity(this, color, 20.f); ent->SetTrajectory(origin, (MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), (GetRandom()-GetRandom()) * .2f)) * 2.f, 1.f, 0.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(1.4f + GetRandom()*GetRandom()*0.8f, 0.2f); ent->SetBlockHitAction(ParticleSpriteEntity::Ignore); ent->SetLifeTime(6.f + GetRandom() * 5.f, 0.1f, 8.f); localEntities.push_back(ent); } // fragments img = renderer->RegisterImage("Gfx/White.tga"); color = MakeVector4(1,1,1, 0.7f); for(int i = 0; i < 42; i++){ ParticleSpriteEntity *ent = new ParticleSpriteEntity(this, img, color); Vector3 dir = MakeVector3(GetRandom()-GetRandom(), GetRandom()-GetRandom(), -GetRandom() * 3.f); dir += velBias * .5f; float radius = 0.1f + GetRandom()*GetRandom()*0.2f; ent->SetTrajectory(origin + dir * .2f + MakeVector3(0, 0, -1.2f), dir * 13.f, .1f + radius * 3.f, 1.f); ent->SetRotation(GetRandom() * (float)M_PI * 2.f); ent->SetRadius(radius); ent->SetLifeTime(3.5f + GetRandom() * 2.f, 0.f, 1.f); ent->SetBlockHitAction(ParticleSpriteEntity::Delete); localEntities.push_back(ent); } // TODO: wave? } #pragma mark - Server Packet Handlers void Client::LocalPlayerCreated(){ followPos = world->GetLocalPlayer()->GetEye(); weapInput = WeaponInput(); playerInput = PlayerInput(); keypadInput = KeypadInput(); selectedTool = world->GetLocalPlayer()->GetTool(); toolRaiseState = .0f; } void Client::JoinedGame() { // note: local player doesn't exist yet now // tune for spectate mode followingPlayerId = world->GetLocalPlayerIndex(); followPos = MakeVector3(256, 256, 30); followVel = MakeVector3(0, 0, 0); } void Client::PlayerCreatedBlock(spades::client::Player *p){ SPADES_MARK_FUNCTION(); if(!IsMuted()) { IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Block/Build.wav"); audioDevice->Play(c, p->GetEye() + p->GetFront(), AudioParam()); } } void Client::TeamCapturedTerritory(int teamId, int terId) { TCGameMode::Territory *ter = static_cast(world->GetMode())->GetTerritory(terId); int old = ter->ownerTeamId; std::string msg; msg = chatWindow->TeamColorMessage(world->GetTeam(teamId).name, teamId); msg += " captured "; if(old < 2){ msg += chatWindow->TeamColorMessage(world->GetTeam(old).name, old); msg += "'s territory"; }else{ msg += "an neutral territory"; } chatWindow->AddMessage(msg); msg = world->GetTeam(teamId).name; msg += " captured "; if(old < 2){ msg += world->GetTeam(old).name; msg += "'s Territory"; }else{ msg += "an Neutral Territory"; } NetLog("%s", msg.c_str()); centerMessageView->AddMessage(msg); if(world->GetLocalPlayer() && !IsMuted()){ if(teamId == world->GetLocalPlayer()->GetTeamId()){ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/TC/YourTeamCaptured.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else{ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/TC/EnemyCaptured.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } } } void Client::PlayerCapturedIntel(spades::client::Player *p){ std::string msg; msg = chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId()); msg += " captured "; msg += chatWindow->TeamColorMessage(world->GetTeam(1 - p->GetTeamId()).name, 1 - p->GetTeamId()); msg += "'s intel"; chatWindow->AddMessage(msg); msg = p->GetName(); msg += " captured "; msg += world->GetTeam(1 - p->GetTeamId()).name; msg += "'s Intel."; NetLog("%s", msg.c_str()); centerMessageView->AddMessage(msg); if(world->GetLocalPlayer() && !IsMuted()){ if(p->GetTeamId() == world->GetLocalPlayer()->GetTeamId()){ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/CTF/YourTeamCaptured.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else{ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/CTF/EnemyCaptured.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } } } void Client::PlayerPickedIntel(spades::client::Player *p) { std::string msg; msg = chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId()); msg += " picked up "; msg += chatWindow->TeamColorMessage(world->GetTeam(1 - p->GetTeamId()).name, 1 - p->GetTeamId()); msg += "'s intel"; chatWindow->AddMessage(msg); msg = p->GetName(); msg += " picked up "; msg += world->GetTeam(1 - p->GetTeamId()).name; msg += "'s intel."; NetLog("%s", msg.c_str()); centerMessageView->AddMessage(msg); if(!IsMuted()) { IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/CTF/PickedUp.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } } void Client::PlayerDropIntel(spades::client::Player *p) { std::string msg; msg = chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId()); msg += " dropped "; msg += chatWindow->TeamColorMessage(world->GetTeam(1 - p->GetTeamId()).name, 1 - p->GetTeamId()); msg += "'s intel"; chatWindow->AddMessage(msg); msg = p->GetName(); msg += " dropped "; msg += world->GetTeam(1 - p->GetTeamId()).name; msg += "'s intel."; NetLog("%s", msg.c_str()); centerMessageView->AddMessage(msg); } void Client::PlayerDestroyedBlockWithWeaponOrTool(spades::IntVector3 blk){ Vector3 origin = {blk.x + .5f, blk.y + .5f, blk.z + .5f}; if(!map->IsSolid(blk.x, blk.y, blk.z)) return;; IAudioChunk *c = audioDevice->RegisterSound("Sounds/Misc/BlockDestroy.wav"); if(!IsMuted()){ audioDevice->Play(c, origin, AudioParam()); } uint32_t col = map->GetColor(blk.x, blk.y, blk.z); IntVector3 colV = {(uint8_t)col, (uint8_t)(col >> 8), (uint8_t)(col >> 16)}; EmitBlockDestroyFragments(blk, colV); } void Client::PlayerDiggedBlock(spades::IntVector3 blk){ Vector3 origin = {blk.x + .5f, blk.y + .5f, blk.z + .5f}; IAudioChunk *c = audioDevice->RegisterSound("Sounds/Misc/BlockDestroy.wav"); if(!IsMuted()){ audioDevice->Play(c, origin, AudioParam()); } for(int z = blk.z - 1 ; z <= blk.z + 1; z++){ if(z < 0 || z > 61) continue; if(!map->IsSolid(blk.x, blk.y, z)) continue; uint32_t col = map->GetColor(blk.x, blk.y, z); IntVector3 colV = {(uint8_t)col, (uint8_t)(col >> 8), (uint8_t)(col >> 16)}; EmitBlockDestroyFragments(IntVector3::Make(blk.x, blk.y, z), colV); } } void Client::PlayerLeaving(spades::client::Player *p) { std::string msg; msg = "Player " + chatWindow->TeamColorMessage(p->GetName(), p->GetTeamId()); msg += " has left"; chatWindow->AddMessage(msg); } void Client::PlayerJoinedTeam(spades::client::Player *p) { std::string msg; msg = p->GetName(); msg += " joined "; msg += chatWindow->TeamColorMessage(world->GetTeam(p->GetTeamId()).name, p->GetTeamId()); msg += " team"; chatWindow->AddMessage(msg); } void Client::GrenadeDestroyedBlock(spades::IntVector3 blk){ for(int x = blk.x - 1; x <= blk.x + 1; x++) for(int y = blk.y - 1; y <= blk.y + 1; y++) for(int z = blk.z - 1 ; z <= blk.z + 1; z++){ if(z < 0 || z > 61 || x < 0 || x >= 512 || y < 0 || y >= 512) continue; if(!map->IsSolid(x, y, z)) continue; uint32_t col = map->GetColor(x, y, z); IntVector3 colV = {(uint8_t)col, (uint8_t)(col >> 8), (uint8_t)(col >> 16)}; EmitBlockDestroyFragments(IntVector3::Make(x, y, z), colV); } } void Client::TeamWon(int teamId){ std::string msg; msg = chatWindow->TeamColorMessage(world->GetTeam(teamId).name, teamId); msg += " wins!"; chatWindow->AddMessage(msg); msg = world->GetTeam(teamId).name; msg += " wins!"; NetLog("%s", msg.c_str()); centerMessageView->AddMessage(msg); if(world->GetLocalPlayer()){ if(teamId == world->GetLocalPlayer()->GetTeamId()){ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/Win.wav"); audioDevice->PlayLocal(chunk, AudioParam()); }else{ IAudioChunk *chunk = audioDevice->RegisterSound("Sounds/Feedback/Lose.wav"); audioDevice->PlayLocal(chunk, AudioParam()); } } } #pragma mark - IWorldListener Handlers void Client::PlayerJumped(spades::client::Player *p){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ IAudioChunk *c = p->GetWade() ? audioDevice->RegisterSound("Sounds/Player/WaterJump.wav"): audioDevice->RegisterSound("Sounds/Player/Jump.wav"); audioDevice->Play(c, p->GetOrigin(), AudioParam()); } } void Client::PlayerLanded(spades::client::Player *p, bool hurt) { SPADES_MARK_FUNCTION(); if(!IsMuted()){ IAudioChunk *c; if(hurt) c = audioDevice->RegisterSound("Sounds/Player/FallHurt.wav"); else if(p->GetWade()) c = audioDevice->RegisterSound("Sounds/Player/WaterLand.wav"); else c = audioDevice->RegisterSound("Sounds/Player/Land.wav"); audioDevice->Play(c, p->GetOrigin(), AudioParam()); } } void Client::PlayerMadeFootstep(spades::client::Player *p){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ const char *snds[] = { "Sounds/Player/Footstep1.wav", "Sounds/Player/Footstep2.wav", "Sounds/Player/Footstep3.wav", "Sounds/Player/Footstep4.wav" }; const char *wsnds[] = { "Sounds/Player/Wade1.wav", "Sounds/Player/Wade2.wav", "Sounds/Player/Wade3.wav", "Sounds/Player/Wade4.wav" }; IAudioChunk *c = p->GetWade() ? audioDevice->RegisterSound(wsnds[rand() % 4]): audioDevice->RegisterSound(snds[rand() % 4]); audioDevice->Play(c, p->GetOrigin(), AudioParam()); } } void Client::PlayerFiredWeapon(spades::client::Player *p) { SPADES_MARK_FUNCTION(); Vector3 muzzle; // make dlight { Vector3 vec; Matrix4 eyeMatrix = Matrix4::FromAxis(-p->GetRight(), p->GetFront(), -p->GetUp(), p->GetEye()); Matrix4 mat; mat = Matrix4::Translate(-0.13f, .5f, 0.2f); mat = eyeMatrix * mat; vec = (mat * MakeVector3(0, 1, 0)).GetXYZ(); muzzle = vec; MuzzleFire(vec, p->GetFront(), p == world->GetLocalPlayer()); } if(cg_ejectBrass){ float dist = (p->GetOrigin() - lastSceneDef.viewOrigin).GetPoweredLength(); if(dist < 130.f * 130.f) { IModel *model = NULL; IAudioChunk *snd = NULL; IAudioChunk *snd2 = NULL; switch(p->GetWeapon()->GetWeaponType()){ case RIFLE_WEAPON: model = renderer->RegisterModel("Models/Weapons/Rifle/Casing.kv6"); snd = (rand()&0x1000)? audioDevice->RegisterSound("Sounds/Weapons/Rifle/ShellDrop1.wav"): audioDevice->RegisterSound("Sounds/Weapons/Rifle/ShellDrop2.wav"); snd2 = audioDevice->RegisterSound("Sounds/Weapons/Rifle/ShellWater.wav"); break; case SHOTGUN_WEAPON: // FIXME: don't want to show shotgun't casing // because it isn't ejected when firing //model = renderer->RegisterModel("Models/Weapons/Shotgun/Casing.kv6"); break; case SMG_WEAPON: model = renderer->RegisterModel("Models/Weapons/SMG/Casing.kv6"); snd = (rand()&0x1000)? audioDevice->RegisterSound("Sounds/Weapons/SMG/ShellDrop1.wav"): audioDevice->RegisterSound("Sounds/Weapons/SMG/ShellDrop2.wav"); snd2 = audioDevice->RegisterSound("Sounds/Weapons/SMG/ShellWater.wav"); break; } if(model){ Vector3 origin; origin = muzzle - p->GetFront() * 0.5f; Vector3 vel; vel = p->GetFront() * 0.5f + p->GetRight() + p->GetUp() * 0.2f; switch(p->GetWeapon()->GetWeaponType()){ case SMG_WEAPON: vel -= p->GetFront() * 0.7f; break; case SHOTGUN_WEAPON: vel *= .5f; break; default: break; } ILocalEntity *ent; ent = new GunCasing(this, model, snd, snd2, origin, p->GetFront(), vel); AddLocalEntity(ent); } } } if(!IsMuted()){ IAudioChunk *c; std::string weapPrefix = GetWeaponPrefix("Sounds", p->GetWeapon()->GetWeaponType()); bool isLocal = p == world->GetLocalPlayer(); c = isLocal ? audioDevice->RegisterSound((weapPrefix + "/FireLocal.wav").c_str()): audioDevice->RegisterSound((weapPrefix + "/Fire.wav").c_str()); AudioParam param; param.volume = 8.f; if(isLocal) audioDevice->PlayLocal(c,MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, param); if(isLocal) localFireVibrationTime = time; // play far sound c = audioDevice->RegisterSound((weapPrefix + "/FireFar.wav").c_str()); param.volume = 1.0f; //if(p->GetWeapon()->GetWeaponType() == SMG_WEAPON) // param.volume *= .3f; param.referenceDistance = 10.f; if(isLocal) audioDevice->PlayLocal(c,MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, param); c = audioDevice->RegisterSound((weapPrefix + "/FireStereo.wav").c_str()); if(isLocal) audioDevice->PlayLocal(c,MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, param); if(p->GetWeapon()->GetWeaponType() == SMG_WEAPON){ switch((rand() >> 8) & 3) { case 0: c = audioDevice->RegisterSound((weapPrefix + "/Mech1.wav").c_str()); break; case 1: c = audioDevice->RegisterSound((weapPrefix + "/Mech2.wav").c_str()); break; case 2: c = audioDevice->RegisterSound((weapPrefix + "/Mech3.wav").c_str()); break; case 3: c = audioDevice->RegisterSound((weapPrefix + "/Mech4.wav").c_str()); break; } param.volume = 1.6f; if(isLocal) audioDevice->PlayLocal(c,MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, param); } } } void Client::PlayerDryFiredWeapon(spades::client::Player *p) { SPADES_MARK_FUNCTION(); if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/DryFire.wav"); if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), AudioParam()); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, AudioParam()); } } void Client::PlayerReloadingWeapon(spades::client::Player *p) { SPADES_MARK_FUNCTION(); if(!IsMuted()){ IAudioChunk *c; std::string weapPrefix = GetWeaponPrefix("Sounds", p->GetWeapon()->GetWeaponType()); bool isLocal = p == world->GetLocalPlayer(); c = isLocal ? audioDevice->RegisterSound((weapPrefix+"/ReloadLocal.wav").c_str()): audioDevice->RegisterSound((weapPrefix+"/Reload.wav").c_str()); AudioParam param; param.volume = 0.2f; if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .2f, param); } } void Client::PlayerReloadedWeapon(spades::client::Player *p){ SPADES_MARK_FUNCTION(); bool plays = false; if(p->GetWeapon()->IsReloadSlow()){ Weapon *w = p->GetWeapon(); plays = true; } if(!plays) return; if(!IsMuted()){ IAudioChunk *c; std::string weapPrefix = GetWeaponPrefix("Sounds", p->GetWeapon()->GetWeaponType()); bool isLocal = p == world->GetLocalPlayer(); c = isLocal ? audioDevice->RegisterSound((weapPrefix+"/CockLocal.wav").c_str()): audioDevice->RegisterSound((weapPrefix+"/Cock.wav").c_str()); AudioParam param; param.volume = 0.2f; if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), param); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .2f, param); } } void Client::PlayerChangedTool(spades::client::Player *p){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c; if(isLocal){ switch(p->GetTool()) { case Player::ToolSpade: c = audioDevice->RegisterSound("Sounds/Weapons/Spade/RaiseLocal.wav"); break; case Player::ToolBlock: c = audioDevice->RegisterSound("Sounds/Weapons/Block/RaiseLocal.wav"); break; case Player::ToolWeapon: switch(p->GetWeapon()->GetWeaponType()){ case RIFLE_WEAPON: c = audioDevice->RegisterSound("Sounds/Weapons/Rifle/RaiseLocal.wav"); break; case SMG_WEAPON: c = audioDevice->RegisterSound("Sounds/Weapons/SMG/RaiseLocal.wav"); break; case SHOTGUN_WEAPON: c = audioDevice->RegisterSound("Sounds/Weapons/Shotgun/RaiseLocal.wav"); break; } break; case Player::ToolGrenade: c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/RaiseLocal.wav"); break; } }else{ c = audioDevice->RegisterSound("Sounds/Weapons/Switch.wav"); } if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), AudioParam()); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, AudioParam()); } } void Client::PlayerRestocked(spades::client::Player *p){ if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c = isLocal ? audioDevice->RegisterSound("Sounds/Weapons/SwitchLocal.wav"): audioDevice->RegisterSound("Sounds/Weapons/Switch.wav"); if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), AudioParam()); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .3f + p->GetRight() * .4f, AudioParam()); } } void Client::PlayerThrownGrenade(spades::client::Player *p, Grenade *g){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Throw.wav"); if(g && isLocal){ net->SendGrenade(g); } if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.4f, 0.1f, .3f), AudioParam()); else audioDevice->Play(c, p->GetEye() + p->GetFront() * 0.5f - p->GetUp() * .2f + p->GetRight() * .3f, AudioParam()); } } void Client::PlayerMissedSpade(spades::client::Player *p){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Spade/Miss.wav"); if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.2f, -.1f, 0.7f), AudioParam()); else audioDevice->Play(c, p->GetOrigin() + p->GetFront() * 0.8f - p->GetUp() * .2f, AudioParam()); } } void Client::PlayerHitPlayerWithSpade(spades::client::Player *p){ SPADES_MARK_FUNCTION(); SPRaise("Obsolete function called"); } void Client::PlayerHitBlockWithSpade(spades::client::Player *p, Vector3 hitPos, IntVector3 blockPos, IntVector3 normal){ SPADES_MARK_FUNCTION(); uint32_t col = map->GetColor(blockPos.x, blockPos.y, blockPos.z); IntVector3 colV = {(uint8_t)col, (uint8_t)(col >> 8), (uint8_t)(col >> 16)}; Vector3 shiftedHitPos = hitPos; shiftedHitPos.x += normal.x * .05f; shiftedHitPos.y += normal.y * .05f; shiftedHitPos.z += normal.z * .05f; EmitBlockFragments(shiftedHitPos, colV); if(!IsMuted()){ bool isLocal = p == world->GetLocalPlayer(); IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Spade/HitBlock.wav"); if(isLocal) audioDevice->PlayLocal(c, MakeVector3(.1f, -.1f, 1.2f), AudioParam()); else audioDevice->Play(c, p->GetOrigin() + p->GetFront() * 0.5f - p->GetUp() * .2f, AudioParam()); } } void Client::PlayerKilledPlayer(spades::client::Player *killer, spades::client::Player *victim, KillType kt) { // play hit sound if(kt == KillTypeWeapon || kt == KillTypeHeadshot) { // don't play on local: see BullethitPlayer if(victim != world->GetLocalPlayer()) { if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh.wav"); audioDevice->Play(c, victim->GetEye(), AudioParam()); } } } // begin following if(victim == world->GetLocalPlayer()){ followingPlayerId = victim->GetId(); Vector3 v = -victim->GetFront(); followYaw = atan2(v.y, v.x); followPitch = 30.f * M_PI /180.f; } // emit blood (also for local player) // FIXME: emiting blood for either // client-side or server-side hit? switch(kt){ case KillTypeGrenade: case KillTypeHeadshot: case KillTypeMelee: case KillTypeWeapon: Bleed(victim->GetEye()); break; default: break; } // create ragdoll corpse if(cg_ragdoll && victim->GetTeamId() < 2){ Corpse *corp; corp = new Corpse(renderer, map, victim); if(victim == world->GetLocalPlayer()) lastMyCorpse = corp; if(killer != victim && kt != KillTypeGrenade){ Vector3 dir = victim->GetPosition() - killer->GetPosition(); dir = dir.Normalize(); if(kt == KillTypeMelee){ dir *= 6.f; }else{ if(killer->GetWeapon()->GetWeaponType() == SMG_WEAPON){ dir *= 2.8f; }else if(killer->GetWeapon()->GetWeaponType() == SHOTGUN_WEAPON){ dir *= 4.5f; }else{ dir *= 3.5f; } } corp->AddImpulse(dir); }else if(kt == KillTypeGrenade){ corp->AddImpulse(MakeVector3(0, 0, -4.f - GetRandom() * 4.f)); } corp->AddImpulse(victim->GetVelocty()); corpses.push_back(corp); if(corpses.size() > corpseHardLimit){ corp = corpses.front(); delete corp; corpses.pop_front(); }else if(corpses.size() > corpseSoftLimit){ RemoveInvisibleCorpses(); } } // add chat message std::string s; s = ChatWindow::TeamColorMessage(killer->GetName(), killer->GetTeamId()); std::string cause; bool ff = killer->GetTeamId() == victim->GetTeamId(); if(killer == victim) ff = false; cause = " ["; switch(kt){ case KillTypeClassChange: cause += "Weapon Change"; break; case KillTypeFall: cause += "Fall"; break; case KillTypeGrenade: cause += "Grenade"; break; case KillTypeHeadshot: cause += "Headshot"; break; case KillTypeMelee: cause += "Spade"; break; case KillTypeTeamChange: cause += "Team Change"; break; case KillTypeWeapon: cause += killer->GetWeapon()->GetName(); break; } cause += "] "; if(ff) s += ChatWindow::ColoredMessage(cause, MsgColorRed); else s += cause; if(killer != victim){ s += ChatWindow::TeamColorMessage(victim->GetName(), victim->GetTeamId()); } killfeedWindow->AddMessage(s); // log to netlog if(killer != victim) { NetLog("%s (%s)%s%s (%s)", killer->GetName().c_str(), world->GetTeam(killer->GetTeamId()).name.c_str(), cause.c_str(), victim->GetName().c_str(), world->GetTeam(victim->GetTeamId()).name.c_str()); }else{ NetLog("%s (%s)%s", killer->GetName().c_str(), world->GetTeam(killer->GetTeamId()).name.c_str(), cause.c_str()); } // show big message if player is involved if(victim != killer){ if(killer == world->GetLocalPlayer()){ char buf[256]; sprintf(buf, "You have killed %s", victim->GetName().c_str()); centerMessageView->AddMessage(buf); } } } void Client::BulletHitPlayer(spades::client::Player *hurtPlayer, HitType type, spades::Vector3 hitPos, spades::client::Player *by) { SPADES_MARK_FUNCTION(); SPAssert(type != HitTypeBlock); // don't bleed local player if(hurtPlayer != world->GetLocalPlayer() || !ShouldRenderInThirdPersonView()){ Bleed(hitPos); } if(hurtPlayer == world->GetLocalPlayer()){ // don't player hit sound now; // local bullet impact sound is // played by checking the decrease of HP return; } if(!IsMuted()){ if(type == HitTypeMelee){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Spade/HitPlayer.wav"); audioDevice->Play(c, hitPos, AudioParam()); }else{ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh.wav"); audioDevice->Play(c, hitPos, AudioParam()); } } if(by == world->GetLocalPlayer() && hurtPlayer){ net->SendHit(hurtPlayer->GetId(), type); } } void Client::BulletHitBlock(Vector3 hitPos, IntVector3 blockPos, IntVector3 normal){ SPADES_MARK_FUNCTION(); uint32_t col = map->GetColor(blockPos.x, blockPos.y, blockPos.z); IntVector3 colV = {(uint8_t)col, (uint8_t)(col >> 8), (uint8_t)(col >> 16)}; Vector3 shiftedHitPos = hitPos; shiftedHitPos.x += normal.x * .05f; shiftedHitPos.y += normal.y * .05f; shiftedHitPos.z += normal.z * .05f; EmitBlockFragments(shiftedHitPos, colV); if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Hit.wav"); audioDevice->Play(c, shiftedHitPos, AudioParam()); } } void Client::AddBulletTracer(spades::client::Player *player, spades::Vector3 muzzlePos, spades::Vector3 hitPos) { Tracer *t; float vel; switch(player->GetWeapon()->GetWeaponType()) { case RIFLE_WEAPON: vel = 700.f; break; case SMG_WEAPON: vel = 360.f; break; case SHOTGUN_WEAPON: return; } t = new Tracer(this, muzzlePos, hitPos, vel); AddLocalEntity(t); } void Client::BlocksFell(std::vector blocks) { if(blocks.empty()) return; FallingBlock *b = new FallingBlock(this, blocks); AddLocalEntity(b); if(!IsMuted()){ IntVector3 v = blocks[0]; Vector3 o; o.x = v.x; o.y = v.y; o.z = v.z; o += .5f; IAudioChunk *c = audioDevice->RegisterSound("Sounds/Misc/BlockFall.wav"); audioDevice->Play(c, o, AudioParam()); } } void Client::GrenadeBounced(spades::client::Grenade *g){ SPADES_MARK_FUNCTION(); if(g->GetPosition().z < 63.f){ if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Bounce.wav"); audioDevice->Play(c, g->GetPosition(), AudioParam()); } } } void Client::GrenadeDroppedIntoWater(spades::client::Grenade *g){ SPADES_MARK_FUNCTION(); if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/DropWater.wav"); audioDevice->Play(c, g->GetPosition(), AudioParam()); } } void Client::GrenadeExploded(spades::client::Grenade *g) { SPADES_MARK_FUNCTION(); bool inWater = g->GetPosition().z > 63.f; if(inWater){ if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplode.wav"); AudioParam param; param.volume = 10.f; audioDevice->Play(c, g->GetPosition(), param); c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeFar.wav"); param.volume = 40.f; audioDevice->Play(c, g->GetPosition(), param); c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeStereo.wav"); param.volume = 40.f; audioDevice->Play(c, g->GetPosition(), param); } GrenadeExplosionUnderwater(g->GetPosition()); }else{ GrenadeExplosion(g->GetPosition()); if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Explode.wav"); AudioParam param; param.volume = 10.f; audioDevice->Play(c, g->GetPosition(), param); c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeFar.wav"); param.volume = 40.f; audioDevice->Play(c, g->GetPosition(), param); c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeStereo.wav"); param.volume = 40.f; audioDevice->Play(c, g->GetPosition(), param); // debri sound c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Debris.wav"); param.volume = 5.f; IntVector3 outPos; Vector3 soundPos = g->GetPosition(); if(world->GetMap()->CastRay(soundPos, MakeVector3(0,0,1), 8.f, outPos)){ soundPos.z = (float)outPos.z - .2f; } audioDevice->Play(c, soundPos, param); } } } void Client::LocalPlayerPulledGrenadePin() { SPADES_MARK_FUNCTION(); if(!IsMuted()){ IAudioChunk *c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Fire.wav"); audioDevice->PlayLocal(c, MakeVector3(.4f, -.3f, .5f), AudioParam()); } } void Client::LocalPlayerBlockAction(spades::IntVector3 v, BlockActionType type){ net->SendBlockAction(v, type); } void Client::LocalPlayerCreatedLineBlock(spades::IntVector3 v1, spades::IntVector3 v2) { net->SendBlockLine(v1, v2); } void Client::LocalPlayerHurt(HurtType type, bool sourceGiven, spades::Vector3 source) { SPADES_MARK_FUNCTION(); if(sourceGiven){ Player * p = world->GetLocalPlayer(); if(!p) return; Vector3 rel = source - p->GetEye(); rel.z = 0.f; rel = rel.Normalize(); hurtRingView->Add(rel); } } } }