1295 lines
39 KiB
C++
1295 lines
39 KiB
C++
/*
|
|
Copyright (c) 2013 yvt
|
|
based on code of pysnip (c) Mathias Kaerlev 2011-2012.
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "Client.h"
|
|
|
|
#include <Core/ConcurrentDispatch.h>
|
|
#include <Core/Settings.h>
|
|
#include <Core/Strings.h>
|
|
|
|
#include "IAudioChunk.h"
|
|
#include "IAudioDevice.h"
|
|
|
|
#include "CenterMessageView.h"
|
|
#include "ChatWindow.h"
|
|
#include "ClientPlayer.h"
|
|
#include "ClientUI.h"
|
|
#include "Corpse.h"
|
|
#include "FallingBlock.h"
|
|
#include "HurtRingView.h"
|
|
#include "ILocalEntity.h"
|
|
#include "LimboView.h"
|
|
#include "MapView.h"
|
|
#include "PaletteView.h"
|
|
#include "Tracer.h"
|
|
|
|
#include "GameMap.h"
|
|
#include "Grenade.h"
|
|
#include "Weapon.h"
|
|
#include "World.h"
|
|
|
|
#include "NetClient.h"
|
|
|
|
DEFINE_SPADES_SETTING(cg_ragdoll, "1");
|
|
SPADES_SETTING(cg_blood);
|
|
DEFINE_SPADES_SETTING(cg_ejectBrass, "1");
|
|
DEFINE_SPADES_SETTING(cg_hitFeedbackSoundGain, "0.2");
|
|
|
|
SPADES_SETTING(cg_alerts);
|
|
SPADES_SETTING(cg_centerMessage);
|
|
|
|
SPADES_SETTING(cg_shake);
|
|
|
|
SPADES_SETTING(cg_holdAimDownSight);
|
|
|
|
namespace spades {
|
|
namespace client {
|
|
|
|
#pragma mark - World States
|
|
|
|
float Client::GetSprintState() {
|
|
if (!world)
|
|
return 0.f;
|
|
|
|
stmp::optional<ClientPlayer &> p = GetLocalClientPlayer();
|
|
if (!p)
|
|
return 0.f;
|
|
return p->GetSprintState();
|
|
}
|
|
|
|
float Client::GetAimDownState() {
|
|
if (!world)
|
|
return 0.f;
|
|
|
|
stmp::optional<ClientPlayer &> p = GetLocalClientPlayer();
|
|
if (!p)
|
|
return 0.f;
|
|
return p->GetAimDownState();
|
|
}
|
|
|
|
bool Client::CanLocalPlayerUseToolNow() {
|
|
if (!world || !world->GetLocalPlayer() || !world->GetLocalPlayer()->IsAlive()) {
|
|
return false;
|
|
}
|
|
|
|
if (GetSprintState() > 0 || world->GetLocalPlayer()->GetInput().sprint) {
|
|
// Player is unable to use a tool while/soon after sprinting
|
|
return false;
|
|
}
|
|
|
|
stmp::optional<ClientPlayer &> clientPlayer = GetLocalClientPlayer();
|
|
|
|
if (clientPlayer.value().IsChangingTool()) {
|
|
// Player is unable to use a tool while switching to another tool
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
stmp::optional<ClientPlayer &> Client::GetLocalClientPlayer() {
|
|
if (!world || !world->GetLocalPlayerIndex()) {
|
|
return {};
|
|
}
|
|
return clientPlayers.at(static_cast<std::size_t>(*world->GetLocalPlayerIndex()));
|
|
}
|
|
|
|
#pragma mark - World Actions
|
|
/** Captures the color of the block player is looking at. */
|
|
void Client::CaptureColor() {
|
|
if (!world)
|
|
return;
|
|
stmp::optional<Player &> p = world->GetLocalPlayer();
|
|
if (!p)
|
|
return;
|
|
if (!p->IsAlive())
|
|
return;
|
|
|
|
IntVector3 outBlockCoord;
|
|
uint32_t col;
|
|
if (!world->GetMap()->CastRay(p->GetEye(), p->GetFront(), 256.f, outBlockCoord)) {
|
|
auto c = world->GetFogColor();
|
|
col = c.x | c.y << 8 | c.z << 16;
|
|
} else {
|
|
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();
|
|
}
|
|
|
|
void Client::SetSelectedTool(Player::ToolType type, bool quiet) {
|
|
if (type == world->GetLocalPlayer()->GetTool())
|
|
return;
|
|
lastTool = world->GetLocalPlayer()->GetTool();
|
|
hasLastTool = true;
|
|
|
|
world->GetLocalPlayer()->SetTool(type);
|
|
net->SendTool();
|
|
|
|
if (!quiet) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/SwitchLocal.opus");
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, -.3f, .5f),
|
|
AudioParam());
|
|
}
|
|
}
|
|
|
|
#pragma mark - World Update
|
|
|
|
void Client::UpdateWorld(float dt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
stmp::optional<Player &> player = world->GetLocalPlayer();
|
|
|
|
if (player) {
|
|
// disable input when UI is open
|
|
if (scriptedUI->NeedsInput()) {
|
|
weapInput.primary = false;
|
|
if (player->GetTeamId() >= 2 || player->GetTool() != Player::ToolWeapon) {
|
|
weapInput.secondary = false;
|
|
}
|
|
playerInput = PlayerInput();
|
|
}
|
|
|
|
if (player->GetTeamId() >= 2) {
|
|
UpdateLocalSpectator(dt);
|
|
} else {
|
|
UpdateLocalPlayer(dt);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// dynamic time step
|
|
// physics diverges from server
|
|
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
|
|
|
|
// update player view (doesn't affect physics/game logics)
|
|
for (auto &clientPlayer : clientPlayers) {
|
|
if (clientPlayer) {
|
|
clientPlayer->Update(dt);
|
|
}
|
|
}
|
|
|
|
// corpse never accesses audio nor renderer, so
|
|
// we can do it in the separate thread
|
|
class CorpseUpdateDispatch : public ConcurrentDispatch {
|
|
Client &client;
|
|
float dt;
|
|
|
|
public:
|
|
CorpseUpdateDispatch(Client &c, float dt) : client{c}, dt{dt} {}
|
|
void Run() override {
|
|
for (auto &c : client.corpses) {
|
|
for (int i = 0; i < 4; i++)
|
|
c->Update(dt / 4.f);
|
|
}
|
|
}
|
|
};
|
|
CorpseUpdateDispatch corpseDispatch{*this, dt};
|
|
corpseDispatch.Start();
|
|
|
|
// local entities should be done in the client thread
|
|
{
|
|
decltype(localEntities)::iterator it;
|
|
std::vector<decltype(it)> 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++) {
|
|
localEntities.erase(its[i]);
|
|
}
|
|
}
|
|
|
|
corpseDispatch.Join();
|
|
|
|
if (grenadeVibration > 0.f) {
|
|
grenadeVibration -= dt;
|
|
if (grenadeVibration < 0.f)
|
|
grenadeVibration = 0.f;
|
|
}
|
|
|
|
if (grenadeVibrationSlow > 0.f) {
|
|
grenadeVibrationSlow -= dt;
|
|
if (grenadeVibrationSlow < 0.f)
|
|
grenadeVibrationSlow = 0.f;
|
|
}
|
|
|
|
if (hitFeedbackIconState > 0.f) {
|
|
hitFeedbackIconState -= dt * 4.f;
|
|
if (hitFeedbackIconState < 0.f)
|
|
hitFeedbackIconState = 0.f;
|
|
}
|
|
|
|
if (time > lastPosSentTime + 1.f && world->GetLocalPlayer()) {
|
|
stmp::optional<Player &> p = world->GetLocalPlayer();
|
|
if (p->IsAlive() && p->GetTeamId() < 2) {
|
|
net->SendPosition();
|
|
lastPosSentTime = time;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Handles movement of spectating local player. */
|
|
void Client::UpdateLocalSpectator(float dt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
auto &sharedState = followAndFreeCameraState;
|
|
auto &freeState = freeCameraState;
|
|
|
|
Vector3 lastPos = freeState.position;
|
|
freeState.velocity *= powf(.3f, dt);
|
|
freeState.position += freeState.velocity * dt;
|
|
|
|
if (freeState.position.x < 0.f) {
|
|
freeState.velocity.x = fabsf(freeState.velocity.x) * 0.2f;
|
|
freeState.position = lastPos + freeState.velocity * dt;
|
|
}
|
|
if (freeState.position.y < 0.f) {
|
|
freeState.velocity.y = fabsf(freeState.velocity.y) * 0.2f;
|
|
freeState.position = lastPos + freeState.velocity * dt;
|
|
}
|
|
if (freeState.position.x > (float)GetWorld()->GetMap()->Width()) {
|
|
freeState.velocity.x = fabsf(freeState.velocity.x) * -0.2f;
|
|
freeState.position = lastPos + freeState.velocity * dt;
|
|
}
|
|
if (freeState.position.y > (float)GetWorld()->GetMap()->Height()) {
|
|
freeState.velocity.y = fabsf(freeState.velocity.y) * -0.2f;
|
|
freeState.position = lastPos + freeState.velocity * dt;
|
|
}
|
|
|
|
GameMap::RayCastResult minResult;
|
|
float minDist = 1.e+10f;
|
|
Vector3 minShift;
|
|
|
|
// check collision
|
|
if (freeState.velocity.GetLength() < .01) {
|
|
freeState.position = lastPos;
|
|
freeState.velocity *= 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, freeState.position - lastPos, 256);
|
|
if (result.hit && !result.startSolid &&
|
|
Vector3::Dot(result.hitPos - freeState.position - shift,
|
|
freeState.position - lastPos) < 0.f) {
|
|
|
|
float dist =
|
|
Vector3::Dot(result.hitPos - freeState.position - shift,
|
|
(freeState.position - lastPos).Normalize());
|
|
if (dist < minDist) {
|
|
minResult = result;
|
|
minDist = dist;
|
|
minShift = shift;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (minDist < 1.e+9f) {
|
|
GameMap::RayCastResult result = minResult;
|
|
Vector3 shift = minShift;
|
|
freeState.position = result.hitPos - shift;
|
|
freeState.position.x += result.normal.x * .02f;
|
|
freeState.position.y += result.normal.y * .02f;
|
|
freeState.position.z += result.normal.z * .02f;
|
|
|
|
// bounce
|
|
Vector3 norm = {(float)result.normal.x, (float)result.normal.y,
|
|
(float)result.normal.z};
|
|
float dot = Vector3::Dot(freeState.velocity, norm);
|
|
freeState.velocity -= norm * (dot * 1.2f);
|
|
}
|
|
|
|
// acceleration
|
|
Vector3 front;
|
|
Vector3 up = {0, 0, -1};
|
|
|
|
front.x = -cosf(sharedState.yaw) * cosf(sharedState.pitch);
|
|
front.y = -sinf(sharedState.yaw) * cosf(sharedState.pitch);
|
|
front.z = sinf(sharedState.pitch);
|
|
|
|
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) {
|
|
freeState.velocity += front;
|
|
} else if (playerInput.moveBackward) {
|
|
freeState.velocity -= front;
|
|
}
|
|
if (playerInput.moveLeft) {
|
|
freeState.velocity -= right;
|
|
} else if (playerInput.moveRight) {
|
|
freeState.velocity += right;
|
|
}
|
|
if (playerInput.jump) {
|
|
freeState.velocity += up2;
|
|
} else if (playerInput.crouch) {
|
|
freeState.velocity -= up2;
|
|
}
|
|
|
|
SPAssert(freeState.velocity.GetLength() < 100.f);
|
|
}
|
|
|
|
/** Handles movement of joined local player. */
|
|
void Client::UpdateLocalPlayer(float dt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
Player &player = GetWorld()->GetLocalPlayer().value();
|
|
|
|
PlayerInput inp = playerInput;
|
|
WeaponInput winp = weapInput;
|
|
|
|
Vector3 velocity = player.GetVelocity();
|
|
Vector3 horizontalVelocity{velocity.x, velocity.y, 0.0f};
|
|
|
|
if (horizontalVelocity.GetLength() < 0.1f) {
|
|
inp.sprint = false;
|
|
}
|
|
|
|
// Can't use a tool while sprinting or switching to another tool, etc.
|
|
if (!CanLocalPlayerUseToolNow()) {
|
|
winp.primary = false;
|
|
winp.secondary = false;
|
|
}
|
|
|
|
// don't allow to stand up when ceilings are too low
|
|
if (inp.crouch == false) {
|
|
if (player.GetInput().crouch) {
|
|
if (!player.TryUncrouch(false)) {
|
|
inp.crouch = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't allow jumping in the air
|
|
if (inp.jump) {
|
|
if (!player.IsOnGroundOrWade())
|
|
inp.jump = false;
|
|
}
|
|
|
|
if (player.GetTool() == Player::ToolWeapon) {
|
|
// disable weapon while reloading (except shotgun)
|
|
if (player.IsAwaitingReloadCompletion() && !player.GetWeapon().IsReloadSlow()) {
|
|
winp.primary = false;
|
|
winp.secondary = false;
|
|
}
|
|
|
|
// stop firing if the player is out of ammo
|
|
if (player.GetWeapon().GetAmmo() == 0) {
|
|
winp.primary = false;
|
|
}
|
|
}
|
|
|
|
player.SetInput(inp);
|
|
player.SetWeaponInput(winp);
|
|
|
|
// send player input
|
|
{
|
|
PlayerInput sentInput = inp;
|
|
WeaponInput sentWeaponInput = winp;
|
|
|
|
// FIXME: send only there are any changed?
|
|
net->SendPlayerInput(sentInput);
|
|
net->SendWeaponInput(sentWeaponInput);
|
|
}
|
|
|
|
if (hasDelayedReload) {
|
|
world->GetLocalPlayer()->Reload();
|
|
net->SendReload();
|
|
hasDelayedReload = false;
|
|
}
|
|
|
|
// PlayerInput actualInput = player.GetInput();
|
|
WeaponInput actualWeapInput = player.GetWeaponInput();
|
|
|
|
if (!(actualWeapInput.secondary && player.IsToolWeapon() && player.IsAlive()) &&
|
|
!(cg_holdAimDownSight && weapInput.secondary)) {
|
|
if (player.IsToolWeapon()) {
|
|
// there is a possibility that player has respawned or something.
|
|
// stop aiming down
|
|
weapInput.secondary = false;
|
|
}
|
|
}
|
|
|
|
// is the selected tool no longer usable (ex. out of ammo)?
|
|
if (!player.IsToolSelectable(player.GetTool())) {
|
|
// release mouse button before auto-switching tools
|
|
winp.primary = false;
|
|
winp.secondary = false;
|
|
weapInput = winp;
|
|
net->SendWeaponInput(weapInput);
|
|
actualWeapInput = winp = player.GetWeaponInput();
|
|
|
|
// select another tool
|
|
Player::ToolType t = player.GetTool();
|
|
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);
|
|
}
|
|
|
|
// send orientation
|
|
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;
|
|
|
|
// show block count when building block lines.
|
|
if (player.IsAlive() && player.GetTool() == Player::ToolBlock &&
|
|
player.GetWeaponInput().secondary && player.IsBlockCursorDragging()) {
|
|
if (player.IsBlockCursorActive()) {
|
|
auto blocks = world->CubeLine(player.GetBlockCursorDragPos(),
|
|
player.GetBlockCursorPos(), 256);
|
|
auto msg = _TrN("Client", "{0} block", "{0} blocks", blocks.size());
|
|
AlertType type = static_cast<int>(blocks.size()) > player.GetNumBlocks()
|
|
? AlertType::Warning
|
|
: AlertType::Notice;
|
|
ShowAlert(msg, type, 0.f, true);
|
|
} else {
|
|
// invalid
|
|
auto msg = _Tr("Client", "-- blocks");
|
|
AlertType type = AlertType::Warning;
|
|
ShowAlert(msg, type, 0.f, true);
|
|
}
|
|
}
|
|
|
|
if (player.IsAlive())
|
|
lastAliveTime = time;
|
|
|
|
if (player.GetHealth() < lastHealth) {
|
|
// ouch!
|
|
lastHealth = player.GetHealth();
|
|
lastHurtTime = world->GetTime();
|
|
|
|
Handle<IAudioChunk> c;
|
|
switch (SampleRandomInt(0, 3)) {
|
|
case 0:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal1.opus");
|
|
break;
|
|
case 1:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal2.opus");
|
|
break;
|
|
case 2:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal3.opus");
|
|
break;
|
|
case 3:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/FleshLocal4.opus");
|
|
break;
|
|
}
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), AudioParam());
|
|
|
|
float hpper = player.GetHealth() / 100.f;
|
|
int cnt = 18 - (int)(player.GetHealth() / 100.f * 8.f);
|
|
hurtSprites.resize(std::max(cnt, 6));
|
|
for (size_t i = 0; i < hurtSprites.size(); i++) {
|
|
HurtSprite &spr = hurtSprites[i];
|
|
spr.angle = SampleRandomFloat() * (2.f * static_cast<float>(M_PI));
|
|
spr.scale = .2f + SampleRandomFloat() * SampleRandomFloat() * .7f;
|
|
spr.horzShift = SampleRandomFloat();
|
|
spr.strength = .3f + SampleRandomFloat() * .7f;
|
|
if (hpper > .5f) {
|
|
spr.strength *= 1.5f - hpper;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
lastHealth = player.GetHealth();
|
|
}
|
|
|
|
inp.jump = false;
|
|
}
|
|
|
|
#pragma mark - IWorldListener Handlers
|
|
|
|
void Client::PlayerObjectSet(int id) {
|
|
if (clientPlayers[id]) {
|
|
clientPlayers[id] = nullptr;
|
|
}
|
|
|
|
stmp::optional<Player &> p = world->GetPlayer(id);
|
|
if (p) {
|
|
clientPlayers[id] = Handle<ClientPlayer>::New(*p, *this);
|
|
}
|
|
}
|
|
|
|
void Client::PlayerJumped(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
|
|
Handle<IAudioChunk> c =
|
|
p.GetWade() ? audioDevice->RegisterSound("Sounds/Player/WaterJump.opus")
|
|
: audioDevice->RegisterSound("Sounds/Player/Jump.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), p.GetOrigin(), AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::PlayerLanded(spades::client::Player &p, bool hurt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c;
|
|
if (hurt)
|
|
c = audioDevice->RegisterSound("Sounds/Player/FallHurt.opus");
|
|
else if (p.GetWade())
|
|
c = audioDevice->RegisterSound("Sounds/Player/WaterLand.opus");
|
|
else
|
|
c = audioDevice->RegisterSound("Sounds/Player/Land.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), p.GetOrigin(), AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::PlayerMadeFootstep(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
std::array<const char *, 8> snds = {
|
|
"Sounds/Player/Footstep1.opus", "Sounds/Player/Footstep2.opus",
|
|
"Sounds/Player/Footstep3.opus", "Sounds/Player/Footstep4.opus",
|
|
"Sounds/Player/Footstep5.opus", "Sounds/Player/Footstep6.opus",
|
|
"Sounds/Player/Footstep7.opus", "Sounds/Player/Footstep8.opus"};
|
|
std::array<const char *, 12> rsnds = {
|
|
"Sounds/Player/Run1.opus", "Sounds/Player/Run2.opus",
|
|
"Sounds/Player/Run3.opus", "Sounds/Player/Run4.opus",
|
|
"Sounds/Player/Run5.opus", "Sounds/Player/Run6.opus",
|
|
"Sounds/Player/Run7.opus", "Sounds/Player/Run8.opus",
|
|
"Sounds/Player/Run9.opus", "Sounds/Player/Run10.opus",
|
|
"Sounds/Player/Run11.opus", "Sounds/Player/Run12.opus",
|
|
};
|
|
std::array<const char *, 8> wsnds = {
|
|
"Sounds/Player/Wade1.opus", "Sounds/Player/Wade2.opus",
|
|
"Sounds/Player/Wade3.opus", "Sounds/Player/Wade4.opus",
|
|
"Sounds/Player/Wade5.opus", "Sounds/Player/Wade6.opus",
|
|
"Sounds/Player/Wade7.opus", "Sounds/Player/Wade8.opus"};
|
|
bool sprinting = clientPlayers[p.GetId()]
|
|
? clientPlayers[p.GetId()]->GetSprintState() > 0.5f
|
|
: false;
|
|
Handle<IAudioChunk> c = p.GetWade()
|
|
? audioDevice->RegisterSound(SampleRandomElement(wsnds))
|
|
: audioDevice->RegisterSound(SampleRandomElement(snds));
|
|
audioDevice->Play(c.GetPointerOrNull(), p.GetOrigin(), AudioParam());
|
|
if (sprinting && !p.GetWade()) {
|
|
AudioParam param;
|
|
param.volume *= clientPlayers[p.GetId()]->GetSprintState();
|
|
c = audioDevice->RegisterSound(SampleRandomElement(rsnds));
|
|
audioDevice->Play(c.GetPointerOrNull(), p.GetOrigin(), param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::PlayerFiredWeapon(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (&p == world->GetLocalPlayer()) {
|
|
localFireVibrationTime = time;
|
|
}
|
|
|
|
clientPlayers.at(p.GetId())->FiredWeapon();
|
|
}
|
|
void Client::PlayerDryFiredWeapon(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
bool isLocal = &p == world->GetLocalPlayer();
|
|
Handle<IAudioChunk> c = audioDevice->RegisterSound("Sounds/Weapons/DryFire.opus");
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, -.3f, .5f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
p.GetEye() + p.GetFront() * 0.5f - p.GetUp() * .3f +
|
|
p.GetRight() * .4f,
|
|
AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::PlayerReloadingWeapon(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
clientPlayers.at(p.GetId())->ReloadingWeapon();
|
|
}
|
|
|
|
void Client::PlayerReloadedWeapon(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
clientPlayers.at(p.GetId())->ReloadedWeapon();
|
|
}
|
|
|
|
void Client::PlayerChangedTool(spades::client::Player &p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
bool isLocal = &p == world->GetLocalPlayer();
|
|
Handle<IAudioChunk> c;
|
|
if (isLocal) {
|
|
// played by ClientPlayer::Update
|
|
return;
|
|
} else {
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Switch.opus");
|
|
}
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, -.3f, .5f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
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();
|
|
Handle<IAudioChunk> c =
|
|
isLocal ? audioDevice->RegisterSound("Sounds/Weapons/RestockLocal.opus")
|
|
: audioDevice->RegisterSound("Sounds/Weapons/Restock.opus");
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, -.3f, .5f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
p.GetEye() + p.GetFront() * 0.5f - p.GetUp() * .3f +
|
|
p.GetRight() * .4f,
|
|
AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::PlayerThrewGrenade(spades::client::Player &p,
|
|
stmp::optional<const Grenade &> g) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
bool isLocal = &p == world->GetLocalPlayer();
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/Throw.opus");
|
|
|
|
if (g && isLocal) {
|
|
net->SendGrenade(*g);
|
|
}
|
|
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, 0.1f, .3f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
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();
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Spade/Miss.opus");
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.2f, -.1f, 0.7f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
p.GetOrigin() + p.GetFront() * 0.8f - p.GetUp() * .2f,
|
|
AudioParam());
|
|
}
|
|
}
|
|
|
|
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 (&p == world->GetLocalPlayer()) {
|
|
localFireVibrationTime = time;
|
|
}
|
|
|
|
if (!IsMuted()) {
|
|
bool isLocal = &p == world->GetLocalPlayer();
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Spade/HitBlock.opus");
|
|
if (isLocal)
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.1f, -.1f, 1.2f),
|
|
AudioParam());
|
|
else
|
|
audioDevice->Play(c.GetPointerOrNull(),
|
|
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()) {
|
|
Handle<IAudioChunk> c;
|
|
switch (SampleRandomInt(0, 2)) {
|
|
case 0:
|
|
c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh1.opus");
|
|
break;
|
|
case 1:
|
|
c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh2.opus");
|
|
break;
|
|
case 2:
|
|
c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh3.opus");
|
|
break;
|
|
}
|
|
AudioParam param;
|
|
param.volume = 4.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), victim.GetEye(), param);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The local player is dead; initialize the look-you-are-dead cam
|
|
if (&victim == world->GetLocalPlayer()) {
|
|
followCameraState.enabled = false;
|
|
|
|
Vector3 v = -victim.GetFront();
|
|
followAndFreeCameraState.yaw = atan2(v.y, v.x);
|
|
followAndFreeCameraState.pitch = 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) {
|
|
auto corp = stmp::make_unique<Corpse>(*renderer, *map, victim);
|
|
|
|
if (&victim == world->GetLocalPlayer())
|
|
lastMyCorpse = corp.get();
|
|
|
|
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 - SampleRandomFloat() * 4.f));
|
|
}
|
|
corp->AddImpulse(victim.GetVelocity() * 32.f);
|
|
corpses.emplace_back(std::move(corp));
|
|
|
|
if (corpses.size() > corpseHardLimit) {
|
|
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 isFriendlyFire = killer.GetTeamId() == victim.GetTeamId();
|
|
if (&killer == &victim)
|
|
isFriendlyFire = false;
|
|
|
|
Weapon &w = killer.GetWeapon(); // only used in case of KillTypeWeapon
|
|
switch (kt) {
|
|
case KillTypeWeapon:
|
|
switch (w.GetWeaponType()) {
|
|
case RIFLE_WEAPON: cause += _Tr("Client", "Rifle"); break;
|
|
case SMG_WEAPON: cause += _Tr("Client", "SMG"); break;
|
|
case SHOTGUN_WEAPON: cause += _Tr("Client", "Shotgun"); break;
|
|
default: SPUnreachable();
|
|
}
|
|
break;
|
|
case KillTypeFall:
|
|
//! A cause of death shown in the kill feed.
|
|
cause += _Tr("Client", "Fall");
|
|
break;
|
|
case KillTypeMelee:
|
|
//! A cause of death shown in the kill feed.
|
|
cause += _Tr("Client", "Melee");
|
|
break;
|
|
case KillTypeGrenade: cause += _Tr("Client", "Grenade"); break;
|
|
case KillTypeHeadshot:
|
|
//! A cause of death shown in the kill feed.
|
|
cause += _Tr("Client", "Headshot");
|
|
break;
|
|
case KillTypeTeamChange:
|
|
//! A cause of death shown in the kill feed.
|
|
cause += _Tr("Client", "Team Change");
|
|
break;
|
|
case KillTypeClassChange:
|
|
//! A cause of death shown in the kill feed.
|
|
cause += _Tr("Client", "Weapon Change");
|
|
break;
|
|
default: cause += "???"; break;
|
|
}
|
|
|
|
s += " [";
|
|
if (isFriendlyFire)
|
|
s += ChatWindow::ColoredMessage(cause, MsgColorFriendlyFire);
|
|
else if (&killer == world->GetLocalPlayer() || &victim == world->GetLocalPlayer())
|
|
s += ChatWindow::ColoredMessage(cause, MsgColorGray);
|
|
else
|
|
s += cause;
|
|
s += "] ";
|
|
|
|
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) {
|
|
stmp::optional<Player &> local = world->GetLocalPlayer();
|
|
if (&killer == local || &victim == local) {
|
|
std::string msg;
|
|
if (&killer == local) {
|
|
if ((int)cg_centerMessage == 2)
|
|
msg = _Tr("Client", "You have killed {0}", victim.GetName());
|
|
} else {
|
|
msg = _Tr("Client", "You were killed by {0}", killer.GetName());
|
|
}
|
|
centerMessageView->AddMessage(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::BulletHitPlayer(spades::client::Player &hurtPlayer, HitType type,
|
|
spades::Vector3 hitPos, spades::client::Player &by,
|
|
std::unique_ptr<IBulletHitScanState> &stateCell) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
SPAssert(type != HitTypeBlock);
|
|
|
|
// don't bleed local player
|
|
if (!IsFirstPerson(GetCameraMode()) || &GetCameraTargetPlayer() != &hurtPlayer) {
|
|
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;
|
|
}
|
|
|
|
// This function gets called for each pellet. We want to play these sounds no more than
|
|
// once for each instance of firing. `BulletHitScanState`, stored in `stateCell`, tells
|
|
// whether we have played each sound for the current firing session.
|
|
struct BulletHitScanState : IBulletHitScanState {
|
|
bool hasPlayedNormalHitSound = false;
|
|
bool hasPlayedHeadshotSound = false;
|
|
};
|
|
|
|
if (!stateCell) {
|
|
stateCell = stmp::make_unique<BulletHitScanState>();
|
|
}
|
|
|
|
auto &hitScanState = dynamic_cast<BulletHitScanState &>(*stateCell);
|
|
|
|
if (!IsMuted() && !hitScanState.hasPlayedNormalHitSound) {
|
|
if (type == HitTypeMelee) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Spade/HitPlayer.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), hitPos, AudioParam());
|
|
} else {
|
|
Handle<IAudioChunk> c;
|
|
switch (SampleRandomInt(0, 2)) {
|
|
case 0:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh1.opus");
|
|
break;
|
|
case 1:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh2.opus");
|
|
break;
|
|
case 2:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Flesh3.opus");
|
|
break;
|
|
}
|
|
AudioParam param;
|
|
param.volume = 4.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), hitPos, param);
|
|
}
|
|
|
|
hitScanState.hasPlayedNormalHitSound = true;
|
|
}
|
|
|
|
if (&by == world->GetLocalPlayer()) {
|
|
net->SendHit(hurtPlayer.GetId(), type);
|
|
|
|
if (type == HitTypeHead && !hitScanState.hasPlayedHeadshotSound) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Feedback/HeadshotFeedback.opus");
|
|
AudioParam param;
|
|
param.volume = cg_hitFeedbackSoundGain;
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), param);
|
|
|
|
hitScanState.hasPlayedHeadshotSound = true;
|
|
}
|
|
|
|
hitFeedbackIconState = 1.f;
|
|
if (hurtPlayer.GetTeamId() == world->GetLocalPlayer()->GetTeamId()) {
|
|
hitFeedbackFriendly = true;
|
|
} else {
|
|
hitFeedbackFriendly = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (blockPos.z == 63) {
|
|
BulletHitWaterSurface(shiftedHitPos);
|
|
if (!IsMuted()) {
|
|
AudioParam param;
|
|
param.volume = 2.f;
|
|
|
|
Handle<IAudioChunk> c;
|
|
|
|
param.pitch = .9f + SampleRandomFloat() * 0.2f;
|
|
switch (SampleRandomInt(0, 3)) {
|
|
case 0:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Water1.opus");
|
|
break;
|
|
case 1:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Water2.opus");
|
|
break;
|
|
case 2:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Water3.opus");
|
|
break;
|
|
case 3:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Water4.opus");
|
|
break;
|
|
}
|
|
audioDevice->Play(c.GetPointerOrNull(), shiftedHitPos, param);
|
|
}
|
|
} else {
|
|
EmitBlockFragments(shiftedHitPos, colV);
|
|
|
|
if (!IsMuted()) {
|
|
AudioParam param;
|
|
param.volume = 2.f;
|
|
|
|
Handle<IAudioChunk> c;
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Block.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), shiftedHitPos, param);
|
|
|
|
param.pitch = .9f + SampleRandomFloat() * 0.2f;
|
|
param.volume = 2.f;
|
|
switch (SampleRandomInt(0, 3)) {
|
|
case 0:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Ricochet1.opus");
|
|
break;
|
|
case 1:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Ricochet2.opus");
|
|
break;
|
|
case 2:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Ricochet3.opus");
|
|
break;
|
|
case 3:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Ricochet4.opus");
|
|
break;
|
|
}
|
|
audioDevice->Play(c.GetPointerOrNull(), shiftedHitPos, param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::AddBulletTracer(spades::client::Player &player, spades::Vector3 muzzlePos,
|
|
spades::Vector3 hitPos) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
// Do not display tracers for bullets fired by the local player
|
|
if (IsFirstPerson(GetCameraMode()) && GetCameraTargetPlayerId() == player.GetId()) {
|
|
return;
|
|
}
|
|
|
|
float vel;
|
|
switch (player.GetWeapon().GetWeaponType()) {
|
|
case RIFLE_WEAPON: vel = 700.f; break;
|
|
case SMG_WEAPON: vel = 360.f; break;
|
|
case SHOTGUN_WEAPON: vel = 500.f; break;
|
|
}
|
|
AddLocalEntity(stmp::make_unique<Tracer>(*this, muzzlePos, hitPos, vel));
|
|
AddLocalEntity(stmp::make_unique<MapViewTracer>(muzzlePos, hitPos, vel));
|
|
}
|
|
|
|
void Client::BlocksFell(std::vector<IntVector3> blocks) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (blocks.empty())
|
|
return;
|
|
AddLocalEntity(stmp::make_unique<FallingBlock>(this, blocks));
|
|
|
|
if (!IsMuted()) {
|
|
|
|
IntVector3 v = blocks[0];
|
|
Vector3 o;
|
|
o.x = v.x;
|
|
o.y = v.y;
|
|
o.z = v.z;
|
|
o += .5f;
|
|
|
|
Handle<IAudioChunk> c = audioDevice->RegisterSound("Sounds/Misc/BlockFall.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), o, AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::GrenadeBounced(const Grenade &g) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (g.GetPosition().z < 63.f) {
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/Bounce.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), AudioParam());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::GrenadeDroppedIntoWater(const Grenade &g) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/DropWater.opus");
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::GrenadeExploded(const Grenade &g) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
bool inWater = g.GetPosition().z > 63.f;
|
|
|
|
if (inWater) {
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplode.opus");
|
|
AudioParam param;
|
|
param.volume = 10.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeFar.opus");
|
|
param.volume = 6.f;
|
|
param.referenceDistance = 10.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeStereo.opus");
|
|
param.volume = 2.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
}
|
|
|
|
GrenadeExplosionUnderwater(g.GetPosition());
|
|
} else {
|
|
|
|
GrenadeExplosion(g.GetPosition());
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c, cs;
|
|
|
|
switch (SampleRandomInt(0, 1)) {
|
|
case 0:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Explode1.opus");
|
|
cs = audioDevice->RegisterSound(
|
|
"Sounds/Weapons/Grenade/ExplodeStereo1.opus");
|
|
break;
|
|
case 1:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Explode2.opus");
|
|
cs = audioDevice->RegisterSound(
|
|
"Sounds/Weapons/Grenade/ExplodeStereo2.opus");
|
|
break;
|
|
}
|
|
|
|
AudioParam param;
|
|
param.volume = 30.f;
|
|
param.referenceDistance = 5.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
param.referenceDistance = 1.f;
|
|
audioDevice->Play(cs.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeFar.opus");
|
|
param.volume = 6.f;
|
|
param.referenceDistance = 40.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeFarStereo.opus");
|
|
param.referenceDistance = 10.f;
|
|
audioDevice->Play(c.GetPointerOrNull(), g.GetPosition(), param);
|
|
|
|
// debri sound
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/Debris.opus");
|
|
param.volume = 5.f;
|
|
param.referenceDistance = 3.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.GetPointerOrNull(), soundPos, param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::LocalPlayerPulledGrenadePin() {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/Fire.opus");
|
|
audioDevice->PlayLocal(c.GetPointerOrNull(), MakeVector3(.4f, -.3f, .5f),
|
|
AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::LocalPlayerBlockAction(spades::IntVector3 v, BlockActionType type) {
|
|
SPADES_MARK_FUNCTION();
|
|
net->SendBlockAction(v, type);
|
|
}
|
|
void Client::LocalPlayerCreatedLineBlock(spades::IntVector3 v1, spades::IntVector3 v2) {
|
|
SPADES_MARK_FUNCTION();
|
|
net->SendBlockLine(v1, v2);
|
|
}
|
|
|
|
void Client::LocalPlayerHurt(HurtType type, bool sourceGiven, spades::Vector3 source) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (sourceGiven) {
|
|
stmp::optional<Player &> p = world->GetLocalPlayer();
|
|
if (!p)
|
|
return;
|
|
Vector3 rel = source - p->GetEye();
|
|
rel.z = 0.f;
|
|
rel = rel.Normalize();
|
|
hurtRingView->Add(rel);
|
|
}
|
|
}
|
|
|
|
void Client::LocalPlayerBuildError(BuildFailureReason reason) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!cg_alerts) {
|
|
PlayAlertSound();
|
|
return;
|
|
}
|
|
|
|
switch (reason) {
|
|
case BuildFailureReason::InsufficientBlocks:
|
|
ShowAlert(_Tr("Client", "Insufficient blocks."), AlertType::Error);
|
|
break;
|
|
case BuildFailureReason::InvalidPosition:
|
|
ShowAlert(_Tr("Client", "You cannot place a block there."), AlertType::Error);
|
|
break;
|
|
}
|
|
}
|
|
} // namespace client
|
|
} // namespace spades
|