280 lines
8.2 KiB
C#
280 lines
8.2 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2020 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, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.Traits
|
|
{
|
|
public enum ObjectiveState { Incomplete, Completed, Failed }
|
|
|
|
public class MissionObjective
|
|
{
|
|
public readonly bool Required;
|
|
public readonly string Description;
|
|
public readonly string Type;
|
|
public ObjectiveState State;
|
|
|
|
public MissionObjective(string description, string type, bool required = true)
|
|
{
|
|
Description = description;
|
|
Type = type;
|
|
Required = required;
|
|
State = ObjectiveState.Incomplete;
|
|
}
|
|
}
|
|
|
|
public class MissionObjectivesInfo : TraitInfo
|
|
{
|
|
[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;
|
|
|
|
[NotificationReference("Speech")]
|
|
public readonly string WinNotification = null;
|
|
|
|
[NotificationReference("Speech")]
|
|
public readonly string LoseNotification = null;
|
|
|
|
[NotificationReference("Speech")]
|
|
public readonly string LeaveNotification = null;
|
|
|
|
public override object Create(ActorInitializer init) { return new MissionObjectives(init.World, this); }
|
|
}
|
|
|
|
public class MissionObjectives : INotifyWinStateChanged, ISync, IResolveOrder
|
|
{
|
|
public readonly MissionObjectivesInfo Info;
|
|
readonly List<MissionObjective> objectives = new List<MissionObjective>();
|
|
public ReadOnlyList<MissionObjective> Objectives;
|
|
|
|
[Sync]
|
|
public int ObjectivesHash
|
|
{
|
|
get
|
|
{
|
|
var hash = 0;
|
|
foreach (var objective in objectives)
|
|
hash ^= Sync.HashUsingHashCode(objective.State);
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
// 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 info)
|
|
{
|
|
Info = info;
|
|
Objectives = new ReadOnlyList<MissionObjective>(objectives);
|
|
}
|
|
|
|
public int Add(Player player, string description, string type, bool required = true, bool inhibitAnnouncement = false)
|
|
{
|
|
var newID = objectives.Count;
|
|
objectives.Insert(newID, new MissionObjective(description, type, required));
|
|
|
|
ObjectiveAdded(player, inhibitAnnouncement);
|
|
foreach (var inou in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
|
inou.OnObjectiveAdded(player, newID);
|
|
|
|
return newID;
|
|
}
|
|
|
|
public void MarkCompleted(Player player, int objectiveID)
|
|
{
|
|
if (objectiveID >= objectives.Count || objectives[objectiveID].State != ObjectiveState.Incomplete)
|
|
return;
|
|
|
|
objectives[objectiveID].State = ObjectiveState.Completed;
|
|
foreach (var inou in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
|
inou.OnObjectiveCompleted(player, objectiveID);
|
|
|
|
if (objectives[objectiveID].Required
|
|
&& objectives.Where(o => o.Required).All(o => o.State == ObjectiveState.Completed))
|
|
{
|
|
foreach (var inwc in player.PlayerActor.TraitsImplementing<INotifyWinStateChanged>())
|
|
inwc.OnPlayerWon(player);
|
|
|
|
CheckIfGameIsOver(player);
|
|
}
|
|
}
|
|
|
|
public void MarkFailed(Player player, int objectiveID)
|
|
{
|
|
if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Failed)
|
|
return;
|
|
|
|
objectives[objectiveID].State = ObjectiveState.Failed;
|
|
foreach (var inou in player.PlayerActor.TraitsImplementing<INotifyObjectivesUpdated>())
|
|
inou.OnObjectiveFailed(player, objectiveID);
|
|
|
|
if (objectives[objectiveID].Required)
|
|
{
|
|
foreach (var inwc in player.PlayerActor.TraitsImplementing<INotifyWinStateChanged>())
|
|
inwc.OnPlayerLost(player);
|
|
|
|
CheckIfGameIsOver(player);
|
|
}
|
|
}
|
|
|
|
void CheckIfGameIsOver(Player player)
|
|
{
|
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
|
|
|
var gameOver = players.All(p => p.WinState != WinState.Undefined || !p.HasObjectives);
|
|
if (gameOver)
|
|
Game.RunAfterDelay(Info.GameOverDelay, () =>
|
|
{
|
|
if (!Game.IsCurrentWorld(player.World))
|
|
return;
|
|
|
|
player.World.EndGame();
|
|
player.World.SetPauseState(true);
|
|
player.World.PauseStateLocked = true;
|
|
});
|
|
}
|
|
|
|
void INotifyWinStateChanged.OnPlayerWon(Player player)
|
|
{
|
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
|
var enemies = players.Where(p => !p.IsAlliedWith(player));
|
|
|
|
if (Info.Cooperative)
|
|
{
|
|
WinStateCooperative = WinState.Won;
|
|
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);
|
|
}
|
|
|
|
if (Info.EarlyGameOver)
|
|
foreach (var p in enemies)
|
|
p.PlayerActor.Trait<MissionObjectives>().ForceDefeat(p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player.WinState = WinState.Won;
|
|
player.World.OnPlayerWinStateChanged(player);
|
|
|
|
if (Info.EarlyGameOver)
|
|
foreach (var p in enemies)
|
|
p.PlayerActor.Trait<MissionObjectives>().ForceDefeat(p);
|
|
}
|
|
|
|
CheckIfGameIsOver(player);
|
|
}
|
|
|
|
void INotifyWinStateChanged.OnPlayerLost(Player player)
|
|
{
|
|
var players = player.World.Players.Where(p => !p.NonCombatant);
|
|
var enemies = players.Where(p => !p.IsAlliedWith(player));
|
|
|
|
if (Info.Cooperative)
|
|
{
|
|
WinStateCooperative = WinState.Lost;
|
|
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);
|
|
}
|
|
|
|
if (Info.EarlyGameOver)
|
|
{
|
|
foreach (var p in enemies)
|
|
{
|
|
p.WinState = WinState.Won;
|
|
p.World.OnPlayerWinStateChanged(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player.WinState = WinState.Lost;
|
|
player.World.OnPlayerWinStateChanged(player);
|
|
|
|
if (Info.EarlyGameOver)
|
|
{
|
|
foreach (var p in enemies)
|
|
{
|
|
p.WinState = WinState.Won;
|
|
p.World.OnPlayerWinStateChanged(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
CheckIfGameIsOver(player);
|
|
}
|
|
|
|
public void ForceDefeat(Player player)
|
|
{
|
|
for (var id = 0; id < Objectives.Count; id++)
|
|
if (Objectives[id].State == ObjectiveState.Incomplete)
|
|
MarkFailed(player, id);
|
|
}
|
|
|
|
public event Action<Player, bool> ObjectiveAdded = (player, inhibitAnnouncement) => { player.HasObjectives = true; };
|
|
|
|
public void ResolveOrder(Actor self, Order order)
|
|
{
|
|
if (order.OrderString == "Surrender")
|
|
{
|
|
ForceDefeat(self.Owner);
|
|
self.Owner.Spectating = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Desc("Provides game mode progress information for players.",
|
|
"Goes on WorldActor - observers don't have a player it can live on.",
|
|
"Current options for PanelName are 'SKIRMISH_STATS' and 'MISSION_OBJECTIVES'.")]
|
|
public class ObjectivesPanelInfo : TraitInfo
|
|
{
|
|
public string PanelName = null;
|
|
|
|
[Desc("in ms")]
|
|
public int ExitDelay = 1400;
|
|
|
|
public override object Create(ActorInitializer init) { return new ObjectivesPanel(this); }
|
|
}
|
|
|
|
public class ObjectivesPanel : IObjectivesPanel
|
|
{
|
|
readonly ObjectivesPanelInfo info;
|
|
public ObjectivesPanel(ObjectivesPanelInfo info) { this.info = info; }
|
|
public string PanelName { get { return info.PanelName; } }
|
|
public int ExitDelay { get { return info.ExitDelay; } }
|
|
}
|
|
}
|