I haven't had a detailed look but I see HumanPlayer has NetworkInfo. Does this mean that you can't have a network game with 2 AI players and no human players?
My thought was that the AI player only needs to really *be* on the server, so I didn't give them any network information since it's presumed that the server takes care of that, although the client will continue to execute their own AI such that it will predict the server AI's behavior. You can have a network game with 2 AI players and at least two HumanPlayers who are spectators. I haven't thought out any functionality to allow AIs to run on an alternate machine however because I figured the server should be "in charge" of that. Even if somebody else has their own custom AI script, I'm thinking we should require that to be on the server as well (and the whole checksum process to make sure they're using the same version of the script, etc.).
Could you please give more details on what is the purpose of FactionController? I mean, isn't it possible to reference factions directly from player?
Yea, well you're problem is that you're just too damn good!
Yes, when I actually went about implementing it I indeed did exactly as you suggest!
I guess when I was designing this I was thinking more from the standpoint of how it would look when set across the network as a message. Of course, remember that these are the game settings (for the most part). So now the GameSettings::Faction class has a "Players players" data member (Players is a typedef of vector<Player*>). Additionally, the GameSettings object has a member of the same type and name as well that contains all players. So the players object of GameSettings contains all players, AI and human, and the GameSettings::Faction players data member contains all of the players that control that faction, with the primary always being first. The distinction of primary is important because of some of the game settings that affect how units from that faction are managed (namely the GsAutoRepair and GsAutoReturn from the .ini file, but this could expand in the future).
As far as something to point from the Player object back to the factions they control, I honestly hadn't gotten that far yet so I'm not certain if it will be needed. However, the Faction class in the Game namespace (i.e., the "real" faction class) now has a "Players &players" data member as well. I'm not 100% certain of the design just yet (as far as creational patterns and object life-cycle) but I think that Game::Faction objects will keep a reference to either the the Player object that GameSettings::Faction owns or, or just the GameSettings::Faction object its self (which is essentially the settings for that faction) as the GameSettings object will exist for the duration of the game and should, thus, remain valid. Part of my thoughts on this is that midway through a game, a player can drop off. If there is more than one player controlling a faction, then perhaps they will just continue to play with less players on that faction. Of perhaps the human player is replaced by an AI player. Either way, this means that we need to change the GameSettings object mid-game so it needs to be dynamic throughout the game's lifetime. Thus, I would rather the data be kept in one place and have the Game::Faction object simply refer to that directly.
I'm not sure, but wouldn't it be more natural that the sole difference between HumanPlayer and AIPlayer is use of different command sources/sinks? I.e. for HumanPlayer you'd have Player instance and NetworkCommander (or like) instance attached to it and for AIPlayer you'd have just another Player instance and AICommander instance attached to that other other Player instance. This way should give you better decoupling of player related stuff and AI or network interaction stuff. And, well, it will let you have network games with AI players only (although, I don't see why you might want this )
hmm... I think the short answer is that there is a completely different class hierarchy to manage command generation & processing. The Game::Player and derived classes are primarily used to communicate player information from one host to another, although they are used outside of networking as well (which is why they are in the Game namespace and not Game::Net). Commands by the local human player are generated via clicks & key input as processed by the Game::Gui class, although a few things related to game settings (pause, speed changes, etc.) that will generate network traffic are managed in the class Game::Game (Gui's base class). When the Game's update() method is called, one of the things that it does is to run each AI by calling AiInterface::update(), which creates commands for the AI.
Commands are never executed immediately, they are always queued up and then processed when the World object gets it's turn to update. I may have inadvertently removed the code from World::update() that tells the network interface to update, but that's being changed anyway. When a world update starts, the network interface will have it's beginFrame() method called and when the update is completed, it's endFrame() method will be called. The beginFrame() method retrieves & processes any complete network messages and shoves any commands received into the Commander object. During the world update, auto-commands are commonly generated (auto-attack, auto-repair, auto-flee, etc.) and these are given to the Commander as well. When the game is a network game, the Commander hands them to the network interface (Game::Net::GameInterface actually). Thus, at the end of the world update, all of these commands plus any commands that were issued via the user interface or AI (when the Game object was updating) are all sent at once. Starting in version 0.2.12, they will be sent with a target frame (which will later be replaced by a target game time). This is to ensure that all players in a network game execute the command at the same time (it's even queued up on the machine of the person who issued the command until that future frame arrives).
So, that is how it works. If you see some ways to refactor this, please do share. I would venture to say that there is some key functionality in the World and UnitUpdater classes that could be de-coupled, but I haven't been able to isolate it yet because those two classes are such catch-alls. I would say that in the time I've worked on this project, 90% of the problems that I've had after making a change to the game are due to not fully understanding how these two classes behave (as they tend to encapsulate many of the rules of the engine).
While I'm on the topic of re-factoring, let me mention of few items on my re-factoring agenda
- Get rid of all cases where empty constructors are called and an init() or load() is later called. I want these to have proper constructors that initialize all of their members correctly the first time. It may not always be possible to kill the empty constructor, but in this case, I want it as close to that as possible.
- If a classes can render its self as xml, I want it to inherit from a new class Shared::Xml::XmlWritable.
- If it can be initialized from xml, I want it to have a constructor that accepts a const XmlNode & (followed by any other parameters it may need to initialize). If a class's objects are supposed to be able to re-initialized, then it's fine to have a separate load(const XmlNode &) function that the constructor calls, otherwise, the code to read from Xml should only be in the constructor.
- Eliminate passing around pointers as much as possible in favor of references (they are harder to screw up).
- For objects we have to allocate, I want to start moving towards using smart pointers (I've already had to start doing this with GameSettings because it was just getting unwieldy trying to manage who owned a particular GameSettings object at a give point in time).
- All enums and other constants should follow standard C/C++ naming conventions (i.e., the value names should be UPPER_CASE)