1
0
forked from 0ad/0ad

# Add victory/defeat conditions, based on patch from fcxSanya.

Fixes #565.

This was SVN commit r8234.
This commit is contained in:
Ykkrosh 2010-10-01 20:51:21 +00:00
parent 4b3e819b67
commit fcedcae052
22 changed files with 330 additions and 10 deletions

View File

@ -6,7 +6,7 @@
// *******************************************
// messageBox
// *******************************************
// @params: int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions
// @params: int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions, arr mbButtonsCode
// @return: void
// @desc: Displays a new modal message box.
// *******************************************

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<page>
<include>common/setup.xml</include>
<include>common/styles.xml</include>
<include>common/sprite1.xml</include>
<include>common/init.xml</include>
<include>summary/summary.xml</include>
<include>common/global.xml</include>
</page>

View File

@ -17,6 +17,9 @@ var g_IsTrainingQueueBlocked = false;
// Cache EntityStates
var g_EntityStates = {}; // {id:entState}
// Whether the player has lost/won and reached the end of their game
var g_GameEnded = false;
function GetEntityState(entId)
{
if (!(entId in g_EntityStates))
@ -75,9 +78,36 @@ function init(initData, hotloadData)
function leaveGame()
{
var simState = Engine.GuiInterfaceCall("GetSimulationState");
var playerState = simState.players[Engine.GetPlayerID()];
var gameResult;
if (playerState.state == "won")
{
gameResult = "You have won the battle!";
}
else if (playerState.state == "defeated")
{
gameResult = "You have been defeated...";
}
else // "active"
{
gameResult = "You have abandoned the game.";
// Tell other players that we have given up and
// been defeated
Engine.PostNetworkCommand({
"type": "defeat-player",
"playerId": Engine.GetPlayerID()
});
}
stopMusic();
endGame();
Engine.SwitchGuiPage("page_pregame.xml");
Engine.SwitchGuiPage("page_summary.xml", { "gameResult" : gameResult });
}
// Return some data that we'll use when hotloading this file after changes
@ -88,6 +118,8 @@ function getHotloadData()
function onTick()
{
checkPlayerState();
while (true)
{
var message = Engine.PollNetworkClient();
@ -121,6 +153,28 @@ function onTick()
getGUIObjectByName("resourcePop").textcolor = "white";
}
function checkPlayerState()
{
var simState = Engine.GuiInterfaceCall("GetSimulationState");
var playerState = simState.players[Engine.GetPlayerID()];
if (!g_GameEnded)
{
if (playerState.state == "defeated")
{
g_GameEnded = true;
messageBox(400, 200, "You have been defeated... Do you want to leave the game now?",
"Defeat", 0, ["Yes", "No!"], [leaveGame, null]);
}
else if (playerState.state == "won")
{
g_GameEnded = true;
messageBox(400, 200, "You have won the battle! Do you want to leave the game now?",
"Victory", 0, ["Yes", "No!"], [leaveGame, null]);
}
}
}
function onSimulationUpdate()
{
g_Selection.dirty = false;

View File

@ -0,0 +1,4 @@
function init(data)
{
getGUIObjectByName("summaryText").caption = data.gameResult;
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
==========================================
- POST-GAME SUMMARY SCREEN -
==========================================
-->
<objects>
<script file="gui/summary/summary.js"/>
<object type="image" sprite="bkFillBlack">
<object type="image"
style="wheatWindowGranite"
size="25 35 100%-25 100%-25"
>
<object type="button" style="wheatWindowTitleBar">
Summary
</object>
<object name="summaryText"
type="text"
size="50 50 100%-50 100%-200"
font="serif-16"
text_align="center"
text_valign="center"
/>
<object type="button" style="wheatButton" size="100%-150 100%-40 100% 100%">
Main menu
<action on="Press"><![CDATA[
Engine.SwitchGuiPage("page_pregame.xml");
]]></action>
</object>
</object>
</object>
</objects>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,110 @@
// Repetition interval (msecs) for checking end game conditions
var g_ProgressInterval = 1000;
/**
* System component which regularly checks victory/defeat conditions
* and if they are satisfied then it marks the player as victorious/defeated.
*/
function EndGameManager() {}
EndGameManager.prototype.Schema =
"<a:component type='system'/><empty/>";
EndGameManager.prototype.Init = function()
{
// Game type, initialised from the map settings.
// One of: "conquest" (default) and "endless"
this.gameType = "conquest";
};
EndGameManager.prototype.SetGameType = function(newGameType)
{
this.gameType = newGameType;
};
/**
* Begin checking the end-game conditions.
* Must be called once, after calling SetGameType.
*/
EndGameManager.prototype.Start = function()
{
if (this.gameType != "endless")
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_EndGameManager, "ProgressTimeout", g_ProgressInterval, {});
}
};
EndGameManager.prototype.OnDestroy = function()
{
if (this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
EndGameManager.prototype.ProgressTimeout = function(data)
{
this.UpdatePlayerStates();
// Repeat the timer
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_EndGameManager, "ProgressTimeout", g_ProgressInterval, data);
};
EndGameManager.prototype.UpdatePlayerStates = function()
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
switch (this.gameType)
{
case "conquest":
// If a player is currently active but has no suitable units left,
// mark that player as defeated
// (Start from player 1 since we ignore Gaia)
for (var i = 1; i < cmpPlayerManager.GetNumPlayers(); i++)
{
var playerEntityId = cmpPlayerManager.GetPlayerByID(i);
var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
if (cmpPlayer.GetState() == "active")
{
if (cmpPlayer.GetConquestCriticalEntitiesCount() == 0)
{
Engine.PostMessage(playerEntityId, MT_PlayerDefeated, null);
}
}
}
// If there's only player remaining active, mark them as the winner
// TODO: update this code for allies
var alivePlayersCount = 0;
var lastAlivePlayerId;
// (Start from 1 to ignore Gaia)
for (var i = 1; i < cmpPlayerManager.GetNumPlayers(); i++)
{
var playerEntityId = cmpPlayerManager.GetPlayerByID(i);
var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
if (cmpPlayer.GetState() == "active")
{
alivePlayersCount++;
lastAlivePlayerId = i;
}
}
if (alivePlayersCount == 1)
{
var playerEntityId = cmpPlayerManager.GetPlayerByID(lastAlivePlayerId);
var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
cmpPlayer.SetState("won");
}
break;
default:
error("Invalid game type "+this.gameType);
break;
}
};
Engine.RegisterComponentType(IID_EndGameManager, "EndGameManager", EndGameManager);

View File

@ -36,7 +36,8 @@ GuiInterface.prototype.GetSimulationState = function(player)
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
"trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked()
"trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(),
"state": cmpPlayer.GetState()
};
ret.players.push(playerData);
}

View File

@ -62,12 +62,14 @@ Identity.prototype.Schema =
"<value>Mechanical</value>" +
"<value>Super</value>" +
"<value>Hero</value>" +
"<value>Structure</value>" +
"<value>Civic</value>" +
"<value>Economic</value>" +
"<value>Defensive</value>" +
"<value>Village</value>" +
"<value>Town</value>" +
"<value>City</value>" +
"<value>ConquestCritical</value>" +
"<value a:help='Primary weapon type'>Bow</value>" + // TODO: what are these used for?
"<value a:help='Primary weapon type'>Javelin</value>" +
"<value a:help='Primary weapon type'>Spear</value>" +

View File

@ -19,6 +19,8 @@ Player.prototype.Init = function()
"metal": 500,
"stone": 1000
};
this.state = "active"; // game state - one of "active", "defeated", "won"
this.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class
};
Player.prototype.SetPlayerID = function(id)
@ -146,12 +148,40 @@ Player.prototype.TrySubtractResources = function(amounts)
return true;
};
Player.prototype.GetState = function()
{
return this.state;
};
Player.prototype.SetState = function(newState)
{
this.state = newState;
};
Player.prototype.GetConquestCriticalEntitiesCount = function()
{
return this.conquestCriticalEntitiesCount;
};
// Keep track of population effects of all entities that
// become owned or unowned by this player
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
var classes = [];
// Load class list only if we're going to need it
if (msg.from == this.playerID || msg.to == this.playerID)
{
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (cmpIdentity)
classes = cmpIdentity.GetClassesList();
}
if (msg.from == this.playerID)
{
if (classes.indexOf("ConquestCritical") != -1)
this.conquestCriticalEntitiesCount--;
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
@ -162,6 +192,9 @@ Player.prototype.OnGlobalOwnershipChanged = function(msg)
if (msg.to == this.playerID)
{
if (classes.indexOf("ConquestCritical") != -1)
this.conquestCriticalEntitiesCount++;
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
@ -171,4 +204,19 @@ Player.prototype.OnGlobalOwnershipChanged = function(msg)
}
};
Player.prototype.OnPlayerDefeated = function()
{
this.state = "defeated";
// Reassign all player's entities to Gaia
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID);
for each (var entity in entities)
{
// Note: maybe we need to reassign units and buildings only?
var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
cmpOwnership.SetOwner(0);
}
}
Engine.RegisterComponentType(IID_Player, "Player", Player);

View File

@ -0,0 +1,2 @@
Engine.RegisterInterface("EndGameManager");
Engine.RegisterMessageType("PlayerDefeated");

View File

@ -31,6 +31,7 @@ AddMock(100, IID_Player, {
GetPopulationLimit: function() { return 20; },
GetResourceCounts: function() { return { food: 100 }; },
IsTrainingQueueBlocked: function() { return false; },
GetState: function() { return "active"; },
});
AddMock(101, IID_Player, {
@ -41,6 +42,7 @@ AddMock(101, IID_Player, {
GetPopulationLimit: function() { return 30; },
GetResourceCounts: function() { return { food: 200 }; },
IsTrainingQueueBlocked: function() { return false; },
GetState: function() { return "active"; },
});
TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
@ -53,6 +55,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
popLimit: 20,
resourceCounts: { food: 100 },
trainingQueueBlocked: false,
state: "active",
},
{
name: "Player 2",
@ -62,6 +65,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
popLimit: 30,
resourceCounts: { food: 200 },
trainingQueueBlocked: false,
state: "active",
}
]
});

View File

@ -143,6 +143,14 @@ function ProcessCommand(player, cmd)
cmpRallyPoint.Unset();
}
break;
case "defeat-player":
// Get player entity by playerId
var cmpPlayerMananager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerEnt = cmpPlayerManager.GetPlayerByID(cmd.playerId);
// Send "OnPlayerDefeated" message to player
Engine.PostMessage(playerEnt, MT_PlayerDefeated, null);
break;
default:
error("Ignoring unrecognised command type '" + cmd.type + "'");

View File

@ -19,6 +19,13 @@ function LoadMapSettings(settings)
if (cmpRangeManager)
cmpRangeManager.SetLosRevealAll(true);
}
var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
if (settings.GameType)
{
cmpEndGameManager.SetGameType(settings.GameType);
}
cmpEndGameManager.Start();
}
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);

View File

@ -2,7 +2,11 @@
<Entity parent="template_entity_full">
<Identity>
<GenericName>Structure</GenericName>
<IconSheet>snPortraitSheetBuildings</IconSheet>
<Classes datatype="tokens">
Structure
ConquestCritical
</Classes>
<IconSheet>snPortraitSheetBuildings</IconSheet>
</Identity>
<BuildRestrictions>
<PlacementType>standard</PlacementType>

View File

@ -2,6 +2,9 @@
<Entity parent="template_entity_full">
<Identity>
<GenericName>Unit</GenericName>
<Classes datatype="tokens">
ConquestCritical
</Classes>
</Identity>
<Minimap>
<Type>unit</Type>

View File

@ -113,6 +113,7 @@ public:
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");
LOAD_SCRIPTED_COMPONENT("Timer");
LOAD_SCRIPTED_COMPONENT("EndGameManager");
#undef LOAD_SCRIPTED_COMPONENT
}

View File

@ -462,6 +462,22 @@ public:
return r;
}
virtual std::vector<entity_id_t> GetEntitiesByPlayer(int playerId)
{
std::vector<entity_id_t> entities;
u32 ownerMask = CalcOwnerMask(playerId);
for (std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
// Check owner and add to list if it matches
if (CalcOwnerMask(it->second.owner) & ownerMask)
entities.push_back(it->first);
}
return entities;
}
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;

View File

@ -28,6 +28,7 @@ DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyA
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector<entity_id_t>, ICmpRangeManager, GetEntitiesByPlayer, int)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_1("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, bool)
END_INTERFACE_WRAPPER(RangeManager)

View File

@ -125,6 +125,14 @@ public:
*/
virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag) = 0;
/**
* Returns list of all entities for specific player.
* (This is on this interface because it shares a lot of the implementation.
* Maybe it should be extended to be more like ExecuteQuery without
* the range parameter.)
*/
virtual std::vector<entity_id_t> GetEntitiesByPlayer(int playerId) = 0;
/**
* Toggle the rendering of debug info.
*/