397 lines
12 KiB
C++
Raw Normal View History

2013-08-29 11:45:22 +09:00
/*
Copyright (c) 2013 yvt
2013-09-05 00:52:03 +09:00
based on code of pysnip (c) Mathias Kaerlev 2011-2012.
2013-08-29 11:45:22 +09:00
This file is part of OpenSpades.
2013-08-29 11:45:22 +09:00
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.
2013-08-29 11:45:22 +09:00
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.
2013-08-29 11:45:22 +09:00
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
2013-08-29 11:45:22 +09:00
*/
2013-08-18 16:18:06 +09:00
#include "Weapon.h"
#include "GameProperties.h"
2017-01-08 18:02:21 +09:00
#include "IWorldListener.h"
#include "World.h"
#include <Core/Debug.h>
#include <Core/Exception.h>
2013-08-18 16:18:06 +09:00
namespace spades {
namespace client {
Weapon::Weapon(World *w, Player *p)
: world(w),
owner(p),
time(0),
shooting(false),
shootingPreviously(false),
reloading(false),
nextShotTime(0.f),
reloadStartTime(-101.f),
2017-11-18 21:15:07 +00:00
reloadEndTime(-100.f),
lastDryFire(false) {
2013-08-18 16:18:06 +09:00
SPADES_MARK_FUNCTION();
}
Weapon::~Weapon() { SPADES_MARK_FUNCTION(); }
void Weapon::Restock() { stock = GetMaxStock(); }
2013-08-18 16:18:06 +09:00
void Weapon::Reset() {
SPADES_MARK_FUNCTION();
shooting = false;
reloading = false;
ammo = GetClipSize();
stock = GetMaxStock();
time = 0.f;
nextShotTime = 0.f;
}
void Weapon::SetShooting(bool b) { shooting = b; }
bool Weapon::IsReadyToShoot() {
2017-01-08 18:02:21 +09:00
return (ammo > 0 || !owner->IsLocalPlayer()) && time >= nextShotTime &&
(!reloading || IsReloadSlow());
2013-08-18 16:18:06 +09:00
}
float Weapon::GetReloadProgress() {
return (time - reloadStartTime) / (reloadEndTime - reloadStartTime);
}
float Weapon::TimeToNextFire() { return nextShotTime - time; }
bool Weapon::FrameNext(float dt) {
2013-08-18 16:18:06 +09:00
SPADES_MARK_FUNCTION();
2017-01-08 18:02:21 +09:00
bool ownerIsLocalPlayer = owner->IsLocalPlayer();
2013-08-18 16:18:06 +09:00
bool fired = false;
bool dryFire = false;
if (shooting && (!reloading || IsReloadSlow())) {
2013-08-18 16:18:06 +09:00
// abort slow reload
reloading = false;
2017-01-08 18:02:21 +09:00
if (!shootingPreviously) {
nextShotTime = std::max(nextShotTime, time);
}
2017-01-08 18:02:21 +09:00
// Automatic operation of weapon.
if (time >= nextShotTime && (ammo > 0 || !ownerIsLocalPlayer)) {
2013-08-18 16:18:06 +09:00
fired = true;
2017-01-08 18:02:21 +09:00
// Consume an ammo.
2017-12-22 22:38:34 +09:00
if (ammo > 0) {
2017-01-08 18:02:21 +09:00
ammo--;
}
if (world->GetListener()) {
2013-08-18 16:18:06 +09:00
world->GetListener()->PlayerFiredWeapon(owner);
2017-01-08 18:02:21 +09:00
}
nextShotTime += GetDelay();
} else if (time >= nextShotTime) {
2013-08-18 16:18:06 +09:00
dryFire = true;
}
shootingPreviously = true;
} else {
shootingPreviously = false;
2013-08-18 16:18:06 +09:00
}
if (reloading) {
if (time >= reloadEndTime) {
2017-12-22 22:38:34 +09:00
// A reload was completed (non-slow-loading weapon), or a single shell was
// loaded (slow-loading weapon).
//
// For local players, the number of bullets/shells loaded onto the magazine is
// sent by the server. However, we still calculate it by ourselves for slow
// -loading weapons so the reloading animation and the number displayed on the
// screen is synchronized. (This is especially important on a slow connection.)
//
// For remote players, the server does not send any information regarding the
// number of bullets/shells loaded or held as stock. This is problematic for
// slow-loading weapons because we can't tell how many times the reloading
// animation has to be repeated. For now, we just assume a remote player has
// an infinite supply of ammo, but a limited number of bullets in a clip.
2013-08-18 16:18:06 +09:00
reloading = false;
if (IsReloadSlow()) {
2017-12-22 22:38:34 +09:00
if (ammo < GetClipSize() && (stock > 0 || !ownerIsLocalPlayer)) {
ammo++;
stock--;
}
slowReloadLeftCount--;
if (slowReloadLeftCount > 0)
Reload(false);
else if (world->GetListener())
world->GetListener()->PlayerReloadedWeapon(owner);
} else {
2017-01-08 18:02:21 +09:00
if (!ownerIsLocalPlayer) {
2017-12-22 22:38:34 +09:00
ammo = GetClipSize();
}
if (world->GetListener())
world->GetListener()->PlayerReloadedWeapon(owner);
2013-08-18 16:18:06 +09:00
}
}
}
time += dt;
if (dryFire && !lastDryFire) {
if (world->GetListener())
2013-08-18 16:18:06 +09:00
world->GetListener()->PlayerDryFiredWeapon(owner);
}
lastDryFire = dryFire;
return fired;
}
void Weapon::ReloadDone(int ammo, int stock) {
SPADES_MARK_FUNCTION_DEBUG();
this->ammo = ammo;
this->stock = stock;
}
void Weapon::AbortReload() {
SPADES_MARK_FUNCTION_DEBUG();
reloading = false;
}
2017-12-22 22:38:34 +09:00
void Weapon::Reload(bool initial) {
2013-08-18 16:18:06 +09:00
SPADES_MARK_FUNCTION();
2017-12-22 22:38:34 +09:00
bool ownerIsLocalPlayer = owner->IsLocalPlayer();
if (reloading)
2013-08-18 16:18:06 +09:00
return;
2017-12-22 22:38:34 +09:00
// Is the clip already full?
if (ammo >= GetClipSize())
2013-08-18 16:18:06 +09:00
return;
2017-12-22 22:38:34 +09:00
if (ownerIsLocalPlayer) {
if (stock == 0)
return;
if (IsReloadSlow() && ammo > 0 && shooting)
return;
}
if (initial)
slowReloadLeftCount = stock - std::max(0, stock - GetClipSize() + ammo);
2013-08-18 16:18:06 +09:00
reloading = true;
shooting = false;
reloadStartTime = time;
2013-08-18 16:18:06 +09:00
reloadEndTime = time + GetReloadTime();
if (world->GetListener())
2013-08-18 16:18:06 +09:00
world->GetListener()->PlayerReloadingWeapon(owner);
}
void Weapon::ForceReloadDone() {
int newStock;
newStock = std::max(0, stock - GetClipSize() + ammo);
ammo += stock - newStock;
stock = newStock;
}
class RifleWeapon3 : public Weapon {
2013-08-18 16:18:06 +09:00
public:
RifleWeapon3(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "Rifle"; }
float GetDelay() override { return 0.5f; }
int GetClipSize() override { return 10; }
int GetMaxStock() override { return 50; }
float GetReloadTime() override { return 2.5f; }
bool IsReloadSlow() override { return false; }
WeaponType GetWeaponType() override { return RIFLE_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
2013-08-18 16:18:06 +09:00
case HitTypeTorso: return 49;
case HitTypeHead: return 100;
case HitTypeArms: return 33;
case HitTypeLegs: return 33;
case HitTypeBlock: return 50;
default: SPAssert(false); return 0;
2013-08-18 16:18:06 +09:00
}
}
Vector3 GetRecoil() override {
return MakeVector3(0.025f, 0.05f, 0.f); // measured
2013-08-18 16:18:06 +09:00
}
float GetSpread() override { return 0.012f; } // measured (standing, crouched)
int GetPelletSize() override { return 1; }
2013-08-18 16:18:06 +09:00
};
class SMGWeapon3 : public Weapon {
2013-08-18 16:18:06 +09:00
public:
SMGWeapon3(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "SMG"; }
float GetDelay() override { return 0.11f; }
int GetClipSize() override { return 30; }
int GetMaxStock() override { return 120; }
float GetReloadTime() override { return 2.5f; }
bool IsReloadSlow() override { return false; }
WeaponType GetWeaponType() override { return SMG_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
2013-08-18 16:18:06 +09:00
case HitTypeTorso: return 29;
case HitTypeHead: return 75;
case HitTypeArms: return 18;
case HitTypeLegs: return 18;
case HitTypeBlock: return 35;
default: SPAssert(false); return 0;
2013-08-18 16:18:06 +09:00
}
}
Vector3 GetRecoil() override {
return MakeVector3(0.01f, 0.0125f, 0.f); // measured
2013-08-18 16:18:06 +09:00
}
float GetSpread() override { return 0.025f; } // measured (standing, crouched)
int GetPelletSize() override { return 1; }
2013-08-18 16:18:06 +09:00
};
class ShotgunWeapon3 : public Weapon {
2013-08-18 16:18:06 +09:00
public:
ShotgunWeapon3(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "Shotgun"; }
float GetDelay() override { return 1.f; }
int GetClipSize() override { return 6; }
int GetMaxStock() override { return 48; }
float GetReloadTime() override { return 0.5f; }
bool IsReloadSlow() override { return true; }
WeaponType GetWeaponType() override { return SHOTGUN_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
2013-08-18 16:18:06 +09:00
case HitTypeTorso: return 27;
case HitTypeHead: return 37;
case HitTypeArms: return 16;
case HitTypeLegs: return 16;
case HitTypeBlock:
// Actually, you cast a hit per pallet. This value is a guess, by the way.
// --GM
return 34;
default: SPAssert(false); return 0;
2013-08-18 16:18:06 +09:00
}
}
Vector3 GetRecoil() override {
return MakeVector3(0.05f, 0.1f, 0.f); // measured
2013-08-18 16:18:06 +09:00
}
float GetSpread() override { return 0.024f; }
int GetPelletSize() override { return 8; }
2013-08-18 16:18:06 +09:00
};
class RifleWeapon4 : public Weapon {
public:
RifleWeapon4(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "Rifle"; }
float GetDelay() override { return 0.6f; }
int GetClipSize() override { return 8; }
int GetMaxStock() override { return 48; }
float GetReloadTime() override { return 2.5f; }
bool IsReloadSlow() override { return false; }
WeaponType GetWeaponType() override { return RIFLE_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
// These are the 0.75 damage values.
// To be honest, we don't need this information, as the server decides the
// damage.
// EXCEPT for blocks, that is.
// --GM
case HitTypeTorso: return 49;
case HitTypeHead: return 100;
case HitTypeArms: return 33;
case HitTypeLegs: return 33;
case HitTypeBlock: return 50;
default: SPAssert(false); return 0;
}
}
Vector3 GetRecoil() override {
// FIXME: needs to measured
return MakeVector3(0.0001f, 0.075f, 0.f);
}
float GetSpread() override { return 0.004f; }
int GetPelletSize() override { return 1; }
};
class SMGWeapon4 : public Weapon {
public:
SMGWeapon4(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "SMG"; }
float GetDelay() override { return 0.1f; }
int GetClipSize() override { return 30; }
int GetMaxStock() override { return 150; }
float GetReloadTime() override { return 2.5f; }
bool IsReloadSlow() override { return false; }
WeaponType GetWeaponType() override { return SMG_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
case HitTypeTorso: return 29;
case HitTypeHead: return 75;
case HitTypeArms: return 18;
case HitTypeLegs: return 18;
case HitTypeBlock: return 34;
default: SPAssert(false); return 0;
}
}
Vector3 GetRecoil() override {
// FIXME: needs to measured
return MakeVector3(0.00005f, 0.0125f, 0.f);
}
float GetSpread() override { return 0.012f; }
int GetPelletSize() override { return 1; }
};
class ShotgunWeapon4 : public Weapon {
public:
ShotgunWeapon4(World *w, Player *p) : Weapon(w, p) {}
std::string GetName() override { return "Shotgun"; }
float GetDelay() override { return 0.8f; }
int GetClipSize() override { return 8; }
int GetMaxStock() override { return 48; }
float GetReloadTime() override { return 0.4f; }
bool IsReloadSlow() override { return true; }
WeaponType GetWeaponType() override { return SHOTGUN_WEAPON; }
int GetDamage(HitType type, float distance) override {
switch (type) {
case HitTypeTorso: return 27;
case HitTypeHead: return 37;
case HitTypeArms: return 16;
case HitTypeLegs: return 16;
case HitTypeBlock: return 34;
default: SPAssert(false); return 0;
}
}
Vector3 GetRecoil() override {
// FIXME: needs to measured
return MakeVector3(0.0002f, 0.075f, 0.f);
}
float GetSpread() override { return 0.036f; }
int GetPelletSize() override { return 8; }
};
Weapon *Weapon::CreateWeapon(WeaponType type, Player *p, const GameProperties &gp) {
2013-08-18 16:18:06 +09:00
SPADES_MARK_FUNCTION();
switch (gp.protocolVersion) {
case ProtocolVersion::v075:
switch (type) {
case RIFLE_WEAPON: return new RifleWeapon3(p->GetWorld(), p);
case SMG_WEAPON: return new SMGWeapon3(p->GetWorld(), p);
case SHOTGUN_WEAPON: return new ShotgunWeapon3(p->GetWorld(), p);
default: SPInvalidEnum("type", type);
}
case ProtocolVersion::v076:
switch (type) {
case RIFLE_WEAPON: return new RifleWeapon4(p->GetWorld(), p);
case SMG_WEAPON: return new SMGWeapon4(p->GetWorld(), p);
case SHOTGUN_WEAPON: return new ShotgunWeapon4(p->GetWorld(), p);
default: SPInvalidEnum("type", type);
}
default:
SPInvalidEnum("protocolVersion", gp.protocolVersion);
2013-08-18 16:18:06 +09:00
}
}
}
}