/// <overloads> /// Starts an interactive replay.</overloads> /// <summary> /// Replays all commands present in the specified <see cref="WorldState"/> but missing from /// the current <see cref="WorldState"/>.</summary> /// <param name="worldState"> /// The <see cref="WorldState"/> containing the new commands to show in an interactive /// replay.</param> /// <param name="isComputer"> /// The new value for the <see cref="IsComputerTurn"/> property while the replay is active. /// </param> /// <exception cref="ArgumentException"> /// One of the conditions described in <see cref="History.AddCommands"/>.</exception> /// <exception cref="ArgumentNullException"> /// <paramref name="worldState"/> is a null reference.</exception> /// <exception cref="PropertyValueException"> /// The current session <see cref="Session.State"/> is neither <see /// cref="SessionState.Closed"/>, <see cref="SessionState.Computer"/>, nor <see /// cref="SessionState.Human"/>.</exception> /// <remarks><para> /// <b>Start</b> sets the session <see cref="Session.State"/> to <see /// cref="SessionState.Replay"/> and <see cref="CurrentState"/> to <see /// cref="ReplayState.Play"/>, and begins an interactive replay of all history commands /// stored in the specified <paramref name="worldState"/> that are not present in the <see /// cref="WorldState.History"/> of the current session's <see cref="Session.WorldState"/>. /// </para><para> /// When the replay ends or is aborted, the current session's <see /// cref="Session.WorldState"/> will be set to the specified <paramref name="worldState"/>, /// and its previous value will be discarded. The session's original <see /// cref="Graphics.MapView.SelectedSite"/> is likewise discarded and replaced with <see /// cref="Site.InvalidLocation"/>. /// </para><para> /// The specified <paramref name="worldState"/> may contain the same number of commands as /// that of the current <see cref="Session"/>. In this case, <b>Start</b> briefly highlights /// the home site of the active faction but does nothing else.</para></remarks> public void Start(WorldState worldState, bool isComputer) { CheckStartState(); // store computer turn flag IsComputerTurn = isComputer; // store current history command count History history = Session.Instance.WorldState.History; int index = history.Commands.Count; // append new history commands, if any history.AddCommands(worldState.History); // set restore point to new world state this._originalWorldState = worldState; // save current session state this._originalState = Session.State; // ignore current site selection this._originalSelected = Site.InvalidLocation; // start replay with first new command Debug.Assert(index <= history.Commands.Count); this._commandIndex = index; // switch session to Replay state Session.State = SessionState.Replay; MainWindow.Instance.StatusMessage.Push(Global.Strings.StatusReplay); // show active faction's home site ShowFaction(Session.Instance.WorldState.ActiveFaction); // enter Play state CurrentState = ReplayState.Play; AsyncAction.Run(PlayCommands); }
/// <summary> /// Replays commands starting at the specified full turn and active faction indices. /// </summary> /// <param name="turn"> /// The index of the full turn at which to start interactive replay.</param> /// <param name="faction"> /// The index of the faction whose activation during the specified <paramref name="turn"/> /// should start interactive replay.</param> /// <exception cref="ArgumentOutOfRangeException"><para> /// <paramref name="turn"/> is greater than the <see cref="WorldState.CurrentTurn"/> of the /// current <see cref="Session.WorldState"/>. /// </para><para>-or-</para><para> /// <paramref name="faction"/> is less than zero.</para></exception> /// <exception cref="PropertyValueException"> /// The current session <see cref="Session.State"/> is neither <see /// cref="SessionState.Closed"/>, <see cref="SessionState.Computer"/>, nor <see /// cref="SessionState.Human"/>.</exception> /// <remarks><para> /// <b>Start</b> sets the session <see cref="Session.State"/> to <see /// cref="SessionState.Replay"/> and <see cref="CurrentState"/> to <see /// cref="ReplayState.Play"/>, skips ahead to the specified <paramref name="turn"/> and /// <paramref name="faction"/> indices, and then begins an interactive replay of all /// remaining commands stored in the session's <see cref="WorldState.History"/>. /// </para><para> /// <b>Start</b> shows an informational message and returns immediately if there are no /// commands to replay. /// </para><para> /// If the specified <paramref name="turn"/> is negative, <b>Start</b> shows a <see /// cref="Dialog.ChangeTurn"/> dialog, allowing the user to enter the turn at which to start /// interactive replay. /// </para><para> /// If the specified <paramref name="faction"/> is greater than the number of surviving /// factions at any point while skipping ahead, interactive replay will begin with the first /// active faction during the specified <paramref name="turn"/>.</para></remarks> public void Start(int turn, int faction) { CheckStartState(); Session session = Session.Instance; WorldState world = session.WorldState; if (turn > world.CurrentTurn) { ThrowHelper.ThrowArgumentOutOfRangeExceptionWithFormat( "turn", turn, Tektosyne.Strings.ArgumentGreaterValue, "CurrentTurn"); } if (faction < 0) { ThrowHelper.ThrowArgumentOutOfRangeException( "faction", faction, Tektosyne.Strings.ArgumentNegative); } // show message and quit if no commands to replay if (world.History.Commands.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogReplayNone, Global.Strings.TitleReplay, MessageBoxButton.OK, MessageBoxImage.Information); return; } if (turn < 0) { // ask user to specify starting turn var dialog = new Dialog.ChangeTurn( world.CurrentTurn, Global.Strings.TitleReplayFromTurn); dialog.Owner = MainWindow.Instance; if (dialog.ShowDialog() != true) { return; } // get turn entered by user turn = dialog.Turn; Debug.Assert(turn >= 0); Debug.Assert(turn <= world.CurrentTurn); } // save current session data this._originalWorldState = session.WorldState; this._originalSelected = Session.MapView.SelectedSite; this._originalState = Session.State; // switch session to Replay state Session.State = SessionState.Replay; // show replay control message... MainWindow.Instance.StatusMessage.Push(Global.Strings.StatusReplay); // ...but wait for new world state MainWindow.Instance.BeginWait(Global.Strings.StatusReplayCommands); MainWindow.Instance.StatusMessage.Push(); TaskEvents events = new TaskEvents(Application.Current.Dispatcher); events.TaskMessage += ((sender, args) => MainWindow.Instance.StatusMessage.Text = args.Value); try { // create world state from scenario WorldState worldState = new WorldState(); worldState.Initialize(events); // copy original turn count worldState.History.CopyFullTurns(this._originalWorldState.History); session.WorldState = worldState; } finally { MainWindow.Instance.StatusMessage.Pop(); MainWindow.Instance.EndWait(); } AsyncAction.Run(delegate { // skip to specified turn & faction this._commandIndex = 0; if (turn > 0 || faction > 0) { Skip(turn, faction); } // set default map view to new world state AsyncAction.Invoke(() => Session.MapView.WorldState = session.WorldState); // show active faction's home if not skipped if (this._commandIndex == 0) { ShowFaction(session.WorldState.ActiveFaction); } // enter Play state CurrentState = ReplayState.Play; PlayCommands(); }); }
/// <summary> /// Allows units of the <see cref="WorldState.ActiveFaction"/>, which must be controlled by /// a local human player, to perform an attack from or on the selected <see cref="Site"/>. /// </summary> /// <exception cref="PropertyValueException"> /// The current session <see cref="Session.State"/> is not <see cref="SessionState.Human"/>. /// </exception> /// <remarks><para> /// <b>Attack</b> shows the <see cref="Dialog.AttackFromSite"/> dialog if the selected site /// contains any units owned by the <see cref="WorldState.ActiveFaction"/> that report /// possible attack targets. If no owned units are present, the <see /// cref="Dialog.AttackSite"/> dialog is shown instead. /// </para><para> /// In either case, <b>Attack</b> issues an <see cref="AttackCommand"/> with the entered /// data for the <see cref="WorldState.ActiveFaction"/>.</para></remarks> public static void Attack() { CheckSessionState(); Session session = Session.Instance; WorldState world = session.WorldState; Faction faction = world.ActiveFaction; // retrieve selected source site Site source = world.GetSite(Session.MapView.SelectedSite); if (source == null) { return; } // retrieve owned units in source site List <Entity> present = new List <Entity>(); foreach (Entity unit in source.Units) { if (unit.Owner == faction) { present.Add(unit); } } // variables used by both "if" branches var eligible = new EntityList(); PointI target = Site.InvalidLocation; IList <PointI> targets = null; EntityList units = null; // prepare single-unit collection var singleUnit = new List <Entity>(1) { null }; // check if source site contains owned units if (present.Count > 0) { // determine units eligible for attack foreach (Unit unit in present) { singleUnit[0] = unit; targets = Finder.FindAttackTargets(world, singleUnit); if (targets.Count > 0) { eligible.Add(unit); } } // check if any present units may attack if (eligible.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogAttackSourceNone, Global.Strings.TitleAttackSite + faction.Name, MessageBoxButton.OK, MessageBoxImage.Information); return; } // show "Attack From Site" dialog for active faction using (var dialog = new Dialog.AttackFromSite(source, eligible)) { dialog.Owner = MainWindow.Instance; if (dialog.ShowDialog() != true) { return; } // retrieve user selections target = dialog.Target; units = dialog.Units; } } else { // retrieve placed units of active faction var placed = faction.GetEntities(EntityCategory.Unit, true); // check if faction owns any placed units if (placed.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogAttackPlacedNone, Global.Strings.TitleAttackSite + faction.Name, MessageBoxButton.OK, MessageBoxImage.Information); return; } // target selected site target = source; // determine units eligible for attack foreach (Unit unit in placed) { singleUnit[0] = unit; targets = Finder.FindAttackTargets(world, singleUnit); if (targets.Contains(target)) { eligible.Add(unit); } } // check if any units may attack if (eligible.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogAttackTargetNone, Global.Strings.TitleAttackSite + faction.Name, MessageBoxButton.OK, MessageBoxImage.Information); return; } // show "Attack Site" dialog for active faction using (var dialog = new Dialog.AttackSite(target, eligible)) { dialog.Owner = MainWindow.Instance; if (dialog.ShowDialog() != true) { return; } // retrieve user selection units = dialog.Units; } } // issue Attack command for valid selections if (target != Site.InvalidLocation && units != null && units.Count > 0) { AsyncAction.Run(() => session.Executor.ExecuteAttack(world, units, target)); } }
/// <summary> /// Allows selected units of the <see cref="WorldState.ActiveFaction"/>, which must be /// controlled by a local human player, to move to another <see cref="Site"/>.</summary> /// <exception cref="PropertyValueException"> /// The current session <see cref="Session.State"/> is not <see cref="SessionState.Human"/>. /// </exception> /// <remarks> /// <b>Move</b> shows the <see cref="Dialog.MoveUnits"/> dialog if the selected site /// contains any units owned by the <see cref="WorldState.ActiveFaction"/> that report any /// possible movement targets, and issues a <b>Move</b> command for the <see /// cref="WorldState.ActiveFaction"/> with the entered data.</remarks> public static void Move() { CheckSessionState(); Session session = Session.Instance; WorldState world = session.WorldState; Faction faction = world.ActiveFaction; // retrieve selected source site Site source = world.GetSite(Session.MapView.SelectedSite); if (source == null) { return; } // retrieve owned units in source site var present = new EntityList(); foreach (Entity unit in source.Units) { if (unit.Owner == faction) { present.Add(unit); } } // check if source site contains owned units if (present.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogMoveSourceEmpty, Global.Strings.TitleMoveUnits + faction.Name, MessageBoxButton.OK, MessageBoxImage.Information); return; } var eligible = new EntityList(); IList <PointI> targets = null; // prepare single-unit collection var singleUnit = new List <Entity>(1) { null }; // determine units eligible for movement foreach (Unit unit in present) { singleUnit[0] = unit; targets = Finder.FindMoveTargets(world, singleUnit); if (targets.Count > 0) { eligible.Add(unit); } } // check if any present units may move if (eligible.Count == 0) { MessageBox.Show(MainWindow.Instance, Global.Strings.DialogMoveSourceNone, Global.Strings.TitleMoveUnits + faction.Name, MessageBoxButton.OK, MessageBoxImage.Information); return; } PointI target = Site.InvalidLocation; EntityList units = null; // show "Move Units" dialog for active faction using (var dialog = new Dialog.MoveUnits(source, eligible)) { dialog.Owner = MainWindow.Instance; if (dialog.ShowDialog() != true) { return; } // retrieve user selections target = dialog.Target; units = dialog.Units; } // issue Move command for valid selections if (target != Site.InvalidLocation && units != null && units.Count > 0) { AsyncAction.Run(() => session.Executor.ExecuteMove(world, units, target)); } }