Skip to content

Commit

Permalink
🤏 micro commit
Browse files Browse the repository at this point in the history
buff docs by 15%
fix forced sends not sending when disabled
update credits
add (optional) auth for immersion to match swarm control
export redeems to clipboard
auto-destroy spawns from redeems
  • Loading branch information
Govorunb committed Jul 6, 2024
1 parent 5300a38 commit 5f9e78d
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 55 deletions.
6 changes: 6 additions & 0 deletions Immersion/ConsoleCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private static string ProcessCommand(IReadOnlyList<string> args)
return subCommand switch
{
"set" => SetGlobal(args),
"trackers" => ListTrackers(),
"enable" or "disable" => EnableDisable(args),
"mute" or "unmute" => ManualMute(subCommand is "mute"),
"react" or "send" => ManualSend(args),
Expand All @@ -55,6 +56,11 @@ private static string ProcessCommand(IReadOnlyList<string> args)
};
}

private static string ListTrackers()
{
return string.Join(", ", Tracker.trackerTypes.Keys.Prepend("sender"));
}

private static string SetGlobal(IReadOnlyList<string> args)
{
if (args is not ["set", string fieldName, ..])
Expand Down
8 changes: 7 additions & 1 deletion Immersion/Globals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ namespace Immersion;
public static class Globals
{
private const string PlayerNameKey = $"{nameof(Immersion)}_PlayerName";
private const string BaseUrlKey = $"{nameof(Immersion)}_BaseURL";
private const string PronounsKey = $"{nameof(Immersion)}_Pronouns";
private const string BaseUrlKey = $"{nameof(Immersion)}_BaseURL";
private const string ApiKeyKey = $"{nameof(Immersion)}_ApiKey";

private const string DefaultPlayerName = "Vedal";
private const string DefaultUrl = "http://localhost:8000/subnautica/";
Expand All @@ -22,6 +23,11 @@ public static string BaseUrl
get => PlayerPrefs.GetString(BaseUrlKey, DefaultUrl);
set => PlayerPrefs.SetString(BaseUrlKey, value);
}
public static string ApiKey
{
get => PlayerPrefs.GetString(ApiKeyKey, "");
set => PlayerPrefs.SetString(ApiKeyKey, value);
}

private static PronounSet? _playerPronouns;
public static PronounSet PlayerPronouns
Expand Down
34 changes: 34 additions & 0 deletions Immersion/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Basic cruddy "send stuff to LLM" mod.

### Setup

Set up a web server at your chosen URL that will accept POST requests on the following endpoints:
- `/mute` - mute/unmute (to reduce blabbering during cutscenes)
- accepts `True` and `False` in POST body
- `/priority` and `/non-priority` - send messages (general context/react stuff)
- accepts the message as a raw string in POST body (e.g. `Something happened`, note no quotation marks)

Then, go in game and run the following commands in the console (`SHIFT + Enter` to open):
- `immersion set player Your Name`
- `immersion set url http://yoururl:12345`
- If your API uses auth, run `immersion set apikey yoursecretapikey`
- `immersion set pronouns you/your`

The default configuration is [defined here](./Globals.cs#L11).

### Console commands
Available console commands are:
- `immersion set <option> <value>` - set config value
- Available options are `player`, `url`, `apikey`, and `pronouns`
- For `pronouns`, there are several [predefined sets of pronouns](./Formatting/PronounSet.cs#L27)
- Otherwise, the command output will guide you to the correct formatting
- `immersion trackers` - list available trackers
- `immersion <enable|disable> [tracker...]` - toggle one or more trackers
- If no trackers are specified (i.e. `immersion enable`) all trackers will be affected
- For a list of available trackers, run `immersion trackers`
- `immersion <prio|noprio>` - force low prio
- While active, all high-priority messages will be routed to the `non-priority` endpoint.
- `noprio` activates this effect and `prio` deactivates it.
- `immersion <react|send> <message>` - manually send with high (for `react`) or low (for `send`) priority.
- Affected by `noprio`.
- `immersion <mute|unmute>` - manual mute/unmute.
5 changes: 4 additions & 1 deletion Immersion/Sender.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using UnityEngine.Networking;
using UWE;

namespace Immersion;

Expand All @@ -17,13 +18,15 @@ public void Send(string path, object data = null)

Uri uri = new(new(Globals.BaseUrl), path);
string postData = data?.ToString() ?? "";
StartCoroutine(SendCoro(uri, postData));
CoroutineHost.StartCoroutine(SendCoro(uri, postData));
}

private IEnumerator SendCoro(Uri uri, string data)
{
using UnityWebRequest req = UnityWebRequest.Post(uri, data);
req.SetRequestHeader("User-Agent", $"Unity/{Application.unityVersion} (SCHIZO.Immersion)");
if (!string.IsNullOrEmpty(Globals.ApiKey))
req.SetRequestHeader("Authorization", $"Bearer {Globals.ApiKey}");
LOGGER.LogWarning($"POST {uri}\n{data}");
yield return req.SendWebRequest();
}
Expand Down
5 changes: 2 additions & 3 deletions Immersion/Trackers/OxygenAlerts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ private void Update()

if (swimDistance > depth)
{
LOGGER.LogDebug($"Close to surface {swimDistance} > {depth}");

//LOGGER.LogDebug($"Close to surface {swimDistance} > {depth}");
continue;
}

Expand All @@ -151,7 +150,7 @@ private void Update()
float dist = (interior.GetGameObject().transform.position - _player.transform.position).magnitude;
if (swimDistance > dist)
{
LOGGER.LogDebug($"Base close by {swimDistance} > {dist}");
//LOGGER.LogDebug($"Base close by {swimDistance} > {dist}");
continue;
}
}
Expand Down
39 changes: 3 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,8 @@ Mod dependencies:
- [BepInEx Mono v5.4.23](https://github.com/BepInEx/BepInEx/releases/tag/v5.4.23.2) (not compatible with v6)
- [Nautilus 1.0.0-pre.29](https://github.com/SubnauticaModding/Nautilus/releases/tag/1.0.0-pre.29) (later 1.0.0 versions should work)

### Credits (currently outdated)

["2Pfrog"] = Credits.Artist,
["AlexejheroDev"] = Credits.Programming + Credits.ProjectLead,
["Azit"] = Credits.Artist,
["baa14453"] = Credits.Lore,
["bred"] = Credits.Modeling + Credits.Texturing + Credits.Animations,
["budwheizzah"] = Credits.Programming + Credits.Artist + Credits.Sounds,
["chrom"] = Credits.Artist,
["CJMAXiK"] = Credits.Artist + Credits.Sounds,
["darkeew"] = Credits.Contributor,
["FutabaKuuhaku"] = Credits.Modeling + Credits.Texturing,
["Goldenbeasty"] = Credits.Texturing,
["Govorunb"] = Credits.Programming,
["greencap"] = Credits.Modeling + Credits.Texturing,
["Hakuhan"] = Credits.Artist,
["Kat"] = Credits.Modeling + Credits.Texturing,
["Kaz"] = Credits.Artist,
["Lorx"] = Credits.Artist,
["Moloch"] = Credits.Artist,
["MyBraza"] = Credits.Artist + Credits.Sounds,
["NetPlayz"] = Credits.Artist,
["P3R"] = Credits.Artist,
["paccha"] = Credits.Artist,
["Rune"] = Credits.Artist,
["SADecsSs"] = Credits.Artist,
["Sandro"] = Credits.Artist,
["SomeOldGuy"] = Credits.Artist,
["sugarph"] = Credits.Artist,
["Troobs"] = Credits.Artist,
["Vaalmyr"] = Credits.Modeling + Credits.Texturing,
["vanorsigma"] = Credits.Contributor,
["w1n7er"] = Credits.Modeling + Credits.Texturing + Credits.Animations,
["yamplum"] = Credits.Artist + Credits.Lore,
["YuG"] = Credits.Modeling + Credits.Texturing,

Hull plates code based on ahk1221's [Custom Hull Plates](https://github.com/MrPurple6411/My-Subnautica-Mods/tree/main/CustomHullPlates) mod<br>
Using code based on Lee23's [ECCLibrary](https://github.com/LeeTwentyThree/ECCLibrary/releases/latest) mod

### Credits
![credits](https://github.com/Alexejhero/Neurosama-Subnautica-Mod/assets/3830522/fef46e4f-2cea-4d5d-a5d8-1d5769a7124e)
3 changes: 2 additions & 1 deletion SCHIZO/Commands/ConsoleCommands/SpawnCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ internal sealed class SpawnCommand() : ConsoleWrapperCommand("spawn")
private static readonly Parameter[] _parameters = [
new(new NamedModel("techType", "TechType", "The tech type to spawn."), typeof(TechType)),
new NumericParameter(new NamedModel("count", "Count", "Amount of entities to spawn."), true, 1),
new NumericParameter(new NamedModel("distance", "Distance", "Maximum distance from the player. Spawns behind if negative."), false, true)
new NumericParameter(new NamedModel("distance", "Distance", "Maximum distance from the player. Spawns behind if negative."), false, true),
new NumericParameter(new NamedModel("lifetime", "Lifetime", "(Internal) The spawned object will be auto-destroyed in this many seconds"), false, float.PositiveInfinity),
];
public override IReadOnlyList<Parameter> Parameters => [.. _parameters];
}
12 changes: 12 additions & 0 deletions SCHIZO/Items/Components/DestroyAtTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using UnityEngine;

namespace SCHIZO.Items.Components;
internal class DestroyAtTime : MonoBehaviour
{
public float time = float.PositiveInfinity;
private void Update()
{
if (Time.time > time)
Destroy(gameObject);
}
}
94 changes: 94 additions & 0 deletions SCHIZO/Patches/AutoDestroyManualSpawns.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using SCHIZO.Helpers;
using SCHIZO.Items.Components;
using UnityEngine;

namespace SCHIZO.Patches;

[HarmonyPatch]
internal static class AutoDestroyManualSpawns
{
private static readonly MethodInfo NotifyCraftEnd = AccessTools.Method(typeof(CrafterLogic), nameof(CrafterLogic.NotifyCraftEnd));
private static readonly MethodInfo UnityEngineDebugLogFormat = AccessTools.Method(typeof(Debug), nameof(Debug.LogFormat), [typeof(string), typeof(object[])]);

[HarmonyTargetMethod]
private static MethodBase TargetMethod() => AccessTools.EnumeratorMoveNext(AccessTools.Method(typeof(SpawnConsoleCommand), nameof(SpawnConsoleCommand.SpawnAsync)));

[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
CodeMatcher matcher = new(instructions, generator);
LocalBuilder lifetimeLocal = generator.DeclareLocal(typeof(float));

matcher.MatchForward(false, new CodeMatch(ci => ci.Calls(UnityEngineDebugLogFormat)));
if (!matcher.IsValid)
{
LOGGER.LogWarning("Could not patch SpawnConsoleCommand.SpawnAsync to auto destroy manual spawns (UnityEngine.Debug.LogFormat call not found)");
return instructions;
}
// i know it's kind of lazy to insert our instructions in the middle of a method call
// unfortunately there's a loop right afterwards so the alternative is even less elegant
//matcher.Advance(1);
// get lifetime from args
// float lifetime = GetLifetime(this);
matcher.Insert(
new(OpCodes.Ldarg_0),
new(OpCodes.Call, AccessTools.Method(typeof(AutoDestroyManualSpawns), nameof(GetLifetime))),
new(OpCodes.Stloc, lifetimeLocal)
);

// match:
// CrafterLogic.NotifyCraftEnd(gameObject, this.<techType>5__2);
matcher.MatchForward(false,
new(OpCodes.Dup),
new(OpCodes.Ldarg_0),
new(OpCodes.Ldfld),
new(ci => ci.Calls(NotifyCraftEnd))
);
if (!matcher.IsValid)
{
LOGGER.LogWarning("Could not patch SpawnConsoleCommand.SpawnAsync to auto destroy manual spawns (CrafterLogic.NotifyCraftEnd call not found)");
return instructions;
}
matcher.Insert(
new(OpCodes.Dup),
lifetimeLocal.LoadInstruction(),
CodeInstruction.Call((GameObject go, float lifetime) => AddManualSpawnTag(go, lifetime))
);

return matcher.InstructionEnumeration();
}

private static void AddManualSpawnTag(GameObject obj, float lifetime)
{
if (lifetime is <= 0 or float.PositiveInfinity) return;

float dieAt = Time.time + lifetime;
LOGGER.LogDebug($"{obj.name} ({obj.GetInstanceID()}) is fated to die in {lifetime}s (at {dieAt})");
DestroyAtTime destroy = obj.EnsureComponent<DestroyAtTime>();
destroy.time = dieAt;

// prevent unload/load since it would remove our manually-added component
Component.Destroy(obj.GetComponent<LargeWorldEntity>());
}

private static float GetLifetime(object iEnumerator)
{
Hashtable args = GetArgs(iEnumerator);
if (args.Count >= 4 && float.TryParse((string) args[3], out float lifetime))
{
return lifetime;
}
return float.PositiveInfinity;
}

private static Hashtable GetArgs(object iEnumerator)
{
NotificationCenter.Notification x = (NotificationCenter.Notification) AccessTools.Field(iEnumerator.GetType(), "n").GetValue(iEnumerator);
return x.data;
}
}
2 changes: 1 addition & 1 deletion SCHIZO/Resources/AssetBundles/Assets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SCHIZO.Resources;

public static class Assets
{
private const int _rnd = -1520284776;
private const int _rnd = 1553678597;

private static readonly UnityEngine.AssetBundle _a = ResourceManager.GetAssetBundle("assets");

Expand Down
Binary file modified SCHIZO/Resources/AssetBundles/assets
Binary file not shown.
5 changes: 3 additions & 2 deletions SCHIZO/SwarmControl/ExportRedeems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using SCHIZO.Commands.Attributes;
using SCHIZO.Commands.Base;
using SCHIZO.Commands.Context;
using SCHIZO.Commands.Output;
using SCHIZO.SwarmControl.Redeems;
using SwarmControl.Models.Game;
using UnityEngine;

namespace SCHIZO.SwarmControl;
[Command(Name = "sc_export",
Expand Down Expand Up @@ -40,7 +40,8 @@ protected override object ExecuteCore(CommandExecutionContext ctx)
NullValueHandling = NullValueHandling.Ignore,
});
LOGGER.LogInfo(json);
GUIUtility.systemCopyBuffer = json;

return CommonResults.OK();
return "Redeems JSON logged and copied to clipboard";
}
}
13 changes: 13 additions & 0 deletions SCHIZO/SwarmControl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Game client portion of the [Swarm Control](https://github.com/VedalAI/swarm-control) Twitch extension.

Intended for Below Zero, nothing is guaranteed to work in base Subnautica (it's low prio, PRs welcome)

### Setup
After setting up the [server](https://github.com/VedalAI/swarm-control), launch the game (with the mod) and do the following:
- Open the console with `SHIFT + Enter`
- Use the `sc_url` command to set the base URL for the server. e.g. `sc_url https://subnautica.example.com/`
- Set the private API key with `sc_apikey` - e.g. `sc_apikey totallysecretkey`
- Go into `Options` -> `Mods` and click `Connect Swarm Control`
- After the game connects and you load a save, any redeems you have configured should become available!

Running `sc_export` in the console will save the config json (without the `version` field) to your clipboard.
6 changes: 4 additions & 2 deletions SCHIZO/SwarmControl/Redeems/Spawns/SpawnAggressiveCreature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ namespace SCHIZO.SwarmControl.Redeems.Spawns;

#nullable enable
[CommandCategory("Spawns")]
[Redeem(Name = "spawn_aggressive",
[Redeem(
Name = "spawn_aggressive",
DisplayName = "Spawn Aggressive Creature",
Description = "Spawn an aggressive creature near the player"
Description = "Spawn an aggressive creature near the player. NOTE: Spawns are automatically cleaned up after some time."
)]
internal class SpawnAggressiveCreature : SpawnFiltered<AggressiveCreature>
{
protected override float SpawnDistance => 10;
protected override float Lifetime => 90f;
}
5 changes: 5 additions & 0 deletions SCHIZO/SwarmControl/Redeems/Spawns/SpawnFiltered.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ internal class SpawnFiltered<T> : ProxyCommand<SpawnCommand>
protected virtual string SpawnThingName => "Creature";
protected virtual string TechTypeParamName => SpawnThingName.ToLowerInvariant();
protected virtual float SpawnDistance => 5;
/// <summary>
/// After this many seconds, the spawned object will be destroyed.
/// </summary>
protected virtual float Lifetime => float.PositiveInfinity;

public override IReadOnlyList<Parameter> Parameters { get; }

Expand Down Expand Up @@ -63,6 +67,7 @@ public SpawnFiltered() : base("spawn")
}
bool behind = args.GetOrDefault("behind", false);
targetArgs["distance"] = behind ? -SpawnDistance : SpawnDistance;
targetArgs["lifetime"] = Lifetime;

return targetArgs;
}
Expand Down
7 changes: 5 additions & 2 deletions SCHIZO/SwarmControl/Redeems/Spawns/SpawnPassiveCreature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ namespace SCHIZO.SwarmControl.Redeems.Spawns;
[Redeem(
Name = "spawn_passive",
DisplayName = "Spawn Passive Creature",
Description = "Spawn a passive creature near the player"
Description = "Spawn a passive creature near the player. NOTE: Spawns are automatically cleaned up after some time."
)]
internal class SpawnPassiveCreature : SpawnFiltered<PassiveCreature>;
internal class SpawnPassiveCreature : SpawnFiltered<PassiveCreature>
{
protected override float Lifetime => 300f;
}
Loading

0 comments on commit 5f9e78d

Please sign in to comment.