Rewrite the camera control logic

- Fixes #664
- Fixes #520
This commit is contained in:
yvt 2017-12-05 15:42:24 +09:00
parent fdcb77789e
commit 660066fc2f
14 changed files with 735 additions and 511 deletions

View File

@ -393,6 +393,7 @@
E82E67F318EA7EAB004DBA18 /* font-unifont.pak */ = {isa = PBXFileReference; lastKnownFileType = file; name = "font-unifont.pak"; path = "Resources/font-unifont.pak"; sourceTree = "<group>"; };
E82E680318EA7F60004DBA18 /* libysrspades.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libysrspades.dylib; path = lib/libysrspades.dylib; sourceTree = "<group>"; };
E82E680618EA9502004DBA18 /* XSpades.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = XSpades.entitlements; path = XSpades/XSpades.entitlements; sourceTree = "<group>"; };
E831D8661FD65BD5003C0D97 /* ClientCameraMode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCameraMode.h; sourceTree = "<group>"; };
E834F54E17942C43004EBE88 /* Grenade.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Grenade.cpp; sourceTree = "<group>"; };
E834F54F17942C43004EBE88 /* Grenade.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Grenade.h; sourceTree = "<group>"; };
E834F55117944778004EBE88 /* NetClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NetClient.cpp; sourceTree = "<group>"; };
@ -1613,6 +1614,7 @@
E8FE749518CC6F2900291338 /* Client_Scene.cpp */,
E82E66B218E9A35C004DBA18 /* Client_FPSCounter.cpp */,
E8CF03C2178EE6D8000683D4 /* Client.h */,
E831D8661FD65BD5003C0D97 /* ClientCameraMode.h */,
E834F56D1797D92F004EBE88 /* ChatWindow.cpp */,
E834F56E1797D932004EBE88 /* ChatWindow.h */,
E8E0AF92179942DB00C6B5A9 /* Corpse.cpp */,

View File

@ -103,11 +103,6 @@ namespace spades {
targetFocalLength(20.f),
autoFocusEnabled(true),
// FIXME: preferences?
followYaw(0.f),
followPitch(0.f),
inGameLimbo(false),
fontManager(fontManager),
alertDisappearTime(-10000.f),
@ -703,7 +698,7 @@ namespace spades {
myTeam = world->GetLocalPlayer()->GetTeamId();
}
int nextId = followingPlayerId;
int nextId = followedPlayerId;
do {
reverse ? --nextId : ++nextId;
if (nextId >= static_cast<int>(world->GetNumPlayerSlots()))
@ -727,23 +722,12 @@ namespace spades {
}
break;
} while (nextId != followingPlayerId);
} while (nextId != followedPlayerId);
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;
followedPlayerId = nextId;
if (followedPlayerId == world->GetLocalPlayerIndex()) {
followCameraState.enabled = false;
}
}
}
}

View File

@ -35,6 +35,7 @@
#include <Core/ServerAddress.h>
#include <Core/Stopwatch.h>
#include <Gui/View.h>
#include "ClientCameraMode.h"
namespace spades {
class IStream;
@ -144,7 +145,6 @@ namespace spades {
KeypadInput keypadInput;
Player::ToolType lastTool;
bool hasLastTool;
bool firstPersonSpectate;
Vector3 lastFront;
float lastPosSentTime;
int lastHealth;
@ -160,6 +160,7 @@ namespace spades {
float strength;
};
std::vector<HurtSprite> hurtSprites;
float GetAimDownState();
float GetSprintState();
@ -205,17 +206,78 @@ namespace spades {
float targetFocalLength;
bool autoFocusEnabled;
// when dead...
/** Following player ID, which may be local player itself */
int followingPlayerId;
float followYaw, followPitch;
/** only for when spectating */
Vector3 followPos;
/** only for when spectating */
Vector3 followVel;
// Spectator camera control
/** The state of the following camera used for spectating. */
struct {
bool firstPerson = true;
/** Controls whether the follow camera is enabled. */
bool enabled = false;
} followCameraState;
/** The state of the free floating camera used for spectating. */
struct {
/** The temporally smoothed position (I guess). */
Vector3 position {0.0f, 0.0f, 0.0f};
/** The temporally smoothed velocity (I guess). */
Vector3 velocity {0.0f, 0.0f, 0.0f};
} freeCameraState;
/**
* The state shared by both of the third-person and free-floating cameras.
*
* Note: These values are not used in the `cg_thirdperson` mode.
*/
struct {
/** The yaw angle. */
float yaw = 0.0f;
/** The pitch angle. */
float pitch = 0.0f;
} followAndFreeCameraState;
/**
* The ID of the player being followed (in a spectator mode, or when the local player is
* dead). Must be valid as long as the follow cam is enabled.
*
* Must *not* specify a local player.
*/
int followedPlayerId;
/**
* Chooses the next player to follow and assigns it to `this->followingPlayerId`.
* If the next player is the local player, disables the follow cam.
*/
void FollowNextPlayer(bool reverse);
/** @return true following is activated (and followingPlayerId should be used) */
bool IsFollowing();
/**
* Retrieves the current camera mode.
*/
ClientCameraMode GetCameraMode();
/**
* Retrieves the target player ID of the current camera mode (as returned by
* `GetCameraMode`).
*
* Throws an exception if the current camera mode does not have a player in concern.
*/
int GetCameraTargetPlayerId();
/**
* Retrieves the target player of the current camera mode (as returned by
* `GetCameraMode`).
*
* Throws an exception if the current camera mode does not have a player in concern.
*/
Player &GetCameraTargetPlayer();
/**
* Calculate the zoom value incorporating the effect of ADS for a first-person view.
*
* The camera mode must be first-person.
*/
float GetAimDownZoomScale();
bool inGameLimbo;
@ -301,9 +363,6 @@ namespace spades {
void DrawCTFObjects();
void DrawTCObjects();
float GetAimDownZoomScale();
bool ShouldRenderInThirdPersonView();
Player *GetViewedPlayer();
SceneDefinition CreateSceneDefinition();
std::string ScreenShotPath();

View File

@ -0,0 +1,125 @@
/*
Copyright (c) 2017 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Core/Exception.h>
namespace spades {
namespace client {
enum class ClientCameraMode {
/**
* The world is not loaded.
*/
None,
/**
* That specific camera position (you know what I mean).
*/
NotJoined,
/**
* First-person view of the local player.
*/
FirstPersonLocal,
/**
* First-person view of a non-local player.
*/
FirstPersonFollow,
/**
* Third-person view of the local player (only allowed in a limited situation where
* it is enabled by `cg_thirdperson`, or the player is dead).
*/
ThirdPersonLocal,
/**
* Third-person view of a non-lcoal player.
*/
ThirdPersonFollow,
/**
* Free floating camera view, which is usable only when the user has joined a server
* as a spectator.
*/
Free,
};
inline bool IsThirdPerson(ClientCameraMode mode) {
switch (mode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::FirstPersonFollow:
case ClientCameraMode::Free:
return false;
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::ThirdPersonFollow:
return true;
}
SPUnreachable();
}
inline bool IsFirstPerson(ClientCameraMode mode) {
switch (mode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::ThirdPersonFollow:
case ClientCameraMode::Free:
return false;
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::FirstPersonFollow:
return true;
}
SPUnreachable();
}
inline bool HasTargetPlayer(ClientCameraMode mode) {
switch (mode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::Free:
return false;
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::ThirdPersonFollow:
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::FirstPersonFollow:
return true;
}
SPUnreachable();
}
inline bool FollowsNonLocalPlayer(ClientCameraMode mode) {
switch (mode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::Free:
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::FirstPersonLocal:
return false;
case ClientCameraMode::ThirdPersonFollow:
case ClientCameraMode::FirstPersonFollow:
return true;
}
SPUnreachable();
}
}
}

View File

@ -1042,11 +1042,8 @@ namespace spades {
}
bool ClientPlayer::ShouldRenderInThirdPersonView() {
// the player from whom's perspective the game is seen
if (player != client->GetViewedPlayer()) return true;
return client->ShouldRenderInThirdPersonView();
// The player from whom's perspective the game is
return !IsFirstPerson(client->GetCameraMode()) || player != &client->GetCameraTargetPlayer();
}
struct ClientPlayer::AmbienceInfo {

View File

@ -756,19 +756,15 @@ namespace spades {
DrawSpectateHUD();
}
if (IsFollowing() && !cg_hideHud) {
if (followingPlayerId == p->GetId()) {
// just spectating
} else {
font = fontManager->GetGuiFont();
std::string msg = _Tr("Client", "Following {0}",
world->GetPlayerPersistent(followingPlayerId).name);
Vector2 size = font->Measure(msg);
Vector2 pos = MakeVector2(scrWidth - 8.f, 256.f + 32.f);
pos.x -= size.x;
font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1),
MakeVector4(0, 0, 0, 0.5));
}
if (FollowsNonLocalPlayer(GetCameraMode()) && !cg_hideHud) {
font = fontManager->GetGuiFont();
std::string msg = _Tr("Client", "Following {0}",
world->GetPlayerPersistent(GetCameraTargetPlayerId()).name);
Vector2 size = font->Measure(msg);
Vector2 pos = MakeVector2(scrWidth - 8.f, 256.f + 32.f);
pos.x -= size.x;
font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1),
MakeVector4(0, 0, 0, 0.5));
}
if (!cg_hideHud) {

View File

@ -120,60 +120,80 @@ namespace spades {
return;
}
if (IsFollowing()) {
SPAssert(world != nullptr);
auto cameraMode = GetCameraMode();
x = -x;
if (!cg_invertMouseY)
y = -y;
switch (cameraMode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::FirstPersonFollow:
// No-op
break;
followYaw -= x * 0.003f;
followPitch -= y * 0.003f;
if (followPitch < -M_PI * .45f)
followPitch = -static_cast<float>(M_PI) * .45f;
if (followPitch > M_PI * .45f)
followPitch = static_cast<float>(M_PI) * .45f;
followYaw = fmodf(followYaw, static_cast<float>(M_PI) * 2.f);
} else if (world && world->GetLocalPlayer()) {
Player *p = world->GetLocalPlayer();
float aimDownState = GetAimDownState();
if (p->IsAlive()) {
x /= GetAimDownZoomScale();
y /= GetAimDownZoomScale();
float rad = x * x + y * y;
if (rad > 0.f) {
if ((float)cg_mouseExpPower < 0.001f || isnan((float)cg_mouseExpPower)) {
SPLog("Invalid cg_mouseExpPower value, resetting to 1.0");
cg_mouseExpPower = 1.f;
}
float factor = renderer->ScreenWidth() * .1f;
factor *= factor;
rad /= factor;
rad = powf(rad, (float)cg_mouseExpPower * 0.5f - 0.5f);
// shouldn't happen...
if (isnan(rad))
rad = 1.f;
x *= rad;
y *= rad;
}
if (aimDownState > 0.f) {
float scale = cg_zoomedMouseSensScale;
scale = powf(scale, aimDownState);
x *= scale;
y *= scale;
}
x *= (float)cg_mouseSensitivity;
y *= (float)cg_mouseSensitivity;
if (cg_invertMouseY)
case ClientCameraMode::Free:
case ClientCameraMode::ThirdPersonFollow: {
// Move the third-person or free-floating camera
x = -x;
if (!cg_invertMouseY)
y = -y;
p->Turn(x * 0.003f, y * 0.003f);
auto &state = followAndFreeCameraState;
state.yaw -= x * 0.003f;
state.pitch -= y * 0.003f;
if (state.pitch < -M_PI * .45f)
state.pitch = -static_cast<float>(M_PI) * .45f;
if (state.pitch > M_PI * .45f)
state.pitch = static_cast<float>(M_PI) * .45f;
state.yaw = fmodf(state.yaw, static_cast<float>(M_PI) * 2.f);
break;
}
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::ThirdPersonLocal: {
SPAssert(world);
SPAssert(world->GetLocalPlayer());
Player *p = world->GetLocalPlayer();
if (p->IsAlive()) {
float aimDownState = GetAimDownState();
x /= GetAimDownZoomScale();
y /= GetAimDownZoomScale();
float rad = x * x + y * y;
if (rad > 0.f) {
if ((float)cg_mouseExpPower < 0.001f || isnan((float)cg_mouseExpPower)) {
SPLog("Invalid cg_mouseExpPower value, resetting to 1.0");
cg_mouseExpPower = 1.f;
}
float factor = renderer->ScreenWidth() * .1f;
factor *= factor;
rad /= factor;
rad = powf(rad, (float)cg_mouseExpPower * 0.5f - 0.5f);
// shouldn't happen...
if (isnan(rad))
rad = 1.f;
x *= rad;
y *= rad;
}
if (aimDownState > 0.f) {
float scale = cg_zoomedMouseSensScale;
scale = powf(scale, aimDownState);
x *= scale;
y *= scale;
}
x *= (float)cg_mouseSensitivity;
y *= (float)cg_mouseSensitivity;
if (cg_invertMouseY)
y = -y;
p->Turn(x * 0.003f, y * 0.003f);
}
break;
}
}
}
@ -287,27 +307,60 @@ namespace spades {
}
return;
}
if (IsFollowing()) {
if (CheckKey(cg_keyAttack, name)) {
if (down) {
if (world->GetLocalPlayer()->GetTeamId() >= 2 ||
time > lastAliveTime + 1.3f)
FollowNextPlayer(false);
auto cameraMode = GetCameraMode();
switch (cameraMode) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::FirstPersonLocal:
break;
case ClientCameraMode::ThirdPersonLocal:
if (world->GetLocalPlayer()->IsAlive()) {
break;
}
return;
} else if (CheckKey(cg_keyAltAttack, name)) {
if (down) {
if (world->GetLocalPlayer()->GetTeamId() >= 2) {
// spectating
followingPlayerId = world->GetLocalPlayerIndex();
} else if (time > lastAliveTime + 1.3f) {
// dead
FollowNextPlayer(true);
case ClientCameraMode::FirstPersonFollow:
case ClientCameraMode::ThirdPersonFollow:
case ClientCameraMode::Free:
if (CheckKey(cg_keyAttack, name)) {
if (down) {
if (cameraMode == ClientCameraMode::Free ||
cameraMode == ClientCameraMode::ThirdPersonLocal) {
// Start with the local player
followedPlayerId = world->GetLocalPlayerIndex();
}
if (world->GetLocalPlayer()->IsSpectator() ||
time > lastAliveTime + 1.3f) {
FollowNextPlayer(false);
followCameraState.enabled = true;
}
}
return;
} else if (CheckKey(cg_keyAltAttack, name)) {
if (down) {
if (cameraMode == ClientCameraMode::Free ||
cameraMode == ClientCameraMode::ThirdPersonLocal) {
// Start with the local player
followedPlayerId = world->GetLocalPlayerIndex();
}
if (world->GetLocalPlayer()->IsSpectator()) {
// Unfollow
followCameraState.enabled = false;
} else if (time > lastAliveTime + 1.3f) {
FollowNextPlayer(true);
followCameraState.enabled = true;
}
}
return;
} else if (CheckKey(cg_keyJump, name)) {
if (down) {
followCameraState.firstPerson = !followCameraState.firstPerson;
}
return;
}
return;
}
break;
}
if (world->GetLocalPlayer()) {
Player *p = world->GetLocalPlayer();
@ -367,9 +420,6 @@ namespace spades {
} else if (CheckKey(cg_keySneak, name)) {
playerInput.sneak = down;
} else if (CheckKey(cg_keyJump, name)) {
if (down && IsFollowing()) {
firstPersonSpectate = !firstPersonSpectate;
}
playerInput.jump = down;
} else if (CheckKey(cg_keyAttack, name)) {
weapInput.primary = down;

View File

@ -112,22 +112,20 @@ namespace spades {
}
Player *Client::HotTrackedPlayer(hitTag_t *hitFlag) {
if (!world)
if (!IsFirstPerson(GetCameraMode()))
return nullptr;
Player *p = world->GetLocalPlayer();
if (!p || !p->IsAlive())
return nullptr;
if (ShouldRenderInThirdPersonView())
return nullptr;
Vector3 origin = p->GetEye();
Vector3 dir = p->GetFront();
World::WeaponRayCastResult result = world->WeaponRayCast(origin, dir, p);
auto &p = GetCameraTargetPlayer();
Vector3 origin = p.GetEye();
Vector3 dir = p.GetFront();
World::WeaponRayCastResult result = world->WeaponRayCast(origin, dir, &p);
if (result.hit == false || result.player == nullptr)
return nullptr;
// don't hot track enemies (non-spectator only)
if (result.player->GetTeamId() != p->GetTeamId() && p->GetTeamId() < 2)
if (result.player->GetTeamId() != p.GetTeamId() && p.GetTeamId() < 2)
return nullptr;
if (hitFlag) {
*hitFlag = result.hitFlag;

View File

@ -51,7 +51,7 @@ namespace spades {
#pragma mark - Server Packet Handlers
void Client::LocalPlayerCreated() {
followPos = world->GetLocalPlayer()->GetEye();
freeCameraState.position = world->GetLocalPlayer()->GetEye();
weapInput = WeaponInput();
playerInput = PlayerInput();
keypadInput = KeypadInput();
@ -60,12 +60,12 @@ namespace spades {
}
void Client::JoinedGame() {
// note: local player doesn't exist yet now
// Note: A local player doesn't exist yet
// tune for spectate mode
followingPlayerId = world->GetLocalPlayerIndex();
followPos = MakeVector3(256, 256, 30);
followVel = MakeVector3(0, 0, 0);
// Prepare the spectate mode
followCameraState.enabled = false;
freeCameraState.position = MakeVector3(256, 256, 30);
freeCameraState.velocity = MakeVector3(0, 0, 0);
}
void Client::PlayerCreatedBlock(spades::client::Player *p) {
@ -233,6 +233,17 @@ namespace spades {
}
void Client::PlayerLeaving(spades::client::Player *p) {
// Choose the next player if a follow cam is active on this player
if (FollowsNonLocalPlayer(GetCameraMode()) && &GetCameraTargetPlayer() == p) {
FollowNextPlayer(false);
// Still unable to find a substitute?
if (&GetCameraTargetPlayer() == p) {
// Turn off the follow cam mode
followCameraState.enabled = false;
}
}
{
std::string msg;
msg = _Tr("Client", "Player {0} has left",

View File

@ -55,34 +55,62 @@ namespace spades {
#pragma mark - Drawing
bool Client::ShouldRenderInThirdPersonView() {
if (IsFollowing()){
if (!GetViewedPlayer()->IsAlive()){
return true;
ClientCameraMode Client::GetCameraMode() {
if (!world) {
return ClientCameraMode::None;
}
Player *p = world->GetLocalPlayer();
if (!p) {
return ClientCameraMode::NotJoined;
}
if (p->IsAlive() && !p->IsSpectator()) {
// There exists an alive (non-spectator) local player
if ((int)cg_thirdperson != 0 && world->GetNumPlayers() <= 1) {
return ClientCameraMode::ThirdPersonLocal;
}
return ClientCameraMode::FirstPersonLocal;
} else {
// The local player is dead or a spectator
if (followCameraState.enabled) {
if (followCameraState.firstPerson) {
return ClientCameraMode::FirstPersonFollow;
} else {
return ClientCameraMode::ThirdPersonFollow;
}
} else {
if (p->IsSpectator()) {
return ClientCameraMode::Free;
} else {
// Look at your own cadaver!
return ClientCameraMode::ThirdPersonLocal;
}
}
return !firstPersonSpectate;
}
if (world && world->GetLocalPlayer()) {
if (!world->GetLocalPlayer()->IsAlive())
return true;
}
if ((int)cg_thirdperson != 0 && world->GetNumPlayers() <= 1) {
return true;
}
return false;
}
Player *Client::GetViewedPlayer() {
// what happens if we are in free mode?
// doesn't matter for the current code, but keep this in mind
if (IsFollowing()){
return world->GetPlayer(followingPlayerId);
}
else {
return world->GetLocalPlayer();
int Client::GetCameraTargetPlayerId() {
switch (GetCameraMode()) {
case ClientCameraMode::None:
case ClientCameraMode::NotJoined:
case ClientCameraMode::Free:
SPUnreachable();
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::ThirdPersonLocal:
SPAssert(world);
return world->GetLocalPlayerIndex();
case ClientCameraMode::FirstPersonFollow:
case ClientCameraMode::ThirdPersonFollow:
return followedPlayerId;
}
SPUnreachable();
}
Player &Client::GetCameraTargetPlayer() {
Player *p = world->GetPlayer(GetCameraTargetPlayerId());
SPAssert(p);
return *p;
}
float Client::GetLocalFireVibration() {
@ -95,30 +123,17 @@ namespace spades {
}
float Client::GetAimDownZoomScale() {
if (!world) {
Player &player = GetCameraTargetPlayer();
if (!player.IsToolWeapon() ||
!player.IsAlive()) {
return 1.f;
}
if (ShouldRenderInThirdPersonView()) {
return 1.f;
}
Player* player = GetViewedPlayer();
if (
!player ||
!player->IsToolWeapon() ||
!player->IsAlive()
) {
return 1.f;
}
ClientPlayer* clientPlayer = clientPlayers[player->GetId()];
if (!clientPlayer) {
return 1.f;
}
ClientPlayer* clientPlayer = clientPlayers[player.GetId()];
SPAssert(clientPlayer);
float delta = .8f;
switch (player->GetWeapon()->GetWeaponType()) {
switch (player.GetWeapon()->GetWeaponType()) {
case SMG_WEAPON: delta = .8f; break;
case RIFLE_WEAPON: delta = 1.4f; break;
case SHOTGUN_WEAPON: delta = .4f; break;
@ -153,49 +168,143 @@ namespace spades {
renderer->SetFogColor(
MakeVector3(fogColor.x / 255.f, fogColor.y / 255.f, fogColor.z / 255.f));
Player *player = world->GetLocalPlayer();
def.blurVignette = 0.0f;
def.blurVignette = .0f;
float roll = 0.f;
float scale = 1.f;
float vibPitch = 0.f;
float vibYaw = 0.f;
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(false);
if ((limit--) <= 0) {
break;
switch (GetCameraMode()) {
case ClientCameraMode::None:
SPUnreachable();
case ClientCameraMode::NotJoined:
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 = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) * 2.f;
def.zNear = 0.05f;
def.skipWorld = false;
break;
case ClientCameraMode::FirstPersonLocal:
case ClientCameraMode::FirstPersonFollow: {
Player &player = GetCameraTargetPlayer();
Matrix4 eyeMatrix = clientPlayers[player.GetId()]->GetEyeMatrix();
def.viewOrigin = eyeMatrix.GetOrigin();
def.viewAxis[0] = -eyeMatrix.GetAxis(0);
def.viewAxis[1] = -eyeMatrix.GetAxis(2);
def.viewAxis[2] = eyeMatrix.GetAxis(1);
if (shakeLevel >= 1) {
float localFireVibration = GetLocalFireVibration();
localFireVibration *= localFireVibration;
if (player.GetTool() == Player::ToolSpade) {
localFireVibration *= 0.4f;
}
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;
def.radialBlur += localFireVibration * 0.05f;
// sprint bob
{
float sp = SmoothStep(GetSprintState());
vibYaw += sinf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.01f * sp;
roll -= sinf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.005f * (sp);
float p = cosf(player.GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f);
p = p * p;
p *= p;
p *= p;
vibPitch += p * 0.01f * sp;
if (shakeLevel >= 2) {
vibYaw += coherentNoiseSamplers[0].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.005f * sp;
vibPitch += coherentNoiseSamplers[1].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.01f * sp;
roll += coherentNoiseSamplers[2].Sample(
player.GetWalkAnimationProgress() * 2.5f) *
0.008f * sp;
scale += sp * 0.1f;
}
}
}
def.fovY = (float)cg_fov * static_cast<float>(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;
// DoF when doing ADS
float aimDownState = GetAimDownState();
float per = aimDownState;
per *= per * per;
def.depthOfFieldFocalLength = per * 13.f + .054f;
// Hurt effect
{
float wTime = world->GetTime();
if (wTime < lastHurtTime + .15f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .15f;
per *= .5f - player.GetHealth() / 100.f * .3f;
def.blurVignette += per * 6.f;
}
if (wTime < lastHurtTime + .2f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .2f;
per *= .5f - player.GetHealth() / 100.f * .3f;
def.saturation *= std::max(0.f, 1.f - per * 4.f);
}
}
// Apply ADS zoom
scale /= GetAimDownZoomScale();
// Update initial floating camera pos
freeCameraState.position = def.viewOrigin;
freeCameraState.velocity = MakeVector3(0, 0, 0);
break;
}
player = world->GetPlayer(followingPlayerId);
}
if (player) {
float roll = 0.f;
float scale = 1.f;
float vibPitch = 0.f;
float vibYaw = 0.f;
scale /= GetAimDownZoomScale();
case ClientCameraMode::ThirdPersonLocal:
case ClientCameraMode::ThirdPersonFollow: {
Player &player = GetCameraTargetPlayer();
Vector3 center = player.GetEye();
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()) {
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))) {
(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))) {
(int)floorf(center.y), (int)floorf(z))) {
center.z = z;
break;
} else {
@ -205,31 +314,21 @@ namespace spades {
}
float distance = 5.f;
if (player == world->GetLocalPlayer() &&
world->GetLocalPlayer()->GetTeamId() < 2 &&
!world->GetLocalPlayer()->IsAlive()) {
if (&player == world->GetLocalPlayer() &&
world->GetLocalPlayer()->GetTeamId() < 2 &&
!world->GetLocalPlayer()->IsAlive()) {
// deathcam.
float elapsedTime = time - lastAliveTime;
distance -= 3.f * expf(-elapsedTime * 1.f);
}
auto &state = followAndFreeCameraState;
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;
eye.x += cosf(state.yaw) * cosf(state.pitch) * distance;
eye.y += sinf(state.yaw) * cosf(state.pitch) * distance;
eye.z -= sinf(state.pitch) * 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
// Prevent the camera from being behind a wall
GameMap::RayCastResult result;
result = map->CastRay2(center, (eye - center).Normalize(), 256);
if (result.hit) {
@ -245,39 +344,32 @@ namespace spades {
Vector3 front = center - eye;
front = front.Normalize();
if (ShouldRenderInThirdPersonView()) {
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;
} else {
def.viewOrigin = player->GetEye();
def.viewAxis[0] = player->GetRight();
def.viewAxis[1] = player->GetUp();
def.viewAxis[2] = player->GetFront();
}
Vector3 up = MakeVector3(0, 0, -1);
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.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
renderer->ScreenHeight()) * 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) {
// Update initial floating camera pos
freeCameraState.position = def.viewOrigin;
freeCameraState.velocity = MakeVector3(0, 0, 0);
break;
}
case ClientCameraMode::Free: {
// spectator view (noclip view)
Vector3 center = followPos;
Vector3 center = freeCameraState.position;
Vector3 front;
Vector3 up = {0, 0, -1};
front.x = -cosf(followYaw) * cosf(followPitch);
front.y = -sinf(followYaw) * cosf(followPitch);
front.z = sinf(followPitch);
auto &state = followAndFreeCameraState;
front.x = -cosf(state.yaw) * cosf(state.pitch);
front.y = -sinf(state.yaw) * cosf(state.pitch);
front.z = sinf(state.pitch);
def.viewOrigin = center;
def.viewAxis[0] = -Vector3::Cross(up, front).Normalize();
@ -286,180 +378,98 @@ namespace spades {
def.fovY = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
renderer->ScreenHeight()) * 2.f;
// for 1st view, camera blur can be used
def.denyCameraBlur = false;
} else {
Matrix4 eyeMatrix = clientPlayers[player->GetId()]->GetEyeMatrix();
def.viewOrigin = eyeMatrix.GetOrigin();
def.viewAxis[0] = -eyeMatrix.GetAxis(0);
def.viewAxis[1] = -eyeMatrix.GetAxis(2);
def.viewAxis[2] = eyeMatrix.GetAxis(1);
if (shakeLevel >= 1) {
float localFireVibration = GetLocalFireVibration();
localFireVibration *= localFireVibration;
if (player->GetTool() == Player::ToolSpade) {
localFireVibration *= 0.4f;
}
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;
def.radialBlur += localFireVibration * 0.05f;
// sprint bob
{
float sp = SmoothStep(GetSprintState());
vibYaw += sinf(player->GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.01f * sp;
roll -= sinf(player->GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f) *
0.005f * (sp);
float p = cosf(player->GetWalkAnimationProgress() *
static_cast<float>(M_PI) * 2.f);
p = p * p;
p *= p;
p *= p;
vibPitch += p * 0.01f * sp;
if (shakeLevel >= 2) {
vibYaw += coherentNoiseSamplers[0].Sample(
player->GetWalkAnimationProgress() * 2.5f) *
0.005f * sp;
vibPitch += coherentNoiseSamplers[1].Sample(
player->GetWalkAnimationProgress() * 2.5f) *
0.01f * sp;
roll += coherentNoiseSamplers[2].Sample(
player->GetWalkAnimationProgress() * 2.5f) *
0.008f * sp;
scale += sp * 0.1f;
}
}
}
def.fovY = (float)cg_fov * static_cast<float>(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;
float aimDownState = GetAimDownState();
float per = aimDownState;
per *= per * per;
def.depthOfFieldFocalLength = per * 13.f + .054f;
def.blurVignette = .0f;
break;
}
// add vibration for both 1st/3rd view
{
// add grenade vibration
float grenVib = grenadeVibration;
if (grenVib > 0.f) {
if (shakeLevel >= 1) {
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;
def.radialBlur += grenVib * 0.1f;
}
}
}
{
// add grenade vibration
float grenVib = grenadeVibrationSlow;
if (grenVib > 0.f) {
if (shakeLevel >= 2) {
grenVib *= 4.f;
if (grenVib > 1.f)
grenVib = 1.f;
grenVib *= grenVib;
roll +=
coherentNoiseSamplers[0].Sample(time * 8.f) * 0.2f * grenVib;
vibPitch +=
coherentNoiseSamplers[1].Sample(time * 12.f) * 0.1f * grenVib;
vibYaw +=
coherentNoiseSamplers[2].Sample(time * 11.f) * 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 = atanf(tanf(def.fovX * .5f) * scale) * 2.f;
def.fovY = atanf(tanf(def.fovY * .5f) * scale) * 2.f;
}
{
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);
}
{
float wTime = world->GetTime();
if (wTime < lastHurtTime + .15f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .15f;
per *= .5f - player->GetHealth() / 100.f * .3f;
def.blurVignette += per * 6.f;
}
if (wTime < lastHurtTime + .2f && wTime >= lastHurtTime) {
float per = 1.f - (wTime - lastHurtTime) / .2f;
per *= .5f - player->GetHealth() / 100.f * .3f;
def.saturation *= std::max(0.f, 1.f - per * 4.f);
}
}
def.zNear = 0.05f;
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 = (float)cg_fov * static_cast<float>(M_PI) / 180.f;
def.fovX = atanf(tanf(def.fovY * .5f) * renderer->ScreenWidth() /
renderer->ScreenHeight()) *
2.f;
def.zNear = 0.05f;
def.skipWorld = false;
}
// Add vibration effects
{
float grenVib = grenadeVibration;
if (grenVib > 0.f) {
if (shakeLevel >= 1) {
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;
def.radialBlur += grenVib * 0.1f;
}
}
}
{
float grenVib = grenadeVibrationSlow;
if (grenVib > 0.f) {
if (shakeLevel >= 2) {
grenVib *= 4.f;
if (grenVib > 1.f)
grenVib = 1.f;
grenVib *= grenVib;
roll +=
coherentNoiseSamplers[0].Sample(time * 8.f) * 0.2f * grenVib;
vibPitch +=
coherentNoiseSamplers[1].Sample(time * 12.f) * 0.1f * grenVib;
vibYaw +=
coherentNoiseSamplers[2].Sample(time * 11.f) * 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 = atanf(tanf(def.fovX * .5f) * scale) * 2.f;
def.fovY = atanf(tanf(def.fovY * .5f) * scale) * 2.f;
}
{
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);
}
if (def.viewOrigin.z < 0.f) {
// Need to move the far plane because there's no vertical fog
def.zFar -= def.viewOrigin.z;
}
if ((int)cg_manualFocus) {
// Depth of field is manually controlled
def.depthOfFieldNearBlurStrength = def.depthOfFieldFarBlurStrength =
0.5f * (float)cg_depthOfFieldAmount;
def.depthOfFieldFocalLength = focalLength;
} else {
def.depthOfFieldNearBlurStrength = cg_depthOfFieldAmount;
def.depthOfFieldFarBlurStrength = 0.f;
}
def.zNear = 0.05f;
def.skipWorld = false;
} else {
SPAssert(GetCameraMode() == ClientCameraMode::None);
// Let there be darkness
def.viewOrigin = MakeVector3(0, 0, 0);
def.viewAxis[0] = MakeVector3(1, 0, 0);
def.viewAxis[1] = MakeVector3(0, 0, -1);
@ -477,27 +487,12 @@ namespace spades {
renderer->SetFogColor(MakeVector3(0, 0, 0));
}
if (def.viewOrigin.z < 0.f) {
// Need to move the far plane because there's no vertical fog
def.zFar -= def.viewOrigin.z;
}
SPAssert(!std::isnan(def.viewOrigin.x));
SPAssert(!std::isnan(def.viewOrigin.y));
SPAssert(!std::isnan(def.viewOrigin.z));
def.radialBlur = std::min(def.radialBlur, 1.f);
if ((int)cg_manualFocus) {
// Depth of field is manually controlled
def.depthOfFieldNearBlurStrength = def.depthOfFieldFarBlurStrength =
0.5f * (float)cg_depthOfFieldAmount;
def.depthOfFieldFocalLength = focalLength;
} else {
def.depthOfFieldNearBlurStrength = cg_depthOfFieldAmount;
def.depthOfFieldFarBlurStrength = 0.f;
}
return def;
}

View File

@ -276,25 +276,28 @@ namespace spades {
void Client::UpdateLocalSpectator(float dt) {
SPADES_MARK_FUNCTION();
Vector3 lastPos = followPos;
followVel *= powf(.3f, dt);
followPos += followVel * dt;
auto &sharedState = followAndFreeCameraState;
auto &freeState = freeCameraState;
if (followPos.x < 0.f) {
followVel.x = fabsf(followVel.x) * 0.2f;
followPos = lastPos + followVel * dt;
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 (followPos.y < 0.f) {
followVel.y = fabsf(followVel.y) * 0.2f;
followPos = lastPos + followVel * dt;
if (freeState.position.y < 0.f) {
freeState.velocity.y = fabsf(freeState.velocity.y) * 0.2f;
freeState.position = lastPos + freeState.velocity * dt;
}
if (followPos.x > (float)GetWorld()->GetMap()->Width()) {
followVel.x = fabsf(followVel.x) * -0.2f;
followPos = lastPos + followVel * 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 (followPos.y > (float)GetWorld()->GetMap()->Height()) {
followVel.y = fabsf(followVel.y) * -0.2f;
followPos = lastPos + followVel * 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;
@ -302,22 +305,22 @@ namespace spades {
Vector3 minShift;
// check collision
if (followVel.GetLength() < .01) {
followPos = lastPos;
followVel *= 0.f;
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, followPos - lastPos, 256);
result = map->CastRay2(lastPos + shift, freeState.position - lastPos, 256);
if (result.hit && !result.startSolid &&
Vector3::Dot(result.hitPos - followPos - shift,
followPos - lastPos) < 0.f) {
Vector3::Dot(result.hitPos - freeState.position - shift,
freeState.position - lastPos) < 0.f) {
float dist = Vector3::Dot(result.hitPos - followPos - shift,
(followPos - lastPos).Normalize());
float dist = Vector3::Dot(result.hitPos - freeState.position - shift,
(freeState.position - lastPos).Normalize());
if (dist < minDist) {
minResult = result;
minDist = dist;
@ -329,25 +332,25 @@ namespace spades {
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;
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(followVel, norm);
followVel -= norm * (dot * 1.2f);
float dot = Vector3::Dot(freeState.velocity, norm);
freeState.velocity -= 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);
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();
@ -361,22 +364,22 @@ namespace spades {
up2 *= scale;
if (playerInput.moveForward) {
followVel += front;
freeState.velocity += front;
} else if (playerInput.moveBackward) {
followVel -= front;
freeState.velocity -= front;
}
if (playerInput.moveLeft) {
followVel -= right;
freeState.velocity -= right;
} else if (playerInput.moveRight) {
followVel += right;
freeState.velocity += right;
}
if (playerInput.jump) {
followVel += up2;
freeState.velocity += up2;
} else if (playerInput.crouch) {
followVel -= up2;
freeState.velocity -= up2;
}
SPAssert(followVel.GetLength() < 100.f);
SPAssert(freeState.velocity.GetLength() < 100.f);
}
/** Handles movement of joined local player. */
@ -794,13 +797,13 @@ namespace spades {
}
}
// begin following
// The local player is dead; initialize the look-you-are-dead cam
if (victim == world->GetLocalPlayer()) {
followingPlayerId = victim->GetId();
followCameraState.enabled = false;
Vector3 v = -victim->GetFront();
followYaw = atan2(v.y, v.x);
followPitch = 30.f * M_PI / 180.f;
followAndFreeCameraState.yaw = atan2(v.y, v.x);
followAndFreeCameraState.pitch = 30.f * M_PI / 180.f;
}
// emit blood (also for local player)
@ -943,7 +946,7 @@ namespace spades {
SPAssert(type != HitTypeBlock);
// don't bleed local player
if (hurtPlayer != world->GetLocalPlayer() || ShouldRenderInThirdPersonView()) {
if (!IsFirstPerson(GetCameraMode()) || &GetCameraTargetPlayer() != hurtPlayer) {
Bleed(hitPos);
}
@ -980,7 +983,7 @@ namespace spades {
if (by == world->GetLocalPlayer() && hurtPlayer) {
net->SendHit(hurtPlayer->GetId(), type);
if (type == HitTypeHead) {
Handle<IAudioChunk> c =
audioDevice->RegisterSound("Sounds/Feedback/HeadshotFeedback.opus");
@ -988,7 +991,7 @@ namespace spades {
param.volume = cg_hitFeedbackSoundGain;
audioDevice->PlayLocal(c, param);
}
hitFeedbackIconState = 1.f;
if (hurtPlayer->GetTeamId() == world->GetLocalPlayer()->GetTeamId()) {
hitFeedbackFriendly = true;
@ -1077,7 +1080,8 @@ namespace spades {
spades::Vector3 hitPos) {
SPADES_MARK_FUNCTION();
if (IsFollowing() && followingPlayerId == player->GetId()) {
// Do not display tracers for bullets fired by the local player
if (IsFirstPerson(GetCameraMode()) && GetCameraTargetPlayerId() == player->GetId()) {
return;
}

View File

@ -241,12 +241,13 @@ namespace spades {
if (!world)
return;
Player *player = world->GetLocalPlayer();
if (client->IsFollowing()) {
player = world->GetPlayer(client->followingPlayerId);
}
if (!player)
if (!HasTargetPlayer(client->GetCameraMode())) {
// Do not display `MapView` until the player is joined and there is a player to
// focus
return;
}
Player &player = client->GetCameraTargetPlayer();
if (largeMap)
if (zoomState < .0001f)
@ -255,11 +256,11 @@ namespace spades {
GameMap *map = world->GetMap();
Vector2 mapSize = MakeVector2(map->Width(), map->Height());
Vector3 pos = player->GetPosition();
;
if (player->GetTeamId() >= 2) {
pos = client->followPos;
Vector3 pos = player.GetPosition();
if (player.IsSpectator()) {
pos = client->freeCameraState.position;
}
Vector2 center = {pos.x, pos.y};
float cfgMapSize = cg_minimapSize;
if (cfgMapSize < 32)
@ -462,38 +463,37 @@ namespace spades {
Vector4 teamColorF = ModifyColor(teamColor);
teamColorF *= alpha;
// draw local player's view
// Draw the local player's view
{
Player *p = player;
Handle<IImage> viewIcon = renderer->RegisterImage("Gfx/Map/View.png");
if (p->IsAlive()) {
Vector3 front = p->GetFront2D();
float ang = atan2(front.x, -front.y);
if (player->GetTeamId() >= 2) {
ang = client->followYaw - static_cast<float>(M_PI) * .5f;
if (player.IsAlive()) {
Vector3 front = player.GetFront2D();
float ang;
if (player.IsSpectator()) {
ang = client->followAndFreeCameraState.yaw - static_cast<float>(M_PI) * .5f;
} else {
ang = atan2(front.x, -front.y);
}
renderer->SetColorAlphaPremultiplied(teamColorF * 0.9f);
DrawIcon(player->GetTeamId() >= 2 ? client->followPos : p->GetPosition(),
DrawIcon(player.IsSpectator() ? client->freeCameraState.position : player.GetPosition(),
viewIcon, ang);
}
}
bool isSpectating = player->GetTeamId() >= 2;
// draw player's icon
for (int i = 0; i < world->GetNumPlayerSlots(); i++) {
Player *p = world->GetPlayer(i);
if (p == nullptr ||
(p->GetTeamId() != world->GetLocalPlayer()->GetTeamId() && !isSpectating) ||
(p->GetTeamId() != world->GetLocalPlayer()->GetTeamId() && !player.IsSpectator()) ||
!p->IsAlive())
continue;
Vector3 front = p->GetFront2D();
float ang = atan2(front.x, -front.y);
if (player->GetTeamId() >= 2) {
ang = client->followYaw - static_cast<float>(M_PI) * .5f;
if (p->IsSpectator()) {
ang = client->followAndFreeCameraState.yaw - static_cast<float>(M_PI) * .5f;
}
// use a spec color for each player
@ -511,24 +511,24 @@ namespace spades {
if (iconMode) {
WeaponType weapon = world->GetPlayer(i)->GetWeaponType();
if (weapon == WeaponType::SMG_WEAPON) {
DrawIcon(player->GetTeamId() >= 2 ? client->followPos
DrawIcon(p->IsSpectator() ? client->freeCameraState.position
: p->GetPosition(),
playerSMG, ang);
}
else if (weapon == WeaponType::RIFLE_WEAPON) {
DrawIcon(player->GetTeamId() >= 2 ? client->followPos
DrawIcon(p->IsSpectator() ? client->freeCameraState.position
: p->GetPosition(),
playerRifle, ang);
}
else if (weapon == WeaponType::SHOTGUN_WEAPON) {
DrawIcon(player->GetTeamId() >= 2 ? client->followPos
DrawIcon(p->IsSpectator() ? client->freeCameraState.position
: p->GetPosition(),
playerShotgun, ang);
}
} else { // draw normal color
DrawIcon(player->GetTeamId() >= 2 ? client->followPos : p->GetPosition(),
DrawIcon(p->IsSpectator() ? client->freeCameraState.position : p->GetPosition(),
playerIcon, ang);
}
}

View File

@ -143,6 +143,7 @@ namespace spades {
Weapon *GetWeapon() { return weapon; }
WeaponType GetWeaponType() { return weaponType; }
int GetTeamId() { return teamId; }
bool IsSpectator() { return teamId >= 2; }
std::string GetName();
IntVector3 GetColor();
IntVector3 GetBlockColor() { return blockColor; }

View File

@ -47,6 +47,8 @@ namespace spades {
#define SPNotImplemented() SPRaise("Not implemented")
#define SPUnreachable() SPRaise("Internal error; unreachable code")
#define SPInvalidArgument(name) SPRaise("Invalid argument: %s", name)
#define SPInvalidEnum(name, value) SPRaise("Invalid enum: %s: %d", name, (int)(value))