diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4838480
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+.idea/
+.vs/
+*.user
+/local
+
+bin/
+obj/
+
+BetterFirstPerson/obj/
+BetterFirstPerson/bin/
\ No newline at end of file
diff --git a/BetterFirstPerson.sln b/BetterFirstPerson.sln
new file mode 100644
index 0000000..53e4e64
--- /dev/null
+++ b/BetterFirstPerson.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35013.160
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterFirstPerson", "BetterFirstPerson\BetterFirstPerson.csproj", "{1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/BetterFirstPerson/BetterFirstPerson.csproj b/BetterFirstPerson/BetterFirstPerson.csproj
new file mode 100644
index 0000000..2643a94
--- /dev/null
+++ b/BetterFirstPerson/BetterFirstPerson.csproj
@@ -0,0 +1,27 @@
+
+
+ net8.0
+ enable
+ enable
+ $(AssemblySearchPaths);$(GDWeavePath)/core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BetterFirstPerson/BetterFirstPersonMod.cs b/BetterFirstPerson/BetterFirstPersonMod.cs
new file mode 100644
index 0000000..de932e3
--- /dev/null
+++ b/BetterFirstPerson/BetterFirstPersonMod.cs
@@ -0,0 +1,18 @@
+using GDWeave;
+using Serilog;
+using Serilog.Core;
+
+namespace BetterFirstPerson;
+
+public class BetterFirstPersonMod : IMod {
+ public Config Config;
+
+ public BetterFirstPersonMod(IModInterface modInterface) {
+ this.Config = modInterface.ReadConfig();
+ modInterface.Logger.Information("BetterFirstPerson loaded!");
+
+ modInterface.RegisterScriptMod(new PlayerPatch(this));
+ }
+
+ public void Dispose() {}
+}
diff --git a/BetterFirstPerson/Config.cs b/BetterFirstPerson/Config.cs
new file mode 100644
index 0000000..d5a7566
--- /dev/null
+++ b/BetterFirstPerson/Config.cs
@@ -0,0 +1,7 @@
+using System.Text.Json.Serialization;
+
+namespace BetterFirstPerson;
+
+public class Config {
+ [JsonInclude] public bool ResetUpDownOnFreecam = false;
+}
diff --git a/BetterFirstPerson/FirstPersonFix.cs b/BetterFirstPerson/FirstPersonFix.cs
new file mode 100644
index 0000000..5259356
--- /dev/null
+++ b/BetterFirstPerson/FirstPersonFix.cs
@@ -0,0 +1,213 @@
+using GDWeave;
+using GDWeave.Godot;
+using GDWeave.Godot.Variants;
+using GDWeave.Modding;
+using Serilog;
+using Serilog.Configuration;
+
+namespace BetterFirstPerson;
+
+public class PlayerPatch(BetterFirstPersonMod mod) : IScriptMod
+{
+ public bool ShouldRun(string path) => path == "res://Scenes/Entities/Player/player.gdc";
+
+ public IEnumerable Modify(string path, IEnumerable tokens)
+ {
+ var waiter = new MultiTokenWaiter([
+ t => t is IdentifierToken{Name: "skeleton"},
+ t => t.Type is TokenType.Period,
+ t => t is IdentifierToken{Name: "set_bone_custom_pose" },
+ t => t.Type is TokenType.ParenthesisOpen,
+ t => t is ConstantToken{Value: IntVariant{Value:1 } },
+ t => t.Type is TokenType.Comma,
+ t => t.Type is TokenType.BuiltInType,
+ t => t.Type is TokenType.ParenthesisOpen,
+ t => t.Type is TokenType.ParenthesisClose,
+ t => t.Type is TokenType.ParenthesisClose,
+ t => t.Type is TokenType.Newline,
+ ], allowPartialMatch: true);
+
+ foreach (var token in tokens)
+ {
+ // was useful since GDWeave.Test didn't work for me :p
+ // mod.logger.Information(token.ToString());
+ if (waiter.Check(token))
+ {
+ yield return new Token(TokenType.Newline, 1);
+
+ // fuck me
+
+ // if camera_zoom < 0.5 and not freecamming:
+ yield return new Token(TokenType.CfIf);
+ yield return new IdentifierToken(TokenType.Identifier, 32, "camera_zoom");
+ yield return new Token(TokenType.OpLess);
+ yield return new ConstantToken(new RealVariant(0.5));
+ yield return new Token(TokenType.OpAnd);
+ yield return new Token(TokenType.OpNot);
+ yield return new IdentifierToken(TokenType.Identifier, 134, "freecamming");
+ yield return new Token(TokenType.Colon);
+ yield return new Token(TokenType.Newline, 2);
+
+ // if state != STATES.FISHING and state != STATES.FISHING_STRUGGLE:
+ yield return new Token(TokenType.CfIf);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(1)); // 1 - BUSY
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(6)); // 6 - FISHING_CAST
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(7)); // 7 - FISHING
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(9)); // 9 - FISHING_STRUGGLE
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(10)); // 10 - SHOVEL_CAST
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(11)); // 11 - SHOVEL_STRUGGLE
+
+ // idk what these are :p
+
+ //yield return new Token(TokenType.OpOr);
+ //yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ //yield return new Token(TokenType.OpNotEqual);
+ //yield return new ConstantToken(new IntVariant(12)); // 12 - NET_CAST
+ //yield return new Token(TokenType.OpOr);
+ //yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ //yield return new Token(TokenType.OpNotEqual);
+ //yield return new ConstantToken(new IntVariant(13)); // 13 - NET_STRUGGLE
+
+ yield return new Token(TokenType.OpAnd);
+ yield return new IdentifierToken(TokenType.Identifier, 47, "state");
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new IntVariant(16)); // 16 - SHOVEL_STRUGGLE
+
+ yield return new Token(TokenType.Colon);
+ yield return new Token(TokenType.Newline, 3);
+
+ // self.rotation.y = camera.rotation.y
+ yield return new Token(TokenType.Self);
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken("rotation");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken("y");
+ yield return new Token(TokenType.OpAssign);
+ yield return new IdentifierToken(TokenType.Identifier, 137, "camera");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken(TokenType.Identifier, 286, "rotation");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken(TokenType.Identifier, 288, "y");
+ yield return new Token(TokenType.Newline, 3);
+
+ // var back_bend = clamp(camera.rotation.x * 54, -50.0, 50.0)
+ yield return new Token(TokenType.PrVar);
+ yield return new IdentifierToken("back_bend");
+ yield return new Token(TokenType.OpAssign);
+ yield return new Token(TokenType.BuiltInFunc, 53);
+ yield return new Token(TokenType.ParenthesisOpen);
+ yield return new IdentifierToken(TokenType.Identifier, 137, "camera");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken(TokenType.Identifier, 286, "rotation");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken(TokenType.Identifier, 288, "x");
+ yield return new Token(TokenType.OpMul);
+ yield return new ConstantToken(new IntVariant(54));
+ yield return new Token(TokenType.Comma);
+ yield return new ConstantToken(new RealVariant(-50.0));
+ yield return new Token(TokenType.Comma);
+ yield return new ConstantToken(new RealVariant(50.0));
+ yield return new Token(TokenType.ParenthesisClose);
+ yield return new Token(TokenType.Newline, 3);
+
+ // $body / player_body / Armature / Skeleton / BoneAttachment / Spatial.rotation_degrees = Vector3(0.0, camera.rotation.y, back_bend * 0.7)
+ yield return new Token(TokenType.Dollar);
+ yield return new IdentifierToken(TokenType.Identifier, 142, "body");
+ yield return new Token(TokenType.OpDiv);
+ yield return new IdentifierToken(TokenType.Identifier, 144, "player_body");
+ yield return new Token(TokenType.OpDiv);
+ yield return new IdentifierToken(TokenType.Identifier, 145, "Armature");
+ yield return new Token(TokenType.OpDiv);
+ yield return new IdentifierToken(TokenType.Identifier, 146, "Skeleton");
+ yield return new Token(TokenType.OpDiv);
+ yield return new IdentifierToken(TokenType.Identifier, 597, "BoneAttachment");
+ yield return new Token(TokenType.OpDiv);
+ yield return new IdentifierToken(TokenType.Identifier, 598, "Spatial");
+ yield return new Token(TokenType.Period);
+ yield return new IdentifierToken(TokenType.Identifier, 342, "rotation_degrees");
+ yield return new Token(TokenType.OpAssign);
+ yield return new Token(TokenType.BuiltInType, 7);
+ yield return new Token(TokenType.ParenthesisOpen);
+ yield return new ConstantToken(new RealVariant(0.0));
+ yield return new Token(TokenType.Comma);
+ yield return new ConstantToken(new RealVariant(0.0));
+ yield return new Token(TokenType.Comma);
+ yield return new IdentifierToken("back_bend");
+ yield return new Token(TokenType.OpMul);
+ yield return new ConstantToken(new RealVariant(0.7));
+ yield return new Token(TokenType.ParenthesisClose);
+ yield return new Token(TokenType.Newline, 3);
+
+ // animation_data["back_bend"] = deg2rad( - back_bend)
+ yield return new IdentifierToken(TokenType.Identifier, 126, "animation_data");
+ yield return new Token(TokenType.BracketOpen);
+ yield return new ConstantToken(new StringVariant("back_bend"));
+ yield return new Token(TokenType.BracketClose);
+ yield return new Token(TokenType.OpAssign);
+ yield return new Token(TokenType.BuiltInFunc, 43);
+ yield return new Token(TokenType.ParenthesisOpen);
+ yield return new Token(TokenType.OpSub);
+ yield return new IdentifierToken("back_bend");
+ yield return new Token(TokenType.ParenthesisClose);
+ yield return new Token(TokenType.Newline, 1);
+
+ if (mod.Config.ResetUpDownOnFreecam)
+ {
+ // else:
+ yield return new Token(TokenType.CfElse);
+ yield return new Token(TokenType.Colon);
+
+ yield return new Token(TokenType.Newline, 2);
+ // if not freecamming:
+ yield return new Token(TokenType.CfIf);
+ yield return new Token(TokenType.OpNot);
+ yield return new IdentifierToken(TokenType.Identifier, 134, "freecamming");
+ yield return new Token(TokenType.Colon);
+ yield return new Token(TokenType.Newline, 3);
+
+ // if animation_data["back_bend"] != 0.0:
+ yield return new Token(TokenType.CfIf);
+ yield return new IdentifierToken(TokenType.Identifier, 126, "animation_data");
+ yield return new Token(TokenType.BracketOpen);
+ yield return new ConstantToken(new StringVariant("back_bend"));
+ yield return new Token(TokenType.BracketClose);
+ yield return new Token(TokenType.OpNotEqual);
+ yield return new ConstantToken(new RealVariant(0.0));
+ yield return new Token(TokenType.Colon);
+ yield return new Token(TokenType.Newline, 4);
+
+ // animation_data["back_bend"] = 0.0
+ yield return new IdentifierToken(TokenType.Identifier, 126, "animation_data");
+ yield return new Token(TokenType.BracketOpen);
+ yield return new ConstantToken(new StringVariant("back_bend"));
+ yield return new Token(TokenType.BracketClose);
+ yield return new Token(TokenType.OpAssign);
+ yield return new ConstantToken(new RealVariant(0.0));
+ yield return new Token(TokenType.Newline, 1);
+ }
+ }
+ else
+ {
+ yield return token;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/BetterFirstPerson/manifest.json b/BetterFirstPerson/manifest.json
new file mode 100644
index 0000000..d8ec0aa
--- /dev/null
+++ b/BetterFirstPerson/manifest.json
@@ -0,0 +1,4 @@
+{
+ "Id": "BetterFirstPerson",
+ "AssemblyPath": "BetterFirstPerson.dll"
+}
diff --git a/README.md b/README.md
index 7491bcf..da5b7a7 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,6 @@
# BetterFirstPerson
-a better first person mod for webfishing
\ No newline at end of file
+better first person mod for webfishing
+
+## what does this mod do
+makes the character rotate in first person (includes up and down body bending :3)
\ No newline at end of file