# Add victory/defeat conditions, based on patch from fcxSanya.
Fixes #565. This was SVN commit r8234.
This commit is contained in:
parent
4b3e819b67
commit
fcedcae052
@ -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.
|
||||
// *******************************************
|
||||
|
9
binaries/data/mods/public/gui/page_summary.xml
Normal file
9
binaries/data/mods/public/gui/page_summary.xml
Normal 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>
|
@ -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;
|
||||
|
4
binaries/data/mods/public/gui/summary/summary.js
Normal file
4
binaries/data/mods/public/gui/summary/summary.js
Normal file
@ -0,0 +1,4 @@
|
||||
function init(data)
|
||||
{
|
||||
getGUIObjectByName("summaryText").caption = data.gameResult;
|
||||
}
|
38
binaries/data/mods/public/gui/summary/summary.xml
Normal file
38
binaries/data/mods/public/gui/summary/summary.xml
Normal 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>
|
BIN
binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
(Stored with Git LFS)
BIN
binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
(Stored with Git LFS)
Binary file not shown.
BIN
binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
(Stored with Git LFS)
BIN
binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
(Stored with Git LFS)
Binary file not shown.
BIN
binaries/data/mods/public/maps/scenarios/Units_demo.xml
(Stored with Git LFS)
BIN
binaries/data/mods/public/maps/scenarios/Units_demo.xml
(Stored with Git LFS)
Binary file not shown.
@ -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);
|
@ -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);
|
||||
}
|
||||
|
@ -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>" +
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,2 @@
|
||||
Engine.RegisterInterface("EndGameManager");
|
||||
Engine.RegisterMessageType("PlayerDefeated");
|
@ -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",
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -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 + "'");
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -2,6 +2,9 @@
|
||||
<Entity parent="template_entity_full">
|
||||
<Identity>
|
||||
<GenericName>Unit</GenericName>
|
||||
<Classes datatype="tokens">
|
||||
ConquestCritical
|
||||
</Classes>
|
||||
</Identity>
|
||||
<Minimap>
|
||||
<Type>unit</Type>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user