Items and AI

posted in trill41 for project ABx
Published April 21, 2019
Advertisement

Items

I was thinking about weapons and equipment's, which all are Items. So sooner or later some item system must be implemented. All items are stored in a database table (let's name it game_items) with a name, value, the type of the item (weapon, armor, crap...), a 3D model file (which is loaded by the client) and an optional Lua script file, because e.g. weapons are scriptable.

In OOP terms the game_items table has a list of item classes. Now we just need another table with the objects, because when an item drops for a player and the player picks it up, the player doesn't want that this item disappears, so it must be stored in the database. Let's name this table concrete_items.

This table contains what item it is (so the primary key of the item in the game_items table), where it is stored, to which player it belongs, item stats (like damage for weapons), and some other information like creation time, value etc.

Drops

When NPCs die, they may or may not drop a random item. Each item has a certain chance to drop on a certain map. This requires another database table which associates an item with a map and a chance to drop on this map.

Selecting a random item from a pool of items that may drop on the current map isn't really hard, there are algorithms that do that, like WalkerMethod which is O(1), or my adaption of it.

Items drop for certain players, other players can not pickup an item dropped for a different player. When an NPC dies and it drops an item, an item is picked from the item pool and a new concrete item is created and inserted into the database with random stats. A random player is selected from the party that killed the NPC and this new item drops for this player.

If a player picks up an item, it is added the the players inventory. Technically just the storage place of the concrete item is changed from Scene to Inventory.

itemdrop800.jpg.09174438de2efe1fd3db05f7eb294944.jpg

AI

First I thought I'll make a PvP only game because then I don't need to mess around with game AI, but I realized even PvP games need some NPCs that have some kind of AI. Then I came across a behavior tree library (SimpleAI) that seemed to be a nice fit. It was really easy to integrate this library into the game server.

ai::Zone

A Game instance has a Map object with the terrain, navigation mesh, the octree and now an ai::Zone object.

ai::ICharacter and ai::AI

The class hierarchy of game objects looks a bit like this.

classes.png.c96675b5b74a5b9e7388e2795acb3969.png

SimpleAI controls the NPC via the ai::ICharacter class. The NPC class could just inherit also from the ai::ICharacter class, but I try to avoid multiple inheritance where I can, so the NPC class has a ai::ICharacter member.

Then the NPC has an ai::AI object which has the behavior and does all the "intelligent" stuff.

Behaviors

Behaviors are defined with simple Lua scripts, e.g.:


-- Try to stay alive
function stayAlive(parentnode)
  -- Executes all the connected children in the order they were added (no matter what
  -- the TreeNodeStatus of the previous child was).
  local parallel = parentnode:addNode("Parallel", "stayalive")
  parallel:setCondition("IsSelfHealthLow")
    parallel:addNode("HealSelf", "healself")
  -- TODO: Flee
end

-- Heal an ally
function healAlly(parentnode)
  local parallel = parentnode:addNode("Parallel", "healally")
  parallel:setCondition("And(IsAllyHealthLow,Filter(SelectLowHealth))")
    parallel:addNode("HealOther", "healother")
end

-- Do nothing
function idle(parentnode)
  -- This node tries to execute all the attached children until one succeeds. This composite only
  -- fails if all children failed, too.
  local prio = parentnode:addNode("PrioritySelector", "idle")
    prio:addNode("Idle{1000}", "idle1000")
end

function initPriest()
  local name = "PRIEST"
  local rootNode = AI.createTree(name):createRoot("PrioritySelector", name)
  stayAlive(rootNode)
  healAlly(rootNode)
  -- ...
  idle(rootNode)
end

So now we have a behavior with the name "PRIEST" and all NPCs with this behavior try to stay alive, heal an ally or do nothing.

Conditions, Filters and Actions

Of course the IsSelfHealthLow is not part of SimpleAI (although it already comes with a set of Conditions, Filters and Actions). The IsSelfHealthLow condition just checks if the health points of the NPC is under some threshold:


class IsSelfHealthLow : public ai::ICondition
{
public:
    CONDITION_CLASS(IsSelfHealthLow)
    CONDITION_FACTORY(IsSelfHealthLow)

    bool evaluate(const ai::AIPtr& entity) override
    {
        const ai::Zone* zone = entity->getZone();
        if (zone == nullptr)
            return false;

        const AiCharacter& chr = entity->getCharacterCast<AiCharacter>();
        const auto& npc = chr.GetNpc();
        if (npc.IsDead())
            // Too late
            return false;
        return npc.resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD;
    }
};

If now IsSelfHealthLow::evaluate() evaluated to true it executes the action HealSelf which is again a C++ class. It tries to find a skill that do some self healing, and uses it.

For just staying alive, no Filters are needed, but for a Priest that may also heal others, Filters are need to select those Allies with low health points. Such a Filter class could look like this:


void SelectLowHealth::filter(const ai::AIPtr& entity)
{
    ai::FilteredEntities& entities = getFilteredEntities(entity);
    Game::Npc& chr = getNpc(entity);
    std::map<uint32_t, std::pair<float, float>> sorting;

    chr.VisitAlliesInRange(Game::Ranges::Aggro, [&](const Game::Actor* o)
    {
        if (o->resourceComp_.GetHealthRatio() < LOW_HP_THRESHOLD)
        {
            entities.push_back(o->id_);
            sorting[o->id_] = std::make_pair<float, float>(o->resourceComp_.GetHealthRatio(), o->GetDistance(&chr));
        }
    });
    std::sort(entities.begin(), entities.end(), [&sorting](int i, int j)
    {
        const std::pair<float, float>& p1 = sorting[i];
        const std::pair<float, float>& p2 = sorting[j];
        if (fabs(p1.first - p2.first) < 0.05)
            // If same HP (max 5% difference) use shorter distance
            return p1.second < p2.second;
        return p1.first < p2.first;
    });
}

Now the HealOther class can get the filtered entities (which is just a std::vector<uint32_t> containing the IDs of the allies with low health points sorted by priority), and use some heal skill on that target.

Conclusion

SimpleAI is a great library, it is easy to integrate, easy to use and easy to configure, and as far as I can see now, it just works.

 

Previous Entry Gameplay
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement

Latest Entries

Advertisement