diff --git a/source/Game.cpp b/source/Game.cpp index 3411313..eac7bf7 100644 --- a/source/Game.cpp +++ b/source/Game.cpp @@ -173,7 +173,7 @@ Game::keyUp(const sf::Event& event) { mPlayer->setDirection(Player::Direction::RIGHT, true); break; case sf::Keyboard::F: - mPlayer->swapGadgets(); + mPlayer->pickUpItem(); break; default: break; diff --git a/source/World.cpp b/source/World.cpp index 3c5c4d0..85327a9 100755 --- a/source/World.cpp +++ b/source/World.cpp @@ -41,13 +41,20 @@ World::insertCharacter(std::shared_ptr character) { insert(character); } +void +World::remove(std::shared_ptr drawable) { + Sprite::Category cat = drawable->getCategory(); + auto item = std::find(mDrawables[cat].begin(), mDrawables[cat].end(), drawable); + mDrawables[cat].erase(item); +} + /** * Returns all characters that are within maxDistance from position. */ std::vector > World::getCharacters(const sf::Vector2f& position, float maxDistance) const { std::vector > visible; - for (auto it : mCharacters) { + for (const auto& it : mCharacters) { if (position == it->getPosition()) continue; if (thor::squaredLength(position - it->getPosition()) <= @@ -88,7 +95,7 @@ void World::applyMovement(std::shared_ptr sprite, int elapsed) { sf::Vector2f offset = sprite->getSpeed() * (elapsed / 1000.0f); for (auto w = mDrawables.begin(); w != mDrawables.end(); w++) { - for (auto& other : w->second) { + for (const auto& other : w->second) { if (sprite == other) continue; // Ignore anything that is filtered by masks. @@ -135,7 +142,7 @@ World::draw(sf::RenderTarget& target, sf::RenderStates states) const { screen.left += target.getView().getCenter().x - target.getView().getSize().x / 2; screen.top += target.getView().getCenter().y - target.getView().getSize().y / 2; for (auto v = mDrawables.begin(); v != mDrawables.end(); v++) { - for (auto item : v->second) { + for (const auto& item : v->second) { if (item->isInside(screen)) target.draw(static_cast(*item), states); } @@ -184,3 +191,21 @@ World::raycast(const sf::Vector2f& lineStart, } return true; } + +/** + * Returns the item closest to position after linear search. + */ +std::shared_ptr +World::getNearestItem(const sf::Vector2f& position) const { + std::shared_ptr closest; + float distance = std::numeric_limits::max(); + for (const auto& v : mDrawables) { + for (const auto& d : v.second) { + std::shared_ptr converted = std::dynamic_pointer_cast(d); + if (converted.get() != nullptr && + thor::squaredLength(converted->getPosition() - position) < distance) + closest = converted; + } + } + return closest; +} diff --git a/source/World.h b/source/World.h index c050279..7f7275b 100755 --- a/source/World.h +++ b/source/World.h @@ -24,12 +24,14 @@ class World : public sf::Drawable { public: void insert(std::shared_ptr drawable); void insertCharacter(std::shared_ptr character); + void remove(std::shared_ptr drawable); void step(int elapsed); void think(int elapsed); std::vector > getCharacters(const sf::Vector2f& position, float maxDistance) const; bool raycast(const sf::Vector2f& lineStart, const sf::Vector2f& lineEnd) const; + std::shared_ptr getNearestItem(const sf::Vector2f& position) const; private: diff --git a/source/abstract/Character.cpp b/source/abstract/Character.cpp index 5bddf92..2f0bb49 100644 --- a/source/abstract/Character.cpp +++ b/source/abstract/Character.cpp @@ -18,6 +18,7 @@ const float Character::VISION_DISTANCE = 500.0f; const float Character::POINT_REACHED_DISTANCE = 1.0f; +const float Character::ITEM_PICKUP_MAX_DISTANCE_SQUARED = 2500.0f; /** * Saves pointer to this instance in static var for think(). @@ -259,7 +260,34 @@ Character::getRightGadgetName() const { return mRightGadget->getName(); } +/** + * Picks up the nearest weapon or gadget, and drops the active weapon (or right gadget). + * + * If no item is nearby, gadgets are swapped instead. + */ void -Character::swapGadgets() { - std::swap(mLeftGadget, mRightGadget); +Character::pickUpItem() { + std::shared_ptr item = mWorld.getNearestItem(getPosition()); + if (item != nullptr && thor::squaredLength(item->getPosition() - getPosition()) <= + ITEM_PICKUP_MAX_DISTANCE_SQUARED) { + std::shared_ptr weapon = std::dynamic_pointer_cast(item); + if (weapon != nullptr) { + mWorld.insert(mActiveWeapon); + mActiveWeapon->drop(getPosition()); + mActiveWeapon = weapon; + mActiveWeapon->setHolder(*this); + } + std::shared_ptr gadget = std::dynamic_pointer_cast(item); + if (gadget != nullptr) { + mWorld.insert(mRightGadget); + mRightGadget->drop(getPosition()); + mRightGadget = mLeftGadget; + mLeftGadget = gadget; + } + // TODO: implement (or just make invisible?) + mWorld.remove(item); + } + else { + std::swap(mLeftGadget, mRightGadget); + } } diff --git a/source/abstract/Character.h b/source/abstract/Character.h index d727fd7..ed50a10 100644 --- a/source/abstract/Character.h +++ b/source/abstract/Character.h @@ -63,7 +63,7 @@ protected: void useRightGadget(); std::string getLeftGadgetName() const; std::string getRightGadgetName() const; - void swapGadgets(); + void pickUpItem(); protected: @@ -76,6 +76,8 @@ private: /// Distance to a path point where it will be considered as reached (to /// avoid floating point equality check). static const float POINT_REACHED_DISTANCE; + /// Maximum distance from character where an item can be picked up. + static const float ITEM_PICKUP_MAX_DISTANCE_SQUARED; friend class World; World& mWorld; Pathfinder& mPathfinder; diff --git a/source/items/Weapon.cpp b/source/items/Weapon.cpp index 880c382..3ba881d 100755 --- a/source/items/Weapon.cpp +++ b/source/items/Weapon.cpp @@ -16,7 +16,7 @@ Weapon::Weapon(World& world, Character& holder, const Yaml& config) : Item(sf::Vector2f(80, 30), "weapon.png"), mWorld(world), - mHolder(holder), + mHolder(&holder), mName(config.get("name", std::string())), mProjectile(config.get("bullet", std::string("bullet.yaml"))), mDamage(config.get("damage", 0)), @@ -142,6 +142,11 @@ Weapon::cancelReload() { mTimer.restart(sf::milliseconds(mFireInterval)); } +void +Weapon::setHolder(Character& holder) { + mHolder = &holder; +} + /** * Creates a new projectile and inserts it into the world. * @@ -149,18 +154,18 @@ Weapon::cancelReload() { */ void Weapon::insertProjectile(float angle) { - sf::Vector2f offset(mHolder.getDirection() * mHolder.getRadius()); + sf::Vector2f offset(mHolder->getDirection() * mHolder->getRadius()); - float spread = (mHolder.getSpeed() == sf::Vector2f()) + float spread = (mHolder->getSpeed() == sf::Vector2f()) ? mSpread : mSpreadMoving; std::uniform_real_distribution distribution(- spread, spread); angle += distribution(mGenerator) + 90.0f; - sf::Vector2f direction(thor::rotatedVector(mHolder.getDirection(), angle)); + sf::Vector2f direction(thor::rotatedVector(mHolder->getDirection(), angle)); - std::shared_ptr projectile(new Bullet(mHolder.getPosition() + offset, - mHolder, direction, mProjectile, mProjectileSpeed, + std::shared_ptr projectile(new Bullet(mHolder->getPosition() + offset, + *mHolder, direction, mProjectile, mProjectileSpeed, mDamage, mMaxRange)); mWorld.insert(projectile); } diff --git a/source/items/Weapon.h b/source/items/Weapon.h index 85039e1..03f5dd3 100755 --- a/source/items/Weapon.h +++ b/source/items/Weapon.h @@ -35,6 +35,7 @@ public: std::string getName() const; void reload(); void cancelReload(); + void setHolder(Character& holder); private: void fire(); @@ -42,7 +43,8 @@ private: private: World& mWorld; - Character& mHolder; + /// Non-owning pointer instead of reference to allow reassigning. + Character* mHolder; thor::Timer mTimer; const std::string mName; diff --git a/source/sprites/Player.h b/source/sprites/Player.h index 1d0ea65..5a55db0 100644 --- a/source/sprites/Player.h +++ b/source/sprites/Player.h @@ -50,7 +50,7 @@ public: using Character::getHealth; using Character::getLeftGadgetName; using Character::getRightGadgetName; - using Character::swapGadgets; + using Character::pickUpItem; private: void onThink(int elapsed) override;