Add AI handling for online racing games

This commit is contained in:
Benau 2019-10-15 16:19:30 +08:00
parent 170e4be0ca
commit 1614868b5d
6 changed files with 143 additions and 28 deletions

View File

@ -1440,7 +1440,7 @@ int handleCmdLine(bool has_server_config, bool has_parent_process)
{
std::string cmd =
std::string("--stdout=server_ai.log --no-graphics"
" --auto-connect --connect-now=127.0.0.1:") +
" --connect-now=127.0.0.1:") +
StringUtils::toString(STKHost::get()->getPrivatePort()) +
" --no-console-log --network-ai="
+ StringUtils::toString(ai_num);

View File

@ -536,6 +536,7 @@ void ServerLobby::setup()
m_item_seed = 0;
m_winner_peer_id = 0;
m_client_starting_time = 0;
m_ai_count = 0;
auto players = STKHost::get()->getPlayersForNewGame();
if (m_game_setup->isGrandPrix() && !m_game_setup->isGrandPrixStarted())
{
@ -547,6 +548,11 @@ void ServerLobby::setup()
for (auto player : players)
player->setKartName("");
}
if (auto ai = m_ai_peer.lock())
{
for (auto player : ai->getPlayerProfiles())
player->setKartName("");
}
StateManager::get()->resetActivePlayers();
// We use maximum 16bit unsigned limit
@ -664,7 +670,8 @@ void ServerLobby::kickHost(Event* event)
NetworkString& data = event->data();
uint32_t host_id = data.getUInt32();
std::shared_ptr<STKPeer> peer = STKHost::get()->findPeerByHostId(host_id);
if (peer)
// Ignore kicking ai peer if ai handling is on
if (peer && (!ServerConfig::m_ai_handling || !peer->isAIPeer()))
peer->kick();
} // kickHost
@ -1113,7 +1120,7 @@ void ServerLobby::asynchronousUpdate()
m_timeout.store(std::numeric_limits<int64_t>::max());
}
if (m_timeout.load() < (int64_t)StkTime::getMonoTimeMs() ||
(checkPeersReady() &&
(checkPeersReady(true/*ignore_ai_peer*/) &&
(int)players >= ServerConfig::m_min_start_game_players))
{
resetPeersReady();
@ -1151,7 +1158,8 @@ void ServerLobby::asynchronousUpdate()
// m_server_has_loaded_world is set by main thread with atomic write
if (m_server_has_loaded_world.load() == false)
return;
if (!checkPeersReady())
if (!checkPeersReady(
ServerConfig::m_ai_handling && m_ai_count == 0/*ignore_ai_peer*/))
return;
// Reset for next state usage
resetPeersReady();
@ -1188,6 +1196,17 @@ void ServerLobby::asynchronousUpdate()
ItemManager::updateRandomSeed(m_item_seed);
m_game_setup->setRace(winner_vote);
auto players = STKHost::get()->getPlayersForNewGame();
auto ai = m_ai_peer.lock();
if (supportsAI() && ai)
{
auto ai_profiles = ai->getPlayerProfiles();
if (m_ai_count > 0)
{
ai_profiles.resize(m_ai_count);
players.insert(players.end(), ai_profiles.begin(),
ai_profiles.end());
}
}
m_game_setup->sortPlayersForGrandPrix(players);
m_game_setup->sortPlayersForGame(players);
for (unsigned i = 0; i < players.size(); i++)
@ -1795,7 +1814,7 @@ void ServerLobby::update(int ticks)
Log::info("ServerLobby", "End of game message sent");
break;
case RESULT_DISPLAY:
if (checkPeersReady() ||
if (checkPeersReady(true/*ignore_ai_peer*/) ||
(int64_t)StkTime::getMonoTimeMs() > m_timeout.load())
{
// Send a notification to all clients to exit
@ -2016,10 +2035,33 @@ void ServerLobby::startSelection(const Event *event)
m_available_kts.second.erase(track_erase);
}
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL)
{
unsigned max_player = 0;
STKHost::get()->updatePlayers(&max_player);
if (auto ai = m_ai_peer.lock())
{
if (supportsAI())
{
unsigned total_ai_available =
(unsigned)ai->getPlayerProfiles().size();
m_ai_count = max_player > total_ai_available ?
0 : total_ai_available - max_player + 1;
// Disable ai peer for this game
if (m_ai_count == 0)
ai->setValidated(false);
else
ai->setValidated(true);
}
else
{
ai->setValidated(false);
m_ai_count = 0;
}
}
else
m_ai_count = 0;
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL)
{
auto it = m_available_kts.second.begin();
while (it != m_available_kts.second.end())
{
@ -2842,6 +2884,7 @@ void ServerLobby::connectionRequested(Event* event)
// Reject non-valiated player joinning if WAN server and not disabled
// encforement of validation, unless it's player from localhost or lan
// And no duplicated online id or split screen players in ranked server
// AIPeer only from lan and only 1 if ai handling
std::set<uint32_t> all_online_ids =
STKHost::get()->getAllPlayerOnlineIds();
bool duplicated_ranked_player =
@ -2853,7 +2896,11 @@ void ServerLobby::connectionRequested(Event* event)
NetworkConfig::get()->isWAN() &&
ServerConfig::m_validating_player) ||
(ServerConfig::m_strict_players &&
(player_count != 1 || online_id == 0 || duplicated_ranked_player)))
(player_count != 1 || online_id == 0 || duplicated_ranked_player)) ||
(peer->isAIPeer() && !peer->getAddress().isLAN()) ||
(peer->isAIPeer() &&
ServerConfig::m_ai_handling && !m_ai_peer.expired()) ||
(peer->isAIPeer() && m_game_setup->isGrandPrix()))
{
NetworkString* message = getNetworkString(2);
message->setSynchronous(true);
@ -2865,6 +2912,9 @@ void ServerLobby::connectionRequested(Event* event)
return;
}
if (ServerConfig::m_ai_handling && peer->isAIPeer())
m_ai_peer = peer;
if (encrypted_size != 0)
{
m_pending_connection[peer] = std::make_pair(online_id,
@ -2984,7 +3034,7 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr<STKPeer> peer,
peer->addPlayer(player);
}
peer->setValidated();
peer->setValidated(true);
// send a message to the one that asked to connect
NetworkString* server_info = getNetworkString();
@ -3053,7 +3103,7 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr<STKPeer> peer,
}
}
#ifdef ENABLE_SQLITE3
if (m_server_stats_table.empty())
if (m_server_stats_table.empty() || peer->isAIPeer())
return;
std::string query;
if (ServerConfig::m_ipv6_server && !peer->getIPV6Address().empty())
@ -3135,6 +3185,31 @@ void ServerLobby::updatePlayerList(bool update_when_reset_server)
return;
auto all_profiles = STKHost::get()->getAllPlayerProfiles();
// N - 1 AI
auto ai = m_ai_peer.lock();
if (supportsAI() && ai)
{
auto ai_profiles = ai->getPlayerProfiles();
if (m_state.load() == WAITING_FOR_START_GAME ||
update_when_reset_server)
{
if (all_profiles.size() > ai_profiles.size())
ai_profiles.clear();
else if (!all_profiles.empty())
{
ai_profiles.resize(
ai_profiles.size() - all_profiles.size() + 1);
}
}
else
{
// Use fixed number of AI calculated when started game
ai_profiles.resize(m_ai_count);
}
all_profiles.insert(all_profiles.end(), ai_profiles.begin(),
ai_profiles.end());
}
NetworkString* pl = getNetworkString();
pl->setSynchronous(true);
pl->addUInt8(LE_UPDATE_PLAYER_LIST)
@ -3390,6 +3465,8 @@ bool ServerLobby::handleAllVotes(PeerVote* winner_vote,
auto peers = STKHost::get()->getPeers();
for (auto peer : peers)
{
if (peer->isAIPeer())
continue;
if (peer->hasPlayerProfiles() && !peer->isWaitingForGame())
cur_players += 1.0f;
}
@ -3862,6 +3939,9 @@ void ServerLobby::addWaitingPlayersToGame()
getRankingForPlayer(peer->getPlayerProfiles()[0]);
}
}
// Re-activiate the ai
if (auto ai = m_ai_peer.lock())
ai->setValidated(true);
} // addWaitingPlayersToGame
//-----------------------------------------------------------------------------
@ -4465,3 +4545,27 @@ void ServerLobby::saveInitialItems()
assert(nim);
nim->saveCompleteState(m_items_complete_state);
} // saveInitialItems
//-----------------------------------------------------------------------------
bool ServerLobby::supportsAI()
{
return getGameMode() == 3 || getGameMode() == 4;
} // supportsAI
//-----------------------------------------------------------------------------
bool ServerLobby::checkPeersReady(bool ignore_ai_peer) const
{
bool all_ready = true;
for (auto p : m_peers_ready)
{
auto peer = p.first.lock();
if (ignore_ai_peer && peer->isAIPeer())
continue;
if (!peer)
continue;
all_ready = all_ready && p.second;
if (!all_ready)
return false;
}
return true;
} // checkPeersReady

View File

@ -116,6 +116,8 @@ private:
* (disconnected). */
std::weak_ptr<STKPeer> m_server_owner;
std::weak_ptr<STKPeer> m_ai_peer;
std::atomic<uint32_t> m_server_owner_id;
/** Official karts and tracks available in server. */
@ -207,6 +209,9 @@ private:
uint64_t m_client_starting_time;
// Calculated before each game started
unsigned m_ai_count;
// connection management
void clientDisconnected(Event* event);
void connectionRequested(Event* event);
@ -227,19 +232,7 @@ private:
void updateServerOwner();
void handleServerConfiguration(Event* event);
void updateTracksForMode();
bool checkPeersReady() const
{
bool all_ready = true;
for (auto p : m_peers_ready)
{
if (p.first.expired())
continue;
all_ready = all_ready && p.second;
if (!all_ready)
return false;
}
return true;
}
bool checkPeersReady(bool ignore_ai_peer) const;
void resetPeersReady()
{
for (auto it = m_peers_ready.begin(); it != m_peers_ready.end();)
@ -333,6 +326,7 @@ private:
void testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const;
void writeDisconnectInfoTable(STKPeer* peer);
void writePlayerReport(Event* event);
bool supportsAI();
public:
ServerLobby();
virtual ~ServerLobby();

View File

@ -385,6 +385,12 @@ namespace ServerConfig
"empty to disable. "
"This table can be shared for all servers if you use the same name."));
SERVER_CFG_PREFIX BoolServerConfigParam m_ai_handling
SERVER_CFG_DEFAULT(BoolServerConfigParam(false, "ai-handling",
"If true this server will auto add / remove AI connected with "
"network-ai=x, which will kick N - 1 bot(s) where N is the number "
"of human players. Only use this for non-GP racing server."));
// ========================================================================
/** Server version, will be advanced if there are protocol changes. */
static const uint32_t m_server_version = 6;

View File

@ -266,7 +266,11 @@ STKHost::STKHost(bool server)
if (addr.port == 0 && !UserConfigParams::m_random_server_port)
addr.port = stk_config->m_server_port;
// Reserve 1 peer to deliver full server message
m_network = new Network(ServerConfig::m_server_max_players + 1,
int peer_count = ServerConfig::m_server_max_players + 1;
// 1 more peer to hold ai peer
if (ServerConfig::m_ai_handling)
peer_count++;
m_network = new Network(peer_count,
/*channel_limit*/EVENT_CHANNEL_COUNT, /*max_in_bandwidth*/0,
/*max_out_bandwidth*/ 0, &addr, true/*change_port_if_bound*/);
}
@ -1059,7 +1063,8 @@ void STKHost::mainLoop()
// Remove peer which has not been validated after a specific time
// It is validated when the first connection request has finished
if (!it->second->isValidated() &&
if (!it->second->isAIPeer() &&
!it->second->isValidated() &&
it->second->getConnectedTime() > timeout)
{
Log::info("STKHost", "%s has not been validated for more"
@ -1155,7 +1160,7 @@ void STKHost::mainLoop()
getPeerCount());
// Client always trust the server
if (!is_server)
stk_peer->setValidated();
stk_peer->setValidated(true);
} // ENET_EVENT_TYPE_CONNECT
else if (event.type == ENET_EVENT_TYPE_DISCONNECT)
{
@ -1510,6 +1515,8 @@ std::vector<std::shared_ptr<NetworkPlayerProfile> >
{
if (peer.second->isDisconnected() || !peer.second->isValidated())
continue;
if (ServerConfig::m_ai_handling && peer.second->isAIPeer())
continue;
auto peer_profile = peer.second->getPlayerProfiles();
p.insert(p.end(), peer_profile.begin(), peer_profile.end());
}
@ -1561,7 +1568,7 @@ void STKHost::initClientNetwork(ENetEvent& event, Network* new_network)
}
auto stk_peer = std::make_shared<STKPeer>(event.peer, this,
m_next_unique_host_id++);
stk_peer->setValidated();
stk_peer->setValidated(true);
m_peers[event.peer] = stk_peer;
setPrivatePort();
auto pm = ProtocolManager::lock();
@ -1599,6 +1606,8 @@ std::vector<std::shared_ptr<NetworkPlayerProfile> >
auto& stk_peer = p.second;
if (stk_peer->isWaitingForGame())
continue;
if (ServerConfig::m_ai_handling && stk_peer->isAIPeer())
continue;
for (auto& q : stk_peer->getPlayerProfiles())
players.push_back(q);
}
@ -1623,6 +1632,8 @@ void STKHost::updatePlayers(unsigned* ingame, unsigned* waiting,
auto& stk_peer = p.second;
if (!stk_peer->isValidated())
continue;
if (ServerConfig::m_ai_handling && stk_peer->isAIPeer())
continue;
if (stk_peer->isWaitingForGame())
waiting_players += (uint32_t)stk_peer->getPlayerProfiles().size();
else

View File

@ -140,7 +140,7 @@ public:
void addPlayer(std::shared_ptr<NetworkPlayerProfile> p)
{ m_players.push_back(p); }
// ------------------------------------------------------------------------
void setValidated() { m_validated.store(true); }
void setValidated(bool val) { m_validated.store(val); }
// ------------------------------------------------------------------------
/** Returns if the client is validated by server. */
bool isValidated() const { return m_validated.load(); }