Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -445,6 +445,7 @@ duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached +gamestarttimeout = 60000 ; Wait this many milliseconds before disconnecting during game start and map generation. [overlay] fps = "false" ; Show frames per second in top right corner Index: binaries/data/mods/public/gui/common/network.js =================================================================== --- binaries/data/mods/public/gui/common/network.js +++ binaries/data/mods/public/gui/common/network.js @@ -110,6 +110,18 @@ function kickPlayer(username, ban) { + // first transform the username to see if it matches any with unprintable characters + for(var i in g_PlayerAssignments){ + let s = g_PlayerAssignments[i].name; + var ascii = /^[ -~]+$/; + + if ( !ascii.test( s ) ) { + if(s.indexOf(username) !== -1){ + username = s; + break; + } + } + } if (g_IsController) Engine.KickPlayer(username, ban); else Index: binaries/data/mods/public/gui/lobby/lobby.js =================================================================== --- binaries/data/mods/public/gui/lobby/lobby.js +++ binaries/data/mods/public/gui/lobby/lobby.js @@ -1002,6 +1002,7 @@ for (let player of stringifiedTeamListToPlayerData(game.players)) { + //warn(player.Name); let playerNickRating = splitRatingFromNick(player.Name); if (player.Team != "observer") @@ -1017,6 +1018,7 @@ Math.round(playerRatings.reduce((sum, current) => sum + current) / playerRatings.length) : g_DefaultLobbyRating; + //warn(game.mods); if (!hasSameMods(JSON.parse(game.mods), Engine.GetEngineInfo().mods)) game.state = "incompatible"; @@ -1173,6 +1175,8 @@ let rating = getRejoinRating(game); let username = rating ? g_Username + " (" + rating + ")" : g_Username; + if (typeof g_Name !== "undefined" && g_Name != "") + username = g_Name; if (game.state == "incompatible") messageBox( @@ -1225,6 +1229,16 @@ port = game.port; } + let username = g_Username; + let rating = getRejoinRating(game); + if (typeof g_Name !== "undefined" && g_Name != ""){ + warn("using fake name: " + g_Name); + let splitR = splitRatingFromNick(g_Name); + username = splitR.nick; + rating = splitR.rating; + } + + if (ip.split('.').length != 4) { addChatMessage({ @@ -1241,8 +1255,8 @@ "multiplayerGameType": "join", "ip": ip, "port": port, - "name": g_Username, - "rating": getRejoinRating(game), + "name": username, + "rating": rating, "useSTUN": !!game.stunIP, "hostJID": game.hostUsername + "@" + g_LobbyServer + "/0ad" }); Index: binaries/data/mods/public/gui/session/input.js =================================================================== --- binaries/data/mods/public/gui/session/input.js +++ binaries/data/mods/public/gui/session/input.js @@ -1334,6 +1334,7 @@ var g_NumberOfBatches; var g_BatchTrainingEntityAllowedCount; var g_BatchSize = getDefaultBatchTrainingSize(); +var g_BatchTrainingCount; function OnTrainMouseWheel(dir) { @@ -1394,12 +1395,17 @@ let appropriateBuildings = getBuildingsWhichCanTrainEntity(selection, trainEntType); let canBeAddedCount = getEntityLimitAndCount(playerState, trainEntType).canBeAddedCount; + var limits = getEntityLimitAndCount(playerState, trainEntType); + var batchTrainingPossible = limits.canBeAddedCount == undefined || limits.canBeAddedCount > 1; + let decrement = Engine.HotkeyIsPressed("selection.remove"); let template; if (!decrement) template = GetTemplateData(trainEntType); + let batchIncrementSize = getBatchTrainingSize(); + // Batch training only possible if we can train at least 2 units if (Engine.HotkeyIsPressed("session.batchtrain") && (canBeAddedCount == undefined || canBeAddedCount > 1)) { @@ -1452,8 +1458,70 @@ } else { + g_BatchTrainingEntities = selection; + g_BatchTrainingType = trainEntType; + g_BatchTrainingEntityAllowedCount = limits.canBeAddedCount; + g_BatchTrainingCount = batchIncrementSize; + + // Non-batched - just create a single entity in each building + // (but no more than entity limit allows) + if(batchTrainingPossible){ + var sState = g_SimState; + var sparePop = sState.players[g_ViewedPlayer]["popLimit"] - sState.players[g_ViewedPlayer]["popCount"]; + var minCount = 100; + var eCosts = multiplyEntityCosts(template, 1); + var playerRes = sState.players[g_ViewedPlayer].resourceCounts; + var min_ = function(a,b){if (a 0) + { + reserveFraction += state.production.queue[0].progress; + } + else + { + reserveFraction += 1.0; + trainBuildings.push(g_BatchTrainingEntities[i]); + } + } + } + if (trainBuildings.length == 0) + return; + if (reserveFraction < 1.0) + return; + g_BatchTrainingEntities = trainBuildings; + + if("population" in template.cost){ + minCount = min_(minCount,sparePop / template.cost.population); + print(minCount + " population\n"); + } + print(minCount + " " + String(appropriateBuildings) + " " + String(reserveFraction) +"\n"); + g_NumberOfBatches = Math.floor(minCount / reserveFraction); + if (g_NumberOfBatches > 0){ + flushTrainingBatch(); + return; + } + } // Non-batched - just create a single entity in each building // (but no more than entity limit allows) + let buildingsForTraining = appropriateBuildings; if (canBeAddedCount !== undefined) buildingsForTraining = buildingsForTraining.slice(0, canBeAddedCount); Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -875,6 +875,8 @@ }); } +var g_CorralTicks = 0; + function onSimulationUpdate() { // Templates change depending on technologies and auras, so they have to be reloaded after such a change. @@ -896,10 +898,56 @@ handleNotifications(); updateGUIObjects(); + //if (GetEntityState(9309) || GetEntityState(9315)) + //{ + // warn(g_SimState.timeElapsed); + //} + + g_CorralTicks = (g_CorralTicks+1)%7; + if(g_CorralTicks != 0) + return; + + var corralnames = ["athen_corral", "brit_corral", "cart_corral", "gaul_corral", "iber_corral", "mace_corral", "maur_corral", "pers_corral", "ptol_corral", "rome_corral", "sele_corral", "spart_corral", "kush_corral"]; + var corrals = []; + for(var i=0;iSetLongTimeout(false); } CLoadedGameMessage loaded; @@ -680,6 +682,9 @@ JSContext* cx = client->GetScriptInterface().GetContext(); JSAutoRequest rq(cx); + // Prevent player drops during loading. + client->m_Session->SetLongTimeout(true); + // Find the player assigned to our GUID int player = -1; if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end()) @@ -704,6 +709,9 @@ CNetClient* client = (CNetClient*)context; + // Prevent player drops during loading. + client->m_Session->SetLongTimeout(true); + // The server wants us to start downloading the game state from it, so do so client->m_Session->GetFileTransferer().StartTask( shared_ptr(new CNetFileReceiveTask_ClientRejoin(*client)) @@ -879,8 +887,13 @@ // If we have rejoined an in progress game, send the rejoined message to the server. if (client->m_Rejoin) + { client->SendRejoinedMessage(); + // We allowed longer timeouts during loading, so now return them to normal. + client->m_Session->SetLongTimeout(false); + } + return true; } Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -1101,6 +1101,8 @@ ); session->SetNextState(NSS_JOIN_SYNCING); + // Prevent player drops during loading. + session->SetLongTimeout(true); } return true; @@ -1249,6 +1251,9 @@ if (session->GetGUID() == server.m_HostGUID) server.StartGame(); + // Prevent player drops during game loading. + session->SetLongTimeout(true); + return true; } @@ -1280,6 +1285,9 @@ loadedSession->SendMessage(&message); server.Broadcast(&message, { NSS_INGAME }); + // The client has loaded, so no longer needs a long timeout. + loadedSession->SetLongTimeout(false); + return true; } @@ -1356,6 +1364,9 @@ session->SendMessage(&pausedMessage); } + // The client has loaded, so no longer needs a long timeout to prevent drops. + session->SetLongTimeout(false); + return true; } Index: source/network/NetSession.h =================================================================== --- source/network/NetSession.h +++ source/network/NetSession.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -105,6 +105,12 @@ */ u32 GetMeanRTT() const; + /** + * Allows increasing the timeout to prevent drops during an expensive operation, + * and decreasing it back to normal afterwards. + */ + void SetLongTimeout(bool longTimeout); + CNetFileTransferer& GetFileTransferer() { return m_FileTransferer; } private: @@ -182,6 +188,12 @@ void SetLocalClient(bool isLocalClient); /** + * Allows increasing the timeout to prevent drops during an expensive operation, + * and decreasing it back to normal afterwards. + */ + void SetLongTimeout(bool longTimeout); + + /** * Send a message to the client. */ virtual bool SendMessage(const CNetMessage* message); Index: source/network/NetSession.cpp =================================================================== --- source/network/NetSession.cpp +++ source/network/NetSession.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2018 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -27,11 +27,21 @@ #include "scriptinterface/ScriptInterface.h" const u32 NETWORK_WARNING_TIMEOUT = 2000; - +const u32 NETWORK_GAMESTART_TIMEOUT = 80000; const u32 MAXIMUM_HOST_TIMEOUT = std::numeric_limits::max(); static const int CHANNEL_COUNT = 1; +void LongTimeout(ENetPeer* peer, bool longTimeout) +{ +#if (ENET_VERSION >= ENET_VERSION_CREATE(1, 3, 4)) + if (longTimeout) + enet_peer_timeout(peer, 0, NETWORK_GAMESTART_TIMEOUT, NETWORK_GAMESTART_TIMEOUT); + else + enet_peer_timeout(peer, 0, 0, 0); +#endif +} + CNetClientSession::CNetClientSession(CNetClient& client) : m_Client(client), m_FileTransferer(this), m_Host(NULL), m_Server(NULL), m_Stats(NULL) { @@ -200,7 +210,10 @@ return m_Server->roundTripTime; } - +void CNetClientSession::SetLongTimeout(bool longTimeout) +{ + LongTimeout(m_Server, longTimeout); +} CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) : m_Server(server), m_FileTransferer(this), m_Peer(peer) @@ -262,3 +275,11 @@ enet_peer_timeout(m_Peer, 0, MAXIMUM_HOST_TIMEOUT, MAXIMUM_HOST_TIMEOUT); #endif } + +void CNetServerSession::SetLongTimeout(bool longTimeout) +{ + // Don't change the timeout if the client is local, since that is handled by SetLocalClient. + if (m_IsLocalClient) + return; + LongTimeout(m_Peer, longTimeout); +}