Compare commits

...

3 Commits

@ -1,4 +1,6 @@
# Define two contexts - one for keybindings used during the building state and one for the tool's state
CriticalPathAnalyzer.CriticalPathAnalyzerBuilding:
# This keybinding is used during the game's standard building state - need to inject into the build actions
InjectTriggersInto:
- MHG.BuildActions
Triggers:

@ -1,3 +1,4 @@
# Define all available keybindings of the mod
CriticalPathAnalyzer.OpenPathAnalyzer:
Heading: "CriticalPathAnalyzer"
DefaultBinding:

@ -26,12 +26,16 @@ namespace CriticalPathAnalyzer.Client {
KeybindingsInjector.Inject();
// Inject packet handlers
RawPacketHandlerInjector.addPacketHandler(new AnnounceModPacketHandler(Manifest.Version));
RawPacketHandlerInjector.addPacketHandler(new AnalyzePathResponseHandler());
RawPacketHandlerInjector.addPacketHandler(new AnnounceModPacketHandler(Manifest.Version));
RawPacketHandlerInjector.addPacketHandler(new AnalyzePathResponseHandler());
Logger.Info("CriticalPathAnalyzer mod loaded");
}
/// <summary>
/// Handler method for the server's analysis response.
/// Gets called from the AnalyzePathResponseHandler packet handler class
/// </summary>
public static void OnAnalyzePathResponse(AnalyzePathResponse response) {
LoggerInstance.Info($"Got response from server");
CriticalPathAnalyzerTool.HandleResponse(response);

@ -9,9 +9,17 @@ namespace CriticalPathAnalyzer.Client {
public class CriticalPathAnalyzerGameState : GameState {
public const string Id = "CriticalPathAnalyzerTool";
public override bool MouseLocked => true;
public override string TextID => Id;
/// <summary>
/// Locked mouse means you look around when you move it.
/// False would display the cursor to be able to click on UI elements.
/// </summary>
public override bool MouseLocked => true;
/// <summary>
/// This list gets printed on the game's top left UI panel which displays the currently available keybindings
/// </summary>
public override IEnumerable<InputTrigger> HelpScreenTriggers => new InputTrigger[] {
UITrigger.Back,
CriticalPathAnalyzerTrigger.AnalyzePathStart,
@ -19,10 +27,16 @@ namespace CriticalPathAnalyzer.Client {
CriticalPathAnalyzerTrigger.DisplayLoop,
};
/// <summary>
/// Called when the game transitions into this game state i.e. after pressing the configured keybinding
/// </summary>
public override void OnEnter() {
CriticalPathAnalyzerClient.LoggerInstance.Info("CPA enter");
}
/// <summary>
/// Ran every frame when inside this game state
/// </summary>
public override void OnRun() {
if (CustomInput.DownThisFrame(UITrigger.Back)) {
GameStateManager.TransitionBackToBuildingState();
@ -35,6 +49,9 @@ namespace CriticalPathAnalyzer.Client {
}
}
/// <summary>
/// Called when the game transitions away from this game state i.e. after pressing the back keybinding
/// </summary>
public override void OnExit() {
CriticalPathAnalyzerClient.LoggerInstance.Info("CPA exit");
CriticalPathAnalyzerTool.Clear();

@ -1,4 +1,5 @@
namespace CriticalPathAnalyzer.Client.Keybindings {
// Define two contexts - one for keybindings used during the building state and one for the tool's state
public enum CriticalPathAnalyzerContext {
CriticalPathAnalyzerBuilding,
CriticalPathAnalyzerTool,

@ -1,4 +1,5 @@
namespace CriticalPathAnalyzer.Client.Keybindings {
// Define all available keybindings of the mod
public enum CriticalPathAnalyzerTrigger {
None,
OpenPathAnalyzer,

@ -4,6 +4,7 @@ using LogicWorld.SharedCode.Networking;
namespace CriticalPathAnalyzer.Client.Network {
public class AnalyzePathResponseHandler : PacketHandler<AnalyzePathResponse> {
public override void Handle(AnalyzePathResponse packet, HandlerContext context) {
// Pass the packet to the client mod
CriticalPathAnalyzerClient.OnAnalyzePathResponse(packet);
}
}

@ -9,8 +9,6 @@ using LogicWorld.Outlines;
using LogicWorld.Physics;
using LogicWorld.Players;
#pragma warning disable CS8073 // The result of the expression is always the same since a value of this type is never equal to 'null'
namespace CriticalPathAnalyzer.Client.Tool {
public class CriticalPathAnalyzerTool {
private static ILogicLogger _logger;
@ -28,6 +26,9 @@ namespace CriticalPathAnalyzer.Client.Tool {
_logger = logger;
}
/// <summary>
/// Remove all highlights
/// </summary>
public static void Clear() {
PathHighlighter.RemoveHighlighting();
Outliner.RemoveOutline(_startPegAddress);
@ -36,6 +37,10 @@ namespace CriticalPathAnalyzer.Client.Tool {
_endPegAddress = PegAddress.Empty;
}
/// <summary>
/// Cast a ray from the player's camera to find a peg or a wire the player is looking at.
/// </summary>
/// <returns>The peg's address or peg address of an input in case of a wire</returns>
private static PegAddress RayCastPeg() {
// Ray-cast into the world to find what the player is looking at
HitInfo hitInfo = PlayerCaster.CameraCast(Masks.Environment | Masks.Structure | Masks.Peg | Masks.Wire);
@ -60,6 +65,9 @@ namespace CriticalPathAnalyzer.Client.Tool {
return PegAddress.Empty;
}
/// <summary>
/// Try to select the start of the path.
/// </summary>
public static void SelectPathStart() {
_logger.Info("Analyze Path Start");
PegAddress pegAddress = RayCastPeg();
@ -73,6 +81,9 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
}
/// <summary>
/// Try to select the end of the path.
/// </summary>
public static void SelectPathEnd() {
_logger.Info("Analyze Path End");
PegAddress pegAddress = RayCastPeg();
@ -86,6 +97,9 @@ namespace CriticalPathAnalyzer.Client.Tool {
}
}
/// <summary>
/// Remove standard highlighting and only highlight clusters that form a loop.
/// </summary>
public static void DisplayLoop() {
if (_response == null) {
_logger.Error("Got no response - cannot display loop");
@ -96,6 +110,9 @@ namespace CriticalPathAnalyzer.Client.Tool {
PathHighlighter.HighlightWires(_response.LoopingClusters);
}
/// <summary>
/// Send a request to the server to analyze the path between the selected start and end pegs.
/// </summary>
private static void CalculateCriticalPath() {
if (_startPegAddress.IsEmpty() || _endPegAddress.IsEmpty()) {
_logger.Error("Invalid pegs");
@ -110,6 +127,11 @@ namespace CriticalPathAnalyzer.Client.Tool {
});
}
/// <summary>
/// Handle incoming response packet from the server.
/// Highlight pegs and wires involved in the circuit with their color based on their delay from the start.
/// </summary>
/// <param name="response">The server's response packet</param>
public static void HandleResponse(AnalyzePathResponse response) {
if (_currentRequestGuid != response.RequestGuid) {
// Not matching Guid, old or wrong request, discard.

@ -8,10 +8,6 @@ using LogicWorld.Outlines;
namespace CriticalPathAnalyzer.Client.Tool {
public class PathHighlighter {
private static readonly OutlineData Color = new OutlineData(new Color24(0xff0000));
private static readonly OutlineData NextColor = new OutlineData(new Color24(0xff00ff));
private static readonly OutlineData PerimeterColor = new OutlineData(new Color24(0xffff00));
private static List<ClusterDetails> _clusters = new List<ClusterDetails>();
private static List<ComponentAddress> _highlightedComponents = new List<ComponentAddress>();
private static List<WireAddress> _highlightedWires = new List<WireAddress>();
@ -20,7 +16,7 @@ namespace CriticalPathAnalyzer.Client.Tool {
_clusters = clusters;
foreach (ClusterDetails clusterDetails in _clusters) {
HighlightCluster(clusterDetails, Color);
HighlightCluster(clusterDetails);
}
// IWorldData world = Instances.MainWorld.Data;
@ -40,7 +36,10 @@ namespace CriticalPathAnalyzer.Client.Tool {
// }
}
private static void HighlightCluster(ClusterDetails cluster, OutlineData outlineOld) {
/// <summary>
/// Highlight all pegs and wires of the given cluster.
/// </summary>
private static void HighlightCluster(ClusterDetails cluster) {
IWorldData world = Instances.MainWorld.Data;
var outline = new OutlineData(new Color24(cluster.Color));

@ -34,13 +34,17 @@ namespace CriticalPathAnalyzer.Server {
throw new Exception("Could not get Service 'NetworkServer'.");
}
// Inject verifier:
// Inject client verifier:
RawJoinVerifierInjector.addVerifier(this);
PacketHandlerManager.getCustomPacketHandler<ClientLoadedWorldPacket>()
.addHandlerToEnd(new ClientJoinedPacketHandler(this));
RawPacketHandlerInjector.addPacketHandler(new CriticalPathAnalyzerRequestHandler(this));
}
/// <summary>
/// Gets called before a player (client) joins the server.
/// Used to check if the player has this mod installed
/// </summary>
public void Verify(VerificationContext ctx) {
bool hasMod = ctx.ApprovalPacket.ClientMods.Contains(Manifest.ID);
string playerName = ctx.PlayerID.Name;
@ -48,6 +52,10 @@ namespace CriticalPathAnalyzer.Server {
LoggerInstance.Info($"Verifying player {playerName}: hasMod={hasMod}");
}
/// <summary>
/// Gets called after verifying a connecting player.
/// If the player has this mod installed, send a packet informing about the mod's presence on the server.
/// </summary>
public void PlayerJoined(PlayerData playerData, HandlerContext context) {
_playerHasWireTracer.TryGetValue(playerData.Name, out bool hasWireTracer);
if (hasWireTracer) {
@ -57,6 +65,10 @@ namespace CriticalPathAnalyzer.Server {
}
}
/// <summary>
/// Gets called from the AnalyzePathRequestHandler.
/// Use the tool to trace the path and send back a response.
/// </summary>
public void AnalyzePath(Connection sender, Guid requestGuid, PegAddress start, PegAddress end) {
LoggerInstance.Info("Got AnalyzePath request");
if (!ServerPathTracer.TracePath(requestGuid, start, end, out AnalyzePathResponse response)) {

@ -192,6 +192,11 @@ namespace CriticalPathAnalyzer.Server.Tool {
}
/// <summary>
/// Check if the peg exists in the world
/// </summary>
/// <param name="address">Address of the peg</param>
/// <returns>True if it exists, false if it doesn't</returns>
private static bool PegExists(PegAddress address) {
IComponentInWorld component = World.Lookup(address.ComponentAddress);
if (component == null) {
@ -224,6 +229,12 @@ namespace CriticalPathAnalyzer.Server.Tool {
return linker != null;
}
/// <summary>
/// An output peg can be connected to multiple clusters. Collect all of them.
/// </summary>
/// <param name="pegAddress">Address of the peg</param>
/// <param name="primaryClusters">Found clusters get added into this hash set</param>
/// <exception cref="Exception">A wire of the peg is not in the world</exception>
private static void CollectMainClusters(PegAddress pegAddress, HashSet<Cluster> primaryClusters) {
if (pegAddress.IsInputAddress(out InputAddress inputAddress)) {
primaryClusters.Add(GetClusterAt(inputAddress));

@ -11,6 +11,7 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
[Key(1)] public List<ClusterDetails> Clusters;
[Key(2)] public int CriticalPathLength;
[Key(3)] public List<ClusterDetails> LoopingClusters;
// [Key(4)] public List<ComponentAddress> PerimeterComponents;
// [Key(5)] public List<ClusterDetails> NextClusters;
@ -18,9 +19,21 @@ namespace CriticalPathAnalyzer.Shared.Packets.S2C {
[MessagePackObject]
public sealed class ClusterDetails {
/// <summary>
/// List of all pegs inside the cluster
/// </summary>
[Key(0)] public List<PegAddress> Pegs;
[Key(1)] public List<ComponentAddress> ConnectingComponents; // Sockets
[Key(2)] public List<ComponentAddress> LinkingComponents; // Relays, fast buffers
/// <summary>
/// Sockets
/// </summary>
[Key(1)] public List<ComponentAddress> ConnectingComponents;
/// <summary>
/// Relays and fast buffers
/// </summary>
[Key(2)] public List<ComponentAddress> LinkingComponents;
[Key(3)] public int Color;
}
}
Loading…
Cancel
Save