this repo has no description

Compare changes

Choose any two refs to compare.

+5
.gitignore
··· 1 + .idea 2 + bin 3 + obj 4 + *.DotSettings.user 5 + manifestation
+66
.tangled/workflows/build-publish.yaml
··· 1 + when: 2 + - event: ["push", "manual"] 3 + branch: ["master"] 4 + - event: ["push", "manual"] 5 + branch: ["testing"] 6 + 7 + engine: "nixery" 8 + 9 + # using the default values 10 + clone: 11 + skip: false 12 + depth: 1 13 + submodules: false 14 + 15 + dependencies: 16 + # nixpkgs 17 + nixpkgs: 18 + - cargo 19 + - dotnet-sdk 20 + - mono 21 + - wget 22 + - unzip 23 + - git 24 + - gcc 25 + 26 + steps: 27 + # SETUP passwd 28 + - name: "Create /etc/passwd" 29 + command: "echo root:x:0:0::$PWD/home:/usr/bin/bash > /etc/passwd" 30 + - name: "Create fake home" 31 + command: "mkdir $PWD/home" 32 + 33 + # GODOTSTEAM 34 + - name: "Get GodotSteam" 35 + command: "wget -nv https://codeberg.org/godotsteam/godotsteam/releases/download/v3.21/linux64-g352-s158-gs321.zip -O godotsteam.zip" 36 + - name: "Unzip GodotSteam" 37 + command: "unzip godotsteam.zip -d godotsteam" 38 + - name: "Make GodotSteam executable" 39 + command: "chmod +x godotsteam/linux-352-editor.64" 40 + 41 + # GDWEAVE 42 + - name: "Get GDWeave" 43 + command: "wget -nv https://github.com/NotNite/GDWeave/releases/download/v2.0.14/GDWeave.zip -O gdweave.zip" 44 + - name: "Unzip GDWeave" 45 + command: "unzip gdweave.zip" 46 + 47 + # Setup home dir 48 + 49 + 50 + # MANIFESTATION 51 + - name: "Install Manifestation" 52 + command: "cargo install --git https://github.com/NotNite/manifestation.git" 53 + 54 + - name: "Create Manifestation config dir" 55 + command: "mkdir manifestation_config" 56 + 57 + - name: "Add GodotSteam to manifestation config" 58 + command: "echo godot_path = \\\"${PWD}/godotsteam/linux-352-editor.64\\\" > manifestation_config/config.toml" 59 + - name: "Add GDWeave to manifestation config" 60 + command: "echo gdweave_path = \\\"${PWD}/GDWeave\\\" >> manifestation_config/config.toml" 61 + 62 + - name: "LOG" 63 + command: "cat manifestation_config/config.toml" 64 + 65 + - name: "Run manifestation" 66 + command: "MANIFESTATION_CONFIG_DIR=$PWD/manifestation_config ~/.cargo/bin/manifestation ./manifestation.toml"
+37
Atproto/AtProtoSaveFactory.cs
··· 1 + using GDWeave; 2 + using GDWeave.Modding; 3 + using Teemaw.Calico.LexicalTransformer; 4 + 5 + namespace Atproto; 6 + 7 + public class AtProtoSaveFactory 8 + { 9 + public static IScriptMod Create(IModInterface mod) 10 + { 11 + return new TransformationRuleScriptModBuilder() 12 + .ForMod(mod) 13 + .Named("AtProtoSave") 14 + .Patching("res://Scenes/Singletons/UserSave/usersave.gdc") 15 + .AddRule(new TransformationRuleBuilder() 16 + .Named("ready_slot_condition") 17 + .Matching(TransformationPatternFactory.CreateGdSnippetPattern("_load_save(last_loaded_slot)", 2)) 18 + .Do(Operation.ReplaceAll) 19 + .With(""" 20 + var Atproto = $"/root/Atproto" 21 + if last_loaded_slot != Atproto.ATPROTO_SLOT or Atproto.config.Autoload: 22 + _load_save(last_loaded_slot) 23 + else: 24 + last_loaded_slot = -1 25 + """, 2) 26 + ) 27 + .AddRule(new TransformationRuleBuilder() 28 + .Named("save_file") 29 + .Matching(TransformationPatternFactory.CreateGdSnippetPattern( 30 + "\"locked_refs\": PlayerData.locked_refs, \n\t}\n", 2)) 31 + .Do(Operation.Append) 32 + .With("var atproto = $\"/root/Atproto\"\nif atproto.can_save_to_atproto():\n\tatproto.AtProtoClient.save_file()\n", 1) 33 + ) 34 + 35 + .Build(); 36 + } 37 + }
+26
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.2.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 + <Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(GDWeavePath)' != ''"> 17 + <ItemGroup> 18 + <_SourceFiles Include="$(OutDir)\**\*.*"/> 19 + </ItemGroup> 20 + 21 + <Copy 22 + SourceFiles="@(_SourceFiles)" 23 + DestinationFolder="$(GDWeavePath)/mods/$(AssemblyName)" 24 + /> 25 + </Target> 26 + </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 + }
+11
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 + [JsonInclude] public string Save = ""; 9 + [JsonInclude] public bool Autoconnect; 10 + [JsonInclude] public bool Autoload; 11 + }
+18
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 + modInterface.RegisterScriptMod(AtProtoSaveFactory.Create(modInterface)); 13 + } 14 + 15 + public void Dispose() { 16 + // Cleanup anything you do here 17 + } 18 + }
+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
+29
README.md
··· 1 + # AtProto Webfishing 2 + 3 + A Webfishing mod that send data to your PDS 4 + 5 + [ThunderStore Link](https://thunderstore.io/c/webfishing/p/EstymMods/Webfishing_Atproto/) 6 + 7 + # Features 8 + - Remote save files 9 + - Caught fish are saved to your PDS 10 + 11 + ## Requirements 12 + - [GDWeave](https://thunderstore.io/c/webfishing/p/NotNet/GDWeave/) 13 + - [TackleBox](https://thunderstore.io/c/webfishing/p/PuppyGirl/TackleBox/) 14 + 15 + ## Configuration 16 + In game using the AtProto menu, you can input your AtProto Handle as well as an App Password to login. 17 + 18 + After it's done, you can select a save file or create one, then load it ! 19 + 20 + When a file is loaded, the savedata will be synchronized to your PDS each time the game saves. 21 + 22 + ## How to send your save online 23 + 24 + To send your save on your PDS, simply create a new save and click on "Duplicate Save", it will create a new save on the PDS with the local data you have. 25 + 26 + 27 + ## Credits 28 + 29 + - Thanks to [Tianyi Ma](https://github.com/tma02) for his utils
-1
gdscript/.import/.gdignore
··· 1 -
-143
gdscript/mods/AtProto/atproto.gd
··· 1 - extends Node 2 - class_name AtProto 3 - 4 - # AtProto 5 - var did 6 - var pds 7 - 8 - var accessJwt 9 - var refreshJwt 10 - 11 - 12 - # HTTP 13 - var requester: HTTPRequest 14 - 15 - 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) 23 - 24 - func is_token_expired() -> bool: 25 - var json = Marshalls.base64_to_utf8(self.accessJwt.split(".")[1]) 26 - var data = parse_json(json) 27 - var expires = data.exp 28 - var unix = floor(Time.get_unix_time_from_system()) 29 - return expires < unix 30 - 31 - func create_record(record): 32 - 33 - if is_token_expired(): 34 - refresh_token("create_record", record) 35 - return 36 - 37 - var payload = { 38 - repo = did, 39 - collection = record.at_type, 40 - record = record 41 - } 42 - 43 - var json_payload = JSON.print(payload) 44 - json_payload = json_payload.replace("at_type", "$type") 45 - 46 - var req = self.requester 47 - 48 - var header = [ 49 - "Authorization: Bearer " + self.accessJwt, 50 - "Content-Type: application/json" 51 - ] 52 - 53 - req.request(pds + "/xrpc/com.atproto.repo.createRecord", header, true, HTTPClient.METHOD_POST, json_payload) 54 - 55 - 56 - ################ 57 - # LOGIN # 58 - ################ 59 - 60 - func login(handle, password): 61 - var req = self.requester 62 - 63 - req.connect("request_completed", self, "after_handle_resolver", [password]) 64 - req.request("https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=" + handle) 65 - 66 - 67 - func after_handle_resolver(_result, _response_code, _headers, body: PoolByteArray, password): 68 - var req = self.requester 69 - req.disconnect("request_completed", self, "after_handle_resolver") 70 - var res = parse_json(body.get_string_from_utf8()) 71 - self.did = res.did 72 - 73 - req.connect("request_completed", self, "after_get_pds", [password]) 74 - req.request("https://plc.directory/" + self.did) 75 - 76 - 77 - func after_get_pds(_result, _response_code, _headers, body: PoolByteArray, password): 78 - var req = self.requester 79 - req.disconnect("request_completed", self, "after_get_pds") 80 - 81 - var res = parse_json(body.get_string_from_utf8()) 82 - for x in res.service: 83 - if x.id == "#atproto_pds": 84 - self.pds = x.serviceEndpoint 85 - 86 - var payload = { 87 - identifier = self.did, 88 - password = password 89 - } 90 - 91 - req.connect("request_completed", self, "after_create_session") 92 - req.request(pds + "/xrpc/com.atproto.server.createSession", ["Content-Type: application/json"], true, HTTPClient.METHOD_POST, JSON.print(payload)) 93 - 94 - 95 - 96 - func after_create_session(_result, _response_code, _headers, body: PoolByteArray): 97 - var req = self.requester 98 - req.disconnect("request_completed", self, "after_create_session") 99 - 100 - var res = parse_json(body.get_string_from_utf8()) 101 - self.accessJwt = res.accessJwt 102 - self.refreshJwt = res.refreshJwt 103 - 104 - ####################### 105 - # REFRESH TOKEN # 106 - ####################### 107 - func refresh_token(method = "", payload = ""): 108 - var req = self.requester 109 - 110 - var headers = [ 111 - "Authorization: Bearer " + self.refreshJwt, 112 - "Content-Type: application/json" 113 - ] 114 - req.connect("request_completed", self, "after_refresh_token", [method, payload]) 115 - req.request(pds + "/xrpc/com.atproto.server.refreshSession", headers, true, HTTPClient.METHOD_POST) 116 - 117 - func after_refresh_token(_result, _response_code, _headers, body: PoolByteArray, method, payload): 118 - var req = self.requester 119 - req.disconnect("request_completed", self, "after_refresh_token") 120 - 121 - var res = parse_json(body.get_string_from_utf8()) 122 - self.accessJwt = res.accessJwt 123 - self.refreshJwt = res.refreshJwt 124 - 125 - if method != "": 126 - self.call(method, payload) 127 - 128 - 129 - ###################### 130 - # STATIC CALLS # 131 - ###################### 132 - 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
-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)
-31
gdscript/project.godot
··· 1 - ; Engine configuration file. 2 - ; It's best edited using the editor UI and not directly, 3 - ; since the parameters that go here are not all obvious. 4 - ; 5 - ; Format: 6 - ; [section] ; section goes between [] 7 - ; param=value ; assign values to parameters 8 - 9 - config_version=4 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 - [application] 22 - 23 - config/name="Webfishing AtProto" 24 - 25 - [gui] 26 - 27 - common/drop_mouse_on_gui_input_disabled=true 28 - 29 - [physics] 30 - 31 - common/enable_pause_aware_picking=true
icon.png

This is a binary file and will not be displayed.

+36
lexicon/fish.json
··· 1 + { 2 + "id": "dev.regnault.webfishing.fish", 3 + "defs": { 4 + "main": { 5 + "key": "tid", 6 + "type": "record", 7 + "output": { 8 + "schema": { 9 + "type": "object", 10 + "required": [ 11 + "id", 12 + "name", 13 + "quality" 14 + ], 15 + "properties": { 16 + "id": { 17 + "type": "string" 18 + }, 19 + "name": { 20 + "type": "string" 21 + }, 22 + "size": { 23 + "type": "string" 24 + }, 25 + "quality": { 26 + "type": "integer" 27 + } 28 + } 29 + }, 30 + "encoding": "application/json" 31 + } 32 + } 33 + }, 34 + "$type": "com.atproto.lexicon.schema", 35 + "lexicon": 1 36 + }
+448
lexicon/save.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "dev.regnault.webfishing.save", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record declaring a save data of the game webfishing", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "inventory" 13 + ], 14 + "properties": { 15 + "inventory": { 16 + "type": "array", 17 + "items": { 18 + "type": "ref", 19 + "ref": "#item" 20 + } 21 + }, 22 + "hotbar": { 23 + "type": "ref", 24 + "ref": "#hotbar" 25 + }, 26 + "cosmetics_unlocked": { 27 + "type": "array", 28 + "items": { 29 + "type": "string" 30 + } 31 + }, 32 + "cosmetics_equipped": { 33 + "type": "ref", 34 + "ref": "#cosmetics" 35 + }, 36 + "new_cosmetics": { 37 + "type": "array", 38 + "items": { 39 + "type": "string" 40 + } 41 + }, 42 + "version": { 43 + "type": "string" 44 + }, 45 + "money": { 46 + "type": "integer" 47 + }, 48 + "bait_inv": { 49 + "type": "ref", 50 + "ref": "#bait_inv" 51 + }, 52 + "bait_selected": { 53 + "type": "string" 54 + }, 55 + "bait_unlocked": { 56 + "type": "array", 57 + "items": { 58 + "type": "string" 59 + } 60 + }, 61 + "journal": { 62 + "type": "array", 63 + "items": { 64 + "type": "ref", 65 + "ref": "#journal_category" 66 + } 67 + }, 68 + "quests": { 69 + "type": "array", 70 + "items": { 71 + "type": "ref", 72 + "ref": "#quest_entry" 73 + } 74 + }, 75 + "completed_quests": { 76 + "type": "array", 77 + "items": { 78 + "type": "string" 79 + } 80 + }, 81 + "level": { 82 + "type": "integer" 83 + }, 84 + "xp": { 85 + "type": "integer" 86 + }, 87 + "max_bait": { 88 + "type": "integer" 89 + }, 90 + "lure_unlocked": { 91 + "type": "array", 92 + "items": { 93 + "type": "string" 94 + } 95 + }, 96 + "lure_selected": { 97 + "type": "string" 98 + }, 99 + "saved_aqua_fish": { 100 + "type": "ref", 101 + "ref": "#aqua_fish" 102 + }, 103 + "inbound_mail": { 104 + "type": "array", 105 + "items": { 106 + "type": "ref", 107 + "ref": "#letter" 108 + } 109 + }, 110 + "rod_power": { 111 + "type": "integer" 112 + }, 113 + "rod_speed": { 114 + "type": "integer" 115 + }, 116 + "rod_chance": { 117 + "type": "integer" 118 + }, 119 + "rod_luck": { 120 + "type": "integer" 121 + }, 122 + "saved_tags": { 123 + "type": "array", 124 + "items": { 125 + "type": "string" 126 + } 127 + }, 128 + "loan_level": { 129 + "type": "integer" 130 + }, 131 + "loan_left": { 132 + "type": "integer" 133 + }, 134 + "buddy_level": { 135 + "type": "integer" 136 + }, 137 + "buddy_speed": { 138 + "type": "integer" 139 + }, 140 + "guitar_shapes": { 141 + "type": "array", 142 + "items": { 143 + "type": "ref", 144 + "ref": "#guitar_shapes" 145 + } 146 + }, 147 + "fish_caught": { 148 + "type": "integer" 149 + }, 150 + "cash_total": { 151 + "type": "integer" 152 + }, 153 + "voice_pitch": { 154 + "type": "string" 155 + }, 156 + "voice_speed": { 157 + "type": "integer" 158 + }, 159 + 160 + "locked_refs": { 161 + "type": "array", 162 + "items": { 163 + "type": "integer" 164 + } 165 + } 166 + } 167 + } 168 + }, 169 + "item": { 170 + "type": "object", 171 + "properties": { 172 + "id": { 173 + "type": "string" 174 + }, 175 + "ref": { 176 + "type": "integer" 177 + }, 178 + "size": { 179 + "type": "string" 180 + }, 181 + "quality": { 182 + "type": "integer" 183 + }, 184 + "tags": { 185 + "type": "array", 186 + "items": { 187 + "type": "string" 188 + } 189 + }, 190 + "custom_name": { 191 + "type": "string" 192 + }, 193 + "count": { 194 + "type": "integer" 195 + } 196 + } 197 + }, 198 + "hotbar": { 199 + "type": "object", 200 + "properties": { 201 + "0":{ 202 + "type": "integer" 203 + }, 204 + "1":{ 205 + "type": "integer" 206 + }, 207 + "2":{ 208 + "type": "integer" 209 + }, 210 + "3":{ 211 + "type": "integer" 212 + }, 213 + "4":{ 214 + "type": "integer" 215 + } 216 + } 217 + }, 218 + "cosmetics": { 219 + "type": "object", 220 + "properties": { 221 + "species": { 222 + "type": "string" 223 + }, 224 + "pattern": { 225 + "type": "string" 226 + }, 227 + "primary_color": { 228 + "type": "string" 229 + }, 230 + "secondary_color": { 231 + "type": "string" 232 + }, 233 + "hat": { 234 + "type": "string" 235 + }, 236 + "undershirt": { 237 + "type": "string" 238 + }, 239 + "overshirt":{ 240 + "type": "string" 241 + }, 242 + "title": { 243 + "type": "string" 244 + }, 245 + "bobber": { 246 + "type": "string" 247 + }, 248 + "eye": { 249 + "type": "string" 250 + }, 251 + "nose": { 252 + "type": "string" 253 + }, 254 + "mouth": { 255 + "type": "string" 256 + }, 257 + "accessory": { 258 + "type": "array", 259 + "items": { 260 + "type": "string" 261 + } 262 + }, 263 + "tail": { 264 + "type": "string" 265 + }, 266 + "legs": { 267 + "type": "string" 268 + } 269 + } 270 + }, 271 + "bait_inv": { 272 + "type": "object", 273 + "properties": { 274 + "": { 275 + "type": "integer" 276 + }, 277 + "worms": { 278 + "type": "integer" 279 + }, 280 + "cricket": { 281 + "type": "integer" 282 + }, 283 + "leech": { 284 + "type": "integer" 285 + }, 286 + "minnow": { 287 + "type": "integer" 288 + }, 289 + "squid": { 290 + "type": "integer" 291 + }, 292 + "nautilus": { 293 + "type": "integer" 294 + } 295 + } 296 + }, 297 + "journal_category": { 298 + "type": "object", 299 + "properties": { 300 + "name": { 301 + "type": "string" 302 + }, 303 + "entries": { 304 + "type": "array", 305 + "items": { 306 + "type": "ref", 307 + "ref": "#journal_entry" 308 + } 309 + } 310 + } 311 + }, 312 + "journal_entry": { 313 + "type": "object", 314 + "properties": { 315 + "name": { 316 + "type": "string" 317 + }, 318 + "count": { 319 + "type": "integer" 320 + }, 321 + "record": { 322 + "type": "string" 323 + }, 324 + "quality": { 325 + "type": "array", 326 + "items": { 327 + "type": "integer" 328 + } 329 + } 330 + } 331 + }, 332 + "quest_entry": { 333 + "type": "object", 334 + "properties": { 335 + "id": { 336 + "type": "integer" 337 + }, 338 + "title": { 339 + "type": "string" 340 + }, 341 + "tier": { 342 + "type": "integer" 343 + }, 344 + "action": { 345 + "type": "string" 346 + }, 347 + "gold_reward": { 348 + "type": "integer" 349 + }, 350 + "xp_reward": { 351 + "type": "integer" 352 + }, 353 + "rewards": { 354 + "type": "array", 355 + "items": { 356 + "type": "string" 357 + } 358 + }, 359 + "goal_id": { 360 + "type": "string" 361 + }, 362 + "icon": { 363 + "type": "string" 364 + }, 365 + "progress": { 366 + "type": "integer" 367 + }, 368 + "max_level": { 369 + "type": "integer" 370 + }, 371 + "hidden": { 372 + "type": "boolean" 373 + }, 374 + "goal_amt": { 375 + "type": "integer" 376 + }, 377 + "goal_array": { 378 + "type": "array", 379 + "items": { 380 + "type": "integer" 381 + } 382 + } 383 + } 384 + }, 385 + "aqua_fish": { 386 + "type": "object", 387 + "properties": { 388 + "id": { 389 + "type": "string" 390 + }, 391 + "size": { 392 + "type": "string" 393 + }, 394 + "ref": { 395 + "type": "integer" 396 + }, 397 + "quality": { 398 + "type": "integer" 399 + } 400 + } 401 + }, 402 + "guitar_shapes": { 403 + "type": "array", 404 + "items": { 405 + "type": "ref", 406 + "ref": "#guitar_shape" 407 + } 408 + }, 409 + "guitar_shape": { 410 + "type": "array", 411 + "maxLength": 6, 412 + "minLength": 6, 413 + "items": { 414 + "type": "integer" 415 + } 416 + }, 417 + "letter": { 418 + "type": "object", 419 + "properties": { 420 + "letter_id": { 421 + "type": "integer" 422 + }, 423 + "header": { 424 + "type": "string" 425 + }, 426 + "closing": { 427 + "type": "string" 428 + }, 429 + "body": { 430 + "type": "string" 431 + }, 432 + "items": { 433 + "type": "array", 434 + "items": { 435 + "type": "ref", 436 + "ref": "#item" 437 + } 438 + }, 439 + "to": { 440 + "type": "string" 441 + }, 442 + "from": { 443 + "type": "string" 444 + } 445 + } 446 + } 447 + } 448 + }
+27
lexicon/savefile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "dev.regnault.webfishing.savefile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record declaring a savefile of Webfishing.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "name", 13 + "uri" 14 + ], 15 + "properties": { 16 + "name": { 17 + "type": "string" 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "at-uri" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+20
manifestation.toml
··· 1 + id = "Atproto" 2 + name = "Atproto Webfishing" 3 + description = "A mod that sends data to your AtProto PDS." 4 + version = "1.0.3" 5 + homepage = "https://tangled.org/@regnault.dev/webfishing-atproto" 6 + author = "Estym" 7 + 8 + icon = "icon.png" 9 + readme = "README.md" 10 + 11 + [project] 12 + csharp = "./Atproto/Atproto.csproj" 13 + godot = "./project/project.godot" 14 + 15 + [[dependencies]] 16 + thunderstore_version = "NotNet-GDWeave-2.0.12" 17 + 18 + [[dependencies]] 19 + id = "TackleBox" 20 + thunderstore_version = "PuppyGirl-TackleBox-0.5.2"
+2
project/.gitignore
··· 1 + .import 2 + export_presets.cfg
+264
project/mods/Atproto/assets/at_proto.tres
··· 1 + [gd_resource type="Theme" load_steps=28 format=2] 2 + 3 + [ext_resource path="res://Assets/Textures/UI/scrollbar.png" type="Texture" id=1] 4 + [ext_resource path="res://Assets/Themes/main_font.tres" type="DynamicFont" id=2] 5 + 6 + [sub_resource type="StyleBoxFlat" id=1] 7 + content_margin_left = 12.0 8 + content_margin_right = 12.0 9 + bg_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 10 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 11 + corner_radius_top_left = 12 12 + corner_radius_top_right = 12 13 + corner_radius_bottom_right = 12 14 + corner_radius_bottom_left = 12 15 + corner_detail = 5 16 + 17 + [sub_resource type="StyleBoxEmpty" id=34] 18 + 19 + [sub_resource type="StyleBoxFlat" id=30] 20 + content_margin_left = 12.0 21 + content_margin_right = 12.0 22 + bg_color = Color( 0.611765, 0.568627, 0.290196, 1 ) 23 + border_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 24 + corner_radius_top_left = 12 25 + corner_radius_top_right = 12 26 + corner_radius_bottom_right = 12 27 + corner_radius_bottom_left = 12 28 + corner_detail = 5 29 + expand_margin_left = 1.0 30 + expand_margin_right = 1.0 31 + expand_margin_top = 1.0 32 + expand_margin_bottom = 1.0 33 + 34 + [sub_resource type="StyleBoxFlat" id=31] 35 + content_margin_left = 12.0 36 + content_margin_right = 12.0 37 + bg_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 38 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 39 + corner_radius_top_left = 12 40 + corner_radius_top_right = 12 41 + corner_radius_bottom_right = 12 42 + corner_radius_bottom_left = 12 43 + 44 + [sub_resource type="StyleBoxFlat" id=32] 45 + content_margin_left = 12.0 46 + content_margin_right = 12.0 47 + bg_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 48 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 49 + corner_radius_top_left = 12 50 + corner_radius_top_right = 12 51 + corner_radius_bottom_right = 12 52 + corner_radius_bottom_left = 12 53 + corner_detail = 5 54 + 55 + [sub_resource type="StyleBoxFlat" id=18] 56 + bg_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 57 + border_width_left = 6 58 + border_color = Color( 1, 1, 1, 0 ) 59 + corner_radius_top_left = 4 60 + corner_radius_top_right = 4 61 + corner_radius_bottom_right = 4 62 + corner_radius_bottom_left = 4 63 + 64 + [sub_resource type="StyleBoxFlat" id=15] 65 + bg_color = Color( 0.611765, 0.568627, 0.290196, 1 ) 66 + border_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 67 + corner_radius_top_left = 12 68 + corner_radius_top_right = 12 69 + corner_radius_bottom_right = 12 70 + corner_radius_bottom_left = 12 71 + corner_detail = 5 72 + 73 + [sub_resource type="StyleBoxFlat" id=21] 74 + bg_color = Color( 0.0627451, 0.109804, 0.192157, 1 ) 75 + draw_center = false 76 + border_width_left = 16 77 + border_color = Color( 1, 1, 1, 0 ) 78 + corner_radius_top_left = 4 79 + corner_radius_top_right = 4 80 + corner_radius_bottom_right = 4 81 + corner_radius_bottom_left = 4 82 + 83 + [sub_resource type="StyleBoxEmpty" id=20] 84 + 85 + [sub_resource type="StyleBoxFlat" id=35] 86 + 87 + [sub_resource type="StyleBoxFlat" id=36] 88 + 89 + [sub_resource type="StyleBoxFlat" id=37] 90 + bg_color = Color( 0.0627451, 0.109804, 0.192157, 1 ) 91 + corner_radius_top_left = 8 92 + corner_radius_top_right = 8 93 + corner_radius_bottom_right = 8 94 + corner_radius_bottom_left = 8 95 + expand_margin_top = 3.0 96 + expand_margin_bottom = 3.0 97 + 98 + [sub_resource type="StyleBoxFlat" id=33] 99 + content_margin_left = 12.0 100 + content_margin_right = 12.0 101 + bg_color = Color( 0.572549, 0.45098, 0.290196, 1 ) 102 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 103 + corner_radius_top_left = 12 104 + corner_radius_top_right = 12 105 + corner_radius_bottom_right = 12 106 + corner_radius_bottom_left = 12 107 + 108 + [sub_resource type="StyleBoxFlat" id=9] 109 + bg_color = Color( 1, 0.933333, 0.835294, 1 ) 110 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 111 + corner_radius_top_left = 32 112 + corner_radius_top_right = 32 113 + corner_radius_bottom_right = 32 114 + corner_radius_bottom_left = 32 115 + 116 + [sub_resource type="ImageTexture" id=24] 117 + 118 + [sub_resource type="StyleBoxFlat" id=25] 119 + content_margin_left = 4.0 120 + content_margin_right = 4.0 121 + bg_color = Color( 0.611765, 0.568627, 0.290196, 1 ) 122 + corner_radius_top_left = 8 123 + corner_radius_top_right = 8 124 + corner_radius_bottom_right = 8 125 + corner_radius_bottom_left = 8 126 + expand_margin_left = 2.0 127 + expand_margin_right = 2.0 128 + expand_margin_top = 2.0 129 + 130 + [sub_resource type="StyleBoxEmpty" id=26] 131 + 132 + [sub_resource type="StyleBoxEmpty" id=27] 133 + 134 + [sub_resource type="StyleBoxFlat" id=28] 135 + content_margin_left = 8.0 136 + content_margin_right = 8.0 137 + bg_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 138 + corner_radius_bottom_right = 12 139 + corner_radius_bottom_left = 12 140 + expand_margin_top = 10.0 141 + expand_margin_bottom = 8.0 142 + 143 + [sub_resource type="StyleBoxEmpty" id=29] 144 + 145 + [sub_resource type="StyleBoxFlat" id=4] 146 + bg_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 147 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 148 + corner_radius_top_left = 12 149 + corner_radius_top_right = 12 150 + corner_radius_bottom_right = 12 151 + corner_radius_bottom_left = 12 152 + expand_margin_left = 2.0 153 + expand_margin_right = 2.0 154 + 155 + [sub_resource type="StyleBoxFlat" id=8] 156 + bg_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 157 + border_width_left = 2 158 + border_width_top = 2 159 + border_width_right = 2 160 + border_width_bottom = 2 161 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 162 + corner_radius_top_left = 6 163 + corner_radius_top_right = 6 164 + corner_radius_bottom_right = 6 165 + corner_radius_bottom_left = 6 166 + corner_detail = 5 167 + anti_aliasing = false 168 + 169 + [sub_resource type="StyleBoxFlat" id=6] 170 + bg_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 171 + border_width_left = 2 172 + border_width_top = 2 173 + border_width_right = 2 174 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 175 + corner_radius_top_left = 6 176 + corner_radius_top_right = 6 177 + corner_detail = 5 178 + anti_aliasing = false 179 + 180 + [sub_resource type="StyleBoxFlat" id=7] 181 + bg_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 182 + border_width_left = 2 183 + border_width_top = 2 184 + border_width_right = 2 185 + border_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 186 + corner_radius_top_left = 6 187 + corner_radius_top_right = 6 188 + corner_detail = 5 189 + expand_margin_bottom = 2.0 190 + anti_aliasing = false 191 + 192 + [sub_resource type="StyleBoxEmpty" id=23] 193 + 194 + [resource] 195 + default_font = ExtResource( 2 ) 196 + Button/colors/font_color = Color( 1, 0.933333, 0.835294, 1 ) 197 + Button/colors/font_color_disabled = Color( 0.835294, 0.666667, 0.45098, 1 ) 198 + Button/colors/font_color_focus = Color( 1, 0.933333, 0.835294, 1 ) 199 + Button/colors/font_color_hover = Color( 1, 0.933333, 0.835294, 1 ) 200 + Button/colors/font_color_pressed = Color( 0.835294, 0.666667, 0.45098, 1 ) 201 + Button/styles/disabled = SubResource( 1 ) 202 + Button/styles/focus = SubResource( 34 ) 203 + Button/styles/hover = SubResource( 30 ) 204 + Button/styles/normal = SubResource( 31 ) 205 + Button/styles/pressed = SubResource( 32 ) 206 + HScrollBar/styles/grabber = SubResource( 18 ) 207 + HScrollBar/styles/grabber_highlight = SubResource( 15 ) 208 + HScrollBar/styles/grabber_pressed = SubResource( 15 ) 209 + HScrollBar/styles/scroll = SubResource( 21 ) 210 + HScrollBar/styles/scroll_focus = SubResource( 20 ) 211 + HSlider/icons/grabber = ExtResource( 1 ) 212 + HSlider/icons/grabber_disabled = ExtResource( 1 ) 213 + HSlider/icons/grabber_highlight = ExtResource( 1 ) 214 + HSlider/styles/grabber_area = SubResource( 35 ) 215 + HSlider/styles/grabber_area_highlight = SubResource( 36 ) 216 + HSlider/styles/slider = SubResource( 37 ) 217 + Label/colors/font_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 218 + LineEdit/colors/cursor_color = Color( 1, 0.933333, 0.835294, 1 ) 219 + LineEdit/colors/font_color = Color( 1, 0.933333, 0.835294, 1 ) 220 + LineEdit/colors/font_color_selected = Color( 1, 0.933333, 0.835294, 1 ) 221 + LineEdit/colors/font_color_uneditable = Color( 0.835294, 0.666667, 0.45098, 1 ) 222 + LineEdit/styles/focus = SubResource( 33 ) 223 + LineEdit/styles/normal = SubResource( 33 ) 224 + LineEdit/styles/read_only = SubResource( 33 ) 225 + Panel/styles/panel = SubResource( 9 ) 226 + PopupMenu/colors/font_color = Color( 1, 0.933333, 0.835294, 1 ) 227 + PopupMenu/colors/font_color_hover = Color( 1, 0.933333, 0.835294, 1 ) 228 + PopupMenu/icons/checked = SubResource( 24 ) 229 + PopupMenu/icons/radio_checked = SubResource( 24 ) 230 + PopupMenu/icons/radio_unchecked = SubResource( 24 ) 231 + PopupMenu/icons/submenu = SubResource( 24 ) 232 + PopupMenu/icons/unchecked = SubResource( 24 ) 233 + PopupMenu/styles/hover = SubResource( 25 ) 234 + PopupMenu/styles/labeled_separator_left = SubResource( 26 ) 235 + PopupMenu/styles/labeled_separator_right = SubResource( 27 ) 236 + PopupMenu/styles/panel = SubResource( 28 ) 237 + PopupMenu/styles/panel_disabled = SubResource( 28 ) 238 + PopupMenu/styles/separator = SubResource( 29 ) 239 + ProgressBar/colors/font_color = Color( 1, 0.933333, 0.835294, 1 ) 240 + ProgressBar/styles/bg = SubResource( 1 ) 241 + ProgressBar/styles/fg = SubResource( 4 ) 242 + RichTextLabel/colors/default_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 243 + TabContainer/colors/font_color_bg = Color( 0.415686, 0.266667, 0.12549, 1 ) 244 + TabContainer/colors/font_color_disabled = Color( 0.611765, 0.0784314, 0.0784314, 1 ) 245 + TabContainer/colors/font_color_fg = Color( 1, 0.933333, 0.835294, 1 ) 246 + TabContainer/styles/panel = SubResource( 8 ) 247 + TabContainer/styles/tab_bg = SubResource( 6 ) 248 + TabContainer/styles/tab_disabled = SubResource( 1 ) 249 + TabContainer/styles/tab_fg = SubResource( 7 ) 250 + Tabs/colors/font_color_bg = Color( 0.835294, 0.666667, 0.45098, 1 ) 251 + Tabs/colors/font_color_fg = Color( 0.835294, 0.666667, 0.45098, 1 ) 252 + TextEdit/colors/caret_color = Color( 1, 0.933333, 0.835294, 1 ) 253 + TextEdit/colors/font_color = Color( 0.415686, 0.266667, 0.12549, 1 ) 254 + TextEdit/colors/font_color_readonly = Color( 0.415686, 0.266667, 0.12549, 1 ) 255 + TextEdit/colors/selection_color = Color( 0.352941, 0.458824, 0.352941, 1 ) 256 + TextEdit/styles/completion = SubResource( 23 ) 257 + TextEdit/styles/focus = SubResource( 23 ) 258 + TextEdit/styles/normal = SubResource( 23 ) 259 + TextEdit/styles/read_only = SubResource( 23 ) 260 + VScrollBar/styles/grabber = SubResource( 18 ) 261 + VScrollBar/styles/grabber_highlight = SubResource( 15 ) 262 + VScrollBar/styles/grabber_pressed = SubResource( 15 ) 263 + VScrollBar/styles/scroll = SubResource( 21 ) 264 + VScrollBar/styles/scroll_focus = SubResource( 20 )
+462
project/mods/Atproto/atproto_client.gd
··· 1 + extends Node 2 + 3 + # Signals 4 + signal connection(suceeded) 5 + signal savefile_loaded(uri) 6 + 7 + # State 8 + var can_save = true 9 + 10 + # AtProto 11 + var did 12 + var pds 13 + var accessJwt 14 + var refreshJwt 15 + var Atproto 16 + 17 + func _enter_tree(): 18 + Atproto = self.get_parent() 19 + 20 + func connected() -> bool: 21 + return accessJwt != null 22 + 23 + func is_token_expired() -> bool: 24 + var token_data = self.accessJwt.split(".")[1] 25 + var data = null 26 + for x in ["", "=", "=="] : 27 + var json = Marshalls.base64_to_utf8(token_data + x) 28 + data = parse_json(json) 29 + if data != null: break 30 + var expires = data.exp 31 + var unix = floor(Time.get_unix_time_from_system()) 32 + return expires < unix 33 + 34 + func get_header(): 35 + return [ 36 + "Authorization: Bearer " + self.accessJwt, 37 + "Content-Type: application/json" 38 + ] 39 + 40 + func create_record(record, callback : FuncRef = null): 41 + 42 + if is_token_expired(): 43 + refresh_token("create_record", [record]) 44 + return 45 + 46 + var payload = { 47 + repo = did, 48 + collection = record.at_type, 49 + record = record 50 + } 51 + 52 + var json_payload = JSON.print(payload) 53 + json_payload = json_payload.replace("at_type", "$type") 54 + 55 + var req = HTTPRequest.new() 56 + self.add_child(req) 57 + req.connect("request_completed", self, "_create_record_handler", [req, callback]) 58 + req.request(pds + "/xrpc/com.atproto.repo.createRecord", get_header(), true, HTTPClient.METHOD_POST, json_payload) 59 + 60 + 61 + func _create_record_handler(_result, code, _headers, body: PoolByteArray, req: HTTPRequest, callback: FuncRef): 62 + req.queue_free() 63 + if callback == null: 64 + return 65 + 66 + var res = parse_json(body.get_string_from_utf8()) 67 + callback.call_func(res) 68 + 69 + ## LIST RECORDS 70 + 71 + func list_records(callback: FuncRef, collection: String, limit: int = 50, cursor = ""): 72 + var query_string = "repo=" + did 73 + query_string += "&collection=" + collection.http_escape() 74 + query_string += "&limit=" + str(limit).http_escape() 75 + query_string += "&cursor=" + str(limit).http_escape() 76 + var req_str = pds + "/xrpc/com.atproto.repo.listRecords?" + query_string 77 + 78 + var req = HTTPRequest.new() 79 + self.add_child(req) 80 + req.connect("request_completed", self, "_list_record_handler", [req, callback]) 81 + req.request(req_str, get_header(), true, HTTPClient.METHOD_GET) 82 + 83 + func _list_record_handler(_result, code, _headers, body: PoolByteArray, req: HTTPRequest, callback: FuncRef): 84 + req.queue_free() 85 + var b = body.get_string_from_utf8() 86 + var res = parse_json(b) 87 + callback.call_func(res.records) 88 + 89 + 90 + ## GET RECORD 91 + 92 + func get_record(callback: FuncRef, did: String, collection: String, rkey: String): 93 + var query_string = "repo=" + did.http_escape() 94 + query_string += "&collection=" + collection.http_escape() 95 + query_string += "&rkey=" + rkey.http_escape() 96 + 97 + var req = HTTPRequest.new() 98 + self.add_child(req) 99 + req.connect("request_completed", self, "_get_record_handler", [req, callback]) 100 + req.request(pds + "/xrpc/com.atproto.repo.getRecord?" + query_string, get_header(), true, HTTPClient.METHOD_GET) 101 + 102 + func _get_record_handler(_result, code, _headers, body: PoolByteArray, req: HTTPRequest, callback: FuncRef): 103 + req.queue_free() 104 + var res = parse_json(body.get_string_from_utf8()) 105 + callback.call_func(res) 106 + 107 + 108 + ## PUT RECORD 109 + 110 + func put_record(uri, record, callback: FuncRef = null): 111 + if is_token_expired(): 112 + refresh_token("put_record", [uri, record]) 113 + return 114 + 115 + var splitted_uri = uri.split("/") 116 + 117 + 118 + var payload = { 119 + repo = splitted_uri[2], 120 + collection = splitted_uri[3], 121 + rkey = splitted_uri[4], 122 + record = record 123 + } 124 + 125 + var json_payload = JSON.print(payload) 126 + json_payload = json_payload.replace("at_type", "$type") 127 + 128 + var req = HTTPRequest.new() 129 + self.add_child(req) 130 + req.connect("request_completed", self, "_put_record_handler", [req, callback]) 131 + req.request(pds + "/xrpc/com.atproto.repo.putRecord", get_header(), true, HTTPClient.METHOD_POST, json_payload) 132 + 133 + func _put_record_handler(_result, code, _headers, body: PoolByteArray, req: HTTPRequest, callback: FuncRef): 134 + req.queue_free() 135 + if callback == null: 136 + return 137 + var res = parse_json(body.get_string_from_utf8()) 138 + callback.call_func(res) 139 + 140 + ################ 141 + # LOGIN # 142 + ################ 143 + 144 + func login(handle, password): 145 + var req = HTTPRequest.new() 146 + self.add_child(req) 147 + 148 + req.connect("request_completed", self, "after_handle_resolver", [req, password]) 149 + req.request("https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=" + handle) 150 + 151 + 152 + func after_handle_resolver(_result, code, _headers, body: PoolByteArray, req: HTTPRequest, password): 153 + req.disconnect("request_completed", self, "after_handle_resolver") 154 + 155 + if code != 200: 156 + emit_signal("connection", false) 157 + return 158 + 159 + var res = parse_json(body.get_string_from_utf8()) 160 + self.did = res.did 161 + 162 + req.connect("request_completed", self, "after_get_pds", [req, password]) 163 + req.request("https://plc.directory/" + self.did) 164 + 165 + 166 + func after_get_pds(_result, code, _headers, body: PoolByteArray,req: HTTPRequest, password): 167 + req.disconnect("request_completed", self, "after_get_pds") 168 + 169 + if code != 200: 170 + emit_signal("connection", false) 171 + return 172 + 173 + var res = parse_json(body.get_string_from_utf8()) 174 + for x in res.service: 175 + if x.id == "#atproto_pds": 176 + self.pds = x.serviceEndpoint 177 + 178 + var payload = { 179 + identifier = self.did, 180 + password = password 181 + } 182 + 183 + req.connect("request_completed", self, "after_create_session", [req]) 184 + req.request(pds + "/xrpc/com.atproto.server.createSession", ["Content-Type: application/json"], true, HTTPClient.METHOD_POST, JSON.print(payload)) 185 + 186 + 187 + func after_create_session(_result, code, _headers, body: PoolByteArray, req: HTTPRequest): 188 + req.queue_free() 189 + 190 + if code != 200: 191 + emit_signal("connection", false) 192 + return 193 + 194 + var res = parse_json(body.get_string_from_utf8()) 195 + self.accessJwt = res.accessJwt 196 + self.refreshJwt = res.refreshJwt 197 + emit_signal("connection", true) 198 + 199 + ####################### 200 + # REFRESH TOKEN # 201 + ####################### 202 + func refresh_token(method = "", payload = []): 203 + var req = self.requester 204 + 205 + var headers = [ 206 + "Authorization: Bearer " + self.refreshJwt, 207 + "Content-Type: application/json" 208 + ] 209 + req.connect("request_completed", self, "after_refresh_token", [method, payload]) 210 + req.request(pds + "/xrpc/com.atproto.server.refreshSession", headers, true, HTTPClient.METHOD_POST) 211 + 212 + func after_refresh_token(_result, _response_code, _headers, body: PoolByteArray, method, payload): 213 + var req = self.requester 214 + req.disconnect("request_completed", self, "after_refresh_token") 215 + 216 + var res = parse_json(body.get_string_from_utf8()) 217 + self.accessJwt = res.accessJwt 218 + self.refreshJwt = res.refreshJwt 219 + 220 + if method != "": 221 + self.callv(method, payload) 222 + 223 + 224 + ###################### 225 + # Method Calls # 226 + ###################### 227 + 228 + func catch_fish(fish, size, quality): 229 + var fish_data = Globals.item_data[fish]["file"] 230 + var record = { 231 + at_type = "dev.regnault.webfishing.fish", 232 + id = fish, 233 + name = fish_data.item_name, 234 + size = str(size), 235 + quality = quality 236 + } 237 + create_record(record) 238 + 239 + 240 + # SAVES 241 + 242 + func create_save_file(uri: String, filename: String, callback: FuncRef = null): 243 + var record = { 244 + at_type = "dev.regnault.webfishing.savefile", 245 + uri = uri, 246 + name = filename, 247 + } 248 + create_record(record, callback) 249 + pass 250 + 251 + func get_saves(callback: FuncRef): 252 + list_records(callback, "dev.regnault.webfishing.savefile") 253 + 254 + 255 + func get_save_data(): 256 + var save_data = { 257 + "inventory": PlayerData.inventory, 258 + "hotbar": PlayerData.hotbar, 259 + "cosmetics_unlocked": PlayerData.cosmetics_unlocked, 260 + "cosmetics_equipped": PlayerData.cosmetics_equipped, 261 + "new_cosmetics": [], 262 + "version": Globals.GAME_VERSION, 263 + "muted_players": PlayerData.players_muted, 264 + "hidden_players": PlayerData.players_hidden, 265 + "recorded_time": PlayerData.last_recorded_time, 266 + "money": PlayerData.money, 267 + "bait_inv": PlayerData.bait_inv, 268 + "bait_selected": PlayerData.bait_selected, 269 + "bait_unlocked": PlayerData.bait_unlocked, 270 + "shop": PlayerData.current_shop, 271 + "journal": PlayerData.journal_logs, 272 + "quests": PlayerData.current_quests, 273 + "completed_quests": PlayerData.completed_quests, 274 + "level": PlayerData.badge_level, 275 + "xp": PlayerData.badge_xp, 276 + "max_bait": PlayerData.max_bait, 277 + "lure_unlocked": PlayerData.lure_unlocked, 278 + "lure_selected": PlayerData.lure_selected, 279 + "saved_aqua_fish": PlayerData.saved_aqua_fish, 280 + "inbound_mail": PlayerData.inbound_mail, 281 + "rod_power": PlayerData.rod_power_level, 282 + "rod_speed": PlayerData.rod_speed_level, 283 + "rod_chance": PlayerData.rod_chance_level, 284 + "rod_luck": PlayerData.rod_luck_level, 285 + "saved_tags": PlayerData.saved_tags, 286 + "loan_level": PlayerData.loan_level, 287 + "loan_left": PlayerData.loan_left, 288 + "buddy_level": PlayerData.buddy_level, 289 + "buddy_speed": PlayerData.buddy_speed, 290 + "guitar_shapes": PlayerData.guitar_shapes, 291 + "fish_caught": PlayerData.fish_caught, 292 + "cash_total": PlayerData.cash_total, 293 + "voice_pitch": PlayerData.voice_pitch, 294 + "voice_speed": PlayerData.voice_speed, 295 + "locked_refs": PlayerData.locked_refs, 296 + } 297 + save_data = save_data.duplicate(true) 298 + 299 + # JOURNAL 300 + var modified_journal = [] 301 + for area in save_data.journal: 302 + var area_entry = { 303 + name = area, 304 + entries = [] 305 + } 306 + for entry_name in save_data.journal[area]: 307 + var entry = save_data.journal[area][entry_name] 308 + area_entry.entries.append({ 309 + name = entry_name, 310 + count = entry.count, 311 + record = str(entry.record), 312 + quality = entry.quality 313 + }) 314 + modified_journal.append(area_entry) 315 + save_data.journal = modified_journal 316 + 317 + # Quests 318 + var modified_quests = [] 319 + for quest_id in save_data.quests: 320 + var entry = save_data.quests[quest_id].duplicate(true) 321 + entry.id = quest_id 322 + modified_quests.append(entry) 323 + save_data.quests = modified_quests 324 + 325 + # Inventory 326 + for item in save_data.inventory: 327 + item.size = str(item.size) 328 + 329 + 330 + # Version 331 + save_data.version = str(save_data.version) 332 + 333 + # Voice Pitch 334 + save_data.voice_pitch = str(save_data.voice_pitch) 335 + 336 + # Aqua Fish 337 + save_data.saved_aqua_fish.size = str(save_data.saved_aqua_fish.size) 338 + 339 + # Letters 340 + for letter in save_data.inbound_mail: 341 + for item in letter.items: 342 + item.size = str(item.size) 343 + 344 + return save_data 345 + 346 + func save_file(callback: FuncRef = null, creation = false): 347 + if UserSave.current_loaded_slot != Atproto.ATPROTO_SLOT: 348 + return 349 + if !connected(): return 350 + 351 + var save_data = get_save_data() 352 + save_data.at_type = "dev.regnault.webfishing.save" 353 + 354 + if Atproto.save_loaded != "": 355 + put_record(Atproto.save_loaded, save_data) 356 + 357 + func create_save(callback: FuncRef = null): 358 + if !connected(): return 359 + var save_data = get_save_data() 360 + save_data.at_type = "dev.regnault.webfishing.save" 361 + create_record(save_data, callback) 362 + 363 + func load_save(uri: String): 364 + var splitted_uri = uri.split("/") 365 + var did = splitted_uri[2] 366 + var collection = splitted_uri[3] 367 + var rkey = splitted_uri[4] 368 + get_record(funcref(self, "_after_get_save"), did, collection, rkey) 369 + pass 370 + 371 + func _after_get_save(save_record): 372 + var save = save_record.value 373 + 374 + UserSave._load_save(Atproto.ATPROTO_SLOT) 375 + 376 + var modified_journal: Dictionary = {} 377 + for area in save.journal: 378 + var area_entries = {} 379 + for entry in area.entries: 380 + area_entries[entry.name] = { 381 + count = entry.count, 382 + record = float(entry.record), 383 + quality = entry.quality 384 + } 385 + 386 + modified_journal[area.name] = area_entries 387 + save.journal = modified_journal 388 + 389 + var modified_quests = {} 390 + for quest in save.quests: 391 + var id = quest.id 392 + modified_quests[quest.id] = quest 393 + modified_quests[quest.id].erase("id") 394 + save.quests = modified_quests 395 + 396 + # Inventory 397 + for item in save.inventory: 398 + item.size = float(item.size) 399 + item.quality = int(item.quality) 400 + var x = PlayerData.QUALITY_DATA[item.quality] 401 + 402 + save.version = float(save.version) 403 + save.saved_aqua_fish.size = float(save.saved_aqua_fish.size) 404 + for letter in save.inbound_mail: 405 + for item in letter.items: 406 + item.size = float(item.size) 407 + item.quality = int(item.quality) 408 + var x = PlayerData.QUALITY_DATA[item.quality] 409 + 410 + var modified_hotbar = {} 411 + for item in save.hotbar: 412 + modified_hotbar[int(item)] = save.hotbar[item] 413 + save.hotbar = modified_hotbar 414 + 415 + PlayerData.inventory = save.inventory 416 + PlayerData.hotbar = save.hotbar 417 + PlayerData.cosmetics_unlocked = save.cosmetics_unlocked 418 + PlayerData.cosmetics_equipped = save.cosmetics_equipped 419 + PlayerData.money = save.money 420 + PlayerData.players_muted = save.muted_players 421 + PlayerData.players_hidden = save.hidden_players 422 + PlayerData.bait_inv = save.bait_inv 423 + PlayerData.bait_selected = save.bait_selected 424 + PlayerData.bait_unlocked = save.bait_unlocked 425 + PlayerData.max_bait = save.max_bait 426 + PlayerData.lure_unlocked = save.lure_unlocked 427 + PlayerData.lure_selected = save.lure_selected 428 + PlayerData.journal_logs = save.journal 429 + PlayerData.current_quests = save.quests 430 + PlayerData.completed_quests = save.completed_quests 431 + PlayerData.badge_level = int(save.level) 432 + PlayerData.badge_xp = int(save.xp) 433 + PlayerData.saved_aqua_fish = save.saved_aqua_fish 434 + PlayerData.inbound_mail = save.inbound_mail 435 + PlayerData.saved_tags = save.saved_tags 436 + PlayerData.loan_level = int(save.loan_level) 437 + PlayerData.loan_left = save.loan_left 438 + PlayerData.rod_power_level = save.rod_power 439 + PlayerData.rod_speed_level = save.rod_speed 440 + PlayerData.rod_chance_level = save.rod_chance 441 + PlayerData.rod_luck_level = save.rod_luck 442 + PlayerData.buddy_level = save.buddy_level 443 + PlayerData.buddy_speed = save.buddy_speed 444 + PlayerData.guitar_shapes = save.guitar_shapes 445 + PlayerData.fish_caught = save.fish_caught 446 + PlayerData.cash_total = save.cash_total 447 + PlayerData.voice_pitch = float(save.voice_pitch) 448 + PlayerData.voice_speed = save.voice_speed 449 + PlayerData.locked_refs = save.locked_refs 450 + PlayerData._validate_guitar_shapes() 451 + PlayerData._validate_inventory() 452 + PlayerData._journal_check() 453 + PlayerData._missing_quest_check() 454 + PlayerData._unlock_defaults() 455 + 456 + can_save = false 457 + UserSave._save_slot(Atproto.ATPROTO_SLOT) 458 + can_save = true 459 + 460 + emit_signal("savefile_loaded", save_record.uri) 461 + 462 +
+68
project/mods/Atproto/main.gd
··· 1 + extends Node 2 + 3 + var config: Dictionary 4 + 5 + const ATPROTO_SLOT = 99 6 + var save_loaded: String 7 + var default_config: Dictionary = {} 8 + 9 + onready var TackleBox := $"/root/TackleBox" 10 + const AtProtoClient_t := preload("res://mods/Atproto/atproto_client.gd") 11 + var AtProtoClient: AtProtoClient_t 12 + 13 + # UI 14 + const AtProtoMenu := preload("res://mods/Atproto/ui/menus/atproto_config.tscn") 15 + const AtProtoButton := preload("res://mods/Atproto/ui/buttons/atproto.tscn") 16 + 17 + var setuped = false 18 + 19 + func _enter_tree(): 20 + AtProtoClient = AtProtoClient_t.new() 21 + add_child(AtProtoClient) 22 + AtProtoClient.connect("savefile_loaded", self, "set_save_file") 23 + get_tree().connect("node_added", self, "_add_atproto_menu") 24 + 25 + 26 + func _ready() -> void: 27 + _init_config() 28 + 29 + func _init_config(): 30 + var saved_config = TackleBox.get_mod_config(name) 31 + for key in default_config.keys(): 32 + if not saved_config[key]: 33 + saved_config[key] = default_config[key] 34 + config = saved_config 35 + TackleBox.set_mod_config(name, config) 36 + if config.Autoconnect == true: 37 + AtProtoClient.login(config.Handle, config.Password) 38 + 39 + func _save_config(): 40 + TackleBox.set_mod_config(name, config) 41 + 42 + func _add_atproto_menu(node: Node): 43 + if node.name == "main_menu": 44 + var atproto_menu: Node = AtProtoMenu.instance() 45 + atproto_menu.visible = false 46 + node.add_child(atproto_menu) 47 + 48 + var button = AtProtoButton.instance() 49 + var menu_list: Node = node.get_node("VBoxContainer") 50 + var settings_button: Node = menu_list.get_node("settings") 51 + menu_list.add_child(button) 52 + menu_list.move_child(button, settings_button.get_index() + 1) 53 + atproto_menu.connect("setup_done", self, "_after_setup") 54 + pass 55 + 56 + func _after_setup(): 57 + if setuped: 58 + return 59 + setuped = true 60 + 61 + if config.Save != "" and config.Autoload and AtProtoClient.connected(): 62 + AtProtoClient.load_save(config.Save) 63 + 64 + func can_save_to_atproto(): 65 + return AtProtoClient.can_save && UserSave.current_loaded_slot == ATPROTO_SLOT && AtProtoClient.connected() 66 + 67 + func set_save_file(save_uri): 68 + save_loaded = save_uri
+7
project/mods/Atproto/ui/buttons/atproto.gd
··· 1 + extends GenericUIButton 2 + 3 + 4 + func _on_mods_pressed() -> void: 5 + var atproto_menu = $"../../atproto_config" 6 + atproto_menu._refresh() 7 + atproto_menu.visible = true
+38
project/mods/Atproto/ui/buttons/atproto.tscn
··· 1 + [gd_scene load_steps=8 format=2] 2 + 3 + [ext_resource path="res://Scenes/Singletons/Tooltips/tooltip_node.gd" type="Script" id=1] 4 + [ext_resource path="res://Assets/Themes/accid___.ttf" type="DynamicFontData" id=2] 5 + [ext_resource path="res://Assets/Themes/button_tan_hover.tres" type="StyleBox" id=3] 6 + [ext_resource path="res://Assets/Themes/button_tan_pressed.tres" type="StyleBox" id=4] 7 + [ext_resource path="res://Assets/Themes/button_tan_normal.tres" type="StyleBox" id=5] 8 + [ext_resource path="res://mods/Atproto/ui/buttons/atproto.gd" type="Script" id=6] 9 + 10 + [sub_resource type="DynamicFont" id=1] 11 + size = 34 12 + font_data = ExtResource( 2 ) 13 + 14 + [node name="atproto" type="Button" groups=["menu_button"]] 15 + margin_right = 12.0 16 + margin_bottom = 20.0 17 + size_flags_vertical = 3 18 + custom_colors/font_color_disabled = Color( 1, 0.929412, 0.839216, 1 ) 19 + custom_colors/font_color_focus = Color( 0.419608, 0.270588, 0.129412, 1 ) 20 + custom_colors/font_color = Color( 0.419608, 0.270588, 0.129412, 1 ) 21 + custom_colors/font_color_hover = Color( 1, 0.929412, 0.839216, 1 ) 22 + custom_colors/font_color_pressed = Color( 1, 0.929412, 0.839216, 1 ) 23 + custom_fonts/font = SubResource( 1 ) 24 + custom_styles/hover = ExtResource( 3 ) 25 + custom_styles/pressed = ExtResource( 4 ) 26 + custom_styles/disabled = ExtResource( 4 ) 27 + custom_styles/normal = ExtResource( 5 ) 28 + text = "Atproto Config" 29 + script = ExtResource( 6 ) 30 + 31 + [node name="TooltipNode" type="Control" parent="."] 32 + anchor_right = 1.0 33 + anchor_bottom = 1.0 34 + script = ExtResource( 1 ) 35 + header = "[color=#6a4420]Mods" 36 + body = "Manage your AtProto Data !" 37 + 38 + [connection signal="pressed" from="." to="." method="_on_mods_pressed"]
+304
project/mods/Atproto/ui/menus/atproto_config.gd
··· 1 + extends Node 2 + 3 + onready var Atproto := $"/root/Atproto" 4 + 5 + const AtProtoNewSaveMenu := preload("res://mods/Atproto/ui/menus/new_save.tscn") 6 + 7 + signal setup_done() 8 + 9 + var SaveMenu: Node 10 + var Settings: VBoxContainer 11 + var Credentials: VBoxContainer 12 + var Saves : VBoxContainer 13 + 14 + func _ready(): 15 + Atproto.AtProtoClient.connect("savefile_loaded", self, "set_save_file") 16 + Settings = $"%atproto_settings" 17 + Credentials = Settings.get_node("credentials") 18 + Saves = Settings.get_node("saves") 19 + init_save_menu() 20 + init_credentials() 21 + 22 + if Atproto.AtProtoClient.connected() : 23 + after_login() 24 + else: 25 + init_saves() 26 + 27 + func init_credentials() -> void: 28 + 29 + Credentials.get_node("handle/LineEdit").text = Atproto.config.Handle 30 + Credentials.get_node("password/LineEdit").text = Atproto.config.Password 31 + 32 + var autoconnect: OptionButton = Credentials.get_node("autoconnect").get_node("OptionButton") 33 + autoconnect.add_item("No", 0) 34 + autoconnect.set_item_metadata(0, false) 35 + autoconnect.add_item("Yes", 1) 36 + autoconnect.set_item_metadata(1, true) 37 + 38 + if Atproto.config.Autoconnect: 39 + autoconnect.select(1) 40 + 41 + func init_saves(saves = []) -> void: 42 + var new_save_button: Button = Saves.get_node("buttons").get_node("new_save") 43 + var load_save_button: Button = Saves.get_node("buttons").get_node("load_save") 44 + 45 + var autoload: OptionButton = Saves.get_node("autoload").get_node("OptionButton") 46 + autoload.clear() 47 + autoload.add_item("No", 0) 48 + autoload.set_item_metadata(0, false) 49 + autoload.add_item("Yes", 1) 50 + autoload.set_item_metadata(1, true) 51 + if Atproto.config.Autoload: 52 + autoload.select(1) 53 + 54 + var save_option: OptionButton = Saves.get_node("save").get_node("OptionButton") 55 + save_option.clear() 56 + if Atproto.AtProtoClient.connected(): 57 + var i = 0 58 + for save_record in saves: 59 + var save = save_record.value 60 + save_option.add_item(save.name) 61 + save_option.set_item_metadata(save_option.get_item_count()-1, save.uri) 62 + if Atproto.config.Save == save.uri: 63 + save_option.select(i) 64 + i += 1 65 + save_option.disabled = save_option.get_item_count() == 0 66 + load_save_button.disabled = save_option.get_item_count() == 0 67 + new_save_button.disabled = false 68 + else: 69 + save_option.disabled = true 70 + save_option.add_item("No saves") 71 + 72 + load_save_button.disabled = true 73 + new_save_button.disabled = true 74 + 75 + _refresh() 76 + emit_signal("setup_done") 77 + 78 + func _on_apply_pressed() -> void: 79 + var save : OptionButton = Saves.get_node("save/OptionButton") 80 + Atproto.config.Autoload = Saves.get_node("autoload/OptionButton").get_selected_id() == 1 81 + if (Atproto.config.Save != save.get_selected_metadata() or Atproto.save_loaded != save.get_selected_metadata()) and Atproto.config.Autoload: 82 + Atproto.AtProtoClient.load_save(save.get_selected_metadata()) 83 + 84 + 85 + var autoconnect: OptionButton = Credentials.get_node("autoconnect").get_node("OptionButton") 86 + 87 + Atproto.config.Autoconnect = autoconnect.get_selected_id() == 1 88 + 89 + 90 + Atproto.config.Handle = Credentials.get_node("handle/LineEdit").text 91 + Atproto.config.Password = Credentials.get_node("password/LineEdit").text 92 + 93 + Atproto._save_config() 94 + 95 + set("visible", false) 96 + 97 + func _on_close_pressed() -> void: 98 + set("visible", false) 99 + 100 + 101 + func start_loading(): 102 + var panel = get_node("Panel") 103 + panel.get_node("Loader").set("visible", true) 104 + panel.get_node("close").set("disabled", true) 105 + panel.get_node("apply").set("disabled", true) 106 + 107 + func stop_loading(): 108 + var panel = get_node("Panel") 109 + panel.get_node("Loader").set("visible", false) 110 + panel.get_node("close").set("disabled", false) 111 + panel.get_node("apply").set("disabled", false) 112 + 113 + # CONNECTION 114 + 115 + func _on_connect_button_button_down(): 116 + var handle_field : LineEdit = Credentials.get_node("handle/LineEdit") 117 + var password_field : LineEdit = Credentials.get_node("password/LineEdit") 118 + Atproto.AtProtoClient.connect("connection", self, "_after_connect") 119 + start_loading() 120 + Atproto.AtProtoClient.login(handle_field.text, password_field.text) 121 + 122 + func _after_connect(success): 123 + if !success: 124 + PopupMessage._show_popup("An error has occured !") 125 + else: 126 + PopupMessage._show_popup("Connection successful !") 127 + after_login() 128 + stop_loading() 129 + 130 + func after_login(_a = null): 131 + Atproto.AtProtoClient.get_saves(funcref(self, "init_saves")) 132 + 133 + func _refresh(): 134 + $"%atproto_settings/save_info".text = get_loaded_save_info() 135 + 136 + func get_loaded_save_info(): 137 + if UserSave.current_loaded_slot != Atproto.ATPROTO_SLOT: 138 + return "No AtProto save loaded" 139 + else: 140 + var save_option: OptionButton = Saves.get_node("save").get_node("OptionButton") 141 + var i = 0 142 + while i < save_option.get_item_count(): 143 + var metadata = save_option.get_item_metadata(i) 144 + if metadata == Atproto.save_loaded: 145 + return save_option.get_item_text(i) + " is currently loaded" 146 + i+=1 147 + return "Invalid save file loaded" 148 + # SAVES 149 + 150 + func set_save_file(save_uri): 151 + Atproto.config.Save = save_uri 152 + PopupMessage._show_popup("AtProto save loaded : " + get_current_save_name()) 153 + _refresh() 154 + pass 155 + 156 + func _on_load_save_button_down(): 157 + var save: OptionButton = Saves.get_node("save").get_node("OptionButton") 158 + Atproto.AtProtoClient.load_save(save.get_selected_metadata()) 159 + pass 160 + 161 + 162 + func get_current_save_name() -> String: 163 + if UserSave.current_loaded_slot != 99: 164 + return "Slot " + str(UserSave.current_loaded_slot + 1) 165 + 166 + var save_option: OptionButton = Saves.get_node("save").get_node("OptionButton") 167 + var i = 0 168 + while i < save_option.get_item_count(): 169 + var metadata = save_option.get_item_metadata(i) 170 + if metadata == Atproto.save_loaded: 171 + return save_option.get_item_text(i) 172 + i+=1 173 + return "" 174 + 175 + func _on_new_save_button_down(): 176 + var current = get_current_save_name() 177 + if current != "": 178 + SaveMenu.get_node("save_menu/duplicate").text = "Duplicate " + current 179 + SaveMenu.get_node("save_menu/duplicate").disabled = false 180 + else: 181 + SaveMenu.get_node("save_menu/duplicate").text = "Invalid Save" 182 + SaveMenu.get_node("save_menu/duplicate").disabled = true 183 + 184 + SaveMenu.visible = true 185 + 186 + 187 + # Create Save Menu 188 + func init_save_menu(): 189 + SaveMenu = AtProtoNewSaveMenu.instance() 190 + add_child(SaveMenu) 191 + SaveMenu.visible = false 192 + var x : Button 193 + SaveMenu.get_node("save_menu/close").connect("button_down", self, "close_save_menu") 194 + SaveMenu.get_node("save_menu/duplicate").connect("button_down", self, "create_save", [true]) 195 + SaveMenu.get_node("save_menu/new").connect("button_down", self, "create_save", [false]) 196 + 197 + func create_save(duplicate = false): 198 + var backup = backup_save() 199 + if !duplicate: 200 + PlayerData._reset_save() 201 + Atproto.AtProtoClient.create_save(funcref(self, "create_save_file")) 202 + close_save_menu() 203 + restore_save(backup) 204 + pass 205 + 206 + func save_file_created(record): 207 + var file_name = SaveMenu.get_node("save_menu/Panel/save_name/LineEdit").text 208 + PopupMessage._show_popup("AtProto save created : " + file_name) 209 + SaveMenu.get_node("save_menu/Panel/save_name/LineEdit").text = "" 210 + after_login() 211 + pass 212 + 213 + func create_save_file(save_record): 214 + var file_name = SaveMenu.get_node("save_menu/Panel/save_name/LineEdit").text 215 + var uri = save_record.uri 216 + Atproto.AtProtoClient.create_save_file(uri, file_name, funcref(self, "save_file_created")) 217 + pass 218 + 219 + func backup_save(): 220 + return { 221 + "inventory": PlayerData.inventory, 222 + "hotbar": PlayerData.hotbar, 223 + "cosmetics_unlocked": PlayerData.cosmetics_unlocked, 224 + "cosmetics_equipped": PlayerData.cosmetics_equipped, 225 + "new_cosmetics": PlayerData.new_cosmetics, 226 + "version": Globals.GAME_VERSION, 227 + "muted_players": PlayerData.players_muted, 228 + "hidden_players": PlayerData.players_hidden, 229 + "recorded_time": PlayerData.last_recorded_time, 230 + "money": PlayerData.money, 231 + "bait_inv": PlayerData.bait_inv, 232 + "bait_selected": PlayerData.bait_selected, 233 + "bait_unlocked": PlayerData.bait_unlocked, 234 + "shop": PlayerData.current_shop, 235 + "journal": PlayerData.journal_logs, 236 + "quests": PlayerData.current_quests, 237 + "completed_quests": PlayerData.completed_quests, 238 + "level": PlayerData.badge_level, 239 + "xp": PlayerData.badge_xp, 240 + "max_bait": PlayerData.max_bait, 241 + "lure_unlocked": PlayerData.lure_unlocked, 242 + "lure_selected": PlayerData.lure_selected, 243 + "saved_aqua_fish": PlayerData.saved_aqua_fish, 244 + "inbound_mail": PlayerData.inbound_mail, 245 + "rod_power": PlayerData.rod_power_level, 246 + "rod_speed": PlayerData.rod_speed_level, 247 + "rod_chance": PlayerData.rod_chance_level, 248 + "rod_luck": PlayerData.rod_luck_level, 249 + "saved_tags": PlayerData.saved_tags, 250 + "loan_level": PlayerData.loan_level, 251 + "loan_left": PlayerData.loan_left, 252 + "buddy_level": PlayerData.buddy_level, 253 + "buddy_speed": PlayerData.buddy_speed, 254 + "guitar_shapes": PlayerData.guitar_shapes, 255 + "fish_caught": PlayerData.fish_caught, 256 + "cash_total": PlayerData.cash_total, 257 + "voice_pitch": PlayerData.voice_pitch, 258 + "voice_speed": PlayerData.voice_speed, 259 + "locked_refs": PlayerData.locked_refs, 260 + }.duplicate(true) 261 + 262 + func restore_save(data): 263 + PlayerData.inventory = data.inventory 264 + PlayerData.hotbar = data.hotbar 265 + PlayerData.cosmetics_unlocked = data.cosmetics_unlocked 266 + PlayerData.cosmetics_equipped = data.cosmetics_equipped 267 + PlayerData.new_cosmetics = data.new_cosmetics 268 + PlayerData.players_muted = data.muted_players 269 + PlayerData.players_hidden = data.hidden_players 270 + PlayerData.last_recorded_time = data.recorded_time 271 + PlayerData.money = data.money 272 + PlayerData.bait_inv = data.bait_inv 273 + PlayerData.bait_selected = data.bait_selected 274 + PlayerData.bait_unlocked = data.bait_unlocked 275 + PlayerData.current_shop = data.shop 276 + PlayerData.journal_logs = data.journal 277 + PlayerData.current_quests = data.quests 278 + PlayerData.completed_quests = data.completed_quests 279 + PlayerData.badge_level = data.level 280 + PlayerData.badge_xp = data.xp 281 + PlayerData.max_bait = data.max_bait 282 + PlayerData.lure_unlocked = data.lure_unlocked 283 + PlayerData.lure_selected = data.lure_selected 284 + PlayerData.saved_aqua_fish = data.saved_aqua_fish 285 + PlayerData.inbound_mail = data.inbound_mail 286 + PlayerData.rod_power_level = data.rod_power 287 + PlayerData.rod_speed_level = data.rod_speed 288 + PlayerData.rod_chance_level = data.rod_chance 289 + PlayerData.rod_luck_level = data.rod_luck 290 + PlayerData.saved_tags = data.saved_tags 291 + PlayerData.loan_level = data.loan_level 292 + PlayerData.loan_left = data.loan_left 293 + PlayerData.buddy_level = data.buddy_level 294 + PlayerData.buddy_speed = data.buddy_speed 295 + PlayerData.guitar_shapes = data.guitar_shapes 296 + PlayerData.fish_caught = data.fish_caught 297 + PlayerData.cash_total = data.cash_total 298 + PlayerData.voice_pitch = data.voice_pitch 299 + PlayerData.voice_speed = data.voice_speed 300 + PlayerData.locked_refs = data.locked_refs 301 + 302 + 303 + func close_save_menu(): 304 + SaveMenu.visible = false
+302
project/mods/Atproto/ui/menus/atproto_config.tscn
··· 1 + [gd_scene load_steps=8 format=2] 2 + 3 + [ext_resource path="res://Scenes/Menus/Main Menu/ui_generic_button.gd" type="Script" id=1] 4 + [ext_resource path="res://Assets/Themes/accid___.ttf" type="DynamicFontData" id=3] 5 + [ext_resource path="res://mods/Atproto/ui/menus/atproto_config.gd" type="Script" id=4] 6 + [ext_resource path="res://mods/TackleBox/assets/mod_menu.tres" type="Theme" id=5] 7 + [ext_resource path="res://Assets/Textures/UI/knot_sep2.png" type="Texture" id=6] 8 + 9 + [sub_resource type="DynamicFont" id=2] 10 + size = 48 11 + font_data = ExtResource( 3 ) 12 + 13 + [sub_resource type="StyleBoxFlat" id=1] 14 + content_margin_left = 16.0 15 + content_margin_right = 16.0 16 + content_margin_top = 16.0 17 + content_margin_bottom = 16.0 18 + bg_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 19 + corner_radius_top_left = 16 20 + corner_radius_top_right = 16 21 + corner_radius_bottom_right = 16 22 + corner_radius_bottom_left = 16 23 + 24 + [node name="atproto_config" type="Control"] 25 + anchor_right = 1.0 26 + anchor_bottom = 1.0 27 + rect_pivot_offset = Vector2( -335, -90 ) 28 + theme = ExtResource( 5 ) 29 + script = ExtResource( 4 ) 30 + 31 + [node name="background" type="ColorRect" parent="."] 32 + anchor_right = 1.0 33 + anchor_bottom = 1.0 34 + size_flags_horizontal = 3 35 + size_flags_vertical = 3 36 + color = Color( 0.0627451, 0.109804, 0.192157, 0.431373 ) 37 + 38 + [node name="Panel" type="Panel" parent="."] 39 + anchor_left = 0.3 40 + anchor_top = 0.2 41 + anchor_right = 0.7 42 + anchor_bottom = 0.8 43 + 44 + [node name="Title" type="Label" parent="Panel"] 45 + unique_name_in_owner = true 46 + margin_left = 16.0 47 + margin_top = 16.0 48 + margin_right = 752.0 49 + margin_bottom = 58.0 50 + theme = ExtResource( 5 ) 51 + custom_fonts/font = SubResource( 2 ) 52 + text = "CONFIGURING ATPROTO" 53 + align = 1 54 + 55 + [node name="sep" type="TextureRect" parent="Panel"] 56 + anchor_left = 0.5 57 + anchor_right = 0.5 58 + margin_left = -384.0 59 + margin_top = 80.0 60 + margin_right = 384.0 61 + margin_bottom = 112.0 62 + rect_min_size = Vector2( 0, 32 ) 63 + size_flags_horizontal = 3 64 + texture = ExtResource( 6 ) 65 + expand = true 66 + stretch_mode = 4 67 + 68 + [node name="Panel" type="Panel" parent="Panel"] 69 + anchor_right = 1.0 70 + anchor_bottom = 1.0 71 + margin_left = 16.0 72 + margin_top = 131.0 73 + margin_right = -16.0 74 + margin_bottom = -16.0 75 + custom_styles/panel = SubResource( 1 ) 76 + 77 + [node name="ScrollContainer" type="ScrollContainer" parent="Panel/Panel"] 78 + anchor_right = 1.0 79 + anchor_bottom = 1.0 80 + margin_left = 16.0 81 + margin_top = 8.0 82 + margin_right = -16.0 83 + margin_bottom = -51.0 84 + __meta__ = { 85 + "_editor_description_": " " 86 + } 87 + 88 + [node name="atproto_settings" type="VBoxContainer" parent="Panel/Panel/ScrollContainer"] 89 + unique_name_in_owner = true 90 + margin_right = 704.0 91 + margin_bottom = 428.0 92 + size_flags_horizontal = 3 93 + custom_constants/separation = 40 94 + 95 + [node name="credentials" type="VBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings"] 96 + margin_right = 704.0 97 + margin_bottom = 176.0 98 + size_flags_horizontal = 3 99 + size_flags_vertical = 3 100 + custom_constants/separation = 4 101 + 102 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials"] 103 + margin_right = 704.0 104 + margin_bottom = 32.0 105 + text = "Credentials" 106 + align = 1 107 + 108 + [node name="handle" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials"] 109 + margin_top = 36.0 110 + margin_right = 704.0 111 + margin_bottom = 68.0 112 + 113 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/handle"] 114 + margin_right = 350.0 115 + margin_bottom = 32.0 116 + size_flags_horizontal = 3 117 + text = "Handle:" 118 + 119 + [node name="LineEdit" type="LineEdit" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/handle"] 120 + margin_left = 354.0 121 + margin_right = 704.0 122 + margin_bottom = 32.0 123 + size_flags_horizontal = 3 124 + placeholder_text = "alice.bsky.social" 125 + caret_blink = true 126 + __meta__ = { 127 + "_editor_description_": "" 128 + } 129 + 130 + [node name="password" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials"] 131 + margin_top = 72.0 132 + margin_right = 704.0 133 + margin_bottom = 104.0 134 + 135 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/password"] 136 + margin_right = 350.0 137 + margin_bottom = 32.0 138 + size_flags_horizontal = 3 139 + text = "App Password:" 140 + 141 + [node name="LineEdit" type="LineEdit" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/password"] 142 + margin_left = 354.0 143 + margin_right = 704.0 144 + margin_bottom = 32.0 145 + size_flags_horizontal = 3 146 + secret = true 147 + placeholder_text = "mysecretpassword" 148 + caret_blink = true 149 + __meta__ = { 150 + "_editor_description_": "" 151 + } 152 + 153 + [node name="autoconnect" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials"] 154 + margin_top = 108.0 155 + margin_right = 704.0 156 + margin_bottom = 140.0 157 + 158 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/autoconnect"] 159 + margin_right = 350.0 160 + margin_bottom = 32.0 161 + size_flags_horizontal = 3 162 + text = "Auto-connect:" 163 + 164 + [node name="OptionButton" type="OptionButton" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials/autoconnect"] 165 + margin_left = 354.0 166 + margin_right = 704.0 167 + margin_bottom = 32.0 168 + size_flags_horizontal = 3 169 + 170 + [node name="connect_button" type="Button" parent="Panel/Panel/ScrollContainer/atproto_settings/credentials"] 171 + margin_top = 144.0 172 + margin_right = 704.0 173 + margin_bottom = 176.0 174 + size_flags_horizontal = 5 175 + text = "Connect" 176 + 177 + [node name="saves" type="VBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings"] 178 + margin_top = 216.0 179 + margin_right = 704.0 180 + margin_bottom = 356.0 181 + 182 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/saves"] 183 + margin_right = 704.0 184 + margin_bottom = 32.0 185 + text = "Saves" 186 + align = 1 187 + 188 + [node name="autoload" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/saves"] 189 + margin_top = 36.0 190 + margin_right = 704.0 191 + margin_bottom = 68.0 192 + 193 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/autoload"] 194 + margin_right = 350.0 195 + margin_bottom = 32.0 196 + size_flags_horizontal = 3 197 + text = "Autoload :" 198 + 199 + [node name="OptionButton" type="OptionButton" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/autoload"] 200 + margin_left = 354.0 201 + margin_right = 704.0 202 + margin_bottom = 32.0 203 + size_flags_horizontal = 3 204 + 205 + [node name="save" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/saves"] 206 + margin_top = 72.0 207 + margin_right = 704.0 208 + margin_bottom = 104.0 209 + 210 + [node name="Label" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/save"] 211 + margin_right = 350.0 212 + margin_bottom = 32.0 213 + size_flags_horizontal = 3 214 + text = "Save :" 215 + 216 + [node name="OptionButton" type="OptionButton" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/save"] 217 + margin_left = 354.0 218 + margin_right = 704.0 219 + margin_bottom = 32.0 220 + size_flags_horizontal = 3 221 + 222 + [node name="buttons" type="HBoxContainer" parent="Panel/Panel/ScrollContainer/atproto_settings/saves"] 223 + margin_top = 108.0 224 + margin_right = 704.0 225 + margin_bottom = 140.0 226 + 227 + [node name="new_save" type="Button" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/buttons"] 228 + margin_right = 350.0 229 + margin_bottom = 32.0 230 + size_flags_horizontal = 3 231 + text = "New Save" 232 + 233 + [node name="load_save" type="Button" parent="Panel/Panel/ScrollContainer/atproto_settings/saves/buttons"] 234 + margin_left = 354.0 235 + margin_right = 704.0 236 + margin_bottom = 32.0 237 + size_flags_horizontal = 3 238 + text = "Load Save" 239 + 240 + [node name="save_info" type="Label" parent="Panel/Panel/ScrollContainer/atproto_settings"] 241 + margin_top = 396.0 242 + margin_right = 704.0 243 + margin_bottom = 428.0 244 + text = "[No Save File Loaded]" 245 + align = 1 246 + 247 + [node name="Loader" type="Panel" parent="Panel"] 248 + visible = false 249 + modulate = Color( 1, 1, 1, 0.654902 ) 250 + anchor_right = 1.0 251 + anchor_bottom = 1.0 252 + margin_left = 16.0 253 + margin_top = 131.0 254 + margin_right = -16.0 255 + margin_bottom = -16.0 256 + custom_styles/panel = SubResource( 1 ) 257 + 258 + [node name="Container" type="CenterContainer" parent="Panel/Loader"] 259 + margin_left = -16.0 260 + margin_top = -127.0 261 + margin_right = 755.0 262 + margin_bottom = 523.0 263 + 264 + [node name="Title" type="Label" parent="Panel/Loader/Container"] 265 + margin_left = 314.0 266 + margin_top = 297.0 267 + margin_right = 457.0 268 + margin_bottom = 353.0 269 + size_flags_horizontal = 3 270 + size_flags_vertical = 3 271 + theme = ExtResource( 5 ) 272 + custom_colors/font_color = Color( 0, 0, 0, 1 ) 273 + custom_fonts/font = SubResource( 2 ) 274 + text = "Loading..." 275 + 276 + [node name="close" type="Button" parent="Panel"] 277 + anchor_left = 1.0 278 + anchor_right = 1.0 279 + margin_left = -24.0 280 + margin_top = -16.0 281 + margin_right = 16.0 282 + margin_bottom = 24.0 283 + text = "X" 284 + script = ExtResource( 1 ) 285 + 286 + [node name="apply" type="Button" parent="Panel"] 287 + anchor_left = 1.0 288 + anchor_top = 1.0 289 + anchor_right = 1.0 290 + anchor_bottom = 1.0 291 + margin_left = -188.0 292 + margin_top = -56.0 293 + margin_right = 24.0 294 + margin_bottom = 24.0 295 + text = "Save Changes" 296 + script = ExtResource( 1 ) 297 + 298 + [connection signal="button_down" from="Panel/Panel/ScrollContainer/atproto_settings/credentials/connect_button" to="." method="_on_connect_button_button_down"] 299 + [connection signal="button_down" from="Panel/Panel/ScrollContainer/atproto_settings/saves/buttons/new_save" to="." method="_on_new_save_button_down"] 300 + [connection signal="button_down" from="Panel/Panel/ScrollContainer/atproto_settings/saves/buttons/load_save" to="." method="_on_load_save_button_down"] 301 + [connection signal="pressed" from="Panel/close" to="." method="_on_close_pressed"] 302 + [connection signal="pressed" from="Panel/apply" to="." method="_on_apply_pressed"]
+168
project/mods/Atproto/ui/menus/new_save.tscn
··· 1 + [gd_scene load_steps=8 format=2] 2 + 3 + [ext_resource path="res://Assets/Themes/main.tres" type="Theme" id=1] 4 + [ext_resource path="res://Scenes/Menus/Main Menu/ui_generic_button.gd" type="Script" id=2] 5 + [ext_resource path="res://Assets/Themes/accid___.ttf" type="DynamicFontData" id=3] 6 + [ext_resource path="res://mods/TackleBox/assets/mod_menu.tres" type="Theme" id=4] 7 + [ext_resource path="res://Assets/Textures/UI/knot_sep2.png" type="Texture" id=5] 8 + 9 + [sub_resource type="DynamicFont" id=1] 10 + size = 48 11 + font_data = ExtResource( 3 ) 12 + 13 + [sub_resource type="StyleBoxFlat" id=2] 14 + content_margin_left = 16.0 15 + content_margin_right = 16.0 16 + content_margin_top = 16.0 17 + content_margin_bottom = 16.0 18 + bg_color = Color( 0.835294, 0.666667, 0.45098, 1 ) 19 + corner_radius_top_left = 16 20 + corner_radius_top_right = 16 21 + corner_radius_bottom_right = 16 22 + corner_radius_bottom_left = 16 23 + 24 + [node name="Node2D" type="Control"] 25 + anchor_right = 1.0 26 + anchor_bottom = 1.0 27 + theme = ExtResource( 1 ) 28 + 29 + [node name="background" type="ColorRect" parent="."] 30 + anchor_right = 1.0 31 + anchor_bottom = 1.0 32 + margin_left = -83.0 33 + margin_top = -47.0 34 + margin_right = 33.0 35 + margin_bottom = 29.0 36 + size_flags_horizontal = 3 37 + size_flags_vertical = 3 38 + color = Color( 0.0627451, 0.109804, 0.192157, 0.431373 ) 39 + 40 + [node name="save_menu" type="Panel" parent="."] 41 + unique_name_in_owner = true 42 + anchor_left = 0.3 43 + anchor_top = 0.374 44 + anchor_right = 0.712 45 + anchor_bottom = 0.675 46 + margin_top = 0.0799866 47 + margin_right = 0.959961 48 + 49 + [node name="Title" type="Label" parent="save_menu"] 50 + margin_left = 16.0 51 + margin_top = 16.0 52 + margin_right = 752.0 53 + margin_bottom = 58.0 54 + theme = ExtResource( 4 ) 55 + custom_fonts/font = SubResource( 1 ) 56 + text = "Create a new save" 57 + align = 1 58 + 59 + [node name="sep" type="TextureRect" parent="save_menu"] 60 + anchor_left = 0.5 61 + anchor_right = 0.5 62 + margin_left = -384.0 63 + margin_top = 80.0 64 + margin_right = 384.0 65 + margin_bottom = 112.0 66 + rect_min_size = Vector2( 0, 32 ) 67 + size_flags_horizontal = 3 68 + texture = ExtResource( 5 ) 69 + expand = true 70 + stretch_mode = 4 71 + 72 + [node name="Panel" type="Panel" parent="save_menu"] 73 + anchor_left = 0.119 74 + anchor_top = 0.403 75 + anchor_right = 0.893 76 + anchor_bottom = 0.745 77 + margin_left = -0.248001 78 + margin_top = 0.0249939 79 + margin_right = -0.256042 80 + margin_bottom = -0.125015 81 + custom_styles/panel = SubResource( 2 ) 82 + 83 + [node name="save_name" type="VBoxContainer" parent="save_menu/Panel"] 84 + anchor_right = 1.0 85 + anchor_bottom = 1.0 86 + margin_left = 40.0 87 + margin_top = 8.0 88 + margin_right = -52.0 89 + margin_bottom = -35.0 90 + size_flags_horizontal = 3 91 + 92 + [node name="Label" type="Label" parent="save_menu/Panel/save_name"] 93 + margin_right = 520.0 94 + margin_bottom = 32.0 95 + text = "Save Name" 96 + align = 1 97 + 98 + [node name="LineEdit" type="LineEdit" parent="save_menu/Panel/save_name"] 99 + margin_top = 36.0 100 + margin_right = 520.0 101 + margin_bottom = 68.0 102 + size_flags_horizontal = 3 103 + 104 + [node name="Loader" type="Panel" parent="save_menu"] 105 + visible = false 106 + modulate = Color( 1, 1, 1, 0.654902 ) 107 + anchor_right = 1.0 108 + anchor_bottom = 1.0 109 + margin_left = 16.0 110 + margin_top = 131.0 111 + margin_right = -16.0 112 + margin_bottom = -16.0 113 + custom_styles/panel = SubResource( 2 ) 114 + 115 + [node name="Container" type="CenterContainer" parent="save_menu/Loader"] 116 + margin_left = -16.0 117 + margin_top = -127.0 118 + margin_right = 755.0 119 + margin_bottom = 523.0 120 + 121 + [node name="Title" type="Label" parent="save_menu/Loader/Container"] 122 + margin_left = 314.0 123 + margin_top = 297.0 124 + margin_right = 457.0 125 + margin_bottom = 353.0 126 + size_flags_horizontal = 3 127 + size_flags_vertical = 3 128 + theme = ExtResource( 4 ) 129 + custom_colors/font_color = Color( 0, 0, 0, 1 ) 130 + custom_fonts/font = SubResource( 1 ) 131 + text = "Loading..." 132 + 133 + [node name="close" type="Button" parent="save_menu"] 134 + anchor_left = 1.0 135 + anchor_right = 1.0 136 + margin_left = -24.0 137 + margin_top = -16.0 138 + margin_right = 16.0 139 + margin_bottom = 24.0 140 + text = "X" 141 + script = ExtResource( 2 ) 142 + 143 + [node name="duplicate" type="Button" parent="save_menu"] 144 + anchor_left = 0.734 145 + anchor_top = 1.0 146 + anchor_right = 0.734 147 + anchor_bottom = 1.0 148 + margin_left = -211.712 149 + margin_top = -56.0 150 + margin_right = 0.287964 151 + margin_bottom = 24.0 152 + grow_horizontal = 0 153 + rect_pivot_offset = Vector2( 89, 24 ) 154 + text = "Duplicate Save" 155 + script = ExtResource( 2 ) 156 + 157 + [node name="new" type="Button" parent="save_menu"] 158 + anchor_left = 1.0 159 + anchor_top = 1.0 160 + anchor_right = 1.0 161 + anchor_bottom = 1.0 162 + margin_left = -188.0 163 + margin_top = -56.0 164 + margin_right = 24.0 165 + margin_bottom = 24.0 166 + rect_pivot_offset = Vector2( 89, 24 ) 167 + text = "Create Save" 168 + script = ExtResource( 2 )
+21
project/project.godot
··· 1 + ; Engine configuration file. 2 + ; It's best edited using the editor UI and not directly, 3 + ; since the parameters that go here are not all obvious. 4 + ; 5 + ; Format: 6 + ; [section] ; section goes between [] 7 + ; param=value ; assign values to parameters 8 + 9 + config_version=4 10 + 11 + [application] 12 + 13 + config/name="AtProto Webfishing" 14 + 15 + [gui] 16 + 17 + common/drop_mouse_on_gui_input_disabled=true 18 + 19 + [physics] 20 + 21 + common/enable_pause_aware_picking=true