Commit fb5d3c78 authored by Lumi Monahan's avatar Lumi Monahan
Browse files

Initial commit. Pretty much a broken input recorder/player. Time on playback isn't synced well...

parents

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SuperliminalTAS", "SuperliminalTAS\SuperliminalTAS.csproj", "{5FB93E98-5060-4A81-92A3-428E887D6E65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5FB93E98-5060-4A81-92A3-428E887D6E65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FB93E98-5060-4A81-92A3-428E887D6E65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FB93E98-5060-4A81-92A3-428E887D6E65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FB93E98-5060-4A81-92A3-428E887D6E65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {72A0BEDA-157B-4B79-A071-5FEE65B85781}
EndGlobalSection
EndGlobal
using MelonLoader;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS
{
public class InputReader
{
public Dictionary<double, InputState> RecordedInput = new Dictionary<double, InputState>();
public void Reload()
{
RecordedInput = new Dictionary<double, InputState>();
var csvData = File.ReadAllLines("recorded-run.csv");
var lastTime = 0d;
// Horrible CSV parser :)
foreach (var row in csvData.Skip(1))
{
// "GameTimeDelta,LookH,LookV,MoveH,MoveV,Jump,Rotate,Grab,Level,Checkpoint"
var rowData = row.Split(',');
var timeDelta = double.Parse(rowData[0]);
lastTime += timeDelta;
var inputState = new InputState()
{
LookHorizontal = float.Parse(rowData[1]),
LookVertical = float.Parse(rowData[2]),
MoveHorizontal = float.Parse(rowData[3]),
MoveVertical = float.Parse(rowData[4]),
Jump = rowData[5] == "T",
Rotate = rowData[6] == "T",
Grab = rowData[7] == "T"
};
if (inputState.Grab)
MelonLogger.Log("grab: {0}, csv: {1}", inputState.Grab, row);
RecordedInput.Add(lastTime, inputState);
}
}
public InputState GetInputForTime(double time)
{
foreach (var kv in RecordedInput)
{
if (kv.Key >= time) return kv.Value;
}
return null;
}
}
}
using MelonLoader;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS
{
public class InputRecorder
{
public bool lastUpdateJump = false;
public bool lastUpdateRotate = false;
public bool lastUpdateGrab = false;
public Dictionary<double, InputState> RecordedInput = new Dictionary<double, InputState>();
public void Update()
{
var mod = TASMod.Instance;
if (mod.PlayerInput == null) return; // too early to do anything yet
if (mod.GameTimer == 0) return; // Speedrun has not started yet
if (mod.IsGamePaused) return; // Don't do anything if the game is paused
if (!mod.TimerRunning) return;
var input = new InputState(mod.PlayerInput);
if (!RecordedInput.ContainsKey(mod.GameTimer)) RecordedInput.Add(mod.GameTimer, input);
}
public void OnRunFinished()
{
MelonLogger.Log("Recorded input has {0} entries", RecordedInput.Count);
//InputState lastInput = null;
var minInput = RecordedInput
//.Where(i =>
// {
// var matches = i.Value.Equals(lastInput);
// lastInput = i.Value;
// return !matches;
// })
.Where(i => i.Value.CurrentLevel != Levels.Loading);
MelonLogger.Log("Minimized input has {0} entries", minInput.Count());
var csvData = new List<string>
{
"GameTimeDelta,LookH,LookV,MoveH,MoveV,Jump,Rotate,Grab,Level,Checkpoint"
};
var lastFrame = 0d;
foreach (var entry in minInput)
{
var deltaT = entry.Key - lastFrame;
lastFrame = entry.Key;
var stringEntry = string.Format("{0:F4},{1}",
deltaT,
entry.Value.CsvFormat());
csvData.Add(stringEntry);
}
File.WriteAllLines("recorded-run.csv", csvData);
}
}
}
using MelonLoader;
using Rewired;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS
{
public class InputState
{
public float LookHorizontal { get; set; }
public float LookVertical { get; set; }
public float MoveHorizontal { get; set; }
public float MoveVertical { get; set; }
public bool Jump { get; set; }
public bool Rotate { get; set; }
public bool Grab { get; set; }
public Levels CurrentLevel { get; set; }
public string CurrentCheckpoint { get; set; }
public InputState() { }
public InputState(float lookH, float lookV, float moveH, float moveV, bool jump, bool rotate, bool grab)
{
LookHorizontal = lookH;
LookVertical = lookV;
MoveHorizontal = moveH;
MoveVertical = moveV;
Jump = jump;
Rotate = rotate;
Grab = grab;
}
public InputState(Player player)
{
LookHorizontal = player.GetAxis("Look Horizontal");
LookVertical = player.GetAxis("Look Vertical");
MoveHorizontal = player.GetAxis("Move Horizontal");
MoveVertical = player.GetAxis("Move Vertical");
Jump = player.GetButton("Jump");
Rotate = player.GetButton("Rotate");
Grab = player.GetButtonDown("Grab");
CurrentLevel = TASMod.Instance.CurrentLevel;
CurrentCheckpoint = TASMod.Instance.CurrentCheckpoint;
}
public string CsvFormat()
{
return string.Format("{0:F2},{1:F2},{2:F2},{3:F2},{4},{5},{6},{7},{8}",
LookHorizontal,
LookVertical,
MoveHorizontal,
MoveVertical,
Jump ? "T" : "F",
Rotate ? "T" : "F",
Grab ? "T" : "F",
CurrentLevel,
CurrentCheckpoint);
}
public override bool Equals(object obj)
{
if (obj == null || !GetType().Equals(obj.GetType()))
{
MelonLogger.Log("obj null or not same type");
return false;
}
InputState input = (InputState)obj;
//MelonLogger.Log("match: {0} this: {1} other: {2}", CsvFormat() == input.CsvFormat(), CsvFormat(), input.CsvFormat());
return CsvFormat() == input.CsvFormat();
}
// apparently you gotta override both this and .Equals?
public override int GetHashCode()
{
int hashCode = 1128518326;
hashCode = (hashCode * -1521134295) + LookHorizontal.GetHashCode();
hashCode = (hashCode * -1521134295) + LookVertical.GetHashCode();
hashCode = (hashCode * -1521134295) + MoveHorizontal.GetHashCode();
hashCode = (hashCode * -1521134295) + MoveVertical.GetHashCode();
hashCode = (hashCode * -1521134295) + Jump.GetHashCode();
hashCode = (hashCode * -1521134295) + Rotate.GetHashCode();
hashCode = (hashCode * -1521134295) + Grab.GetHashCode();
return hashCode;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS
{
public enum Levels
{
Menu = 0,
Intro,
Induction,
Optical,
Cubism,
Blackout,
Clone,
Dollhouse,
Labyrinth,
Whitespace,
Retrospect,
Loading = 22
}
}
using Harmony;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS
{
public static class Patcher
{
public static void Patch()
{
var harmony = HarmonyInstance.Create("com.superliminaltas.patch");
harmony.PatchAll(Assembly.GetExecutingAssembly());
}
}
}
using Harmony;
using MelonLoader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS.Patches
{
[HarmonyPatch(typeof(SaveAndCheckpointManager), "EnteredCheckpoint", typeof(CheckPoint))]
public static class EnteredCheckpointPatch
{
public static void Prefix(CheckPoint checkpoint)
{
TASMod.Instance.OnCheckpoint(checkpoint.GetName());
}
}
}
using Harmony;
using MelonLoader;
using Rewired;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS.Patches
{
[HarmonyPatch(typeof(Player), "GetAxis", typeof(string))]
public static class InputGetAxisStringPatches
{
public static void Postfix(string actionName, ref float __result)
{
if (TASMod.Instance.Mode != TASMode.Playback || !TASMod.Instance.TimerRunning) return;
if (actionName == "Look Horizontal")
{
__result = TASMod.Instance.InputState.LookHorizontal;
}
else if (actionName == "Look Vertical")
{
__result = TASMod.Instance.InputState.LookVertical;
}
else if (actionName == "Move Horizontal")
{
__result = TASMod.Instance.InputState.MoveHorizontal;
}
else if (actionName == "Move Vertical")
{
__result = TASMod.Instance.InputState.MoveVertical;
}
else
{
MelonLogger.Log("Unknown actionName {0} in GetAxis", actionName);
}
}
}
}
using Harmony;
using MelonLoader;
using Rewired;
namespace SuperliminalTAS.Patches
{
[HarmonyPatch(typeof(Player), "GetButtonDown", typeof(string))]
public static class InputGetButtonDownPatches
{
public static double LatchTime = 0f;
public static bool GrabLatched = false;
public static void Postfix(string actionName, ref bool __result)
{
if (TASMod.Instance.Mode != TASMode.Playback || !TASMod.Instance.TimerRunning) return;
if (actionName == "Grab")
{
if (TASMod.Instance.InputState.Grab)
{
LatchTime = TASMod.Instance.GameTimer;
GrabLatched = true;
TASMod.Instance.InputState.Grab = false;
}
if (GrabLatched)
{
if (LatchTime != TASMod.Instance.GameTimer)
{
__result = false;
GrabLatched = false;
}
else
{
__result = true;
}
}
}
else if (actionName == "UICancel" || actionName == "Pause")
{
return;
}
else
{
MelonLogger.Log("Unknown actionName {0} in GetButtonDown", actionName);
}
}
}
}
using Harmony;
using MelonLoader;
using Rewired;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SuperliminalTAS.Patches
{
[HarmonyPatch(typeof(Player), "GetButton", typeof(string))]
public static class InputGetButtonPatches
{
public static void Postfix(string actionName, ref bool __result)
{
if (TASMod.Instance.Mode != TASMode.Playback || !TASMod.Instance.TimerRunning) return;
if (actionName == "Jump")
{
__result = TASMod.Instance.InputState.Jump;
}
else if (actionName == "Rotate")
{
__result = TASMod.Instance.InputState.Rotate;
}
else if (actionName == "UISubmit")
{
return;
}
else
{
MelonLogger.Log("Unknown actionName {0} in GetButton", actionName);
}
}
}
}
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SuperliminalTAS")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SuperliminalTAS")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5fb93e98-5060-4a81-92a3-428e887d6e65")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using MelonLoader;
using SuperliminalTAS.TextBar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace SuperliminalTAS
{
public class StatusDisplay
{
private readonly List<BaseComponent> components = new List<BaseComponent>();
public StatusDisplay()
{
components.Add(new GameTime());
components.Add(new CheckpointTime());
components.Add(new LevelTime());
components.Add(new TASModeSelector());
components.Add(new InputView());
}
public void Update()
{
if (GameManager.GM.player == null)
return;
foreach (var component in components)
component.Update();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{5FB93E98-5060-4A81-92A3-428E887D6E65}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SuperliminalTAS</RootNamespace>
<AssemblyName>SuperliminalTAS</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\Mods\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Assembly-CSharp-firstpass">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader.ModHandler">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Rewired_Core">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\Rewired_Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>..\..\..\..\..\Program Files\SteamLibrary\steamapps\common\Superliminal\SuperliminalSteam_Data\Managed\UnityEngine.UIModule.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="InputReader.cs" />
<Compile Include="Levels.cs" />
<Compile Include="Patches\EnteredCheckpointPatch.cs" />
<Compile Include="Patches\InputGetAxisPatches.cs" />
<Compile Include="Patches\InputGetButtonDownPatches.cs" />
<Compile Include="Patches\InputGetButtonPatches.cs" />
<Compile Include="InputRecorder.cs" />
<Compile Include="InputState.cs" />
<Compile Include="StatusDisplay.cs" />
<Compile Include="TASMod.cs" />