1234 lines
36 KiB
C++
1234 lines
36 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");
|
|
|
|
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;
|
|
if (!world->GetLocalPlayer())
|
|
return 0.f;
|
|
|
|
ClientPlayer *p = clientPlayers[(int)world->GetLocalPlayerIndex()];
|
|
if (!p)
|
|
return 0.f;
|
|
return p->GetSprintState();
|
|
}
|
|
|
|
float Client::GetAimDownState() {
|
|
if (!world)
|
|
return 0.f;
|
|
if (!world->GetLocalPlayer())
|
|
return 0.f;
|
|
|
|
ClientPlayer *p = clientPlayers[(int)world->GetLocalPlayerIndex()];
|
|
if (!p)
|
|
return 0.f;
|
|
return p->GetAimDownState();
|
|
}
|
|
|
|
#pragma mark - World Actions
|
|
/** Captures the color of the block player is looking at. */
|
|
void Client::CaptureColor() {
|
|
if (!world)
|
|
return;
|
|
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, MakeVector3(.4f, -.3f, .5f), AudioParam());
|
|
}
|
|
}
|
|
|
|
#pragma mark - World Update
|
|
|
|
void Client::UpdateWorld(float dt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
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 (size_t i = 0; i < clientPlayers.size(); i++) {
|
|
if (clientPlayers[i]) {
|
|
clientPlayers[i]->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) {}
|
|
virtual void Run() {
|
|
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()) {
|
|
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();
|
|
|
|
Vector3 lastPos = followPos;
|
|
followVel *= powf(.3f, dt);
|
|
followPos += followVel * dt;
|
|
|
|
if (followPos.x < 0.f) {
|
|
followVel.x = fabsf(followVel.x) * 0.2f;
|
|
followPos = lastPos + followVel * dt;
|
|
}
|
|
if (followPos.y < 0.f) {
|
|
followVel.y = fabsf(followVel.y) * 0.2f;
|
|
followPos = lastPos + followVel * dt;
|
|
}
|
|
if (followPos.x > (float)GetWorld()->GetMap()->Width()) {
|
|
followVel.x = fabsf(followVel.x) * -0.2f;
|
|
followPos = lastPos + followVel * dt;
|
|
}
|
|
if (followPos.y > (float)GetWorld()->GetMap()->Height()) {
|
|
followVel.y = fabsf(followVel.y) * -0.2f;
|
|
followPos = lastPos + followVel * dt;
|
|
}
|
|
|
|
GameMap::RayCastResult minResult;
|
|
float minDist = 1.e+10f;
|
|
Vector3 minShift;
|
|
|
|
// check collision
|
|
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;
|
|
|
|
// bounce
|
|
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);
|
|
}
|
|
|
|
// acceleration
|
|
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);
|
|
}
|
|
|
|
/** Handles movement of joined local player. */
|
|
void Client::UpdateLocalPlayer(float dt) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
auto *player = world->GetLocalPlayer();
|
|
auto clientPlayer = clientPlayers[world->GetLocalPlayerIndex()];
|
|
|
|
PlayerInput inp = playerInput;
|
|
WeaponInput winp = weapInput;
|
|
|
|
Vector3 velocity = player->GetVelocty();
|
|
Vector3 horizontalVelocity{velocity.x, velocity.y, 0.0f};
|
|
|
|
if (horizontalVelocity.GetLength() < 0.1f) {
|
|
inp.sprint = false;
|
|
}
|
|
|
|
// weapon/tools are disabled while/soon after sprinting
|
|
if (GetSprintState() > 0.001f || inp.sprint) {
|
|
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;
|
|
}
|
|
|
|
// weapon/tools are disabled while changing tools
|
|
if (clientPlayer->IsChangingTool()) {
|
|
winp.primary = false;
|
|
winp.secondary = 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 = std::move(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 ((mt_engine_client() >> 3) & 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, 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 = GetRandom() * (2.f * static_cast<float>(M_PI));
|
|
spr.scale = .2f + GetRandom() * GetRandom() * .7f;
|
|
spr.horzShift = GetRandom();
|
|
spr.strength = .3f + GetRandom() * .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]->Invalidate();
|
|
clientPlayers[id] = nullptr;
|
|
}
|
|
|
|
Player *p = world->GetPlayer(id);
|
|
if (p)
|
|
clientPlayers[id].Set(new ClientPlayer(p, this), false);
|
|
}
|
|
|
|
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, 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, p->GetOrigin(), AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::PlayerMadeFootstep(spades::client::Player *p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
const char *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"};
|
|
const char *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",
|
|
};
|
|
const char *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(wsnds[(mt_engine_client() >> 8) % 8])
|
|
: audioDevice->RegisterSound(snds[(mt_engine_client() >> 8) % 8]);
|
|
audioDevice->Play(c, p->GetOrigin(), AudioParam());
|
|
if (sprinting && !p->GetWade()) {
|
|
AudioParam param;
|
|
param.volume *= clientPlayers[p->GetId()]->GetSprintState();
|
|
c = audioDevice->RegisterSound(rsnds[(mt_engine_client() >> 8) % 12]);
|
|
audioDevice->Play(c, p->GetOrigin(), param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::PlayerFiredWeapon(spades::client::Player *p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (p == world->GetLocalPlayer()) {
|
|
localFireVibrationTime = time;
|
|
}
|
|
|
|
clientPlayers[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, 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();
|
|
|
|
clientPlayers[p->GetId()]->ReloadingWeapon();
|
|
}
|
|
|
|
void Client::PlayerReloadedWeapon(spades::client::Player *p) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
clientPlayers[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, 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();
|
|
Handle<IAudioChunk> c =
|
|
isLocal ? audioDevice->RegisterSound("Sounds/Weapons/RestockLocal.opus")
|
|
: audioDevice->RegisterSound("Sounds/Weapons/Restock.opus");
|
|
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();
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/Throw.opus");
|
|
|
|
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();
|
|
Handle<IAudioChunk> c = audioDevice->RegisterSound("Sounds/Weapons/Spade/Miss.opus");
|
|
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::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, 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()) {
|
|
Handle<IAudioChunk> c;
|
|
switch (mt_engine_client() % 3) {
|
|
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, victim->GetEye(), param);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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() * 32.f);
|
|
corpses.emplace_back(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 ? killer->GetWeapon() : nullptr; // only used in case of KillTypeWeapon
|
|
switch (kt) {
|
|
case KillTypeWeapon:
|
|
switch (w ? w->GetWeaponType() : RIFLE_WEAPON) {
|
|
case RIFLE_WEAPON: cause += _Tr("Client", "Rifle"); break;
|
|
case SMG_WEAPON: cause += _Tr("Client", "SMG"); break;
|
|
case SHOTGUN_WEAPON: cause += _Tr("Client", "Shotgun"); break;
|
|
}
|
|
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) {
|
|
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) {
|
|
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) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Spade/HitPlayer.opus");
|
|
audioDevice->Play(c, hitPos, AudioParam());
|
|
} else {
|
|
Handle<IAudioChunk> c;
|
|
switch ((mt_engine_client() >> 6) % 3) {
|
|
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, hitPos, param);
|
|
}
|
|
}
|
|
|
|
if (by == world->GetLocalPlayer() && hurtPlayer) {
|
|
net->SendHit(hurtPlayer->GetId(), type);
|
|
|
|
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 + GetRandom() * 0.2f;
|
|
switch ((mt_engine_client() >> 6) & 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, shiftedHitPos, param);
|
|
}
|
|
} else {
|
|
EmitBlockFragments(shiftedHitPos, colV);
|
|
|
|
if (!IsMuted()) {
|
|
AudioParam param;
|
|
param.volume = 2.f;
|
|
|
|
Handle<IAudioChunk> c;
|
|
|
|
switch ((mt_engine_client() >> 6) & 3) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Impacts/Block.opus");
|
|
break;
|
|
}
|
|
audioDevice->Play(c, shiftedHitPos, param);
|
|
|
|
param.pitch = .9f + GetRandom() * 0.2f;
|
|
param.volume = 2.f;
|
|
switch ((mt_engine_client() >> 6) & 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, shiftedHitPos, param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::AddBulletTracer(spades::client::Player *player, spades::Vector3 muzzlePos,
|
|
spades::Vector3 hitPos) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (IsFollowing() && followingPlayerId == 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: return;
|
|
}
|
|
AddLocalEntity(new Tracer(this, muzzlePos, hitPos, vel));
|
|
AddLocalEntity(new MapViewTracer(muzzlePos, hitPos, vel));
|
|
}
|
|
|
|
void Client::BlocksFell(std::vector<IntVector3> blocks) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
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;
|
|
|
|
Handle<IAudioChunk> c = audioDevice->RegisterSound("Sounds/Misc/BlockFall.opus");
|
|
audioDevice->Play(c, o, AudioParam());
|
|
}
|
|
}
|
|
|
|
void Client::GrenadeBounced(spades::client::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, g->GetPosition(), AudioParam());
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::GrenadeDroppedIntoWater(spades::client::Grenade *g) {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/DropWater.opus");
|
|
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()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplode.opus");
|
|
AudioParam param;
|
|
param.volume = 10.f;
|
|
audioDevice->Play(c, g->GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeFar.opus");
|
|
param.volume = 6.f;
|
|
param.referenceDistance = 10.f;
|
|
audioDevice->Play(c, g->GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/WaterExplodeStereo.opus");
|
|
param.volume = 2.f;
|
|
audioDevice->Play(c, g->GetPosition(), param);
|
|
}
|
|
|
|
GrenadeExplosionUnderwater(g->GetPosition());
|
|
} else {
|
|
|
|
GrenadeExplosion(g->GetPosition());
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c, cs;
|
|
|
|
switch ((mt_engine_client() >> 8) & 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, g->GetPosition(), param);
|
|
|
|
param.referenceDistance = 1.f;
|
|
audioDevice->Play(cs, g->GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeFar.opus");
|
|
param.volume = 6.f;
|
|
param.referenceDistance = 40.f;
|
|
audioDevice->Play(c, g->GetPosition(), param);
|
|
|
|
c = audioDevice->RegisterSound("Sounds/Weapons/Grenade/ExplodeFarStereo.opus");
|
|
param.referenceDistance = 10.f;
|
|
audioDevice->Play(c, 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, soundPos, param);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::LocalPlayerPulledGrenadePin() {
|
|
SPADES_MARK_FUNCTION();
|
|
|
|
if (!IsMuted()) {
|
|
Handle<IAudioChunk> c =
|
|
audioDevice->RegisterSound("Sounds/Weapons/Grenade/Fire.opus");
|
|
audioDevice->PlayLocal(c, 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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|