Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Events/EventManager.cs
1 #nullable enable
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System;
7 using System.Collections.Generic;
8 using System.Collections.Immutable;
9 using System.Linq;
10 using System.Xml.Linq;
11 
12 namespace Barotrauma
13 {
14  partial class EventManager
15  {
16  private Graph intensityGraph;
17  private Graph targetIntensityGraph;
18  private Graph monsterStrengthGraph;
19  private const float intensityGraphUpdateInterval = 10;
20  private float lastIntensityUpdate;
21 
22  private Vector2 pinnedPosition = new Vector2(256, 128);
23  private bool isDragging;
24 
25  public Event? PinnedEvent { get; set; }
26 
27  private bool isGraphSelected;
28 
29  public void DebugDraw(SpriteBatch spriteBatch)
30  {
31  foreach (Event ev in activeEvents)
32  {
33  Vector2 drawPos = ev.DebugDrawPos;
34  drawPos.Y = -drawPos.Y;
35 
36  var textOffset = new Vector2(-150, 0);
37  spriteBatch.DrawCircle(drawPos, 600, 6, Color.White, thickness: 20);
38  GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUIStyle.LargeFont);
39  }
40  }
41 
42  public void DebugDrawHUD(SpriteBatch spriteBatch, float y)
43  {
44  foreach (ScriptedEvent scriptedEvent in activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
45  {
46  DrawEventTargetTags(spriteBatch, scriptedEvent);
47  }
48 
49  float theoreticalMaxMonsterStrength = 10000;
50  float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * (GameMain.GameSession?.Level?.Difficulty ?? 0f) / 100;
51  float absoluteMonsterStrength = monsterStrength / theoreticalMaxMonsterStrength;
52  float relativeMonsterStrength = monsterStrength / relativeMaxMonsterStrength;
53 
54  GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, backgroundColor: Color.Black * 0.6f, font: GUIStyle.SmallFont);
55  DrawString("Event cooldown", Math.Max(eventCoolDown, 0), Color.White, spacing: 20);
56  DrawString("Current intensity", Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUIStyle.Red, currentIntensity));
57  DrawString("Target intensity", Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUIStyle.Red, targetIntensity));
58  DrawString("Crew health", Math.Round(avgCrewHealth * 100), Color.Lerp(GUIStyle.Red, GUIStyle.Green, avgCrewHealth));
59  DrawString("Hull integrity", Math.Round(avgHullIntegrity * 100), Color.Lerp(GUIStyle.Red, GUIStyle.Green, avgHullIntegrity));
60  DrawString("Flooding amount", Math.Round(floodingAmount * 100), Color.Lerp(GUIStyle.Green, GUIStyle.Red, floodingAmount));
61  DrawString("Fire amount", Math.Round(fireAmount * 100), Color.Lerp(GUIStyle.Green, GUIStyle.Red, fireAmount));
62  DrawString("Enemy danger", Math.Round(enemyDanger * 100), Color.Lerp(GUIStyle.Green, GUIStyle.Red, enemyDanger));
63  DrawString("Current monster strength (total)", Math.Round(monsterStrength), Color.Lerp(GUIStyle.Green, GUIStyle.Red, relativeMonsterStrength));
64  DrawString("Main events", Math.Round(CumulativeMonsterStrengthMain), Color.White);
65  DrawString("Ruin events", Math.Round(CumulativeMonsterStrengthRuins), Color.White);
66  DrawString("Wreck events", Math.Round(CumulativeMonsterStrengthWrecks), Color.White);
67 
68  void DrawString(string text, double value, Color textColor, int spacing = 15)
69  {
70  y += GUI.AdjustForTextScale(spacing);
71  GUI.DrawString(spriteBatch, new Vector2(15, y), $"{text}: {(int)value}", textColor, backgroundColor: Color.Black * 0.6f, font: GUIStyle.SmallFont);
72  }
73 
74 #if DEBUG
75  if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
76  PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.T))
77  {
78  eventCoolDown = 1.0f;
79  }
80 #endif
81 
82  if (intensityGraph == null)
83  {
84  int graphDensity = 360; // 60 min
85  intensityGraph = new Graph(graphDensity);
86  targetIntensityGraph = new Graph(graphDensity);
87  monsterStrengthGraph = new Graph(graphDensity);
88  }
89 
90  if (Timing.TotalTime > lastIntensityUpdate + intensityGraphUpdateInterval)
91  {
92  intensityGraph.Update(currentIntensity);
93  targetIntensityGraph.Update(targetIntensity);
94  monsterStrengthGraph.Update(relativeMonsterStrength);
95  lastIntensityUpdate = (float)Timing.TotalTime;
96  }
97 
98  Rectangle graphRect = new Rectangle(15, (int)(y + GUI.AdjustForTextScale(55)), (int)(200 * GUI.xScale), (int)(100 * GUI.yScale));
99  bool isGraphHovered = graphRect.Contains(PlayerInput.MousePosition);
102  if (!isGraphSelected && isGraphHovered && leftMousePressed)
103  {
104  isGraphSelected = true;
105  }
106  if (isGraphSelected && rightMousePressed)
107  {
108  isGraphSelected = false;
109  }
110  Color intensityColor = Color.Lerp(Color.White, GUIStyle.Red, currentIntensity);
111  if (isGraphHovered || isGraphSelected)
112  {
113  int padding = 15;
114  int graphHeight = Math.Min((int)(GameMain.GraphicsHeight * 0.35f), GameMain.GraphicsHeight - (graphRect.Top + 3 * padding));
115  graphRect.Size = new Point(GameMain.GraphicsWidth - 2 * padding, graphHeight);
116  intensityColor = Color.Red;
117  GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.95f, isFilled: true);
118  }
119  else
120  {
121  GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.6f, isFilled: true);
122  }
123  intensityGraph.Draw(spriteBatch, graphRect, maxValue: 1.0f, xOffset: 0, intensityColor, (sBatch, value, order, pos) =>
124  {
125  if (isGraphHovered || isGraphSelected)
126  {
127  Vector2 bottomPoint = new Vector2(pos.X, graphRect.Bottom);
128  float height = 3 * GUI.yScale;
129  if (order % 6 == 0)
130  {
131  height *= 3;
132  string text = (order / 6).ToString();
133  var font = GUIStyle.SmallFont;
134  Vector2 textSize = font.MeasureString(text);
135  Vector2 textPos = new Vector2(bottomPoint.X - textSize.X / 2, bottomPoint.Y + height * 1.5f);
136  GUI.DrawString(sBatch, textPos, text, Color.White, font: font);
137  }
138  GUI.DrawLine(sBatch, bottomPoint, bottomPoint + Vector2.UnitY * height, Color.White, width: Math.Max(GUI.Scale, 1));
139  DrawTimeStamps(sBatch, Color.Red, pos, order);
140  }
141  });
142  targetIntensityGraph.Draw(spriteBatch, graphRect, maxValue: 1.0f, xOffset: 0, intensityColor * 0.5f);
143  if (isGraphHovered || isGraphSelected)
144  {
145  float? maxValue = 1;
146  Color color = Color.White;
147  if (relativeMonsterStrength > 1)
148  {
149  maxValue = null;
150  color = Color.Yellow;
151  }
152  monsterStrengthGraph.Draw(spriteBatch, graphRect, maxValue, color: color, doForEachValue: (sBatch, value, order, pos) => DrawTimeStamps(sBatch, color, pos, order));
153  }
154 
155  void DrawTimeStamps(SpriteBatch sBatch, Color color, Vector2 pos, int order)
156  {
157  if (isGraphHovered || isGraphSelected)
158  {
159  foreach (var timeStamp in timeStamps)
160  {
161  int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
162  if (t == order)
163  {
164  float size = 6;
165  Vector2 p = new Vector2(pos.X - size / 2, pos.Y - size / 2);
166  ShapeExtensions.DrawPoint(sBatch, p, color, size);
167  break;
168  }
169  }
170  }
171  }
172 
173  GUI.DrawLine(spriteBatch,
174  new Vector2(graphRect.Right, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)),
175  new Vector2(graphRect.Right + 5, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), Color.Orange, width: 3);
176 
177  int yStep = (int)(20 * GUI.yScale);
178  y = graphRect.Bottom + yStep;
179  if (isGraphHovered || isGraphSelected)
180  {
181  y += yStep;
182  }
183  int x = graphRect.X;
184  float adjustedYStep = GUI.AdjustForTextScale(15);
185  if (isCrewAway && crewAwayDuration < settings.FreezeDurationWhenCrewAway)
186  {
187  GUI.DrawString(spriteBatch, new Vector2(x, y), "Events frozen (crew away from sub): " + ToolBox.SecondsToReadableTime(settings.FreezeDurationWhenCrewAway - crewAwayDuration), Color.LightGreen * 0.8f, null, 0, GUIStyle.SmallFont);
188  y += adjustedYStep;
189  }
190  else if (crewAwayResetTimer > 0.0f)
191  {
192  GUI.DrawString(spriteBatch, new Vector2(x, y), "Events frozen (crew just returned to the sub): " + ToolBox.SecondsToReadableTime(crewAwayResetTimer), Color.LightGreen * 0.8f, null, 0, GUIStyle.SmallFont);
193  y += adjustedYStep;
194  }
195  else if (eventCoolDown > 0.0f)
196  {
197  GUI.DrawString(spriteBatch, new Vector2(x, y), "Event cooldown active: " + ToolBox.SecondsToReadableTime(eventCoolDown), Color.LightGreen * 0.8f, null, 0, GUIStyle.SmallFont);
198  y += adjustedYStep;
199  }
200  else if (currentIntensity > eventThreshold)
201  {
202  GUI.DrawString(spriteBatch, new Vector2(x, y),
203  "Intensity too high for new events: " + (int)(currentIntensity * 100) + "%/" + (int)(eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUIStyle.SmallFont);
204  y += adjustedYStep;
205  }
206 
207  adjustedYStep = GUI.AdjustForTextScale(12);
208  foreach (EventSet eventSet in pendingEventSets)
209  {
210  if (Submarine.MainSub == null) { break; }
211 
212  GUI.DrawString(spriteBatch, new Vector2(x, y), "New event (ID " + eventSet.Identifier + ") after: ", Color.Orange * 0.8f, null, 0, GUIStyle.SmallFont);
213  y += adjustedYStep;
214 
215  if (eventSet.PerCave)
216  {
217  GUI.DrawString(spriteBatch, new Vector2(x, y), " submarine near cave", Color.Orange * 0.8f, null, 0, GUIStyle.SmallFont);
218  y += adjustedYStep;
219  }
220  if (eventSet.PerWreck)
221  {
222  GUI.DrawString(spriteBatch, new Vector2(x, y), " submarine near the wreck", Color.Orange * 0.8f, null, 0, GUIStyle.SmallFont);
223  y += adjustedYStep;
224  }
225  if (eventSet.PerRuin)
226  {
227  GUI.DrawString(spriteBatch, new Vector2(x, y), " submarine near the ruins", Color.Orange * 0.8f, null, 0, GUIStyle.SmallFont);
228  y += adjustedYStep;
229  }
230  if (roundDuration < eventSet.MinMissionTime)
231  {
232  GUI.DrawString(spriteBatch, new Vector2(x, y),
233  " " + (int) (eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int) (distanceTraveled * 100.0f) + " %)",
234  ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) ? Color.Lerp(GUIStyle.Yellow, GUIStyle.Red, eventSet.MinDistanceTraveled - distanceTraveled) : GUIStyle.Green) * 0.8f, null, 0, GUIStyle.SmallFont);
235  y += adjustedYStep;
236  }
237 
238  if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
239  {
240  GUI.DrawString(spriteBatch, new Vector2(x, y),
241  " intensity between " + eventSet.MinIntensity.FormatDoubleDecimal() + " and " + eventSet.MaxIntensity.FormatDoubleDecimal(),
242  Color.Orange * 0.8f, null, 0, GUIStyle.SmallFont);
243  y += adjustedYStep;
244  }
245 
246  if (roundDuration < eventSet.MinMissionTime)
247  {
248  GUI.DrawString(spriteBatch, new Vector2(x, y),
249  " " + (int) (eventSet.MinMissionTime - roundDuration) + " s",
250  Color.Lerp(GUIStyle.Yellow, GUIStyle.Red, (eventSet.MinMissionTime - roundDuration)), null, 0, GUIStyle.SmallFont);
251  }
252 
253  y += GUI.AdjustForTextScale(15);
254 
255  if (y > GameMain.GraphicsHeight * 0.9f)
256  {
257  y = graphRect.Bottom + yStep * 2;
258  x += 300;
259  }
260  }
261 
262  GUI.DrawString(spriteBatch, new Vector2(x, y), "Current events: ", Color.White * 0.9f, null, 0, GUIStyle.SmallFont);
263  y += yStep;
264 
265  adjustedYStep = GUI.AdjustForTextScale(18);
266  foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
267  {
268  GUI.DrawString(spriteBatch, new Vector2(x + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUIStyle.SmallFont);
269 
270  Rectangle rect = new Rectangle(new Point(x + 5, (int)y), GUIStyle.SmallFont.MeasureString(ev.ToString()).ToPoint());
271 
272  Rectangle outlineRect = new Rectangle(rect.Location, rect.Size);
273  outlineRect.Inflate(4, 4);
274 
275  if (PinnedEvent == ev) { GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); }
276 
277  if (rect.Contains(PlayerInput.MousePosition))
278  {
279  GUI.MouseCursor = CursorState.Hand;
280  GUI.DrawRectangle(spriteBatch, outlineRect, Color.White);
281  if (ev != PinnedEvent)
282  {
283  DrawEvent(spriteBatch, ev, rect);
284  }
285  else if (rightMousePressed)
286  {
287  PinnedEvent = null;
288  }
289  if (leftMousePressed)
290  {
291  PinnedEvent = ev;
292  }
293  }
294 
295  y += adjustedYStep;
296  if (y > GameMain.GraphicsHeight * 0.9f)
297  {
298  y = graphRect.Bottom + yStep * 2;
299  x += 300;
300  }
301  }
302  }
303 
304  public void DrawPinnedEvent(SpriteBatch spriteBatch)
305  {
306  if (PinnedEvent != null)
307  {
308  Rectangle rect = DrawEvent(spriteBatch, PinnedEvent, null);
309 
310  if (rect != Rectangle.Empty)
311  {
312  if (rect.Contains(PlayerInput.MousePosition) && !isDragging)
313  {
314  GUI.MouseCursor = CursorState.Move;
316  {
317  isDragging = true;
318  }
319 
321  {
322  PinnedEvent = null;
323  isDragging = false;
324  }
325  }
326  }
327 
328  if (isDragging)
329  {
330  GUI.MouseCursor = CursorState.Dragging;
331  pinnedPosition = PlayerInput.MousePosition - (new Vector2(rect.Width / 2.0f, -24));
333  {
334  isDragging = false;
335  }
336  }
337  }
338  }
339 
340  private static void DrawEventTargetTags(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent)
341  {
342  if (Screen.Selected is GameScreen screen)
343  {
344  Camera cam = screen.Cam;
345  Dictionary<Entity, List<Identifier>> tagsDictionary = new Dictionary<Entity, List<Identifier>>();
346  foreach ((Identifier key, List<Entity> value) in scriptedEvent.Targets)
347  {
348  foreach (Entity entity in value)
349  {
350  if (tagsDictionary.ContainsKey(entity))
351  {
352  tagsDictionary[entity].Add(key);
353  }
354  else
355  {
356  tagsDictionary.Add(entity, new List<Identifier> { key });
357  }
358  }
359  }
360 
361  Identifier identifier = scriptedEvent.Prefab.Identifier;
362 
363  foreach ((Entity entity, List<Identifier> tags) in tagsDictionary)
364  {
365  if (entity.Removed) { continue; }
366 
367  string text = tags.Aggregate("Tags:\n", (current, tag) => current + $" {tag.ColorizeObject()}\n").TrimEnd('\r', '\n');
368  if (!identifier.IsEmpty) { text = $"Event: {identifier.ColorizeObject()}\n{text}"; }
369 
370  ImmutableArray<RichTextData>? richTextData = RichTextData.GetRichTextData(text, out text);
371 
372  Vector2 entityPos = cam.WorldToScreen(entity.WorldPosition);
373  Vector2 infoSize = GUIStyle.SmallFont.MeasureString(text);
374 
375  Vector2 infoPos = entityPos + new Vector2(128 * cam.Zoom, -(128 * cam.Zoom));
376  infoPos.Y -= infoSize.Y / 2;
377 
378  Rectangle infoRect = new Rectangle(infoPos.ToPoint(), infoSize.ToPoint());
379  infoRect.Inflate(4, 4);
380 
381  GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
382  GUI.DrawRectangle(spriteBatch, infoRect, Color.White, isFilled: false);
383 
384  GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextData, font: GUIStyle.SmallFont);
385 
386  GUI.DrawLine(spriteBatch, entityPos, new Vector2(infoRect.Location.X, infoRect.Location.Y + infoRect.Height / 2), Color.White);
387  }
388  }
389  }
390 
391  private readonly struct DebugLine
392  {
393  public readonly Vector2 Position;
394  public readonly Color Color;
395 
396  public DebugLine(Vector2 position, Color color)
397  {
398  Position = position;
399  Color = color;
400  }
401  }
402 
403  private Rectangle DrawEvent(SpriteBatch spriteBatch, Event ev, Rectangle? parentRect = null)
404  {
405  return ev switch
406  {
407  ScriptedEvent scriptedEvent => DrawScriptedEvent(spriteBatch, scriptedEvent, parentRect),
408  ArtifactEvent artifactEvent => DrawArtifactEvent(spriteBatch, artifactEvent, parentRect),
409  MonsterEvent monsterEvent => DrawMonsterEvent(spriteBatch, monsterEvent, parentRect),
410  _ => Rectangle.Empty
411  };
412  }
413 
414  private Rectangle DrawScriptedEvent(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent, Rectangle? parentRect = null)
415  {
416  List<DebugLine> positions = new List<DebugLine>();
417 
418  string text = scriptedEvent.GetDebugInfo();
419  if (scriptedEvent.Targets != null)
420  {
421  foreach ((_, List<Entity> entities) in scriptedEvent.Targets)
422  {
423  if (entities == null || !entities.Any()) { continue; }
424 
425  foreach (var entity in entities)
426  {
427  positions.Add(new DebugLine(entity.WorldPosition, Color.White));
428  }
429  }
430  }
431 
432  return DrawInfoRectangle(spriteBatch, scriptedEvent, text, parentRect, positions);
433  }
434 
435  private readonly List<DebugLine> debugPositions = new List<DebugLine>();
436 
437  private Rectangle DrawArtifactEvent(SpriteBatch spriteBatch, ArtifactEvent artifactEvent, Rectangle? parentRect = null)
438  {
439  debugPositions.Clear();
440 
441  string text = artifactEvent.GetDebugInfo();
442 
443  if (artifactEvent.Item != null && !artifactEvent.Item.Removed)
444  {
445  Vector2 pos = artifactEvent.Item.WorldPosition;
446  debugPositions.Add(new DebugLine(pos, Color.White));
447  }
448 
449  return DrawInfoRectangle(spriteBatch, artifactEvent, text, parentRect, debugPositions);
450  }
451 
452  private Rectangle DrawMonsterEvent(SpriteBatch spriteBatch, MonsterEvent monsterEvent, Rectangle? parentRect = null)
453  {
454  debugPositions.Clear();
455 
456  string text = monsterEvent.GetDebugInfo();
457 
458  if (monsterEvent.SpawnPos != null && Submarine.MainSub != null)
459  {
460  Vector2 pos = monsterEvent.SpawnPos.Value;
461  text += $"Distance from submarine: {Vector2.Distance(pos, Submarine.MainSub.WorldPosition).ColorizeObject()}\n";
462  debugPositions.Add(new DebugLine(pos, Color.White));
463  }
464 
465  if (monsterEvent.Monsters != null)
466  {
467  text += !monsterEvent.Monsters.Any() ? $"Monsters: {"None".ColorizeObject()}" : "Monsters:\n";
468 
469  foreach (Character monster in monsterEvent.Monsters)
470  {
471  text += $" {monster.ColorizeObject()} -> (Dead: {monster.IsDead.ColorizeObject()}, Health: {monster.HealthPercentage.ColorizeObject()}%, AIState: {(monster.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle ).ColorizeObject()})\n";
472  if (monster.Removed) { continue; }
473  debugPositions.Add(new DebugLine(monster.WorldPosition, Color.Red));
474  }
475  }
476  return DrawInfoRectangle(spriteBatch, monsterEvent, text, parentRect, debugPositions);
477  }
478 
479  private Rectangle DrawInfoRectangle(SpriteBatch spriteBatch, Event @event, string text, Rectangle? parentRect = null, List<DebugLine>? drawPoints = null)
480  {
481  text = text.TrimEnd('\r', '\n');
482 
483  Identifier identifier = @event.Prefab.Identifier;
484  if (!identifier.IsEmpty)
485  {
486  text = $"Identifier: {identifier.ColorizeObject()}\n{text}";
487  }
488 
489  ImmutableArray<RichTextData>? richTextData = RichTextData.GetRichTextData(text, out text);
490 
491  Vector2 size = GUIStyle.SmallFont.MeasureString(text);
492  Vector2 pos = pinnedPosition;
493  Rectangle infoRect;
494  Rectangle? infoBarRect = null;
495 
496  if (parentRect != null)
497  {
498  Rectangle rect = parentRect.Value;
499  pos = new Vector2(350, GameMain.GraphicsHeight / 2.0f - size.Y / 2);
500  infoRect = new Rectangle(pos.ToPoint(), size.ToPoint());
501  infoRect.Inflate(8, 8);
502 
503  GUI.DrawLine(spriteBatch, new Vector2(rect.Right, rect.Y + rect.Height / 2), new Vector2(infoRect.X, infoRect.Y + infoRect.Height / 2), Color.White);
504  }
505  else
506  {
507  infoRect = new Rectangle(pos.ToPoint(), size.ToPoint());
508  infoRect.Inflate(8, 8);
509 
510  Rectangle barRect = new Rectangle(infoRect.Left, infoRect.Top - 32, infoRect.Width, 32);
511  const string titleHeader = "Pinned event";
512 
513  GUI.DrawRectangle(spriteBatch, barRect, Color.DarkGray * 0.8f, isFilled: true);
514  GUI.DrawString(spriteBatch, barRect.Location.ToVector2() + barRect.Size.ToVector2() / 2 - GUIStyle.SubHeadingFont.MeasureString(titleHeader) / 2, titleHeader, Color.White);
515  GUI.DrawRectangle(spriteBatch, barRect, Color.White);
516  infoBarRect = barRect;
517  }
518 
519  if (drawPoints != null && drawPoints.Any() && Screen.Selected?.Cam != null)
520  {
521  foreach (DebugLine line in drawPoints)
522  {
523  if (line.Position != Vector2.Zero)
524  {
525  float xPos = infoRect.Right;
526 
527  if (parentRect == null && pinnedPosition.X + infoRect.Width / 2.0f > GameMain.GraphicsWidth / 2.0f)
528  {
529  xPos = infoRect.Left;
530  }
531 
532  GUI.DrawLine(spriteBatch, new Vector2(xPos, infoRect.Top + infoRect.Height / 2), Screen.Selected.Cam.WorldToScreen(line.Position), line.Color);
533  }
534  }
535  }
536 
537  GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
538  GUI.DrawRectangle(spriteBatch, infoRect, Color.White);
539 
540  if (richTextData.HasValue && richTextData.Value.Length > 0)
541  {
542  GUI.DrawStringWithColors(spriteBatch, pos, text, Color.White, richTextData.Value, null, 0, GUIStyle.SmallFont);
543  }
544  else
545  {
546  GUI.DrawString(spriteBatch, pos, text, Color.White, null, 0, GUIStyle.SmallFont);
547  }
548  return infoBarRect ?? infoRect;
549  }
550 
551  public void ClientRead(IReadMessage msg)
552  {
553  NetworkEventType eventType = (NetworkEventType)msg.ReadByte();
554  switch (eventType)
555  {
556  case NetworkEventType.STATUSEFFECT:
557  Identifier eventIdentifier = msg.ReadIdentifier();
558  UInt16 actionIndex = msg.ReadUInt16();
559  UInt16 targetCount = msg.ReadUInt16();
560  List<Entity> targets = new List<Entity>();
561  for (int i = 0; i < targetCount; i++)
562  {
563  UInt16 targetID = msg.ReadUInt16();
564  Entity target = Entity.FindEntityByID(targetID);
565  if (target != null) { targets.Add(target); }
566  }
567 
568  var eventPrefab = EventSet.GetEventPrefab(eventIdentifier);
569  if (eventPrefab == null) { return; }
570  int j = 0;
571  foreach (var element in eventPrefab.ConfigElement.Descendants())
572  {
573  if (j != actionIndex)
574  {
575  j++;
576  continue;
577  }
578  foreach (var subElement in element.Elements())
579  {
580  if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
581  StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})");
582  foreach (Entity target in targets)
583  {
584  if (target is Item item)
585  {
586  effect.Apply(effect.type, 1.0f, item, item.AllPropertyObjects);
587  }
588  else
589  {
590  effect.Apply(effect.type, 1.0f, target, target as ISerializableEntity);
591  }
592  }
593  }
594  break;
595  }
596  break;
597  case NetworkEventType.CONVERSATION:
598  {
599  UInt16 identifier = msg.ReadUInt16();
600  string eventSprite = msg.ReadString();
601  byte dialogType = msg.ReadByte();
602  bool continueConversation = msg.ReadBoolean();
603  UInt16 speakerId = msg.ReadUInt16();
604  string text = msg.ReadString();
605  bool fadeToBlack = msg.ReadBoolean();
606  byte optionCount = msg.ReadByte();
607  List<string> options = new List<string>();
608  for (int i = 0; i < optionCount; i++)
609  {
610  options.Add(msg.ReadString());
611  }
612 
613  byte endCount = msg.ReadByte();
614  int[] endings = new int[endCount];
615  for (int i = 0; i < endCount; i++)
616  {
617  endings[i] = msg.ReadByte();
618  }
619 
620  if (string.IsNullOrEmpty(text) && optionCount == 0)
621  {
622  GUIMessageBox.MessageBoxes.ForEachMod(mb =>
623  {
624  if (mb.UserData is Pair<string, UInt16> pair && pair.First == "ConversationAction" && pair.Second == identifier)
625  {
626  (mb as GUIMessageBox)?.Close();
627  }
628  });
629  }
630  else
631  {
632  ConversationAction.CreateDialog(text, Entity.FindEntityByID(speakerId) as Character, options, endings, eventSprite, identifier, fadeToBlack, (ConversationAction.DialogTypes)dialogType, continueConversation);
633  }
634  if (Entity.FindEntityByID(speakerId) is Character speaker)
635  {
636  speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
637  speaker.SetCustomInteract(null, null);
638  }
639  break;
640  }
641  case NetworkEventType.CONVERSATION_SELECTED_OPTION:
642  {
643  UInt16 identifier = msg.ReadUInt16();
644  int selectedOption = msg.ReadByte() - 1;
645  ConversationAction.SelectOption(identifier, selectedOption);
646  break;
647  }
648  case NetworkEventType.MISSION:
649  Identifier missionIdentifier = msg.ReadIdentifier();
650  int locationIndex = msg.ReadInt32();
651  int destinationIndex = msg.ReadInt32();
652  string missionName = msg.ReadString();
654  {
655  MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
656  if (prefab != null)
657  {
658  new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
659  Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
660  {
661  IconColor = prefab.IconColor
662  };
663  if (GameMain.GameSession?.Map is { } map && locationIndex >= 0 && locationIndex < map.Locations.Count)
664  {
665  Location location = map.Locations[locationIndex];
666  map.Discover(location, checkTalents: false);
667 
668  LocationConnection? connection = null;
669  if (destinationIndex != locationIndex && destinationIndex >= 0 && destinationIndex < map.Locations.Count)
670  {
671  Location destination = map.Locations[destinationIndex];
672  connection = map.Connections.FirstOrDefault(c => c.Locations.Contains(location) && c.Locations.Contains(destination));
673  }
674  if (connection != null)
675  {
676  location.UnlockMission(prefab, connection);
677  }
678  else
679  {
680  location.UnlockMission(prefab);
681  }
682  }
683  }
684  }
685  break;
686  case NetworkEventType.UNLOCKPATH:
687  UInt16 connectionIndex = msg.ReadUInt16();
688  if (GameMain.GameSession?.Map?.Connections != null)
689  {
690  if (connectionIndex >= GameMain.GameSession.Map.Connections.Count)
691  {
692  DebugConsole.ThrowError($"Failed to unlock a path on the campaign map. Connection index out of bounds (index: {connectionIndex}, number of connections: {GameMain.GameSession.Map.Connections.Count})");
693  }
694  else
695  {
696  GameMain.GameSession.Map.Connections[connectionIndex].Locked = false;
697  new GUIMessageBox(string.Empty, TextManager.Get("pathunlockedgeneric"),
698  Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, iconStyle: "UnlockPathIcon", relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128));
699  }
700  }
701  break;
702  case NetworkEventType.EVENTLOG:
703  ClientReadEventLog(GameMain.Client, msg);
704  break;
705  case NetworkEventType.EVENTOBJECTIVE:
706  ClientReadEventObjective(GameMain.Client, msg);
707  break;
708  }
709  }
710 
711  private void ClientReadEventLog(GameClient client, IReadMessage msg)
712  {
713  NetEventLogEntry entry = INetSerializableStruct.Read<NetEventLogEntry>(msg);
714  EventLog.AddEntry(entry.EventPrefabId, entry.LogEntryId, entry.Text.Replace("\\n", "\n"));
715  }
716  private static void ClientReadEventObjective(GameClient client, IReadMessage msg)
717  {
718  NetEventObjective entry = INetSerializableStruct.Read<NetEventObjective>(msg);
720  entry.Type,
721  entry.Identifier,
722  entry.ObjectiveTag,
723  entry.ParentObjectiveId,
724  entry.TextTag,
725  entry.CanBeCompleted);
726  }
727  }
728 }
Vector2 WorldToScreen(Vector2 coords)
Definition: Camera.cs:416
float? Zoom
Definition: Camera.cs:78
Triggers a "conversation popup" with text and support for different branching options.
static void CreateDialog(LocalizedString text, Character speaker, IEnumerable< string > options, int[] closingOptions, string eventSprite, UInt16 actionId, bool fadeToBlack, DialogTypes dialogType, bool continueConversation=false)
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
override string ToString()
Definition: Event.cs:29
bool IsFinished
Definition: Event.cs:25
virtual Vector2 DebugDrawPos
Definition: Event.cs:35
EventPrefab Prefab
Definition: Event.cs:16
Used to store logs of scripted events (a sort of "quest log")
void DebugDrawHUD(SpriteBatch spriteBatch, float y)
Event sets are sets of random events that occur within a level (most commonly, monster spawns and scr...
Definition: EventSet.cs:31
static EventPrefab GetEventPrefab(Identifier identifier)
Definition: EventSet.cs:80
readonly bool PerWreck
The set is applied once per each wreck in the level. Can be used to ensure there's a consistent amoun...
Definition: EventSet.cs:187
readonly bool PerCave
The set is applied once per each cave in the level. Can be used to ensure there's a consistent amount...
Definition: EventSet.cs:182
readonly bool PerRuin
The set is applied once per each ruin in the level. Can be used to ensure there's a consistent amount...
Definition: EventSet.cs:177
readonly float MinIntensity
Definition: EventSet.cs:152
readonly float MinMissionTime
The event set won't become active until the round has lasted at least this many seconds.
Definition: EventSet.cs:149
readonly float MinDistanceTraveled
The event set won't become active until the submarine has travelled at least this far....
Definition: EventSet.cs:144
static readonly List< GUIComponent > MessageBoxes
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetLobbyScreen NetLobbyScreen
Definition: GameMain.cs:55
static int GraphicsHeight
Definition: GameMain.cs:168
static GameClient Client
Definition: GameMain.cs:188
void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
Definition: Location.cs:828
List< LocationConnection > Connections
static readonly PrefabCollection< MissionPrefab > Prefabs
static bool KeyDown(InputType inputType)
readonly Identifier Identifier
Definition: Prefab.cs:34
Dictionary< Identifier, List< Entity > > Targets
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static StatusEffect Load(ContentXElement element, string parentDebugName)
virtual void Apply(ActionType type, float deltaTime, Entity entity, ISerializableEntity target, Vector2? worldPosition=null)
static void Trigger(SegmentActionType Type, Identifier Identifier, Identifier ObjectiveTag, Identifier ParentObjectiveId, Identifier TextTag, bool CanBeCompleted, bool autoPlayVideo=false, string videoFile="", int width=450, int height=80)
CursorState
Definition: GUI.cs:40