Skip to content

Instantly share code, notes, and snippets.

@HexiDave
Last active February 20, 2018 13:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HexiDave/436fb5cac57c170e8633c07e1ce4d7f5 to your computer and use it in GitHub Desktop.
Save HexiDave/436fb5cac57c170e8633c07e1ce4d7f5 to your computer and use it in GitHub Desktop.
A mod for Subnautica to allow players to also pull resources from nearby containers at crafting windows
using System;
using System.Collections.Generic;
using ProtoBuf;
using UnityEngine;
using UWE;
// Token: 0x02000202 RID: 514
[ProtoContract]
public sealed class CrafterLogic : MonoBehaviour, IProtoEventListener
{
// Token: 0x17000138 RID: 312
// (get) Token: 0x06000B13 RID: 2835
public bool inProgress
{
get
{
return this.currentTechType != TechType.None && DayNightCycle.main.timePassed < (double)this.timeCraftingEnd;
}
}
// Token: 0x17000139 RID: 313
// (get) Token: 0x06000B14 RID: 2836
public float progress
{
get
{
float num3 = (float)((double)((float)((double)((float)DayNightCycle.main.timePassed))));
double num2 = (double)(this.timeCraftingEnd - this.timeCraftingBegin);
float value = (float)(((double)num3 - (double)this.timeCraftingBegin) / num2);
if (this.timeCraftingEnd > this.timeCraftingBegin)
{
return Mathf.Clamp01(value);
}
return -1f;
}
}
// Token: 0x1700013A RID: 314
// (get) Token: 0x06000B15 RID: 2837
public TechType currentTechType
{
get
{
if (this.linkedIndex > -1)
{
ITechData techData = CraftData.Get(this.craftingTechType, false);
if (techData != null && this.linkedIndex < techData.linkedItemCount)
{
return techData.GetLinkedItem(this.linkedIndex);
}
}
return this.craftingTechType;
}
}
// Token: 0x06000B16 RID: 2838
private void Update()
{
if (this.craftingTechType != TechType.None && this.lastTime < (double)this.timeCraftingEnd)
{
this.NotifyProgress(this.progress);
this.lastTime = DayNightCycle.main.timePassed;
if (this.lastTime >= (double)this.timeCraftingEnd)
{
this.NotifyEnd();
}
}
}
// Token: 0x06000B17 RID: 2839
public bool Craft(TechType techType, float craftTime)
{
if (craftTime > 0f)
{
ITechData techData = CraftData.Get(techType, false);
if (techData != null)
{
this.timeCraftingBegin = DayNightCycle.main.timePassedAsFloat;
this.timeCraftingEnd = this.timeCraftingBegin + craftTime + 0.1f;
this.craftingTechType = techType;
this.linkedIndex = -1;
this.numCrafted = techData.craftAmount;
this.NotifyChanged(this.craftingTechType);
this.NotifyProgress(0f);
return true;
}
}
return false;
}
// Token: 0x06000B18 RID: 2840
public void Reset()
{
this.timeCraftingBegin = -1f;
this.timeCraftingEnd = -1f;
this.craftingTechType = TechType.None;
this.linkedIndex = -1;
this.numCrafted = 0;
this.NotifyChanged(TechType.None);
this.NotifyProgress(0f);
}
// Token: 0x06000B19 RID: 2841
public void TryPickup()
{
if (this.craftingTechType == TechType.None || this.progress < 1f)
{
return;
}
ITechData techData = CraftData.Get(this.craftingTechType, false);
if (techData != null)
{
bool flag = false;
while (!flag)
{
TechType linkedItem = this.craftingTechType;
if (this.linkedIndex != -1)
{
linkedItem = techData.GetLinkedItem(this.linkedIndex);
}
while (this.numCrafted > 0)
{
if (!this.TryPickupSingle(linkedItem))
{
return;
}
this.numCrafted--;
}
if (this.numCrafted == 0)
{
this.linkedIndex++;
if (this.linkedIndex < techData.linkedItemCount)
{
this.numCrafted = 1;
this.NotifyChanged(this.currentTechType);
}
else
{
flag = true;
}
}
}
}
this.Reset();
}
// Token: 0x06000B1A RID: 2842
private bool TryPickupSingle(TechType techType)
{
Inventory main = Inventory.main;
bool flag = false;
GameObject gameObject = CraftData.GetPrefabForTechType(techType, true);
if (gameObject == null)
{
gameObject = global::Utils.genericLootPrefab;
flag = true;
}
if (!(gameObject != null))
{
UWE.Utils.LogReportFormat("Can't find prefab for TechType.{0}", new object[]
{
techType
});
return true;
}
Pickupable component = gameObject.GetComponent<Pickupable>();
if (!(component != null))
{
UWE.Utils.LogReportFormat("Can't find Pickupable component on prefab for TechType.{0}", new object[]
{
techType
});
return true;
}
Vector2int itemSize = CraftData.GetItemSize(component.GetTechType());
if (main.HasRoomFor(itemSize.x, itemSize.y))
{
GameObject gameObject2 = UnityEngine.Object.Instantiate<GameObject>(gameObject);
component = gameObject2.GetComponent<Pickupable>();
if (flag)
{
component.SetTechTypeOverride(techType, true);
}
CrafterLogic.NotifyCraftEnd(gameObject2, this.craftingTechType);
main.ForcePickup(component);
Player.main.PlayGrab();
return true;
}
ErrorMessage.AddMessage(Language.main.Get("InventoryFull"));
return false;
}
// Token: 0x06000B1B RID: 2843
private void NotifyChanged(TechType techType)
{
if (this.onItemChanged != null)
{
this.onItemChanged(techType);
}
}
// Token: 0x06000B1C RID: 2844
private void NotifyProgress(float progress)
{
if (this.onProgress != null)
{
this.onProgress(progress);
}
}
// Token: 0x06000B1D RID: 2845
private void NotifyEnd()
{
if (this.onDone != null)
{
this.onDone();
}
}
// Token: 0x06000B1E RID: 2846
public static bool IsCraftRecipeUnlocked(TechType techType)
{
return !GameModeUtils.RequiresBlueprints() || KnownTech.Contains(techType);
}
// Token: 0x06000B1F RID: 2847
public static bool IsCraftRecipeFulfilled(TechType techType)
{
if (Inventory.main == null)
{
return false;
}
if (!GameModeUtils.RequiresIngredients())
{
return true;
}
ItemsContainer[] itemContainers = CrafterLogic.FindAllItemsContainersInRange(100f);
ITechData techData = CraftData.Get(techType, false);
if (techData != null)
{
int i = 0;
int ingredientCount = techData.ingredientCount;
while (i < ingredientCount)
{
IIngredient ingredient = techData.GetIngredient(i);
if (CrafterLogic.GetTotalPickupCount(ingredient.techType, itemContainers) < ingredient.amount)
{
return false;
}
i++;
}
return true;
}
return false;
}
// Token: 0x06000B20 RID: 2848
public static bool ConsumeEnergy(PowerRelay powerRelay, float amount)
{
float num;
return !GameModeUtils.RequiresPower() || (!(powerRelay == null) && powerRelay.ConsumeEnergy(amount, out num));
}
// Token: 0x06000B21 RID: 2849
public static bool ConsumeResources(TechType techType)
{
if (!CrafterLogic.IsCraftRecipeFulfilled(techType))
{
ErrorMessage.AddWarning(Language.main.Get("DontHaveNeededIngredients"));
return false;
}
ItemsContainer[] itemsContainers = CrafterLogic.FindAllItemsContainersInRange(100f);
ITechData techData = CraftData.Get(techType, false);
if (techData == null)
{
return false;
}
int i = 0;
int ingredientCount = techData.ingredientCount;
while (i < ingredientCount)
{
IIngredient ingredient = techData.GetIngredient(i);
TechType techType2 = ingredient.techType;
int j = 0;
int amount = ingredient.amount;
while (j < amount)
{
CrafterLogic.DestroyItemInContainers(techType2, itemsContainers);
uGUI_IconNotifier.main.Play(techType2, uGUI_IconNotifier.AnimationType.To, null);
j++;
}
i++;
}
return true;
}
// Token: 0x06000B22 RID: 2850
public static void NotifyCraftEnd(GameObject target, TechType techType)
{
if (target == null)
{
return;
}
CrafterLogic.sCraftTargets.Clear();
target.GetComponentsInChildren<ICraftTarget>(true, CrafterLogic.sCraftTargets);
int i = 0;
int count = CrafterLogic.sCraftTargets.Count;
while (i < count)
{
CrafterLogic.sCraftTargets[i].OnCraftEnd(techType);
i++;
}
CrafterLogic.sCraftTargets.Clear();
}
// Token: 0x06000B23 RID: 2851
public void OnProtoSerialize(ProtobufSerializer serializer)
{
}
// Token: 0x06000B24 RID: 2852
public void OnProtoDeserialize(ProtobufSerializer serializer)
{
if (this.craftingTechType != TechType.None)
{
ITechData techData = CraftData.Get(this.craftingTechType, false);
if (techData != null)
{
if (this.linkedIndex != -1 && this.linkedIndex >= techData.linkedItemCount)
{
this.Reset();
}
}
else
{
this.Reset();
}
}
DayNightCycle main = DayNightCycle.main;
if (main != null)
{
this.lastTime = main.timePassed;
}
this.NotifyChanged(this.currentTechType);
this.NotifyProgress(this.progress);
}
// Token: 0x06000B25 RID: 2853
static CrafterLogic()
{
CrafterLogic.sCraftTargets = new List<ICraftTarget>();
}
// Token: 0x06000B26 RID: 2854
public static ItemsContainer[] FindAllItemsContainersInRange(float maxDistance = 100f)
{
if (DayNightCycle.main.timePassed > 1.0 + CrafterLogic.lastCheckTime || CrafterLogic.nearbyItemContainers == null)
{
float num = maxDistance * maxDistance;
StorageContainer[] array = UnityEngine.Object.FindObjectsOfType<StorageContainer>();
List<ItemsContainer> list = new List<ItemsContainer>();
foreach (StorageContainer storageContainer in array)
{
if (storageContainer.container != null && (Player.main.transform.position - storageContainer.transform.position).sqrMagnitude < num)
{
list.Add(storageContainer.container);
}
}
list.Add(Inventory.main.container);
CrafterLogic.nearbyItemContainers = list.ToArray();
CrafterLogic.lastCheckTime = DayNightCycle.main.timePassed;
}
return CrafterLogic.nearbyItemContainers;
}
// Token: 0x06000B27 RID: 2855
public static int GetTotalPickupCount(TechType techType, ItemsContainer[] itemContainers)
{
int num = 0;
foreach (ItemsContainer itemsContainer in itemContainers)
{
num += itemsContainer.GetCount(techType);
}
return num;
}
// Token: 0x06000B28 RID: 2856
public static bool DestroyItemInContainers(TechType techType, ItemsContainer[] itemsContainers)
{
for (int i = 0; i < itemsContainers.Length; i++)
{
if (itemsContainers[i].DestroyItem(techType))
{
return true;
}
}
return false;
}
// Token: 0x04000D0A RID: 3338
public CrafterLogic.OnItemChanged onItemChanged;
// Token: 0x04000D0B RID: 3339
public CrafterLogic.OnProgress onProgress;
// Token: 0x04000D0C RID: 3340
public CrafterLogic.OnDone onDone;
// Token: 0x04000D0D RID: 3341
private const int currentVersion = 1;
// Token: 0x04000D0E RID: 3342
[ProtoMember(1)]
[NonSerialized]
public int version = 1;
// Token: 0x04000D0F RID: 3343
[ProtoMember(2)]
[NonSerialized]
public float timeCraftingBegin = -1f;
// Token: 0x04000D10 RID: 3344
[ProtoMember(3)]
[NonSerialized]
public float timeCraftingEnd = -1f;
// Token: 0x04000D11 RID: 3345
[ProtoMember(4)]
[NonSerialized]
public TechType craftingTechType;
// Token: 0x04000D12 RID: 3346
[ProtoMember(5)]
[NonSerialized]
public int linkedIndex = -1;
// Token: 0x04000D13 RID: 3347
[ProtoMember(6)]
[NonSerialized]
public int numCrafted;
// Token: 0x04000D14 RID: 3348
private double lastTime;
// Token: 0x04000D15 RID: 3349
private static List<ICraftTarget> sCraftTargets;
// Token: 0x0400467E RID: 18046
private static double lastCheckTime = 0.0;
// Token: 0x0400467F RID: 18047
private static ItemsContainer[] nearbyItemContainers;
// Token: 0x02000203 RID: 515
// (Invoke) Token: 0x06000B2A RID: 2858
public delegate void OnItemChanged(TechType techType);
// Token: 0x02000204 RID: 516
// (Invoke) Token: 0x06000B2E RID: 2862
public delegate void OnProgress(float progress);
// Token: 0x02000205 RID: 517
// (Invoke) Token: 0x06000B32 RID: 2866
public delegate void OnDone();
}
Until a mod manager is stable, this will probably have to do.
1. Grab dnSpy: https://github.com/0xd4d/dnSpy/releases
2. Open up dnSpy
3. File -> Open; find the file: <Steam library folder>\steamapps\common\Subnautica\Subnautica_Data\Managed\Assembly-CSharp.dll
4. Open "Search assemblies" (Ctrl + Shift + K) and adjust the "Search For" drop-down to "Class" and search for: CrafterLogic
[It should look like this: https://i.imgur.com/P8Ml9nD.png]
5. Double-click the "CrafterLogic" entry
6. In the new tab, right-click in the code somewhere and select "Edit Class (C#)"
7. Replace everything in that file with the contents below in CrafterLogic.cs (it's mostly adding a few functions - description below)
8. Hit the "Compile" button
9. File -> Save Module
10. Done! Load up Subnautica and test it out!
By default, this mod will pull resources from any storage container it can find within 100m, including the player inventory - which it will try to take from last. The tooltip information will be wrong (i.e. it will say you don't have enough resources even though the Fabricator icon is lit), but that's currently only pulling information from the player inventory. When patching is simpler, I'll look at adding that, too. The comments in the file below are ignore and regenerated when compiled, so they shouldn't cause conflicts.
The file below only does a few things:
1. Adds a few convenience functions (FindAllItemsContainersInRange, GetTotalPickupCount, and DestroyItemInContainers), which are the last 3 functions in the file.
2. It replaces the logic in IsCraftRecipeFulfulled to search all containers in range, not just the player.
3. It replaces the logic in ConsumeResources with modified code from Inventory.ConsumeResourcesForRecipe - again using the containers in range instead of just the player's inventory.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment