#include "game/chooseserverform.h" #include "game/simplerequest.h" #include "game/serviceurls.h" #include "game/loginhandler.h" #include "mpshared/constants.h" #include "base/format.h" #include "base/thread.h" #include "base/misc.h" #include "yajl/wrapper/jsonbuilder.h" #include "game/xtransport.h" #include #include namespace wi { bool ServerInfoSort(const ServerInfo& info1, const ServerInfo& info2) { return info1.sort_key < info2.sort_key; } dword ChooseServerForm::DoForm(Transport **pptra, std::string *server_name) { ChooseServerForm *pfrm = (ChooseServerForm *)gpmfrmm->LoadForm(gpiniForms, kidfChooseServer, new ChooseServerForm()); if (pfrm == NULL) { return knChooseServerResultCancel; } int result = 0; pfrm->DoModal(&result); Transport *ptra = pfrm->transport(); std::string name = pfrm->server_name(); delete pfrm; if (gevm.IsAppStopping()) { return knChooseServerResultAppStop; } if (result == kidcCancel || ptra == NULL) { return knChooseServerResultCancel; } *pptra = ptra; *server_name = name; return knChooseServerResultConnect; } bool ChooseServerForm::DoModal(int *pnResult) { // Hide Connect button until there is something to connect to LabelControl *plbl = (LabelControl *)GetControlPtr(kidcOk); plbl->Show(false); // Position the columns PositionColumns(); Refresh(false); // If there is a single entry this client can connect to, use it without // presenting UI if (infos_.size() == 1) { Control *pctl = GetControlPtr(kidcOk); if (pctl->GetFlags() & kfCtlVisible) { const ServerInfo& info = infos_[0]; if (Connect(info) == knTransportOpenResultSuccess) { *pnResult = kidcOk; return true; } } } return ShellForm::DoModal(pnResult); } void ChooseServerForm::OnZipDone() { if (errors_) { HtMessageBox(kfMbWhiteBorder, "Service Message", errorstr_.c_str()); } } void ChooseServerForm::PositionColumns() { ListControl *plstc = (ListControl *)GetControlPtr(kidcServerList); Rect rcList; plstc->GetRect(&rcList); Font *pfnt = plstc->GetFont(); LabelControl *plblPlayers = (LabelControl *)GetControlPtr(kidcNumPlayers); int cxNumPlayers = pfnt->GetTextExtent(plblPlayers->GetText()); int xNumPlayers = rcList.right - cxNumPlayers; LabelControl *plblStatus = (LabelControl *)GetControlPtr(kidcServerStatus); int cxStatus = pfnt->GetTextExtent(plblStatus->GetText()); int xStatus = xNumPlayers - 40 - cxStatus; int xLocation = rcList.left + (xStatus - rcList.left) / 2; int xName = rcList.left; plstc->SetTabStops(xName - rcList.left, xLocation - rcList.left, xStatus + cxStatus / 2 - rcList.left, xNumPlayers + cxNumPlayers / 2 - rcList.left); plstc->SetTabFlags(0, 0, kfLstTabCenterOn, kfLstTabCenterOn); word ids[] = { kidcServerName, kidcServerLocation, kidcServerStatus, kidcNumPlayers }; int ax[] = { xName, xLocation, xStatus, xNumPlayers }; for (int i = 0; i < ARRAYSIZE(ids); i++) { LabelControl *plbl = (LabelControl *)GetControlPtr(ids[i]); Rect rcCtl; plbl->GetRect(&rcCtl); plbl->SetPosition(ax[i], rcCtl.top); } } void ChooseServerForm::Refresh(bool show_errors) { bool success = RequestInfos(); ListControl *plstc = (ListControl *)GetControlPtr(kidcServerList); plstc->Clear(); if (infos_.size() == 0) { if (success) { errorstr_ = "The multiplayer service is down for maintenance. Try again later."; } if (show_errors) { HtMessageBox(kfMbWhiteBorder, "Service Message", errorstr_.c_str()); } else { errors_ = true; } } else { for (int i = 0; i < infos_.size(); i++) { const ServerInfo& info = infos_[i]; const char *s = base::Format::ToString("%s\t%s\t%s\t%d", info.name.c_str(), info.location.c_str(), info.status.c_str(), info.player_count); plstc->Add(s); } plstc->Select(0, true); } ShowHide(); } void ChooseServerForm::OnControlSelected(word idc) { switch (idc) { case kidcOk: { ListControl *plstc = (ListControl *)GetControlPtr(kidcServerList); int index = plstc->GetSelectedItemIndex(); if (index >= 0 && index < infos_.size()) { dword result = Connect(infos_[index]); if (result == knTransportOpenResultSuccess) { EndForm(kidcOk); break; } ShowTransportError(result); } } break; case kidcRefresh: Refresh(); break; case kidcCancel: EndForm(kidcCancel); break; } } dword ChooseServerForm::Connect(const ServerInfo& info) { // Note for the moment, this only supports XTransport. This is done // this way for the best user experience: so that a Connect can be // attempted while this form is up, and if it fails the form stays up // so the list can be refreshed, or another server tried. Bringing up // this form is expensive because of the ServerInfo query. TransportWaitingUI twui("Contacting Service"); Transport *ptra = (Transport *)new XTransport(info.address); dword result = ptra->Open(); if (result != knTransportOpenResultSuccess) { delete ptra; return result; } connected_ = info; transport_ = ptra; return result; } void ChooseServerForm::ShowTransportError(dword result) { const char *message = NULL; switch(result) { case knTransportOpenResultFail: message = "Failure accessing network."; break; case knTransportOpenResultNoNetwork: message = "Please check for network connectivity."; break; case knTransportOpenResultCantConnect: message = "Could not connect to game server."; break; case knTransportOpenResultNotResponding: message = "Timed out waiting for Game server to respond."; break; case knTransportOpenResultProtocolMismatch: message = "This server requires that you upgrade to the latest Hostile Takeover before continuing."; break; case knTransportOpenResultServerFull: message = "This server is full. Please try another server."; break; } if (message != NULL) { HtMessageBox(kfMbWhiteBorder, "Service Message", message); } } void ChooseServerForm::ShowHide() { ListControl *plstc = (ListControl *)GetControlPtr(kidcServerList); ButtonControl *pbtn = (ButtonControl *)GetControlPtr(kidcOk); int index = plstc->GetSelectedItemIndex(); if (index < 0 || index >= infos_.size()) { pbtn->Show(false); return; } ServerInfo& info = infos_[index]; if (strcmp(info.status.c_str(), "ok") != 0) { pbtn->Show(false); return; } pbtn->Show(true); } void ChooseServerForm::OnControlNotify(word idc, int nNotify) { if (idc == kidcServerList && nNotify == knNotifySelectionChange) { ShowHide(); } ShellForm::OnControlNotify(idc, nNotify); } bool ChooseServerForm::RequestInfos() { // Default error string errorstr_ = "Invalid Response From Service"; TransportWaitingUI twui("Contacting Service"); std::string url = GetServiceUrl(); SimpleRequest req; char errorstr[1024]; if (!req.Get(url.c_str(), NULL, 0, errorstr, sizeof(errorstr))) { errorstr_ = errorstr; return false; } // Parse json json::JsonBuilder builder; builder.Start(); if (!builder.Update((const char *)req.body()->Data(), req.body()->Length())) { return false; } json::JsonObject *obj = builder.End(); if (obj == NULL) { return false; } // Pull out infos and weed out the ones not appropriate for this client json::JsonMap *map = (json::JsonMap *)obj; json::JsonArray *infos = (json::JsonArray *)map->GetObject("infos"); if (infos == NULL) { delete obj; return false; } std::map infomap; for (int i = 0; i < infos->GetCount(); i++) { json::JsonMap *json_map = (json::JsonMap *)infos->GetObject(i); ServerInfo info; info.sort_key = GetIntegerFromMap(json_map, "sort_key"); info.name = GetStringFromMap(json_map, "name"); info.location = GetStringFromMap(json_map, "location"); info.address = base::SocketAddress(GetStringFromMap(json_map, "address").c_str()); info.protocol = GetIntegerFromMap(json_map, "protocol"); info.status = GetStringFromMap(json_map, "status"); info.player_count = GetIntegerFromMap(json_map, "player_count"); info.type = GetStringFromMap(json_map, "type"); info.disallow = GetStringFromMap(json_map, "disallow"); // If production client, only show production servers. #if !defined(DEBUG) && !defined(BETA_TIMEOUT) if (strcmp(info.type.c_str(), "production") != 0) { continue; } #endif // Don't show old protocol servers to newer clients if (info.protocol < kdwProtocolCurrent) { continue; } // Don't show servers to disallowed client platforms #if defined(__MACOSX__) if (info.disallow.find("mac") != std::string::npos) { continue; } #elif defined(DARWIN) if (info.disallow.find("darwin") != std::string::npos) { continue; } #elif defined(__LINUX__) if (info.disallow.find("linux") != std::string::npos) { continue; } #elif defined(__ANDROID__) if (info.disallow.find("android") != std::string::npos) { continue; } #elif defined(IPHONE) || defined(__IPHONEOS__) if (info.disallow.find("iphone") != std::string::npos) { continue; } #endif // If there is an info with this name that matches the protocol, // keep it and discard the new info std::map::iterator it = infomap.find(info.name); if (it != infomap.end()) { if (it->second.protocol == kdwProtocolCurrent) { continue; } // Otherwise delete this so the new info can be added. This // makes upgrading seamless. infomap.erase(it); } // Add new info into the map infomap.insert(std::map::value_type( info.name, info)); } delete obj; // Now read out the infos from the map, and sort them by sort_key infos_.clear(); std::map::iterator it = infomap.begin(); while (it != infomap.end()) { infos_.push_back(it->second); it++; } std::stable_sort(infos_.begin(), infos_.end(), ServerInfoSort); return true; } int ChooseServerForm::GetIntegerFromMap(json::JsonMap *map, const char *key) { json::JsonString *s = (json::JsonString *)map->GetObject(key); if (s == NULL) { return 0; } int n = 0; base::Format::ToInteger(s->GetString(), 10, &n); return n; } std::string ChooseServerForm::GetStringFromMap(json::JsonMap *map, const char *key) { json::JsonString *s = (json::JsonString *)map->GetObject(key); if (s == NULL) { return ""; } return s->GetString(); } std::string ChooseServerForm::GetServiceUrl() { // player, if exists // device id, hashed // protocol version std::string deviceid(base::StringEncoder::QueryEncode(gszDeviceId)); std::string os(base::StringEncoder::QueryEncode(HostGetPlatformString())); const char *url; LoginHandler handler; if (handler.anonymous()) { url = base::Format::ToString("%s?x=%d&d=%s&o=%s", kszServerInfoUrl, kdwProtocolCurrent, deviceid.c_str(), os.c_str()); } else { std::string player(base::StringEncoder::QueryEncode( handler.username())); url = base::Format::ToString("%s?x=%d&p=%s&d=%s&o=%s", kszServerInfoUrl, kdwProtocolCurrent, player.c_str(), deviceid.c_str(), os.c_str()); } return url; } }