Add support for mission objectives
Objectives can be either primary or secondary objectives. Primary ones influence the outcome of the game. If all primary objectives are completed the game is won, and lost when any of them fails. Objectives can be added at any stage during the game, allowing to react dynamically to game events. The objectives backend only contains the information about the objectives themselves. It does not check if objectives are completed or failed. Instead, the state of objectives must be manually marked. The backend, however, does check whether the game is won or lost.
This commit is contained in:
@@ -273,6 +273,15 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
public interface IObjectivesPanel { string ObjectivesPanel { get; } }
|
public interface IObjectivesPanel { string ObjectivesPanel { get; } }
|
||||||
|
|
||||||
|
public interface INotifyObjectivesUpdated
|
||||||
|
{
|
||||||
|
void OnPlayerWon(Player winner);
|
||||||
|
void OnPlayerLost(Player loser);
|
||||||
|
void OnObjectiveAdded(Player player, int objectiveID);
|
||||||
|
void OnObjectiveCompleted(Player player, int objectiveID);
|
||||||
|
void OnObjectiveFailed(Player player, int objectiveID);
|
||||||
|
}
|
||||||
|
|
||||||
public static class DisableExts
|
public static class DisableExts
|
||||||
{
|
{
|
||||||
public static bool IsDisabled(this Actor a)
|
public static bool IsDisabled(this Actor a)
|
||||||
|
|||||||
@@ -42,8 +42,19 @@ namespace OpenRA
|
|||||||
public void AddPlayer(Player p) { Players.Add(p); }
|
public void AddPlayer(Player p) { Players.Add(p); }
|
||||||
public Player LocalPlayer { get; private set; }
|
public Player LocalPlayer { get; private set; }
|
||||||
|
|
||||||
Player renderPlayer;
|
public event Action GameOver = () => { };
|
||||||
|
bool gameOver;
|
||||||
|
public void EndGame()
|
||||||
|
{
|
||||||
|
if (!gameOver)
|
||||||
|
{
|
||||||
|
gameOver = true;
|
||||||
|
GameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool ObserveAfterWinOrLose;
|
public bool ObserveAfterWinOrLose;
|
||||||
|
Player renderPlayer;
|
||||||
public Player RenderPlayer
|
public Player RenderPlayer
|
||||||
{
|
{
|
||||||
get { return renderPlayer == null || (ObserveAfterWinOrLose && renderPlayer.WinState != WinState.Undefined) ? null : renderPlayer; }
|
get { return renderPlayer == null || (ObserveAfterWinOrLose && renderPlayer.WinState != WinState.Undefined) ? null : renderPlayer; }
|
||||||
|
|||||||
@@ -284,6 +284,7 @@
|
|||||||
<Compile Include="Player\PlaceBuilding.cs" />
|
<Compile Include="Player\PlaceBuilding.cs" />
|
||||||
<Compile Include="Player\ProductionQueue.cs" />
|
<Compile Include="Player\ProductionQueue.cs" />
|
||||||
<Compile Include="Player\ProvidesTechPrerequisite.cs" />
|
<Compile Include="Player\ProvidesTechPrerequisite.cs" />
|
||||||
|
<Compile Include="Player\MissionObjectives.cs" />
|
||||||
<Compile Include="PortableChrono.cs" />
|
<Compile Include="PortableChrono.cs" />
|
||||||
<Compile Include="Warheads\DestroyResourceWarhead.cs" />
|
<Compile Include="Warheads\DestroyResourceWarhead.cs" />
|
||||||
<Compile Include="Warheads\CreateEffectWarhead.cs" />
|
<Compile Include="Warheads\CreateEffectWarhead.cs" />
|
||||||
|
|||||||
202
OpenRA.Mods.RA/Player/MissionObjectives.cs
Normal file
202
OpenRA.Mods.RA/Player/MissionObjectives.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation. For more information,
|
||||||
|
* see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
using OpenRA.Traits;
|
||||||
|
|
||||||
|
namespace OpenRA.Mods.RA
|
||||||
|
{
|
||||||
|
public enum ObjectiveType { Primary, Secondary };
|
||||||
|
public enum ObjectiveState { Incomplete, Completed, Failed };
|
||||||
|
|
||||||
|
public class MissionObjective
|
||||||
|
{
|
||||||
|
public readonly ObjectiveType Type;
|
||||||
|
public readonly string Description;
|
||||||
|
public ObjectiveState State;
|
||||||
|
|
||||||
|
public MissionObjective(ObjectiveType type, string description)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Description = description;
|
||||||
|
State = ObjectiveState.Incomplete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MissionObjectivesInfo : ITraitInfo
|
||||||
|
{
|
||||||
|
[Desc("Set this to true if multiple cooperative players have a distinct set of " +
|
||||||
|
"objectives that each of them has to complete to win the game. This is mainly " +
|
||||||
|
"useful for multiplayer coop missions. Do not use this for skirmish team games.")]
|
||||||
|
public readonly bool Cooperative = false;
|
||||||
|
|
||||||
|
[Desc("If set to true, this setting causes the game to end immediately once the first " +
|
||||||
|
"player (or team of cooperative players) fails or completes his objectives. If " +
|
||||||
|
"set to false, players that fail their objectives will stick around and become observers.")]
|
||||||
|
public readonly bool EarlyGameOver = false;
|
||||||
|
|
||||||
|
[Desc("Delay between the game over condition being met, and the game actually ending, in milliseconds.")]
|
||||||
|
public readonly int GameOverDelay = 1500;
|
||||||
|
|
||||||
|
public object Create(ActorInitializer init) { return new MissionObjectives(init.world, this); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MissionObjectives : INotifyObjectivesUpdated, ISync
|
||||||
|
{
|
||||||
|
readonly MissionObjectivesInfo info;
|
||||||
|
readonly List<MissionObjective> objectives = new List<MissionObjective>();
|
||||||
|
public ReadOnlyList<MissionObjective> Objectives;
|
||||||
|
|
||||||
|
[Sync]
|
||||||
|
public int ObjectivesHash { get { return Objectives.Aggregate(0, (code, objective) => code ^ Sync.hash(objective.State)); } }
|
||||||
|
|
||||||
|
// This property is used as a flag in 'Cooperative' games to mark that the player has completed all his objectives.
|
||||||
|
// The player's WinState is only updated when his allies have all completed their objective as well.
|
||||||
|
public WinState WinStateCooperative { get; private set; }
|
||||||
|
|
||||||
|
public MissionObjectives(World world, MissionObjectivesInfo moInfo)
|
||||||
|
{
|
||||||
|
info = moInfo;
|
||||||
|
Objectives = new ReadOnlyList<MissionObjective>(objectives);
|
||||||
|
|
||||||
|
world.ObserveAfterWinOrLose = !info.EarlyGameOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(Player player, string description, ObjectiveType type = ObjectiveType.Primary)
|
||||||
|
{
|
||||||
|
var newID = objectives.Count;
|
||||||
|
|
||||||
|
objectives.Insert(newID, new MissionObjective(type, description));
|
||||||
|
|
||||||
|
foreach (var imo in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
||||||
|
imo.OnObjectiveAdded(player, newID);
|
||||||
|
|
||||||
|
return newID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkCompleted(Player player, int objectiveID)
|
||||||
|
{
|
||||||
|
if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Completed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
objectives[objectiveID].State = ObjectiveState.Completed;
|
||||||
|
|
||||||
|
if (objectives[objectiveID].Type == ObjectiveType.Primary)
|
||||||
|
{
|
||||||
|
var playerWon = objectives.Where(o => o.Type == ObjectiveType.Primary).All(o => o.State == ObjectiveState.Completed);
|
||||||
|
|
||||||
|
foreach (var imo in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
||||||
|
{
|
||||||
|
imo.OnObjectiveCompleted(player, objectiveID);
|
||||||
|
|
||||||
|
if (playerWon)
|
||||||
|
imo.OnPlayerWon(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerWon)
|
||||||
|
CheckIfGameIsOver(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkFailed(Player player, int objectiveID)
|
||||||
|
{
|
||||||
|
if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Failed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
objectives[objectiveID].State = ObjectiveState.Failed;
|
||||||
|
|
||||||
|
if (objectives[objectiveID].Type == ObjectiveType.Primary)
|
||||||
|
{
|
||||||
|
var playerLost = objectives.Where(o => o.Type == ObjectiveType.Primary).Any(o => o.State == ObjectiveState.Failed);
|
||||||
|
|
||||||
|
foreach (var imo in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
||||||
|
{
|
||||||
|
imo.OnObjectiveFailed(player, objectiveID);
|
||||||
|
|
||||||
|
if (playerLost)
|
||||||
|
imo.OnPlayerLost(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerLost)
|
||||||
|
CheckIfGameIsOver(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIfGameIsOver(Player player)
|
||||||
|
{
|
||||||
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||||
|
var allies = players.Where(p => p.IsAlliedWith(player));
|
||||||
|
|
||||||
|
var gameOver = ((info.EarlyGameOver && !info.Cooperative && player.WinState != WinState.Undefined) ||
|
||||||
|
(info.EarlyGameOver && info.Cooperative && allies.All(p => p.WinState != WinState.Undefined)) ||
|
||||||
|
players.All(p => p.WinState != WinState.Undefined));
|
||||||
|
|
||||||
|
if (gameOver)
|
||||||
|
{
|
||||||
|
Game.RunAfterDelay(info.GameOverDelay, () =>
|
||||||
|
{
|
||||||
|
player.World.EndGame();
|
||||||
|
player.World.SetPauseState(true);
|
||||||
|
player.World.PauseStateLocked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerWon(Player player)
|
||||||
|
{
|
||||||
|
if (info.Cooperative)
|
||||||
|
{
|
||||||
|
WinStateCooperative = WinState.Won;
|
||||||
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||||
|
var allies = players.Where(p => p.IsAlliedWith(player));
|
||||||
|
|
||||||
|
if (allies.All(p => p.PlayerActor.Trait<MissionObjectives>().WinStateCooperative == WinState.Won))
|
||||||
|
foreach (var p in allies)
|
||||||
|
{
|
||||||
|
p.WinState = WinState.Won;
|
||||||
|
p.World.OnPlayerWinStateChanged(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player.WinState = WinState.Won;
|
||||||
|
player.World.OnPlayerWinStateChanged(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPlayerLost(Player player)
|
||||||
|
{
|
||||||
|
if (info.Cooperative)
|
||||||
|
{
|
||||||
|
WinStateCooperative = WinState.Lost;
|
||||||
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
||||||
|
var allies = players.Where(p => p.IsAlliedWith(player));
|
||||||
|
|
||||||
|
if (allies.Any(p => p.PlayerActor.Trait<MissionObjectives>().WinStateCooperative == WinState.Lost))
|
||||||
|
foreach (var p in allies)
|
||||||
|
{
|
||||||
|
p.WinState = WinState.Lost;
|
||||||
|
p.World.OnPlayerWinStateChanged(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
player.WinState = WinState.Lost;
|
||||||
|
player.World.OnPlayerWinStateChanged(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnObjectiveAdded(Player player, int id) {}
|
||||||
|
public void OnObjectiveCompleted(Player player, int id) {}
|
||||||
|
public void OnObjectiveFailed(Player player, int id) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ Player:
|
|||||||
PlaceBuilding:
|
PlaceBuilding:
|
||||||
TechTree:
|
TechTree:
|
||||||
SupportPowerManager:
|
SupportPowerManager:
|
||||||
|
MissionObjectives:
|
||||||
ConquestVictoryConditions:
|
ConquestVictoryConditions:
|
||||||
PowerManager:
|
PowerManager:
|
||||||
AllyRepair:
|
AllyRepair:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Player:
|
|||||||
BlockedAudio: NoRoom
|
BlockedAudio: NoRoom
|
||||||
PlaceBuilding:
|
PlaceBuilding:
|
||||||
SupportPowerManager:
|
SupportPowerManager:
|
||||||
|
MissionObjectives:
|
||||||
ConquestVictoryConditions:
|
ConquestVictoryConditions:
|
||||||
PowerManager:
|
PowerManager:
|
||||||
AdviceInterval: 650
|
AdviceInterval: 650
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ Player:
|
|||||||
RequireOwner: false
|
RequireOwner: false
|
||||||
PlaceBuilding:
|
PlaceBuilding:
|
||||||
SupportPowerManager:
|
SupportPowerManager:
|
||||||
|
MissionObjectives:
|
||||||
ConquestVictoryConditions:
|
ConquestVictoryConditions:
|
||||||
PowerManager:
|
PowerManager:
|
||||||
AllyRepair:
|
AllyRepair:
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ Player:
|
|||||||
LowPowerSlowdown: 3
|
LowPowerSlowdown: 3
|
||||||
PlaceBuilding:
|
PlaceBuilding:
|
||||||
SupportPowerManager:
|
SupportPowerManager:
|
||||||
|
MissionObjectives:
|
||||||
ConquestVictoryConditions:
|
ConquestVictoryConditions:
|
||||||
PowerManager:
|
PowerManager:
|
||||||
AllyRepair:
|
AllyRepair:
|
||||||
|
|||||||
Reference in New Issue
Block a user