Add feat-based actions to the action bar
Actions are grouped into slots. Actions within each slot can be scrolled using the mouse wheel.
This commit is contained in:
parent
ba0e987bb1
commit
04cea53ecd
5 changed files with 203 additions and 86 deletions
|
@ -29,6 +29,7 @@
|
|||
#include "../../graphics/window.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "../d20/feats.h"
|
||||
#include "../game.h"
|
||||
#include "../objectconverter.h"
|
||||
#include "../reputes.h"
|
||||
|
@ -48,7 +49,7 @@ static constexpr int kOffsetToReticle = 8;
|
|||
static constexpr int kTitleBarWidth = 250;
|
||||
static constexpr int kTitleBarPadding = 6;
|
||||
static constexpr int kHealthBarHeight = 6;
|
||||
static constexpr int kActionCount = 3;
|
||||
static constexpr int kNumActionSlots = 3;
|
||||
static constexpr int kActionBarMargin = 3;
|
||||
static constexpr int kActionBarPadding = 3;
|
||||
static constexpr int kActionWidth = 35;
|
||||
|
@ -58,6 +59,7 @@ SelectionOverlay::SelectionOverlay(Game *game) : _game(game) {
|
|||
if (!game) {
|
||||
throw invalid_argument("game must not be null");
|
||||
}
|
||||
_actionSlots.resize(kNumActionSlots);
|
||||
}
|
||||
|
||||
void SelectionOverlay::load() {
|
||||
|
@ -73,6 +75,14 @@ void SelectionOverlay::load() {
|
|||
|
||||
addTextureByAction(ContextualAction::Unlock, "isk_security");
|
||||
addTextureByAction(ContextualAction::Attack, "i_attack");
|
||||
|
||||
// TODO: different icons per feat level
|
||||
_textureByAction.insert(make_pair(ContextualAction::PowerAttack, Feats::instance().get(FeatType::PowerAttack)->icon));
|
||||
_textureByAction.insert(make_pair(ContextualAction::CriticalStrike, Feats::instance().get(FeatType::CriticalStrike)->icon));
|
||||
_textureByAction.insert(make_pair(ContextualAction::Flurry, Feats::instance().get(FeatType::Flurry)->icon));
|
||||
_textureByAction.insert(make_pair(ContextualAction::PowerShot, Feats::instance().get(FeatType::PowerBlast)->icon));
|
||||
_textureByAction.insert(make_pair(ContextualAction::SniperShot, Feats::instance().get(FeatType::SniperShot)->icon));
|
||||
_textureByAction.insert(make_pair(ContextualAction::RapidShot, Feats::instance().get(FeatType::RapidShot)->icon));
|
||||
}
|
||||
|
||||
void SelectionOverlay::addTextureByAction(ContextualAction action, const string &resRef) {
|
||||
|
@ -85,21 +95,23 @@ bool SelectionOverlay::handle(const SDL_Event &event) {
|
|||
return handleMouseMotion(event.motion);
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
return handleMouseButtonDown(event.button);
|
||||
case SDL_MOUSEWHEEL:
|
||||
return handleMouseWheel(event.wheel);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SelectionOverlay::handleMouseMotion(const SDL_MouseMotionEvent &event) {
|
||||
_selectedActionIdx = -1;
|
||||
_selectedActionSlot = -1;
|
||||
|
||||
if (!_selectedObject) return false;
|
||||
|
||||
for (int i = 0; i < kActionCount; ++i) {
|
||||
for (int i = 0; i < kNumActionSlots; ++i) {
|
||||
float x, y;
|
||||
getActionScreenCoords(i, x, y);
|
||||
if (event.x >= x && event.y >= y && event.x < x + kActionWidth && event.y < y + kActionHeight) {
|
||||
_selectedActionIdx = i;
|
||||
_selectedActionSlot = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -108,27 +120,34 @@ bool SelectionOverlay::handleMouseMotion(const SDL_MouseMotionEvent &event) {
|
|||
}
|
||||
|
||||
bool SelectionOverlay::handleMouseButtonDown(const SDL_MouseButtonEvent &event) {
|
||||
if (event.button != SDL_BUTTON_LEFT ||
|
||||
_selectedActionIdx == -1 || _selectedActionIdx >= _actions.size()) return false;
|
||||
if (event.button != SDL_BUTTON_LEFT) return false;
|
||||
if (_selectedActionSlot == -1 || _selectedActionSlot >= _actionSlots.size()) return false;
|
||||
|
||||
shared_ptr<Creature> leader(_game->party().getLeader());
|
||||
if (!leader) return false;
|
||||
|
||||
shared_ptr<Area> area(_game->module()->area());
|
||||
|
||||
auto selectedObject = area->selectedObject();
|
||||
if (!selectedObject) return false;
|
||||
|
||||
switch (_actions[_selectedActionIdx]) {
|
||||
const ActionSlot &slot = _actionSlots[_selectedActionSlot];
|
||||
if (slot.indexSelected >= slot.actions.size()) return false;
|
||||
|
||||
switch (slot.actions[slot.indexSelected]) {
|
||||
case ContextualAction::Unlock: {
|
||||
shared_ptr<Creature> partyLeader(_game->party().getLeader());
|
||||
partyLeader->addAction(make_unique<ObjectAction>(ActionType::OpenLock, selectedObject));
|
||||
leader->addAction(make_unique<ObjectAction>(ActionType::OpenLock, selectedObject));
|
||||
break;
|
||||
}
|
||||
|
||||
case ContextualAction::Attack: {
|
||||
shared_ptr<Creature> partyLeader(_game->party().getLeader());
|
||||
partyLeader->addAction(make_unique<AttackAction>(static_pointer_cast<Creature>(selectedObject), partyLeader->getAttackRange(), true));
|
||||
case ContextualAction::Attack:
|
||||
case ContextualAction::PowerAttack:
|
||||
case ContextualAction::CriticalStrike:
|
||||
case ContextualAction::Flurry:
|
||||
case ContextualAction::PowerShot:
|
||||
case ContextualAction::SniperShot:
|
||||
case ContextualAction::RapidShot: {
|
||||
leader->addAction(make_unique<AttackAction>(static_pointer_cast<Creature>(selectedObject), leader->getAttackRange(), true));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -136,7 +155,28 @@ bool SelectionOverlay::handleMouseButtonDown(const SDL_MouseButtonEvent &event)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SelectionOverlay::handleMouseWheel(const SDL_MouseWheelEvent &event) {
|
||||
if (_selectedActionSlot == -1 || _selectedActionSlot >= _actionSlots.size()) return false;
|
||||
|
||||
ActionSlot &slot = _actionSlots[_selectedActionSlot];
|
||||
size_t numSlotActions = slot.actions.size();
|
||||
|
||||
if (event.y > 0) {
|
||||
if (slot.indexSelected-- == 0) {
|
||||
slot.indexSelected = numSlotActions - 1;
|
||||
}
|
||||
} else {
|
||||
if (++slot.indexSelected == numSlotActions) {
|
||||
slot.indexSelected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SelectionOverlay::update() {
|
||||
// TODO: update on selection change only
|
||||
|
||||
_hilightedObject.reset();
|
||||
_hilightedHostile = false;
|
||||
|
||||
|
@ -170,7 +210,43 @@ void SelectionOverlay::update() {
|
|||
|
||||
if (_selectedScreenCoords.z < 1.0f) {
|
||||
_selectedObject = selectedObject;
|
||||
_actions = module->getContextualActions(selectedObject);
|
||||
|
||||
for (int i = 0; i < kNumActionSlots; ++i) {
|
||||
_actionSlots[i].actions.clear();
|
||||
}
|
||||
set<ContextualAction> actions(module->getContextualActions(selectedObject));
|
||||
_hasActions = !actions.empty();
|
||||
if (_hasActions) {
|
||||
if (actions.count(ContextualAction::Attack) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::Attack);
|
||||
}
|
||||
if (actions.count(ContextualAction::PowerAttack) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::PowerAttack);
|
||||
}
|
||||
if (actions.count(ContextualAction::CriticalStrike) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::CriticalStrike);
|
||||
}
|
||||
if (actions.count(ContextualAction::Flurry) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::Flurry);
|
||||
}
|
||||
if (actions.count(ContextualAction::PowerShot) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::PowerShot);
|
||||
}
|
||||
if (actions.count(ContextualAction::SniperShot) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::SniperShot);
|
||||
}
|
||||
if (actions.count(ContextualAction::RapidShot) > 0) {
|
||||
_actionSlots[0].actions.push_back(ContextualAction::RapidShot);
|
||||
}
|
||||
if (actions.count(ContextualAction::Unlock) > 0) {
|
||||
_actionSlots[1].actions.push_back(ContextualAction::Unlock);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < kNumActionSlots; ++i) {
|
||||
if (_actionSlots[i].indexSelected >= _actionSlots[i].actions.size()) {
|
||||
_actionSlots[i].indexSelected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto selectedCreature = ObjectConverter::toCreature(selectedObject);
|
||||
if (selectedCreature) {
|
||||
|
@ -186,9 +262,7 @@ void SelectionOverlay::draw() {
|
|||
}
|
||||
if (_selectedObject) {
|
||||
drawReticle(_selectedHostile ? *_hostileReticle2 : *_friendlyReticle2, _selectedScreenCoords);
|
||||
if (!_actions.empty()) {
|
||||
drawActionBar();
|
||||
}
|
||||
drawActionBar();
|
||||
drawTitleBar();
|
||||
drawHealthBar();
|
||||
}
|
||||
|
@ -223,7 +297,7 @@ void SelectionOverlay::drawTitleBar() {
|
|||
float x = opts.width * _selectedScreenCoords.x - kTitleBarWidth / 2;
|
||||
float y = opts.height * (1.0f - _selectedScreenCoords.y) - _reticleHeight / 2 - barHeight - kOffsetToReticle - kHealthBarHeight - 1.0f;
|
||||
|
||||
if (!_actions.empty()) {
|
||||
if (_hasActions) {
|
||||
y -= kActionHeight + 2 * kActionBarMargin;
|
||||
}
|
||||
glm::mat4 transform(1.0f);
|
||||
|
@ -242,7 +316,7 @@ void SelectionOverlay::drawTitleBar() {
|
|||
{
|
||||
float x = opts.width * _selectedScreenCoords.x;
|
||||
float y = opts.height * (1.0f - _selectedScreenCoords.y) - (_reticleHeight + barHeight) / 2 - kOffsetToReticle - kHealthBarHeight - 1.0f;
|
||||
if (!_actions.empty()) {
|
||||
if (_hasActions) {
|
||||
y -= kActionHeight + 2 * kActionBarMargin;
|
||||
}
|
||||
glm::vec3 position(x, y, 0.0f);
|
||||
|
@ -256,7 +330,7 @@ void SelectionOverlay::drawHealthBar() {
|
|||
float y = opts.height * (1.0f - _selectedScreenCoords.y) - _reticleHeight / 2 - kHealthBarHeight - kOffsetToReticle;
|
||||
float w = glm::clamp(_selectedObject->currentHitPoints() / static_cast<float>(_selectedObject->hitPoints()), 0.0f, 1.0f) * kTitleBarWidth;
|
||||
|
||||
if (!_actions.empty()) {
|
||||
if (_hasActions) {
|
||||
y -= kActionHeight + 2 * kActionBarMargin;
|
||||
}
|
||||
glm::mat4 transform(1.0f);
|
||||
|
@ -273,59 +347,39 @@ void SelectionOverlay::drawHealthBar() {
|
|||
}
|
||||
|
||||
void SelectionOverlay::drawActionBar() {
|
||||
const GraphicsOptions &opts = _game->options().graphics;
|
||||
|
||||
for (int i = 0; i < kActionCount; ++i) {
|
||||
shared_ptr<Texture> frameTexture;
|
||||
if (i == _selectedActionIdx) {
|
||||
frameTexture = _hilightedScroll;
|
||||
} else if (_selectedHostile) {
|
||||
frameTexture = _hostileScroll;
|
||||
} else {
|
||||
frameTexture = _friendlyScroll;
|
||||
}
|
||||
setActiveTextureUnit(TextureUnits::diffuseMap);
|
||||
frameTexture->bind();
|
||||
|
||||
float frameX, frameY;
|
||||
getActionScreenCoords(i, frameX, frameY);
|
||||
|
||||
glm::mat4 transform(1.0f);
|
||||
transform = glm::translate(transform, glm::vec3(frameX, frameY, 0.0f));
|
||||
transform = glm::scale(transform, glm::vec3(kActionWidth, kActionHeight, 1.0f));
|
||||
|
||||
ShaderUniforms uniforms;
|
||||
uniforms.combined.general.projection = Window::instance().getOrthoProjection();
|
||||
uniforms.combined.general.model = move(transform);
|
||||
|
||||
Shaders::instance().activate(ShaderProgram::SimpleGUI, uniforms);
|
||||
Meshes::instance().getQuad()->draw();
|
||||
|
||||
if (i < static_cast<int>(_actions.size())) {
|
||||
ContextualAction action = _actions[i];
|
||||
|
||||
shared_ptr<Texture> texture(_textureByAction.find(action)->second);
|
||||
if (texture) {
|
||||
setActiveTextureUnit(TextureUnits::diffuseMap);
|
||||
texture->bind();
|
||||
|
||||
float y = opts.height * (1.0f - _selectedScreenCoords.y) - (_reticleHeight + kActionHeight + kActionWidth) / 2.0f - kOffsetToReticle - kActionBarMargin;
|
||||
|
||||
transform = glm::mat4(1.0f);
|
||||
transform = glm::translate(transform, glm::vec3(frameX, y, 0.0f));
|
||||
transform = glm::scale(transform, glm::vec3(kActionWidth, kActionWidth, 1.0f));
|
||||
|
||||
ShaderUniforms uniforms;
|
||||
uniforms.combined.general.projection = Window::instance().getOrthoProjection();
|
||||
uniforms.combined.general.model = move(transform);
|
||||
|
||||
Shaders::instance().activate(ShaderProgram::SimpleGUI, uniforms);
|
||||
Meshes::instance().getQuad()->draw();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < kNumActionSlots; ++i) {
|
||||
drawActionFrame(i);
|
||||
drawActionIcon(i);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionOverlay::drawActionFrame(int index) {
|
||||
shared_ptr<Texture> frameTexture;
|
||||
if (index == _selectedActionSlot) {
|
||||
frameTexture = _hilightedScroll;
|
||||
} else if (_selectedHostile) {
|
||||
frameTexture = _hostileScroll;
|
||||
} else {
|
||||
frameTexture = _friendlyScroll;
|
||||
}
|
||||
setActiveTextureUnit(TextureUnits::diffuseMap);
|
||||
frameTexture->bind();
|
||||
|
||||
float frameX, frameY;
|
||||
getActionScreenCoords(index, frameX, frameY);
|
||||
|
||||
glm::mat4 transform(1.0f);
|
||||
transform = glm::translate(transform, glm::vec3(frameX, frameY, 0.0f));
|
||||
transform = glm::scale(transform, glm::vec3(kActionWidth, kActionHeight, 1.0f));
|
||||
|
||||
ShaderUniforms uniforms;
|
||||
uniforms.combined.general.projection = Window::instance().getOrthoProjection();
|
||||
uniforms.combined.general.model = move(transform);
|
||||
|
||||
Shaders::instance().activate(ShaderProgram::SimpleGUI, uniforms);
|
||||
Meshes::instance().getQuad()->draw();
|
||||
}
|
||||
|
||||
bool SelectionOverlay::getActionScreenCoords(int index, float &x, float &y) const {
|
||||
if (!_selectedObject) return false;
|
||||
|
||||
|
@ -336,14 +390,41 @@ bool SelectionOverlay::getActionScreenCoords(int index, float &x, float &y) cons
|
|||
return true;
|
||||
}
|
||||
|
||||
void SelectionOverlay::drawActionIcon(int index) {
|
||||
const ActionSlot &slot = _actionSlots[index];
|
||||
if (slot.indexSelected >= slot.actions.size()) return;
|
||||
|
||||
ContextualAction action = slot.actions[slot.indexSelected];
|
||||
shared_ptr<Texture> texture(_textureByAction.find(action)->second);
|
||||
if (!texture) return;
|
||||
|
||||
setActiveTextureUnit(TextureUnits::diffuseMap);
|
||||
texture->bind();
|
||||
|
||||
float frameX, frameY;
|
||||
getActionScreenCoords(index, frameX, frameY);
|
||||
|
||||
const GraphicsOptions &opts = _game->options().graphics;
|
||||
float y = opts.height * (1.0f - _selectedScreenCoords.y) - (_reticleHeight + kActionHeight + kActionWidth) / 2.0f - kOffsetToReticle - kActionBarMargin;
|
||||
|
||||
glm::mat4 transform(1.0f);
|
||||
transform = glm::translate(transform, glm::vec3(frameX, y, 0.0f));
|
||||
transform = glm::scale(transform, glm::vec3(kActionWidth, kActionWidth, 1.0f));
|
||||
|
||||
ShaderUniforms uniforms;
|
||||
uniforms.combined.general.projection = Window::instance().getOrthoProjection();
|
||||
uniforms.combined.general.model = move(transform);
|
||||
|
||||
Shaders::instance().activate(ShaderProgram::SimpleGUI, uniforms);
|
||||
Meshes::instance().getQuad()->draw();
|
||||
}
|
||||
|
||||
glm::vec3 SelectionOverlay::getColorFromSelectedObject() const {
|
||||
static glm::vec3 red(1.0f, 0.0f, 0.0f);
|
||||
|
||||
if (_selectedObject && _selectedHostile) {
|
||||
return red;
|
||||
}
|
||||
|
||||
return getBaseColor(_game->gameId());
|
||||
return (_selectedObject && _selectedHostile) ?
|
||||
red :
|
||||
getBaseColor(_game->gameId());
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
|
|
|
@ -53,6 +53,11 @@ public:
|
|||
void draw();
|
||||
|
||||
private:
|
||||
struct ActionSlot {
|
||||
std::vector<ContextualAction> actions;
|
||||
uint32_t indexSelected { 0 };
|
||||
};
|
||||
|
||||
Game *_game { nullptr };
|
||||
std::shared_ptr<graphics::Font> _font;
|
||||
std::shared_ptr<graphics::Texture> _friendlyReticle;
|
||||
|
@ -65,24 +70,29 @@ private:
|
|||
std::unordered_map<ContextualAction, std::shared_ptr<graphics::Texture>> _textureByAction;
|
||||
std::shared_ptr<SpatialObject> _hilightedObject;
|
||||
std::shared_ptr<SpatialObject> _selectedObject;
|
||||
std::vector<ContextualAction> _actions;
|
||||
std::vector<ActionSlot> _actionSlots;
|
||||
glm::vec3 _hilightedScreenCoords { 0.0f };
|
||||
glm::vec3 _selectedScreenCoords { 0.0f };
|
||||
int _reticleHeight { 0 };
|
||||
int _selectedActionIdx { -1 };
|
||||
int _selectedActionSlot { -1 };
|
||||
bool _hilightedHostile { false };
|
||||
bool _selectedHostile { false };
|
||||
bool _hasActions { false };
|
||||
|
||||
void addTextureByAction(ContextualAction action, const std::string &resRef);
|
||||
|
||||
bool handleMouseMotion(const SDL_MouseMotionEvent &event);
|
||||
bool handleMouseButtonDown(const SDL_MouseButtonEvent &event);
|
||||
bool handleMouseWheel(const SDL_MouseWheelEvent &event);
|
||||
|
||||
void drawReticle(graphics::Texture &texture, const glm::vec3 &screenCoords);
|
||||
void drawTitleBar();
|
||||
void drawHealthBar();
|
||||
void drawActionBar();
|
||||
|
||||
void drawActionFrame(int index);
|
||||
void drawActionIcon(int index);
|
||||
|
||||
bool getActionScreenCoords(int index, float &x, float &y) const;
|
||||
glm::vec3 getColorFromSelectedObject() const;
|
||||
};
|
||||
|
|
|
@ -267,21 +267,40 @@ void Module::update(float dt) {
|
|||
_area->update(dt);
|
||||
}
|
||||
|
||||
vector<ContextualAction> Module::getContextualActions(const shared_ptr<Object> &object) const {
|
||||
vector<ContextualAction> actions;
|
||||
set<ContextualAction> Module::getContextualActions(const shared_ptr<Object> &object) const {
|
||||
set<ContextualAction> actions;
|
||||
|
||||
switch (object->type()) {
|
||||
case ObjectType::Creature: {
|
||||
auto leader = _game->party().getLeader();
|
||||
auto creature = static_pointer_cast<Creature>(object);
|
||||
if (!creature->isDead() && Reputes::instance().getIsEnemy(*(_game->party().getLeader()), *creature)) {
|
||||
actions.push_back(ContextualAction::Attack);
|
||||
if (!creature->isDead() && Reputes::instance().getIsEnemy(*leader, *creature)) {
|
||||
actions.insert(ContextualAction::Attack);
|
||||
auto weapon = leader->getEquippedItem(InventorySlot::rightWeapon);
|
||||
if (weapon && weapon->isRanged()) {
|
||||
if (leader->attributes().hasFeat(FeatType::PowerBlast)) {
|
||||
actions.insert(ContextualAction::PowerShot);
|
||||
} else if (leader->attributes().hasFeat(FeatType::SniperShot)) {
|
||||
actions.insert(ContextualAction::SniperShot);
|
||||
} else if (leader->attributes().hasFeat(FeatType::RapidShot)) {
|
||||
actions.insert(ContextualAction::RapidShot);
|
||||
}
|
||||
} else {
|
||||
if (leader->attributes().hasFeat(FeatType::PowerAttack)) {
|
||||
actions.insert(ContextualAction::PowerAttack);
|
||||
} else if (leader->attributes().hasFeat(FeatType::CriticalStrike)) {
|
||||
actions.insert(ContextualAction::CriticalStrike);
|
||||
} else if (leader->attributes().hasFeat(FeatType::Flurry)) {
|
||||
actions.insert(ContextualAction::Flurry);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ObjectType::Door: {
|
||||
auto door = static_pointer_cast<Door>(object);
|
||||
if (door->isLocked() && !door->isKeyRequired() && _game->party().getLeader()->attributes().hasSkill(Skill::Security)) {
|
||||
actions.push_back(ContextualAction::Unlock);
|
||||
actions.insert(ContextualAction::Unlock);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "glm/vec3.hpp"
|
||||
|
||||
|
@ -74,7 +75,7 @@ public:
|
|||
bool handle(const SDL_Event &event);
|
||||
void update(float dt);
|
||||
|
||||
std::vector<ContextualAction> getContextualActions(const std::shared_ptr<Object> &object) const;
|
||||
std::set<ContextualAction> getContextualActions(const std::shared_ptr<Object> &object) const;
|
||||
|
||||
const std::string &name() const { return _name; }
|
||||
const ModuleInfo &info() const { return _info; }
|
||||
|
|
|
@ -61,7 +61,13 @@ enum class CursorType {
|
|||
enum class ContextualAction {
|
||||
None,
|
||||
Unlock,
|
||||
Attack
|
||||
Attack,
|
||||
PowerAttack,
|
||||
CriticalStrike,
|
||||
Flurry,
|
||||
PowerShot,
|
||||
SniperShot,
|
||||
RapidShot
|
||||
};
|
||||
|
||||
enum class WeaponType {
|
||||
|
|
Loading…
Reference in a new issue