Improved enemy item selection by adjusting to player items.

This commit is contained in:
Felix Ableitner 2013-08-31 23:00:49 +02:00
parent 0daf1cf6d6
commit 7c2848972f
19 changed files with 175 additions and 72 deletions

View file

@ -31,7 +31,6 @@ Game::Game(tgui::Window& window) :
mWindow.setKeyRepeatEnabled(false);
srand(time(nullptr));
mGenerator.generateCurrentAreaIfNeeded(Vector2f());
initPlayer();
mCrosshairTexture = ResourceManager::i().acquire(Loader::i()
@ -54,8 +53,13 @@ Game::Game(tgui::Window& window) :
}
void Game::initPlayer() {
Character::EquippedItems playerItems = {
Weapon::WeaponType::PISTOL, Weapon::WeaponType::KNIFE,
Gadget::GadgetType::NONE, Gadget::GadgetType::NONE
};
mGenerator.generateCurrentAreaIfNeeded(Vector2f(), playerItems);
mPlayer = std::shared_ptr<Player>(new Player(mWorld, mPathfinder,
mGenerator.getPlayerSpawn()));
mGenerator.getPlayerSpawn(), playerItems));
mWorld.insertCharacter(mPlayer);
}
@ -92,7 +96,7 @@ Game::loop() {
render();
mGenerator.generateCurrentAreaIfNeeded(mPlayer->getPosition());
mGenerator.generateCurrentAreaIfNeeded(mPlayer->getPosition(), mPlayer->getEquippedItems());
}
}

View file

@ -37,7 +37,8 @@ Generator::Generator(World& world, Pathfinder& pathfinder) :
* GENERATE_AREA_SIZE and GENERATE_AREA_RANGE).
*/
void
Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition,
const Character::EquippedItems& playerItems) {
std::map<Vector2i, float> open;
std::set<Vector2i> closed;
@ -65,11 +66,11 @@ Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE) / 2,
Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE));
generateTiles(area);
for (const auto& enemy : getEnemySpawns(area)) {
float distance = thor::length(enemy.first - playerPosition);
for (const auto& spawn : getEnemySpawns(area)) {
float distance = thor::length(spawn - playerPosition);
if (distance > Character::VISION_DISTANCE)
mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(
mWorld, mPathfinder, enemy.first, enemy.second)));
mWorld, mPathfinder, spawn, playerItems)));
}
}
if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
@ -261,19 +262,16 @@ Generator::generateTiles(const sf::IntRect& area) {
* @param area Area for which enemy spawns should be returned.
* @return Pairs of spawn points together with seeds.
*/
std::vector<std::pair<Vector2f, float> >
std::vector<Vector2f>
Generator::getEnemySpawns(const sf::IntRect& area) {
std::vector<std::pair<Vector2f, float> > spawns;
std::vector<Vector2f> spawns;
for (int x = area.left; x < area.left + area.width; x++) {
for (int y = area.top; y < area.top + area.height; y++) {
float noise = mCharacterNoise.getNoise(x, y);
if (noise <= -0.85f) {
Vector2i tilePosition = findClosestFloor(Vector2i(x, y));
// Bad way to get a pseudo random, but deterministic value.
// Just using noise would be better, but that is not uniformly distributed.
int seed = ((int) noise * 100000) xor x xor y;
spawns.push_back(std::make_pair(Vector2f(tilePosition.x * Tile::TILE_SIZE.x,
tilePosition.y * Tile::TILE_SIZE.y), seed));
spawns.push_back(Vector2f(tilePosition.x * Tile::TILE_SIZE.x,
tilePosition.y * Tile::TILE_SIZE.y));
}
}
}

View file

@ -10,6 +10,7 @@
#include <SFML/Graphics.hpp>
#include "../sprites/abstract/Character.h"
#include "../sprites/Tile.h"
#include "SimplexNoise.h"
#include "../util/Vector.h"
@ -23,9 +24,10 @@ class Pathfinder;
class Generator : public sf::Drawable {
public:
explicit Generator(World& world, Pathfinder& pathfinder);
void generateCurrentAreaIfNeeded(const Vector2f& position);
void generateCurrentAreaIfNeeded(const Vector2f& position,
const Character::EquippedItems& playerItems);
Vector2f getPlayerSpawn() const;
std::vector<std::pair<Vector2f, float> > getEnemySpawns(const sf::IntRect& area);
std::vector<Vector2f> getEnemySpawns(const sf::IntRect& area);
private:
typedef std::map<int, std::map<int, Tile::Type> > array;

View file

@ -21,33 +21,46 @@
* @param seed Random value to seed random number generator.
*/
Enemy::Enemy(World& world, Pathfinder& pathfinder,
const Vector2f& position, float seed) :
const Vector2f& position, const EquippedItems& playerItems) :
Character(position, CATEGORY_ACTOR, MASK_ALL, Yaml("enemy.yaml"), world,
pathfinder) {
std::ranlux24_base generator(seed);
pathfinder, generateItems(playerItems)) {
}
// Select primary weapon.
switch (std::uniform_int_distribution<int>(0, 4)(generator)) {
case 0: setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::ASSAULT_RIFLE)); break;
case 1: setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::AUTO_SHOTGUN)); break;
case 2: setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::HMG)); break;
case 3: setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::RIFLE)); break;
case 4: setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::SHOTGUN)); break;
/**
* Returns the items this enemy has equipped, based on items equipped by player.
*
* To do this, a random item is replaced by a slightly better one.
*/
Character::EquippedItems
Enemy::generateItems(EquippedItems playerItems) {
// Uses cast from enum to int to enum in order to increment enum values.
switch (rand() % 4) {
case 0:
if (playerItems.primary + 1 != Weapon::WeaponType::_LAST)
playerItems.primary =
(Weapon::WeaponType) (((int) (playerItems.primary) + 1));
break;
case 1:
if (playerItems.secondary + 1 != Weapon::WeaponType::_LAST)
playerItems.secondary =
(Weapon::WeaponType) (((int) (playerItems.secondary) + 1));
break;
case 2:
if (playerItems.left + 1 != Gadget::GadgetType::_LAST)
playerItems.left = (Gadget::GadgetType) (((int) (playerItems.left)
+ 1));
break;
case 3:
if (playerItems.right + 1 != Gadget::GadgetType::_LAST)
playerItems.left = (Gadget::GadgetType) (((int) (playerItems.left)
+ 1));
break;
}
// Select secondary weapon.
switch (std::uniform_int_distribution<int>(0, 1)(generator)) {
case 0: setSecondWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::PISTOL)); break;
case 1: setSecondWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::KNIFE)); break;
}
// Select gadget.
switch (std::uniform_int_distribution<int>(0, 2)(generator)) {
case 0: setLeftGadget(std::shared_ptr<Gadget>(new Heal())); break;
case 1: setLeftGadget(std::shared_ptr<Gadget>(new Shield())); break;
case 2: setLeftGadget(std::shared_ptr<Gadget>(new RingOfFire(world))); break;
}
return playerItems;
}
void

View file

@ -15,10 +15,11 @@ class World;
class Enemy : public Character {
public:
explicit Enemy(World& world, Pathfinder& pathfinder,
const Vector2f& position, float seed);
const Vector2f& position, const EquippedItems& playerItems);
private:
virtual void onThink(int elapsed) override;
static EquippedItems generateItems(EquippedItems playerItems);
};
#endif /* DG_ENEMY_H_ */

View file

@ -16,12 +16,10 @@
* Initializes Sprite.
*/
Player::Player(World& world, Pathfinder& pathfinder,
const Vector2f& position) :
const Vector2f& position, const EquippedItems& items) :
Character(position, CATEGORY_ACTOR, MASK_ALL, Yaml("player.yaml"), world,
pathfinder),
pathfinder, items),
mDirection(0) {
setFirstWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::PISTOL));
setSecondWeapon(Weapon::getWeapon(world, *this, Weapon::WeaponType::KNIFE));
}
Vector2f

View file

@ -29,7 +29,7 @@ public:
public:
explicit Player(World& world, Pathfinder& pathfinder,
const Vector2f& position);
const Vector2f& position, const EquippedItems& items);
Vector2f getCrosshairPosition() const;
void setCrosshairPosition(const Vector2f& position);

View file

@ -25,7 +25,7 @@ const float Character::ITEM_PICKUP_MAX_DISTANCE = 50.0f;
*/
Character::Character(const Vector2f& position, Category category,
unsigned short mask, const Yaml& config, World& world,
Pathfinder& pathfinder) :
Pathfinder& pathfinder, const EquippedItems& items) :
Circle(position, category, mask, config),
mWorld(world),
mPathfinder(pathfinder),
@ -35,6 +35,10 @@ Character::Character(const Vector2f& position, Category category,
mActiveWeapon(mFirstWeapon),
mLastPosition(getPosition()),
mFaction((Faction) config.get("faction", 1)) {
setFirstWeapon(Weapon::getWeapon(world, *this, items.primary));
setSecondWeapon(Weapon::getWeapon(world, *this, items.secondary));
setLeftGadget(Gadget::getGadget(world, items.left));
setRightGadget(Gadget::getGadget(world, items.right));
}
Character::~Character() {
@ -346,3 +350,13 @@ Character::pickUpItem() {
}
mWorld.remove(closest);
}
Character::EquippedItems
Character::getEquippedItems() const {
return {
(mFirstWeapon) ? mFirstWeapon->getType() : Weapon::WeaponType::NONE,
(mSecondWeapon) ? mSecondWeapon->getType() : Weapon::WeaponType::NONE,
(mLeftGadget) ? mLeftGadget->getType() : Gadget::GadgetType::NONE,
(mRightGadget) ? mRightGadget->getType() : Gadget::GadgetType::NONE
};
}

View file

@ -11,10 +11,10 @@
#include "Circle.h"
#include "../items/Gadget.h"
#include "../items/Weapon.h"
class Pathfinder;
class World;
class Weapon;
class Yaml;
/**
@ -27,6 +27,14 @@ public:
FACTION_ENEMIES = 2
};
struct EquippedItems {
public:
Weapon::WeaponType primary;
Weapon::WeaponType secondary;
Gadget::GadgetType left;
Gadget::GadgetType right;
};
/// Maximum distance where an enemy will be detected.
static const float VISION_DISTANCE;
/// Maximum distance from character where an item can be picked up.
@ -35,11 +43,12 @@ public:
public:
explicit Character(const Vector2f& position, Category category,
unsigned short mask, const Yaml& config, World& world,
Pathfinder& pathfinder);
Pathfinder& pathfinder, const EquippedItems& items);
virtual ~Character() = 0;
void onDamage(int damage);
Faction getFaction() const;
EquippedItems getEquippedItems() const;
protected:
virtual void onThink(int elapsed);
@ -69,8 +78,6 @@ protected:
std::string getRightGadgetName() const;
void pickUpItem();
protected:
private:
void move();
void dropItem(std::shared_ptr<Item> item);

View file

@ -8,12 +8,32 @@
#include "Gadget.h"
#include "../abstract/Character.h"
#include "Heal.h"
#include "Shield.h"
#include "RingOfFire.h"
Gadget::Gadget(std::string name) :
Item(sf::Vector2f(32, 32), "item.png"),
mName(name) {
}
/**
* Constructs a new gadget of type and returns a pointer to it.
*/
std::shared_ptr<Gadget>
Gadget::getGadget(World& world, GadgetType type) {
switch (type) {
case SHIELD:
return std::shared_ptr<Gadget>(new Shield());
case HEAL:
return std::shared_ptr<Gadget>(new Heal());
case RINGOFFIRE:
return std::shared_ptr<Gadget>(new RingOfFire(world));
default:
return std::shared_ptr<Gadget>();
}
}
void
Gadget::use(Character& character) {
if (mCooldownTimer.isExpired()) {

View file

@ -15,15 +15,29 @@
class Character;
class Gadget : public Item {
public:
/**
* Gadgets, ordered by strength.
*/
enum GadgetType {
NONE,
SHIELD,
HEAL,
RINGOFFIRE,
_LAST
};
public:
Gadget(std::string name);
static std::shared_ptr<Gadget> getGadget(World& world, GadgetType type);
void use(Character& character);
virtual void onThink(int elapsed) = 0;
std::string getName() const;
virtual GadgetType getType() const = 0;
protected:
virtual void onUse(Character& character) = 0;
virtual sf::Time getCooldownTime() = 0;
virtual sf::Time getCooldownTime() const = 0;
protected:
thor::Timer mCooldownTimer;

View file

@ -29,7 +29,12 @@ Heal::onThink(int elapsed) {
}
sf::Time
Heal::getCooldownTime() {
Heal::getCooldownTime() const {
return sf::seconds(5);
}
Gadget::GadgetType
Heal::getType() const {
return GadgetType::HEAL;
}

View file

@ -17,7 +17,8 @@ public:
protected:
void onUse(Character& character) override;
void onThink(int elapsed) override;
sf::Time getCooldownTime() override;
sf::Time getCooldownTime() const override;
GadgetType getType() const override;
private:
Character* mCharacter;

View file

@ -42,7 +42,12 @@ RingOfFire::onThink(int elapsed) {
}
sf::Time
RingOfFire::getCooldownTime() {
RingOfFire::getCooldownTime() const {
return sf::seconds(5);
}
Gadget::GadgetType
RingOfFire::getType() const {
return GadgetType::RINGOFFIRE;
}

View file

@ -22,7 +22,8 @@ public:
protected:
void onUse(Character& character) override;
void onThink(int elapsed) override;
sf::Time getCooldownTime() override;
sf::Time getCooldownTime() const override;
GadgetType getType() const override;
private:
static const int WAVES_PER_USE = 3;

View file

@ -35,6 +35,11 @@ Shield::onThink(int elapsed) {
}
sf::Time
Shield::getCooldownTime() {
Shield::getCooldownTime() const {
return sf::seconds(10);
}
Shield::GadgetType
Shield::getType() const {
return GadgetType::SHIELD;
}

View file

@ -20,7 +20,8 @@ public:
protected:
void onUse(Character& character) override;
void onThink(int elapsed) override;
sf::Time getCooldownTime() override;
sf::Time getCooldownTime() const override;
GadgetType getType() const override;
private:
Character* mCharacter;

View file

@ -13,7 +13,7 @@
#include "../effects/Bullet.h"
#include "../../util/Yaml.h"
Weapon::Weapon(World& world, Character& holder, const Yaml& config) :
Weapon::Weapon(World& world, Character& holder, const Yaml& config, WeaponType type) :
Item(Vector2f(32, 32), "item.png"),
mWorld(world),
mHolder(&holder),
@ -35,7 +35,8 @@ Weapon::Weapon(World& world, Character& holder, const Yaml& config) :
mSpread(config.get("spread", 0.0f)),
mSpreadMoving(config.get("spread_moving", 0.0f)),
mMaxRange(config.get("max_range", 0.0f)),
mRequiresAmmo(!config.get("requires_no_ammo", false)) {
mRequiresAmmo(!config.get("requires_no_ammo", false)),
mType(type) {
}
/**
@ -46,20 +47,21 @@ std::shared_ptr<Weapon>
Weapon::getWeapon(World& world, Character& holder, WeaponType type) {
switch (type) {
case WeaponType::KNIFE:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("knife.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("knife.yaml"), type));
case WeaponType::PISTOL:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("pistol.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("pistol.yaml"), type));
case WeaponType::ASSAULT_RIFLE:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("assault_rifle.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("assault_rifle.yaml"), type));
case WeaponType::SHOTGUN:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("shotgun.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("shotgun.yaml"), type));
case WeaponType::AUTO_SHOTGUN:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("auto_shotgun.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("auto_shotgun.yaml"), type));
case WeaponType::RIFLE:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("rifle.yaml")));
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("rifle.yaml"), type));
case WeaponType::HMG:
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("hmg.yaml")));
default: return std::shared_ptr<Weapon>();
return std::shared_ptr<Weapon>(new Weapon(world, holder, Yaml("hmg.yaml"), type));
default:
return std::shared_ptr<Weapon>();
}
}
@ -194,3 +196,8 @@ Weapon::insertProjectile(float angle) {
mDamage, mMaxRange));
mWorld.insert(projectile);
}
Weapon::WeaponType
Weapon::getType() const {
return mType;
}

View file

@ -25,18 +25,23 @@ class Yaml;
class Weapon : public Item {
public:
enum class WeaponType {
/**
* Weapons, ordered by strength.
*/
enum WeaponType {
NONE,
KNIFE,
PISTOL,
RIFLE,
ASSAULT_RIFLE,
SHOTGUN,
AUTO_SHOTGUN,
RIFLE,
HMG
HMG,
_LAST
};
public:
explicit Weapon(World& world, Character& holder, const Yaml& config);
explicit Weapon(World& world, Character& holder, const Yaml& config, WeaponType type);
static std::shared_ptr<Weapon> getWeapon(World& world, Character& holder, WeaponType type);
void pullTrigger();
@ -48,6 +53,7 @@ public:
void reload();
void cancelReload();
void setHolder(Character& holder);
WeaponType getType() const;
private:
void fire();
@ -80,6 +86,7 @@ private:
const float mMaxRange;
const float mRequiresAmmo;
std::default_random_engine mGenerator;
WeaponType mType;
};