diff --git a/rwengine/CMakeLists.txt b/rwengine/CMakeLists.txt index 0672e92d..8af6d625 100644 --- a/rwengine/CMakeLists.txt +++ b/rwengine/CMakeLists.txt @@ -58,9 +58,8 @@ set(RWENGINE_SOURCES src/engine/SaveGame.hpp src/engine/ScreenText.cpp src/engine/ScreenText.hpp - src/items/InventoryItem.hpp - src/items/WeaponItem.cpp - src/items/WeaponItem.hpp + src/items/Weapon.cpp + src/items/Weapon.hpp src/loaders/DataLoader.cpp src/loaders/DataLoader.hpp src/loaders/GenericDATLoader.cpp diff --git a/rwengine/src/ai/CharacterController.cpp b/rwengine/src/ai/CharacterController.cpp index 9e243395..f395bdce 100644 --- a/rwengine/src/ai/CharacterController.cpp +++ b/rwengine/src/ai/CharacterController.cpp @@ -1,11 +1,12 @@ #include #include -#include -#include - #include #include -#include +#include +#include +#include +#include +#include #include constexpr float kCloseDoorIdleTime = 2.f; @@ -384,14 +385,20 @@ bool Activities::ExitVehicle::update(CharacterObject *character, return false; } -#include -#include -#include -bool Activities::ShootWeapon::update(CharacterObject *character, - CharacterController *controller) { +bool Activities::UseItem::update(CharacterObject *character, + CharacterController *controller) { RW_UNUSED(controller); - auto &wepdata = _item->getWeaponData(); + if (itemslot >= kMaxInventorySlots) { + return true; + } + + auto world = character->engine; + const auto &weapon = world->data->weaponData.at(itemslot); + auto &state = character->getCurrentState().weapons[itemslot]; + auto animator = character->animator; + auto shootanim = world->data->animations[weapon->animation1]; + auto throwanim = world->data->animations[weapon->animation2]; // Instant hit weapons loop their anim // Thrown projectiles have lob / throw. @@ -400,85 +407,73 @@ bool Activities::ShootWeapon::update(CharacterObject *character, character->setRotation( glm::angleAxis(character->getLook().x, glm::vec3{0.f, 0.f, 1.f})); - RW_CHECK(wepdata->inventorySlot < maxInventorySlots, - "Inventory slot out of bounds"); - auto &itemState = - character->getCurrentState().weapons[wepdata->inventorySlot]; - if (itemState.bulletsClip == 0 && itemState.bulletsTotal > 0) { - itemState.bulletsClip += - std::min(int(itemState.bulletsTotal), wepdata->clipSize); - itemState.bulletsTotal -= itemState.bulletsClip; + if (state.bulletsClip == 0 && state.bulletsTotal > 0) { + state.bulletsClip += + std::min(int(state.bulletsTotal), weapon->clipSize); + state.bulletsTotal -= state.bulletsClip; } - bool hasammo = itemState.bulletsClip > 0; + bool hasammo = state.bulletsClip > 0; - if (wepdata->fireType == WeaponData::INSTANT_HIT) { - if (_item->isFiring(character) && hasammo) { - auto shootanim = - character->engine->data->animations[wepdata->animation1]; - if (shootanim) { - if (character->animator->getAnimation(AnimIndexAction) != - shootanim) { - character->playActivityAnimation(shootanim, false, false); - } - - auto loopstart = wepdata->animLoopStart / 100.f; - auto loopend = wepdata->animLoopEnd / 100.f; - auto firetime = wepdata->animFirePoint / 100.f; - - auto currID = - character->animator->getAnimationTime(AnimIndexAction); - - if (currID >= firetime && !_fired) { - itemState.bulletsClip--; - _item->fire(character); - _fired = true; - } - if (currID > loopend) { - character->animator->setAnimationTime(AnimIndexAction, - loopstart); - _fired = false; - } + if (weapon->fireType == WeaponData::INSTANT_HIT) { + if (!character->getCurrentState().primaryActive) { + // Character is no longer firing + return true; + } + if (hasammo && shootanim) { + if (animator->getAnimation(AnimIndexAction) != shootanim) { + character->playActivityAnimation(shootanim, false, false); } - } else { - if (character->animator->isCompleted(AnimIndexAction)) { - return true; + + auto loopstart = weapon->animLoopStart / 100.f; + auto loopend = weapon->animLoopEnd / 100.f; + auto firetime = weapon->animFirePoint / 100.f; + + auto currenttime = animator->getAnimationTime(AnimIndexAction); + + if (currenttime >= firetime && !fired) { + state.bulletsClip--; + Weapon::fireHitscan(weapon.get(), character); + fired = true; } + if (currenttime > loopend) { + animator->setAnimationTime(AnimIndexAction, loopstart); + fired = false; + } + } else if (animator->isCompleted(AnimIndexAction)) { + // Should we exit this state when out of ammo? + return true; } } /// @todo Use Thrown flag instead of project (RPG isn't thrown eg.) - else if (wepdata->fireType == WeaponData::PROJECTILE && hasammo) { - auto shootanim = - character->engine->data->animations[wepdata->animation1]; - auto throwanim = - character->engine->data->animations[wepdata->animation2]; - - if (character->animator->getAnimation(AnimIndexAction) == shootanim) { - if (character->animator->isCompleted(AnimIndexAction)) { + else if (weapon->fireType == WeaponData::PROJECTILE && hasammo) { + if (animator->getAnimation(AnimIndexAction) == shootanim) { + if (character->getCurrentState().primaryActive) { + power = animator->getAnimationTime(AnimIndexAction) / 0.5f; + } + if (animator->isCompleted(AnimIndexAction)) { character->playActivityAnimation(throwanim, false, false); } - } else if (character->animator->getAnimation(AnimIndexAction) == - throwanim) { - auto firetime = wepdata->animCrouchFirePoint / 100.f; - auto currID = - character->animator->getAnimationTime(AnimIndexAction); + } else if (animator->getAnimation(AnimIndexAction) == throwanim) { + auto firetime = weapon->animCrouchFirePoint / 100.f; + auto currID = animator->getAnimationTime(AnimIndexAction); - if (currID >= firetime && !_fired) { - itemState.bulletsClip--; - _item->fire(character); - _fired = true; + if (currID >= firetime && !fired) { + state.bulletsClip--; + Weapon::fireProjectile(weapon.get(), character, power); + fired = true; } - if (character->animator->isCompleted(AnimIndexAction)) { + if (animator->isCompleted(AnimIndexAction)) { return true; } } else { - character->playActivityAnimation(throwanim, false, true); + character->playActivityAnimation(shootanim, false, true); } - } else if (wepdata->fireType == WeaponData::MELEE) { - RW_CHECK(wepdata->fireType != WeaponData::MELEE, + } else if (weapon->fireType == WeaponData::MELEE) { + RW_CHECK(weapon->fireType != WeaponData::MELEE, "Melee attacks not implemented"); return true; } else { - RW_ERROR("Unrecognized fireType: " << wepdata->fireType); + RW_ERROR("Unrecognized fireType: " << weapon->fireType); return true; } diff --git a/rwengine/src/ai/CharacterController.hpp b/rwengine/src/ai/CharacterController.hpp index 205621c2..e9fb145c 100644 --- a/rwengine/src/ai/CharacterController.hpp +++ b/rwengine/src/ai/CharacterController.hpp @@ -148,7 +148,6 @@ public: } // TODO: Refactor this with an ugly macro to reduce code dup. -class WeaponItem; /** * @brief Activities for CharacterController behaviour @@ -217,13 +216,14 @@ struct ExitVehicle : public CharacterController::Activity { bool update(CharacterObject* character, CharacterController* controller); }; -struct ShootWeapon : public CharacterController::Activity { - DECL_ACTIVITY(ShootWeapon) +struct UseItem : public CharacterController::Activity { + DECL_ACTIVITY(UseItem) - WeaponItem* _item; - bool _fired; + int itemslot; + bool fired = false; + float power = 0.f; - ShootWeapon(WeaponItem* item) : _item(item), _fired(false) { + UseItem(int slot) : itemslot(slot) { } bool update(CharacterObject* character, CharacterController* controller); diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index a8bb3484..1b1735ba 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -99,11 +98,6 @@ GameWorld::GameWorld(Logger* log, WorkContext* work, GameData* dat) new btGhostPairCallback()); gContactProcessedCallback = ContactProcessedCallback; dynamicsWorld->setInternalTickCallback(PhysicsTickCallback, this); - - // Populate inventory items - for (auto& w : data->weaponData) { - inventoryItems.push_back(new WeaponItem(inventoryItems.size(), w)); - } } GameWorld::~GameWorld() { @@ -433,14 +427,13 @@ PickupObject* GameWorld::createPickup(const glm::vec3& pos, int id, int type) { PickupObject* pickup = nullptr; auto pickuptype = (PickupObject::PickupType)type; - // Attempt to find an InventoryItem associated with this model auto it = std::find_if( - inventoryItems.begin(), inventoryItems.end(), - [=](InventoryItem* itm) { return itm->getModelID() == id; }); + data->weaponData.begin(), data->weaponData.end(), + [=](const std::shared_ptr& x) { return x->modelID == id; }); // If nothing, create a generic pickup instead of an item pickup - if (it != inventoryItems.end()) { - pickup = new ItemPickup(this, pos, modelInfo, pickuptype, *it); + if (it != data->weaponData.end()) { + pickup = new ItemPickup(this, pos, modelInfo, pickuptype, it->get()); } else { RW_UNIMPLEMENTED("Non-weapon pickups"); pickup = new PickupObject(this, pos, modelInfo, pickuptype); @@ -640,14 +633,6 @@ float GameWorld::getGameTime() const { return state->gameTime; } -InventoryItem* GameWorld::getInventoryItem(uint16_t weaponId) const { - RW_CHECK(weaponId < inventoryItems.size(), "InventoryItem ID out of range"); - if (weaponId >= inventoryItems.size()) { - return nullptr; - } - return inventoryItems[weaponId]; -} - void handleVehicleResponse(GameObject* object, btManifoldPoint& mp, bool isA) { bool isVehicle = object->type() == GameObject::Vehicle; if (!isVehicle) return; diff --git a/rwengine/src/engine/GameWorld.hpp b/rwengine/src/engine/GameWorld.hpp index 808a1892..874d424d 100644 --- a/rwengine/src/engine/GameWorld.hpp +++ b/rwengine/src/engine/GameWorld.hpp @@ -27,7 +27,6 @@ class ViewCamera; #include struct BlipData; -class InventoryItem; struct WeaponScan; struct VehicleGenerator; @@ -179,13 +178,6 @@ public: float getGameTime() const; - /** - * @brief getInventoryItem - * @param weaponId The Weapon ID (inventory slot) of the weapon to fetch - * @return Instance of the weapon - */ - InventoryItem* getInventoryItem(uint16_t weaponId) const; - /** * Game data */ @@ -353,11 +345,6 @@ private: std::vector areaIndicators; - /** - * Inventory Item instances - */ - std::vector inventoryItems; - /** * Flag for pausing the simulation */ diff --git a/rwengine/src/engine/SaveGame.cpp b/rwengine/src/engine/SaveGame.cpp index 7e46ea60..dde040b6 100644 --- a/rwengine/src/engine/SaveGame.cpp +++ b/rwengine/src/engine/SaveGame.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/rwengine/src/items/InventoryItem.hpp b/rwengine/src/items/InventoryItem.hpp deleted file mode 100644 index c7fd8971..00000000 --- a/rwengine/src/items/InventoryItem.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#ifndef _INVENTORYITEM_HPP_ -#define _INVENTORYITEM_HPP_ - -class GameObject; -class CharacterObject; - -/** - * @brief The InventoryItem class - * - * Instanciated once per item type - */ -class InventoryItem { - int _itemID; - int _inventorySlot; - int _modelID; - -protected: - InventoryItem(int itemID, int invSlot, int model) - : _itemID(itemID), _inventorySlot(invSlot), _modelID(model) { - } - -public: - virtual ~InventoryItem() { - } - - /** - * @brief getObject - * @return The ID of the model associated with the item. - */ - int getModelID() { - return _modelID; - } - - /** - * @brief getItemID - * @return The index of this item in the item list - */ - int getItemID() { - return _itemID; - } - - /** - * @brief getInventorySlot - * @return The inventory slot number for this item - */ - int getInventorySlot() const { - return _inventorySlot; - } - - /** - * @brief primary Implements mouse 1 action - * @param owner The character using this item - */ - virtual void primary(CharacterObject* owner) = 0; - - /** - * @see primary - * @param owner The character using this item - */ - virtual void secondary(CharacterObject* owner) = 0; - - constexpr static int NO_INVSLOT = -1; -}; - -#endif diff --git a/rwengine/src/items/Weapon.cpp b/rwengine/src/items/Weapon.cpp new file mode 100644 index 00000000..930afa66 --- /dev/null +++ b/rwengine/src/items/Weapon.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +void Weapon::fireHitscan(WeaponData* weapon, CharacterObject* owner) { + auto handFrame = owner->getModel()->findFrame("srhand"); + glm::mat4 handMatrix; + if (handFrame) { + while (handFrame->getParent()) { + handMatrix = + owner->skeleton->getMatrix(handFrame->getIndex()) * handMatrix; + handFrame = handFrame->getParent(); + } + } + + auto farTarget = + owner->getPosition() + + owner->getRotation() * glm::vec3(0.f, weapon->hitRange, 0.f); + auto handPos = glm::vec3(handMatrix * glm::vec4(0.f, 0.f, 0.f, 1.f)); + auto fireOrigin = owner->getPosition() + owner->getRotation() * handPos; + + owner->engine->doWeaponScan( + WeaponScan(weapon->damage, fireOrigin, farTarget, weapon)); +} + +void Weapon::fireProjectile(WeaponData* weapon, CharacterObject* owner, + float force) { + auto handPos = glm::vec3(0.f, 1.5f, 1.f); + auto fireOrigin = owner->getPosition() + owner->getRotation() * handPos; + auto direction = + owner->getRotation() * glm::normalize(glm::vec3{0.f, 1.f, 1.f}); + + auto pt = weapon->name == "grenade" ? ProjectileObject::Grenade + : ProjectileObject::Molotov; + + force = std::max(0.1f, force); + + auto projectile = new ProjectileObject( + owner->engine, fireOrigin, + {pt, direction, + 17.f * force, /// @todo pull a better velocity from somewhere + 3.5f, weapon}); + + auto& pool = owner->engine->getTypeObjectPool(projectile); + pool.insert(projectile); + owner->engine->allObjects.push_back(projectile); +} diff --git a/rwengine/src/items/Weapon.hpp b/rwengine/src/items/Weapon.hpp new file mode 100644 index 00000000..b9ce7808 --- /dev/null +++ b/rwengine/src/items/Weapon.hpp @@ -0,0 +1,12 @@ +#ifndef RWENGINE_WEAPON_HPP +#define RWENGINE_WEAPON_HPP + +#include +#include + +namespace Weapon { +void fireProjectile(WeaponData* wepon, CharacterObject* character, float force); +void fireHitscan(WeaponData* wepon, CharacterObject* character); +} + +#endif diff --git a/rwengine/src/items/WeaponItem.cpp b/rwengine/src/items/WeaponItem.cpp deleted file mode 100644 index 1e901e38..00000000 --- a/rwengine/src/items/WeaponItem.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void WeaponItem::fireHitscan(CharacterObject* owner) { - auto handFrame = owner->getModel()->findFrame("srhand"); - glm::mat4 handMatrix; - if (handFrame) { - while (handFrame->getParent()) { - handMatrix = - owner->skeleton->getMatrix(handFrame->getIndex()) * handMatrix; - handFrame = handFrame->getParent(); - } - } - - auto farTarget = - owner->getPosition() + - owner->getRotation() * glm::vec3(0.f, _wepData->hitRange, 0.f); - auto handPos = glm::vec3(handMatrix * glm::vec4(0.f, 0.f, 0.f, 1.f)); - auto fireOrigin = owner->getPosition() + owner->getRotation() * handPos; - - owner->engine->doWeaponScan( - WeaponScan(_wepData->damage, fireOrigin, farTarget, _wepData.get())); - -// Particle FX involved: -// - smokeII emited around barrel -// - Some circle particle used for the tracer -// - smoke emited at hit point -// - gunflash -#if 0 // Should be merged into the VisualFX system - auto flashDir = owner->getRotation() * glm::vec3{0.f, 0.f, 1.f}; - auto flashUp = owner->getRotation() * glm::vec3{0.f, -1.f, 0.f}; - - auto tracerTex = owner->engine->data->findTexture("shad_exp")->getName(); - auto flashTex = owner->engine->data->findTexture("gunflash2")->getName(); - auto flashTex1 = owner->engine->data->findTexture("gunflash1")->getName(); - - float tracertime = 0.1f; - auto distance = glm::distance(fireOrigin, farTarget); - const float tracerspeed = distance / tracertime * 0.5f; - float tracersize = _wepData->hitRange / 4.f; - float flashtime = 0.015f; - auto shotdir = glm::normalize(farTarget - fireOrigin); - - /// @TODO move this into rendering logic. - /*_character->engine->renderer.addParticle({ - fireOrigin + shotdir * tracersize / 2.f, - shotdir, - tracerspeed, - GameRenderer::FXParticle::UpCamera, - _character->engine->gameTime, tracertime, - tracerTex, - {0.04f, tracersize}, - {0.f, 0.f, 0.f} - }); - - _character->engine->renderer.addParticle({ - fireOrigin, - flashDir, - 0.f, - GameRenderer::FXParticle::Free, - _character->engine->gameTime, flashtime, - flashTex, - {0.2f, 0.2f}, - flashUp - }); - - _character->engine->renderer.addParticle({ - fireOrigin + shotdir * 0.1f, - flashDir, - 0.f, - GameRenderer::FXParticle::Free, - _character->engine->gameTime, flashtime, - flashTex, - {0.2f, 0.2f}, - flashUp - }); - - _character->engine->renderer.addParticle({ - fireOrigin + shotdir * 0.2f, - flashDir, - 0.f, - GameRenderer::FXParticle::Free, - _character->engine->gameTime, flashtime, - flashTex1, - {0.2f, 0.2f}, - flashUp - }); - */ -#endif -} - -void WeaponItem::fireProjectile(CharacterObject* owner) { - auto handPos = glm::vec3(0.f, 1.5f, 1.f); - auto fireOrigin = owner->getPosition() + owner->getRotation() * handPos; - auto direction = - owner->getRotation() * glm::normalize(glm::vec3{0.f, 1.f, 1.f}); - - auto pt = _wepData->name == "grenade" ? ProjectileObject::Grenade - : ProjectileObject::Molotov; - - // Work out the velocity multiplier as a function of how long the player - // Was holding down the fire button. If _fireStop < 0.f then the player - // is still holding the button down. - float throwTime = owner->engine->getGameTime() - - owner->getCurrentState().primaryStartTime / 1000.f; - float forceFactor = throwTime; - if (owner->getCurrentState().primaryEndTime >= - owner->getCurrentState().primaryStartTime) { - uint32_t heldTime = owner->getCurrentState().primaryEndTime - - owner->getCurrentState().primaryStartTime; - forceFactor = (heldTime) / 1000.f; - } - forceFactor = std::max(0.1f, forceFactor / throwTime); - - auto projectile = new ProjectileObject( - owner->engine, fireOrigin, - {pt, direction, - 17.f * forceFactor, /// @todo pull a better velocity from somewhere - 3.5f, _wepData}); - - auto& pool = owner->engine->getTypeObjectPool(projectile); - pool.insert(projectile); - owner->engine->allObjects.push_back(projectile); -} - -void WeaponItem::primary(CharacterObject* owner) { - if (owner->getCurrentState().primaryActive) { - // ShootWeapon will call ::fire() on us at the appropriate time. - owner->controller->setNextActivity(new Activities::ShootWeapon(this)); - } -} - -void WeaponItem::secondary(CharacterObject* owner) { - RW_UNUSED(owner); -} - -void WeaponItem::fire(CharacterObject* owner) { - switch (_wepData->fireType) { - case WeaponData::INSTANT_HIT: - fireHitscan(owner); - break; - case WeaponData::PROJECTILE: - fireProjectile(owner); - break; - default: - /// @todo meele - break; - } -} - -bool WeaponItem::isFiring(CharacterObject* owner) { - return owner->getCurrentState().primaryActive; -} diff --git a/rwengine/src/items/WeaponItem.hpp b/rwengine/src/items/WeaponItem.hpp deleted file mode 100644 index f8b9e22f..00000000 --- a/rwengine/src/items/WeaponItem.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#ifndef _WEAPONITEM_HPP_ -#define _WEAPONITEM_HPP_ -#include -#include -#include - -/** - * @brief The WeaponItem class - * Logic for basic weapon types - * - * This is instanciated once -per item type-, so state is shared between - * all instances of the same weapon. Timing is controlled by the CharacterState - */ -class WeaponItem : public InventoryItem { - std::shared_ptr _wepData; - - void fireHitscan(CharacterObject* owner); - void fireProjectile(CharacterObject* owner); - -public: - WeaponItem(int itemID, std::shared_ptr data) - : InventoryItem(itemID, data->inventorySlot, data->modelID) - , _wepData(data) { - } - - void primary(CharacterObject* owner); - - void secondary(CharacterObject* owner); - - void fire(CharacterObject* owner); - - bool isFiring(CharacterObject* owner); - - std::shared_ptr& getWeaponData() { - return _wepData; - } -}; - -#endif diff --git a/rwengine/src/objects/CharacterObject.cpp b/rwengine/src/objects/CharacterObject.cpp index e61ce2ef..6749adb0 100644 --- a/rwengine/src/objects/CharacterObject.cpp +++ b/rwengine/src/objects/CharacterObject.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -14,8 +13,7 @@ static glm::vec3 enter_offset(0.81756252f, 0.34800607f, -0.486281008f); const float CharacterObject::DefaultJumpSpeed = 2.f; CharacterObject::CharacterObject(GameWorld* engine, const glm::vec3& pos, - const glm::quat& rot, - BaseModelInfo *modelinfo) + const glm::quat& rot, BaseModelInfo* modelinfo) : GameObject(engine, pos, rot, modelinfo) , currentState({}) , currentVehicle(nullptr) @@ -521,12 +519,11 @@ void CharacterObject::activityFinished() { motionBlockedByActivity = false; } -void CharacterObject::addToInventory(InventoryItem* item) { - RW_CHECK(item->getInventorySlot() < maxInventorySlots, - "Inventory Slot greater than maxInventorySlots"); - if (item->getInventorySlot() < maxInventorySlots) { - currentState.weapons[item->getInventorySlot()].weaponId = - item->getItemID(); +void CharacterObject::addToInventory(int slot, int ammo) { + RW_CHECK(slot < kMaxInventorySlots, "Slot greater than kMaxInventorySlots"); + if (slot < kMaxInventorySlots) { + currentState.weapons[slot].weaponId = slot; + currentState.weapons[slot].bulletsTotal += ammo; } } @@ -534,19 +531,16 @@ void CharacterObject::setActiveItem(int slot) { currentState.currentWeapon = slot; } -InventoryItem* CharacterObject::getActiveItem() { - if (currentVehicle) return nullptr; - auto weaponId = currentState.weapons[currentState.currentWeapon].weaponId; - return engine->getInventoryItem(weaponId); -} - void CharacterObject::removeFromInventory(int slot) { currentState.weapons[slot].weaponId = 0; + if (currentState.currentWeapon == slot) { + currentState.currentWeapon = 0; + } } void CharacterObject::cycleInventory(bool up) { if (up) { - for (int j = currentState.currentWeapon + 1; j < maxInventorySlots; + for (int j = currentState.currentWeapon + 1; j < kMaxInventorySlots; ++j) { if (currentState.weapons[j].weaponId != 0) { currentState.currentWeapon = j; @@ -565,7 +559,7 @@ void CharacterObject::cycleInventory(bool up) { } // Nothing? set the highest - for (int j = maxInventorySlots - 1; j >= 0; --j) { + for (int j = kMaxInventorySlots - 1; j >= 0; --j) { if (currentState.weapons[j].weaponId != 0 || j == 0) { currentState.currentWeapon = j; return; @@ -575,17 +569,21 @@ void CharacterObject::cycleInventory(bool up) { } void CharacterObject::useItem(bool active, bool primary) { - if (getActiveItem()) { + /// @todo verify if this is the correct logic + auto item = getActiveItem(); + if (currentState.weapons[item].weaponId == unsigned(item)) { if (primary) { - if (active) - currentState.primaryStartTime = engine->getGameTime() * 1000.f; - else - currentState.primaryEndTime = engine->getGameTime() * 1000.f; + if (!currentState.primaryActive && active) { + // If we've just started, activate + controller->setNextActivity(new Activities::UseItem(item)); + } + else if (currentState.primaryActive && !active) { + // UseItem will cancel itself upon !primaryActive + } currentState.primaryActive = active; - getActiveItem()->primary(this); } else { currentState.secondaryActive = active; - getActiveItem()->secondary(this); + /// @todo handle scopes and sights } } } diff --git a/rwengine/src/objects/CharacterObject.hpp b/rwengine/src/objects/CharacterObject.hpp index e081cd67..6c9b4292 100644 --- a/rwengine/src/objects/CharacterObject.hpp +++ b/rwengine/src/objects/CharacterObject.hpp @@ -8,7 +8,7 @@ #include #include -constexpr int maxInventorySlots = 13; +constexpr int kMaxInventorySlots = 13; // Animation slots used for character animation blending constexpr unsigned int AnimIndexMovement = 0; @@ -26,18 +26,15 @@ struct CharacterState { float rotation; float health = 100.f; float armour = 0.f; - std::array weapons; + std::array weapons; uint16_t currentWeapon = 0; uint32_t lastFireTimeMS = 0; bool primaryActive = false; bool secondaryActive = false; - uint32_t primaryStartTime = 0; - uint32_t primaryEndTime = 0; }; class VehicleObject; class GameWorld; -class InventoryItem; struct AnimationGroup { Animation* idle; @@ -234,9 +231,21 @@ public: */ void activityFinished(); - void addToInventory(InventoryItem* item); + /** + * @brief addToInventory Adds ammo to the specified item slot + * @param slot The slot to add ammo for + * @param ammo The quanity of ammunition to add + * + * Will give the weapon (set the ID) if it is not possessed + */ + void addToInventory(int slot, int ammo); + void setActiveItem(int slot); - InventoryItem* getActiveItem(); + int getActiveItem() const { return currentState.currentWeapon; } + + /** + * @brief removeFromInventory Removes item at slot from inventory + */ void removeFromInventory(int slot); /** diff --git a/rwengine/src/objects/ItemPickup.cpp b/rwengine/src/objects/ItemPickup.cpp index 4f64dcb2..57055240 100644 --- a/rwengine/src/objects/ItemPickup.cpp +++ b/rwengine/src/objects/ItemPickup.cpp @@ -1,22 +1,20 @@ +#include +#include #include -#include #include #include #include ItemPickup::ItemPickup(GameWorld *world, const glm::vec3 &position, BaseModelInfo *modelinfo, PickupType type, - InventoryItem *item) + WeaponData *item) : PickupObject(world, position, modelinfo, type), item(item) { - RW_CHECK(item != nullptr, "Pickup created with null item"); } bool ItemPickup::onCharacterTouch(CharacterObject *character) { - character->addToInventory(item); - auto &wep = character->getCurrentState().weapons[item->getInventorySlot()]; - auto totalRounds = 0, clipRounds = 0; + auto totalRounds = 0; - switch (item->getModelID()) { + switch (item->modelID) { case 173: /* Pistol */ totalRounds = 45; break; @@ -50,8 +48,7 @@ bool ItemPickup::onCharacterTouch(CharacterObject *character) { totalRounds /= 5; } - wep.bulletsTotal = totalRounds; - wep.bulletsClip = clipRounds; + character->addToInventory(item->inventorySlot, totalRounds); return true; } diff --git a/rwengine/src/objects/ItemPickup.hpp b/rwengine/src/objects/ItemPickup.hpp index 90dccb69..4df90d3d 100644 --- a/rwengine/src/objects/ItemPickup.hpp +++ b/rwengine/src/objects/ItemPickup.hpp @@ -1,21 +1,19 @@ #pragma once #ifndef _ITEMPICKUP_HPP_ #define _ITEMPICKUP_HPP_ +#include #include #include -class InventoryItem; - /** * @brief The ItemPickup class * Inserts an item into a characters inventory on pickup. */ class ItemPickup : public PickupObject { - InventoryItem* item; - + WeaponData* item; public: - ItemPickup(GameWorld* world, const glm::vec3& position, BaseModelInfo *modelinfo, PickupType type, - InventoryItem* item); + ItemPickup(GameWorld* world, const glm::vec3& position, + BaseModelInfo* modelinfo, PickupType type, WeaponData* item); bool onCharacterTouch(CharacterObject* character); }; diff --git a/rwengine/src/objects/ProjectileObject.hpp b/rwengine/src/objects/ProjectileObject.hpp index aa39339d..65c93025 100644 --- a/rwengine/src/objects/ProjectileObject.hpp +++ b/rwengine/src/objects/ProjectileObject.hpp @@ -25,7 +25,7 @@ public: /** Time to dentonation or removal */ float time; - std::shared_ptr weapon; + WeaponData* weapon; }; private: diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index c39603df..4b4c9bb6 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include diff --git a/rwengine/src/render/GameRenderer.hpp b/rwengine/src/render/GameRenderer.hpp index a4d51ac7..eccd8eea 100644 --- a/rwengine/src/render/GameRenderer.hpp +++ b/rwengine/src/render/GameRenderer.hpp @@ -32,7 +32,6 @@ class ProjectileObject; class CutsceneObject; class Animator; -class InventoryItem; class Renderer; diff --git a/rwengine/src/render/ObjectRenderer.cpp b/rwengine/src/render/ObjectRenderer.cpp index b5257630..70764fcf 100644 --- a/rwengine/src/render/ObjectRenderer.cpp +++ b/rwengine/src/render/ObjectRenderer.cpp @@ -7,7 +7,6 @@ #include // Objects that we know how to turn into renderlist entries -#include #include #include #include @@ -302,31 +301,30 @@ void ObjectRenderer::renderCharacter(CharacterObject* pedestrian, renderFrame(pedestrian->getModel(), root->getChildren()[0], matrixModel, pedestrian, 1.f, outList); - if (pedestrian->getActiveItem()) { - auto item = pedestrian->getActiveItem(); + auto item = pedestrian->getActiveItem(); + const auto& weapon = pedestrian->engine->data->weaponData[item]; - if (item->getModelID() == -1) { - return; // No model for this item - } - - auto handFrame = pedestrian->getModel()->findFrame("srhand"); - glm::mat4 localMatrix; - if (handFrame) { - while (handFrame->getParent()) { - localMatrix = - pedestrian->skeleton->getMatrix(handFrame->getIndex()) * - localMatrix; - handFrame = handFrame->getParent(); - } - } - - // Assume items are all simple - auto simple = - m_world->data->findModelInfo(item->getModelID()); - auto geometry = simple->getAtomic(0)->getGeometries().at(0); - renderGeometry(simple->getModel(), geometry, matrixModel * localMatrix, - 1.f, nullptr, outList); + if (weapon->modelID == -1) { + return; // No model for this item } + + auto handFrame = pedestrian->getModel()->findFrame("srhand"); + glm::mat4 localMatrix; + if (handFrame) { + while (handFrame->getParent()) { + localMatrix = + pedestrian->skeleton->getMatrix(handFrame->getIndex()) * + localMatrix; + handFrame = handFrame->getParent(); + } + } + + // Assume items are all simple + auto simple = + m_world->data->findModelInfo(weapon->modelID); + auto geometry = simple->getAtomic(0)->getGeometries().at(0); + renderGeometry(simple->getModel(), geometry, matrixModel * localMatrix, 1.f, + nullptr, outList); } void ObjectRenderer::renderVehicle(VehicleObject* vehicle, diff --git a/rwgame/DrawUI.cpp b/rwgame/DrawUI.cpp index ede27471..255c59e1 100644 --- a/rwgame/DrawUI.cpp +++ b/rwgame/DrawUI.cpp @@ -1,8 +1,8 @@ #include "DrawUI.hpp" #include #include -#include #include +#include #include #include @@ -152,16 +152,14 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, render->text.renderText(ti); #endif - InventoryItem* current = player->getCharacter()->getActiveItem(); + auto item = player->getCharacter()->getActiveItem(); + auto weapon = world->data->weaponData[item]; std::string itemTextureName = "fist"; - if (current) { - uint16_t model = current->getModelID(); - if (model > 0) { - auto weaponData = - world->data->findModelInfo(model); - if (weaponData != nullptr) { - itemTextureName = weaponData->name; - } + if (weapon && weapon->modelID > 0) { + auto model = + world->data->findModelInfo(weapon->modelID); + if (model != nullptr) { + itemTextureName = model->name; } } // Urgh @@ -179,25 +177,20 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world, glm::vec4(iconX, iconY, ui_weaponSize, ui_weaponSize)); } - if (current) { - WeaponItem* wep = static_cast(current); - if (wep->getWeaponData()->fireType != WeaponData::MELEE) { - const CharacterState& cs = - player->getCharacter()->getCurrentState(); - const CharacterWeaponSlot& slotInfo = cs.weapons[cs.currentWeapon]; - ti.text = GameStringUtil::fromString( - std::to_string(slotInfo.bulletsClip) + "-" + - std::to_string(slotInfo.bulletsTotal)); + if (weapon->fireType != WeaponData::MELEE) { + const CharacterState& cs = player->getCharacter()->getCurrentState(); + const CharacterWeaponSlot& slotInfo = cs.weapons[cs.currentWeapon]; + ti.text = GameStringUtil::fromString( + std::to_string(slotInfo.bulletsClip) + "-" + + std::to_string(slotInfo.bulletsTotal)); - ti.baseColour = ui_shadowColour; - ti.font = 2; - ti.size = ui_ammoSize; - ti.align = TextRenderer::TextInfo::Center; - ti.screenPosition = - glm::vec2(iconX + ui_weaponSize / 2.f, - iconY + ui_weaponSize - ui_ammoHeight); - render->text.renderText(ti); - } + ti.baseColour = ui_shadowColour; + ti.font = 2; + ti.size = ui_ammoSize; + ti.align = TextRenderer::TextInfo::Center; + ti.screenPosition = glm::vec2(iconX + ui_weaponSize / 2.f, + iconY + ui_weaponSize - ui_ammoHeight); + render->text.renderText(ti); } } diff --git a/rwgame/states/DebugState.cpp b/rwgame/states/DebugState.cpp index aa1aab06..b5adeaed 100644 --- a/rwgame/states/DebugState.cpp +++ b/rwgame/states/DebugState.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -279,11 +278,10 @@ Menu* DebugState::createWeaponMenu() { [=] { this->enterMenu(createDebugMenu()); }, kDebugEntryHeight)); - for (int i = 1; i < maxInventorySlots; ++i) { - auto item = getWorld()->getInventoryItem(i); + for (int i = 1; i < kMaxInventorySlots; ++i) { auto& name = getWorld()->data->weaponData[i]->name; m->addEntry( - Menu::lambda(name, [=] { giveItem(item); }, kDebugEntryHeight)); + Menu::lambda(name, [=] { giveItem(i); }, kDebugEntryHeight)); } return m; @@ -475,17 +473,14 @@ void DebugState::spawnFollower(unsigned int id) { } } -void DebugState::giveItem(InventoryItem* item) { +void DebugState::giveItem(int slot) { CharacterObject* player = nullptr; if (game->getPlayer()) { player = game->getPlayer()->getCharacter(); } if (player) { - player->addToInventory(item); - auto& wep = player->getCurrentState().weapons[item->getInventorySlot()]; - wep.bulletsTotal = 100; - wep.bulletsClip = 0; + player->addToInventory(slot, 100); } } diff --git a/rwgame/states/DebugState.hpp b/rwgame/states/DebugState.hpp index 86257cad..57965091 100644 --- a/rwgame/states/DebugState.hpp +++ b/rwgame/states/DebugState.hpp @@ -33,7 +33,7 @@ public: void spawnVehicle(unsigned int id); void spawnFollower(unsigned int id); - void giveItem(InventoryItem* item); + void giveItem(int slot); const ViewCamera& getCamera(); }; diff --git a/rwgame/states/IngameState.cpp b/rwgame/states/IngameState.cpp index b8dadafa..8588caa6 100644 --- a/rwgame/states/IngameState.cpp +++ b/rwgame/states/IngameState.cpp @@ -6,11 +6,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include @@ -105,9 +105,9 @@ void IngameState::startTest() { getWorld()->state->playerObject = playerChar->getGameObjectID(); glm::vec3 itemspawn(276.5f, -609.f, 36.5f); - for (int i = 1; i < maxInventorySlots; ++i) { - auto item = getWorld()->getInventoryItem(i); - getWorld()->createPickup(itemspawn, item->getModelID(), + for (int i = 1; i < getWorld()->data->weaponData.size(); ++i) { + auto& item = getWorld()->data->weaponData[i]; + getWorld()->createPickup(itemspawn, item->modelID, PickupObject::OnStreet); itemspawn.x += 2.5f; } diff --git a/tests/test_items.cpp b/tests/test_items.cpp index aa41177a..0e0d3050 100644 --- a/tests/test_items.cpp +++ b/tests/test_items.cpp @@ -1,5 +1,4 @@ #include -#include #include #include "test_globals.hpp" @@ -11,24 +10,17 @@ BOOST_AUTO_TEST_CASE(test_character_inventory) { auto character = Global::get().e->createPedestrian(1, {0.f, 0.f, 0.f}); BOOST_REQUIRE(character != nullptr); - auto item = Global::get().e->getInventoryItem(4); - auto fist = Global::get().e->getInventoryItem(0); + character->addToInventory(1, 10); - BOOST_REQUIRE(item != nullptr); - BOOST_REQUIRE(fist != nullptr); - BOOST_CHECK_NE(fist, item); + BOOST_CHECK_EQUAL(character->getActiveItem(), 0); - character->addToInventory(item); + character->setActiveItem(1); - BOOST_CHECK_EQUAL(character->getActiveItem(), fist); + BOOST_CHECK_EQUAL(character->getActiveItem(), 1); - character->setActiveItem(item->getInventorySlot()); + character->removeFromInventory(1); - BOOST_CHECK_EQUAL(character->getActiveItem(), item); - - character->removeFromInventory(item->getInventorySlot()); - - BOOST_CHECK_EQUAL(character->getActiveItem(), fist); + BOOST_CHECK_EQUAL(character->getActiveItem(), 0); Global::get().e->destroyObject(character); } diff --git a/tests/test_pickup.cpp b/tests/test_pickup.cpp index aeca2253..e7dc6950 100644 --- a/tests/test_pickup.cpp +++ b/tests/test_pickup.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -65,16 +64,16 @@ BOOST_AUTO_TEST_CASE(test_item_pickup) { Global::get().e->createPedestrian(1, {30.1f, 0.f, 0.f}); BOOST_REQUIRE(character != nullptr); - auto item = Global::get().e->getInventoryItem(3); - BOOST_REQUIRE(item != nullptr); + auto pistol = Global::get().d->weaponData[1].get(); + auto model = Global::get().d->modelinfo[pistol->modelID].get(); - ItemPickup* p = new ItemPickup(Global::get().e, {30.f, 0.f, 0.f}, - nullptr, PickupObject::OnStreet, item); + ItemPickup* p = new ItemPickup(Global::get().e, {30.f, 0.f, 0.f}, model, + PickupObject::OnStreet, pistol); Global::get().e->allObjects.push_back(p); // Check the characters inventory is empty. - for (int i = 0; i < maxInventorySlots; ++i) { + for (int i = 0; i < kMaxInventorySlots; ++i) { BOOST_CHECK(character->getCurrentState().weapons[i].weaponId == 0); } @@ -84,8 +83,7 @@ BOOST_AUTO_TEST_CASE(test_item_pickup) { auto& inventory = character->getCurrentState().weapons; BOOST_CHECK(std::any_of(std::begin(inventory), std::end(inventory), [&](const CharacterWeaponSlot& i) { - return i.weaponId == - item->getInventorySlot(); + return i.weaponId == pistol->inventorySlot; })); Global::get().e->destroyObject(p); diff --git a/tests/test_weapon.cpp b/tests/test_weapon.cpp index 03e05702..a5a97474 100644 --- a/tests/test_weapon.cpp +++ b/tests/test_weapon.cpp @@ -30,7 +30,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) { auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f}); BOOST_REQUIRE(character != nullptr); - auto wepdata = Global::get().e->data->weaponData[5]; + auto wepdata = Global::get().e->data->weaponData[5].get(); auto projectile = new ProjectileObject( Global::get().e, {26.f, 1.f, 10.f}, @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) { auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f}); BOOST_REQUIRE(character != nullptr); - auto wepdata = Global::get().e->data->weaponData[6]; + auto wepdata = Global::get().e->data->weaponData[6].get(); auto projectile = new ProjectileObject( Global::get().e, {26.f, 1.f, 10.f}, @@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE(TestProjectile) { auto character = Global::get().e->createPedestrian(1, {25.f, 0.f, 0.f}); BOOST_REQUIRE(character != nullptr); - auto wepdata = Global::get().e->data->weaponData[7]; + auto wepdata = Global::get().e->data->weaponData[7].get(); auto projectile = new ProjectileObject( Global::get().e, {26.f, 1.f, 10.f},