1
0
forked from 0ad/0ad

Handle map loading errors in autostart and normal setup modes. Adds TODOs for Atlas and Replay modes. See #764.

Fixes loading screen 'title'.
Fixes broken autostart behavior for random maps and multiplayer games.

This was SVN commit r9193.
This commit is contained in:
historic_bruno 2011-04-07 02:32:16 +00:00
parent 2a6cfb36d2
commit 2b138f47db
13 changed files with 214 additions and 90 deletions

View File

@ -0,0 +1,29 @@
/*
DESCRIPTION : Error-handling utility functions.
NOTES :
*/
// ====================================================================
function cancelOnError(msg)
{
// Delete game objects
endGame();
// Return to pregame
Engine.SwitchGuiPage("page_pregame.xml");
// Display error dialog if message given
if (msg)
{
Engine.PushGuiPage("page_msgbox.xml", {
width: 400,
height: 300,
message: '[font="serif-bold-18"]' + msg + '[/font]',
title: "Loading Aborted",
mode: 2
});
}
// Reset cursor
setCursor("arrow-default");
}

View File

@ -2,10 +2,6 @@ var g_Data;
function init(data)
{
var mapName = "map";
if (data && data.attribs)
mapName = data.attribs.map;
g_Data = data;
// Set to "hourglass" cursor.
@ -49,11 +45,11 @@ function init(data)
switch (data.attribs.mapType)
{
case "scenario":
loadingMapName.caption = "Loading \"" + mapName + "\"";
loadingMapName.caption = "Loading \"" + data.attribs.map + "\"";
break;
case "random":
loadingMapName.caption = "Generating \"" + mapName + "\"";
loadingMapName.caption = "Generating \"" + data.attribs.script + "\"";
break;
default:

View File

@ -8,6 +8,7 @@
<objects>
<script file="gui/loading/loading.js"/>
<script file="gui/common/functions_utility_error.js"/>
<!-- LOADING SCREEN BACKGROUND -->
<object type="image" sprite="loadingBackground"/>

View File

@ -3,6 +3,7 @@
<objects>
<script file="gui/pregame/mainmenu.js"/>
<script file="gui/common/functions_utility_error.js"/>
<!--
==========================================

View File

@ -78,6 +78,12 @@ function handleNetMessage(message)
obj.caption = "";
obj.hidden = true;
break;
case "connected":
obj.caption = "Connected to the server.";
obj.hidden = false;
case "authenticated":
obj.caption = "Connection to the server has been authenticated.";
obj.hidden = false;
case "disconnected":
obj.caption = "Connection to the server has been lost.\n\nThe game has ended.";
obj.hidden = false;
@ -120,6 +126,11 @@ function handleNetMessage(message)
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
// To prevent errors, ignore these message types that occur during autostart
case "gamesetup":
case "start":
break;
default:
error("Unrecognised net message type "+message.type);

View File

@ -41,8 +41,6 @@ CMapGenerator::CMapGenerator() : m_ScriptInterface("RMS", "MapGenerator", Script
bool CMapGenerator::GenerateMap(const VfsPath& scriptFile, const CScriptValRooted& settings)
{
TIMER(L"GenerateMap");
// Init RNG seed
uint32 seed;
if (!m_ScriptInterface.GetProperty(settings.get(), "Seed", seed))

View File

@ -33,6 +33,7 @@
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
@ -188,11 +189,10 @@ int CMapReader::UnpackMap()
{
// now unpack everything into local data
int ret = UnpackTerrain();
if(ret != 0) // failed or timed out
if (ret != 0) // failed or timed out
{
return ret;
if (unpacker.GetVersion() < 4)
debug_warn(L"Old unsupported map version - objects and lighting will be lost");
}
return 0;
}
@ -409,8 +409,6 @@ private:
void ReadCinema(XMBElement parent);
void ReadTriggers(XMBElement parent);
int ReadEntities(XMBElement parent, double end_time);
int ReadOldEntities(XMBElement parent, double end_time);
int ReadNonEntities(XMBElement parent, double end_time);
};
@ -1046,7 +1044,6 @@ int CMapReader::ReadXML()
return ret;
}
int CMapReader::DelayLoadFinished()
{
// we were dynamically allocated by CWorld::Initialize
@ -1070,6 +1067,8 @@ int CMapReader::LoadRMSettings()
int CMapReader::GenerateMap()
{
TIMER(L"GenerateMap");
CMapGenerator mapGen;
VfsPath scriptPath;
@ -1082,15 +1081,8 @@ int CMapReader::GenerateMap()
// Try to generate map
if (!mapGen.GenerateMap(scriptPath, scriptSettings))
{ // RMS failed
// TODO: Need to do something safe here, like cancel loading and return to main menu
LOGERROR(L"Map generation failed: RMS returned undefined");
return -1;
}
if (mapGen.GetMapData().undefined())
{
LOGERROR(L"undefined map data");
{ // RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
// Copy data from mapgen to simulator context
@ -1102,11 +1094,14 @@ int CMapReader::GenerateMap()
int CMapReader::ParseTerrain()
{
// parse terrain from map data
TIMER(L"ParseTerrain");
// parse terrain from map data
// an error here should stop the loading process
#define GET_TERRAIN_PROPERTY(prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData.get(), #prop, out))\
LOGERROR(L"CMapReader::ParseTerrain() failed to get '%hs' property", #prop);
{ LOGERROR(L"CMapReader::ParseTerrain() failed to get '%hs' property", #prop);\
throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
int size;
GET_TERRAIN_PROPERTY(size, size)
@ -1151,6 +1146,8 @@ int CMapReader::ParseTerrain()
int CMapReader::ParseEntities()
{
TIMER(L"ParseEntities");
// parse entities from map data
std::vector<Entity> entities;

View File

@ -54,6 +54,7 @@ that of Atlas depending on commandline parameters.
#include "ps/UserReport.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
@ -184,28 +185,39 @@ static int ProgressiveLoad()
{
wchar_t description[100];
int progress_percent;
LibError ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent);
switch(ret)
try
{
// no load active => no-op (skip code below)
case INFO::OK:
return 0;
// current task didn't complete. we only care about this insofar as the
// load process is therefore not yet finished.
case ERR::TIMED_OUT:
break;
// just finished loading
case INFO::ALL_COMPLETE:
g_Game->ReallyStartGame();
wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting..");
// LDR_ProgressiveLoad returns L""; set to valid text to
// avoid problems in converting to JSString
break;
// error!
default:
CHECK_ERR(ret);
// can't do this above due to legit ERR::TIMED_OUT
break;
LibError ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent);
switch(ret)
{
// no load active => no-op (skip code below)
case INFO::OK:
return 0;
// current task didn't complete. we only care about this insofar as the
// load process is therefore not yet finished.
case ERR::TIMED_OUT:
break;
// just finished loading
case INFO::ALL_COMPLETE:
g_Game->ReallyStartGame();
wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting..");
// LDR_ProgressiveLoad returns L""; set to valid text to
// avoid problems in converting to JSString
break;
// error!
default:
CHECK_ERR(ret);
// can't do this above due to legit ERR::TIMED_OUT
break;
}
}
catch (PSERROR_Game_World_MapLoadFailed e)
{
// Map loading failed
// Call script function to do the actual work
// (delete game data, switch GUI page, show error, etc.)
CancelLoad(CStr(e.what()).FromUTF8());
}
GUI_DisplayLoadProgress(progress_percent, description);

View File

@ -786,7 +786,7 @@ void EarlyInit()
srand(time(NULL)); // NOTE: this rand should *not* be used for simulation!
}
static bool Autostart(const CmdLineArgs& args);
bool Autostart(const CmdLineArgs& args);
void Init(const CmdLineArgs& args, int UNUSED(flags))
{
@ -923,10 +923,24 @@ void InitGraphics(const CmdLineArgs& args, int flags)
ogl_WarnIfError();
if (!Autostart(args))
try
{
const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
if (!Autostart(args))
{
const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
}
}
catch (PSERROR_Game_World_MapLoadFailed e)
{
// Map Loading failed
// Start the engine so we have a GUI
InitPs(true, L"page_pregame.xml", JSVAL_VOID);
// Call script function to do the actual work
// (delete game data, switch GUI page, show error, etc.)
CancelLoad(CStr(e.what()).FromUTF8());
}
}
@ -945,19 +959,29 @@ void RenderCursor(bool RenderingState)
g_DoRenderCursor = RenderingState;
}
static bool Autostart(const CmdLineArgs& args)
bool Autostart(const CmdLineArgs& args)
{
/*
* Handle various command-line options, for quick testing of various features:
* -autostart=mapname -- single-player
* -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players
* -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1
* -autostart=scriptname -autostart-random=104 -- random map, seed 104 (default is 0, for random choose -1)
* -autostart=name -- map name for scenario, or rms name for random map
* -autostart-playername=name -- multiplayer player name
* -autostart-host -- multiplayer host mode
* -autostart-players=2 -- number of players
* -autostart-client -- multiplayer client mode
* -autostart-ip=127.0.0.1 -- multiplayer connect to 127.0.0.1
* -autostart-random=104 -- random map, optional seed value = 104 (default is 0, random is -1)
* -autostart-size=12 -- random map size in patches = 12 (default is 12)
*
* Examples:
* -autostart=Acropolis -autostart-host -autostart-players=2 -- Host game on Acropolis map, 2 players
* -autostart=latium -autostart-random=-1 -- Start single player game on latium random map, random rng seed
*/
CStr autoStartName = args.Get("autostart");
if (autoStartName.empty())
{
return false;
}
g_Game = new CGame();
@ -989,19 +1013,44 @@ static bool Autostart(const CmdLineArgs& args)
}
}
scriptInterface.SetProperty(attrs.get(), "script", std::string(autoStartName), false); // RMS name
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"), false);
// Random map definition will be loaded from JSON file, so we need to parse it
std::wstring scriptPath = L"maps/random/" + autoStartName.FromUTF8() + L".json";
CScriptValRooted scriptData = scriptInterface.ReadJSONFile(scriptPath);
if (!scriptData.undefined() && scriptInterface.GetProperty(scriptData.get(), "settings", settings))
{
// JSON loaded ok - copy script name over to game attributes
std::wstring scriptFile;
scriptInterface.GetProperty(settings.get(), "Script", scriptFile);
scriptInterface.SetProperty(attrs.get(), "script", scriptFile); // RMS filename
}
else
{
// Problem with JSON file
CStrW msg = L"Error reading random map script \"" + scriptPath + L"\".\nCheck application log for details.";
throw PSERROR_Game_World_MapLoadFailed(msg.ToUTF8().c_str());
}
// For random map, there are special settings
// TODO: Get these from command line - using defaults for now
scriptInterface.SetProperty(settings.get(), "Size", 12); // Random map size (in patches)
// Get optional map size argument (default 12)
uint mapSize = 12;
if (args.Has("autostart-size"))
{
CStr size = args.Get("autostart-size");
mapSize = size.ToUInt();
}
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"));
scriptInterface.SetProperty(settings.get(), "Seed", seed); // Random seed
scriptInterface.SetProperty(settings.get(), "BaseTerrain", std::string("grass1_spring")); // Base terrain texture
scriptInterface.SetProperty(settings.get(), "BaseHeight", 0); // Base terrain height
scriptInterface.SetProperty(settings.get(), "Size", mapSize); // Random map size (in patches)
// Define players
// TODO: Get these from command line? - using defaults for now
// Get optional number of players (default 2)
size_t numPlayers = 2;
if (args.Has("autostart-players"))
{
CStr num = args.Get("autostart-players");
numPlayers = num.ToUInt();
}
// Set up player data
for (size_t i = 0; i < numPlayers; ++i)
{
CScriptVal player;
@ -1013,8 +1062,8 @@ static bool Autostart(const CmdLineArgs& args)
}
else
{
scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName), false);
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName));
scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"));
}
// Set player data for AIs
@ -1049,7 +1098,14 @@ static bool Autostart(const CmdLineArgs& args)
CScriptVal mpInitData;
g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false);
CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())));
// Get optional playername
CStrW userName = L"anonymous";
if (args.Has("autostart-playername"))
{
userName = args.Get("autostart-playername").FromUTF8();
}
if (args.Has("autostart-host"))
{
@ -1057,7 +1113,9 @@ static bool Autostart(const CmdLineArgs& args)
size_t maxPlayers = 2;
if (args.Has("autostart-players"))
{
maxPlayers = args.Get("autostart-players").ToUInt();
}
g_NetServer = new CNetServer(maxPlayers);
@ -1067,7 +1125,7 @@ static bool Autostart(const CmdLineArgs& args)
debug_assert(ok);
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
g_NetClient->SetUserName(userName);
g_NetClient->SetupConnection("127.0.0.1");
}
else if (args.Has("autostart-client"))
@ -1075,11 +1133,13 @@ static bool Autostart(const CmdLineArgs& args)
InitPs(true, L"page_loading.xml", mpInitData.get());
g_NetClient = new CNetClient(g_Game);
// TODO: player name, etc
g_NetClient->SetUserName(userName);
CStr ip = "127.0.0.1";
if (args.Has("autostart-ip"))
{
ip = args.Get("autostart-ip");
}
bool ok = g_NetClient->SetupConnection(ip);
debug_assert(ok);
@ -1088,7 +1148,9 @@ static bool Autostart(const CmdLineArgs& args)
{
g_Game->SetPlayerID(1);
g_Game->StartGame(attrs);
LDR_NonprogressiveLoad();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
@ -1097,3 +1159,22 @@ static bool Autostart(const CmdLineArgs& args)
return true;
}
void CancelLoad(const CStrW& message)
{
LDR_Cancel();
// Call the cancelOnError GUI function, but only if it exists
if (g_GUI && g_GUI->HasPages())
{
JSContext* cx = g_ScriptingHost.getContext();
jsval fval, rval;
JSBool ok = JS_GetProperty(cx, g_GUI->GetScriptObject(), "cancelOnError", &fval);
debug_assert(ok);
jsval msgval = ToJSVal(message);
if (ok && !JSVAL_IS_VOID(fval))
ok = JS_CallFunctionValue(cx, g_GUI->GetScriptObject(), fval, 1, &msgval, &rval);
}
}

View File

@ -61,5 +61,6 @@ class CmdLineArgs;
extern void Init(const CmdLineArgs& args, int flags);
extern void InitGraphics(const CmdLineArgs& args, int flags);
extern void Shutdown(int flags);
extern void CancelLoad(const CStrW& message);
#endif // INCLUDED_GAMESETUP

View File

@ -155,7 +155,10 @@ void CReplayPlayer::Replay()
CScriptValRooted attribs = game.GetSimulation2()->GetScriptInterface().ParseJSON(line);
game.StartGame(attribs);
// TODO: Non progressive load can fail - need a decent way to handle this
LDR_NonprogressiveLoad();
PSRETURN ret = game.ReallyStartGame();
debug_assert(ret == PSRETURN_OK);
}

View File

@ -88,8 +88,8 @@ void CWorld::RegisterInit(const CStrW& mapFile, int playerID)
catch (PSERROR_File& err)
{
delete reader;
LOGERROR(L"Failed to load map %ls: %hs", mapfilename.string().c_str(), err.what());
throw PSERROR_Game_World_MapLoadFailed();
LOGERROR(L"Failed to load scenario %ls: %hs", mapfilename.string().c_str(), err.what());
throw PSERROR_Game_World_MapLoadFailed("Failed to load scenario");
}
}
}
@ -99,24 +99,15 @@ void CWorld::RegisterInitRMS(const CStrW& scriptFile, const CScriptValRooted& se
// If scriptFile is empty, a blank map will be generated using settings (no RMS run)
CMapReader* reader = 0;
try
{
reader = new CMapReader;
CTriggerManager* pTriggerManager = NULL;
reader->LoadRandomMap(scriptFile, settings, m_Terrain,
CRenderer::IsInitialised() ? g_Renderer.GetWaterManager() : NULL,
CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL,
&g_LightEnv, m_pGame->GetView(),
m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL,
pTriggerManager, m_pGame->GetSimulation2(), playerID);
// fails immediately, or registers for delay loading
}
catch (PSERROR_File& err)
{
delete reader;
LOGERROR(L"Failed to generate random map %ls: %hs", scriptFile.c_str(), err.what());
throw PSERROR_Game_World_MapLoadFailed();
}
reader = new CMapReader;
CTriggerManager* pTriggerManager = NULL;
reader->LoadRandomMap(scriptFile, settings, m_Terrain,
CRenderer::IsInitialised() ? g_Renderer.GetWaterManager() : NULL,
CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL,
&g_LightEnv, m_pGame->GetView(),
m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL,
pTriggerManager, m_pGame->GetSimulation2(), playerID);
// registers for delay loading
}

View File

@ -66,7 +66,10 @@ namespace
scriptInterface.SetProperty(attrs.get(), "map", std::wstring(mapBase), false);
g_Game->StartGame(attrs);
// TODO: Non progressive load can fail - need a decent way to handle this
LDR_NonprogressiveLoad();
PSRETURN ret = g_Game->ReallyStartGame();
debug_assert(ret == PSRETURN_OK);