Compare commits

...

2 Commits

Author SHA1 Message Date
Jill 86ad0c5ee7 add page suggestions at the bottom of the page 2022-05-03 23:56:31 +03:00
Jill d0220de8ee good error 2022-05-03 23:50:22 +03:00
6 changed files with 252 additions and 30 deletions

56
src/routes/__error.svelte Normal file
View File

@ -0,0 +1,56 @@
<script context="module">
/** @type {import('@sveltejs/kit').Load} */
export function load({ error, status }) {
return {
props: {
status: status,
message: error.message,
stack: error.stack
}
};
}
</script>
<script>
export let status;
export let message;
export let stack;
</script>
<style>
.gradient {
font-size: 100px;
line-height: 0.1;
background: repeating-linear-gradient(98deg, rgba(190,190,190,1) 0%, rgba(190,190,190,1) 10%, rgba(207,207,207,1) 10%, rgba(207,207,207,1) 20%);
background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bolder;
}
h1 {
margin-bottom: 0.5em;
}
.error {
display: flex;
flex-direction: column;
align-items: center;
max-width: 800px;
}
pre {
max-width: 100%;
overflow-x: auto;
background-color: #222;
color: #fff;
padding: 1em;
border-radius: 1em;
}
</style>
<div class="error">
<div class="gradient">
<h1>{status}</h1>
</div>
{message}
{#if stack}
<pre>{stack}</pre>
{/if}
</div>

View File

@ -11,9 +11,21 @@ export async function get({ params }) {
}
}
} else {
let pages = [];
let split = params.name.split('/');
if (split.length > 1) {
pages = await getPostsFlat(split[split.length - 2]);
}
let post = await getPost(params.name);
pages = pages.filter(l => l.path !== post.path);
return {
body: {
page: await getPost(params.name)
page: post,
pages: pages
}
}
}

View File

@ -6,7 +6,7 @@
</script>
{#if page}
<PageRenderer {page}/>
<PageRenderer {page} {pages}/>
{:else if pages}
<Pages {pages}/>
{:else}

View File

@ -1,7 +1,9 @@
<script>
import Page from "$lib/Page.svelte";
import { extractAnchors, toAnchor } from '$lib/anchors';
import Pages from "$lib/Pages.svelte";
export let page;
export let pages;
</script>
<style>
@ -54,31 +56,34 @@
color: #fff;
}
}
</style>
<svelte:head>
<title>{page.name} - Guidebook</title>
<meta name="description" content="{page.description || 'A Guidebook guide!'}" />
</svelte:head>
<div class="stuff">
<div class="side">
<nav>
<el>
{#each extractAnchors(page.content) as anchor}
{#if anchor.level === 2}
<li style="margin-left: {Math.max(anchor.level - 2, 0) * 0.3}em">
<a href="#{toAnchor(anchor.name)}">
{anchor.name}
</a>
</li>
{/if}
{/each}
</el>
</nav>
</div>
<content>
<Page content={page.content}></Page>
</content>
</div>
</style>
<svelte:head>
<title>{page.name} - Guidebook</title>
<meta name="description" content="{page.description || 'A Guidebook guide!'}" />
</svelte:head>
<div class="stuff">
<div class="side">
<nav>
<el>
{#each extractAnchors(page.content) as anchor}
{#if anchor.level === 2}
<li style="margin-left: {Math.max(anchor.level - 2, 0) * 0.3}em">
<a href="#{toAnchor(anchor.name)}">
{anchor.name}
</a>
</li>
{/if}
{/each}
</el>
</nav>
</div>
<content>
<Page content={page.content}></Page>
</content>
</div>
{#if pages}
<Pages pages={pages.slice(0, 3)}/>
{/if}

View File

@ -0,0 +1,4 @@
{
"name": "How to get a random value (copy)",
"description": "What's the best way to get a random value in Isaac? What's the laziest way? This page anwsers that, and more."
}

145
storage/test/test (copy).md Normal file
View File

@ -0,0 +1,145 @@
# I want to get a random value in Isaac!
Well I'm glad you asked, because this simple question has many anwsers depending on how much effort you want to put in!
There are 3 types of random you can use in Isaac, going from the one that requires the least amount of effort to the one that requires the most amount of effort. There is no _"objectively best"_ method, as it depends on your usecase, the size of your mod, and how much you truly care.
## Why?
For this guide, let's assume your usecase is for adding a random vector to an entity's velocity:
```lua
entity.Velocity = entity.Velocity + randomVec
```
These same methods can be applied to just about anything random in Isaac (like generating a random item), but we'll be specifically using this usecase for our examples.
## `math.random`
Lua's vanilla `math.random` is what a typical Lua veteran coming to Isaac will choose. And it makes sense; it has a simple, intuitive interface, and it just, kind of works. There's no setup, it's cross-version, cross-API, and works anywhere.
```lua
local randomVec = Vector.FromAngle(math.random() * 360)
```
But there's quite a lot of downsides!
### 1. `math.random` can be affected with `math.randomseed`
If any mod, _any_ mod that you have loaded runs `math.randomseed`, the `math.random` seed will be set _globally_. If Mod A runs `math.randomseed`, the random values of Mod B will be affected. And this could be happening at a rate of once per render frame, meaning you can potentially never get actually random outputs.
This is a pretty massive dealbreaker for our use-case. You can still _kind of_ get around this by doing:
```lua
math.randomseed(Isaac.GetTime())
local randomVec = Vector.FromAngle(math.random() * 360)
```
But this still has the other 2 downsides.
### 2. The seed does not update with the game's seed
This is a pretty minor issue, but it matters to some. When Isaac starts a run, it will create a seed (or use a user-given seed). This seed will be used for _every element_ of Isaac's RNG. This means that if two players use the same seed, the first item they'll find in an item room will be the exact same.
Now, if you generate some value with `math.random` (for instance, how many items you find in an item room), this will no longer be the case; `math.random` is initially seeded on the amount of seconds your computer has been on for _[citation neeeded]_. This also means that co-op games with `netstart` will no longer function correctly; one computer will generate one value, the other will generate another, and this'll result in a desync.
You can _still_ fix this, if you're desperate, however!
```lua
math.randomseed(Game():GetSeeds():GetStartSeed())
local randomVec = Vector.FromAngle(math.random() * 360)
```
This is beginning to be a bit cumbersome, however... Not only will this only generate a unique value once, it _still_ does not protect us against the third downside!
### 3. `math.random` does not function correctly cross-platform
[Lua 5.3 uses C's `rand()` for random number generation](https://www.lua.org/source/5.3/lmathlib.c.html#l_rand). C's `rand()` implementation depends on the compiler used (GCC, Clang, MSVC, etc.) _[citation needed]_. What this means for your mod is what values you recieve from `math.random` with the same seed are dependant on the host's OS; if you're running Windows, you'll get one value, if you're running Linux, you'll get another.
Now, this isn't a _massive_ deal breaker either. However, there's _no way of fixing this_ without using an alternative pseudo-random value generator. But what this means for your mod is two people on different OS's will get different values from the same seed, potentially desyncing online co-op games.
It's also worth noting that as of Repentance, only a Windows build is available; thus, Linux and Mac must play on Wine/Proton, emulating Windows's `rand()` implementation, rather than using the native one. Because of this, this may be irrelevant in your use case - but it's not known if native Linux/Mac builds release, and it will still behave this way on Afterbirth+.
### `math.random` - TL;DR
`math.random` is a nice, simple, but lazy method for getting a random value. But it suffers from potential exploitation, it ignores the seed, and has issues behaving the same on different operating systems. It's good as a temporary, simple solution, but highly unrecommended to use in larger mods, especially if you want online co-op to function. However, it may also be useful as a simple seeded random number generator.
## `Random()` / `RandomVector()`
[`Random`](https://wofsauge.github.io/IsaacDocs/rep/GlobalFunctions.html#random) and [`RandomVector`](https://wofsauge.github.io/IsaacDocs/rep/GlobalFunctions.html#randomvector) are Isaac API functions that return, well, a random int and a random vector, respectively. It's simple and easy, just plug it in and it'll work.
```lua
local randomVec = RandomVector()
-- or...
local randomVec = Vector.FromAngle(Random()/(2^32)) -- untested
```
What's also really nice about it is `RandomVector` will always return a vector of size 1 - thus you can multiply it to achieve higher velocities with no issue:
```lua
local randomVec = RandomVector() * 32 -- will always be of length 32
```
However, this still faces a familiar downside!
### 1. `Random()` is unseeded
Same as `math.random`, `Random` and `RandomVector` aren't seeded on anything. Unlike `math.random`, however, you also cannot seed it - this prevents potential sabotages from different mods, but doesn't let you seed it with the run's current seed. There's no known way around this, so if you want this functionality out of it, you'll have to use the `RNG` class.
## The `RNG` class
The [`RNG`](https://wofsauge.github.io/IsaacDocs/rep/RNG.html) class is what the game uses internally for RNG; so because of this, it's the most compatible solution. You'll be able to shove it into vanilla Isaac API functions with no issue. However, due to the Isaac API being, well, the Isaac API, it's also the clunkiest, and it's a bit cumbersome to setup.
First, you must create a new `RNG` class, either as a global, as an entry in your mod's table, or as a value you pass around:
```lua
MyMod.RNG = RNG()
```
Once you've done this, create a `MC_POST_GAME_STARTED` callback that'll set the seed of your `RNG` object:
```lua
local RECOMMENDED_SHIFT_IDX = 35 -- keep this as-is!
MyMod:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, function()
MyMod.RNG:SetSeed(Game():GetSeeds():GetStartSeed(), RECOMMENDED_SHIFT_IDX)
end)
```
And now, you can generate a random vector!
```lua
local randomVec = Vector.FromAngle(MyMod.RNG:RandomFloat() * 360)
```
However, this isn't the end of it. You'll also have to store your seed in your mod's savefile and load it back up when the run is continued to prevent save-scumming - a player could go inside a room, see a value they don't want to see, exit, continue, and get a different value. However, this is outside of the scope of this guide. _Think of it as an exercise for the reader._
Got all that? Good, because that's still not everything. It's preferable to have different RNG classes for different parts of your mod. If you generate items, but use the same `RNG` object to also generate the movement of an entity, the player could get a different item by taking longer to kill an entity:
```lua
MyMod.random = {}
MyMod.random.entities = RNG()
MyMod.random.items = RNG()
-- etc ...
local RECOMMENDED_SHIFT_IDX = 35
MyMod:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, function()
for _, rng in pairs(MyMod.random) do
rng:SetSeed(Game():GetSeeds():GetStartSeed(), RECOMMENDED_SHIFT_IDX)
end
end)
-- then:
local itemId = MyMod.random.items:RandomInt(maxItem)
local randomVec = Vector.FromAngle(MyMod.random.entities:RandomFloat() * 360)
```
This method truly has no downsides. Besides, well, the exhaustive work you have to do to set it up, treat it right, save it, prevent save scumming, and all sorts of nonsense. So if you're not willing to settle for quick & dirty, this is the easiest option you've got.
Of course, it's recommended to abstract this away into your own little classes and libraries, as is for just about anything in the great, wonderful Isaac API, but completely not necessary.
## Conclusions
Use `math.random` if you simply don't care. Use `Random`/`RandomVector` if you care about potentially bad random values. Use `RNG` if you're a perfectionist. That's about everything!