Client LuaCsForBarotrauma
MissionAction.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
13  partial class MissionAction : EventAction
14  {
15  [Serialize("", IsPropertySaveable.Yes, description: "Identifier of the mission to unlock.")]
16  public Identifier MissionIdentifier { get; set; }
17 
18  [Serialize("", IsPropertySaveable.Yes, description: "Tag of the mission to unlock. If there are multiple missions with the tag, one is chosen randomly.")]
19  public Identifier MissionTag { get; set; }
20 
21  [Serialize("", IsPropertySaveable.Yes, description: "The mission can only be unlocked in a location that's occupied by this faction.")]
22  public Identifier RequiredFaction { get; set; }
23 
24  public ImmutableArray<Identifier> LocationTypes { get; }
25 
26  [Serialize(0, IsPropertySaveable.Yes, description: "Minimum distance to the location the mission is unlocked in (1 = one path between locations).")]
27  public int MinLocationDistance { get; set; }
28 
29  [Serialize(true, IsPropertySaveable.Yes, description: "If true, the mission has to be unlocked in a location further on the campaign map.")]
30  public bool UnlockFurtherOnMap { get; set; }
31 
32  [Serialize(false, IsPropertySaveable.Yes, description: "If true, a suitable location is forced on the map if one isn't found.")]
33  public bool CreateLocationIfNotFound { get; set; }
34 
35  private bool isFinished;
36 
37  private readonly Random random;
38 
39  public MissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
40  {
41  if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty)
42  {
43  DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured.",
44  contentPackage: element.ContentPackage);
45  }
46  if (!MissionIdentifier.IsEmpty && !MissionTag.IsEmpty)
47  {
48  DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.",
49  contentPackage: element.ContentPackage);
50  }
51  LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
52  //the action chooses the same mission if
53  // 1. event seed is the same (based on level seed, changes when events are completed)
54  // 2. event is the same (two different events shouldn't choose the same mission)
55  // 3. the MissionAction is the same (two different actions in the same event shouldn't choose the same mission)
56  random = new MTRandom(
57  parentEvent.RandomSeed +
58  ToolBox.StringToInt(ParentEvent.Prefab.Identifier.Value) +
59  ParentEvent.Actions.Count);
60  }
61 
62  public override bool IsFinished(ref string goTo)
63  {
64  return isFinished;
65  }
66  public override void Reset()
67  {
68  isFinished = false;
69  }
70 
71  public override void Update(float deltaTime)
72  {
73  if (isFinished) { return; }
74 
75  Identifier missionDebugId = (MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier);
76 
77  if (GameMain.GameSession.GameMode is CampaignMode campaign)
78  {
79  Mission unlockedMission = null;
80  var unlockLocation = FindUnlockLocation(MinLocationDistance, UnlockFurtherOnMap, LocationTypes, mustAllowLocationTypeChanges: false);
81 
82  if (unlockLocation == null && UnlockFurtherOnMap)
83  {
84  DebugConsole.NewMessage($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" further on the map. Attempting to find a location earlier on the map...");
85  unlockLocation = FindUnlockLocation(MinLocationDistance, unlockFurtherOnMap: false, LocationTypes, mustAllowLocationTypeChanges: false);
86  }
87 
88  if (unlockLocation == null && CreateLocationIfNotFound)
89  {
90  DebugConsole.NewMessage($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\". Attempting to change the type of an empty location to create a suitable location...");
91  //find an empty location at least 3 steps away, further on the map
92  var emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: true, "none".ToIdentifier().ToEnumerable(),
93  mustAllowLocationTypeChanges: true,
94  requireCorrectFaction: false);
95  if (emptyLocation == null)
96  {
97  DebugConsole.NewMessage($"Failed to find a suitable empty location further on the map. Attempting to find a location earlier on the map...");
98  emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: false, "none".ToIdentifier().ToEnumerable(),
99  mustAllowLocationTypeChanges: true,
100  requireCorrectFaction: false);
101  }
102  if (emptyLocation != null)
103  {
104  System.Diagnostics.Debug.Assert(!emptyLocation.LocationTypeChangesBlocked);
105  emptyLocation.ChangeType(campaign, LocationType.Prefabs[LocationTypes[0]]);
106  unlockLocation = emptyLocation;
107  if (!RequiredFaction.IsEmpty)
108  {
109  emptyLocation.Faction = campaign.Factions.Find(f => f.Prefab.Identifier == RequiredFaction);
110  }
111  }
112  }
113 
114  if (unlockLocation != null)
115  {
116  if (!MissionIdentifier.IsEmpty)
117  {
118  unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier,
119  invokingContentPackage: ParentEvent.Prefab.ContentPackage);
120  }
121  else if (!MissionTag.IsEmpty)
122  {
123  unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random,
124  invokingContentPackage: ParentEvent.Prefab.ContentPackage);
125  }
126  if (campaign is MultiPlayerCampaign mpCampaign)
127  {
128  mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
129  }
130  if (unlockedMission != null)
131  {
132  unlockedMission.OriginLocation = campaign.Map.CurrentLocation;
133  campaign.Map.Discover(unlockLocation, checkTalents: false);
134  if (unlockedMission.Locations[0] == unlockedMission.Locations[1] || unlockedMission.Locations[1] == null)
135  {
136  DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the location \"{unlockLocation.DisplayName}\".");
137  }
138  else
139  {
140  DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the connection from \"{unlockedMission.Locations[0].DisplayName}\" to \"{unlockedMission.Locations[1].DisplayName}\".");
141  }
142 #if CLIENT
143  new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", unlockedMission.Name),
144  Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: unlockedMission.Prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
145  {
146  IconColor = unlockedMission.Prefab.IconColor
147  };
148 #else
149  missionsUnlockedThisRound.Add(unlockedMission);
150  NotifyMissionUnlock(unlockedMission);
151 #endif
152  }
153  }
154  else
155  {
156  DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})",
158  }
159  }
160  isFinished = true;
161  }
162 
163  private Location FindUnlockLocation(int minDistance, bool unlockFurtherOnMap, IEnumerable<Identifier> locationTypes, bool mustAllowLocationTypeChanges, bool requireCorrectFaction = true)
164  {
165  var campaign = GameMain.GameSession.GameMode as CampaignMode;
166  if (LocationTypes.Length == 0 && minDistance <= 1)
167  {
168  return campaign.Map.CurrentLocation;
169  }
170 
171  var currentLocation = campaign.Map.CurrentLocation;
172  int distance = 0;
173  HashSet<Location> checkedLocations = new HashSet<Location>();
174  HashSet<Location> pendingLocations = new HashSet<Location>() { currentLocation };
175  do
176  {
177  List<Location> currentLocations = pendingLocations.ToList();
178  pendingLocations.Clear();
179  foreach (var location in currentLocations)
180  {
181  checkedLocations.Add(location);
182  if (IsLocationValid(currentLocation, location, unlockFurtherOnMap, distance, minDistance, locationTypes, mustAllowLocationTypeChanges, requireCorrectFaction))
183  {
184  return location;
185  }
186  else
187  {
188  foreach (LocationConnection connection in location.Connections)
189  {
190  var otherLocation = connection.OtherLocation(location);
191  if (checkedLocations.Contains(otherLocation)) { continue; }
192  pendingLocations.Add(otherLocation);
193  }
194  }
195  }
196  distance++;
197  } while (pendingLocations.Any());
198 
199  return null;
200  }
201 
202  private bool IsLocationValid(Location currLocation, Location location, bool unlockFurtherOnMap, int distance, int minDistance, IEnumerable<Identifier> locationTypes, bool mustAllowLocationTypeChanges, bool requireCorrectFaction)
203  {
204  if (mustAllowLocationTypeChanges && location.LocationTypeChangesBlocked)
205  {
206  return false;
207  }
208  if (requireCorrectFaction && !RequiredFaction.IsEmpty)
209  {
210  if (location.Faction?.Prefab.Identifier != RequiredFaction &&
211  location.SecondaryFaction?.Prefab.Identifier != RequiredFaction)
212  {
213  return false;
214  }
215  }
216  if (!locationTypes.Contains(location.Type.Identifier) && !(location.HasOutpost() && locationTypes.Contains("AnyOutpost".ToIdentifier())))
217  {
218  return false;
219  }
220  if (distance < minDistance)
221  {
222  return false;
223  }
224  if (unlockFurtherOnMap && location.MapPosition.X < currLocation.MapPosition.X)
225  {
226  return false;
227  }
228  return true;
229  }
230 
231  public override string ToDebugString()
232  {
233  return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier)})";
234  }
235  }
236 }
Identifier[] GetAttributeIdentifierArray(Identifier[] def, params string[] keys)
ContentPackage? ContentPackage
readonly ScriptedEvent ParentEvent
Definition: EventAction.cs:102
EventPrefab Prefab
Definition: Event.cs:16
readonly int RandomSeed
Definition: Event.cs:12
static GameSession?? GameSession
Definition: GameMain.cs:88
static readonly PrefabCollection< LocationType > Prefabs
Definition: LocationType.cs:15
Mersenne Twister based random
Definition: MTRandom.cs:9
Unlocks a mission in a nearby level or location.
override bool IsFinished(ref string goTo)
Has the action finished.
override string ToDebugString()
Rich test to display in debugdraw
override void Update(float deltaTime)
ImmutableArray< Identifier > LocationTypes
override void Reset()
MissionAction(ScriptedEvent parentEvent, ContentXElement element)
Location OriginLocation
Where was this mission received from? Affects which faction we give reputation for if the mission is ...
ContentPackage? ContentPackage
Definition: Prefab.cs:37
readonly Identifier Identifier
Definition: Prefab.cs:34
List< EventAction > Actions