this repo has no description

Working mod

+3
.gitignore
··· 1 + .idea 2 + bin 3 + obj
+16
Atproto.sln
··· 1 +  2 + Microsoft Visual Studio Solution File, Format Version 12.00 3 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atproto", "Atproto\Atproto.csproj", "{1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}" 4 + EndProject 5 + Global 6 + GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 + Debug|Any CPU = Debug|Any CPU 8 + Release|Any CPU = Release|Any CPU 9 + EndGlobalSection 10 + GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 + {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 + {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 + {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 + {1A273855-C2D0-40BB-B22C-CE17AA4D0A9B}.Release|Any CPU.Build.0 = Release|Any CPU 15 + EndGlobalSection 16 + EndGlobal
+30
Atproto/Atproto.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + <PropertyGroup> 3 + <TargetFramework>net8.0</TargetFramework> 4 + <ImplicitUsings>enable</ImplicitUsings> 5 + <Nullable>enable</Nullable> 6 + <AssemblySearchPaths>$(AssemblySearchPaths);$(GDWeavePath)/core</AssemblySearchPaths> 7 + <Version>1.0.0.0</Version> 8 + <RootNamespace>Atproto</RootNamespace> 9 + </PropertyGroup> 10 + 11 + <ItemGroup> 12 + <Reference Include="GDWeave" Private="false"/> 13 + <Reference Include="Serilog" Private="false"/> 14 + </ItemGroup> 15 + 16 + <ItemGroup> 17 + <None Include="manifest.json" CopyToOutputDirectory="PreserveNewest"/> 18 + </ItemGroup> 19 + 20 + <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(GDWeavePath)' != ''"> 21 + <ItemGroup> 22 + <_SourceFiles Include="$(OutDir)\**\*.*"/> 23 + </ItemGroup> 24 + 25 + <Copy 26 + SourceFiles="@(_SourceFiles)" 27 + DestinationFolder="$(GDWeavePath)/mods/$(AssemblyName)" 28 + /> 29 + </Target> 30 + </Project>
+87
Atproto/CalicoUtils/LexicalTransformer/TransformationPatternFactory.cs
··· 1 + using GDWeave.Godot; 2 + using Teemaw.Calico.Util; 3 + using static GDWeave.Godot.TokenType; 4 + 5 + namespace Teemaw.Calico.LexicalTransformer; 6 + 7 + using MultiTokenPattern = Func<Token, bool>[]; 8 + 9 + public static class TransformationPatternFactory 10 + { 11 + /// <summary> 12 + /// Creates a new pattern array which matches `extends &lt;Identifier&gt;\n`. Useful for patching into the global 13 + /// scope. 14 + /// </summary> 15 + /// <returns></returns> 16 + public static MultiTokenPattern CreateGlobalsPattern() 17 + { 18 + return 19 + [ 20 + t => t.Type is PrExtends, 21 + t => t.Type is Identifier, 22 + t => t.Type is Newline 23 + ]; 24 + } 25 + 26 + /// <summary> 27 + /// Creates a new pattern array which matches a function definition. This will not match the preceding or trailing 28 + /// Newline tokens. 29 + /// </summary> 30 + /// <param name="name">The name of the function to match.</param> 31 + /// <param name="args"> 32 + /// An array of the names of arguments of the function to match. If null or empty, the returned checks will only 33 + /// match a function which does not accept arguments. 34 + /// </param> 35 + /// <returns></returns> 36 + public static MultiTokenPattern CreateFunctionDefinitionPattern(string name, string[]? args = null) 37 + { 38 + var checks = new List<Func<Token, bool>>(); 39 + 40 + checks.Add(t => t.Type is PrFunction); 41 + checks.Add(t => t is IdentifierToken token && token.Name == name); 42 + checks.Add(t => t.Type is ParenthesisOpen); 43 + if (args is { Length: > 0 }) 44 + { 45 + foreach (var arg in args) 46 + { 47 + checks.Add(t => t is IdentifierToken token && token.Name == arg); 48 + checks.Add(t => t.Type is Comma); 49 + } 50 + 51 + checks.RemoveAt(checks.Count - 1); 52 + } 53 + 54 + checks.Add(t => t.Type is ParenthesisClose); 55 + checks.Add(t => t.Type is Colon); 56 + 57 + return checks.ToArray(); 58 + } 59 + 60 + /// <summary> 61 + /// Creates a new pattern array which matches the provided GDScript snippet. 62 + /// </summary> 63 + /// <param name="snippet">A GDScript snippet to match.</param> 64 + /// <param name="indent">The base indent of the snippet.</param> 65 + /// <returns></returns> 66 + public static MultiTokenPattern CreateGdSnippetPattern(string snippet, uint indent = 0) 67 + { 68 + var tokens = ScriptTokenizer.Tokenize(snippet, indent); 69 + 70 + return tokens.Select(snippetToken => (Func<Token, bool>)(t => 71 + { 72 + if (t.Type == Identifier) 73 + { 74 + return snippetToken is IdentifierToken snippetIdentifier && t is IdentifierToken identifier && 75 + snippetIdentifier.Name == identifier.Name; 76 + } 77 + 78 + if (t.Type == Constant) 79 + { 80 + return snippetToken is ConstantToken snippetConstant && t is ConstantToken constant && 81 + snippetConstant.Value.Equals(constant.Value); 82 + } 83 + 84 + return t.Type == snippetToken.Type; 85 + })).ToArray(); 86 + } 87 + }
+231
Atproto/CalicoUtils/LexicalTransformer/TransformationRule.cs
··· 1 + using GDWeave.Godot; 2 + using GDWeave.Modding; 3 + using ScriptTokenizer = Teemaw.Calico.Util.ScriptTokenizer; 4 + 5 + namespace Teemaw.Calico.LexicalTransformer; 6 + 7 + using MultiTokenPattern = Func<Token, bool>[]; 8 + 9 + public enum Operation 10 + { 11 + /// <summary> 12 + /// Do not patch. 13 + /// </summary> 14 + None, 15 + 16 + /// <summary> 17 + /// Replace all tokens of the waiter. This is a buffered operation. Buffered rules do not support overlapping token 18 + /// patterns with other buffered rules. 19 + /// </summary> 20 + ReplaceAll, 21 + 22 + /// <summary> 23 + /// Replace the final token of the waiter. 24 + /// </summary> 25 + ReplaceLast, 26 + 27 + /// <summary> 28 + /// Appends after the final token of the waiter. 29 + /// </summary> 30 + Append, 31 + 32 + /// <summary> 33 + /// Prepends before the first token of the waiter. This is a buffered operation. Buffered rules do not support 34 + /// overlapping token patterns with other buffered rules. 35 + /// </summary> 36 + Prepend, 37 + } 38 + 39 + public static class OperationExtensions 40 + { 41 + public static bool RequiresBuffer(this Operation operation) 42 + { 43 + return operation is Operation.ReplaceAll or Operation.Prepend; 44 + } 45 + 46 + public static bool YieldTokenBeforeOperation(this Operation operation) 47 + { 48 + return operation is Operation.Append or Operation.None; 49 + } 50 + 51 + public static bool YieldTokenAfterOperation(this Operation operation) 52 + { 53 + return !operation.RequiresBuffer() && !operation.YieldTokenBeforeOperation() && 54 + operation != Operation.ReplaceLast; 55 + } 56 + } 57 + 58 + /// <summary> 59 + /// This holds the information required to perform a patch at a single locus. 60 + /// </summary> 61 + /// <param name="Name">The name of this descriptor. Used for logging.</param> 62 + /// <param name="Pattern">A list of checks to be used in a MultiTokenWaiter.</param> 63 + /// <param name="ScopePattern">A list of checks to be used in a MultiTokenWaiter marking the .</param> 64 + /// <param name="Tokens">A list of GDScript tokens which will be patched in.</param> 65 + /// <param name="Operation">The type of patch.</param> 66 + /// <param name="Times">The number of times this rule is expected to match.</param> 67 + /// <param name="Predicate">A predicate which must return true for the rule to match.</param> 68 + public record TransformationRule( 69 + string Name, 70 + MultiTokenPattern Pattern, 71 + MultiTokenPattern ScopePattern, 72 + IEnumerable<Token> Tokens, 73 + Operation Operation, 74 + uint Times, 75 + Func<bool> Predicate) 76 + { 77 + 78 + public MultiTokenWaiter CreateMultiTokenWaiter() => new(Pattern); 79 + 80 + public MultiTokenWaiter CreateMultiTokenWaiterForScope() => new(ScopePattern); 81 + } 82 + 83 + /// <summary> 84 + /// Builder for TransformationRule. Times defaults to 1. Operation defaults to <see cref="Operation.Append"/>. 85 + /// </summary> 86 + public class TransformationRuleBuilder 87 + { 88 + private string? _name; 89 + private MultiTokenPattern? _pattern; 90 + private MultiTokenPattern _scopePattern = []; 91 + private IEnumerable<Token>? _tokens; 92 + private uint _times = 1; 93 + private Operation _operation = Operation.Append; 94 + private Func<bool> _predicate = () => true; 95 + 96 + /// <summary> 97 + /// Sets the name for the TransformationRule. Used for logging. 98 + /// </summary> 99 + /// <param name="name"></param> 100 + /// <returns></returns> 101 + public TransformationRuleBuilder Named(string name) 102 + { 103 + _name = name; 104 + return this; 105 + } 106 + 107 + /// <summary> 108 + /// Sets the token pattern which will be matched by the TransformationRule. 109 + /// </summary> 110 + /// <param name="pattern"></param> 111 + /// <returns></returns> 112 + public TransformationRuleBuilder Matching(MultiTokenPattern pattern) 113 + { 114 + _pattern = pattern; 115 + return this; 116 + } 117 + 118 + /// <summary> 119 + /// Sets the token content which will be patched in for the TransformationRule. 120 + /// </summary> 121 + /// <param name="tokens"></param> 122 + /// <returns></returns> 123 + public TransformationRuleBuilder With(IEnumerable<Token> tokens) 124 + { 125 + _tokens = tokens; 126 + return this; 127 + } 128 + 129 + /// <summary> 130 + /// Sets the token content which will be patched in for the TransformationRule. 131 + /// </summary> 132 + /// <param name="token"></param> 133 + /// <returns></returns> 134 + public TransformationRuleBuilder With(Token token) 135 + { 136 + _tokens = [token]; 137 + return this; 138 + } 139 + 140 + /// <summary> 141 + /// Sets the token content which will be patched in for the TransformationRule with a GDScript snippet. 142 + /// </summary> 143 + /// <param name="snippet"></param> 144 + /// <param name="indent">The base indentation level for the tokenizer.</param> 145 + /// <returns></returns> 146 + public TransformationRuleBuilder With(string snippet, uint indent = 0) 147 + { 148 + _tokens = ScriptTokenizer.Tokenize(snippet, indent); 149 + return this; 150 + } 151 + 152 + /// <summary> 153 + /// Sets the <see cref="Operation"/> of the TransformationRule. 154 + /// </summary> 155 + /// <param name="operation"></param> 156 + /// <returns></returns> 157 + public TransformationRuleBuilder Do(Operation operation) 158 + { 159 + _operation = operation; 160 + return this; 161 + } 162 + 163 + /// <summary> 164 + /// Sets the number of times the rule is expected to match. 165 + /// </summary> 166 + /// <param name="times"></param> 167 + /// <returns></returns> 168 + public TransformationRuleBuilder ExpectTimes(uint times) 169 + { 170 + _times = times; 171 + return this; 172 + } 173 + 174 + /// <summary> 175 + /// Sets the scope of this rule. 176 + /// </summary> 177 + /// <param name="scopePattern"></param> 178 + /// <returns></returns> 179 + public TransformationRuleBuilder ScopedTo(MultiTokenPattern scopePattern) 180 + { 181 + _scopePattern = scopePattern; 182 + return this; 183 + } 184 + 185 + /// <summary> 186 + /// Sets the predicate function whose return value will decide if this rule will be checked. 187 + /// </summary> 188 + /// <param name="predicate"></param> 189 + /// <returns></returns> 190 + public TransformationRuleBuilder When(Func<bool> predicate) 191 + { 192 + _predicate = predicate; 193 + return this; 194 + } 195 + 196 + /// <summary> 197 + /// Sets a value which will decide if this rule will be checked. 198 + /// </summary> 199 + /// <param name="eligible">If true, this rule will be checked.</param> 200 + /// <returns></returns> 201 + public TransformationRuleBuilder When(bool eligible) 202 + { 203 + _predicate = () => eligible; 204 + return this; 205 + } 206 + 207 + /// <summary> 208 + /// Builds the TransformationRule. 209 + /// </summary> 210 + /// <returns></returns> 211 + /// <exception cref="ArgumentNullException">Thrown if any required fields were not set.</exception> 212 + public TransformationRule Build() 213 + { 214 + if (string.IsNullOrEmpty(_name)) 215 + { 216 + throw new ArgumentNullException(nameof(_name), "Name cannot be null or empty"); 217 + } 218 + 219 + if (_pattern == null) 220 + { 221 + throw new ArgumentNullException(nameof(_pattern), "Pattern cannot be null"); 222 + } 223 + 224 + if (_tokens == null) 225 + { 226 + throw new ArgumentNullException(nameof(_tokens), "Tokens cannot be null"); 227 + } 228 + 229 + return new TransformationRule(_name, _pattern, _scopePattern, _tokens, _operation, _times, _predicate); 230 + } 231 + }
+179
Atproto/CalicoUtils/LexicalTransformer/TransformationRuleScriptMod.cs
··· 1 + using GDWeave; 2 + using GDWeave.Godot; 3 + using GDWeave.Modding; 4 + using static Teemaw.Calico.LexicalTransformer.Operation; 5 + 6 + namespace Teemaw.Calico.LexicalTransformer; 7 + 8 + /// <summary> 9 + /// An IScriptMod implementation that handles patching through the provided list of TransformationRules. 10 + /// </summary> 11 + /// <param name="mod">IModInterface of the current mod.</param> 12 + /// <param name="name">The name of this script mod. Used for logging.</param> 13 + /// <param name="scriptPath">The GD res:// path of the script which will be patched.</param> 14 + /// <param name="rules">A list of patches to perform. Multiple descriptors with overlapping checks is not supported.</param> 15 + public class TransformationRuleScriptMod( 16 + IModInterface mod, 17 + string name, 18 + string scriptPath, 19 + Func<bool> predicate, 20 + TransformationRule[] rules) 21 + : IScriptMod 22 + { 23 + public bool ShouldRun(string path) 24 + { 25 + if (path != scriptPath) return false; 26 + if (!predicate.Invoke()) 27 + { 28 + mod.Logger.Information($"[{name}] Predicate failed, not patching."); 29 + return false; 30 + } 31 + 32 + return true; 33 + } 34 + 35 + public IEnumerable<Token> Modify(string path, IEnumerable<Token> tokens) 36 + { 37 + var eligibleRules = rules.Where(rule => 38 + { 39 + var eligible = rule.Predicate(); 40 + if (!eligible) 41 + { 42 + mod.Logger.Information($"[{name}] Skipping patch {rule.Name}..."); 43 + } 44 + 45 + return eligible; 46 + }).ToList(); 47 + var transformers = eligibleRules.Select(rule => 48 + (Rule: rule, Waiter: rule.CreateMultiTokenWaiter(), Buffer: new List<Token>())) 49 + .ToList(); 50 + mod.Logger.Information($"[{name}] Patching {path}"); 51 + 52 + var patchOccurrences = 53 + eligibleRules.ToDictionary(r => r.Name, r => (Occurred: 0, Expected: r.Times)); 54 + var bufferAfterChecks = true; 55 + var stagingBuffer = new List<Token>(); 56 + stagingBuffer.AddRange(tokens); 57 + var transformedBuffer = new List<Token>(); 58 + 59 + foreach (var transformer in transformers) 60 + { 61 + var hasScopePattern = transformer.Rule.ScopePattern.Length > 0; 62 + var inScope = !hasScopePattern; 63 + uint? scopeIndent = null; 64 + var scopeWaiter = transformer.Rule.CreateMultiTokenWaiterForScope(); 65 + 66 + foreach (var token in stagingBuffer) 67 + { 68 + if (inScope && hasScopePattern) 69 + { 70 + // Try to find the scope's base indentation 71 + if (scopeIndent == null && token.Type == TokenType.Newline) 72 + { 73 + scopeIndent = token.AssociatedData ?? 0; 74 + } 75 + // Check if we should leave the scope 76 + else if (scopeIndent != null && token.Type == TokenType.Newline) 77 + { 78 + inScope = (token.AssociatedData ?? 0) >= scopeIndent; 79 + } 80 + } 81 + else if (hasScopePattern) 82 + { 83 + // We should never reach this case if the scope pattern has Length = 0. 84 + if (scopeWaiter.Check(token)) 85 + { 86 + scopeWaiter.Reset(); 87 + // Latch inScope to true if we match the scope pattern. 88 + inScope = true; 89 + } 90 + } 91 + 92 + if (!inScope) 93 + { 94 + transformedBuffer.Add(token); 95 + continue; 96 + } 97 + 98 + transformer.Waiter.Check(token); 99 + if (transformer.Waiter.Step == 0) 100 + { 101 + transformedBuffer.AddRange(transformer.Buffer); 102 + transformer.Buffer.Clear(); 103 + } 104 + else 105 + { 106 + if (transformer.Rule.Operation.RequiresBuffer()) 107 + { 108 + transformer.Buffer.Add(token); 109 + bufferAfterChecks = false; 110 + } 111 + 112 + if (transformer.Waiter.Matched) 113 + { 114 + transformer.Waiter.Reset(); 115 + 116 + if (transformer.Rule.Operation.YieldTokenBeforeOperation()) 117 + { 118 + transformedBuffer.Add(token); 119 + bufferAfterChecks = false; 120 + } 121 + else 122 + { 123 + bufferAfterChecks = 124 + transformer.Rule.Operation.YieldTokenAfterOperation(); 125 + } 126 + 127 + switch (transformer.Rule.Operation) 128 + { 129 + case Prepend: 130 + transformedBuffer.AddRange(transformer.Rule.Tokens); 131 + transformedBuffer.AddRange(transformer.Buffer); 132 + transformer.Buffer.Clear(); 133 + break; 134 + case ReplaceLast: 135 + case Append: 136 + case ReplaceAll: 137 + transformer.Buffer.Clear(); 138 + transformedBuffer.AddRange(transformer.Rule.Tokens); 139 + break; 140 + case None: 141 + default: 142 + break; 143 + } 144 + 145 + mod.Logger.Information( 146 + $"[{name}] Patch {transformer.Rule.Name} OK!"); 147 + patchOccurrences[transformer.Rule.Name] = 148 + patchOccurrences[transformer.Rule.Name] with 149 + { 150 + Occurred = patchOccurrences[transformer.Rule.Name].Occurred + 1 151 + }; 152 + } 153 + } 154 + 155 + if (bufferAfterChecks) 156 + { 157 + transformedBuffer.Add(token); 158 + } 159 + else 160 + { 161 + bufferAfterChecks = true; 162 + } 163 + } 164 + 165 + stagingBuffer.Clear(); 166 + stagingBuffer.AddRange(transformedBuffer); 167 + transformedBuffer.Clear(); 168 + } 169 + 170 + foreach (var result in patchOccurrences.Where(result => 171 + result.Value.Occurred != result.Value.Expected)) 172 + { 173 + mod.Logger.Error( 174 + $"[{name}] Patch {result.Key} FAILED! Times expected={result.Value.Expected}, actual={result.Value.Occurred}"); 175 + } 176 + 177 + return stagingBuffer; 178 + } 179 + }
+112
Atproto/CalicoUtils/LexicalTransformer/TransformationRuleScriptModBuilder.cs
··· 1 + using GDWeave; 2 + 3 + namespace Teemaw.Calico.LexicalTransformer; 4 + 5 + public class TransformationRuleScriptModBuilder 6 + { 7 + private IModInterface? _mod; 8 + private string? _name; 9 + private string? _scriptPath; 10 + private Func<bool> _predicate = () => true; 11 + private List<TransformationRule> _rules = []; 12 + 13 + /// <summary> 14 + /// Sets the IModInterface to be used by the ScriptMod. 15 + /// </summary> 16 + /// <param name="mod"></param> 17 + /// <returns></returns> 18 + public TransformationRuleScriptModBuilder ForMod(IModInterface mod) 19 + { 20 + _mod = mod; 21 + return this; 22 + } 23 + 24 + /// <summary> 25 + /// Sets the predicate which must return true for this ScriptMod to run. Config options MUST be 26 + /// evaluated at patch-time since the environment is not fully known at load-time. Currently, 27 + /// this affects mod conflict detection, but there may be other affected features in the future. 28 + /// </summary> 29 + /// <param name="predicate"></param> 30 + /// <returns></returns> 31 + public TransformationRuleScriptModBuilder When(Func<bool> predicate) 32 + { 33 + _predicate = predicate; 34 + return this; 35 + } 36 + 37 + /// <summary> 38 + /// Sets the name of the ScriptMod. Used for logging. 39 + /// </summary> 40 + /// <param name="name"></param> 41 + /// <returns></returns> 42 + public TransformationRuleScriptModBuilder Named(string name) 43 + { 44 + _name = name; 45 + return this; 46 + } 47 + 48 + /// <summary> 49 + /// Sets the Godot resource path of the script to be patched. 50 + /// </summary> 51 + /// <param name="scriptPath"></param> 52 + /// <returns></returns> 53 + public TransformationRuleScriptModBuilder Patching(string scriptPath) 54 + { 55 + _scriptPath = scriptPath; 56 + return this; 57 + } 58 + 59 + /// <summary> 60 + /// Adds a TransformationRule to the TransformationRuleScriptMod. 61 + /// </summary> 62 + /// <param name="rule"></param> 63 + /// <returns></returns> 64 + public TransformationRuleScriptModBuilder AddRule(TransformationRule rule) 65 + { 66 + if (_rules.Select(r => r.Name).Contains(rule.Name)) 67 + { 68 + throw new InvalidOperationException( 69 + $"Another rule with the name '{rule.Name}' already exists!"); 70 + } 71 + 72 + _rules.Add(rule); 73 + return this; 74 + } 75 + 76 + /// <summary> 77 + /// Adds the TransformationRule built by calling Build() on the provided builder to the TransformationRuleScriptMod. 78 + /// </summary> 79 + /// <param name="rule"></param> 80 + /// <returns></returns> 81 + public TransformationRuleScriptModBuilder AddRule(TransformationRuleBuilder rule) 82 + { 83 + return AddRule(rule.Build()); 84 + } 85 + 86 + /// <summary> 87 + /// Build the TransformationRuleScriptMod. 88 + /// </summary> 89 + /// <returns></returns> 90 + /// <exception cref="ArgumentNullException">Thrown if any required fields were not set.</exception> 91 + public TransformationRuleScriptMod Build() 92 + { 93 + if (_mod == null) 94 + { 95 + throw new ArgumentNullException(nameof(_mod), "Mod cannot be null"); 96 + } 97 + 98 + if (string.IsNullOrEmpty(_name)) 99 + { 100 + throw new ArgumentNullException(nameof(_name), "Name cannot be null or empty"); 101 + } 102 + 103 + if (string.IsNullOrEmpty(_scriptPath)) 104 + { 105 + throw new ArgumentNullException(nameof(_scriptPath), 106 + "Script path cannot be null or empty"); 107 + } 108 + 109 + return new TransformationRuleScriptMod(_mod, _name, _scriptPath, _predicate, 110 + _rules.ToArray()); 111 + } 112 + }
+507
Atproto/CalicoUtils/Util/ScriptTokenizer.cs
··· 1 + // MIT License 2 + // 3 + // Copyright (c) 2024 NotNite 4 + // 5 + // Permission is hereby granted, free of charge, to any person obtaining a copy 6 + // of this software and associated documentation files (the "Software"), to deal 7 + // in the Software without restriction, including without limitation the rights 8 + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + // copies of the Software, and to permit persons to whom the Software is 10 + // furnished to do so, subject to the following conditions: 11 + // 12 + // The above copyright notice and this permission notice shall be included in all 13 + // copies or substantial portions of the Software. 14 + // 15 + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + // SOFTWARE. 22 + 23 + using System.Text; 24 + using GDWeave.Godot; 25 + using GDWeave.Godot.Variants; 26 + 27 + namespace Teemaw.Calico.Util; 28 + 29 + /** 30 + * This is copied from https://github.com/NotNite/GDWeave/blob/main/GDWeave/Script/ScriptTokenizer.cs since this class 31 + * was not made visible. Minor modifications have been made to make it more ergonomic to tokenize snippets. 32 + */ 33 + public static class ScriptTokenizer { 34 + private static readonly Dictionary<string, TokenType> Tokens = new() { 35 + {"continue", TokenType.CfContinue}, 36 + {"return", TokenType.CfReturn}, 37 + {"break", TokenType.CfBreak}, 38 + {"match", TokenType.CfMatch}, 39 + {"while", TokenType.CfWhile}, 40 + {"elif", TokenType.CfElif}, 41 + {"else", TokenType.CfElse}, 42 + {"pass", TokenType.CfPass}, 43 + {"for", TokenType.CfFor}, 44 + {"if", TokenType.CfIf}, 45 + {"const", TokenType.PrConst}, 46 + {"var", TokenType.PrVar}, 47 + {"func", TokenType.PrFunction}, 48 + {"class", TokenType.PrClass}, 49 + {"extends", TokenType.PrExtends}, 50 + {"is", TokenType.PrIs}, 51 + {"as", TokenType.PrAs}, 52 + {"@onready", TokenType.PrOnready}, 53 + {"@tool", TokenType.PrTool}, 54 + {"@export", TokenType.PrExport}, 55 + // CALICO: 56 + {"yield", TokenType.PrYield}, 57 + 58 + {"setget", TokenType.PrSetget}, 59 + {"static", TokenType.PrStatic}, 60 + 61 + {"void", TokenType.PrVoid}, 62 + {"enum", TokenType.PrEnum}, 63 + {"preload", TokenType.PrPreload}, 64 + {"assert", TokenType.PrAssert}, 65 + 66 + {"signal", TokenType.PrSignal}, 67 + {"breakpoint", TokenType.PrBreakpoint}, 68 + 69 + {"sync", TokenType.PrSync}, 70 + {"remote", TokenType.PrRemote}, 71 + {"master", TokenType.PrMaster}, 72 + {"slave", TokenType.PrSlave}, 73 + {"puppet", TokenType.PrPuppet}, 74 + 75 + {"remotesync", TokenType.PrRemotesync}, 76 + {"mastersync", TokenType.PrMastersync}, 77 + {"puppetsync", TokenType.PrPuppetsync}, 78 + 79 + {"\n", TokenType.Newline}, 80 + 81 + {"PI", TokenType.ConstPi}, 82 + {"TAU", TokenType.ConstTau}, 83 + {"INF", TokenType.ConstInf}, 84 + {"NAN", TokenType.ConstNan}, 85 + 86 + {"error", TokenType.Error}, 87 + {"cursor", TokenType.Cursor}, 88 + 89 + {"self", TokenType.Self}, 90 + 91 + {"in", TokenType.OpIn}, 92 + 93 + {"_", TokenType.Wildcard}, 94 + 95 + {"[", TokenType.BracketOpen}, 96 + {"]", TokenType.BracketClose}, 97 + {"{", TokenType.CurlyBracketOpen}, 98 + {"}", TokenType.CurlyBracketClose}, 99 + 100 + {"(", TokenType.ParenthesisOpen}, 101 + {")", TokenType.ParenthesisClose}, 102 + 103 + {",", TokenType.Comma}, 104 + {";", TokenType.Semicolon}, 105 + {".", TokenType.Period}, 106 + {"?", TokenType.QuestionMark}, 107 + {":", TokenType.Colon}, 108 + {"$", TokenType.Dollar}, 109 + {"->", TokenType.ForwardArrow}, 110 + 111 + {">>=", TokenType.OpAssignShiftRight}, 112 + {"<<=", TokenType.OpAssignShiftLeft}, 113 + 114 + {">>", TokenType.OpShiftRight}, 115 + {"<<", TokenType.OpShiftLeft}, 116 + 117 + {"==", TokenType.OpEqual}, 118 + {"!=", TokenType.OpNotEqual}, 119 + {"&&", TokenType.OpAnd}, 120 + {"||", TokenType.OpOr}, 121 + {"!", TokenType.OpNot}, 122 + 123 + {"+=", TokenType.OpAssignAdd}, 124 + {"-=", TokenType.OpAssignSub}, 125 + {"*=", TokenType.OpAssignMul}, 126 + {"/=", TokenType.OpAssignDiv}, 127 + {"%=", TokenType.OpAssignMod}, 128 + {"&=", TokenType.OpAssignBitAnd}, 129 + {"|=", TokenType.OpAssignBitOr}, 130 + {"^=", TokenType.OpAssignBitXor}, 131 + 132 + {"+", TokenType.OpAdd}, 133 + {"-", TokenType.OpSub}, 134 + {"*", TokenType.OpMul}, 135 + {"/", TokenType.OpDiv}, 136 + {"%", TokenType.OpMod}, 137 + 138 + {"~", TokenType.OpBitInvert}, 139 + {"&", TokenType.OpBitAnd}, 140 + {"|", TokenType.OpBitOr}, 141 + {"^", TokenType.OpBitXor}, 142 + 143 + {"<=", TokenType.OpLessEqual}, 144 + {">=", TokenType.OpGreaterEqual}, 145 + {"<", TokenType.OpLess}, 146 + {">", TokenType.OpGreater}, 147 + 148 + {"=", TokenType.OpAssign}, 149 + }; 150 + 151 + private static readonly HashSet<string> Symbols = new() { 152 + "->", 153 + 154 + ">>=", 155 + "<<=", 156 + 157 + ">>", 158 + "<<", 159 + 160 + "==", 161 + "!=", 162 + "&&", 163 + "||", 164 + "!", 165 + 166 + "+=", 167 + "-=", 168 + "*=", 169 + "/=", 170 + "%=", 171 + "&=", 172 + "|=", 173 + "^=", 174 + 175 + "_", 176 + 177 + "[", 178 + "]", 179 + 180 + "{", 181 + "}", 182 + 183 + "(", 184 + ")", 185 + 186 + ",", 187 + ";", 188 + ".", 189 + "?", 190 + ":", 191 + "$", 192 + "+", 193 + "-", 194 + "*", 195 + "/", 196 + "%", 197 + 198 + "~", 199 + "&", 200 + "|", 201 + "^", 202 + 203 + "<=", 204 + ">=", 205 + "<", 206 + ">", 207 + 208 + "=", 209 + }; 210 + 211 + private static readonly List<string> BuiltinFunctions = Enum.GetNames<BuiltinFunction>().ToList(); 212 + 213 + private static void InsertNewLine(IEnumerator<string> enumerator, uint baseIndent, List<Token> toFlush) { 214 + if (!enumerator.MoveNext()) { 215 + return; 216 + } 217 + 218 + var tabCount = uint.Parse(enumerator.Current); 219 + toFlush.Add(new Token(TokenType.Newline, tabCount + baseIndent)); 220 + } 221 + 222 + private static void BuildIdentifierName(IEnumerator<string> enumerator, List<Token> toFlush, out string? found) { 223 + found = string.Empty; 224 + if (!enumerator.MoveNext()) { 225 + return; 226 + } 227 + 228 + if (enumerator.Current == ":") { 229 + toFlush.Add(new Token(TokenType.Wildcard)); 230 + toFlush.Add(new Token(TokenType.Semicolon)); 231 + return; 232 + } 233 + 234 + found = "_" + enumerator.Current; 235 + } 236 + 237 + private static void BuildNumber(IEnumerator<string> enumerator, List<Token> toFlush, out bool foundFull) { 238 + foundFull = true; 239 + int sign = 1; 240 + 241 + if (enumerator.Current == "-") { 242 + sign = -1; 243 + if (!enumerator.MoveNext()) return; 244 + } 245 + 246 + if (!long.TryParse(enumerator.Current, out long upper)) { 247 + toFlush.Add(new Token(TokenType.OpSub)); 248 + foundFull = false; 249 + return; 250 + } 251 + 252 + if (!enumerator.MoveNext()) return; 253 + 254 + if (enumerator.Current != ".") { 255 + toFlush.Add(new ConstantToken(new IntVariant(upper * sign))); 256 + foundFull = false; 257 + return; 258 + } 259 + 260 + if (!enumerator.MoveNext()) return; 261 + 262 + if (!long.TryParse(enumerator.Current, out long lower)) { 263 + // I dont think there is really a proper return for here. 264 + // You'd have a number that looks like this "1000." 265 + // No following decimal 266 + // Comment if you had ideas 267 + return; 268 + } 269 + 270 + var result = upper + (lower / Math.Pow(10, lower.ToString().Length)); 271 + toFlush.Add(new ConstantToken(new RealVariant(result * sign))); 272 + } 273 + 274 + public static IEnumerable<Token> Tokenize(string gdScript, uint baseIndent = 0) { 275 + var finalTokens = new List<Token>(); 276 + var tokens = SanitizeInput(TokenizeString(gdScript + " ")); 277 + 278 + var previous = string.Empty; 279 + var idName = string.Empty; 280 + 281 + var toFlush = new List<Token>(2); 282 + // CALICO: We don't need this since we're dealing with snippets 283 + //finalTokens.Add(new Token(TokenType.Newline, baseIndent)); 284 + var enumerator = tokens.GetEnumerator(); 285 + var reparse = false; 286 + while (reparse ? true : enumerator.MoveNext()) { 287 + reparse = false; 288 + 289 + if (enumerator.Current == "\n") { 290 + InsertNewLine(enumerator, baseIndent, toFlush); 291 + endAndFlushId(); 292 + continue; 293 + } 294 + 295 + if (enumerator.Current == "_") { 296 + BuildIdentifierName(enumerator, toFlush, out string? found); 297 + if (found == string.Empty) { 298 + endAndFlushId(); 299 + continue; 300 + } 301 + 302 + idName += found; 303 + 304 + end(); 305 + continue; 306 + } 307 + 308 + //if (enumerator.Current == "-" || char.IsDigit(enumerator.Current[0])) { 309 + if (char.IsDigit(enumerator.Current[0])) { 310 + BuildNumber(enumerator, toFlush, out bool foundFull); 311 + reparse = !foundFull; 312 + endAndFlushId(); 313 + continue; 314 + } 315 + 316 + if (BuiltinFunctions.Contains(enumerator.Current)) { 317 + toFlush.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunctions.IndexOf(enumerator.Current))); 318 + endAndFlushId(); 319 + continue; 320 + } 321 + 322 + if (Tokens.TryGetValue(enumerator.Current, out var type)) { 323 + toFlush.Add(new Token(type)); 324 + endAndFlushId(); 325 + continue; 326 + } 327 + 328 + if (enumerator.Current.StartsWith('"')) { 329 + var current = enumerator.Current; 330 + toFlush.Add(new ConstantToken(new StringVariant(current.Substring(1, current.Length - 2)))); 331 + endAndFlushId(); 332 + continue; 333 + } 334 + 335 + if (bool.TryParse(enumerator.Current, out var resultB)) { 336 + toFlush.Add(new ConstantToken(new BoolVariant(resultB))); 337 + endAndFlushId(); 338 + continue; 339 + } 340 + 341 + idName += enumerator.Current; 342 + 343 + end(); 344 + 345 + void end() { 346 + previous = enumerator.Current; 347 + finalTokens.AddRange(toFlush); 348 + toFlush.Clear(); 349 + } 350 + 351 + void endAndFlushId() { 352 + if (idName != string.Empty) { 353 + if (idName.Trim() == "return") 354 + { 355 + // CALICO: Hack to handle `return` being the last token of a line 356 + finalTokens.Add(new Token(TokenType.CfReturn)); 357 + } 358 + else if (idName.Trim() == "self") 359 + { 360 + // CALICO: Hack to handle `self` being the last token of a line 361 + finalTokens.Add(new Token(TokenType.Self)); 362 + } 363 + else switch (idName.Trim()) 364 + { 365 + // TODO: CALICO: clean up this hack 366 + case "print": 367 + // CALICO: Without this, `print` is tokenized as an identifier. 368 + finalTokens.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunction.TextPrint)); 369 + break; 370 + case "min": 371 + // CALICO: Without this, `min` is tokenized as an identifier. 372 + finalTokens.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunction.LogicMin)); 373 + break; 374 + case "null": 375 + // CALICO: Without this, `null` is tokenized as an identifier. 376 + finalTokens.Add(new ConstantToken(new NilVariant())); 377 + break; 378 + case "break": 379 + // CALICO: Without this, `break` is tokenized as an identifier. 380 + finalTokens.Add(new Token(TokenType.CfBreak)); 381 + break; 382 + case "match": 383 + // CALICO: Without this, `match` is tokenized as an identifier. 384 + finalTokens.Add(new Token(TokenType.CfMatch)); 385 + break; 386 + case "Color": 387 + // CALICO: Without this, `Color` is tokenized as an identifier. 388 + finalTokens.Add(new Token(TokenType.BuiltInType, 14)); 389 + break; 390 + case "Vector3": 391 + // CALICO: Without this, `Vector3` is tokenized as an identifier. 392 + finalTokens.Add(new Token(TokenType.BuiltInType, 7)); 393 + break; 394 + case "lerp_angle": 395 + // CALICO: Without this, `lerp_angle` is tokenized as an identifier. 396 + finalTokens.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunction.MathLerpAngle)); 397 + break; 398 + case "int": 399 + // CALICO: https://docs.godotengine.org/en/3.5/tutorials/io/binary_serialization_api.html 400 + finalTokens.Add(new Token(TokenType.BuiltInType, 2)); 401 + break; 402 + case "pow": 403 + // CALICO: 404 + finalTokens.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunction.MathPow)); 405 + break; 406 + case "abs": 407 + // CALICO: 408 + finalTokens.Add(new Token(TokenType.BuiltInFunc, (uint?) BuiltinFunction.MathAbs)); 409 + break; 410 + default: 411 + // CALICO: We change this to trim the idName, otherwise the whitespace messes with the token 412 + finalTokens.Add(new IdentifierToken(idName.Trim())); 413 + break; 414 + } 415 + idName = string.Empty; 416 + } 417 + 418 + end(); 419 + } 420 + } 421 + 422 + // CALICO: We don't need this since we're dealing with snippets 423 + //finalTokens.Add(new(TokenType.Newline, baseIndent)); 424 + 425 + foreach (var t in finalTokens) yield return t; 426 + } 427 + 428 + private static IEnumerable<string> SanitizeInput(IEnumerable<string> tokens) { 429 + foreach (var token in tokens) { 430 + if (token != "\n" && string.IsNullOrWhiteSpace(token)) { 431 + continue; 432 + } 433 + 434 + yield return token; 435 + } 436 + } 437 + 438 + private static IEnumerable<string> TokenizeString(string text) { 439 + StringBuilder builder = new(20); 440 + for (var i = 0; i < text.Length; i++) { 441 + switch (text[i]) { 442 + case '"': { 443 + yield return ClearBuilder(); 444 + builder.Append('"'); 445 + i++; 446 + for (; i < text.Length; i++) { 447 + builder.Append(text[i]); 448 + if (text[i] == '"') { 449 + break; 450 + } 451 + } 452 + 453 + yield return ClearBuilder(); 454 + continue; 455 + } 456 + 457 + // This is stupid and awful 458 + case '\n': { 459 + yield return ClearBuilder(); 460 + var start = i; 461 + i++; 462 + for (; i < text.Length && text[i] == '\t'; i++) ; 463 + i--; 464 + yield return "\n"; 465 + yield return $"{i - start}"; 466 + continue; 467 + } 468 + } 469 + 470 + var matched = false; 471 + foreach (var delimiter in Symbols) { 472 + if (Match(text, i, delimiter)) { 473 + yield return ClearBuilder(); 474 + yield return delimiter; 475 + i += delimiter.Length - 1; 476 + matched = true; 477 + break; 478 + } 479 + } 480 + 481 + if (matched) continue; 482 + 483 + if (text[i] == ' ') { 484 + yield return ClearBuilder(); 485 + continue; 486 + } 487 + 488 + builder.Append(text[i]); 489 + } 490 + 491 + yield return "\n"; 492 + 493 + string ClearBuilder() { 494 + var built = builder.ToString(); 495 + builder.Clear(); 496 + return built; 497 + } 498 + } 499 + 500 + private static bool Match(string text, int index, string match) { 501 + if (index + match.Length > text.Length) return false; 502 + for (var i = 0; i < match.Length; i++) { 503 + if (text[index + i] != match[i]) return false; 504 + } 505 + return true; 506 + } 507 + }
+131
Atproto/CalicoUtils/Util/TokenUtil.cs
··· 1 + using GDWeave.Godot; 2 + using GDWeave.Godot.Variants; 3 + using static GDWeave.Godot.TokenType; 4 + 5 + namespace Teemaw.Calico.Util; 6 + 7 + public static class TokenUtil 8 + { 9 + public static IEnumerable<Token> ReplaceAssignmentsAsDeferred(IEnumerable<Token> tokens, 10 + HashSet<string>? ignoredIdentifiers = null) 11 + { 12 + Token? lastToken = null; 13 + var inAssignmentStatement = false; 14 + var skipLine = false; 15 + foreach (var t in tokens) 16 + { 17 + switch (t) 18 + { 19 + case { Type: Newline }: 20 + skipLine = false; 21 + break; 22 + case { Type: PrVar }: 23 + skipLine = true; 24 + break; 25 + default: 26 + { 27 + if (lastToken is IdentifierToken identifier && 28 + (ignoredIdentifiers == null || !ignoredIdentifiers.Contains(identifier.Name)) && 29 + t is { Type: OpAssign } && !skipLine) 30 + { 31 + inAssignmentStatement = true; 32 + yield return new IdentifierToken("set_deferred"); 33 + yield return new Token(ParenthesisOpen); 34 + yield return new ConstantToken(new StringVariant(identifier.Name)); 35 + yield return new Token(Comma); 36 + // Don't return the last token or current token 37 + lastToken = null; 38 + continue; 39 + } 40 + 41 + break; 42 + } 43 + } 44 + 45 + if (t.Type == Newline && inAssignmentStatement) 46 + { 47 + inAssignmentStatement = false; 48 + if (lastToken != null) 49 + yield return lastToken; 50 + // Close out the set_deferred call... 51 + yield return new Token(ParenthesisClose); 52 + // ...before the Newline token. 53 + yield return t; 54 + } 55 + else if (lastToken != null) 56 + yield return lastToken; 57 + 58 + lastToken = inAssignmentStatement && t is { Type: Identifier } ? StripAssociatedData(t) : t; 59 + } 60 + 61 + if (lastToken != null) 62 + yield return lastToken; 63 + } 64 + 65 + public static Token ReplaceToken(Token cursor, Token target, Token replacement) 66 + { 67 + return TokenEquals(cursor, target) ? replacement : cursor; 68 + } 69 + 70 + public static IEnumerable<Token> ReplaceTokens(IEnumerable<Token> haystack, IEnumerable<Token> needle, 71 + IEnumerable<Token> replacements) 72 + { 73 + var haystackList = haystack.ToList(); 74 + var needleList = needle.ToList(); 75 + var replacementsList = replacements.ToList(); 76 + 77 + if (needleList.Count == 0) return haystackList; 78 + 79 + var result = new List<Token>(); 80 + var i = 0; 81 + 82 + while (i < haystackList.Count) 83 + { 84 + if (IsMatch(haystackList, needleList, i)) 85 + { 86 + result.AddRange(replacementsList); 87 + i += needleList.Count; 88 + } 89 + else 90 + { 91 + result.Add(haystackList[i]); 92 + i++; 93 + } 94 + } 95 + 96 + return result; 97 + } 98 + 99 + private static bool IsMatch(List<Token> haystack, List<Token> needle, int startIndex) 100 + { 101 + if (startIndex + needle.Count > haystack.Count) 102 + return false; 103 + 104 + for (int i = 0; i < needle.Count; i++) 105 + { 106 + if (!TokenEquals(haystack[startIndex + i], needle[i])) 107 + return false; 108 + } 109 + 110 + return true; 111 + } 112 + 113 + private static bool TokenEquals(Token token, Token token1) 114 + { 115 + if (token is IdentifierToken id && token1 is IdentifierToken id1) 116 + { 117 + return id.Name == id1.Name; 118 + } 119 + if (token is ConstantToken constant && token1 is ConstantToken constant1) 120 + { 121 + return constant.Value.Equals(constant1.Value); 122 + } 123 + return token.Type == token1.Type && token.AssociatedData == token1.AssociatedData; 124 + } 125 + 126 + private static Token StripAssociatedData(Token token) 127 + { 128 + token.AssociatedData = null; 129 + return token; 130 + } 131 + }
+9
Atproto/CalicoUtils/Util/WeaveUtil.cs
··· 1 + using GDWeave; 2 + 3 + namespace Teemaw.Calico.Util; 4 + 5 + public static class WeaveUtil 6 + { 7 + public static bool IsModLoaded(IModInterface modInterface, string modName) => 8 + modInterface.LoadedMods.Contains(modName); 9 + }
+22
Atproto/CatchFishFactory.cs
··· 1 + using GDWeave; 2 + using GDWeave.Modding; 3 + using Teemaw.Calico.LexicalTransformer; 4 + 5 + namespace Atproto; 6 + 7 + public static class CatchFishFactory 8 + { 9 + public static IScriptMod Create(IModInterface mod) 10 + { 11 + return new TransformationRuleScriptModBuilder() 12 + .ForMod(mod) 13 + .Named("CatchFish") 14 + .Patching("res://Scenes/Entities/Player/player.gdc") 15 + .AddRule(new TransformationRuleBuilder() 16 + .Named("create_fish_record") 17 + .Matching(TransformationPatternFactory.CreateGdSnippetPattern( 18 + "PlayerData._log_item(fish_roll, size, quality)\n", 3)) 19 + .Do(Operation.Append) 20 + .With("$\"/root/Atproto\".AtProtoClient.catch_fish(fish_roll, size, quality)\n", 3)).Build(); 21 + } 22 + }
+22
Atproto/CatptureFishFactory.cs
··· 1 + using GDWeave; 2 + using GDWeave.Modding; 3 + using Teemaw.Calico.LexicalTransformer; 4 + 5 + namespace Atproto; 6 + 7 + public static class CatptureFishFactory 8 + { 9 + public static IScriptMod Create(IModInterface mod) 10 + { 11 + return new TransformationRuleScriptModBuilder() 12 + .ForMod(mod) 13 + .Named("CaptureFish") 14 + .Patching("res://Scenes/Entities/Props/fish_trap.gdc") 15 + .AddRule(new TransformationRuleBuilder() 16 + .Named("create_fish_record") 17 + .Matching(TransformationPatternFactory.CreateGdSnippetPattern( 18 + "PlayerData._log_item(fish_roll, size, quality)\n", 1)) 19 + .Do(Operation.Append) 20 + .With("$\"/root/Atproto\".AtProtoClient.catch_fish(fish_roll, size, quality)\n", 1)).Build(); 21 + } 22 + }
+8
Atproto/Config.cs
··· 1 + using System.Text.Json.Serialization; 2 + 3 + namespace Atproto; 4 + 5 + public class Config { 6 + [JsonInclude] public string Handle = ""; 7 + [JsonInclude] public string Password = ""; 8 + }
+17
Atproto/Mod.cs
··· 1 + using GDWeave; 2 + 3 + namespace Atproto; 4 + 5 + public class Mod : IMod { 6 + public Config Config; 7 + 8 + public Mod(IModInterface modInterface) { 9 + Config = modInterface.ReadConfig<Config>(); 10 + modInterface.RegisterScriptMod(CatchFishFactory.Create(modInterface)); 11 + modInterface.RegisterScriptMod(CatptureFishFactory.Create(modInterface)); 12 + } 13 + 14 + public void Dispose() { 15 + // Cleanup anything you do here 16 + } 17 + }
+14
Atproto/manifest.json
··· 1 + { 2 + "Id": "Atproto", 3 + "AssemblyPath": "Atproto.dll", 4 + "Metadata": { 5 + "Name": "Atproto Webfishing", 6 + "Author": "Estym", 7 + "Version": "1.0.0", 8 + "Description": "This is a sample mod." 9 + }, 10 + "PackPath": "atproto.pck", 11 + "Dependencies": [ 12 + "TackleBox" 13 + ] 14 + }
+15
README.md
··· 1 + # ATProto Webfishing 2 + 3 + A Webfishing mod that send data to your PDS 4 + 5 + ## Requirements 6 + - [GDWeave](https://thunderstore.io/c/webfishing/p/NotNet/GDWeave/) 7 + - [TackleBox](https://thunderstore.io/c/webfishing/p/PuppyGirl/TackleBox/) 8 + 9 + ## Configuration 10 + In game using the TackleBox mod menu, you can input your AtProto Handle as well as an App Password to login 11 + After it's done, simply save and quit the game, you'll be connected on the next startup 12 + 13 + ## Credits 14 + 15 + - Thanks to [Tiany Ma](https://github.com/tma02) for its utils
-1
gdscript/.import/.gdignore
··· 1 -
+13 -21
gdscript/mods/AtProto/atproto.gd gdscript/mods/Atproto/atproto_client.gd
··· 1 1 extends Node 2 - class_name AtProto 3 2 4 3 # AtProto 5 4 var did 6 5 var pds 7 - 8 6 var accessJwt 9 7 var refreshJwt 10 8 ··· 13 11 var requester: HTTPRequest 14 12 15 13 func _enter_tree(): 16 - var root = get_tree().get_root() 17 - var instance = root.get_node("AtProto") 18 - self.name = "AtProto" 19 - if instance == null: 20 - root.add_child(self, true) 21 - self.requester = HTTPRequest.new() 22 - self.add_child(self.requester, true) 14 + self.requester = HTTPRequest.new() 15 + self.add_child(self.requester, true) 23 16 24 17 func is_token_expired() -> bool: 25 18 var json = Marshalls.base64_to_utf8(self.accessJwt.split(".")[1]) ··· 127 120 128 121 129 122 ###################### 130 - # STATIC CALLS # 123 + # Method Calls # 131 124 ###################### 132 125 133 - static func catch_fish(node, fish, size, quality): 134 - # var fish_data = Globals.item_data[fish]["file"] 135 - # var record = { 136 - # at_type = "dev.regnault.webfishing.fish", 137 - # id = fish, 138 - # name = fish_data.item_name, 139 - # size = str(size), 140 - # quality = quality 141 - # } 142 - # node.get_tree().get_root().get_node("AtProto").create_record(record) 143 - pass 126 + func catch_fish(fish, size, quality): 127 + var fish_data = Globals.item_data[fish]["file"] 128 + var record = { 129 + at_type = "dev.regnault.webfishing.fish", 130 + id = fish, 131 + name = fish_data.item_name, 132 + size = str(size), 133 + quality = quality 134 + } 135 + create_record(record)
-5
gdscript/mods/AtProto/main.gd
··· 1 - extends Node 2 - 3 - func _enter_tree(): 4 - var at_proto = AtProto.new() 5 - get_tree().root.add_node(at_proto)
+36
gdscript/mods/Atproto/main.gd
··· 1 + extends Node 2 + 3 + var config: Dictionary 4 + var default_config: Dictionary = {} 5 + 6 + onready var TackleBox := $"/root/TackleBox" 7 + const AtProtoClient_t := preload("res://mods/Atproto/atproto_client.gd") 8 + var AtProtoClient: AtProtoClient_t 9 + 10 + func _enter_tree(): 11 + AtProtoClient = AtProtoClient_t.new() 12 + add_child(AtProtoClient) 13 + 14 + func _ready() -> void: 15 + TackleBox.connect("mod_config_updated", self, "_on_config_update") 16 + _init_config() 17 + 18 + func _init_config(): 19 + var saved_config = TackleBox.get_mod_config(name) 20 + for key in default_config.keys(): 21 + if not saved_config[key]: 22 + saved_config[key] = default_config[key] 23 + config = saved_config 24 + TackleBox.set_mod_config(name, config) 25 + if config.Handle != "" and config.Password != "": 26 + AtProtoClient.login(config.Handle, config.Password) 27 + 28 + 29 + func _on_config_update(mod_id: String, new_config: Dictionary) -> void: 30 + if mod_id != name: 31 + return 32 + if config.hash() == new_config.hash(): 33 + return 34 + config = new_config 35 + if config.Handle != "" and config.Password != "": 36 + AtProtoClient.login(config.Handle, config.Password)
+1 -11
gdscript/project.godot
··· 8 8 9 9 config_version=4 10 10 11 - _global_script_classes=[ { 12 - "base": "Node", 13 - "class": "AtProto", 14 - "language": "GDScript", 15 - "path": "res://mods/AtProto/atproto.gd" 16 - } ] 17 - _global_script_class_icons={ 18 - "AtProto": "" 19 - } 20 - 21 11 [application] 22 12 23 - config/name="Webfishing AtProto" 13 + config/name="AtProto Webfishing" 24 14 25 15 [gui] 26 16