Added XML Schemas To Docs (#57)

* Add Bootstrap Extension

* Rename main.yml

* Artifact Upload

* Fix Bootstrap Reference Error

* BootstrapTreeProcessor

* getiterator removed

* keys function

* Style Images

* Update docs_build.yml

* Added Meta Files

* Template Get

* Fix Page Ref

* Update BASE_URL

* Sort Schemas

* Add Sitemaps

* Add favicons, open-graph, and setup guide

* Update Setup.md

* Update .gitignore

* Update Setup.md

* Use _blank on external links

* Restructured Docs

* Fix Links

* Added XML Schemas

* Name XML Schemas
This commit is contained in:
Ben C 2022-03-04 23:27:26 -08:00 committed by GitHub
parent a78bde530e
commit 2552d1b8a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 682 additions and 276 deletions

View File

@ -42,6 +42,8 @@ jobs:
cp NewHorizons/schema.json content/schemas/
cp NewHorizons/star_system_schema.json content/schemas/
cp NewHorizons/translation_schema.json content/schemas/
cp NewHorizons/shiplog_schema.xsd content/schemas/
cp NewHorizons/dialogue_schema.xsd content/schemas/
- name: Create Output Dir
run: |

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Dialogue Tree -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Dialogue Tree Info -->
<xs:element name="DialogueTree">

View File

@ -62,7 +62,8 @@
"$ref": "#/$defs/colorPart"
},
"A": {
"$ref": "#/$defs/colorPart"
"$ref": "#/$defs/colorPart",
"default": 255
}
}
},
@ -112,8 +113,7 @@
"properties": {
"name": {
"type": "string",
"description": "Unique name of your planet",
"default": "New planet"
"description": "Unique name of your planet"
},
"starSystem": {
"type": "string",

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Astro Object Entry -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Astro Object Entry Info -->
<xs:element name="AstroObjectEntry">

View File

@ -8,6 +8,7 @@ jinja2 = "*"
json-schema-for-humans = "*"
markdown = "*"
htmlmin = "*"
xmlschema = "*"
[dev-packages]

22
docs/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "32e1cfede01ba2b312ef0b550d6d9b15ce2a2a89db4c1a961143f2a543c7cd0f"
"sha256": "c708dfd89a9b02892b9323b222b2a077a8aee1852c02a2324d85dd29a06d45ba"
},
"pipfile-spec": 6,
"requires": {
@ -55,6 +55,14 @@
"markers": "python_version >= '3.6'",
"version": "==0.5.6"
},
"elementpath": {
"hashes": [
"sha256:2a432775e37a19e4362443078130a7dbfc457d7d093cd421c03958d9034cc08b",
"sha256:3a27aaf3399929fccda013899cb76d3ff111734abf4281e5f9d3721ba0b9ffa3"
],
"markers": "python_version >= '3.7'",
"version": "==2.5.0"
},
"htmlmin": {
"hashes": [
"sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"
@ -99,7 +107,7 @@
"sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0",
"sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e"
],
"markers": "python_version >= '3.5' and python_full_version < '4.0.0'",
"markers": "python_version >= '3.5' and python_version < '4'",
"version": "==2.4.2"
},
"markupsafe": {
@ -253,8 +261,16 @@
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
"sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.8"
},
"xmlschema": {
"hashes": [
"sha256:3ce6fe408a8c0a0ca5917cbe6181a933dfb5cfade9714eeb07b6335f9aff7b10",
"sha256:a7ba52b774a87b59c6428cd9e3601210cbb226552208015bd40800698a6500ad"
],
"index": "pypi",
"version": "==1.9.2"
}
},
"develop": {}

View File

@ -1,17 +1,9 @@
{% if dumb %}
{% from 'macros.jinja2' import external_link, is_active_page, nav_item with context %}
{% else %}
{% from 'base/macros.jinja2' import external_link, is_active_page, nav_item with context %}
{% endif %}
{% from 'base/macros.jinja2' import external_link, is_active_page, nav_item with context %}
<!DOCTYPE html>
<html lang="en">
<head>
{% if dumb %}
{% include "meta.jinja2" %}
{% else %}
{% include "base/meta.jinja2" %}
{% endif %}
<link href="https://bootswatch.com/5/darkly/bootstrap.min.css"
crossorigin="anonymous" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
@ -21,7 +13,7 @@
<script crossorigin="anonymous" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
{% block resources %} {% endblock %}
</head>
<body>
<body onload="{% block onLoad %}{% endblock %}">
<header>
<nav class="navbar navbar-expand-xl navbar-dark bg-dark mb-2">
<div class="container-fluid">

View File

@ -1,3 +0,0 @@
{%- macro restriction(inner_text, css_class_name, html_id) -%}
<p><span class="badge bg-dark restriction {{ css_class_name }}-restriction" id="{{ html_id }}">{{ inner_text }}</span></p>
{%- endmacro -%}

View File

@ -3,3 +3,5 @@
{% macro is_active_page(title) %}{% if title == page.title %}active{% endif %}{% endmacro %}
{% macro nav_item(title, href) %}<li class="nav-item me-1"><a href="{{ href }}" class="nav-link {% if title|lower == page.title|lower %}active{% endif %}" {% if title|lower == page.title|lower %}aria-current="page"{% endif %}>{{ title }}</a></li>{% endmacro %}
{% macro badge(type, content, classes) %}<span class="badge rounded-pill bg-{{ type }} {{ classes }}">{{ content|safe }}</span>{% endmacro %}

View File

@ -10,7 +10,7 @@
{% block content %}
<div class="row">
<div class="col">
{{ content|markdown }}
{{ rendered|safe }}
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,8 @@
{% from "macro_restriction.jinja2" import restriction with context %}
{% from "base/schema/json/macro_restriction.jinja2" import restriction with context %}
{% from "base/macros.jinja2" import badge %}
{% macro tabbed_section(operator, current_node) %}
{% include "tabbed_section.jinja2" %}
{% include "base/schema/json/tabbed_section.jinja2" %}
{% endmacro %}
{% macro content(schema, skip_headers=False) %}
@ -12,14 +13,14 @@
{% if not skip_headers %}
{% if config.show_breadcrumbs %}
{% include "breadcrumbs.jinja2" %}
{% include "base/schema/json/breadcrumbs.jinja2" %}
{% endif %}
{# Display type #}
{% if not schema is combining %}
<div class="row">
<div class="col">
<span class="badge bg-secondary">Type: {{ type_name }}</span>
{{ badge("secondary", "Type: " + type_name) }}
</div>
</div>
{% endif %}
@ -29,14 +30,14 @@
{% if default_value %}
<div class="row">
<div class="col">
{{ " " }}<span class="badge bg-secondary">Default: {{ default_value }}</span>
{{ badge("secondary", "Default: " + default_value) }}
</div>
</div>
{% endif %}
<br/>
{% set description = (schema | get_description) %}
{% include "section_description.jinja2" %}
{% include "base/schema/json/section_description.jinja2" %}
{% endif %}
@ -52,7 +53,7 @@
{{ content(schema.kw_any_of.array_items[0]) }}
{% else %}
{% if schema.explicit_no_additional_properties %}
{{ " " }}<span class="badge bg-info no-additional">No Additional Properties</span>
{{ badge("info", "No Additional Properties") }}
{% endif %}
{# Combining: allOf, anyOf, oneOf, not #}
@ -66,7 +67,7 @@
<div class="one-of-value" id="{{ schema.kw_one_of.html_id }}">{{ tabbed_section("oneOf", schema.kw_one_of) }}</div>
{% endif %}
{% if schema.kw_not %}
{% include "section_not.jinja2" %}
{% include "base/schema/json/section_not.jinja2" %}
{% endif %}
{# Enum and const #}
@ -91,29 +92,29 @@
{# Conditional subschema, or if-then-else section #}
{% if schema.has_conditional %}
{% include "section_conditional_subschema.jinja2" %}
{% include "base/schema/json/section_conditional_subschema.jinja2" %}
{% endif %}
{# Required properties that are not defined under "properties". They will only be listed #}
{% include "section_undocumented_required_properties.jinja2" %}
{% include "base/schema/json/section_undocumented_required_properties.jinja2" %}
{# Show the requested type(s) #}
{% include "badge_type.jinja2" %}
{% include "base/schema/json/badge_type.jinja2" %}
{# Show array restrictions #}
{% if type_name.startswith("array") %}
{% include "section_array.jinja2" %}
{% include "base/schema/json/section_array.jinja2" %}
{% endif %}
{# Display examples #}
{% set examples = schema.examples %}
{% if examples %}
{% include "section_examples.jinja2" %}
{% include "base/schema/json/section_examples.jinja2" %}
{% endif %}
{# Properties, pattern properties, additional properties #}
{% for sub_property in schema.iterate_properties %}
{% include "section_properties.jinja2" %}
{% include "base/schema/json/section_properties.jinja2" %}
{% endfor %}
{% endif %}

View File

@ -0,0 +1,5 @@
{% from "base/macros.jinja2" import badge %}
{% macro restriction(inner_text, css_class_name, html_id) %}
<p>{{ badge("dark", inner_text|safe, 'p-1') }}</p>
{% endmacro %}

View File

@ -1,10 +1,12 @@
{% extends "base.jinja2" %}
{% from 'content.jinja2' import content with context %}
{% extends "base/base.jinja2" %}
{% from 'base/schema/json/content.jinja2' import content with context %}
{% block resources %}
<script src="/schemas/schema_doc.min.js"></script>
{% include "base/schema/schema_includes.jinja2" %}
{% endblock %}
{% block onLoad %}anchorOnLoad();{% endblock %}
{% block content %}
<div class="row">
<div class="col text-center">

View File

@ -1,3 +1,4 @@
{% from "base/macros.jinja2" import badge %}
{% set html_id = sub_property.html_id %}
<div class="row my-2">
<div class="col">
@ -17,14 +18,15 @@
{% if sub_property.is_additional_properties %}
</em>
{% endif %}
{% macro subprop_badge(type, content) %}{{ badge(type, content|title, "required-property, ms-2") }}{% endmacro %}
{% if sub_property.is_required_property %}
{{ " " }}<span class="badge bg-warning required-property ms-2">Required</span>
{{ " " }}{{ subprop_badge('warning', "Required") }}
{% endif %}
{% if sub_property is deprecated %}
{{ " " }}<span class="badge bg-danger deprecated-property ms-2">Deprecated</span>
{{ " " }}{{ subprop_badge('danger', "Deprecated") }}
{% endif %}
{% if sub_property.is_pattern_property %}
{{ " " }}<span class="badge bg-info pattern-property ms-2">Pattern Property</span>
{{ " " }}{{ subprop_badge('info', "Pattern Property") }}
{% endif %}
</button>
</h2>

View File

@ -0,0 +1,2 @@
<script src="{{ 'scripts/schema.min.js'|static }}"></script>
<link rel="stylesheet" href="{{ 'styles/schema.css'|static }}"/>

View File

@ -0,0 +1,85 @@
{% extends "base/base.jinja2" %}
{% from "base/macros.jinja2" import badge %}
{% macro body(element, trail) %}
<div class="breadcrumbs">
{% for node in trail|split("-") %}
{% if loop.first %}
Root
{% else %}
<a href="#{{ node }}"
onclick="anchorLink('{{ node }}')">{{ node }}</a>
{% endif %}
{% if not loop.last %}
<i class="bi-arrow-right-short"></i>
{% endif %}
{% endfor %}
</div>
{% if element.occurs[0] != 1 or element.occurs[1] != 1 %}
{{ badge("secondary", element.occurs|occurs_text) }}
{% endif %}
{{ badge("secondary", "Type: " + element.type|name, "ms-2") }}
{% if element.type.has_complex_content() %}
{% for child in element.type.content|children %}
{{ render(child, trail) }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro render(element, trail) %}
<div class="row my-2">
<div class="col">
{% set trail=trail+"-"+element.name %}
{% set html_id=trail %}
<div class="accordion" id="accordion_{{ html_id }}">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_{{ html_id }}">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#{{ html_id }}" aria-expanded="false"
aria-controls="{{ html_id }}" onclick="setAnchor('#{{ html_id }}')">
<span class="property-name align-middle pb-1">{{ element.name | escape }}</span>
{% if element.occurs[0] == 1 and element.occurs[1] == 1 %}
{{ " " }}{{ badge("warning", "Required", "ms-2") }}
{% endif %}
</button>
</h2>
<div id="{{ trail }}" class="accordion-collapse collapse"
aria-labelledby="heading_{{ html_id }}"
data-parent="#accordion_{{ html_id }}">
<div class="accordion-body">
{% if element.parent != None and element.type.name == element.parent.parent.name %}
{{ badge("info", "<em>Recursive Reference</em>") }}
{% else %}
{{ body(element, trail) }}
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endmacro %}
{% block resources %}
{% include "base/schema/schema_includes.jinja2" %}
{% endblock %}
{% block onLoad %}anchorOnLoad();{% endblock %}
{% block content %}
<div class="row">
<div class="col text-center">
<h1>{{ page.title|title }}</h1>
</div>
</div>
<div class="row">
<div class="col">
{% for element in schema.elements.values() %}
{% for child in element.type.content %}
{{ render(child, element.name) }}
{% endfor %}
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -3,20 +3,20 @@ Sort-Priority: 90
## Details/Scatterer
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:
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){ target="_blank" } 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
- Do not use the search functionality on Scene Explorer, it is really really slow. Use the "Object Search" tab instead.
- Do not use the search functionality on Scene Explorer, it is really, really slow. Use the "Object Search" tab instead.
- Generally you can find planets by writing their name with no spaces/punctuation followed by "_Body".
## Asset Bundles
Here is a template project: [Outer Wilds Unity Template](https://github.com/xen-42/outer-wilds-unity-template)
Here is a template project: [Outer Wilds Unity Template](https://github.com/xen-42/outer-wilds-unity-template){ target="_blank" }
The template project contains ripped versions of all the game scripts, meaning you can put things like DirectionalForceVolumes in your Unity project to have artificial gravity volumes loaded right into the game.
If for whatever reason you want to set up a Unity project manually instead of using the template, follow these instructions:
1. Start up a Unity 2017 project (I use Unity 2017.4.40f1 (64-bit), so if you use something else I can't guarantee it will work). The DLC updated Outer Wilds to 2019.4.27 so that probably works but I personally haven't tried it.
1. Start up a Unity 2017 project (I use Unity 2017.4.40f1 (64-bit), so if you use something else I can't guarantee it will work). The DLC updated Outer Wilds to 2019.4.27 so that probably works, but I personally haven't tried it.
2. In the "Assets" folder in Unity, create a new folder called "Editor". In it create a file called "CreateAssetBundle.cs" with the following code in it:
```cs
@ -40,8 +40,8 @@ public class CreateAssetBundles
```
3. Create your object in the Unity scene and save it as a prefab.
4. Add all files used (models, prefabs, textures, materials, etc) to an asset bundle by selecting them and using the drop down in the bottom right. Here I am adding a rover model to my "rss" asset bundle for the Real Solar System add-on.
4. Add all files used (models, prefabs, textures, materials, etc.) to an asset bundle by selecting them and using the dropdown in the bottom right. Here I am adding a rover model to my "rss" asset bundle for the Real Solar System add-on.
![setting asset bundle]({{ 'images/detailing/asset_bundle.png'|static }})
5. In the top left click the "Assets" drop-down and select "Build AssetBundles". This should create your asset bundle in a folder in the root directory called "StreamingAssets".
6. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double check everything is included, open the .manifest file in a text editor to see the files included and their paths.
6. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double-check everything is included, open the .manifest file in a text editor to see the files included and their paths.

View File

@ -6,76 +6,90 @@ Sort-Priority: 200
# Welcome!
This is the official documentation for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons)
This is the official documentation for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons){ target="_blank" }
## Getting Started with Planet Creation
There is a template [here](https://github.com/xen-42/ow-new-horizons-config-template) if you want to release your own planet mod using configs. You can learn how the configs work by picking apart the [Real Solar System](https://github.com/xen-42/outer-wilds-real-solar-system) mod or the [New Horizons Examples](https://github.com/xen-42/ow-new-horizons-examples) mod.
There is a template [here](https://github.com/xen-42/ow-new-horizons-config-template){ target="_blank" } if you want to release your own
planet mod using configs. You can learn how the configs work by picking apart
the [Real Solar System](https://github.com/xen-42/outer-wilds-real-solar-system){ target="_blank" } mod or
the [New Horizons Examples](https://github.com/xen-42/ow-new-horizons-examples){ target="_blank" } mod.
Planets are created using a JSON file format structure, and placed in a folder called planets (or in any subdirectory of it) in the location where New Horizons is installed (by default this folder doesn't exist, you have to create it within the xen.NewHorizons directory).
Planets are created using a JSON file format structure, and placed in a folder called planets (or in any subdirectory of
it) in the location where New Horizons is installed (by default this folder doesn't exist, you have to create it within
the xen.NewHorizons directory).
To locate this directory, click the "⋮" symbol next to "New Horizons" in the Outer Wilds Mod Manager and then click "show in explorer" in the pop-up.
To locate this directory, click the "⋮" symbol next to "New Horizons" in the Outer Wilds Mod Manager and then click "
show in explorer" in the pop-up.
![Click the three dots in the mod manager]({{ 'images/home/mod_manager_dots.png'|static }})
![Create a new folder named "planets"]({{ 'images/home/create_planets.png'|static }})
Now that you have created your planets folder, this is where you will put your planet config files. A config file will look something like this:
Now that you have created your planets folder, this is where you will put your planet config files. A config file will
look something like this:
```json
{
"name" : "Wetrock",
"name": "Wetrock",
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/master/NewHorizons/schema.json",
"starSystem" : "SolarSystem",
"Base" :
{
"groundSize" : 100,
"surfaceSize" : 101,
"surfaceGravity" : 12,
"hasMapMarker" : true
"starSystem": "SolarSystem",
"Base": {
"groundSize": 100,
"surfaceSize": 101,
"surfaceGravity": 12,
"hasMapMarker": true
},
"Orbit" :
{
"semiMajorAxis" : 1300,
"inclination" : 0,
"primaryBody" : "TIMBER_HEARTH",
"isMoon" : true,
"isTidallyLocked" : true,
"longitudeOfAscendingNode" : 0,
"eccentricity" : 0,
"Orbit": {
"semiMajorAxis": 1300,
"inclination": 0,
"primaryBody": "TIMBER_HEARTH",
"isMoon": true,
"isTidallyLocked": true,
"longitudeOfAscendingNode": 0,
"eccentricity": 0,
"argumentOfPeriapsis": 0
},
"Atmosphere" :
{
"size" : 150,
"fogTint" :
{
"r" : 200,
"g" : 255,
"b" : 255,
"a" : 255
"Atmosphere": {
"size": 150,
"fogTint": {
"r": 200,
"g": 255,
"b": 255,
"a": 255
},
"fogSize": 150,
"fogDensity":0.2,
"hasRain" : true
"fogDensity": 0.2,
"hasRain": true
},
"Props" :
"Props": {
"scatter": [
{
"scatter" : [
{"path" : "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var", "count" : 12}
"path": "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var",
"count": 12
}
]
}
}
```
The first field you should have in any config file is the `name`. This should be unique in the solar system. If it isn't, the mod will instead try to modify the planet that already has that name.
The first field you should have in any config file is the `name`. This should be unique in the solar system. If it
isn't, the mod will instead try to modify the planet that already has that name.
After `name` is `starSystem`. You can use this to place the planet in a different system accessible using a black-hole (see the [Singularity](#singularity) module). To ensure compatibility with other mods this name should be unique. After setting a value for this, the changes in the config will only affect that body in that star system. By default, it is "SolarSystem", which is the scene from the stock game.
After `name` is `starSystem`. You can use this to place the planet in a different system accessible using a black-hole (
see the [Singularity]({{ 'body'|route }}#Singularity) module). To ensure compatibility with other mods this name should be unique. After
setting a value for this, the changes in the config will only affect that body in that star system. By default, it is "
SolarSystem", which is the scene from the stock game.
Including the "$schema" line is optional, but will allow your text editor to highlight errors and auto-suggest words in your config. I recommend using VSCode as a text editor, but anything that supports Json files will work. Something as basic as notepad will work but will not highlight any of your errors.
Including the "$schema" line is optional, but will allow your text editor to highlight errors and auto-suggest words in
your config. I recommend using VSCode as a text editor, but anything that supports Json files will work. Something as
basic as notepad will work but will not highlight any of your errors.
The config file is then split into modules, each one with its own fields that define how that part of the planet will be generated. In the example above I've used the `Base`, `Orbit`, `Atmosphere`, and `Props` modules. A config file must have a `Base` and `Orbit` module, the rest are optional.
The config file is then split into modules, each one with its own fields that define how that part of the planet will be
generated. In the example above I've used the `Base`, `Orbit`, `Atmosphere`, and `Props` modules. A config file must
have a `Base` and `Orbit` module, the rest are optional.
Each { must match up with a closing } to denote its section. If you don't know how JSONs work then check Wikipedia.
Each `{` must match up with a closing `}` to denote its section. If you don't know how JSONs work then check Wikipedia.
Modules look like this:
@ -93,14 +107,20 @@ Modules look like this:
}
```
In this example the `Star` module has a `size` field and a `tint` field. Since the colour is a complex object it needs another set of { and } around it, and then it has its own fields inside it : `r`, `g`, `b`, and `a`. Don't forget to put commas after each field.
In this example the `Star` module has a `size` field and a `tint` field. Since the colour is a complex object it needs
another set of `{` and `}` around it, and then it has its own fields inside it : `r`, `g`, `b`, and `a`. Don't forget to put
commas after each field.
Most fields are either true/false, a decimal number, and integer number, or a string (word with quotation marks around it).
Most fields are either true/false, a decimal number, and integer number, or a string (word with quotation marks around
it).
Check out the rest of the site for how to format planet, star system, dialogue, ship log, and translation files!
## Helpful resources
The texturemap/heightmap feature was inspired by the KSP mod Kopernicus. A lot of the same techniques that apply to planet creation there apply to New Horizons. You can check out a planetary texturing guide repository [here](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/).
The texturemap/heightmap feature was inspired by the KSP mod Kopernicus. A lot of the same techniques that apply to
planet creation there apply to New Horizons. You can check out a planetary texturing guide
repository [here](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/){ target="_blank" }.
[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.
[Photopea](https://www.photopea.com/){ target="_blank" } 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.

View File

@ -11,19 +11,19 @@ Welcome! this page outlines how to create a custom ship log.
These mods are useful when developing your ship log
- [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer)
- [Collider Visualizer](https://outerwildsmods.com/mods/collidervisualizer)
- [Save Editor](https://outerwildsmods.com/mods/saveeditor)
- [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer){ target="_blank" }
- [Collider Visualizer](https://outerwildsmods.com/mods/collidervisualizer){ target="_blank" }
- [Save Editor](https://outerwildsmods.com/mods/saveeditor){ target="_blank" }
## Helpful Tools
These tools/references are highly recommended
- [VSCode](https://code.visualstudio.com/) { target="_blank" }
- [VSCode XML Addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml)
- [XML Basics Tutorial](https://www.w3schools.com/xml/xml_whatis.asp)
- [The XML Schema](https://github.com/xen-42/outer-wilds-new-horizons/blob/master/NewHorizons/shiplog_schema.xsd)
- [The Examples Mod](https://github.com/xen-42/ow-new-horizons-examples)
- [VSCode XML Addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml){ target="_blank" }
- [XML Basics Tutorial](https://www.w3schools.com/xml/xml_whatis.asp){ target="_blank" }
- [The XML Schema](https://github.com/xen-42/outer-wilds-new-horizons/blob/master/NewHorizons/shiplog_schema.xsd){ target="_blank" }
- [The Examples Mod](https://github.com/xen-42/ow-new-horizons-examples){ target="_blank" }
# Understanding Ship Logs
@ -161,7 +161,7 @@ navigate to ShipLogManager.
In the example XML, you may notice something like `xsi:noNamespaceSchemaLocation` at the top, this tells whatever editor
you're using that the file at that link is the schema. The game simply ignores this though, so it won't be able to catch
errors at runtime.
Some editors may require you to [Trust](https://code.visualstudio.com/docs/editor/workspace-trust) the workspace to use
Some editors may require you to [Trust](https://code.visualstudio.com/docs/editor/workspace-trust){ target="_blank" } the workspace to use
the schema file. Doing this varies per-editor, and you may also have to right-click the link and click download.
## Loading The File

View File

@ -0,0 +1,33 @@
.jsfh-animated-property {
animation: eclair;
animation-iteration-count: 1;
animation-fill-mode: forwards;
animation-duration: .75s;
}
@keyframes eclair {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
}
.accordion-button:not(.collapsed) {
color: #effff7;
background-color: #2b2b2b;
}
.accordion-button:not(.collapsed)::after {
color: #effff7;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.accordion-item {
border: 1px solid rgba(0, 0, 0, 0.4);
}
.bg-warning {
color: #131313;
}

View File

@ -2,102 +2,76 @@ import os
from pathlib import Path
from shutil import copytree, rmtree
from htmlmin import minify
from jinja2 import Environment, select_autoescape, FileSystemLoader
from markupsafe import Markup
from json_schema_for_humans.generate import GenerationConfiguration
from markdown import Markdown
from lib.BootstrapExtension import BootstrapExtension
from lib.Schema import Schema
from lib.Page import Page
from lib.Content.HTMLPage import HTMLPage
from lib.Content.JSONSchema import JSONSchema
from lib.Content.MDPage import MDPage
from lib.Content.MetaItem import MetaItem, MinifiedMetaItem
from lib.Content.XMLSchema import XMLSchema
print("Setup")
OUT_DIR = os.getenv("OUT_DIR", "/")
BASE_URL = os.getenv("BASE_URL", "")
env = Environment(
loader=FileSystemLoader("content/"),
autoescape=select_autoescape(['jinja2'])
)
scanners = (MDPage, HTMLPage)
schema_scanners = (JSONSchema, XMLSchema)
meta_files = (MinifiedMetaItem(Path('browserconfig.jinja2'), '.xml'), MinifiedMetaItem(Path('sitemap.jinja2'), '.xml'), MetaItem(Path('robots.jinja2'), '.txt'))
router = {}
env.filters.update({
'upper_first': lambda x: x[0].upper() + x[1:],
'static': lambda path: str(Path(OUT_DIR, path).as_posix()),
'route': lambda title: router.get(title.lower(), "#"),
'full_url': lambda relative: BASE_URL + (relative[1:] if relative[0] == "/" else relative)
})
MetaItem.initialize(env)
MinifiedMetaItem.initialize(env)
pages = []
schemas = []
print("Clearing Old Output")
if Path("out/").exists():
rmtree("out/", ignore_errors=True)
copytree("content/static", "out")
os.makedirs("out/schemas", exist_ok=True)
env = Environment(
loader=FileSystemLoader("content"),
autoescape=select_autoescape(['jinja2'])
)
print("Scanning For Files")
markdown_settings = {
'extensions': ['extra', 'toc', 'meta', BootstrapExtension()]
}
for scanner in scanners:
new_pages = scanner.initialize(env)
for page in new_pages:
page.add_route(router, OUT_DIR)
pages += new_pages
schema_settings = GenerationConfiguration(custom_template_path="content/base/schema_base.jinja2")
schema_settings.link_to_reused_ref = False
schema_settings.minify = True
minify_settings = {
'remove_empty_space': True,
'keep_pre': True,
'remove_optional_attribute_quotes': False
}
env.minify_settings = minify_settings
md = Markdown(**markdown_settings)
env.filters["upper_first"] = lambda x: x[0].upper() + x[1:]
env.filters["markdown"] = lambda text: Markup(md.convert(text))
env.filters["static"] = lambda path: str(Path(OUT_DIR, path).as_posix())
pages_paths = Path("content/pages").glob("**/*.md")
schemas_paths = Path("content/schemas").glob("**/*.json")
router = {}
env.filters['route'] = lambda title: router.get(title.lower(), "#")
env.filters['full_url'] = lambda relative: BASE_URL + (relative[1:] if relative[0] == "/" else relative)
pages = []
schemas = []
for page_path in pages_paths:
new_page = Page(page_path, env, markdown_settings)
router[new_page.title.lower()] = OUT_DIR + str(new_page.out_path.relative_to('out/'))
pages.append(new_page)
for schema_path in schemas_paths:
new_schema = Schema(schema_path, env, schema_settings)
router[new_schema.title.lower()] = OUT_DIR + "schemas/" + str(new_schema.out_path.relative_to("out/schemas/"))
schemas.append(new_schema)
for scanner in schema_scanners:
new_schemas = scanner.initialize(env)
for schema in new_schemas:
schema.add_route(router, OUT_DIR)
schemas += new_schemas
content = pages + schemas
if OUT_DIR != "":
router['home'] = OUT_DIR
print("Generating Pages")
pages.sort(key=lambda p: p.sort_priority, reverse=True)
schemas.sort(key=lambda s: s.title)
def log_build(in_path, out_path):
print("Building:", str(in_path), "->", str(out_path))
def build_meta(in_path, out_path, do_minify=False):
log_build(in_path, out_path)
meta_template = env.get_template(str(in_path.relative_to("content/")))
with Path("out/", out_path).open(mode="w+", encoding="utf-8") as file:
out = meta_template.render(content=content)
if do_minify:
out = minify(out, **minify_settings)
file.write(out)
print("Building Meta Files")
build_meta(Path("content/sitemap.jinja2"), Path("sitemap.xml"), do_minify=True)
build_meta(Path("content/robots.jinja2"), Path("robots.txt"))
build_meta(Path("content/browserconfig.jinja2"), Path("fav/browserconfig.xml"), do_minify=True)
print("Building Pages")
for item in content:
log_build(item.in_path, item.out_path)
item.render(page=item, pages=pages, schemas=schemas)
item.generate(pages=pages, schemas=schemas)
for meta_file in meta_files:
meta_file.env = env
meta_file.generate(content=content)
print("Done")

View File

@ -0,0 +1,58 @@
from abc import ABC
from pathlib import Path
from htmlmin import minify
from jinja2 import Environment
class AbstractContentItem(ABC):
output_ext: str = '.html'
env: Environment
in_path: Path
out_path: Path
root_dir: Path
def __init__(self, in_path: Path, ext: str = None):
self.env = None
if ext is not None:
self.output_ext = ext
self.in_path = in_path
self.out_path = Path('out/', in_path.name).with_suffix(self.output_ext)
@classmethod
def initialize(cls, env: Environment) -> list:
raise NotImplementedError()
def render(self, **context) -> str:
raise NotImplementedError()
def _save(self, rendered: str):
self.out_path.parent.mkdir(mode=511, parents=True, exist_ok=True)
with self.out_path.open(mode='w+', encoding='utf-8') as file:
file.write(rendered)
def generate(self, **context) -> None:
print("Building:", self.in_path, "->", self.out_path)
self._save(self.render(**context))
def add_route(self, routes, out_dir):
pass
class MinifyMixin(AbstractContentItem, ABC):
MINIFY_SETTINGS = {
'remove_empty_space': True,
'keep_pre': True,
'remove_optional_attribute_quotes': False
}
def _save(self, rendered: str):
rendered = minify(rendered, **self.MINIFY_SETTINGS)
super(MinifyMixin, self)._save(rendered)

View File

@ -0,0 +1,24 @@
from abc import ABC
from pathlib import Path
from jinja2 import Template, Environment
from lib.Content.AbstractTemplatedItem import AbstractTemplatedItem
class AbstractSchemaItem(AbstractTemplatedItem, ABC):
root_dir = Path('schemas/')
def __init__(self, in_path: Path):
super().__init__(in_path)
self.out_path = Path('out/schemas/', self.out_path.relative_to('out'))
@classmethod
def initialize(cls, env: Environment):
new_pages = super(AbstractSchemaItem, cls).initialize(env)
for page in new_pages:
page.out_path = page.out_path.with_stem(page.title.replace(" ", "_").lower())
return new_pages
def render(self, template: Template, **context):
raise NotImplementedError()

View File

@ -0,0 +1,48 @@
from abc import ABC
from itertools import chain
from pathlib import Path
from jinja2 import Template, Environment
from lib.Content.AbstractContentItem import AbstractContentItem, MinifyMixin
class AbstractTemplatedItem(MinifyMixin, AbstractContentItem, ABC):
extensions: tuple[str]
title: str = None
description: str | None = None
sort_priority: int = None
def load_metadata(self):
if self.title is None:
self.title = self.in_path.stem
if self.description is None:
self.description = None
if self.sort_priority is None:
self.sort_priority = 10
@classmethod
def initialize(cls, env: Environment):
pages = []
file_paths = list(chain(*[Path('content/', cls.root_dir).glob(f'**/*.{ext}') for ext in cls.extensions]))
for path in file_paths:
new_page = cls(path)
new_page.env = env
new_page.load_metadata()
pages.append(new_page)
return pages
def inner_render(self, template: Template, **context):
return template.render(**context)
def render(self, **context):
container = self.env.get_template('base/page_template.jinja2')
template = self.env.get_template(str(self.in_path.relative_to(Path('content/')).as_posix()))
context.update({
'page': self,
'rendered': self.inner_render(template, **context)
})
return container.render(**context)
def add_route(self, router, out_dir):
router[self.title.lower()] = out_dir + str(self.out_path.relative_to('out/'))

View File

@ -0,0 +1,8 @@
from pathlib import Path
from lib.Content.AbstractTemplatedItem import AbstractTemplatedItem
class HTMLPage(AbstractTemplatedItem):
root_dir = Path("pages/")
extensions = ('html', 'jinja2', 'jinja')

View File

@ -0,0 +1,58 @@
import json
import os
import sys
from pathlib import Path
from json_schema_for_humans.generate import generate_schemas_doc
from json_schema_for_humans.generation_configuration import GenerationConfiguration
from json_schema_for_humans.schema.schema_to_render import SchemaToRender
from json_schema_for_humans.template_renderer import TemplateRenderer
from lib.Content.AbstractSchemaItem import AbstractSchemaItem
SCHEMA_SETTINGS = GenerationConfiguration()
SCHEMA_SETTINGS.link_to_reused_ref = False
SCHEMA_SETTINGS.minify = False
class NoPrint:
def __enter__(self):
self._original_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.close()
sys.stdout = self._original_stdout
class JSONSchema(AbstractSchemaItem):
extensions = ('json',)
def load_metadata(self):
self.sort_priority = 10
with self.in_path.open(mode='r', encoding='utf-8') as file:
self.title = json.load(file).get('title', self.in_path.stem)
self.description = "Schema for a " + self.title + " in New Horizons"
super(JSONSchema, self).load_metadata()
def render(self, **context):
context.update({
'page': self
})
dumb_renderer = TemplateRenderer(SCHEMA_SETTINGS)
self.env.filters.update(dumb_renderer.template.environment.filters)
self.env.tests.update(dumb_renderer.template.environment.tests)
self.env.globals.update(dumb_renderer.template.environment.globals)
schemas = [SchemaToRender(self.in_path, None, None)]
schema_template = self.env.get_template(str(Path('base/schema/json/schema_base.jinja2').as_posix()))
template_renderer = TemplateRenderer(SCHEMA_SETTINGS, schema_template)
template_renderer.render = lambda inter: self.template_override(template_renderer, inter, **context)
with NoPrint():
rendered = generate_schemas_doc(schemas, template_renderer)
return rendered[str(self.in_path.name)]
def template_override(self, template: TemplateRenderer, intermediate_schema, **context):
template.template.environment.loader = self.env.loader
rendered = template.template.render(schema=intermediate_schema, config=SCHEMA_SETTINGS,
title=self.title + " Schema", **context)
return rendered

View File

@ -0,0 +1,39 @@
from pathlib import Path
from markdown import Markdown
from lib.Content.AbstractTemplatedItem import AbstractTemplatedItem
from lib.BootstrapExtension import BootstrapExtension
class MDPage(AbstractTemplatedItem):
root_dir = Path("pages/")
extensions = ('md', 'markdown')
MARKDOWN_SETTINGS = {
'extensions': ['extra', 'toc', 'meta', BootstrapExtension()]
}
def load_metadata(self):
md = self.__get_md()
with self.in_path.open(mode='r', encoding='utf-8') as file:
md.convert(file.read())
self.title = md.Meta.get('title')[0]
self.description = md.Meta.get('description', [None])[0]
raw_priority = md.Meta.get('sort-priority')
if raw_priority is not None:
self.sort_priority = int(raw_priority[0])
out_name = md.Meta.get('out-file', None)
if out_name is not None:
self.out_path = self.out_path.with_stem(out_name[0])
super(MDPage, self).load_metadata()
def __get_md(self):
return Markdown(**self.MARKDOWN_SETTINGS)
def inner_render(self, template, **context):
rendered_markdown = super(MDPage, self).inner_render(template, **context)
md = self.__get_md()
return md.convert(rendered_markdown)

View File

@ -0,0 +1,18 @@
from jinja2 import Environment
from lib.Content.AbstractContentItem import AbstractContentItem, MinifyMixin
class MetaItem(AbstractContentItem):
@classmethod
def initialize(cls, env: Environment) -> list:
cls.env = env
def render(self, **context) -> str:
template = self.env.get_template(str(self.in_path.as_posix()))
return template.render(**context)
class MinifiedMetaItem(MinifyMixin, MetaItem):
pass

View File

@ -0,0 +1,85 @@
import os
import xmlschema
from xmlschema.extras.codegen import AbstractGenerator, filter_method
__all__ = ('XMLSchema',)
from lib.Content.AbstractSchemaItem import AbstractSchemaItem
def children(group):
child = [child for child in group if child.__class__.__name__ == "XsdElement"]
for child_list in [children(inner_group) for inner_group in group if inner_group.__class__.__name__ == "XsdGroup"]:
child += child_list
return child
def ancestry(element):
if element.parent is None:
print(element.name)
return [element.name]
else:
if element.name is None:
return ancestry(element.parent)
else:
return [element.name] + ancestry(element.parent)
class HTMLConverter(AbstractGenerator):
formal_language = "html"
searchpaths = [os.getcwd() + "/content/"]
@staticmethod
@filter_method
def children(group):
return children(group)
@staticmethod
@filter_method
def id_path(element):
return '-'.join(reversed(ancestry(element)))
@staticmethod
@filter_method
def split(string, delim):
return string.split(delim)
@staticmethod
@filter_method
def occurs_text(occurs):
words = {
0: "Zero",
1: "One",
None: "Many"
}
return "Appears " + words[occurs[0]] + " To " + words[occurs[1]] + " " + ("Time" if occurs[1] == 1 else "Times")
def update_filters(self, filters):
self._env.filters.update(filters)
class XMLSchema(AbstractSchemaItem):
extensions = ('xsd', 'xml')
def load_metadata(self):
with self.in_path.open(mode='r', encoding='utf-8') as file:
file.readline()
line = file.readline()
if len(line.strip()) != 0 and '<!--' in line and '-->' in line:
self.title = line.replace('<!--', '').replace('-->', '').strip()
super(XMLSchema, self).load_metadata()
def render(self, **context):
context.update({
'page': self
})
with self.in_path.open(mode='r', encoding='utf-8') as file:
schema = xmlschema.XMLSchema(file)
converter = HTMLConverter(schema)
converter.update_filters(self.env.filters)
return converter.render('base/schema/xml/schema_base.jinja2', global_vars=context)[0]

View File

@ -1,43 +0,0 @@
from dataclasses import dataclass
from pathlib import Path
from htmlmin import minify
from jinja2 import Environment
from markdown import Markdown
@dataclass
class Page:
sort_priority: int
in_path: Path
out_path: Path
title: str
description: str | None
env: object
def __init__(self, path, environment, options):
self.in_path = path
self.env = environment
md = Markdown(**options)
with path.open('r', encoding='utf-8') as file:
md.convert(file.read())
self.sort_priority = int(md.Meta.get('sort-priority', '20')[0])
self.title = md.Meta.get('title', (path.stem,))[0]
self.description = md.Meta.get('description', [None])[0]
outfile: Path
try:
outfile = Path("out/", path.relative_to(Path("content/pages/")).parent,
md.Meta['out-file'][0] + '.html')
except KeyError:
outfile = Path("out/", path.relative_to(Path("content/pages/"))).with_suffix('.html')
self.out_path = outfile
def render(self, **options):
template = self.env.get_template(str(self.in_path.relative_to(Path("content/")).as_posix()))
page_template = self.env.get_template("base/page_template.jinja2")
rendered_string = page_template.render(content=template.render(**options), **options)
self.out_path.parent.mkdir(mode=511, parents=True, exist_ok=True)
with self.out_path.open(mode='w+', encoding='utf-8') as file:
file.write(minify(rendered_string, **self.env.minify_settings))

View File

@ -1,44 +0,0 @@
import json
from dataclasses import dataclass
from pathlib import Path
from htmlmin import minify
from json_schema_for_humans.generate import generate_schemas_doc, copy_additional_files_to_target
from json_schema_for_humans.schema.schema_importer import get_schemas_to_render
from json_schema_for_humans.template_renderer import TemplateRenderer, _minify
from json_schema_for_humans.generation_configuration import GenerationConfiguration
# noinspection PyUnresolvedReferences
from lib.Page import Page
@dataclass
class Schema(Page):
config: GenerationConfiguration
def __init__(self, path, env, options):
self.sort_priority = 10
self.in_path = path
self.config = options
self.env = env
with path.open() as file:
self.title = json.load(file).get('title', path.stem)
self.description = "Schema for a " + self.title + " in New Horizons"
self.out_path = Path('out/schemas/', self.in_path.relative_to(Path("content/schemas/")).with_name(self.title.replace(" ", "_").lower()).with_suffix(".html"))
def render(self, **options):
schemas = get_schemas_to_render(self.in_path, self.out_path, ".html")
template_renderer = TemplateRenderer(self.config)
template_renderer.render = lambda inter: self.template_override(template_renderer, inter, **options)
generate_schemas_doc(schemas, template_renderer)
copy_additional_files_to_target(schemas, self.config)
def template_override(self, template, intermediate_schema, **options):
template.template.environment.filters.update(self.env.filters)
rendered = template.template.render(schema=intermediate_schema, dumb=True, config=self.config, title=self.title + " Schema", **options)
if template.config.minify:
rendered = minify(rendered, **self.env.minify_settings)
return rendered

19
docs/lib/utils.py Normal file
View File

@ -0,0 +1,19 @@
import re
def camel_to_pretty(raw):
return ' '.join(re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', raw))
def pretty_title(raw: str) -> str:
if '_' in raw:
return ' '.join(x[0].upper() + x[1:] for x in raw.split('_'))
elif any(x.isupper() for x in raw):
if raw[0].islower():
new_raw = raw[0].upper() + raw[1:]
else:
new_raw = raw
return camel_to_pretty(new_raw)