Starlight lol

This commit is contained in:
Ben C 2023-08-22 12:14:57 -04:00
parent d9e88c3022
commit e3f8ec505b
No known key found for this signature in database
GPG Key ID: 556064B755159BBC
63 changed files with 5083 additions and 8899 deletions

4
docs/.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
docs/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@ -1,25 +1,67 @@
import { defineConfig } from "astro/config";
import preact from "@astrojs/preact";
import react from "@astrojs/react";
import mdx from "@astrojs/mdx";
import starlight from "@astrojs/starlight";
import sitemap from "@astrojs/sitemap";
import { generateSchema } from "./src/schema_generator";
const site = `https://nh.outerwildsmods.com`;
generateSchema("body_schema.json");
// https://astro.build/config
export default defineConfig({
compressHTML: true,
integrations: [
// Enable Preact to support Preact JSX components.
preact(),
// Enable React for the Algolia search component.
react(),
mdx(),
sitemap({
filter: (page) =>
page !== `${site}/404.html` && page !== `${site}/reference/audio-enum/`
starlight({
title: "New Horizons",
description:
"Documentation on how to use the New Horizons planet creation tool for Outer Wilds.",
defaultLocale: "en-us",
customCss: ["/src/styles/custom.css"],
logo: {
src: "/src/assets/icon.webp",
alt: "The New Horizons Logo"
},
social: {
github: "https://github.com/Outer-Wilds-New-Horizons/new-horizons",
discord: "https://discord.gg/wusTQYbYTc"
},
sidebar: [
{
label: "Start Here",
autogenerate: {
directory: "start-here"
}
},
{
label: "Guides",
autogenerate: {
directory: "guides"
}
},
{
label: "Schemas",
items: [
{ label: "Celestial Body Schema", link: "schemas/body-schema" },
{ label: "Star System Schema", link: "schemas/star-system-schema" },
{ label: "Translation Schema", link: "schemas/translation-schema" },
{ label: "Addon Manifest Schema", link: "schemas/addon-manifest-schema" },
{ label: "Dialogue Schema", link: "schemas/dialogue-schema" },
{ label: "Text Schema", link: "schemas/text-schema" },
{ label: "Ship Log Schema", link: "schemas/shiplog-schema" }
]
},
{
label: "Reference",
autogenerate: {
directory: "reference"
}
}
]
})
],
site
// Process images with sharp: https://docs.astro.build/en/guides/assets/#using-sharp
image: {
service: {
entrypoint: "astro/assets/services/sharp"
}
}
});

View File

@ -1,5 +1,5 @@
{
"name": "docs",
"name": "bustling-belt",
"type": "module",
"version": "0.0.1",
"scripts": {
@ -7,33 +7,20 @@
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"format": "prettier --write . --plugin-search-dir=."
"astro": "astro"
},
"dependencies": {
"@algolia/client-search": "^4.17.0",
"@apidevtools/json-schema-ref-parser": "^10.1.0",
"@astrojs/mdx": "^0.19.7",
"@astrojs/preact": "^2.2.1",
"@astrojs/react": "^2.2.1",
"@astrojs/sitemap": "^1.3.3",
"@docsearch/css": "^3.3.4",
"@docsearch/react": "^3.3.4",
"@types/node": "^18.16.3",
"@types/react": "^18.2.5",
"@types/react-dom": "^18.2.3",
"astro": "^2.5.6",
"eslint": "^8.42.0",
"eslint-plugin-prettier": "^4.2.1",
"fast-xml-parser": "^4.2.3",
"preact": "^10.13.2",
"prettier": "^2.8.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"xml-js": "^1.6.11"
"@astrojs/starlight": "^0.5.2",
"astro": "2.7.0",
"sharp": "^0.32.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.59.8",
"html-escaper": "^3.0.3"
"@apidevtools/json-schema-ref-parser": "^10.1.0",
"eslint": "^8.45.0",
"eslint-plugin-prettier": "^5.0.0",
"fast-xml-parser": "^4.2.5",
"prettier": "^3.0.0",
"prettier-plugin-astro": "^0.11.0",
"xml-js": "^1.6.11"
}
}
}

11082
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

1
docs/public/favicon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" d="M81 36 64 0 47 36l-1 2-9-10a6 6 0 0 0-9 9l10 10h-2L0 64l36 17h2L28 91a6 6 0 1 0 9 9l9-10 1 2 17 36 17-36v-2l9 10a6 6 0 1 0 9-9l-9-9 2-1 36-17-36-17-2-1 9-9a6 6 0 1 0-9-9l-9 10v-2Zm-17 2-2 5c-4 8-11 15-19 19l-5 2 5 2c8 4 15 11 19 19l2 5 2-5c4-8 11-15 19-19l5-2-5-2c-8-4-15-11-19-19l-2-5Z" clip-rule="evenodd"/><path d="M118 19a6 6 0 0 0-9-9l-3 3a6 6 0 1 0 9 9l3-3Zm-96 4c-2 2-6 2-9 0l-3-3a6 6 0 1 1 9-9l3 3c3 2 3 6 0 9Zm0 82c-2-2-6-2-9 0l-3 3a6 6 0 1 0 9 9l3-3c3-2 3-6 0-9Zm96 4a6 6 0 0 1-9 9l-3-3a6 6 0 1 1 9-9l3 3Z"/><style>path{fill:#000}@media (prefers-color-scheme:dark){path{fill:#fff}}</style></svg>

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

BIN
docs/src/assets/icon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,13 +0,0 @@
<footer>
<span>© The New Horizons Team 2023 |</span>
<span>Built With {Astro.generator}</span>
</footer>
<style>
footer {
text-align: center;
margin-top: auto;
padding: 2rem;
border-top: 3px solid var(--theme-divider);
}
</style>

View File

@ -1,35 +0,0 @@
---
import "../styles/theme.css";
import "../styles/index.css";
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<link rel="icon" href="/favicon.ico" />
<link rel="sitemap" href="/sitemap.xml" />
<!-- Preload Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital@0;1&display=swap"
rel="stylesheet"
/>
<!-- Scrollable a11y code helper -->
<script src="/make-scrollable-code-focusable.js" is:inline></script>
<!-- This is intentionally inlined to avoid FOUC -->
<script is:inline>
const root = document.documentElement;
const theme = localStorage.getItem("theme");
if (theme === "dark" || (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
root.classList.add("theme-dark");
} else {
root.classList.remove("theme-dark");
}
</script>

View File

@ -1,32 +0,0 @@
---
import type { CollectionEntry } from "astro:content";
import { SITE, OPEN_GRAPH } from "../consts";
type Props = { canonicalUrl: URL } & CollectionEntry<"docs">["data"];
const { ogLocale, image, title, description, canonicalUrl } = Astro.props;
const formattedContentTitle = title === SITE.title ? SITE.title : `${title} | ${SITE.title}`;
const imageSrc = image?.src ?? OPEN_GRAPH.image.src;
const canonicalImageSrc = new URL(imageSrc, Astro.site);
const imageAlt = image?.alt ?? OPEN_GRAPH.image.alt;
---
<!-- Page Metadata -->
<link rel="canonical" href={canonicalUrl} />
<!-- OpenGraph Tags -->
<meta property="og:title" content={formattedContentTitle} />
<meta property="og:type" content="article" />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:locale" content={ogLocale ?? SITE.defaultLanguage} />
<meta property="og:image" content={canonicalImageSrc} />
<meta property="og:image:alt" content={imageAlt} />
<meta name="description" property="og:description" content={description ?? SITE.description} />
<meta property="og:site_name" content={SITE.title} />
<!-- Twitter Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={formattedContentTitle} />
<meta name="twitter:description" content={description ?? SITE.description} />
<meta name="twitter:image" content={canonicalImageSrc} />
<meta name="twitter:image:alt" content={imageAlt} />

View File

@ -1,8 +0,0 @@
---
type Props = {
size: number;
};
const { size } = Astro.props;
---
<img alt="New Horizons Icon" src="/icon.webp" width="40" height="40" />

View File

@ -1,150 +0,0 @@
---
import { getLanguageFromURL, KNOWN_LANGUAGE_CODES } from "../../languages";
import { SITE } from "../../consts";
import AstroLogo from "./AstroLogo.astro";
import SkipToContent from "./SkipToContent.astro";
import SidebarToggle from "./SidebarToggle";
import LanguageSelect from "./LanguageSelect";
import Search from "./Search";
type Props = {
currentPage: string;
};
const { currentPage } = Astro.props;
const lang = getLanguageFromURL(currentPage);
---
<header>
<SkipToContent />
<nav class="nav-wrapper" title="Top Navigation">
<div class="menu-toggle">
<SidebarToggle client:idle />
</div>
<div class="logo flex">
<a href="/">
<AstroLogo size={40} />
<h1>{SITE.title ?? "Documentation"}</h1>
</a>
</div>
<div style="flex-grow: 1;"></div>
{KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle />}
<!-- Disabled for now -->
<!-- <div class="search-item">
<Search client:idle />
</div> -->
</nav>
</header>
<style>
header {
z-index: 11;
height: var(--theme-navbar-height);
width: 100%;
background-color: var(--theme-navbar-bg);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: sticky;
top: 0;
}
.logo {
flex: 1;
display: flex;
overflow: hidden;
width: 30px;
font-size: 2rem;
flex-shrink: 0;
font-weight: 600;
line-height: 1;
color: hsla(var(--color-base-white), 100%, 1);
gap: 0.25em;
z-index: -1;
}
.logo a {
display: flex;
padding: 0.5em 0.25em;
margin: -0.5em -0.25em;
text-decoration: none;
font-weight: bold;
}
.logo a {
transition: color 100ms ease-out;
color: var(--theme-text);
}
.logo a:hover,
.logo a:focus {
color: var(--theme-text-accent);
}
.logo h1 {
display: none;
font: inherit;
color: inherit;
margin: 0;
}
.nav-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 1em;
width: 100%;
max-width: 82em;
padding: 0 1rem;
}
@media (min-width: 50em) {
header {
position: static;
padding: 2rem 0rem;
}
.logo {
width: auto;
margin: 0;
z-index: 0;
}
.logo h1 {
display: initial;
}
.menu-toggle {
display: none;
}
}
/** Style Algolia */
:root {
--docsearch-primary-color: var(--theme-accent);
--docsearch-logo-color: var(--theme-text);
}
.search-item {
display: none;
position: relative;
z-index: 10;
flex-grow: 1;
padding-right: 0.7rem;
display: flex;
max-width: 200px;
}
@media (min-width: 50em) {
.search-item {
max-width: 400px;
}
}
</style>
<style is:global>
.search-item > * {
flex-grow: 1;
}
</style>

View File

@ -1,47 +0,0 @@
.language-select {
flex-grow: 1;
width: 48px;
box-sizing: border-box;
margin: 0;
padding: 0.33em 0.5em;
overflow: visible;
font-weight: 500;
font-size: 1rem;
font-family: inherit;
line-height: inherit;
background-color: var(--theme-bg);
border-color: var(--theme-text-lighter);
color: var(--theme-text-light);
border-style: solid;
border-width: 1px;
border-radius: 0.25rem;
outline: 0;
cursor: pointer;
transition-timing-function: ease-out;
transition-duration: 0.2s;
transition-property: border-color, color;
-webkit-font-smoothing: antialiased;
padding-left: 30px;
padding-right: 1rem;
}
.language-select-wrapper .language-select:hover,
.language-select-wrapper .language-select:focus {
color: var(--theme-text);
border-color: var(--theme-text-light);
}
.language-select-wrapper {
color: var(--theme-text-light);
position: relative;
}
.language-select-wrapper > svg {
position: absolute;
top: 7px;
left: 10px;
pointer-events: none;
}
@media (min-width: 50em) {
.language-select {
width: 100%;
}
}

View File

@ -1,49 +0,0 @@
/** @jsxImportSource react */
import type { FunctionComponent } from "react";
import "./LanguageSelect.css";
import { KNOWN_LANGUAGES, langPathRegex } from "../../languages";
const LanguageSelect: FunctionComponent<{ lang: string }> = ({ lang }) => {
return (
<div className="language-select-wrapper">
<svg
aria-hidden="true"
focusable="false"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 88.6 77.3"
height="1.2em"
width="1.2em"
>
<path
fill="currentColor"
d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z"
/>
<path
fill="currentColor"
d="M53.6,60.6c-10-4-16-9-22-14c0,0,1.3,1.3,0,0c-6,5-20,13-20,13l-4-6c8-5,10-6,19-13c-2.1-1.9-12-13-13-19h8 c4,9,10,14,10,14c10-8,10-19,10-19h8c0,0-1,13-12,24l0,0c5,5,10,9,19,13L53.6,60.6z M1.6,16.6h56v-8h-23v-7h-9v7h-24V16.6z"
/>
</svg>
<select
className="language-select"
value={lang}
onChange={(e) => {
const newLang = e.target.value;
let actualDest = window.location.pathname.replace(langPathRegex, "/");
if (actualDest == "/") actualDest = `/introduction`;
window.location.pathname = "/" + newLang + actualDest;
}}
>
{Object.entries(KNOWN_LANGUAGES).map(([key, value]) => {
return (
<option value={value} key={value}>
{key}
</option>
);
})}
</select>
</div>
);
};
export default LanguageSelect;

View File

@ -1,75 +0,0 @@
/** Style Algolia */
:root {
--docsearch-primary-color: var(--theme-accent);
--docsearch-logo-color: var(--theme-text);
}
.search-input {
flex-grow: 1;
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 0.33em 0.5em;
overflow: visible;
font-weight: 500;
font-size: 1rem;
font-family: inherit;
line-height: inherit;
background-color: var(--theme-divider);
border-color: var(--theme-divider);
color: var(--theme-text-light);
border-style: solid;
border-width: 1px;
border-radius: 0.25rem;
outline: 0;
cursor: pointer;
transition-timing-function: ease-out;
transition-duration: 0.2s;
transition-property: border-color, color;
-webkit-font-smoothing: antialiased;
}
.search-input:hover,
.search-input:focus {
color: var(--theme-text);
border-color: var(--theme-text-light);
}
.search-input:hover::placeholder,
.search-input:focus::placeholder {
color: var(--theme-text-light);
}
.search-input::placeholder {
color: var(--theme-text-light);
}
.search-hint {
position: absolute;
top: 7px;
right: 19px;
padding: 3px 5px;
display: none;
align-items: center;
justify-content: center;
letter-spacing: 0.125em;
font-size: 13px;
font-family: var(--font-mono);
pointer-events: none;
border-color: var(--theme-text-lighter);
color: var(--theme-text-light);
border-style: solid;
border-width: 1px;
border-radius: 0.25rem;
line-height: 14px;
}
@media (min-width: 50em) {
.search-hint {
display: flex;
}
}
/* ------------------------------------------------------------ *\
DocSearch (Algolia)
\* ------------------------------------------------------------ */
.DocSearch-Modal .DocSearch-Hit a {
box-shadow: none;
border: 1px solid var(--theme-accent);
}

View File

@ -1,97 +0,0 @@
/** @jsxImportSource react */
import { useState, useCallback, useRef } from "react";
import { ALGOLIA } from "../../consts";
import "@docsearch/css";
import "./Search.css";
import { createPortal } from "react-dom";
import * as docSearchReact from "@docsearch/react";
/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
const DocSearchModal =
docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
const useDocSearchKeyboardEvents =
docSearchReact.useDocSearchKeyboardEvents ||
(docSearchReact as any).default.useDocSearchKeyboardEvents;
export default function Search() {
const [isOpen, setIsOpen] = useState(false);
const searchButtonRef = useRef<HTMLButtonElement>(null);
const [initialQuery, setInitialQuery] = useState("");
const onOpen = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);
const onClose = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);
const onInput = useCallback(
(e) => {
setIsOpen(true);
setInitialQuery(e.key);
},
[setIsOpen, setInitialQuery]
);
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef
});
return (
<>
<button type="button" ref={searchButtonRef} onClick={onOpen} className="search-input">
<svg width="24" height="24" fill="none">
<path
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span>Search</span>
<span className="search-hint">
<span className="sr-only">Press </span>
<kbd>/</kbd>
<span className="sr-only"> to search</span>
</span>
</button>
{isOpen &&
createPortal(
<DocSearchModal
initialQuery={initialQuery}
initialScrollY={window.scrollY}
onClose={onClose}
indexName={ALGOLIA.indexName}
appId={ALGOLIA.appId}
apiKey={ALGOLIA.apiKey}
transformItems={(items) => {
return items.map((item) => {
// We transform the absolute URL into a relative URL to
// work better on localhost, preview URLS.
const a = document.createElement("a");
a.href = item.url;
const hash = a.hash === "#overview" ? "" : a.hash;
return {
...item,
url: `${a.pathname}${hash}`
};
});
}}
/>,
document.body
)}
</>
);
}

View File

@ -1,44 +0,0 @@
/** @jsxImportSource preact */
import type { FunctionalComponent } from "preact";
import { useState, useEffect } from "preact/hooks";
const MenuToggle: FunctionalComponent = () => {
const [sidebarShown, setSidebarShown] = useState(false);
useEffect(() => {
const body = document.querySelector("body")!;
if (sidebarShown) {
body.classList.add("mobile-sidebar-toggle");
} else {
body.classList.remove("mobile-sidebar-toggle");
}
}, [sidebarShown]);
return (
<button
type="button"
aria-pressed={sidebarShown ? "true" : "false"}
id="menu-toggle"
onClick={() => setSidebarShown(!sidebarShown)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<span className="sr-only">Toggle sidebar</span>
</button>
);
};
export default MenuToggle;

View File

@ -1,26 +0,0 @@
---
type Props = {};
---
<a href="#article" class="sr-only focus:not-sr-only skiplink"><span>Skip to Content</span></a>
<style>
.skiplink,
.skiplink:focus,
.skiplink:focus-visible {
position: absolute;
padding: 0.25em;
font-size: larger;
top: 0;
left: 0;
right: 0;
z-index: 9;
display: block;
text-align: center;
background-color: var(--theme-text-accent);
color: var(--theme-bg);
border-radius: 0.25em;
outline: var(--theme-bg) solid 1px;
outline-offset: 0;
}
</style>

View File

@ -1,124 +0,0 @@
---
import { getLanguageFromURL } from "../../languages";
import { SIDEBAR } from "../../consts";
type Props = {
currentPage: string;
};
const { currentPage } = Astro.props;
const currentPageMatch = currentPage.endsWith("/")
? currentPage.slice(1, -1)
: currentPage.slice(1);
const langCode = getLanguageFromURL(currentPage);
const sidebar = SIDEBAR[langCode];
---
<nav aria-labelledby="grid-left">
<ul class="nav-groups">
{
Object.entries(sidebar).map(([header, children]) => (
<li>
<div class="nav-group">
<h2 class="nav-group-title">{header}</h2>
<ul>
{children.map((child) => {
const url = Astro.site?.pathname + child.link;
return (
<li class="nav-link">
<a
href={url}
aria-current={
currentPageMatch === child.link ? "page" : false
}
>
{child.text}
</a>
</li>
);
})}
</ul>
</div>
</li>
))
}
</ul>
</nav>
<script is:inline>
window.addEventListener("DOMContentLoaded", () => {
var target = document.querySelector('[aria-current="page"]');
if (target && target.offsetTop > window.innerHeight - 100) {
document.querySelector(".nav-groups").scrollTop = target.offsetTop;
}
});
</script>
<style>
nav {
width: 100%;
margin-right: 1rem;
}
.nav-groups {
height: 100%;
padding: 2rem 0;
overflow-x: visible;
overflow-y: auto;
max-height: 100vh;
}
.nav-groups > li + li {
margin-top: 2rem;
}
.nav-groups > :first-child {
padding-top: var(--doc-padding);
}
.nav-groups > :last-child {
padding-bottom: 2rem;
margin-bottom: var(--theme-navbar-height);
}
.nav-group-title {
font-size: 1rem;
font-weight: 700;
padding: 0.1rem 1rem;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.nav-link a {
font-size: 1rem;
margin: 1px;
padding: 0.3rem 1rem;
font: inherit;
color: inherit;
text-decoration: none;
display: block;
}
.nav-link a:hover,
.nav-link a:focus {
background-color: var(--theme-bg-hover);
}
.nav-link a[aria-current="page"] {
color: var(--theme-text-accent);
background-color: var(--theme-bg-accent);
font-weight: 600;
}
@media (min-width: 50em) {
.nav-groups {
padding: 0;
}
}
</style>
<style is:global>
:root.theme-dark .nav-link a[aria-current="page"] {
color: hsla(var(--color-base-white), 100%, 1);
}
</style>

View File

@ -1,51 +0,0 @@
---
import type { MarkdownHeading } from "astro";
import MoreMenu from "../RightSidebar/MoreMenu.astro";
import TableOfContents from "../RightSidebar/TableOfContents";
type Props = {
title: string;
headings: MarkdownHeading[];
githubEditUrl: string;
};
const { title, headings, githubEditUrl } = Astro.props;
---
<article id="article" class="content">
<section class="main-section">
<h1 class="content-title" id="overview">{title}</h1>
<nav class="block sm:hidden">
<TableOfContents client:media="(max-width: 50em)" headings={headings} />
</nav>
<slot />
</section>
<nav class="block sm:hidden">
<MoreMenu editHref={githubEditUrl} />
</nav>
</article>
<style>
.content {
padding: 0;
max-width: 75ch;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.content > section {
margin-bottom: 4rem;
}
.block {
display: block;
}
@media (min-width: 50em) {
.sm\:hidden {
display: none;
}
}
</style>

View File

@ -1,76 +0,0 @@
---
import { COMMUNITY_INVITE_URL } from "../../consts";
type Props = {
editHref: string;
noFileEdit: boolean;
};
const { editHref, noFileEdit } = Astro.props;
const showMoreSection = Boolean(COMMUNITY_INVITE_URL);
---
{showMoreSection && <h2 class="heading">More</h2>}
<ul>
{
!(noFileEdit ?? false) && editHref && (
<li class={`header-link depth-2`}>
<a class="edit-on-github" href={editHref} target="_blank">
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="pen"
class="svg-inline--fa fa-pen fa-w-16"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
height="1em"
width="1em"
>
<path
fill="currentColor"
d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"
/>
</svg>
<span>Edit this page</span>
</a>
</li>
)
}
{
COMMUNITY_INVITE_URL && (
<li class={`header-link depth-2`}>
<a href={COMMUNITY_INVITE_URL} target="_blank">
<svg
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="comment-alt"
class="svg-inline--fa fa-comment-alt fa-w-16"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
height="1em"
width="1em"
>
<path
fill="currentColor"
d="M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 9.8 11.2 15.5 19.1 9.7L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64z"
/>
</svg>
<span>Join our community</span>
</a>
</li>
)
}
</ul>
<style>
.edit-on-github {
text-decoration: none;
font: inherit;
color: inherit;
font-size: 1rem;
}
</style>

View File

@ -1,35 +0,0 @@
---
import type { MarkdownHeading } from "astro";
import TableOfContents from "./TableOfContents";
import MoreMenu from "./MoreMenu.astro";
type Props = {
headings: MarkdownHeading[];
githubEditUrl: string;
noFileEdit: boolean;
};
const { headings, githubEditUrl, noFileEdit } = Astro.props;
---
<nav class="sidebar-nav" aria-labelledby="grid-right">
<div class="sidebar-nav-inner">
<TableOfContents client:media="(min-width: 50em)" headings={headings} />
<MoreMenu editHref={githubEditUrl} noFileEdit={noFileEdit} />
</div>
</nav>
<style>
.sidebar-nav {
width: 100%;
position: sticky;
top: 0;
}
.sidebar-nav-inner {
height: 100%;
padding: 0;
padding-top: var(--doc-padding);
overflow: auto;
}
</style>

View File

@ -1,95 +0,0 @@
import type { MarkdownHeading } from "astro";
import type { FunctionalComponent } from "preact";
import { unescape } from "html-escaper";
import { useState, useEffect, useRef } from "preact/hooks";
type ItemOffsets = {
id: string;
topOffset: number;
};
const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({
headings = []
}) => {
const toc = useRef<HTMLUListElement>();
const onThisPageID = "on-this-page-heading";
const itemOffsets = useRef<ItemOffsets[]>([]);
const [currentID, setCurrentID] = useState("overview");
useEffect(() => {
const getItemOffsets = () => {
const titles = document.querySelectorAll("article :is(h1, h2, h3, h4)");
itemOffsets.current = Array.from(titles).map((title) => ({
id: title.id,
topOffset: title.getBoundingClientRect().top + window.scrollY
}));
};
getItemOffsets();
window.addEventListener("resize", getItemOffsets);
return () => {
window.removeEventListener("resize", getItemOffsets);
};
}, []);
useEffect(() => {
if (!toc.current) return;
const setCurrent: IntersectionObserverCallback = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const { id } = entry.target;
if (id === onThisPageID) continue;
setCurrentID(entry.target.id);
break;
}
}
};
const observerOptions: IntersectionObserverInit = {
// Negative top margin accounts for `scroll-margin`.
// Negative bottom margin means heading needs to be towards top of viewport to trigger intersection.
rootMargin: "-100px 0% -66%",
threshold: 1
};
const headingsObserver = new IntersectionObserver(setCurrent, observerOptions);
// Observe all the headings in the main page content.
document
.querySelectorAll("article :is(h1,h2,h3)")
.forEach((h) => headingsObserver.observe(h));
// Stop observing when the component is unmounted.
return () => headingsObserver.disconnect();
}, [toc.current]);
const onLinkClick = (e) => {
setCurrentID(e.target.getAttribute("href").replace("#", ""));
};
return (
<>
<h2 id={onThisPageID} className="heading">
On this page
</h2>
<ul ref={toc}>
{headings
.filter(({ depth }) => depth > 1 && depth < 4)
.map((heading) => (
<li
className={`header-link depth-${heading.depth} ${
currentID === heading.slug ? "current-header-link" : ""
}`.trim()}
>
<a href={`#${heading.slug}`} onClick={onLinkClick}>
{unescape(heading.text)}
</a>
</li>
))}
</ul>
</>
);
};
export default TableOfContents;

View File

@ -1,5 +1,5 @@
---
import { Schema, SchemaTools } from "../../schema_utils";
import { Schema, SchemaTools } from '../../schema_utils';
export interface Props {
schema: Schema;
@ -99,7 +99,7 @@ const HeadingTag = levelMap[level] ?? "h6";
color: unset;
}
50% {
color: var(--theme-accent);
color: var(--sl-theme-accent);
}
100% {
color: unset;
@ -114,6 +114,7 @@ const HeadingTag = levelMap[level] ?? "h6";
div.header {
display: flex;
flex-direction: column;
gap: 1rem;
}
div.wrapper[data-level="2"] {
@ -151,16 +152,16 @@ const HeadingTag = levelMap[level] ?? "h6";
}
span {
background-color: var(--theme-bg-accent);
background-color: hsl(var(--sl-hue-accent) 50% 10%);
font-size: small;
padding: 0.3rem;
padding: 0.5rem;
margin-right: 0.3rem;
border: solid 2px var(--theme-accent);
border: solid 2px var(--sl-color-accent-low);
border-radius: 5rem;
}
span.required {
background-color: hsla(var(--color-yellow), var(--theme-accent-opacity));
border-color: hsla(var(--color-yellow), 1);
background-color: hsl(var(--sl-hue-yellow) 50% 10%);
border-color: var(--sl-color-yellow-low);
}
</style>

View File

@ -0,0 +1,16 @@
---
import { SchemaTools } from '../../schema_utils';
import Content from './Content.astro';
interface Props {
fileName: string;
}
const { fileName } = Astro.props;
const schema = SchemaTools.readSchema(fileName);
---
<Content schema={schema} level={0} />

View File

@ -0,0 +1,20 @@
---
import { SchemaTools } from '../../schema_utils';
import Content from './Content.astro';
interface Props {
fileName: string;
def: string;
}
const { fileName, def } = Astro.props;
const schema = SchemaTools.readSchema(`../NewHorizons/Schemas/${fileName}`);
const defs = SchemaTools.getDefs(schema);
const targetDef = defs.find(d => d.slug === def);
---
<Content schema={targetDef!} level={0} isDef />

View File

@ -1,59 +0,0 @@
---
title: New Horizons
---
![New Horizons Logo](/home_logo.webp)
This is the official documentation for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons), a framework for creating custom planets in the game [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html) by Mobius Digital. Planets are created using simple JSON and XML files and are loaded at runtime. An [API](api) is also provided for more advanced use-cases.
## Getting Started
For a guide on getting started with New Horizons, go to the [Getting Started](/getting-started) page.
## Helpful Resources
### Templates
There are two templates for New Horizons addons.
The [New Horizons Addon Template](https://github.com/xen-42/ow-new-horizons-config-template) is used for addons that **don't use custom code**,
this is ideal for simple projects and people just starting out.
The [Outer Wilds Mod Template](https://github.com/ow-mods/ow-mod-template) is used for mods that use custom code,
**you must enable "Use New Horizons" in order for it to work with New Horizons**.
This is ideal for people that want to expand on New Horizons and add custom behaviour.
### Texturing
The texturemap/heightmap feature was inspired by the Kerbal Space Program mod Kopernicus. A lot of the same techniques that apply to
planet creation there apply to New Horizons. If you need help with planetary texturing, check out [The KSP texturing guide](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/).
[Photopea](https://www.photopea.com/) is a free browser-based photo editor which has useful features like
rectangular-to-polar coordinate transformation, useful for fixing abnormalities at the poles of your planets.
### Helpful Mods
These mods are useful when developing your addon
- [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) - Used to find the paths of game objects for copying and can be used to manually position props, ship log entries, and more.
- [Collider Visualizer](https://outerwildsmods.com/mods/collidervisualizer) - Useful when creating dialogue triggers or reveal volumes.
- [Save Editor](https://outerwildsmods.com/mods/saveeditor) - Useful when creating a custom [ship log](/ship-log), can be used to reveal all custom facts so you can see them in the ship's computer.
- [Time Saver](https://outerwildsmods.com/mods/timesaver/) - Lets you skip some repeated cutscenes and get into the game faster.
### Helpful Tools
These tools/references are highly recommended
- [VSCode](https://code.visualstudio.com/)
- [VSCode XML Addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml)
- [XML Basics Tutorial](https://www.w3schools.com/xml/xml_whatis.asp)
- [JSON Basics Tutorial](https://www.tutorialspoint.com/json/index.htm)
- [The Examples Mod](https://github.com/xen-42/ow-new-horizons-examples)
## Disclaimer
This work is unofficial fan content created under permission from the [Mobius Digital Fan Content Policy](https://www.mobiusdigitalgames.com/fan-content-policy.html). It includes materials which are the property of Mobius Digital, and it is neither approved nor endorsed by Mobius Digital.
We are not responsible for any mods created using the New Horizons modding framework and assume no responsibility in the event an addon violates the terms.
## License
The license for this project is available [on the GitHub repository](https://github.com/xen-42/outer-wilds-new-horizons/blob/main/LICENSE).

View File

@ -1,67 +0,0 @@
export const SITE = {
title: "New Horizons",
description:
"Documentation on how to use the New Horizons planet creation tool for Outer Wilds.",
defaultLanguage: "en-us"
} as const;
export const OPEN_GRAPH = {
image: {
src: "https://nh.outerwildsmods.com/public/home_logo.webp",
alt: "The New Horizons Logo"
}
};
export const KNOWN_LANGUAGES = {
English: "en"
} as const;
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
export const GITHUB_EDIT_URL = `https://github.com/Outer-Wilds-New-Horizons/new-horizons/tree/main/docs`;
export const COMMUNITY_INVITE_URL = `https://discord.gg/wusTQYbYTc`;
// See "Algolia" section of the README for more information.
export const ALGOLIA = {
indexName: "XXXXXXXXXX",
appId: "XXXXXXXXXX",
apiKey: "XXXXXXXXXX"
};
export type Sidebar = Record<
(typeof KNOWN_LANGUAGE_CODES)[number],
Record<string, { text: string; link: string }[]>
>;
export const SIDEBAR: Sidebar = {
en: {
Intro: [{ text: "Introduction", link: "" }],
Guides: [
{ text: "Getting Started", link: "getting-started" },
{ text: "Creating An Addon", link: "creating-addons" },
{ text: "Updating Existing Planets", link: "updating-planets" },
{ text: "Creating New Planets", link: "planet-generation" },
{ text: "Detailing Planets", link: "details" },
{ text: "Custom Star Systems", link: "star-systems" },
{ text: "Adding Translations", link: "translation" },
{ text: "Understanding XML", link: "xml" },
{ text: "Ship Log", link: "ship-log" },
{ text: "Dialogue", link: "dialogue" },
{ text: "API", link: "api" },
{ text: "Extending Configs", link: "extending-configs" },
{ text: "Publishing Your Addon", link: "publishing" }
],
Schemas: [
{ text: "Celestial Body Schema", link: "schemas/body-schema" },
{ text: "Star System Schema", link: "schemas/star-system-schema" },
{ text: "Translation Schema", link: "schemas/translation-schema" },
{ text: "Addon Manifest Schema", link: "schemas/addon-manifest-schema" },
{ text: "Dialogue Schema", link: "schemas/dialogue-schema" },
{ text: "Text Schema", link: "schemas/text-schema" },
{ text: "Ship Log Schema", link: "schemas/shiplog-schema" }
],
Reference: [
{ text: "Bramble Colors", link: "reference/bramble-colors" },
{ text: "AudioClip Values", link: "reference/audio-enum" }
]
}
};

View File

@ -1,20 +1,7 @@
import { defineCollection, z } from "astro:content";
import { SITE } from "../consts";
import { defineCollection } from 'astro:content';
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
const docs = defineCollection({
schema: z.object({
title: z.string().default(SITE.title),
description: z.string().default(SITE.description),
lang: z.literal("en-us").default(SITE.defaultLanguage),
dir: z.union([z.literal("ltr"), z.literal("rtl")]).default("ltr"),
image: z
.object({
src: z.string(),
alt: z.string()
})
.optional(),
ogLocale: z.string().optional()
})
});
export const collections = { docs };
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
i18n: defineCollection({ type: 'data', schema: i18nSchema() }),
};

View File

@ -1,87 +0,0 @@
---
title: Creating An Addon
description: A guide to creating addons for New Horizons
---
Up until now, you've been using the sandbox feature of New Horizons (simply placing your files in the `xen.NewHorizons` folder).
While this is the easiest way to get started, you won't be able to publish your work like this. In this tutorial we will:
- Create a new GitHub repository from a template
- Use GitHub Desktop to clone this repository to our computer
- Edit the files in this repository to make our addon
## Making a GitHub Repository
To get started, we need a place to store our code. GitHub is one of the most popular websites to store source code, and it's also what the mod database uses to let people access our mod.
First you're going to want to [create a GitHub account](https://github.com/signup), and then head to [the nh-addon-template template repository](https://github.com/xen-42/ow-new-horizons-config-template).
Now, click the green "Use This Template" button.
- Set the Name to your username followed by a dot (`.`), followed by your mod's name in PascalCase (no spaces, new words have capital letters). So for example if my username was "Test" and my mod's name was "Really Cool Addon", I would name the repo `Test.ReallyCoolAddon`.
- The description is what will appear in the mod manager under the mod's name, you can always edit it later
- You can set the visibility to what you want; But when you go to publish your mod, it will need to be public
## Cloning the Repository
Now that we've created our GitHub repository (or "repo"), we need to clone (or download) it onto our computer.
To do this we recommend using the [GitHub Desktop App](https://desktop.github.com/), as it's much easier to use than having to fight with the command line.
Once we open GitHub desktop we're going to log in, select File -> Options -> Accounts and sign in to your newly created GitHub account.
Now we're ready to clone the repo, select File -> Clone Repository. Your repository should appear in the list.
Before you click "Clone", we need to select where to store the repo, open up the mod manager and go to "Settings", then copy the value located in the "OWML path" field and paste it in the "Local path" field on GitHub desktop.
This _will_ show an error, and this is going to sound extremely stupid, but just click the "Choose..." button, and press "Select Folder" and it will be fixed.
Our repository is now cloned to our computer!
## Editing Files
Now that our repo is cloned, we're going to need to edit the files in it.
To get started editing the files, simply click "Open in Visual Studio Code" in GitHub Desktop.
### Files Explanation
- .github: This folder contains special files for use on GitHub, they aren't useful right now but will be when we go to publish the mod
- planets: This folder contains a single example config file that destroys the Quantum Moon, we'll keep it for now so we can test our addon later.
- .gitattributes: This is another file that will be useful when publishing
- default-config.json: This file is used in C#-based mods to allow a custom options menu, New Horizons doesn't support a custom options menu, but we still need the file here in order for the addon to work.
- manifest.json: This is the first file we're going to edit, we need to fill it out with information about our mod
- First you're going to set `author` to your author name, this should be the same name that you used when creating the GitHub repo.
- Next, set `name` to the name you want to appear in the mod manager and website.
- Now set `uniqueName` to the name of your GitHub Repo.
- You can leave `version`, `owmlVersion`, and `dependencies` alone
- NewHorizonsConfig.dll: This is the heart of your addon, make sure to never move or rename it.
- README.md: This file is displayed on the mod website when you go to a specific mod's page, you can delete the current contents.
- This file is a [markdown](https://www.markdowntutorial.com/) file, if you're not comfortable writing an entire README right now, just write a small description of your mod.
### Committing The Changes
Now that we have our files set up, switch back to GitHub desktop, you'll notice that the files you've changed have appeared in a list on the left.
What GitHub Desktop does is keep track of changes you make to your files over time.
Then, once you're ready, you commit these changes to your repo by filling out the "Summary" field with a small description of your changes, and then pressing the blue button that says "commit to main".
Think of committing like taking a snapshot of your project at this moment in time. If you ever mess up your project, you can always revert to another commit to get back to a working version. It is highly recommended to commit often, there is no downside to committing too much.
### Pushing The Changes
OK, so we've committed our new changes, but these commits still only exist on our computer, to get these changes onto GitHub we can click the "Push Origin" button on the top right.
## Testing The Addon
Now that we have our manifest filled out, go take a look at the "Mods" tab in the manager and scroll to the bottom of the "Enabled Mods" list.
You should see your mod there with the downloads counter set as a dash and the version set to "0.0.0".
### Checking In-Game
Now when you click "Start Game" and load into the solar system, you should be able to notice that the quantum moon is gone entirely, this means that your addon and its configs were successfully loaded.
## Note About File Paths
Whenever something refers to the "relative path" of a file, it means relative to your mod's directory, this means you **must** include the `planets` folder in the path:
```json
"planets/assets/images/MyCoolImage.png"
```
## Going Forward
Now instead of using the New Horizons mod folder, you can use your own mod's folder instead.

View File

@ -1,255 +0,0 @@
---
title: Getting Started
description: A guide for getting started with New Horizons
---
Congrats on taking the first step to becoming an addon developer!
This tutorial will outline how to begin learning to use new horizons.
## Recommended Tools
It's strongly recommended you get [VSCode](https://code.visualstudio.com/) to edit your files, as it can provide syntax and error highlighting.
## Using The Sandbox
Making an entirely separate addon can get a little complicated, so New Horizons provides a way to play around without the need to set up a full addon.
To get started, navigate to your mod manager and click the ⋮ symbol, then select "Show In Explorer".
![Select "Show in explorer"](/getting_started/mod_manager_show_in_explorer.webp)
Now, create a new folder named "planets". As the name suggests, New Horizons will search the files in this folder for planets to generate.
## Making Your First Planet
To get started, create a new file in this folder called `wetrock.json`, we'll explain what that .json at the end means soon.
Open this file in VSCode (you can do so by right-clicking the file and clicking "Open with Code")
Once in VSCode, paste this code into the file:
```json
{
"name": "Wetrock",
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/body_schema.json",
"starSystem": "SolarSystem",
"Base": {
"groundSize": 100,
"surfaceSize": 101,
"surfaceGravity": 12,
"hasMapMarker": true
},
"Orbit": {
"semiMajorAxis": 1300,
"primaryBody": "TIMBER_HEARTH",
"isMoon": true,
"isTidallyLocked": true
},
"Atmosphere": {
"size": 150,
"fogTint": {
"r": 200,
"g": 255,
"b": 255,
"a": 255
},
"fogSize": 150,
"fogDensity": 0.2,
"hasRain": true
}
}
```
This language is **J**ava**S**cript **O**bject **N**otation, or JSON.
It's a common way to convey data in many programs.
## Understanding JSON
All JSON files start out with an `object`, or a set of key value mappings, for example if we represent a person as JSON it might look like:
```json
{
"name": "Jim"
}
```
Those braces (`{}`) denote an object, and by doing `"name": "Jim"` we're saying that the name of this person is Jim;
`"name"` is the key, and `"Jim"` is the value.
Objects can have multiple keys as well, as long as you separate them by commas:
```json
{
"name": "Jim",
"age": 23
}
```
But wait! why is `Jim` in quotation marks while `23` isn't? that's because of something called data types.
Each value has a datatype, in this case `"Jim"` is a `string`, because it represents a _string_ of characters.
Age is a `number`, it represents a numerical value. If we put 23 in quotation marks, its data type switches from a number to a string.
And if we remove the quotation marks from `"Jim"` we get a syntax error (a red underline). Datatypes are a common source of errors, which is why we recommend using an editor like VSCode.
### JSON Data Types
Here's a list of data types you'll use when making your addons:
#### String
A set of characters surrounded in quotation marks
```json
"Im a string!"
```
If you need to use quotation marks within your string, place a backslash (`\`) before them
```json
"\"Im a string!\" - Mr. String Stringerton"
```
#### Number
A numerical value, can be negative and have decimals, **not** surrounded in quotation marks
```json
-25.3
```
#### Boolean
A `true` or `false` value, think of it like an on or off switch, also not surrounded in quotation marks
```json
true
```
```json
false
```
#### Array
A set of values, values can be of any data type. Items are seperated by commas.
```json
[23, 45, 56]
```
```json
["Bob", "Suzy", "Mark"]
```
And they can be empty like so:
```json
[]
```
#### Object
A set of key value pairs, where each key is a string and each value can be of any data type (even other objects!)
```json
{
"name": "Jim",
"age": 23,
"isMarried": false,
"clothes": {
"shirtColor": "red",
"pantsColor": "blue"
},
"friends": ["Bob", "Wade"],
"enemies": []
}
```
## Back to Wetrock
Now that we understand JSON better, let's look at that config file again:
```json
{
"name": "Wetrock",
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/body_schema.json",
"starSystem": "SolarSystem",
"Base": {
"groundSize": 100,
"surfaceSize": 101,
"surfaceGravity": 12,
"hasMapMarker": true
},
"Orbit": {
"semiMajorAxis": 1300,
"primaryBody": "TIMBER_HEARTH",
"isMoon": true,
"isTidallyLocked": true
},
"Atmosphere": {
"size": 150,
"fogTint": {
"r": 200,
"g": 255,
"b": 255,
"a": 255
},
"fogSize": 150,
"fogDensity": 0.2,
"hasRain": true
}
}
```
Here we can see we have a planet object, which name is "Wetrock", and is in the "SolarSystem" (Base-game) star system.
It has an object called Base, which has a groundSize of 100, and a surfaceSize of 101, and the list continues on.
Alright so now that we understand how the file is structures, let's look into what each value actually does:
- `name` simply sets the name of the planet
- `$schema` we'll get to in a second
- `starSystem` specifies what star system this planet is located in, in this case we're using the base game star system, so we put "SolarSystem"
- Then it has an object called `Base`
- Base has a `groundSize` of 100, this generates a perfect sphere that is 100 units in radius as the ground of our planet
- It also has a `surfaceSize` of 101, surface size is used in many calculations, it's generally good to set it to a bit bigger than ground size.
- `surfaceGravity` describes the strength of gravity on this planet, in this case it's 12 which is the same as Timber Hearth
- `hasMapMarker` tells new horizons that we want this planet to have a marker on the map screen
- Next it has another object called `Orbit`
- `semiMajorAxis` specifies the radius of the orbit (how far away the body is from its parent)
- `primaryBody` is set to TIMBER_HEARTH, this makes our planet orbit timber hearth
- `isMoon` simply tells the game how close you have to be to the planet in map mode before its name appears
- `isTidallyLocked` makes sure that one side of our planet is always facing timber hearth (the primary body)
- Finally, we have `Atmosphere`
- Its `size` is 150, this simply sets how far away from the planet our atmosphere stretches
- Its `fogTint` is set to a color which is an object with r, g, b, and a properties (properties is another word for keys)
- `fogSize` determines how far away the fog stretches from the planet
- `fogDensity` is simply how dense the fog is
- `hasRain` makes rainfall on the planet
### What's a Schema?
That `$schema` property is a bit special, it instructs VSCode to use a pre-made schema to provide a better editing experience.
With the schema you get:
- Automatic descriptions for properties when hovering over keys
- Automatic error detection for incorrect data types or values
- Autocomplete, also called IntelliSense
## Testing The Planet
With the new planet created (_and saved!_), launch the game through the mod manager and click resume expedition. If all went well you should be able to open your map and see wetrock orbiting Timber Hearth.
If you run into issues please make sure:
- You placed the JSON file in a folder called `planets` in the New Horizons mod folder
- There are no red or yellow squiggly lines in your file
## Experiment
With that, try tweaking some value like groundSize and semiMajorAxis, get a feel for how editing JSON works.
## Reloading Configs
It can get annoying when you have to keep closing and opening the game over and over again to test changes, that's why New Horizons has a "Reload Configs" feature.
To enable it, head to your Mods menu and select New Horizons and check the box that says Debug, this will cause a "Reload Configs" option to appear in your pause menu which will reload changes from your filesystem.
You may also notice blue and yellow logs start appearing in your console, this is New Horizons providing additional info on what it's currently doing, it can be helpful when you're trying to track down an issue.
## Modules
Base, Atmosphere, and Orbit are all modules, which define the different aspects of your planet, modules are represented by JSON objects

View File

@ -5,9 +5,9 @@ description: A guide to adding details to planets in New Horizons
For physical objects there are currently two ways of setting them up: specify an asset bundle and path to load a custom asset you created, or specify the path to the item you want to copy from the game in the scene hierarchy. Use the [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) mod to find an object you want to copy onto your new body. Some objects work better than others for this. Good luck. Some pointers:
- Use "Object Explorer" to search
- Generally you can find planets by writing their name with no spaces/punctuation followed by "\_Body".
- There's also [this community-maintained list of props](https://docs.google.com/spreadsheets/d/1VJaglB1kRL0VqaXhvXepIeymo93zqhWex-j7_QDm6NE/edit?usp=sharing) which you can use to find interesting props and check to see if they have collision.
- Use "Object Explorer" to search
- Generally you can find planets by writing their name with no spaces/punctuation followed by "\_Body".
- There's also [this community-maintained list of props](https://docs.google.com/spreadsheets/d/1VJaglB1kRL0VqaXhvXepIeymo93zqhWex-j7_QDm6NE/edit?usp=sharing) which you can use to find interesting props and check to see if they have collision.
## Using the Prop Placer

View File

@ -5,8 +5,6 @@ description: Guide to making dialogue in New Horizons
This page goes over how to use dialogue in New Horizons.
You may want to view [Understanding XML](/xml) if you haven't already.
## Understanding Dialogue
### Dialogue Tree
@ -160,4 +158,4 @@ Defining `<DialogueTarget>` in the `<DialogueNode>` tag instead of a `<DialogueO
### DialogueTargetShipLogCondition
Used in tandem with `DialogueTarget`, makes it so you must have a [ship log fact](/ship-log#explore-facts) to go to the next node.
Used in tandem with `DialogueTarget`, makes it so you must have a [ship log fact](/guides/ship-log#explore-facts) to go to the next node.

View File

@ -4,16 +4,16 @@ title: Publishing Addons
This page goes over how to publish a release for your mod and submit your mod to the [outer wilds mod database](https://github.com/ow-mods/ow-mod-db) for review.
This guide assumes you've created your addon by following [the addon creation guide](/creating-addons).
This guide assumes you've created your addon by following [the addon creation guide in Getting Started](/start-here/getting-started#creating-addons).
## Housekeeping
Before you release anything, you'll want to make sure:
- Your mod has a descriptive `README.md`. (This will be shown on the website)
- Your repo has the description field (click the cog in the right column on the "Code" tab) set. (this will be shown in the manager)
- There's no `config.json` in your addon. (Not super important, but good practice)
- Your manifest has a valid name, author, and unique name.
- Your mod has a descriptive `README.md`. (This will be shown on the website)
- Your repo has the description field (click the cog in the right column on the "Code" tab) set. (this will be shown in the manager)
- There's no `config.json` in your addon. (Not super important, but good practice)
- Your manifest has a valid name, author, and unique name.
## Releasing

View File

@ -3,12 +3,6 @@ title: Ship Log
description: A guide to editing the ship log in New Horizons
---
## Intro
Welcome! this page outlines how to create a custom ship log.
If you haven't already, you may want to take a look at [Understanding XML](/xml) to get a better idea of how XML works.
## Understanding Ship Logs
First thing's first, I'll define some terminology regarding ship logs in the game, and how ship logs are structured.
@ -160,7 +154,7 @@ You can load your XML file to your planet by doing adding the following to your
### Entry Layout
By default, entries in rumor mode are laid out by rows, where each row is one planet. This will not make for a perfect
layout, so you can use the `entryPositions` property in your star system config to change them.
layout, so you can use the `entryPositions` property **in your star system config** to change them.
For example, if I want to change an entry with the ID of `EXAMPLE_ENTRY` and another with the ID of `EXAMPLE_ENTRY_2`:
```json

View File

@ -0,0 +1,54 @@
---
title: New Horizons
description: Create custom planets for Outer Wilds with New Horizons
template: splash
hero:
tagline: Create custom planets for Outer Wilds with New Horizons
image:
file: /src/assets/splash_hero_image.webp
alt: The New Horizons Logo
actions:
- text: Get Started
link: /start-here/getting-started
icon: right-arrow
variant: primary
- text: Schema Reference
link: /schemas/body_schema
---
import { Card, CardGrid } from "@astrojs/starlight/components";
This is the official site for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons),
a framework for creating custom planets in the game [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html)
by Mobius Digital.
## Features
<CardGrid stagger>
<Card title="Create Planets Through JSON" icon="document">
Use declarative JSON to create custom planets without having to write a line
of code.
</Card>
<Card title="Custom Star Systems" icon="star">
Create solar systems seperate from the base game's with its own planets,
vessel coordinates, and ship log.
</Card>
<Card title="API" icon="forward-slash">
Interface with various life cycle events and functions during star system
creation with New Horizon's API.
</Card>
<Card title="Extendable Configs" icon="puzzle">
Use the `extras` key in JSON files to allow extendable functionality with
other mods.
</Card>
</CardGrid>
## Disclaimer
This work is unofficial fan content created under permission from the [Mobius Digital Fan Content Policy](https://www.mobiusdigitalgames.com/fan-content-policy.html). It includes materials which are the property of Mobius Digital, and it is neither approved nor endorsed by Mobius Digital.
We are not responsible for any mods created using the New Horizons modding framework and assume no responsibility in the event an addon violates the terms.
## License
The license for this project is available [on the GitHub repository](https://github.com/xen-42/outer-wilds-new-horizons/blob/main/LICENSE).

View File

@ -1,12 +1,13 @@
---
title: API
description: A guide on using New Horizons' API
title: API Reference
description: API reference for New Horizons
---
## How to use the API
## API Interface
First create the following interface in your mod:
Put this in a C# file somewhere in your mod:
<!-- Auto update this would be cool -->
```cs
public interface INewHorizons
{
@ -114,6 +115,8 @@ public interface INewHorizons
}
```
## Usage
In your main `ModBehaviour` class you can get the NewHorizons API like so:
```cs
@ -126,10 +129,4 @@ public class MyMod : ModBehaviour
}
```
You can then use the APIs `LoadConfigs()` method to load from a "planets" folder, or use the `GetPlanet()` method to get planets and do whatever with them. Just make sure you create planets in the `Start()` method or at least before the SolarSystem scene loads, or they will not be created.
The `GetChangeStarSystemEvent` and `GetStarSystemLoadedEvent` events let you listen in for when the player starts changing to a new system (called when entering a black hole or using the warp drive) and when the system is fully loaded in, respectively.
You can also use the `GetInstalledAddons` method to get a list of addons that are installed and enabled.
You can also use `SpawnObject` to directly copy a base-game GameObject to the specified position and rotation.
See the doc comments on each method to see what they do.

View File

@ -0,0 +1,9 @@
---
title: Celestial Body Schema
description: Describes a celestial body to generate
editUrl: false
---
import Schema from "/src/components/Schemas/Schema.astro";
<Schema fileName="body_schema.json" />

View File

@ -0,0 +1,145 @@
---
title: Getting Started
description: A guide for getting started with New Horizons
next:
link: /guides/creating-addons
label: Creating Addons
---
## What Is New Horizons?
New Horizons is a mod creation framework for creating custom planets in the game [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html) by Mobius Digital. It primarily uses JSON files, along with a few XML files to define content.
## Recommended Tools
It's strongly recommended you get [VSCode](https://code.visualstudio.com/) to edit your files, as it can provide syntax and error highlighting.
## Try Out New Horizons
Making an entirely separate addon can get a little complicated, so New Horizons provides a way to play around without the need to set up a full addon. If you want to make a full project, see [Creating An Addon](#creating-an-addon).
To get started, navigate to your mod manager and click the ⋮ symbol, then select "Show In Explorer".
![Select "Show in explorer"](/getting_started/mod_manager_show_in_explorer.webp)
Now, create a new folder named "planets". As the name suggests, New Horizons will search the files in this folder for planets to generate.
### Making Your First Planet
To get started, create a new file in this folder called `wetrock.json`, we'll explain what that .json at the end means soon.
Open this file in VSCode (you can do so by right-clicking the file and clicking "Open with Code")
Once in VSCode, paste this code into the file:
```json
{
"name": "Wetrock",
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/body_schema.json",
"starSystem": "SolarSystem",
"Base": {
"groundSize": 100,
"surfaceSize": 101,
"surfaceGravity": 12,
"hasMapMarker": true
},
"Orbit": {
"semiMajorAxis": 1300,
"primaryBody": "TIMBER_HEARTH",
"isMoon": true,
"isTidallyLocked": true
},
"Atmosphere": {
"size": 150,
"fogTint": {
"r": 200,
"g": 255,
"b": 255,
"a": 255
},
"fogSize": 150,
"fogDensity": 0.2,
"hasRain": true
}
}
```
Here we can see we have a planet object, which name is "Wetrock", and is in the "SolarSystem" (Base-game) star system.
It has an object called Base, which has a groundSize of 100, and a surfaceSize of 101, and the list continues on.
Alright so now that we understand how the file is structures, let's look into what each value actually does:
- `name` simply sets the name of the planet
- `$schema` we'll get to in a second
- `starSystem` specifies what star system this planet is located in, in this case we're using the base game star system, so we put "SolarSystem"
- Then it has an object called `Base`
- Base has a `groundSize` of 100, this generates a perfect sphere that is 100 units in radius as the ground of our planet
- It also has a `surfaceSize` of 101, surface size is used in many calculations, it's generally good to set it to a bit bigger than ground size.
- `surfaceGravity` describes the strength of gravity on this planet, in this case it's 12 which is the same as Timber Hearth
- `hasMapMarker` tells new horizons that we want this planet to have a marker on the map screen
- Next it has another object called `Orbit`
- `semiMajorAxis` specifies the radius of the orbit (how far away the body is from its parent)
- `primaryBody` is set to `TIMBER_HEARTH``, this makes our planet orbit timber hearth
- `isMoon` simply tells the game how close you have to be to the planet in map mode before its name appears
- `isTidallyLocked` makes sure that one side of our planet is always facing timber hearth (the primary body)
- Finally, we have `Atmosphere`
- Its `size` is 150, this simply sets how far away from the planet our atmosphere stretches
- Its `fogTint` is set to a color which is an object with r, g, b, and a properties (properties is another word for keys)
- `fogSize` determines how far away the fog stretches from the planet
- `fogDensity` is simply how dense the fog is
- `hasRain` makes rainfall on the planet
#### What's a Schema?
That `$schema` property is a bit special, it instructs VSCode to use a pre-made schema to provide a better editing experience.
With the schema you get:
- Automatic descriptions for properties when hovering over keys
- Automatic error detection for incorrect data types or values
- Autocomplete, also called IntelliSense
The schema we're using here is the [Celestial Body Schema](/schemas/body_schema), but there are many others available in the Schemas section of the left sidebar.
### Testing The Planet
With the new planet created (_and saved!_), launch the game through the mod manager and click resume expedition. If all went well you should be able to open your map and see wetrock orbiting Timber Hearth.
If you run into issues please make sure:
- You placed the JSON file in a folder called `planets` in the New Horizons mod folder
- There are no red or yellow squiggly lines in your file
## Creating An Addon
### Making a GitHub Repository
To get started, you'll need to fork the [the New Horizons addon template](https://github.com/xen-42/ow-new-horizons-config-template)
- Set the Name to your username followed by a dot (`.`), followed by your mod's name in PascalCase (no spaces, new words have capital letters). So for example if my username was "Test" and my mod's name was "Really Cool Addon", I would name the repo `Test.ReallyCoolAddon`.
- The description is what will appear in the mod manager under the mod's name, you can always edit this later
- You can set the visibility to what you want; But when you go to publish your mod, it will need to be public
### Open The Project
Now clone the repository to your local computer and open it in your favorite editor (we recommend [VSCode](https://code.visualstudio.com/)).
### Project Layout
- .github: This folder contains special files for use on GitHub, they aren't useful right now but will be when we go to publish the mod
- planets: This folder contains a single example config file that destroys the Quantum Moon, we'll keep it for now so we can test our addon later.
- .gitattributes: This is another file that will be useful when publishing
- default-config.json: This file is used in C#-based mods to allow a custom options menu, New Horizons doesn't support a custom options menu, but we still need the file here in order for the addon to work.
- manifest.json: This is the first file we're going to edit, we need to fill it out with information about our mod
- First you're going to set `author` to your author name, this should be the same name that you used when creating the GitHub repo.
- Next, set `name` to the name you want to appear in the mod manager and website.
- Now set `uniqueName` to the name of your GitHub Repo.
- You can leave `version`, `owmlVersion`, and `dependencies` alone
- NewHorizonsConfig.dll: This is the heart of your addon, make sure to never move or rename it.
### Testing The Addon
In order for the addon to show up in the manager and subsequently be loaded into the game, you'll need to ensure that it's located in `%APPDATA%\OuterWildsModManager\OWML\Mods` (or `~/.local/share/OuterWildsModManager` on Linux). How you choose to do this is up to you, you could manually copy the mod folder over every time, you could setup an automated [VSCode Launch Configuration](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations) to do it every time you press F5, or you could simply keep your mod in that folder. The last option while although the easiest is not recommended as downloading another version of your mod can overwrite your current working copy.
Once put in the Mods folder, the manager will display your mod without a description or download count, you can then launch the game to see if your planets load in.
#### Checking In-Game
Now when you click "Start Game" and load into the solar system, you should be able to notice that the quantum moon is gone entirely, this means that your addon and its configs were successfully loaded.

View File

@ -0,0 +1,47 @@
---
title: Helpful Resources
description: Helpful resources for developing New Horizons addons
---
## Reloading Configs In-Game
It can get annoying when you have to keep closing and opening the game over and over again to test changes, that's why New Horizons has a "Reload Configs" feature.
To enable it, head to your Mods menu and select New Horizons and check the box that says Debug, this will cause a "Reload Configs" option to appear in your pause menu which will reload changes from your filesystem.
You may also notice blue and yellow logs start appearing in your console, this is New Horizons providing additional info on what it's currently doing, it can be helpful when you're trying to track down an issue.
## Templates
There are two templates for New Horizons addons.
The [New Horizons Addon Template](https://github.com/xen-42/ow-new-horizons-config-template) is used for addons that **don't use custom code**,
this is ideal for simple projects and people just starting out.
The [Outer Wilds Mod Template](https://github.com/ow-mods/ow-mod-template) is used for mods that use custom code,
**you must enable "Use New Horizons" in order for it to work with New Horizons**.
This is ideal for people that want to expand on New Horizons and add custom behaviour.
## Texturing
The texturemap/heightmap feature was inspired by the Kerbal Space Program mod Kopernicus. A lot of the same techniques that apply to
planet creation there apply to New Horizons. If you need help with planetary texturing, check out [The KSP texturing guide](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/).
[Photopea](https://www.photopea.com/) is a free browser-based photo editor which has useful features like
rectangular-to-polar coordinate transformation, useful for fixing abnormalities at the poles of your planets.
## Helpful Mods
These mods are useful when developing your addon
- [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) - Used to find the paths of game objects for copying and can be used to manually position props, ship log entries, and more.
- [Collider Visualizer](https://outerwildsmods.com/mods/collidervisualizer) - Useful when creating dialogue triggers or reveal volumes.
- [Save Editor](https://outerwildsmods.com/mods/saveeditor) - Useful when creating a custom [ship log](/ship-log), can be used to reveal all custom facts so you can see them in the ship's computer.
- [Time Saver](https://outerwildsmods.com/mods/timesaver/) - Lets you skip some repeated cutscenes and get into the game faster.
## Helpful Tools
These tools/references are highly recommended
- [VSCode](https://code.visualstudio.com/)
- [VSCode XML Addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml)
- [XML Basics Tutorial](https://www.w3schools.com/xml/xml_whatis.asp)
- [JSON Basics Tutorial](https://www.tutorialspoint.com/json/index.htm)
- [The Examples Mod](https://github.com/xen-42/ow-new-horizons-examples)

View File

@ -1,57 +0,0 @@
---
title: Understanding XML
description: A guide for understanding how to write XML for New Horizons
---
XML is the other language New Horizons uses for content.
XML files are usually passed straight to the game's code instead of going through New Horizons.
## Syntax
XML is composed of tags, a tag can represent a section or attribute
```xml
<Person>
<Name>Jim</Name>
<Age>32</Age>
<IsMarried/>
</Person>
```
Notice how each tag is closed by an identical tag with a slash at the front (i.e. `<Person>` is closed by `</Person>`).
If the tag has no content you can use the self-closing tag shorthand (i.e. `<IsMarried/>` doesn't need a closing tag because of the `/` at the end).
This XML could be written in JSON as:
```json
{
"name": "Jim",
"age": 32,
"isMarried": true
}
```
XML is a lot more descriptive, you can actually tell that the object is supposed to be a person by the name of the tag.
## Structure
All XML files must have **one** top-level tag, this varies depending on what you're using it for (like how ship logs use a `<AstroObjectEntry>` tag).
## Schemas
XML files can also have schemas, you specify them by adding attributes to the top-level tag:
```xml
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Link goes here">
</Person>
```
In order to get schema validation and autofill you'll need the [Redhat XML VSCode extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml).
## Uses
XML is used for the following:
- [Ship log Entries](/ship-log)
- [Dialogue](/dialogue)

10
docs/src/env.d.ts vendored
View File

@ -1,10 +1,2 @@
/// <reference types="astro/client" />
/// <reference path="../.astro/types.d.ts" />
interface ImportMetaEnv {
readonly GITHUB_TOKEN: string | undefined;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
/// <reference types="astro/client-image" />

View File

@ -1,10 +0,0 @@
import { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES } from "./consts";
export { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES };
export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//;
export function getLanguageFromURL(pathname: string) {
const langCodeMatch = pathname.match(langPathRegex);
const langCode = langCodeMatch ? langCodeMatch[1] : "en";
return langCode as (typeof KNOWN_LANGUAGE_CODES)[number];
}

View File

@ -1,144 +0,0 @@
---
import type { MarkdownHeading } from "astro";
import type { CollectionEntry } from "astro:content";
import HeadCommon from "../components/HeadCommon.astro";
import HeadSEO from "../components/HeadSEO.astro";
import Header from "../components/Header/Header.astro";
import PageContent from "../components/PageContent/PageContent.astro";
import LeftSidebar from "../components/LeftSidebar/LeftSidebar.astro";
import RightSidebar from "../components/RightSidebar/RightSidebar.astro";
import Footer from "../components/Footer/Footer.astro";
import { GITHUB_EDIT_URL, SITE } from "../consts";
type Props = CollectionEntry<"docs">["data"] & {
headings: MarkdownHeading[];
noFileEdit?: boolean;
};
const { headings, noFileEdit, ...data } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const currentPage = Astro.url.pathname;
const currentFile = `src/content/docs${currentPage.replace(/\/$/, "")}.md`;
const githubEditUrl = `${GITHUB_EDIT_URL}/${currentFile}`;
---
<html dir={data.dir} lang={data.lang} class="initial">
<head>
<HeadCommon />
<HeadSEO {...data} canonicalUrl={canonicalURL} />
<title>
{data.title === SITE.title ? data.title : `${data.title} | ${SITE.title}`}
</title>
<style>
body {
width: 100%;
display: grid;
grid-template-rows: var(--theme-navbar-height) 1fr;
--gutter: 0.5rem;
--doc-padding: 2rem;
}
.layout {
display: grid;
grid-auto-flow: column;
grid-template-columns: minmax(var(--gutter), 1fr) minmax(0, var(--max-width)) minmax(
var(--gutter),
1fr
);
overflow-x: hidden;
}
.grid-sidebar {
height: 100vh;
position: sticky;
top: 0;
padding: 0;
}
#grid-left {
position: fixed;
background-color: var(--theme-bg);
z-index: 10;
display: none;
}
#grid-main {
padding: var(--doc-padding) var(--gutter);
grid-column: 2;
display: flex;
flex-direction: column;
height: 100%;
}
#grid-right {
display: none;
}
@media (min-width: 50em) {
.layout {
overflow: initial;
grid-template-columns: 20rem minmax(0, var(--max-width));
gap: 1em;
}
#grid-left {
display: flex;
padding-left: 2rem;
position: sticky;
grid-column: 1;
}
}
@media (min-width: 72em) {
.layout {
grid-template-columns: 20rem minmax(0, var(--max-width)) 18rem;
padding-left: 0;
padding-right: 0;
margin: 0 auto;
}
#grid-right {
grid-column: 3;
display: flex;
}
}
</style>
<style is:global>
.layout > * {
width: 100%;
height: 100%;
}
.mobile-sidebar-toggle {
overflow: hidden;
}
.mobile-sidebar-toggle #grid-left {
display: block;
top: 2rem;
}
</style>
</head>
<body>
<Header currentPage={currentPage} />
<main class="layout">
<aside id="grid-left" class="grid-sidebar" title="Site Navigation">
<LeftSidebar currentPage={currentPage} />
</aside>
<div id="grid-main">
<PageContent title={data.title} headings={headings} githubEditUrl={githubEditUrl}>
<slot />
</PageContent>
</div>
<aside id="grid-right" class="grid-sidebar" title="Table of Contents">
<RightSidebar
headings={headings}
githubEditUrl={githubEditUrl}
noFileEdit={noFileEdit ?? false}
/>
</aside>
</main>
<Footer />
</body>
</html>

View File

@ -1,13 +0,0 @@
---
import MainLayout from "../layouts/MainLayout.astro";
---
<MainLayout
headings={[]}
title="Page Not Found"
description="Page Not Found"
dir="ltr"
lang="en-us"
>
The page you requested could not be found.
</MainLayout>

View File

@ -1,22 +0,0 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import MainLayout from "../layouts/MainLayout.astro";
export async function getStaticPaths() {
const docs = await getCollection("docs");
return docs.map((entry) => ({
params: {
slug: entry.slug
},
props: entry
}));
}
type Props = CollectionEntry<"docs">;
const post = Astro.props;
const { Content, headings } = await post.render();
---
<MainLayout headings={headings} {...post.data}>
<Content />
</MainLayout>

View File

@ -1,16 +0,0 @@
---
// See home.mdx for actual content, this just styles and renders it.
import { Content as HomeContent, getHeadings } from "../components/home.mdx";
import MainLayout from "../layouts/MainLayout.astro";
---
<MainLayout
lang="en-us"
headings={getHeadings()}
title="New Horizons"
dir="ltr"
description="Documentation on how to use the New Horizons planet creation tool for Outer Wilds."
>
<HomeContent />
</MainLayout>

View File

@ -1,51 +0,0 @@
---
import { readdir } from "fs/promises";
import Content from "../../../../components/Schemas/Content.astro";
import MainLayout from "../../../../layouts/MainLayout.astro";
import { Schema, SchemaTools } from "../../../../schema_utils";
import Breadcrumb from "../../../../components/Schemas/Breadcrumb.astro";
export interface Props {
def: Schema;
}
export async function getStaticPaths() {
const schemaFiles = await readdir("../NewHorizons/Schemas");
const schemas = schemaFiles.map((f) => SchemaTools.readSchema(f));
let allDefs: Schema[] = [];
for (const schema of schemas) {
allDefs = allDefs.concat(SchemaTools.getDefs(schema));
}
return allDefs.map((def) => {
return {
params: {
schema: def.rootSlug,
def: def.slug
},
props: {
def
}
};
});
}
const { def } = Astro.props;
const title = SchemaTools.getTitle(def)?.toString();
const headings = SchemaTools.getHeaders(def);
---
<MainLayout
noFileEdit
lang="en-us"
dir="ltr"
headings={headings}
title={title ?? "??"}
description={`New Horizons ${def.rootTitle} Definition For ${title}`}
>
<Breadcrumb schema={def} />
<Content level={0} schema={def} isDef />
</MainLayout>

View File

@ -1,50 +0,0 @@
---
import { readdir } from "node:fs/promises";
import { SchemaTools, type Schema } from "../../../../schema_utils";
import Breadcrumb from "../../../../components/Schemas/Breadcrumb.astro";
import MainLayout from "../../../../layouts/MainLayout.astro";
export interface Props {
schema: Schema;
}
export async function getStaticPaths() {
const schemaFiles = await readdir("../NewHorizons/Schemas");
return schemaFiles.map((f) => {
const schema = SchemaTools.readSchema(f);
return {
params: {
schema: schema.slug
},
props: {
schema
}
};
});
}
const { schema } = Astro.props;
const title = `${SchemaTools.getTitle(schema) ?? "??"} Definitions`;
const defs = SchemaTools.getDefs(schema).map((d) => d.slug);
---
<MainLayout
noFileEdit
lang="en-us"
dir="ltr"
headings={[]}
title={title}
description={`New Horizons ${title}`}
>
<Breadcrumb schema={schema} onlyDefs />
<ul>
{
defs.map((d) => (
<li>
<a href={`/schemas/${schema.slug}/defs/${d}`}>{d}</a>
</li>
))
}
</ul>
</MainLayout>

View File

@ -1,40 +0,0 @@
---
import { readdir } from "node:fs/promises";
import { Schema, SchemaTools } from "../../../schema_utils";
import Content from "../../../components/Schemas/Content.astro";
import MainLayout from "../../../layouts/MainLayout.astro";
export interface Props {
schema: Schema;
}
export async function getStaticPaths() {
const schemaFiles = await readdir("../NewHorizons/Schemas");
return schemaFiles.map((f) => {
const schema = SchemaTools.readSchema(f);
return {
params: {
schema: schema.slug
},
props: {
schema
}
};
});
}
const { schema } = Astro.props;
const title = SchemaTools.getTitle(schema)?.toString();
const headings = SchemaTools.getHeaders(schema);
---
<MainLayout
noFileEdit
lang="en-us"
dir="ltr"
headings={headings}
title={title ?? "??"}
description={`New Horizons ${title} Reference`}
>
<Content level={0} schema={schema} />
</MainLayout>

View File

@ -0,0 +1,35 @@
import { Schema, SchemaTools } from "./schema_utils";
import * as fs from "node:fs";
const addFrontmatter = (content: string, frontmatter: Record<string, boolean | string>) => {
const entries = Object.entries(frontmatter).map(([key, value]) => `${key}: ${value}`)
if (entries.length === 0) {
return content
}
return `---\n${entries.join('\n')}\n---\n\n${content}`
}
export const generateSchema = (fileName: string) => {
const schema = SchemaTools.readSchema(fileName);
const dir = `src/content/docs/schemas/${schema.slug}`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const frontMatter = {
title: SchemaTools.getTitle(schema) as string,
description: SchemaTools.getDescription(schema) as string,
editUrl: false
};
const content = `import Schema from "/src/components/Schemas/Schema.astro";\n\n<Schema fileName="${schema.fileName}" />\n`
fs.writeFileSync(`${dir}/index.mdx`, addFrontmatter(content, frontMatter))
};

View File

@ -0,0 +1,3 @@
:root {
--sl-hue-accent: 133;
}

View File

@ -1,412 +0,0 @@
* {
box-sizing: border-box;
margin: 0;
}
/* Global focus outline reset */
*:focus:not(:focus-visible) {
outline: none;
}
:root {
--user-font-scale: 1rem - 16px;
--max-width: calc(100% - 1rem);
}
@media (min-width: 50em) {
:root {
--max-width: 46em;
}
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
font-family: var(--font-body);
font-size: 1rem;
font-size: clamp(0.9rem, 0.75rem + 0.375vw + var(--user-font-scale), 1rem);
line-height: 1.5;
max-width: 100vw;
}
nav ul {
list-style: none;
padding: 0;
}
.content > section > * + * {
margin-top: 1.25rem;
}
.content > section > :first-child {
margin-top: 0;
}
/* Typography */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 1rem;
font-weight: bold;
line-height: 1;
}
h1,
h2 {
max-width: 40ch;
}
:is(h2, h3):not(:first-child) {
margin-top: 3rem;
}
:is(h4, h5, h6):not(:first-child) {
margin-top: 2rem;
}
h1 {
font-size: 3.25rem;
font-weight: 800;
}
h2 {
font-size: 2.5rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1.3rem;
}
h5 {
font-size: 1rem;
}
p {
line-height: 1.65em;
}
.content ul {
line-height: 1.1em;
}
p,
.content ul {
color: var(--theme-text-light);
}
small,
.text_small {
font-size: 0.833rem;
}
a {
color: var(--theme-text-accent);
font-weight: 400;
text-underline-offset: 0.08em;
align-items: center;
gap: 0.5rem;
}
article > section :is(ul, ol) > * + * {
margin-top: 0.75rem;
}
article > section nav :is(ul, ol) > * + * {
margin-top: inherit;
}
article > section li > :is(p, pre, blockquote):not(:first-child) {
margin-top: 1rem;
}
article > section :is(ul, ol) {
padding-left: 1em;
}
article > section nav :is(ul, ol) {
padding-left: inherit;
}
article > section nav {
margin-top: 1rem;
margin-bottom: 2rem;
}
article > section ::marker {
font-weight: bold;
color: var(--theme-text-light);
}
article > section iframe {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
}
a > code {
position: relative;
color: var(--theme-text-accent);
background: transparent;
text-underline-offset: var(--padding-block);
}
a > code::before {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
background: var(--theme-accent);
opacity: var(--theme-accent-opacity);
border-radius: var(--border-radius);
}
a:hover,
a:focus {
text-decoration: underline;
}
a:focus {
outline: 2px solid currentColor;
outline-offset: 0.25em;
}
strong {
font-weight: 600;
color: inherit;
}
/* Supporting Content */
code {
--border-radius: 3px;
--padding-block: 0.2rem;
--padding-inline: 0.33rem;
font-family: var(--font-mono);
font-size: 0.85em;
color: inherit;
background-color: var(--theme-code-inline-bg);
padding: var(--padding-block) var(--padding-inline);
margin: calc(var(--padding-block) * -1) -0.125em;
border-radius: var(--border-radius);
word-break: break-word;
}
pre.astro-code > code {
all: unset;
}
pre > code {
font-size: 1em;
}
table,
pre {
position: relative;
--padding-block: 1rem;
--padding-inline: 2rem;
padding: var(--padding-block) var(--padding-inline);
padding-right: calc(var(--padding-inline) * 2);
margin-left: calc(var(--padding-inline) * -1);
margin-right: calc(var(--padding-inline) * -1);
font-family: var(--font-mono);
line-height: 1.5;
font-size: 0.85em;
overflow-y: hidden;
overflow-x: auto;
}
table {
width: 100%;
padding: var(--padding-block) 0;
margin: 0;
border-collapse: collapse;
}
/* Zebra striping */
tr:nth-of-type(even) {
background: var(--theme-code-inline-bg);
}
th {
background: var(--theme-bg-accent);
color: var(--theme-color);
font-weight: bold;
}
td,
th {
padding: 6px;
text-align: left;
}
pre {
background-color: var(--theme-code-bg);
color: var(--theme-code-text);
}
blockquote code {
background-color: var(--theme-bg);
}
@media (min-width: 37.75em) {
pre {
--padding-inline: 1.25rem;
border-radius: 8px;
margin-left: 0;
margin-right: 0;
}
}
blockquote {
margin: 2rem 0;
padding: 1.25em 1.5rem;
border-left: 3px solid var(--theme-text-light);
background-color: var(--theme-bg-offset);
border-radius: 0 0.25rem 0.25rem 0;
line-height: 1.7;
}
img {
max-width: 100%;
}
.flex {
display: flex;
align-items: center;
}
button {
display: flex;
align-items: center;
justify-items: center;
gap: 0.25em;
padding: 0.33em 0.67em;
border: 0;
background: var(--theme-bg);
display: flex;
font-size: 1rem;
align-items: center;
gap: 0.25em;
border-radius: 99em;
color: var(--theme-text);
background-color: var(--theme-bg);
}
h2.heading {
font-size: 1rem;
font-weight: 700;
padding: 0.1rem 1rem;
text-transform: uppercase;
margin-bottom: 0.5rem;
}
.header-link {
font-size: 1em;
transition: border-inline-start-color 100ms ease-out, background-color 200ms ease-out;
border-left: 4px solid var(--theme-divider);
}
.header-link a {
display: inline-flex;
gap: 0.5em;
width: 100%;
font: inherit;
padding: 0.6rem 0;
line-height: 1.3;
color: inherit;
text-decoration: none;
unicode-bidi: plaintext;
}
@media (min-width: 50em) {
.header-link a {
padding: 0.275rem 0;
}
}
.header-link:hover,
.header-link:focus,
.header-link:focus-within {
border-inline-start-color: var(--theme-accent-secondary);
}
.header-link:hover a,
.header-link a:focus {
color: var(--theme-text);
text-decoration: underline;
}
.header-link svg {
opacity: 0.6;
}
.header-link:hover svg {
opacity: 0.8;
}
/* Add line and padding on the left side */
.header-link {
padding-inline-start: 1rem;
}
.header-link.depth-3 {
padding-inline-start: 2rem;
}
.header-link.depth-4 {
padding-inline-start: 3rem;
}
/* Screenreader Only Text */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.focus\:not-sr-only:focus,
.focus\:not-sr-only:focus-visible {
position: static;
width: auto;
height: auto;
padding: 0;
margin: 0;
overflow: visible;
clip: auto;
white-space: normal;
}
:target {
scroll-margin: calc(var(--theme-sidebar-offset, 5rem) + 2rem) 0 2rem;
}
/* Highlight TOC header link matching the current scroll position */
.current-header-link {
background-color: var(--theme-bg-accent);
border-inline-start-color: var(--theme-accent);
/* Indicates the current heading for forced colors users in older browsers */
outline: 1px solid transparent;
}
@media (forced-colors: active) {
.current-header-link {
border: 1px solid CanvasText;
}
}
.current-header-link a {
color: var(--theme-text);
}

View File

@ -1,102 +0,0 @@
:root {
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
--font-body: system-ui, var(--font-fallback);
--font-mono: "IBM Plex Mono", Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console",
"Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono",
"Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
/*
* Variables with --color-base prefix define
* the hue, and saturation values to be used for
* hsla colors.
*
* ex:
*
* --color-base-{color}: {hue}, {saturation};
*
*/
--color-base-white: 0, 0%;
--color-base-black: 240, 100%;
--color-base-gray: 150, 14%;
--color-base-blue: 212, 100%;
--color-base-blue-dark: 212, 72%;
--color-base-green: 158, 79%;
--color-base-orange: 22, 100%;
--color-base-purple: 269, 79%;
--color-base-red: 351, 100%;
--color-base-yellow: 41, 100%;
/*
* Color palettes are made using --color-base
* variables, along with a lightness value to
* define different variants.
*
*/
--color-gray-5: var(--color-base-gray), 5%;
--color-gray-10: var(--color-base-gray), 10%;
--color-gray-20: var(--color-base-gray), 20%;
--color-gray-30: var(--color-base-gray), 30%;
--color-gray-40: var(--color-base-gray), 40%;
--color-gray-50: var(--color-base-gray), 50%;
--color-gray-60: var(--color-base-gray), 60%;
--color-gray-70: var(--color-base-gray), 70%;
--color-gray-80: var(--color-base-gray), 80%;
--color-gray-90: var(--color-base-gray), 90%;
--color-gray-95: var(--color-base-gray), 95%;
--color-blue: var(--color-base-blue), 61%;
--color-blue-dark: var(--color-base-blue-dark), 39%;
--color-green: var(--color-base-green), 42%;
--color-orange: var(--color-base-orange), 50%;
--color-purple: var(--color-base-purple), 54%;
--color-red: var(--color-base-red), 54%;
--color-yellow: var(--color-base-yellow), 59%;
}
body {
background: var(--theme-bg);
color: var(--theme-text);
}
:root {
color-scheme: dark;
--theme-accent-opacity: 0.15;
--theme-accent: hsla(var(--color-green), 1);
--theme-text-accent: hsla(var(--color-green), 1);
--theme-divider: hsla(var(--color-gray-10), 1);
--theme-text: hsla(var(--color-gray-90), 1);
--theme-text-light: hsla(var(--color-gray-80), 1);
/* @@@: not used anywhere */
--theme-text-lighter: hsla(var(--color-gray-40), 1);
--theme-bg: #222222;
--theme-bg-hover: hsla(var(--color-gray-40), 1);
--theme-bg-offset: hsla(var(--color-gray-5), 1);
--theme-bg-accent: hsla(var(--color-green), var(--theme-accent-opacity));
--theme-code-inline-bg: hsla(var(--color-gray-10), 1);
--theme-code-inline-text: hsla(var(--color-base-white), 100%, 1);
--theme-code-bg: hsla(var(--color-gray-5), 1);
--theme-code-text: hsla(var(--color-base-white), 100%, 1);
--theme-navbar-bg: #303030;
--theme-selection-color: hsla(var(--color-base-white), 100%, 1);
--theme-selection-bg: hsla(var(--color-purple), var(--theme-accent-opacity));
/* DocSearch [Algolia] */
--docsearch-modal-background: var(--theme-bg);
--docsearch-searchbox-focus-background: var(--theme-divider);
--docsearch-footer-background: var(--theme-divider);
--docsearch-text-color: var(--theme-text);
--docsearch-hit-background: var(--theme-divider);
--docsearch-hit-shadow: none;
--docsearch-hit-color: var(--theme-text);
--docsearch-footer-shadow: inset 0 2px 10px #000;
--docsearch-modal-shadow: inset 0 0 8px #000;
}
::selection {
color: var(--theme-selection-color);
background-color: var(--theme-selection-bg);
}

View File

@ -1,8 +1,3 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "preserve",
"skipLibCheck": true,
"strictNullChecks": true
}
}
"extends": "astro/tsconfigs/strict"
}