Skip to content

Commit 52cd05d

Browse files
authored
Fix beacons not reappearing after reconnecting (#2227)
2 parents 3b13f1a + 7d47cc3 commit 52cd05d

File tree

8 files changed

+103
-58
lines changed

8 files changed

+103
-58
lines changed

Nitrox.Test/Server/Serialization/WorldPersistenceTest.cs

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ private static void StoryGoalTest(StoryGoalData storyGoal, StoryGoalData storyGo
8181
{
8282
Assert.IsTrue(storyGoal.CompletedGoals.SequenceEqual(storyGoalAfter.CompletedGoals));
8383
Assert.IsTrue(storyGoal.RadioQueue.SequenceEqual(storyGoalAfter.RadioQueue));
84-
Assert.IsTrue(storyGoal.GoalUnlocks.SequenceEqual(storyGoalAfter.GoalUnlocks));
8584
AssertHelper.IsListEqual(storyGoal.ScheduledGoals.OrderBy(x => x.GoalKey), storyGoalAfter.ScheduledGoals.OrderBy(x => x.GoalKey), (scheduledGoal, scheduledGoalAfter) =>
8685
{
8786
Assert.AreEqual(scheduledGoal.TimeExecute, scheduledGoalAfter.TimeExecute);

NitroxClient/GameLogic/InitialSync/StoryGoalInitialSyncProcessor.cs

+48-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Generic;
43
using System.Linq;
54
using NitroxClient.GameLogic.InitialSync.Abstract;
5+
using NitroxClient.MonoBehaviours;
66
using NitroxModel.DataStructures.GameLogic;
77
using NitroxModel.Packets;
88
using Story;
@@ -22,10 +22,9 @@ public StoryGoalInitialSyncProcessor(TimeManager timeManager)
2222
AddStep(SetupTrackers);
2323
AddStep(SetupAuroraAndSunbeam);
2424
AddStep(SetScheduledGoals);
25-
AddStep(RefreshStoryWithLatestData);
2625
}
2726

28-
private static IEnumerator SetupStoryGoalManager(InitialPlayerSync packet)
27+
private static void SetupStoryGoalManager(InitialPlayerSync packet)
2928
{
3029
List<string> completedGoals = packet.StoryGoalData.CompletedGoals;
3130
List<string> radioQueue = packet.StoryGoalData.RadioQueue;
@@ -61,20 +60,23 @@ private static IEnumerator SetupStoryGoalManager(InitialPlayerSync packet)
6160
- Personal goals : {personalGoals.Count}
6261
- Radio queue : {radioQueue.Count}
6362
""");
64-
yield break;
6563
}
6664

67-
private static IEnumerator SetupTrackers(InitialPlayerSync packet)
65+
private static void SetupTrackers(InitialPlayerSync packet)
6866
{
6967
List<string> completedGoals = packet.StoryGoalData.CompletedGoals;
7068
StoryGoalManager storyGoalManager = StoryGoalManager.main;
71-
72-
// Initialize CompoundGoalTracker and OnGoalUnlockTracker and clear their already completed goals
69+
OnGoalUnlockTracker onGoalUnlockTracker = storyGoalManager.onGoalUnlockTracker;
70+
CompoundGoalTracker compoundGoalTracker = storyGoalManager.compoundGoalTracker;
71+
72+
// Initializing CompoundGoalTracker and OnGoalUnlockTracker again (with OnSceneObjectsLoaded) requires us to
73+
// we first clear what was done in the first iteration of OnSceneObjectsLoaded
74+
onGoalUnlockTracker.goalUnlocks.Clear();
75+
compoundGoalTracker.goals.Clear();
76+
// we force initialized to false so OnSceneObjectsLoaded actually does something
77+
storyGoalManager.initialized = false;
7378
storyGoalManager.OnSceneObjectsLoaded();
74-
75-
storyGoalManager.compoundGoalTracker.goals.RemoveAll(goal => completedGoals.Contains(goal.key));
76-
completedGoals.ForEach(goal => storyGoalManager.onGoalUnlockTracker.goalUnlocks.Remove(goal));
77-
79+
7880
// Clean LocationGoalTracker, BiomeGoalTracker and ItemGoalTracker already completed goals
7981
storyGoalManager.locationGoalTracker.goals.RemoveAll(goal => completedGoals.Contains(goal.key));
8082
storyGoalManager.biomeGoalTracker.goals.RemoveAll(goal => completedGoals.Contains(goal.key));
@@ -90,15 +92,39 @@ private static IEnumerator SetupTrackers(InitialPlayerSync packet)
9092
}
9193
}
9294
techTypesToRemove.ForEach(techType => storyGoalManager.itemGoalTracker.goals.Remove(techType));
93-
yield break;
95+
96+
// OnGoalUnlock might trigger the creation of a signal which is later on set to invisible when getting close to it
97+
// the invisibility is managed by PingInstance_Set_Patches and is restored during PlayerPreferencesInitialSyncProcessor
98+
// So we still need to recreate the signals at every game launch
99+
100+
// To avoid having the SignalPing play its sound we just make its notification null while triggering it
101+
// (the sound is something like "coordinates added to the gps" or something)
102+
SignalPing prefabSignalPing = onGoalUnlockTracker.signalPrefab.GetComponent<SignalPing>();
103+
PDANotification pdaNotification = prefabSignalPing.vo;
104+
prefabSignalPing.vo = null;
105+
106+
foreach (OnGoalUnlock onGoalUnlock in onGoalUnlockTracker.unlockData.onGoalUnlocks)
107+
{
108+
if (completedGoals.Contains(onGoalUnlock.goal))
109+
{
110+
// Code adapted from OnGoalUnlock.Trigger
111+
foreach (UnlockSignalData unlockSignalData in onGoalUnlock.signals)
112+
{
113+
unlockSignalData.Trigger(onGoalUnlockTracker);
114+
}
115+
}
116+
}
117+
118+
// recover the notification sound
119+
prefabSignalPing.vo = pdaNotification;
94120
}
95121

96122
// Must happen after CompletedGoals
97-
private static IEnumerator SetupAuroraAndSunbeam(InitialPlayerSync packet)
123+
private static void SetupAuroraAndSunbeam(InitialPlayerSync packet)
98124
{
99125
TimeData timeData = packet.TimeData;
100126

101-
AuroraWarnings auroraWarnings = UnityEngine.Object.FindObjectOfType<AuroraWarnings>();
127+
AuroraWarnings auroraWarnings = Player.mainObject.GetComponentInChildren<AuroraWarnings>(true);
102128
auroraWarnings.timeSerialized = DayNightCycle.main.timePassedAsFloat;
103129
auroraWarnings.OnProtoDeserialize(null);
104130

@@ -115,15 +141,17 @@ private static IEnumerator SetupAuroraAndSunbeam(InitialPlayerSync packet)
115141
StoryGoalCustomEventHandler.main.countdownStartingTime = sunbeamCountdownGoal.TimeExecute - 2370;
116142
// See StoryGoalCustomEventHandler.endTime for calculation (endTime - 30 seconds)
117143
}
118-
119-
yield break;
120144
}
121145

122146
// Must happen after CompletedGoals
123-
private static IEnumerator SetScheduledGoals(InitialPlayerSync packet)
147+
private static void SetScheduledGoals(InitialPlayerSync packet)
124148
{
125149
List<NitroxScheduledGoal> scheduledGoals = packet.StoryGoalData.ScheduledGoals;
126150

151+
// We don't want any scheduled goal we add now to be executed before initial sync has finished, else they might not get broadcasted
152+
StoryGoalScheduler.main.paused = true;
153+
Multiplayer.OnLoadingComplete += () => StoryGoalScheduler.main.paused = false;
154+
127155
foreach (NitroxScheduledGoal scheduledGoal in scheduledGoals)
128156
{
129157
// Clear duplicated goals that might have appeared during loading and before sync
@@ -135,17 +163,17 @@ private static IEnumerator SetScheduledGoals(InitialPlayerSync packet)
135163
goalType = (Story.GoalType)scheduledGoal.GoalType,
136164
timeExecute = scheduledGoal.TimeExecute,
137165
};
138-
if (goal.timeExecute >= DayNightCycle.main.timePassedAsDouble && !StoryGoalManager.main.completedGoals.Contains(goal.goalKey))
166+
if (!StoryGoalManager.main.completedGoals.Contains(goal.goalKey))
139167
{
140168
StoryGoalScheduler.main.schedule.Add(goal);
141169
}
142170
}
143171

144-
yield break;
172+
RefreshStoryWithLatestData();
145173
}
146174

147175
// Must happen after CompletedGoals
148-
private static IEnumerator RefreshStoryWithLatestData()
176+
private static void RefreshStoryWithLatestData()
149177
{
150178
// If those aren't set up yet, they'll initialize correctly in time
151179
// Else, we need to force them to acquire the right data
@@ -157,7 +185,6 @@ private static IEnumerator RefreshStoryWithLatestData()
157185
{
158186
PrecursorGunStoryEvents.main.Start();
159187
}
160-
yield break;
161188
}
162189

163190
private void SetTimeData(InitialPlayerSync packet)

NitroxModel/DataStructures/GameLogic/InitialStoryGoalData.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ public class InitialStoryGoalData
99
{
1010
public List<string> CompletedGoals { get; set; }
1111
public List<string> RadioQueue { get; set; }
12-
public List<string> GoalUnlocks { get; set; }
1312
public List<NitroxScheduledGoal> ScheduledGoals { get; set; }
1413

1514
/// <remarks>
@@ -24,11 +23,10 @@ protected InitialStoryGoalData()
2423
// Constructor for serialization. Has to be "protected" for json serialization.
2524
}
2625

27-
public InitialStoryGoalData(List<string> completedGoals, List<string> radioQueue, List<string> goalUnlocks, List<NitroxScheduledGoal> scheduledGoals, Dictionary<string, float> personalCompletedGoalsWithTimestamp)
26+
public InitialStoryGoalData(List<string> completedGoals, List<string> radioQueue, List<NitroxScheduledGoal> scheduledGoals, Dictionary<string, float> personalCompletedGoalsWithTimestamp)
2827
{
2928
CompletedGoals = completedGoals;
3029
RadioQueue = radioQueue;
31-
GoalUnlocks = goalUnlocks;
3230
ScheduledGoals = scheduledGoals;
3331
PersonalCompletedGoalsWithTimestamp = personalCompletedGoalsWithTimestamp;
3432
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Reflection;
2+
using NitroxClient.MonoBehaviours;
3+
using NitroxModel.Helper;
4+
5+
namespace NitroxPatcher.Patches.Dynamic;
6+
7+
/// <remarks>
8+
/// Prevents <see cref="CrashedShipExploder.Update"/> from occurring before initial sync has completed.
9+
/// It lets us avoid a very weird edge case in which SetExplodeTime happens before server time is set on the client,
10+
/// after what some event in this Update method might be triggered because there's a dead frame before the StoryGoalInitialSyncProcessor step
11+
/// which sets up all the aurora story-related stuff locally.
12+
/// </remarks>
13+
public sealed partial class CrashedShipExploder_Update_Patch : NitroxPatch, IDynamicPatch
14+
{
15+
private static readonly MethodInfo TARGET_METHOD = Reflect.Method((CrashedShipExploder t) => t.Update());
16+
17+
public static bool Prefix()
18+
{
19+
return Multiplayer.Main && Multiplayer.Main.InitialSyncCompleted;
20+
}
21+
}

NitroxPatcher/Patches/Dynamic/StoryGoalScheduler_Schedule_Patch.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public sealed partial class StoryGoalScheduler_Schedule_Patch : NitroxPatch, IDy
1616
public static bool Prefix(StoryGoal goal, out bool __state)
1717
{
1818
__state = StoryGoalScheduler.main.schedule.Any(scheduledGoal => scheduledGoal.goalKey == goal.key) ||
19-
(goal.goalType == Story.GoalType.Radio && StoryGoalManager.main.pendingRadioMessages.Contains(goal.key));
19+
(goal.goalType == Story.GoalType.Radio && StoryGoalManager.main.pendingRadioMessages.Contains(goal.key)) ||
20+
StoryGoalManager.main.completedGoals.Contains(goal.key);
2021

2122
if (__state)
2223
{

NitroxServer/Communication/Packets/Processors/StoryGoalExecutedProcessor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public override void Process(StoryGoalExecuted packet, Player player)
3030
case StoryGoalExecuted.EventType.RADIO:
3131
if (added)
3232
{
33-
storyGoalData.RadioQueue.Add(packet.Key);
33+
storyGoalData.RadioQueue.Enqueue(packet.Key);
3434
}
3535
break;
3636
case StoryGoalExecuted.EventType.PDA:

NitroxServer/GameLogic/Unlockables/StoryGoalData.cs

+30-30
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,43 @@
33
using NitroxModel.DataStructures;
44
using NitroxModel.DataStructures.GameLogic;
55

6-
namespace NitroxServer.GameLogic.Unlockables
7-
{
8-
[DataContract]
9-
public class StoryGoalData
10-
{
11-
[DataMember(Order = 1)]
12-
public ThreadSafeSet<string> CompletedGoals { get; } = new();
6+
namespace NitroxServer.GameLogic.Unlockables;
137

14-
[DataMember(Order = 2)]
15-
public ThreadSafeList<string> RadioQueue { get; } = new();
8+
[DataContract]
9+
public class StoryGoalData
10+
{
11+
[DataMember(Order = 1)]
12+
public ThreadSafeSet<string> CompletedGoals { get; } = [];
1613

17-
[DataMember(Order = 3)]
18-
public ThreadSafeSet<string> GoalUnlocks { get; } = new();
14+
[DataMember(Order = 2)]
15+
public ThreadSafeQueue<string> RadioQueue { get; } = [];
1916

20-
[DataMember(Order = 4)]
21-
public ThreadSafeList<NitroxScheduledGoal> ScheduledGoals { get; set; } = new();
17+
[DataMember(Order = 3)]
18+
public ThreadSafeList<NitroxScheduledGoal> ScheduledGoals { get; set; } = [];
2219

23-
public bool RemovedLatestRadioMessage()
20+
public bool RemovedLatestRadioMessage()
21+
{
22+
if (RadioQueue.Count <= 0)
2423
{
25-
if (RadioQueue.Count <= 0)
26-
{
27-
return false;
28-
}
29-
30-
RadioQueue.RemoveAt(0);
31-
return true;
24+
return false;
3225
}
3326

34-
public static StoryGoalData From(StoryGoalData storyGoals, ScheduleKeeper scheduleKeeper)
35-
{
36-
storyGoals.ScheduledGoals = new ThreadSafeList<NitroxScheduledGoal>(scheduleKeeper.GetScheduledGoals());
37-
return storyGoals;
38-
}
27+
string message = RadioQueue.Dequeue();
3928

40-
public InitialStoryGoalData GetInitialStoryGoalData(ScheduleKeeper scheduleKeeper, Player player)
41-
{
42-
return new InitialStoryGoalData(new List<string>(CompletedGoals), new List<string>(RadioQueue), new List<string>(GoalUnlocks), scheduleKeeper.GetScheduledGoals(), new(player.PersonalCompletedGoalsWithTimestamp));
43-
}
29+
// Just like StoryGoalManager.ExecutePendingRadioMessage
30+
CompletedGoals.Add($"OnPlay{message}");
31+
32+
return true;
33+
}
34+
35+
public static StoryGoalData From(StoryGoalData storyGoals, ScheduleKeeper scheduleKeeper)
36+
{
37+
storyGoals.ScheduledGoals = new ThreadSafeList<NitroxScheduledGoal>(scheduleKeeper.GetScheduledGoals());
38+
return storyGoals;
39+
}
40+
41+
public InitialStoryGoalData GetInitialStoryGoalData(ScheduleKeeper scheduleKeeper, Player player)
42+
{
43+
return new InitialStoryGoalData(new List<string>(CompletedGoals), new List<string>(RadioQueue), scheduleKeeper.GetScheduledGoals(), new(player.PersonalCompletedGoalsWithTimestamp));
4444
}
4545
}

NitroxServer/Server.cs

-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ public string GetSaveSummary(Perms viewerPerms = Perms.CONSOLE)
7272
- Story goals completed: {world.GameData.StoryGoals.CompletedGoals.Count}
7373
- Radio messages stored: {world.GameData.StoryGoals.RadioQueue.Count}
7474
- World gamemode: {serverConfig.GameMode}
75-
- Story goals unlocked: {world.GameData.StoryGoals.GoalUnlocks.Count}
7675
- Encyclopedia entries: {world.GameData.PDAState.EncyclopediaEntries.Count}
7776
- Known tech: {world.GameData.PDAState.KnownTechTypes.Count}
7877
""");

0 commit comments

Comments
 (0)