Skip to content

Commit

Permalink
Update 0.8 (#387)
Browse files Browse the repository at this point in the history
- Protocol 32
- New project for the dedicated server
- Command scheduling adjustments
- Timestamp fixer for async time
- ThingFilterContext API
- ISyncSimple API
- Use a custom assembly loader
- Add some unit tests
- Replay saving in dev mode
- Refactor Sync, hosting logic
- Experiment with async/await for joining state
- Split WorldTimeComp off
- Enable nullable references in Common
- Remove Sync MultiTarget methods and fields
- Awaitable LongEvent tasks
- Use GUILayout layouting for SaveGameWindow
- Tweak tick speed calculations
- Use System.IO.Compression instead of DotNetZip.Reduced
- Show average map TPS in ticks-behind square
- Show player's average frame time in chat
- Warn on decompression errors
- Don't show cursors of players from other factions
- Add "set faction" debug tool
- Server now runs at 30 tps (from 60 tps)
- Update Languages

- Fix replay watching, fix speed controls when watching replays
- Fix player list breaking when players rejoin
- Fix no desync traces on maps with non-zero id
- Fix handling of added configs by "Fix and restart"
  • Loading branch information
Zetrith authored Jun 17, 2023
1 parent 4a46899 commit d7aa398
Show file tree
Hide file tree
Showing 194 changed files with 5,861 additions and 3,671 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- run: mkdir -p output/Multiplayer

- name: Move files
run: mv About/ Assemblies/ Defs/ Languages/ Textures/ output/Multiplayer
run: mv About/ Assemblies/ AssembliesCustom/ Defs/ Languages/ Textures/ output/Multiplayer

- name: Upload Mod Artifacts
uses: actions/upload-artifact@v2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ __pycache__/
*.pyc

/Assemblies
/AssembliesCustom
/Source/Publicise-1.0.0.zip
/Source/publicise_hash.txt
/Source/Publicise.exe
Expand Down
Binary file removed Assemblies/1I18N.dll
Binary file not shown.
Binary file removed Assemblies/2I18N.West.dll
Binary file not shown.
2 changes: 1 addition & 1 deletion Languages
25 changes: 25 additions & 0 deletions Source/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
8 changes: 0 additions & 8 deletions Source/API/AdhocAPI.cs

This file was deleted.

33 changes: 12 additions & 21 deletions Source/Client/AsyncTime/AsyncTimeComp.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
extern alias zip;

using HarmonyLib;
using Multiplayer.Common;
using RimWorld;
Expand All @@ -15,7 +13,6 @@

namespace Multiplayer.Client
{

public class AsyncTimeComp : IExposable, ITickable
{
public static Map tickingMap;
Expand Down Expand Up @@ -59,15 +56,16 @@ public float TickRateMultiplier(TimeSpeed speed)
}
}

public TimeSpeed TimeSpeed
public TimeSpeed DesiredTimeSpeed => timeSpeedInt;

public void SetDesiredTimeSpeed(TimeSpeed speed)
{
get => timeSpeedInt;
set => timeSpeedInt = value;
timeSpeedInt = speed;
}

public bool Paused => this.ActualRateMultiplier(TimeSpeed) == 0f;
public bool Paused => this.ActualRateMultiplier(DesiredTimeSpeed) == 0f;

public float RealTimeToTickThrough { get; set; }
public float TimeToTickThrough { get; set; }

public Queue<ScheduledCommand> Cmds { get => cmds; }

Expand Down Expand Up @@ -159,7 +157,7 @@ public void TickMapTrading()

// These are normally called in Map.MapUpdate() and react to changes in the game state even when the game is paused (not ticking)
// Update() methods are not deterministic, but in multiplayer all game state changes (which don't happen during ticking) happen in commands
// Thus these methods can be moved to Tick() and ExecuteCmd()
// Thus these methods can be moved to Tick() and ExecuteCmd() by way of this method
public void UpdateManagers()
{
map.regionGrid.UpdateClean();
Expand Down Expand Up @@ -226,7 +224,10 @@ public void ExposeData()

public void FinalizeInit()
{
cmds = new Queue<ScheduledCommand>(Multiplayer.session.dataSnapshot.mapCmds.GetValueSafe(map.uniqueID) ?? new List<ScheduledCommand>());
cmds = new Queue<ScheduledCommand>(
Multiplayer.session.dataSnapshot?.MapCmds.GetValueSafe(map.uniqueID) ?? new List<ScheduledCommand>()
);

Log.Message($"Init map with cmds {cmds.Count}");
}

Expand All @@ -241,7 +242,6 @@ public void ExecuteCmd(ScheduledCommand cmd)

MpContext context = data.MpContext();

var updateWorldTime = false;
keepTheMap = false;
var prevMap = Current.Game.CurrentMap;
Current.Game.currentMapIndex = (sbyte)map.Index;
Expand Down Expand Up @@ -284,8 +284,7 @@ public void ExecuteCmd(ScheduledCommand cmd)
if (cmdType == CommandType.MapTimeSpeed && Multiplayer.GameComp.asyncTime)
{
TimeSpeed speed = (TimeSpeed)data.ReadByte();
TimeSpeed = speed;
updateWorldTime = true;
SetDesiredTimeSpeed(speed);

MpLog.Debug("Set map time speed " + speed);
}
Expand Down Expand Up @@ -334,8 +333,6 @@ public void ExecuteCmd(ScheduledCommand cmd)
if (!keepTheMap)
TrySetCurrentMap(prevMap);

Multiplayer.WorldComp.UpdateTimeSpeed(); // In case a letter pauses the map

keepTheMap = false;

Multiplayer.game.sync.TryAddCommandRandomState(randState);
Expand Down Expand Up @@ -479,12 +476,6 @@ public void QuestManagerTickAsyncTime()

MultiplayerAsyncQuest.TickMapQuests(this);
}

public void TrySetPrevTimeSpeed(TimeSpeed speed)
{
if (prevTime != null)
prevTime = prevTime.Value with { speed = speed };
}
}

public enum DesignatorMode : byte
Expand Down
137 changes: 7 additions & 130 deletions Source/Client/AsyncTime/AsyncTimePatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ static IEnumerable<MethodBase> TargetMethods()
}

[HarmonyPatch(typeof(Autosaver), nameof(Autosaver.AutosaverTick))]

static class DisableAutosaver
{
static bool Prefix() => Multiplayer.Client == null;
Expand Down Expand Up @@ -135,7 +134,7 @@ static void Postfix(ref float __result)

var map = PreDrawCalcMarker.calculating.Map ?? Find.CurrentMap;
var asyncTime = map.AsyncTime();
var timeSpeed = Multiplayer.IsReplay ? TickPatch.replayTimeSpeed : asyncTime.TimeSpeed;
var timeSpeed = Multiplayer.IsReplay ? TickPatch.replayTimeSpeed : asyncTime.DesiredTimeSpeed;

__result = TickPatch.Simulating ? 6 : asyncTime.ActualRateMultiplier(timeSpeed);
}
Expand All @@ -150,78 +149,12 @@ static void Postfix(ref bool __result)
if (WorldRendererUtility.WorldRenderedNow) return;

var asyncTime = Find.CurrentMap.AsyncTime();
var timeSpeed = Multiplayer.IsReplay ? TickPatch.replayTimeSpeed : asyncTime.TimeSpeed;
var timeSpeed = Multiplayer.IsReplay ? TickPatch.replayTimeSpeed : asyncTime.DesiredTimeSpeed;

__result = asyncTime.ActualRateMultiplier(timeSpeed) == 0;
}
}

[HarmonyPatch]
public class StorytellerTickPatch
{
public static bool updating;

static IEnumerable<MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(Storyteller), nameof(Storyteller.StorytellerTick));
yield return AccessTools.Method(typeof(StoryWatcher), nameof(StoryWatcher.StoryWatcherTick));
}

static bool Prefix()
{
updating = true;
return Multiplayer.Client == null || Multiplayer.Ticking;
}

static void Postfix() => updating = false;
}

[HarmonyPatch(typeof(Storyteller))]
[HarmonyPatch(nameof(Storyteller.AllIncidentTargets), MethodType.Getter)]
public class StorytellerTargetsPatch
{
static void Postfix(List<IIncidentTarget> __result)
{
if (Multiplayer.Client == null) return;

if (Multiplayer.MapContext != null)
{
__result.Clear();
__result.Add(Multiplayer.MapContext);
}
else if (MultiplayerWorldComp.tickingWorld)
{
__result.Clear();

foreach (var caravan in Find.WorldObjects.Caravans)
if (caravan.IsPlayerControlled)
__result.Add(caravan);

__result.Add(Find.World);
}
}
}

// The MP Mod's ticker calls Storyteller.StorytellerTick() on both the World and each Map, each tick
// This patch aims to ensure each "spawn raid" Quest is only triggered once, to prevent 2x or 3x sized raids
[HarmonyPatch(typeof(Quest), nameof(Quest.PartsListForReading), MethodType.Getter)]
public class QuestPartsListForReadingPatch
{
static void Postfix(ref List<QuestPart> __result)
{
if (StorytellerTickPatch.updating)
{
__result = __result.Where(questPart => {
if (questPart is QuestPart_ThreatsGenerator questPartThreatsGenerator)
{
return questPartThreatsGenerator.mapParent?.Map == Multiplayer.MapContext;
}
return true;
}).ToList();
}
}
}

[HarmonyPatch(typeof(TickManager), nameof(TickManager.Notify_GeneratedPotentiallyHostileMap))]
static class GeneratedHostileMapPatch
{
Expand All @@ -236,56 +169,6 @@ static void Postfix()
}
}

[HarmonyPatch(typeof(StorytellerUtility), nameof(StorytellerUtility.DefaultParmsNow))]
static class MapContextIncidentParms
{
static void Prefix(IIncidentTarget target, ref Map __state)
{
// This may be running inside a context already
if (AsyncTimeComp.tickingMap != null)
return;

if (MultiplayerWorldComp.tickingWorld && target is Map map)
{
AsyncTimeComp.tickingMap = map;
map.AsyncTime().PreContext();
__state = map;
}
}

static void Postfix(Map __state)
{
if (__state != null)
{
__state.AsyncTime().PostContext();
AsyncTimeComp.tickingMap = null;
}
}
}

[HarmonyPatch(typeof(IncidentWorker), nameof(IncidentWorker.TryExecute))]
static class MapContextIncidentExecute
{
static void Prefix(IncidentParms parms, ref Map __state)
{
if (MultiplayerWorldComp.tickingWorld && parms.target is Map map)
{
AsyncTimeComp.tickingMap = map;
map.AsyncTime().PreContext();
__state = map;
}
}

static void Postfix(Map __state)
{
if (__state != null)
{
__state.AsyncTime().PostContext();
AsyncTimeComp.tickingMap = null;
}
}
}

[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string))]
static class ReceiveLetterPause
{
Expand All @@ -302,14 +185,14 @@ static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> inst
}
}

static AutomaticPauseMode AutomaticPauseMode()
private static AutomaticPauseMode AutomaticPauseMode()
{
return Multiplayer.Client != null
? (AutomaticPauseMode) Multiplayer.GameComp.pauseOnLetter
: Prefs.AutomaticPauseMode;
}

static void PauseOnLetter(TickManager manager)
private static void PauseOnLetter(TickManager manager)
{
if (Multiplayer.Client == null)
{
Expand All @@ -319,21 +202,15 @@ static void PauseOnLetter(TickManager manager)

if (Multiplayer.GameComp.asyncTime)
{
var tickable = (ITickable)Multiplayer.MapContext.AsyncTime() ?? Multiplayer.WorldComp;
tickable.TimeSpeed = TimeSpeed.Paused;
var tickable = (ITickable)Multiplayer.MapContext.AsyncTime() ?? Multiplayer.AsyncWorldTime;
tickable.SetDesiredTimeSpeed(TimeSpeed.Paused);
Multiplayer.GameComp.ResetAllTimeVotes(tickable.TickableId);
if (tickable is AsyncTimeComp comp)
comp.TrySetPrevTimeSpeed(TimeSpeed.Paused);
}
else
{
Multiplayer.WorldComp.SetTimeEverywhere(TimeSpeed.Paused);
Multiplayer.AsyncWorldTime.SetTimeEverywhere(TimeSpeed.Paused);
foreach (var tickable in TickPatch.AllTickables)
{
Multiplayer.GameComp.ResetAllTimeVotes(tickable.TickableId);
if (tickable is AsyncTimeComp comp)
comp.TrySetPrevTimeSpeed(TimeSpeed.Paused);
}
}
}
}
Expand Down
Loading

0 comments on commit d7aa398

Please sign in to comment.