diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 000000000..0c0beba5f --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,43 @@ +name: Publish Documentation + +on: + push: + branches: + - docs + + +jobs: + publish-docs: + runs-on: ubuntu-latest + steps: + - name: Chekout + uses: actions/checkout@v3 + + - name: Dotnet Setup + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.x + + - uses: nuget/setup-nuget@v1 + name: Nuget Setup + + - name: Nuget Restore + run: nuget restore Nautilus.sln + + - name: Build Project + run: dotnet build Nautilus.sln -c SN.STABLE + + - name: Install DocFX + run: dotnet tool update -g docfx + + - name: Build documentation + run: docfx Nautilus/docfx.json + + - name: Build documentation x2 + run: docfx Nautilus/docfx.json + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: Nautilus/_site \ No newline at end of file diff --git a/Nautilus/DocFX/darkfx/partials/affix.tmpl.partial b/Nautilus/DocFX/darkfx/partials/affix.tmpl.partial new file mode 100644 index 000000000..11caeb3d0 --- /dev/null +++ b/Nautilus/DocFX/darkfx/partials/affix.tmpl.partial @@ -0,0 +1,40 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + diff --git a/Nautilus/DocFX/darkfx/partials/footer.tmpl.partial b/Nautilus/DocFX/darkfx/partials/footer.tmpl.partial new file mode 100644 index 000000000..a9da7c3bb --- /dev/null +++ b/Nautilus/DocFX/darkfx/partials/footer.tmpl.partial @@ -0,0 +1,29 @@ +{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + \ No newline at end of file diff --git a/Nautilus/DocFX/darkfx/partials/head.tmpl.partial b/Nautilus/DocFX/darkfx/partials/head.tmpl.partial new file mode 100644 index 000000000..a627ba33c --- /dev/null +++ b/Nautilus/DocFX/darkfx/partials/head.tmpl.partial @@ -0,0 +1,21 @@ +{{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} + + + + + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + + {{#_description}}{{/_description}} + + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_enableNewTab}}{{/_enableNewTab}} + \ No newline at end of file diff --git a/Nautilus/DocFX/darkfx/styles/days_one.ttf b/Nautilus/DocFX/darkfx/styles/days_one.ttf new file mode 100644 index 000000000..ef77473c6 Binary files /dev/null and b/Nautilus/DocFX/darkfx/styles/days_one.ttf differ diff --git a/Nautilus/DocFX/darkfx/styles/main.css b/Nautilus/DocFX/darkfx/styles/main.css new file mode 100644 index 000000000..fab70a712 --- /dev/null +++ b/Nautilus/DocFX/darkfx/styles/main.css @@ -0,0 +1,511 @@ +:root, body.dark-theme { + --color-foreground: #ccd5dc; + --color-navbar: #9d9d9d; + --color-breadcrumb: #999; + --color-underline: #ddd; + --color-toc-hover: #fff; + --color-background: #1d1d1d; + --color-background-subnav: #333337; + --color-background-dark: #282c34; + --color-background-table-alt: #212123; + --color-background-quote: #69696e; +} + +body.light-theme { + --color-foreground: #171717; + --color-navbar: #66666d; + --color-breadcrumb: #4a4a4a; + --color-toc-hover: #4c4c4c; + --color-background: #ffffff; + --color-background-subnav: #f5f5f5; + --color-background-dark: #ddd; + --color-background-table-alt: #f9f9f9; +} + +@font-face { + font-family: Days One; + src: url("days_one.ttf"); +} + +body { + color: var(--color-foreground); + line-height: 1.5; + font-size: 14px; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + word-wrap: break-word; + background-color: var(--color-background); +} + +.lang-diff-rem { + color: rgb(236, 89, 117); +} + +.lang-diff-add { + color: rgb(62, 175, 124);; +} + +.btn.focus, .btn:focus, .btn:hover { + color: var(--color-foreground); +} + +h1 { + font-weight: 600; + font-size: 32px; +} + +h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.8; +} + +h3 { + font-weight: 600; + font-size: 20px; + line-height: 1.8; +} + +h5 { + font-size: 14px; + padding: 10px 0px; +} + +article h1, article h2, article h3, article h4 { + margin-top: 35px; + margin-bottom: 15px; +} + +article h4 { + padding-bottom: 8px; + border-bottom: 2px solid var(--color-underline); +} + +.navbar-brand>img { + color: var(--color-background); +} + +.navbar { + border: none; +} + +.subnav { + border-top: 1px solid var(--color-underline); + background-color: var(--color-background-subnav); +} + +.sidenav, .fixed_header, .toc { + background-color: var(--color-background); +} + +.navbar-inverse { + background-color: var(--color-background-dark); + z-index: 100; +} + +.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text { + color: var(--color-navbar); + background-color: var(--color-background-dark); + border-bottom: 3px solid transparent; + padding-bottom: 12px; +} + +.navbar-inverse .navbar-nav>li>a:focus, .navbar-inverse .navbar-nav>li>a:hover { + color: var(--color-foreground); + background-color: var(--color-background-dark); + border-bottom: 3px solid var(--color-background-subnav); + transition: all ease 0.25s; +} + +.navbar-inverse .navbar-nav>.active>a, .navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover { + color: var(--color-foreground); + background-color: var(--color-background-dark); + border-bottom: 3px solid var(--color-foreground); + transition: all ease 0.25s; +} + +.navbar-form .form-control { + border: none; + border-radius: 0; +} + +.light-theme .navbar-brand svg { + filter: brightness(90%); +} + +.toc .level1>li { + font-weight: 400; +} + +.toc .nav>li>a { + color: var(--color-foreground); +} + +.sidefilter { + background-color: var(--color-background); + border-left: none; + border-right: none; +} + +.sidefilter { + background-color: var(--color-background); + border-left: none; + border-right: none; +} + +.toc-filter { + padding: 10px; + margin: 0; + background-color: var(--color-background); +} + +.toc-filter>input { + border: none; + border-radius: unset; + background-color: var(--color-background-subnav); + padding: 5px 0 5px 20px; + font-size: 90% +} + +.toc-filter>.clear-icon { + position: absolute; + top: 17px; + right: 15px; +} + +.toc-filter>input:focus { + color: var(--color-foreground); + transition: all ease 0.25s; +} + +.toc-filter>.filter-icon { + display: none; +} + +.sidetoc>.toc { + background-color: var(--color-background); + overflow-x: hidden; +} + +.sidetoc { + background-color: var(--color-background); + border: none; +} + +.alert { + background-color: inherit; + border: none; + padding: 10px 0; + border-radius: 0; +} + +.alert>p { + margin-bottom: 0; + padding: 5px 10px; + border-bottom: 1px solid; + background-color: var(--color-background-dark); +} + +.alert>h5 { + padding: 10px 15px; + margin-top: 0; + margin-bottom: 0; + text-transform: uppercase; + font-weight: bold; + border-top: 2px solid; + background-color: var(--color-background-dark); + border-radius: none; +} + +.alert>ul { + margin-bottom: 0; + padding: 5px 40px; +} + +.alert-info { + color: #1976d2; +} + +.alert-warning { + color: #f57f17; +} + +.alert-danger { + color: #d32f2f; +} + +pre { + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + word-break: break-all; + word-wrap: break-word; + background-color: var(--color-background-dark); + border-radius: 0; + border: none; + color: var(--color-foreground); +} + +code { + background: var(--color-background-dark) !important; + border-radius: 2px; + color: var(--color-foreground); +} + +.hljs { + color: var(--color-foreground); +} + +.hljs-subst { + color: rgb(156, 220, 254) !important; +} + +.hljs-title { + color: rgb(1, 207, 255) !important; +} + +.hljs-string { + color: rgb(214, 157, 133) !important; +} + +.hljs-doctag, .hljs-comment { + color: rgb(99, 180, 86); +} + +.hljs-keyword, .hljs-built_in { + color: rgb(86, 156, 214); +} + +.hljs-literal, .hljs-number { + color: rgb(181, 206, 168); +} + +.toc .nav>li.active>.expand-stub::before, .toc .nav>li.in>.expand-stub::before, .toc .nav>li.in.active>.expand-stub::before, .toc .nav>li.filtered>.expand-stub::before { + content: "▾"; +} + +.toc .nav>li>.expand-stub::before, .toc .nav>li.active>.expand-stub::before { + content: "▸"; +} + +.affix ul ul>li>a:before { + content: "|"; +} + +.breadcrumb { + background-color: var(--color-background-subnav); +} + +.breadcrumb .label.label-primary { + background: #444; + border-radius: 0; + font-weight: normal; + font-size: 100%; +} + +#breadcrumb .breadcrumb>li a { + border-radius: 0; + font-weight: normal; + font-size: 85%; + display: inline; + padding: 0 .6em 0; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + color: var(--color-breadcrumb); +} + +#breadcrumb .breadcrumb>li a:hover { + color: var(--color-foreground); + transition: all ease 0.25s; +} + +.breadcrumb>li+li:before { + content: "⯈"; + font-size: 75%; + color: var(--color-background-dark); + padding: 0; +} + +.light-theme .breadcrumb>li+li:before { + color: var(--color-foreground) + } + +.toc .level1>li { + font-weight: 600; + font-size: 130%; + padding-left: 5px; +} + +.footer { + border-top: none; + background-color: var(--color-background-dark); + padding: 15px 0; + font-size: 90%; +} + +.toc .nav>li>a:hover, .toc .nav>li>a:focus { + color: var(--color-toc-hover); + transition: all ease 0.1s; +} + +.form-control { + background-color: var(--color-background-subnav); + border: none; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; +} + +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: none; + box-shadow: none; +} + +input#search-query:focus { + color: var(--color-foreground); +} + +.table-bordered, .table-bordered>tbody>tr>td, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>td, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>thead>tr>th { + border: 1px solid var(--color-background-dark); +} + +.table-striped>tbody>tr:nth-of-type(odd) { + background-color: var(--color-background-table-alt); +} + +blockquote { + padding: 10px 20px; + margin: 0 0 10px; + font-size: 110%; + border-left: 5px solid var(--color-background-quote); + color: var(--color-background-quote); +} + +.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span, .pagination>.disabled>span:focus, .pagination>.disabled>span:hover { + background-color: var(--color-background-subnav); + border-color: var(--color-background-subnav); +} + +.breadcrumb>li, .pagination { + display: inline; +} + +.tabGroup a[role="tab"] { + color: var(--color-navbar); + border-bottom: 2px solid var(--color-background-dark); +} + +.tabGroup a[role="tab"][aria-selected="true"] { + color: var(--color-foreground); +} + +.tabGroup section[role="tabpanel"] { + border: 1px solid var(--color-background-dark); +} + +.sideaffix > div.contribution > ul > li > a.contribution-link:hover { + background-color: var(--color-background); +} + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 20px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 4px; + bottom: 3px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #337ab7; +} + +input:focus + .slider { + box-shadow: 0 0 1px #337ab7; +} + +input:checked + .slider:before { + -webkit-transform: translateX(19px); + -ms-transform: translateX(19px); + transform: translateX(19px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 20px; +} + +.slider.round:before { + border-radius: 50%; +} +.toggle-mode .icon { + display: inline-block; +} + +.toggle-mode .icon i { + font-style: normal; + font-size: 17px; + display: inline-block; + padding-right: 7px; + padding-left: 7px; + vertical-align: middle; +} + +@media (min-width: 1600px) { + .container { + width: 100%; + } + .sidefilter { + width: 18%; + } + .sidetoc { + width: 18%; + } + .article.grid-right { + margin-left: 19%; + } + .sideaffix { + width: 11.5%; + } + .affix ul>li.active>a { + white-space: initial; + } + .affix ul>li>a { + width: 99%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +} \ No newline at end of file diff --git a/Nautilus/DocFX/darkfx/styles/toggle-theme.js b/Nautilus/DocFX/darkfx/styles/toggle-theme.js new file mode 100644 index 000000000..f2ea5f5fd --- /dev/null +++ b/Nautilus/DocFX/darkfx/styles/toggle-theme.js @@ -0,0 +1,35 @@ +const sw = document.getElementById("switch-style"), sw_mobile = document.getElementById("switch-style-m"), b = document.body; +if (b) { + function toggleTheme(target, dark) { + target.classList.toggle("dark-theme", dark) + target.classList.toggle("light-theme", !dark) + } + + function switchEventListener() { + toggleTheme(b, this.checked); + if (window.localStorage) { + this.checked ? localStorage.setItem("theme", "dark-theme") : localStorage.setItem("theme", "light-theme") + } + } + + var isDarkTheme = !window.localStorage || !window.localStorage.getItem("theme") || window.localStorage && localStorage.getItem("theme") === "dark-theme"; + + if(sw && sw_mobile){ + sw.checked = isDarkTheme; + sw_mobile.checked = isDarkTheme; + + sw.addEventListener("change", switchEventListener); + sw_mobile.addEventListener("change", switchEventListener); + + // sync state between switches + sw.addEventListener("change", function() { + sw_mobile.checked = this.checked; + }); + + sw_mobile.addEventListener("change", function() { + sw.checked = this.checked; + }); + } + + toggleTheme(b, isDarkTheme); +} \ No newline at end of file diff --git a/Nautilus/DocFX/favicon.ico b/Nautilus/DocFX/favicon.ico new file mode 100644 index 000000000..5278a550b Binary files /dev/null and b/Nautilus/DocFX/favicon.ico differ diff --git a/Nautilus/DocFX/logo.svg b/Nautilus/DocFX/logo.svg new file mode 100644 index 000000000..c2ad6e745 --- /dev/null +++ b/Nautilus/DocFX/logo.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nautilus/Documentation/.gitignore b/Nautilus/Documentation/.gitignore new file mode 100644 index 000000000..6fd2911d9 --- /dev/null +++ b/Nautilus/Documentation/.gitignore @@ -0,0 +1,10 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site +README.html \ No newline at end of file diff --git a/Nautilus/Documentation/README.md b/Nautilus/Documentation/README.md new file mode 100644 index 000000000..bfb1312c2 --- /dev/null +++ b/Nautilus/Documentation/README.md @@ -0,0 +1,16 @@ +Before contributing, make sure you have [.NET SDK](https://dotnet.microsoft.com/en-us/download) installed, then open a terminal and enter the following command to install docfx: +```powershell +dotnet tool update -g docfx +``` +To build the website locally, run the following command: +```powershell +docfx Nautilus/docfx.json --serve +``` + +The website will then be launched on http://localhost:8080 and you can preview it. + +## Contributing +Simply make a PR to the `docs` branch with your changes. +Make sure to add your documentation files (markdown) to the toc.yml appropriate for the type of document you're trying to add as well. + +**Please refrain from pushing C# changes to the `docs` branch.** \ No newline at end of file diff --git a/Nautilus/Documentation/api/.gitignore b/Nautilus/Documentation/api/.gitignore new file mode 100644 index 000000000..e8079a3be --- /dev/null +++ b/Nautilus/Documentation/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/Nautilus/Documentation/guides/overview.md b/Nautilus/Documentation/guides/overview.md new file mode 100644 index 000000000..dd89f0cb3 --- /dev/null +++ b/Nautilus/Documentation/guides/overview.md @@ -0,0 +1,5 @@ +# Guides + +### TODO: [Development Setup Guide](https://www.youtube.com/watch?v=dQw4w9WgXcQ) +### TODO: [Simple Mod Guide](https://www.youtube.com/watch?v=dQw4w9WgXcQ) +### [Updating from SML 2.0 to Nautilus](sml2-to-nautilus.md) \ No newline at end of file diff --git a/Nautilus/Documentation/guides/sml2-to-nautilus.md b/Nautilus/Documentation/guides/sml2-to-nautilus.md new file mode 100644 index 000000000..bdc1d85e5 --- /dev/null +++ b/Nautilus/Documentation/guides/sml2-to-nautilus.md @@ -0,0 +1,346 @@ +# Updating to Nautilus + +In this article, we will be talking about the necessary changes you must apply to update your mod to Nautilus from SMLHelper 2.0. + +## Namespace +The root namespace for Nautilus is not the same as SMLHelper 2.0. +
+- <RootNamespace>SMLHelper.V2</RootNamespace>
++ <RootNamespace>SMLHelper</RootNamespace>
+
+ + +## Handlers + +Handlers no longer implement an interface matching their name. Additionally, they're now `public static`. +This means they also no longer have a public static `Main` property anymore, so you will have to drop it from anywhere +mentioned in your code. + +### Handler.cs +Following the handler interfaces change, the overly under-used `Handler` class will leave us in Nautilus +
+// Handler.cs
+
+- namespace SMLHelper.V2
+- {
+-     using Interfaces;
+- 
+-     /// <summary>
+-     /// A simple location where every SMLHelper handler class can be accessed.
+-     /// </summary>
+-     public static class Handler
+-     {
+-         public static IBioReactorHandler BioReactorHandler => Handlers.BioReactorHandler.Main;
+- 
+-         public static ICraftDataHandler CraftDataHandler => Handlers.CraftDataHandler.Main;
+- 
+-         public static ICraftTreeHandler CraftTreeHandler => Handlers.CraftTreeHandler.Main;
+- 
+-         public static IIngameMenuHandler IngameMenuHandler => Handlers.IngameMenuHandler.Main;
+-         ...
+-     }
+
+
+ +### BioReactorHandler +The `BioReactorHandler` class is removed in Nautilus because it only had one very simple method to patch, and was forcing patch-time. That means if you tried to modify a bio charge _after_ +SML's entry point, it didn't get applied. + +The following example demonstrates how you can implement the same functionality the `BioReactorHandler` class offered. +
+- BioReactorHandler.SetBioreactorCharge(TechType.Peeper, 69f);
++ BaseBioReactor.charge[TechType.Peeper] = 69f;
+
+ +### FishHandler +Ever since this class has been added, it never received any further updates due to unpopularity among modders, and unfamiliarity with how creatures worked in general and thus, has been broken for a long time. +The `FishHandler` has been removed in Nautilus. At the time being, we have not added a system to replace it, so stay tuned for that. + +### PDAEncyclopediaHandler And PDALogHandler +Beginning with Nautilus, both of these handler methods were moved to `PDAHandler` as they only had one method each. +
+PDAEncyclopedia.EntryData entry = new PDAEncyclopedia.EntryData()
+{
+  key = "SomeEncy",
+  path = "Tech/Tools",
+  nodes = new[] { "Tech", "Tools" }
+};
+
+- PDAEncyclopediaHandler.AddCustomEntry(entry);
++ PDAHandler.AddEncyclopediaEntry(entry);
+
+- PDALogHandler.AddCustomEntry("SomeLog", "SomeLanguageKey");
++ PDAHandler.AddLogEntry("SomeLog", "SomeLanguageKey");
+
+ +## Enum Handlers +Beginning with Nautilus, enum handling will be made generic. Instead of working with individual handlers associated with the enum type (E.G: For `TechType` we had `TechTypeHandler`), +Now you can add a new enum value to any enum type by using `EnumHandler.AddEntry(string enumName)`. + +This means the following handlers are removed: +* `TechTypeHandler` +* `BackgroundTypeHandler` +* `EquipmentHandler` +* `PingTypeHandler` +* `TechCategoryHandler` +* `TechGroupHandler` +* `CraftTreeTypeHandler` - Only the methods below are removed: + 1. `CreateCustomCraftTreeAndType` + 2. `ModdedCraftTreeTypeExists` + + +The [EnumHandler](xref:Nautilus.Handlers.EnumHandler) class contains the following methods, all of them can be used for any enum type: + +| Signature | Summary | +|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------| +| `EnumBuilder AddEntry(string name)` | Adds a new enum value instance of TEnum type. | +| `bool ModdedEnumExists(string name)` | Safely looks for a custom enum object from another mod. | +| `bool TryGetValue(string name, out TEnum enumValue)` | Safely looks for a custom enum object from another mod and outputs the instance if found. | + + +> [!NOTE] +> The [EnumHandler](xref:Nautilus.Handlers.EnumHandler) class only takes care of registering a new enum object for an enum type. Further configuration is now handled via extension methods for the [EnumBuilder](xref:Nautilus.Handlers.EnumBuilder`1) type. + +Below we will talk about the necessary changes you will need to make your custom enum values work for each of the aforementioned handlers. + + +### Configuring Custom TechType Objects +
+- TechType customTech = TechTypeHandler.AddTechType("CustomTech", "Custom Tech", "Custom Tech that makes me go yes.", SpriteManager.Get(TechType.Titanium), unlockedAtStart: false);
++ TechType customTech = EnumHandler.AddEntry<TechType>("CustomTech")
++             .WithPdaInfo("Custom Tech", "Custom Tech that makes me go yes.", unlockedAtStart: false)
++             .WithIcon(SpriteManager.Get(TechType.Titanium));
+
+ +### Configuring Custom CraftData.BackgroundType Objects +
+- CraftData.BackgroundType customBG = BackgroundTypeHandler.AddBackgroundType("CustomBackground", SpriteManager.GetBackground(TechType.Battery));
++ CraftData.BackgroundType customBG = EnumHandler.AddEntry<CraftData.BackgroundType>("CustomBackground")
++             .WithBackground(SpriteManager.GetBackground(TechType.Battery));
+
+ +### Configuring Custom EquipmentType Objects +
+- EquipmentType customEquipment = EquipmentHandler.AddEquipmentType("CustomEquipment");
++ EquipmentType customEquipment = EnumHandler.AddEntry<EquipmentType>("CustomEquipment");
+
+ +### Configuring Custom PingType Objects +
+- PingType customPing = PingHandler.RegisterNewPingType("CustomPing", SpriteManager.Get(SpriteManager.Group.Pings, PingType.Signal.ToString()));
++ PingType customPing = EnumHandler.AddEntry<PingType>("CustomPing")
++             .WithIcon(SpriteManager.Get(SpriteManager.Group.Pings, PingType.Signal.ToString()));
+
+ +### Configuring Custom TechCategory and TechGroup Objects +
+- TechGroup customGroup = TechGroupHandler.AddTechCategory("CustomGroup", "Custom Group");
++ TechGroup customGroup = EnumHandler.AddEntry<TechCategory>("CustomGroup").WithPdaInfo("Custom Group");
+
+- TechCategory customCategory = TechCategoryHandler.AddTechCategory("CustomCategory", "Custom Category");
+- TechCategoryHandler.TryRegisterTechCategoryToTechGroup(customGroup, customCategory);
++ TechCategory customCategory = EnumHandler.AddEntry<TechCategory>("CustomCategory").WithPdaInfo("Custom Group")
++             .RegisterToTechGroup(customGroup);
+
+ +### Configuring Custom CraftTree.Type Objects +
+- ModCraftTreeRoot root = CraftTreeHandler.CreateCustomCraftTreeAndType(CustomTree, out CraftTree.Type customTree);
++ CraftTree.Type customTree = EnumHandler.AddEntry<CraftTree.Type>("CustomTree")
++             .CreateCraftTreeRoot(out ModCraftTreeRoot root);
+
+root.AddTabNode("SomeTab");
+
+ +___ + +## Options +Lorem ipsum dolor sit amet. Qui error architecto in officiis molestiae sit molestiae perspiciatis non necessitatibus voluptatibus sit fugiat veritatis eos aliquam rerum. Et ducimus provident est veniam magni nam veniam possimus aut molestiae explicabo cum doloribus atque? Ut adipisci aspernatur eos modi adipisci in sapiente rerum. Qui velit quis sed quia nobis et aspernatur repudiandae. + +Qui internos autem id recusandae inventore est autem voluptas et repudiandae vitae qui dolorem sapiente aut corporis dolor. Cum earum voluptate ut odit mollitia sit veritatis quos vel maiores fuga sit omnis nisi et quos repellat qui magnam mollitia. + +___ + +## Assets +The Assets system received a complete rewrite in Nautilus, making it the biggest change of this version. + +With this rewrite, asset classes are no longer an inherited chain mess, meaning `Buildable`, `Craftable`, `CustomFabricator`, `Equipable`, `FishPrefab`, `PdaItem`, `Spawnable` and `ModPrefab` classes have been removed. + +As of Nautilus, the asset system will have three main parts: Custom Prefabs, Gadgets, and Prefab Templates. + +The following table represents all the previous asset classes and what they have been replaced with in Nautilus. + +| SML 2.0 (old) | Nautilus (new) | +|------------------------|--------------------------------------------------------------------| +| `ModPrefab` | [CustomPrefab](xref:Nautilus.Assets.CustomPrefab) | +| `Buildable`, `PdaItem` | [ScanningGadget](xref:Nautilus.Assets.Gadgets.ScanningGadget) | +| `Equipable` | [EquipmentGadget](xref:Nautilus.Assets.Gadgets.EquipmentGadget) | +| `CustomFabricator` | [FabricatorGadget](xref:Nautilus.Assets.Gadgets.FabricatorGadget) | +| `Craftable` | [CraftingGadget](xref:Nautilus.Assets.Gadgets.GadgetExtensions#) | +| `Spawnable` | `ICustomPrefab.SetSpawns` | + + +### Custom Prefabs +`CustomPrefab` is a class that takes care of registering gadgets and also the game object into the game. +This class is essentially equivalent to the previous `ModPrefab` class. It is what you will use to actually make a custom prefab. + +### Gadgets +To put it simply, Gadgets are classes that take certain data and register them to the game for our custom prefab item. +They are pretty much equivalent to the different asset classes and their properties we had before. +Gadgets will be our primary way of interacting with game systems and to add functionality to a tech type and/or class ID. + +> [!NOTE] +> Gadgets only interact with tech types and/or class IDs. They don't have any business with a prefab's game object. + +### Prefab Templates +Previously on SML 2.0, asset classes optionally also provided game objects (E.G: `CustomFabricator`). To allow for diversity in the game object template you choose from +and also to make it easier to manage such functionality and modularize game objects, we have moved game object templates to their own system: Prefab templates. + +Prefab templates will be our main way of providing a base game object for custom prefabs. There are a couple of options you can choose from that suit your needs, however, it is not enforced to choose one; +you can still build up a game object from scratch. + +A couple of prefab templates that will be available in Nautilus are the following: + - CloneTemplate + - EnergySourceTemplate + - FabricatorTemplate + + +## Custom Prefab Examples +In this example, we will demonstrate how you can change an SML 2.0 custom prefab to the Nautilus system. + +### SML 2.0 +```csharp +public class SeamothBrineResistanceModule : Equipable +{ + public static TechType TechTypeID { get; protected set; } + public SeamothBrineResistanceModule() + : base("SeamothBrineResistModule", + "Seamoth brine resistant coating", + "Makes the Seamoth resistant to corrosive brine pools, by means of a protective coating.") + { + OnFinishedPatching += () => + { + TechTypeID = this.TechType; + }; + } + public override EquipmentType EquipmentType => EquipmentType.SeamothModule; + public override TechType RequiredForUnlock => TechType.BaseUpgradeConsole; + public override TechGroup GroupForPDA => TechGroup.VehicleUpgrades; + public override TechCategory CategoryForPDA => TechCategory.VehicleUpgrades; + public override CraftTree.Type FabricatorType => CraftTree.Type.SeamothUpgrades; + public override string[] StepsToFabricatorTab => new string[] { "SeamothModules" }; + public override QuickSlotType QuickSlotType => QuickSlotType.Passive; + public override GameObject GetGameObject() + { + var prefab = CraftData.GetPrefabForTechType(TechType.SeamothElectricalDefense); + var obj = GameObject.Instantiate(prefab); + return obj; + } + protected override TechData GetBlueprintRecipe() + { + return new TechData() + { + craftAmount = 1, + Ingredients = + { + new Ingredient(TechType.Polyaniline, 1), + new Ingredient(TechType.CopperWire, 2), + new Ingredient(TechType.AluminumOxide, 2), + new Ingredient(TechType.Nickel, 1), + }, + }; + } +} +``` + +### Nautilus +```csharp +// Create a custom prefab instance and set the class ID, friendly name, and description respectively +var seamothBrineResistanceModule = new CustomPrefab( + "SeamothBrineResistModule", + "Seamoth brine resistant coating", + "Makes the Seamoth resistant to corrosive brine pools, by means of a protective coating."); + +// Set our prefab to a clone of the Seamoth electrical defense module +seamothBrineResistanceModule.SetPrefab(new CloneTemplate(seamothBrineResistanceModule.Info, TechType.SeamothElectricalDefense)); + +// Make our item compatible with the seamoth module slot +seamothBrineResistanceModule.SetEquipment(EquipmentType.SeamothModule) + .WithQuickSlotType(QuickSlotType.Passive); + +// Make the Vehicle upgrade console a requirement for our item's blueprint +ScanningGadget scanning = seamothBrineResistanceModule.SetUnlock(TechType.BaseUpgradeConsole) + +// Add our item to the Vehicle upgrades category +scanning.WithPdaGroupCategory(TechGroup.VehicleUpgrades, TechCategory.VehicleUpgrades); + +var recipe = new RecipeData() +{ + craftAmount = 1, + Ingredients = + { + new CraftData.Ingredient(TechType.Polyaniline, 1), + new CraftData.Ingredient(TechType.CopperWire, 2), + new CraftData.Ingredient(TechType.AluminumOxide, 2), + new CraftData.Ingredient(TechType.Nickel, 1), + }, +}; + +// Add a recipe for our item, as well as add it to the Moonpool fabricator and Seamoth modules tab +seamothBrineResistanceModule.SetRecipe(recipe) + .WithFabricatorType(CraftTree.Type.SeamothUpgrades) + .WithStepsToFabricatorTab("SeamothModules"); + +// Register our item to the game +seamothBrineResistanceModule.Register(); +``` + +This example is based off of a real mod. You can get access to the full source code [here](https://github.com/Metious/MetiousSubnauticaMods/tree/master/SeamothBrineResist). + +--- + +## Audio and FMOD +In the last few versions of SML 2, we made a lot of changes to the audio system SML offered, this was because of FMOD. +FMOD is the sound engine Subnautica uses. It is more advanced and flexible compared to the built-in Unity audio system. + +Since we discovered the best practices and better ways to deal with custom sounds, we have deleted a bunch of previously-obsolete methods from +`CustomSoundHandler` and `AudioUtils` classes, as well as the `SoundChannel` enumeration in Nautilus. + +Beginning with Nautilus, all custom sounds will require a bus instead of a SoundChannel to determine the effects (E.G: reverb, muffling, low-pass, etc..) and the volume slider. +Additionally, the `PlaySound` signature was also modified and renamed to `TryPlaySound`. + +
+- Channel channel = AudioUtils.PlaySound(soundPath, SoundChannel.Music);
++ if (AudioUtils.TryPlaySound(soundPath, AudioUtils.BusPaths.Music, out Channel channel))
++ {
++   // do something with channel
++ }
+
+
+- Channel channel = AudioUtils.PlaySound(soundPath, SoundChannel.Voice);
++ if (AudioUtils.TryPlaySound(soundPath, AudioUtils.BusPaths.PDAVoice, out Channel channel))
++ {
++   // do something with channel
++ }
+
+
+- Channel channel = AudioUtils.PlaySound(soundPath, SoundChannel.Ambient);
++ if (AudioUtils.TryPlaySound(soundPath, AudioUtils.BusPaths.UnderwaterAmbient, out Channel channel))
++ {
++   // do something with channel
++ }
+
+
+- Channel channel = AudioUtils.PlaySound(soundPath, SoundChannel.Master);
++ if (AudioUtils.TryPlaySound(soundPath, "bus:/", out Channel channel))
++ {
++   // do something with channel
++ }
+
+
+ +> [!WARNING] +> Creating or playing a custom sound on the master bus is il-advised as it is dangerous and has the possibility of breaking the audio for a game session. +> Try to set an appropriate bus for your sound instead of the master one. + +--- \ No newline at end of file diff --git a/Nautilus/Documentation/guides/toc.yml b/Nautilus/Documentation/guides/toc.yml new file mode 100644 index 000000000..891125cb5 --- /dev/null +++ b/Nautilus/Documentation/guides/toc.yml @@ -0,0 +1,5 @@ +- name: Overview + href: overview.md + +- name: Updating to Nautilus + href: sml2-to-nautilus.md \ No newline at end of file diff --git a/Nautilus/Documentation/images/tutorials/command-results.png b/Nautilus/Documentation/images/tutorials/command-results.png new file mode 100644 index 000000000..e2058bdf8 Binary files /dev/null and b/Nautilus/Documentation/images/tutorials/command-results.png differ diff --git a/Nautilus/Documentation/index.md b/Nautilus/Documentation/index.md new file mode 100644 index 000000000..b537bfb9b --- /dev/null +++ b/Nautilus/Documentation/index.md @@ -0,0 +1,6 @@ +# Nautilus: Subnautica Modding Library + +Nautilus is a modding library that aims on enhancing developer productivity by offering common helper utilities as easy to use and robust as possible. + +Nautilus offers systems such as adding/editing items, icons, spawns, sounds and so much more! + diff --git a/Nautilus/Documentation/toc.yml b/Nautilus/Documentation/toc.yml new file mode 100644 index 000000000..1b07229a9 --- /dev/null +++ b/Nautilus/Documentation/toc.yml @@ -0,0 +1,16 @@ +- name: API Documentation + href: api/ + +- name: Guides + href: guides/ + topicHref: guides/overview.md + +- name: Tutorials + href: tutorials/ + topicHref: tutorials/overview.md + +- name: Discord + href: https://discord.gg/UpWuWwq + +- name: GitHub + href: https://github.com/SubnauticaModding/Nautilus \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/background-type.md b/Nautilus/Documentation/tutorials/background-type.md new file mode 100644 index 000000000..1c50b54a7 --- /dev/null +++ b/Nautilus/Documentation/tutorials/background-type.md @@ -0,0 +1,45 @@ +## How does the game handle background colors for items? +The game has a built-in enum called `BackgroundType`, which sits in the `CraftData` class. The possible values for this enum are listed below. + +```csharp +public enum BackgroundType +{ + Normal, + Blueprint, + PlantWater, + PlantWaterSeed, + PlantAir, + PlantAirSeed, + ExosuitArm +} +``` + +## How can I create a custom background type? +To create a new custom background type, you will need to register an image as the background for some `BackgroundType` instance. + +Fortunately, the custom enums system has made this step really simple. All you will have to do is name your brand new `BackgroundType` instance, then register an image for it. +```csharp +private void Awake() +{ + var myCustomBackground = EnumHandler.AddEntry("CustomBackground") + .WithBackground(ImageUtils.LoadSpriteFromFile(pathToImage)); +} +``` + +And that's it. Now you can use the new `CraftData.BackgroundType` instance anywhere you want. + +## How can I change an item's background? +To edit an item's background type, you need to call the `CraftDataHandler.SetBackgroundType` method sitting in the `Nautilus.Handlers` namespace + + +### Examples +The following example demonstrates the usage of `SetBackgroundType` That makes the titanium background color green. + +```csharp +CraftDataHandler.SetBackgroundType(TechType.Titanium, CraftData.BackgroundType.PlantAirSeed); +``` + +Similarly, if we wanted to set the titanium's background to our custom background from earlier, it would look like the following: +```csharp +CraftDataHandler.SetBackgroundType(TechType.Titanium, myCustomBackground); +``` \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/console-commands.md b/Nautilus/Documentation/tutorials/console-commands.md new file mode 100644 index 000000000..a5afda596 --- /dev/null +++ b/Nautilus/Documentation/tutorials/console-commands.md @@ -0,0 +1,129 @@ +# Adding Custom Console Commands + +Nautilus provides a simple API for allowing you to define custom console commands for your mod to respond to when the user types them into the in-game dev console, with simple parameter type checking to enable user- and developer-friendly error reporting, both on-screen and in the log. + +![An example error message](../images/tutorials/command-results.png) + +## Supported Parameter Types +Currently, only the following types are supported by the API: +- `string` +- `bool` +- `int` +- `float` +- `double` + +If you wish to use a type not in this list, it is recommended to use `string` (as that is what the value coming from the user will be anyway) and handle converting and error-checking the value for yourself. + +If the user provides incorrect parameters for a command, they will be notified of the expected parameters on-screen and in the log file. + +## Registering a Custom Console Command +There are three ways to register custom console commands, so you can use whichever suits your purpose or coding-style best. + +> [!WARNING] +> Registered commands must be unique. If any mod has already added the command, your command will be rejected. + +> [!NOTE] +> A command can have a return type, but it is not necessary. If it does return any type, it will be printed on-screen and in the log file. + +### Registering a `delegate` (Callback) as a Command +By calling `ConsoleCommandsHandler.RegisterConsoleCommand(string command, T callback)`, you can pass an instance of the delegate, whether it is an anonymous lambda or reference to a method that implements the delegate signature to register your callback as a response to the command. + +Note that with a `delegate` command, it is not possible to use optional parameters. +If you want optional parameters, it is recommended to [register a `public static` method as a command](#registering-a-public-static-method-as-a-command) instead. + +In the example below, we are registering a console command by the use of a delegate. The delegate will respond to the "delegatecommand" +command from the dev console in the game. + +You can also use `System.Func` or `System.Action` delegates to define the signature for your command signature. + +```csharp +using BepInEx; +using Nautilus.Handlers; + +[BepInPlugin(PluginInfo.GUID, PluginInfo.MOD_NAME, PluginInfo.VERSION)] +public class MyPlugin : BaseUnityPlugin +{ + private void Start() + { + ConsoleCommandsHandler.RegisterConsoleCommand("delegatecommand", (myString, myInt, myBool) => + { + return $"Parameters: {myString} {myInt} {myBool}"; + }); + } + + private delegate string MyCommand(string myString, int myInt, bool myBool); +} +``` + +The command: `delegatecommand foo 3 true` is a valid signature for the code above. + +### Registering a `public static` Method as a Command +By calling `ConsoleCommandsHandler.RegisterConsoleCommand(string command, Type declaringType, string methodName, Type[] parameters = null)`, you can specify a `public static` method as a response to the command. The API here is similar to Harmony in that it will search for the method in the given type, using the optional `Type[] parameters` to target overloads. + +In the example below, we are registering a console command by specifying a target method. The method will respond to the "methodcommand" +command from the dev console in the game. + +```csharp +using BepInEx; +using Nautilus.Handlers; + +[BepInPlugin(PluginInfo.GUID, PluginInfo.MOD_NAME, PluginInfo.VERSION)] +public class MyPlugin : BaseUnityPlugin +{ + private void Start() + { + ConsoleCommandsHandler.RegisterConsoleCommand("methodcommand", typeof(MyMod), nameof(MyCommand)); + + Logger.LogInfo("Patched successfully!"); + } + + public static string MyCommand(string myString, int myInt, bool myBool = false) + { + return $"Parameters: {myString} {myInt} {myBool}"; + } +} +``` + +The command: `methodcommand foo 3 true` is a valid signature for the code above. + +### Registering Multiple `public static` Methods Within a Class as Commands +By calling `ConsoleCommandsHandler.RegisterConsoleCommands(Type type)`, you can register all `public static` methods decorated with the `ConsoleCommandAttribute` as console commands. + +In the example below, we are registering all console commands specified in the `MyConsoleCommands` types as console commands. +Methods decorated with the `ConsoleCommandAttribute` will respond to the specified command from the dev console in the game. + +```csharp +using BepInEx; +using Nautilus.Commands; +using Nautilus.Handlers; + +[BepInPlugin(PluginInfo.GUID, PluginInfo.MOD_NAME, PluginInfo.VERSION)] +public class MyPlugin : BaseUnityPlugin +{ + private void Start() + { + ConsoleCommandsHandler.RegisterConsoleCommands(typeof(MyConsoleCommands)); + + Logger.Log(Logger.Level.Info, "Patched successfully!"); + } +} + +public static class MyConsoleCommands +{ + /// The MyAttributedCommand method will respond to the "attributedcommand" command from the dev console. + [ConsoleCommand("attributedcommand")] + public static string MyAttributedCommand(string myString, int myInt, bool myBool = false) + { + return $"Parameters: {myString} {myInt} {myBool}"; + } +} +``` + +The commands: +- `attributedcommand foo 3 true` +- `attributedcommand foo 3` +are both valid signatures for the code above. + +> [!NOTE] +> [Optional arguments](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#optional-arguments) can be ignored when executing a command in the dev console. +> When ignored, the specified default value will be used instead. \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/crafting-recipes.md b/Nautilus/Documentation/tutorials/crafting-recipes.md new file mode 100644 index 000000000..fa9934d00 --- /dev/null +++ b/Nautilus/Documentation/tutorials/crafting-recipes.md @@ -0,0 +1,51 @@ +# Editing Crafting Recipes + +Recipes in Subnautica are combining one or multiple items to craft a new and more advanced item in various crafting stations. + +Nautilus offers the [RecipeData](xref:Nautilus.Crafting.RecipeData) class with sufficient data for recipes. + +Below is a table of all the parameters you may interact with in the RecipeData class. + +| Parameter Name | Type | Description | +|----------------|----------------------|----------------------------------------------------------------| +| craftAmount | int | Amounts of copies of the item that is created for this recipe. | +| Ingredients | List<TechType> | A list of ingredients required for this recipe. | +| LinkedItems | List<TechType> | Items that will also be created when this recipe is crafted. | + + +To register or edit recipes, use the `Nautilus.Handlers.CraftDataHandler.SetRecipeData()` method. + +## Examples +The following examples demonstrate the usage of the `SetRecipeData` method. +```csharp +// Set the Titanium Ingot's recipe to only two titaniums +RecipeData titaniumIngotRecipe = new RecipeData(new CraftData.Ingredient(TechType.Titanium, 2)); + +// register the recipe +CraftDataHandler.SetRecipeData(TechType.TitaniumIngot, titaniumIngotRecipe); + + +// Make the scrap metal recipe yield 10 titaniums instead of 5 +RecipeData scrapMetalRecipe = new RecipeData +{ + // We don't want to get a new scrap metal in this recipe, so it should be 0. + craftAmount = 0, + + // Require a scrap metal for the recipe + Ingredients = + { + new CraftData.Ingredient(TechType.ScrapMetal) + }, + + // Yield 10 titaniums when crafted + LinkedItems = Enumerable.Repeat(TechType.Titanium, 10).ToList() +}; + +// register the recipe +CraftDataHandler.SetRecipeData(TechType.ScrapMetal, scrapMetalRecipe); +``` + +## See also +- [SetRecipeData()](xref:Nautilus.Handlers.CraftDataHandler.SetRecipeData(TechType,Nautilus.Crafting.RecipeData)) +- [SetRecipeData()](xref:Nautilus.Handlers.CraftDataHandler.GetRecipeData(TechType)) +- [RecipeData](xref:Nautilus.Crafting.RecipeData) \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/equipment-type.md b/Nautilus/Documentation/tutorials/equipment-type.md new file mode 100644 index 000000000..13d3ca762 --- /dev/null +++ b/Nautilus/Documentation/tutorials/equipment-type.md @@ -0,0 +1,63 @@ +## What are equipment types? +`EquipmentType` is an enum that handles special items. The possible values for this enum are listed below. + +```csharp +public enum EquipmentType +{ + None, // Normal item + Hand, // The item can be equipped in the Hand slot + Head, // The item can be equipped in the Head slot + Body, // The item can be equipped in the Body slot + Gloves, // The item can be equipped in the Gloves slot + Foots, // The item can be equipped in the Feet slot + Tank, // The item can be equipped in the Oxygen Tank slot + Chip, // The item can be equipped in the Chip slots + CyclopsModule, // The item can be equipped in the Cyclops as an upgrade module + VehicleModule, // The item can be equipped both in the Seamoth and in the Prawn Suit as an upgrade module + NuclearReactor, // The item can be used in a Nuclear Reactor + BatteryCharger, // When batteries are thrown in it, they get charged (for buildables) + PowerCellCharger, // When power cells are thrown in it, they get charged (for buildables) + SeamothModule, // The item can be equipped in the Seamoth as an upgrade module + ExosuitModule, // The item can be equipped in the Prawn Suit as an upgrade module + ExosuitArm, // The item can be equipped in the Prawn Suit as an arm + DecoySlot // (Need actual name) Possibly for the decoy tube thing in the cyclops +} +``` + +## How can I create a custom background type? +Since equipment types are simply just enums, we can use the enum handler to create a new instance. +```csharp +private void Awake() +{ + var myCustomEquipmentType = EnumHandler.AddEntry("CustomEquipmentType"); +} +``` + +And that's it. Now you can use the new `CraftData.BackgroundType` instance anywhere you want. + +## How can edit an item's equipment type? +To edit an item's equipment type, you need to call the `CraftDataHandler.SetEquipmentType` method sitting in the `Nautilus.Handlers` namespace + +### Examples +The following example demonstrates the usage of `SetEquipmentType` that enables the player to wear titanium on their head. + +```csharp +CraftDataHandler.SetEquipmentType(TechType.Titanium, EquipmentType.Head); +``` + +Similarly, if we wanted to set the titanium's equipment type to our custom equipment type from earlier, it would look like the following: +```csharp +CraftDataHandler.SetBackgroundType(TechType.Titanium, myCustomEquipmentType); +``` + +If you're setting the equipment type for a custom prefab, we recommend using the `ICustomPrefab.SetEquipment` method instead. +```csharp +var customPrefab = new CustomPrefab("CustomItem", ".", "."); +customPrefab.SetEquipment(EquipmentType.Head); +// rest of the custom prefab configuration is omitted for brevity. +``` + +> [!WARNING] +> It is dangerous to edit equipment types for items that already have one, because they can break. +> For instance, modifying the equipment type for the Radiation Helmet will disable the player from wearing it. + diff --git a/Nautilus/Documentation/tutorials/localization.md b/Nautilus/Documentation/tutorials/localization.md new file mode 100644 index 000000000..d87affe76 --- /dev/null +++ b/Nautilus/Documentation/tutorials/localization.md @@ -0,0 +1,89 @@ +# Localization + +In Subnautica, localization is a key-value string dataset where the key represents a unique identifier that is the same on all languages, and the value represents the translation in a language. + +Nautilus provides a few different approaches to go about adding localization in the [LanguageHandler](xref:Nautilus.Handlers.LanguageHandler) class. + +## Json Files Localization +The most common method of adding localization in game development is via json files where the json file name represents the language name (I.E: `English.json` for English). + +The json files contain a key-value pair where the key is the language key and the value is the translation. + + +### Examples +The following examples demonstrate the usage of json-file-based localizations. + +Json files: +```json +// English.json +{ + "TitaniumClone": "Titanium Clone", + "Tooltip_TitaniumClone": "Titanium clone that makes me go yes." +} +``` +```json +// Spanish.json +{ + "TitaniumClone": "Clon de Titanio", + "Tooltip_TitaniumClone": "Clon de Titanio que me hace decir que sí" +} +``` + +To register json-file-based localizations, all you will have to call is one line of code: +```csharp +LanguageHandler.RegisterLocalizationFolder(); +``` + +> [!NOTE] +> By default, Nautilus expects these json files to be located in the {modFolder}/Localization folder. + +The following example registers the `Translations` folder as the localization folder: +```csharp +LanguageHandler.RegisterLocalizationFolder("Translations"); +``` + +## Dictionary Localization +Nautilus also offers to register string key-value dataset as localization. + +### Examples +The following examples demonstrate the usage of dictionary-based localization. +```csharp +Dictionary _languageEntriesEng = new() +{ + { "TitaniumClone", "Titanium Clone" }, { "Tooltip_TitaniumClone", "Titanium clone that makes me go yes." } +}; + +Dictionary _languageEntriesEsp = new() +{ + { "TitaniumClone", "Clon de Titanio" }, { "Tooltip_TitaniumClone", "Clon de Titanio que me hace decir que sí" } +}; + +// Register our English language entries to the English language +LanguageHandler.RegisterLocalization("English", _languageEntriesEng); + +// Register our Spanish language entries to the Spanish language +LanguageHandler.RegisterLocalization("Spanish", _languageEntriesEsp); +``` + +--- + +## Singular Translation + +Another approach that can be used is translating one key to any desired language. +Additionally, all Nautilus methods that interact with language keys also offer modders to choose the language to translate for. + +### Examples +The following examples demonstrate the usage of singular translations. +```csharp +LanguageHandler.SetLanguageLine("TitaniumClone", "Titanium Clone", "English"); +LanguageHandler.SetLanguageLine("TitaniumClone", "Clon de Titanio", "Spanish"); + +// Adds Spanish translation instead of English +PrefabInfo info = + PrefabInfo.WithTechType("TitaniumClone", + "Clon de Titanio", + "Clon de Titanio que me hace decir que sí", "Spanish"); +``` + +## See also +- [LanguageHandler](xref:Nautilus.Handlers.LanguageHandler) \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/overview.md b/Nautilus/Documentation/tutorials/overview.md new file mode 100644 index 000000000..8c7323dcf --- /dev/null +++ b/Nautilus/Documentation/tutorials/overview.md @@ -0,0 +1,21 @@ +# Tutorials + +This section covers step-by-step tutorials on how to get some of the useful features of Nautilus done with images, demos, and working code. If you've used SML 2.0 before, please start with our [Updating to SML 3.0 Guide](../guides/sml2-to-nautilus.md). + +## Sections + +### Adding Content +* [Spawns](spawns.md) +* [Custom Console Commands](console-commands.md) +* TODO: [Audio (FMOD)](https://www.youtube.com/watch?v=dQw4w9WgXcQ) + + +### Editing Content +* [Background Type](background-type.md) +* [Equipment Type](equipment-type.md) +* [Crafting Recipes](crafting-recipes.md) + + +### Utilities +* [Localization](localization.md) +* TODO: [In-Game Options](https://www.youtube.com/watch?v=dQw4w9WgXcQ) \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/spawns.md b/Nautilus/Documentation/tutorials/spawns.md new file mode 100644 index 000000000..f163d5904 --- /dev/null +++ b/Nautilus/Documentation/tutorials/spawns.md @@ -0,0 +1,151 @@ +# Adding Spawns + +Most of the time in Subnautica, the game uses two different ways to spawn objects; static spawns that always have a fixed position in the world to spawn in, +and loot distribution which is biome-based and sudo-random. + +Nautilus offers modders to add spawns to either systems. You can add spawns for your own custom item, you can also edit vanilla spawns, +or straight up remove them. + +## Coordinated Spawns +Coordinated spawns is Nautilus' version of the aforementioned static spawns. With this system, you are allowed to specify exact world position and rotation spawns for an item. +You may register one or more coordinated spawn(s) for any item by providing either their class ID, or their tech type. + +### Examples +The following examples demonstrate the usage of [CoordinatedSpawnsHandler](xref:Nautilus.Handlers.CoordinatedSpawnsHandler) methods. + +```csharp +private void Awake() +{ + // Adds a Reaper Leviathan to the lava lakes + SpawnInfo reaperInfo = new SpawnInfo(TechType.ReaperLeviathan, new Vector3(280f, -1400f, 47f)); // Lava Lakes + CoordinatedSpawnsHandler.RegisterCoordinatedSpawn(reaperInfo); + + // Adds multiple spawn infos at once + + // Sand Shark's class ID + string sandSharkId = "5e5f00b4-1531-45c0-8aca-84cbd3b580a4"; + + var spawnInfos = new List() + { + new SpawnInfo(TechType.Seamoth, Vector3.zero), + new SpawnInfo(sandSharkId, new Vector3(10, -4, 5), Vector3.up * 90f) // rotate its Y axis 90 degrees + } + CoordinatedSpawnsHandler.RegisterCoordinatedSpawns(spawnInfos); + + // Spawns a batch of titaniums around 10, -3, 15 world position + var randomPositions = RandomPositions(new Vector3(10f, -3f, 15f)); + CoordinatedSpawnsHandler.RegisterCoordinatedSpawnsForOneTechType( + TechType.Titanium, randomPositions); +} + +private List RandomPositions(Vector3 centerPosition) +{ + var result = new List(); + for (int i = 0; i < 5; i++) + { + result.Add(centerPosition + (Random.insideUnitSphere * i)); + } + return result; +} +``` + +For custom prefabs, it is advised to use the [ICustomPrefab.SetSpawns(SpawnLocation[])](xref:Nautilus.Assets.Gadgets.GadgetExtensions#Nautilus_Assets_Gadgets_GadgetExtensions_SetSpawns_Nautilus_Assets_ICustomPrefab_Nautilus_Assets_SpawnLocation___) method instead of directly interacting with the [CoordinatedSpawnsHandler](xref:Nautilus.Handlers.CoordinatedSpawnsHandler) class. + +The example below demonstrates the usage of the `SetSpawns` method. +```csharp +var blueReaper = new CustomPrefab(PrefabInfo.WithTechType("BlueReaper", "Blue Reaper Leviathan", null)); + +// Creates a clone of the Reaper Leviathan prefab and colors it blue, then set the new prefab as our Blue Reaper's game object. +var blueReaperPrefab = new CloneTemplate(blueReaper.Info, TechType.ReaperLeviathan) +{ + ModifyPrefab = prefab => prefab.GetComponentsInChildren().ForEach(r => r.materials.ForEach(m => m.color = Color.blue)) +}; +blueReaper.SetPrefab(blueReaperPrefab); + +// Adds a spawn for our Blue Reaper Leviathan in the lava lakes. +blueReaper.SetSpawns(new SpawnLocation(280f, -1400, 47f)); + +// Register the Blue Reaper Leviathan to the game. +blueReaper.Register(); +``` + +## Loot Distribution +Loot distribution system is by far the most widely used spawning system in the game. Unlike static spawns, Nautilus does not have it's own version of this system, so +we will be registering distributions into the game's system. +Loot distribution only allows adding or editing distributions using a class ID and prefab file name. You normally will also need to provide a biome type, probability, and count for each loot you want to add. + +Below is a table of all the parameters you may interact with in the loot distribution system. + +| Parameter Name | Type | Description | +|-------------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| classId | string | The classId of the entity to add loot for. | +| prefabFileName | string | The internal file path of the entity to add. | +| biomeDistribution | LootDistributionData.BiomeData | The biome data in which this entity might spawn in. | +| probability | float | The chance of this entity spawning. This value can only be between 0-1 inclusive. 0 being no chance of spawning, while 1 is guaranteed to spawn. | +| count | float | Multiplies 1 with this value. This multiplication is accounted everytime this entity has the highest chance to spawn, not a global count. | +| srcData | LootDistributionData.SrcData | A class that combines the prefab file name and biome distribution to one data type. | +| entityInfo | WorldEntityInfo | Contains information on how to spawn this entity. E.G: The size it should spawn in as, and how far it can stay before unloading. | + +> [!WARNING] +> An Entity Info for each class ID to spawn via loot distribution is required. If an entity info does not have one, the loot distribution will ignore it. + +> [!NOTE] +> Usually, vanilla prefabs do have a world entity info assigned to them. While you can, you don't have to register a new one for those that already have one. + +### Examples +The following examples demonstrate the usage of [LootDistributionHandler](xref:Nautilus.Handlers.LootDistributionHandler) methods. + +```csharp +// Drillable Sulphur's class ID +string drillableSulphurClassId = "697beac5-e39a-4809-854d-9163da9f997e"; + +var biomes = new LootDistribution.BiomeData[] +{ + // Lost river's bones field ground + new LootDistributionData.BiomeData { biome = BiomeType.BonesField_Ground, count = 1, probability = 0.07f }, + + // Inactive Lava Zone floor, near the lava + new LootDistributionData.BiomeData { biome = BiomeType.InactiveLavaZone_Chamber_Floor_Far, count = 1, probability = 0.05f } +}; + +// Add spawn for the drillable sulphur +LootDistributionHandler.AddLootDistributionData(drillableSulphurClassId, biomes); + +string rockgrubClassId = CraftData.GetClassIdForTechType(TechType.Rockgrub); + +// Prevents the rockgrub from spawning in the Bulb zone caves. +LootDistributionHandler.EditLootDistributionData(rockgrubClassId, BiomeType.KooshZone_CaveWall, 0f, 0); +``` + +For custom prefabs, it is advised to use the [ICustomPrefab.SetSpawns(LootDistributionData.BiomeData[])](xref:Nautilus.Assets.Gadgets.GadgetExtensions#Nautilus_Assets_Gadgets_GadgetExtensions_SetSpawns_Nautilus_Assets_ICustomPrefab_Nautilus_Assets_SpawnLocation___) method instead of directly interacting with the [LootDistributionHandler](xref:Nautilus.Handlers.LootDistributionHandler) class. + +The example below demonstrates the usage of the `SetSpawns` method. +```csharp +PrefabInfo titaniumCloneInfo = PrefabInfo.WithTechType("TitaniumClone", "Titanium Clone", "Titanium clone that makes me go yes."); + +// Set the vanilla titanium icon for our item +titaniumCloneInfo.WithIcon(SpriteManager.Get(TechType.Titanium)); + +CustomPrefab titaniumClone = new CustomPrefab(titaniumCloneInfo); + +// Creates a clone of the Titanium prefab and colors it red, then set the new prefab as our Titanium Clone's game object. +PrefabTemplate cloneTemplate = new CloneTemplate(titaniumCloneInfo, TechType.Titanium) +{ + // Callback to change all material colors of this clone to red. + ModifyPrefab = prefab => prefab.GetComponentsInChildren().ForEach(r => r.materials.ForEach(m => m.color = Color.red)) +}; +titaniumClone.SetPrefab(cloneTemplate); + +titaniumClone.SetSpawns( + // Adds a chance for our titanium clone to spawn in Safe shallows grass, x4 each time. + new BiomeData { biome = BiomeType.SafeShallows_Grass, count = 4, probability = 0.1f }, + // Adds a chance for our titanium clone to spawn in Safe shallows caves, once each time. + new BiomeData { biome = BiomeType.SafeShallows_CaveFloor, count = 1, probability = 0.4f }); + +// Register the Titanium Clone to the game. +titaniumClone.Register(); +``` + +## See also + - [CoordinatedSpawnsHandler](xref:Nautilus.Handlers.CoordinatedSpawnsHandler) + - [LootDistributionHandler](xref:Nautilus.Handlers.LootDistributionHandler) \ No newline at end of file diff --git a/Nautilus/Documentation/tutorials/toc.yml b/Nautilus/Documentation/tutorials/toc.yml new file mode 100644 index 000000000..9c5bcd60c --- /dev/null +++ b/Nautilus/Documentation/tutorials/toc.yml @@ -0,0 +1,26 @@ +- name: Overview + href: overview.md + +- name: Adding Content + items: + - name: Spawns + href: spawns.md + - name: Custom Console Commands + href: console-commands.md + +- name: Editing Content + items: + - name: Background Type + href: background-type.md + - name: Equipment Type + href: equipment-type.md + - name: Crafting Recipes + href: crafting-recipes.md + +- name: Utilities + items: + - name: Localization + href: localization.md + + + \ No newline at end of file diff --git a/Nautilus/Nautilus.csproj b/Nautilus/Nautilus.csproj index 4b7ef8afa..bd1daa830 100644 --- a/Nautilus/Nautilus.csproj +++ b/Nautilus/Nautilus.csproj @@ -15,7 +15,7 @@ Copyright @ 2023 Core library for modding Subnautica. - + bin\SN.STABLE\ SUBNAUTICA;SUBNAUTICA_STABLE @@ -40,5 +40,5 @@ - + \ No newline at end of file diff --git a/Nautilus/docfx.json b/Nautilus/docfx.json new file mode 100644 index 000000000..804bb4ead --- /dev/null +++ b/Nautilus/docfx.json @@ -0,0 +1,68 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "*.csproj" + ], + "exclude": [ + "**/obj/**", + "**/bin/**", + "_site/**" + ] + } + ], + "shouldSkipMarkup": true, + "dest": "Documentation/api", + "filter": "filter.yml", + "properties": { + "Configuration": "SN.STABLE" + } + } + ], + "build": { + "content": [ + { + "files": [ + "**/*.yml", + "**/*.md" + ], + "src": "Documentation" + } + ], + "resource": [ + { + "files": [ + "logo.svg", + "favicon.ico" + ], + "src": "DocFX" + }, + { + "files": [ + "Documentation/images/**/*" + ] + } + ], + "overwrite": [ + { + "files": [ + "apidoc/**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "globalMetadata": { + "_enableSearch": true + }, + "dest": "_site", + "template": [ + "default", + "DocFX/darkfx" + ] + } +} \ No newline at end of file diff --git a/Nautilus/filter.yml b/Nautilus/filter.yml new file mode 100644 index 000000000..402f241df --- /dev/null +++ b/Nautilus/filter.yml @@ -0,0 +1,8 @@ +apiRules: + - exclude: + hasAttribute: + uid: System.ComponentModel.EditorBrowsableAttribute + ctorArguments: + - System.ComponentModel.EditorBrowsableState.Never + - exclude: + uidRegex: ^Nautilus\.PluginInfo \ No newline at end of file diff --git a/common.props b/common.props index 1a2dc3eaa..b2fd1491c 100644 --- a/common.props +++ b/common.props @@ -1,6 +1,6 @@ - +