Sign in to follow this  

Treating clients/NPCs similarly?

This topic is 4686 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Currently, my server is designed in such as fashion that both clients and NPCs are treated the same. However, the more features I implemented, the more I'm starting to think to treat them seperatly. When I started out, my client base class only had a few functions, which mostly were events that could be triggered. But now, I'm starting to add features which I totally not need in my NPCs, such as online time, an "Are you sure" prompt, and a whole lot of other features. So I'm kind of in a dilemma wether or not to split up both classes(With the exception of the common feats) and treat clients are CClient and NPCs as CNpc. Thus, hold 1 list of clients and 1 list of NPCs per room. Toolmaker

Share this post


Link to post
Share on other sites
I would hold a list of IEntities in a room, and implement polymorphism through some cheap reflection scheme -- could be as simple as a query_iterface() function taking a string for various kinds of interfaces an entity could implement.

Then, when the player tries to attack, you could query for IAttackTarget, and if it's there, ask the IAttackTarget to confirm whether it's attackable. To get time online, you'd query for IPlayerStats, and if it's there, you could extract the data.

Thus, all your entities could implement the same IEntity interface, but be type variant. Then, in the implementation, you could maybe use implementation inheritance to get code re-use -- CNPC implements IEntity; COtherPlayer derives from CNPC but overrides query_interface() to return more interfaces for more functions.

Share this post


Link to post
Share on other sites
It sounds like a good idea. So basicly, I have a basic class, the ClientBase. I derive my Player and NPCs from them and then define for certain features an interface, such as PlayerStats(Which also holds the stats, such as online time, name, showname, finger info), ConfirmPrompt(holds state, etc.) and so on.

Next, for instance when I need the confirmprompt, I query if the current client has one and then invoke it when needed.

Sounds like a really good idea. However, I have 1 problem with this, since it's a MUD. How am I going to make a fundamental difference between players and NPCs? Because my room info looks like this:

<Room Name>
<Room Desc>
Exits from this room:
<exits>
Players in this room:
<players>
NPCs in this room:
<NPCs>
Items in this room:
<Items>


Toolmaker

Share this post


Link to post
Share on other sites
Quote:


Players in this room:
<players>
NPCs in this room:
<NPCs>
Items in this room:
<Items>



You should stick all entities into a single list of entities. Then, when printing the data, you could do something like:


template< class Filter >
class Print {
public:
void operator()( IEntity * e ) {
if( f( e ) ) {
outstream << e->name() << std::endl;
}
}
Filter f;
};
class PlayerFilter {
public:
bool operator()( IEntity * e ) {
return e->query_interface( "IPlayer" ) != 0;
}
};
std::for_each( entities.begin(), entities.end(), Print<PlayerFilter>() );
std::for_each( entities.begin(), entities.end(), Print<NPCFilter>() );
std::for_each( entities.begin(), entities.end(), Print<ItemFilter>() );


If entities is a vector, then iterating 3x instead of 1x isn't going to be very noticeable CPU hit. If it's a less efficient container for iteration, then you might want to instead walk the list once and sort into three separate temporary containers, and then print each of those.

The base idea is to keep all entities together, and then sort them out only if and when you need to, and come up with faster/more optimized local solutions only when a profiler tells you that you need to. You need to optimize your overall algorithms (n-cubed or n-log-n ?) early on, but the specifics of containers and iteration can often wait until much later.

Share this post


Link to post
Share on other sites
I've been considering doing somthing similar but going to the point of having NPCs be independant processes that may run on the same computer but actually log into the game with slightly different permissions but very similar to a PC. I like the idea because you can do alot of work on the AI of the NPC without worrying about interfering with other parts of the program.

Share this post


Link to post
Share on other sites
I'm not making the NPCs actual clients that connect over winsock to the server for the simple reason that I don't want to have over 1000 connections just for some NPCs that aren't interacted with half of the time anyway. Also, I still need to send them data, as opposing to just let them ignore it.

Hplus:
Your ideas are really neat, and I'll rewrite a my entity system to use it. Much better than what I have now. As design isn't my best side(And I'm gonna learn lots about it coming year) I still have a few questions, hoping you, or someone else can answer.

So, I basicly make 1 entity class(IEntity) and add interfaces to it. That kind of brings down my class to:

class IEntity
{
public:
IEntity();
~IEntity();

void Update();
void AddInterface(IInterface *i);
IInterface * QueryInterface(const string &strInterface);

// Perhaps events such as RoomEnter, RoomLeave, etc.
private:
map<string, IInterface *> m_Interfaces;
};


I assume I'll be adding interfaces during creation of the objects. Such as a player logins, the login manager spawns a new IEntity object, and next adds in the interfaces, such as the IPlayer, INPC, IShopkeeper, IAttackable, IWEapon, IPowerup, etc.

Do I got it straight like this?

Toolmaker

Share this post


Link to post
Share on other sites
Quote:
Original post by Toolmaker
I assume I'll be adding interfaces during creation of the objects. Such as a player logins, the login manager spawns a new IEntity object, and next adds in the interfaces, such as the IPlayer, INPC, IShopkeeper, IAttackable, IWEapon, IPowerup, etc.

Do I got it straight like this?


He's not saying 'add interfaces' to the entity, he's saying 'implement interfaces'.



class Entity
{
...
};

class Actor : public Entity
{
...
};

class NPC : public Actor
{
};

class Player : public Actor
{
};





There are several methods you can use to implement the interface query. One of the simplest would be to implement it via an enum.



class Entity
{
public:
enum Type
{
ActorType,
NPCType,
PlayerType
}

virtual Entity *queryInterface(Type type) = 0;
};

class Actor : public Entity
{
public:
virtual Entity *queryInterface(Type type)
{
if(Test::ActorType == type)
return this;

return null
}
};

class NPC : public Actor
{
public:
virtual Entity *queryInterface(Type type)
{
if(Test::NPCType == type)
return this;

return Actor::queryInterface(type);
}
};





Every Entity subclass would then implement queryInterface in the same fashion. Each class is only interested in matching its own type. By passing the query up the heirarchy when a match is not found, you avoid the need to do complicated boolean OR tests. This system, while simple, is not very scalable or extensible. You could knock up a more dynamic system using the built-in RTTI mechanism, but that might not always be desirable. An appendix in Dave Eberly's '3D Game Engine Design', and one of the gems in 'Game Programming Gems 2' both show alternative ways to implement a similar system. But for a small scale project, a simple enum based system should work fine.

Share this post


Link to post
Share on other sites
Actually I think hplus0603's meaning was that a single entity would have multiple interaction interfaces. This allows more reusability between components, which is good because Toolmaker said that many features are shared between client code and NPC code...

Where it might have been misleading is when he mentioned that the concrete implementations of IEntity could overload query_interface to provide different sets of interfaces.
Toolmaker's idea of using a map to do this, rather than overloading the class, might be good as it allows quite a lot of flexibility.

Share this post


Link to post
Share on other sites
Quote:
Original post by lucky_monkey
Actually I think hplus0603's meaning was that a single entity would have multiple interaction interfaces.


Indeed [embarrass] With that in mind, section 2 of this article may give some useful implementation ideas. Pay particular attention to what they call 'properties' and 'protocol sets'.

EDIT: corrected the link to the article

[Edited by - Aldacron on February 12, 2005 2:10:10 AM]

Share this post


Link to post
Share on other sites
Thanks for all the replies! So, I now have both ideas in my mind, because both have some nice uses and might end up good. Perhaps I should experiment with them for a bit.

Right now, I wrote a simple test application which had a few interfaces and I implemented it by inheriting from the IEntity interface and the other interfaces. So basicly, I got this(Just a little experiment).


enum InterfaceType
{
Entity = 0,
Attackable,
Item,
ItemContainer,
Player,
NPC,
};

class IEntity
{
public:
IEntity();
virtual ~IEntity();

virtual bool IsUpdateable() = 0;
virtual bool Update() = 0;
virtual IEntity *QueryInterface(InterfaceType Interface)
{
if (Interface == m_Type)
return (this);
return (null);
}

virtual void SetName(string strName) { m_strName = strName; }
virtual const string& GetName() { return m_strName; }

protected:
string m_strName;
InterfaceType m_Type;
};

class IAttackable : public IEntity
{
public:
IAttackable(int hp, int armor, int weapon):
m_Type(Attackable), m_Hp(hp), m_Armor(armor), m_Weapon(weapon) {}

virtual ~IAttackable() {}

virtual bool IsUpdatable() { return false; }
virtual bool Update() { return false; }

virtual bool Attack(int weapon);
virtual int GetHP() { return m_Hp; }
virtual void SetArmor(int armor) { m_Armor = armor; }
virtual int GetWeapon() { return m_Weapon; }
virtual void SetWeapon(int weapon) { m_Weapon = weapon; }

protected:
int m_Hp;
int m_Armor;
int m_Weapon;
};

class IItem : public IEntity
{
public:
IItem():m_Type(Item) {}

virtual ~IItem();
virtual bool IsUpdateable() { return (false); }
virtual bool Update() { return (false); }
};

class IBackpack : public IEntity
{
public:
IBackpack():m_Type(ItemContainer) {}

virtual ~IBackpack();
virtual bool IsUpdateable() { return (true); }
virtual bool Update() { return (true); }

size_t GetItemCount() { return m_Items.size()); }
IItem *GetItem(size_t nPos) { return m_Items[nPos]; }
void Add(IItem *pItem) { m_Items.push_back(pItem); }

protected:
vector<IItem *> m_Items;
};

class IPlayer : public IAttackable, IBackPack
{
public:
IPlayer(const string& strName): m_Type(Player), m_strName(strName) {}

IEntity *QueryInterface(InterfaceType Interface)
{
switch (Interface)
{
case Player:
return (this);
break;

case Attackable:
return IAttackable::QueryInterface(Interface);
break;

case ItemContainer:
return IBackPack::QueryInterface(Interface);
break;

default:
return (null);
}
}
};



I'm for sure it doesn't compile, but it this what you guys meant?

Toolmaker

Share this post


Link to post
Share on other sites
Quote:
Original post by Toolmaker
I could use some feedback on my last piece of code. Any ideas?


I would suggest you make the type a bitfield suitable for masking. Then you can have subclasses that implement more than one interface. Update() itself might even become an interface. For example:



// interface types
#define Entity 0<<0
#define Attackable 1<<0
#define Item 1<<1
#define ItemContainer 1<<2
#define Player 1<<3
#define NPC 1<<4
#define Updateable 1<<5

class IEntity
{
public:
IEntity() { AddType(Entity); }
virtual ~IEntity();

virtual IEntity *QueryInterface(InterfaceType Interface)
{
if ((Interface && m_Type) == 1)
return (this);
return (null);
}

virtual void SetName(string strName) { m_strName = strName; }
virtual const string& GetName() { return m_strName; }

void SetType(int Type) { m_Type = Type; }
void AddType(int Type) { m_Type |= Type; }
void RemoveType(int Type) { m_Type ~= Type; }

protected:
string m_strName;
int m_Type;
};

class IUpdateable : public IEntity
{
public:
IUpdateable() { AddType(Updateable); }
~IUpdateable() {};

virtual bool Update() { return false; }
}

class IAttackable : public IUpdateable
{
public:
IAttackable(int hp, int armor, int weapon):
m_Hp(hp), m_Armor(armor), m_Weapon(weapon)
{
AddType(Attackable);
}

virtual ~IAttackable() {}

virtual bool Update() { return true; }

virtual bool Attack(int weapon);
virtual int GetHP() { return m_Hp; }
virtual void SetArmor(int armor) { m_Armor = armor; }
virtual int GetWeapon() { return m_Weapon; }
virtual void SetWeapon(int weapon) { m_Weapon = weapon; }

protected:
int m_Hp;
int m_Armor;
int m_Weapon;
};

class IItem : public IUpdateable
{
public:
IItem():m_Type(Item) { AddType(Item | Updateable); }

virtual ~IItem();
virtual bool Update() { return false; }
};

class IBackpack : public IUpdateable
{
public:
IBackpack(): { AddType(ItemContainer | Updateable); }

virtual ~IBackpack();
virtual bool Update() { return true; }

size_t GetItemCount() { return m_Items.size()); }
IItem *GetItem(size_t nPos) { return m_Items[nPos]; }
void AddItem(IItem *pItem) { m_Items.push_back(pItem); }

protected:
vector<IItem *> m_Items;
};

class IPlayer : public IAttackable, IBackPack
{
public:
IPlayer(const string& strName): m_strName(strName)
{
AddType(Player);
}
};





This code is probably bug ridden, but hopefully you get the idea.

It's important to mention that interfaces should imply functionality, not type.

It should also be noted that in this system, the Entity interface is entirely redundant.

Your game loop might include something like this:


// update all entities

IEntity* entity = GetFirstEntity();

while (entity)
{
IUpdateable* update = entity->QueryInterface(Updateable);

if (update)
update->Update();

entity = GetNextEntity(entity);
}



YMMV. This whole system remains less than ideal in several ways.

Share this post


Link to post
Share on other sites
Quote:
Original post by TheBren
It's important to mention that interfaces should imply functionality, not type.
I'd probably use aggregation to model this rather than inheritance (similar to IEntity in Toolmaker's post earlier). This most probably requires upcasting from IInterface to a derived interface, but this should be ok as long as you can ensure that query_interface returns the correct interface (or none at all).

Share this post


Link to post
Share on other sites

This topic is 4686 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this