Skip to content

Commit

Permalink
feat: Creature Eggs, AddHint, resource tracking, and various fixes (#451
Browse files Browse the repository at this point in the history
)

* Fixed bothersome grammar

* Added PrefabUtils.AddResourceTracker

* Added `AssetReference.ForceValid` extension method

* Added `ErrorMessage.AddHint` extension method

* Added EggTemplate

* Added EggGadget

* Added example for creature eggs

* Formatting fixes (wtf Metious)

* Fix compilation errors on BZ branch

* Use chelicerate egg for BZ example

* Use hardcoded string for hint

* Remove unused using

* Fix problematic example mod patch

---------

Co-authored-by: LeeTwentyThree <[email protected]>
  • Loading branch information
Metious and LeeTwentyThree committed Aug 31, 2023
1 parent f04d892 commit c127bd9
Show file tree
Hide file tree
Showing 10 changed files with 650 additions and 16 deletions.
123 changes: 123 additions & 0 deletions Example mod/CreatureEggExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System.Reflection;
using BepInEx;
using HarmonyLib;
using Nautilus.Assets;
using Nautilus.Assets.Gadgets;
using Nautilus.Assets.PrefabTemplates;
using Nautilus.Extensions;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UWE;

namespace Nautilus.Examples;

[BepInPlugin("com.snmodding.nautilus.creatureegg", "Nautilus Creature Egg Example Mod", Nautilus.PluginInfo.PLUGIN_VERSION)]
[BepInDependency("com.snmodding.nautilus")]
public class CreatureEggExample : BaseUnityPlugin
{
/// <summary>
/// This patch is required because the reaper leviathan doesn't have a WaterParkCreature component and it's required for creatures in the ACU to work properly.
/// </summary>
[HarmonyPatch(typeof(LiveMixin), nameof(LiveMixin.Awake))]
private static class Patcher
{
[HarmonyPostfix]
private static void AwakePostfix(Creature __instance)
{
# if SUBNAUTICA
if (!__instance.TryGetComponent(out ReaperLeviathan _))
#else
if (!__instance.TryGetComponent(out Chelicerate _))
#endif
{
return;
}

#if SUBNAUTICA
if (!PrefabDatabase.TryGetPrefabFilename(CraftData.GetClassIdForTechType(TechType.ReaperLeviathan), out var filename))
#else
if (!PrefabDatabase.TryGetPrefabFilename(CraftData.GetClassIdForTechType(TechType.Chelicerate), out var filename))
#endif
{
return;
}

var wpc = __instance.gameObject.EnsureComponent<WaterParkCreature>();
wpc.data = ScriptableObject.CreateInstance<WaterParkCreatureData>();
wpc.data.eggOrChildPrefab = new AssetReferenceGameObject(filename).ForceValid();
wpc.data.canBreed = true;
// Initial size is when the creature just hatched
wpc.data.initialSize = 0.04f;

// Max size is the maximum size this creature can reach inside the ACU
wpc.data.maxSize = 0.05f;

// Outside size is when you drop the creature outside of the ACU
wpc.data.outsideSize = 0.07f;

// How long will it take for this creature to reach the maximum size
wpc.data.daysToGrow = 6;

wpc.data.isPickupableOutside = false;
}
}
private void Awake()
{
#if SUBNAUTICA
CustomPrefab customEgg = new CustomPrefab("ReaperEgg", "Reaper Leviathan Egg", "Reaper Leviathan Egg that makes me go yes.");
#else
CustomPrefab customEgg = new CustomPrefab("ChelicerateEgg", "Chelicerate Egg", "Chelicerate Egg that makes me go yes.");
#endif
customEgg.Info.WithSizeInInventory(new Vector2int(3, 3));

/*
* Here we make the creature egg immune to the brine acid. Please note that a creature egg doesn't require an egg gadget to work.
* The egg gadget simply has methods that add additional item functionality to the egg, but the egg will still work like an
* egg without this gadget.
* In this example, we've used the egg gadget simply to make it acid immune.
*/
EggGadget eggGadget = customEgg.CreateCreatureEgg();
eggGadget.SetAcidImmune(true);

/*
* Here we set the required ACU stacks to two. This means that you cannot drop this egg in an ACU unless you have 2 ACUs stacks on top of each other.
*/
eggGadget.WithRequiredAcuSize(2);

/*
* Here we create an egg template instance that copies the Crash Egg model.
* In the object initializer, we're setting the hatching creature to ReaperLeviathan and also made the egg take 3 days to hatch.
*/
#if SUBNAUTICA
EggTemplate egg = new EggTemplate(customEgg.Info, TechType.CrashEgg)
#else
EggTemplate egg = new EggTemplate(customEgg.Info, TechType.RockPuncherEgg)
#endif
{
#if SUBNAUTICA
HatchingCreature = TechType.ReaperLeviathan,
#else
HatchingCreature = TechType.Chelicerate,
#endif
HatchingTime = 3
};

/*
* Here we make this egg have an unidentified egg tech type before hatching. Once it hatches, it will receive the main egg tech type.
*/
egg.SetUndiscoveredTechType();

/*
* Set the game object of our custom prefab to the egg template we setup.
*/
customEgg.SetGameObject(egg);

/*
* Register our custom fabricator to the game.
* After this point, do not edit the prefab or modify gadgets as they will not be applied.
*/
customEgg.Register();

Harmony.CreateAndPatchAll(typeof(CreatureEggExample), PluginInfo.PLUGIN_GUID);
}
}
73 changes: 73 additions & 0 deletions Nautilus/Assets/Gadgets/EggGadget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Nautilus.Extensions;
using Nautilus.Patchers;
using Nautilus.Utility;

namespace Nautilus.Assets.Gadgets;

/// <summary>
/// Represents a creature egg gadget.
/// </summary>
public class EggGadget : Gadget
{
/// <summary>
/// The total amount of ACU floors required for the egg to be dropped in the ACU. defaulted to 1.
/// </summary>
public int RequiredAcuSize { get; set; } = 1;

/// <summary>
/// makes the egg immune to the Lost River's Acidic Brine.
/// </summary>
public bool AcidImmune { get; set; } = true;

/// <summary>
/// Constructs a Creature egg gadget instance.
/// </summary>
/// <param name="prefab">The custom prefab to operate on.</param>
/// <param name="requiredAcuSize">The total amount of ACU floors required for the egg to be dropped in the ACU.</param>
public EggGadget(ICustomPrefab prefab, int requiredAcuSize = 1) : base(prefab)
{
RequiredAcuSize = requiredAcuSize;
}

/// <summary>
/// The total amount of ACU floors required for the egg to be dropped in the ACU.
/// </summary>
/// <param name="requiredAcuSize">The ACU stacks value.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public EggGadget WithRequiredAcuSize(int requiredAcuSize)
{
RequiredAcuSize = requiredAcuSize;

return this;
}

/// <summary>
/// makes the egg immune to the Lost River's Acidic Brine.
/// </summary>
/// <param name="isAcidImmune">Should this item be acid immune?</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
public EggGadget SetAcidImmune(bool isAcidImmune)
{
AcidImmune = isAcidImmune;

return this;
}

/// <inheritdoc/>
protected internal override void Build()
{
if (prefab.Info.TechType is TechType.None)
{
InternalLogger.Error($"Prefab '{prefab.Info}' does not contain a TechType. Skipping {nameof(EggGadget)} build.");
return;
}

if (AcidImmune)
DamageSystem.acidImmune.Add(prefab.Info.TechType);

if (RequiredAcuSize > 1)
{
WaterParkPatcher.requiredAcuSize[prefab.Info.TechType] = RequiredAcuSize;
}
}
}
33 changes: 24 additions & 9 deletions Nautilus/Assets/Gadgets/GadgetExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class GadgetExtensions
/// </summary>
/// <param name="customPrefab">The custom prefab to add recipe to.</param>
/// <param name="recipeData">The recipe to add.</param>
/// <returns>An instance to the created <see cref="CraftingGadget"/> to continue the recipe settings on.</returns>
/// <returns>A reference to the created <see cref="CraftingGadget"/> to continue the recipe settings on.</returns>
public static CraftingGadget SetRecipe(this ICustomPrefab customPrefab, RecipeData recipeData)
{
if (!customPrefab.TryGetGadget(out CraftingGadget craftingGadget))
Expand All @@ -35,7 +35,7 @@ public static CraftingGadget SetRecipe(this ICustomPrefab customPrefab, RecipeDa
/// </summary>
/// <param name="customPrefab">The custom prefab to add recipe to.</param>
/// <param name="filePath">The path to the recipe json file. A string with valid recipe data json is also acceptable.</param>
/// <returns>An instance to the created <see cref="CraftingGadget"/> to continue the recipe settings on.</returns>
/// <returns>A reference to the created <see cref="CraftingGadget"/> to continue the recipe settings on.</returns>
public static CraftingGadget SetRecipeFromJson(this ICustomPrefab customPrefab, string filePath)
{
RecipeData recipeData;
Expand Down Expand Up @@ -68,7 +68,7 @@ public static CraftingGadget SetRecipeFromJson(this ICustomPrefab customPrefab,
/// <param name="customPrefab">The custom prefab to add unlocks to.</param>
/// <param name="requiredForUnlock">The blueprint to set as a requirement.</param>
/// <param name="fragmentsToScan">Amount of <paramref name="requiredForUnlock"/> that must be scanned to unlock this item.</param>
/// <returns>An instance to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <returns>A reference to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
public static ScanningGadget SetUnlock(this ICustomPrefab customPrefab, TechType requiredForUnlock, int fragmentsToScan = 1)
{
if (!customPrefab.TryGetGadget(out ScanningGadget scanningGadget))
Expand All @@ -85,7 +85,7 @@ public static ScanningGadget SetUnlock(this ICustomPrefab customPrefab, TechType
/// <param name="customPrefab">The custom prefab to add unlocks to.</param>
/// <param name="group">The main group in the PDA blueprints where this item appears</param>
/// <param name="category">The category within the group in the PDA blueprints where this item appears.</param>
/// <returns>An instance to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <returns>A reference to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <remarks>If the specified <paramref name="group"/> is a tech group that is present in the <see cref="uGUI_BuilderMenu.groups"/> list, this item will automatically
/// become buildable. To avoid this, or make this item a buildable manually, use the <see cref="ScanningGadget.SetBuildable"/> method.</remarks>
public static ScanningGadget SetPdaGroupCategory(this ICustomPrefab customPrefab, TechGroup group, TechCategory category)
Expand All @@ -104,7 +104,7 @@ public static ScanningGadget SetPdaGroupCategory(this ICustomPrefab customPrefab
/// <param name="group">The main group in the PDA blueprints where this item appears.</param>
/// <param name="category">The category within the group in the PDA blueprints where this item appears.</param>
/// <param name="target">It will be added after this target item or at the end if not found.</param>
/// <returns>An instance to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <returns>A reference to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <remarks>If the specified <paramref name="group"/> is a tech group that is present in the <see cref="uGUI_BuilderMenu.groups"/> list, this item will automatically
/// become buildable. To avoid this, or make this item a buildable manually, use the <see cref="ScanningGadget.SetBuildable"/> method.</remarks>
public static ScanningGadget SetPdaGroupCategoryAfter(this ICustomPrefab customPrefab, TechGroup group, TechCategory category, TechType target)
Expand All @@ -122,7 +122,7 @@ public static ScanningGadget SetPdaGroupCategoryAfter(this ICustomPrefab customP
/// <param name="group">The main group in the PDA blueprints where this item appears.</param>
/// <param name="category">The category within the group in the PDA blueprints where this item appears.</param>
/// <param name="target">It will be inserted before this target item or at the beginning if not found.</param>
/// <returns>An instance to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <returns>A reference to the created <see cref="ScanningGadget"/> to continue the scanning settings on.</returns>
/// <remarks>If the specified <paramref name="group"/> is a tech group that is present in the <see cref="uGUI_BuilderMenu.groups"/> list, this item will automatically
/// become buildable. To avoid this, or make this item a buildable manually, use the <see cref="ScanningGadget.SetBuildable"/> method.</remarks>
public static ScanningGadget SetPdaGroupCategoryBefore(this ICustomPrefab customPrefab, TechGroup group, TechCategory category, TechType target)
Expand All @@ -138,7 +138,7 @@ public static ScanningGadget SetPdaGroupCategoryBefore(this ICustomPrefab custom
/// </summary>
/// <param name="customPrefab">The custom prefab to set equipment slot for.</param>
/// <param name="equipmentType">The type of equipment slot this item can fit into.</param>
/// <returns>An instance to the created <see cref="EquipmentGadget"/> to continue the equipment settings on.</returns>
/// <returns>A reference to the created <see cref="EquipmentGadget"/> to continue the equipment settings on.</returns>
public static EquipmentGadget SetEquipment(this ICustomPrefab customPrefab, EquipmentType equipmentType)
{
if (!customPrefab.TryGetGadget(out EquipmentGadget equipmentGadget))
Expand All @@ -156,7 +156,7 @@ public static EquipmentGadget SetEquipment(this ICustomPrefab customPrefab, Equi
/// <param name="customPrefab">The custom prefab to set vehicle upgrade for.</param>
/// <param name="equipmentType">The type of equipment slot this item can fit into. Preferably use something related to vehicles.</param>
/// <param name="slotType">The quick slot type</param>
/// <returns>An instance to the created <see cref="UpgradeModuleGadget"/> to continue the upgrade settings on.</returns>
/// <returns>A reference to the created <see cref="UpgradeModuleGadget"/> to continue the upgrade settings on.</returns>
public static UpgradeModuleGadget SetVehicleUpgradeModule(this ICustomPrefab customPrefab, EquipmentType equipmentType = EquipmentType.VehicleModule, QuickSlotType slotType = QuickSlotType.Passive)
{
if(customPrefab.TryGetGadget(out UpgradeModuleGadget upgradeModuleGadget))
Expand All @@ -178,7 +178,7 @@ public static UpgradeModuleGadget SetVehicleUpgradeModule(this ICustomPrefab cus
/// </summary>
/// <param name="customPrefab">The custom prefab to set equipment slot for.</param>
/// <param name="treeType">The created custom craft tree type.</param>
/// <returns>An instance to the created <see cref="FabricatorGadget"/> to continue the fabricator settings on.</returns>
/// <returns>A reference to the created <see cref="FabricatorGadget"/> to continue the fabricator settings on.</returns>
public static FabricatorGadget CreateFabricator(this ICustomPrefab customPrefab, out CraftTree.Type treeType)
{
if (!customPrefab.TryGetGadget(out FabricatorGadget fabricatorGadget))
Expand Down Expand Up @@ -248,4 +248,19 @@ public static ICustomPrefab SetSpawns(this ICustomPrefab customPrefab, WorldEnti

return customPrefab;
}

/// <summary>
/// Makes this item have additional creature-egg-related functionality.
/// </summary>
/// <param name="customPrefab">The custom prefab the creature egg gadget is created for.</param>
/// <param name="requiredAcuSize">The total amount of ACU floors required for the egg to be dropped in the ACU.</param>
/// <returns>A reference to the created <see cref="EggGadget"/> instance after the operation has completed.</returns>
public static EggGadget CreateCreatureEgg(this ICustomPrefab customPrefab, int requiredAcuSize = 1)
{
if (!customPrefab.TryGetGadget(out EggGadget creatureEggGadget))
creatureEggGadget = customPrefab.AddGadget(new EggGadget(customPrefab, requiredAcuSize));

creatureEggGadget.RequiredAcuSize = requiredAcuSize;
return creatureEggGadget;
}
}
Loading

0 comments on commit c127bd9

Please sign in to comment.