A typical script lives briefly: it starts, does the job, and exits. But sometimes you want to keep settings and results longer — across script restarts and even share them with other scripts. EyeAuras provides “external” variables — at the level of scripts, auras, and folders — for that.
Main use cases:
var x = 123;
int MyCounter { get; set; }
// ...
MyCounter = 16;
Log.Info($"Counter: {MyCounter}");
In raw form, you can read/write variables via an indexer by string key:
Variables["myCounter"] = 1;
Log.Info($"Counter: {Variables["myCounter"]}");
Cons: the type is lost, you need manual casting. In C#, that’s inconvenient and brittle.
Solution — ScriptVariable
var myCounter = Variables.Get<int>("myCounter");
myCounter.Value = myCounter.Value + 1;
Log.Info($"Counter value: {myCounter}"); // 2, 3, 4, ...
Looks like a regular variable: you read, write, log. But internally, the value is stored in a shared “variables store” accessible to other scripts.
var v = Variables.Get<float>("speed");
v.Value = 1.5f;
var auraVar = Variables.Get<int>(@".\Gameplay\Bosses\Shaper", "attempts");
auraVar.Value++;
var aura = AuraTree.GetAuraByPath(@".\Something");
var auraVar = Variables.Get<string>(aura, "status");
Log.Info($"Status: {auraVar.Value}");
Simple rules below — better to understand once than to debug for long.
Value
var i = Variables.Get<int>("i");
Log.Info(i.Value); // 0 if the variable is not set
i.Value = 42; // wrote 42
HasValue
var s = Variables.Get<string>("name");
Log.Info(s.HasValue); // false, no record
s.Value = null;
Log.Info(s.HasValue); // true, record exists but value is null
TryGetValue(out T value)
var f = Variables.Get<float>("ratio");
if (f.TryGetValue(out var val))
{
Log.Info($"ratio = {val}");
}
else
{
Log.Info("ratio is not set or null or cannot be converted to float");
}
GetOrThrow()
var required = Variables.Get<int>("port");
var port = required.GetOrThrow(); // will throw if no record, null, or wrong type
Remove()
var v = Variables.Get<int>("x").Set(10);
v.Remove(); // true
Log.Info(v.Value); // 0
Log.Info(v.HasValue); // false
Implicit conversion to T
var myIntVar = Variables.Get<int>("score").Set(5);
int i = myIntVar; // 5
var missing = Variables.Get<int>("missing");
int j = missing; // 0
String representation
var a = Variables.Get<int>("a");
Log.Info(a.ToString()); // "a = <not set>"
a.Value = 10;
Log.Info(a.ToString()); // "a = 10"
ScriptVariable
Examples:
// 1) Matching types — straightforward
Variables.Get<int>("n").Set(123);
Log.Info(Variables.Get<int>("n").Value); // 123
// 2) Reading as another type
((IScriptVariable)Variables.Get<int>("mix")).Value = "abc"; // write a string "bypassing" type safety
var v = Variables.Get<int>("mix");
Log.Info(v.Value); // 0 — default(int)
Log.Info(v.TryGetValue(out var x)); // false
// v.GetOrThrow(); // throws InvalidOperationException
// 3) Almost anything to string
((IScriptVariable)Variables.Get<string>("text")).Value = 42;
Log.Info(Variables.Get<string>("text").Value); // "42"
Tip: if you need a specific conversion, register a converter in IBindingValueConverterRepository. Then TryGetValue/Value will start returning your desired type.
Want to reactively respond to variable changes? Use Listen().
Example:
var hp = Variables.Get<int>("hp");
var observed = new List<int>();
using var sub = hp.Listen().Subscribe(x => {
Log.Info($"HP: {x}");
observed.Add(x);
});
hp.Value = 100; // HP: 100
hp.Value = 100; // not emitted (distinct)
hp.Value = 90; // HP: 90
hp.Remove(); // HP: 0 (default)
For reference types, the first value will be null, then new values, then null again on Remove() or if you set null.
Each aura/folder is also a variables container. There’s a hierarchy: children “see” the parent’s values and can override them.
Two access methods:
var auraVar = Variables.Get<int>(@".\Something", "myCounter");
auraVar.Value = 16;
var aura = AuraTree.GetAuraByPath(@".\Something");
var auraVar = Variables.Get<int>(aura, "myCounter");
auraVar.Value = 16;
Why this matters: set a “default” on a folder — all nested auras can read it. Where needed, override locally. Very convenient for shared settings (target window, input simulator, etc.).
Nuance: variables prefixed with default. are often used as “base values” across the tree. If you override, say, default.WindowSelector.TargetWindow — it will affect window selection in all nested items.
For convenience there are operators and implicit conversions.
var score = Variables.Get<int>("score").Set(5);
int current = score; // 5
var a = Variables.Get<int>("a").Set(3);
var b = Variables.Get<int>("b").Set(3);
if (a == b) { /* same values */ }
if (a != b) { /* different values */ }
var x = Variables.Get<int>("x").Set(2);
var y = Variables.Get<int>("y").Set(3);
if (y > x) { /* yes, 3 > 2 */ }
if (x < 3) { /* yes */ }
Example “config -> worker”:
// Config script
Variables.Get<string>("target").Set("Shaper");
Variables.Get<int>("maxAttempts").Set(5);
// Worker script
var target = Variables.Get<string>("target").GetOrThrow();
var maxAttempts = Variables.Get<int>("maxAttempts").GetOrThrow();
for (var i = 0; i < maxAttempts; i++)
{
Log.Info($"Try {i+1} on {target}");
}
“Why did Value return 0/false/null instead of throwing?”
“TryGetValue returned false for null — is that a bug?”
“Why didn’t Listen() emit the same value the second time?”
“I removed a variable, what will Listen() emit?”
var v = Variables.Get<int>("n");
// read/write
int x = v.Value; // safe, default if no value/wrong type
v.Value = 10; // write
// strict scenarios
var required = v.GetOrThrow(); // exception if missing/wrong type/null
// careful read
if (v.TryGetValue(out var val)) { /* ok */ } else { /* no value */ }
// subscribe to changes
using var sub = v.Listen().Subscribe(x => Log.Info($"n = {x}"));
// remove
var removed = v.Remove();
// misc
bool has = v.HasValue; // does the record exist?
string name = v.Name;
IHasVariables src = v.Source; // where it reads from/writes to