+16
Atproto.sln
+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
+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
+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 <Identifier>\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
+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
+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
+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
+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
+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
+9
Atproto/CalicoUtils/Util/WeaveUtil.cs
+22
Atproto/CatchFishFactory.cs
+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
+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
+8
Atproto/Config.cs
+17
Atproto/Mod.cs
+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
+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
+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
gdscript/.import/.gdignore
···
1
-
+13
-21
gdscript/mods/AtProto/atproto.gd
gdscript/mods/Atproto/atproto_client.gd
+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
-5
gdscript/mods/AtProto/main.gd
+36
gdscript/mods/Atproto/main.gd
+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
+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