init commit, i think

This commit is contained in:
Jill 2022-09-19 16:42:33 +03:00
commit 62d2b1085d
25 changed files with 5019 additions and 0 deletions

1
.gitconfig Normal file
View File

@ -0,0 +1 @@
pull.rebase=true

52
.gitignore vendored Normal file
View File

@ -0,0 +1,52 @@
# Old SM files
*.sm.auto
*.sm.old
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Folder config file
[Dd]esktop.ini
# Windows shortcuts
*.lnk
# MacOS
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Linux
*~
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# Cache files for Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"sumneko.lua",
"fms-cat.xml-stepmania"
],
}

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"Lua.runtime.path": [
"?.lua"
],
"Lua.runtime.version": "Lua 5.1"
}

641
MANUAL.md Normal file
View File

@ -0,0 +1,641 @@
<sub>
If you're viewing this in VSCode, it's recommended that you open it in a preview with `Ctrl+Shift+V`!
</sub>
---
<br>
<center id="start">
<h2 style="font-size: 42px">
<img src="docs/uranium.png" height="42px" alt="">
<b>Uranium Template</b> <em>α</em>
</h2>
<em style="font-family: 'Comic Sans MS', 'Comic Sans', Impact, Arial">Official manual!!!!</em>
</center>
<br>
**Uranium Template** is a Love2D-inspired NotITG game development template, focusing on keeping things as **Lua-pure** as possible with enough abstractions to make you feel like you're not dealing with Stepmania jank at all.
Uranium Template originally formed during the creation of a currently unreleased project, and since then I've went ahead and refined and polished it up to be usable on its own. Most of the design decisions came from experience using prototype versions of it!
- [Testimonies](#testimonies)
- [Installation](#installation)
- [Distribution](#distribution)
- [How do I start writing code?](#how-do-i-start-writing-code)
- [Defining actors](#defining-actors)
- [Initializing actors](#initializing-actors)
- [Callback usage](#callback-usage)
- [Default callbacks](#default-callbacks)
- [`uranium.update(dt: number)`](#uraniumupdatedt-number)
- [`uranium.init()`](#uraniuminit)
- [`uranium.press(direction: string)`](#uraniumpressdirection-string)
- [`uranium.release(direction: string)`](#uraniumreleasedirection-string)
- [Custom callbacks](#custom-callbacks)
- [Standard library](#standard-library)
- [`vector2D`](#vector2d)
- [`vector2D(x: number | nil, y: number | nil): vector2D`](#vector2dx-number--nil-y-number--nil-vector2d)
- [`vectorFromAngle(ang: number | nil, amp: number | nil): vector2D`](#vectorfromangleang-number--nil-amp-number--nil-vector2d)
- [`vector2D:length(): number`](#vector2dlength-number)
- [`vector2D:lengthSquared(): number`](#vector2dlengthsquared-number)
- [`vector2D:angle(): number`](#vector2dangle-number)
- [`vector2D:normalize(): vector2D`](#vector2dnormalize-vector2d)
- [`vector2D:resize(length: number): vector2D`](#vector2dresizelength-number-vector2d)
- [`vector2D:unpack(): number, number`](#vector2dunpack-number-number)
- [`vector2D:distance(vect: vector2D): number`](#vector2ddistancevect-vector2d-number)
- [`vector2D:distanceSquared(vect: vector2D): number`](#vector2ddistancesquaredvect-vector2d-number)
- [Operations](#operations)
- [`color`](#color)
- [`rgb(r: number, g: number, b: number, a: number | nil): color`](#rgbr-number-g-number-b-number-a-number--nil-color)
- [`hsl(h: number, s: number, l: number, a: number | nil): color`](#hslh-number-s-number-l-number-a-number--nil-color)
- [`hsv(h: number, s: number, v: number, a: number | nil): color`](#hsvh-number-s-number-v-number-a-number--nil-color)
- [`shsv(h: number, s: number, v: number, a: number | nil): color`](#shsvh-number-s-number-v-number-a-number--nil-color)
- [`hex(hex: string): color`](#hexhex-string-color)
- [`color:unpack(): number, number, number, number`](#colorunpack-number-number-number-number)
- [`color:rgb(): number, number, number`](#colorrgb-number-number-number)
- [`color:hsl(): number, number, number`](#colorhsl-number-number-number)
- [`color:hsv(): number, number, number`](#colorhsv-number-number-number)
- [`color:hex(): string`](#colorhex-string)
- [`color:hue(h: number): color`](#colorhueh-number-color)
- [`color:huesmooth(h: number): color`](#colorhuesmoothh-number-color)
- [`color:alpha(a: number): color`](#coloralphaa-number-color)
- [`color:malpha(a: number): color`](#colormalphaa-number-color)
- [`color:invert(): color`](#colorinvert-color)
- [`color:grayscale(): color`](#colorgrayscale-color)
- [`color:hueshift(a: number): color`](#colorhueshifta-number-color)
- [Operations](#operations-1)
- [`input`](#input)
- [A note about keyboard inputs](#a-note-about-keyboard-inputs)
- [`bitop`](#bitop)
- [`rng`](#rng)
- [`rng.init(seed: number[] | nil): rng`](#rnginitseed-number--nil-rng)
- [`rng(a: number | nil, b: number | nil): number`](#rnga-number--nil-b-number--nil-number)
- [`rng:int(min: number, max: number | nil): number`](#rngintmin-number-max-number--nil-number)
- [`rng:float(max: number | nil): number`](#rngfloatmax-number--nil-number)
- [`rng:bool(): boolean`](#rngbool-boolean)
- [`rng:seed(seed: number): void`](#rngseedseed-number-void)
- [`rng:next(): number`](#rngnext-number)
- [`rng:jump(): void`](#rngjump-void)
- [`rng:longJump(): void`](#rnglongjump-void)
- [`uwuify`](#uwuify)
- [Examples](#examples)
- [Default Uranium Template code](#default-uranium-template-code)
- [Simple platformer base](#simple-platformer-base)
- [Credits](#credits)
## Testimonies
- "this template really adds some spice to your modfiles. think industrial glitter in your next chilli!" _- Mayflower_
- "a good template that i have definitely used! jill can i go now. jill please i just want to see my family" _- Aura_
## Installation
Installation is the exact same as any other NotITG template:
1. Unzip your installation zip, as you would a modfile
2. Edit `Song.sm` in your editor of choice (ArrowVortex, NotITG) to include necessary metadata; replace `silence.ogg` with an actual track, if necessary
3. Edit `main.lua` to do whatever you wish to do with it! The entirety of the `src/` folder is yours!
4. _(Recommended)_ Install [sumneko's Lua LSP](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) and grab the latest NotITG typings [here](https://gitlab.com/CraftedCart/notitg_docs/-/archive/master/notitg_docs-master.zip?path=lua) (put them in a folder like `.typings`!)
## Distribution
After you're done with writing your file, be sure to take these steps to reduce the filesize and get your game ready for zipping up!
- Remove `MANUAL.md`, `jillo_png.png`, `docs/`, `.vscode/`, `.gitconfig`, `.gitignore` and `template/typings.lua`. These are files that aren't necessary outside of a development environment!
- Optionally, remove `Song.sm.auto` and `Song.sm.old`. These files may not exist depending on certain factors.
- If you've followed step 4 during [Installation](#installation), be sure to remove your typings folder (likely `.typings`)
- If you're using Git, **PLEASE REMOVE YOUR `.git/` FOLDER!!!**
Afterwards, it should be safe to zip everything up and send it over!
## How do I start writing code?
`main.lua` is the entry-point for your code! From there, you can do the following:
- [Define some actors](#defining-actors), and [call initialization methods on those actors](#initializing-actors) to set them up
- [Define callbacks](#callback-usage), such as the [update](#uraniumupdatedt-number) callback
- Require more files in! Splitting your code into neat little modules is always good practice.
- Make use of the expansive standard library, like the vector or color classes
If you're still a bit clueless, why not check out the [Examples](#examples) section?
## Defining actors
Actors are defined in Uranium Template before any other callback runs, and are defined by a single function of their type:
```lua
local quad = Quad()
local sprite = Sprite('file/location.png')
local text = BitmapText('common', 'hello, world!')
```
### Initializing actors
Once you have an actor defined, you can run whatever methods you want.
> **Note**
> Even though you get a fully functional actor, what you actually get is a _proxied actor_! What this means for you is that you really shouldn't call any getters on the actor, as it'll just return `nil`.
```lua
local text = BitmapText('common', 'hello, world!')
text:xy(scx, scy)
text:zoom(2.3)
text:rotationz(30)
text:diffuse(1, 0.8, 0.8, 1)
```
All methods that you run upon definition will be ran again at the start of every frame:
```lua
local quad = Quad()
quad:xy(scx, scy)
quad:zoomto(60, 60)
quad:diffusealpha(1)
function uranium.update()
-- doesn't need a reset! it'll automatically zoomto 60, 60 and set its alpha to 1
quad:Draw()
quad:zoomto(120, 120)
quad:diffusealpha(0.5)
quad:Draw()
end
```
If you want to avoid this, or otherwise call getter methods, use the [`uranium.init`](#uraniuminit) callback:
```lua
local sprite = Sprite()
function uranium.init()
someTexture = sprite:GetTexture()
end
```
Alternatively, you can also use the actors' individual `InitCommand`s:
```lua
local sprite = Sprite()
sprite:addcommand('Init', function(self)
someTexture = self:GetTexture()
end)
```
## Callback usage
Uranium uses a unique callback system - to define a callback, you define a function under `uranium.` with your desired callback name:
```lua
function uranium.update(dt)
-- runs every frame
end
```
You can do this as many times as you like - it'll call every single function that's defined as `uranium.update`, not just the last!
If you return a non-falsy value in a callback, however, it'll cancel every other callback after it. This can be useful for, eg. capturing inputs and ensuring they don't get passed through to other callbacks on accident.
### Default callbacks
These are the callbacks that are built into Uranium:
#### `uranium.update(dt: number)`
Called every frame. `dt` is the time passed since the last frame, the "deltatime".
#### `uranium.init()`
Called once on `OnCommand`. Every actor has been created, and the game should be starting shortly.
#### `uranium.press(direction: string)`
Called when the player presses on a certain key. **Currently only supports arrow keys!!** `direction` can be `Left`, `Down`, `Up` or `Right` (TODO: change this to an enum).
#### `uranium.release(direction: string)`
Same as [`uranium.press`](#uraniumpressdirection-string), except for releasing a key.
### Custom callbacks
Custom callbacks require no extra setup. Define your callback like usual:
```lua
function uranium.somethingHappened(value)
-- ...
end
```
Then all you need to do to call it is:
```lua
uranium:call('somethingHappened', extra, values, go, here)
```
Callbacks support as many extra values as Lua supports arguments in a function - so let's just say you won't be running out of them any time soon.
## Standard library
The Uranium Template standard library is split up into a few convinient modules. This section aims to comprehensively document them all.
### `vector2D`
`vector2D` is a simple 2D vector class system. For example, to define a vector:
```lua
local vec = vector2D(0, 0)
-- or
local vec = vector(0, 0)
-- or
local vec = vector(0)
-- or
local vec = vector()
```
Then add another vector to it:
```lua
vec = vec + vector(1)
print(vec) --> (1, 1)
```
Then measure its length:
```lua
local len = vec:length()
print(len) --> 1.4142135623730951
-- (sqrt of 2)
```
Then rotate it and index it:
```lua
vec:rotate(180)
local x = vec.x
-- or
local x = vec[1]
print(x) --> -1
```
#### `vector2D(x: number | nil, y: number | nil): vector2D`
Creates a new vector. If only `x` is passed in, `y` = `x`. If no arguments are passed, `x` = `y` = `0`.
#### `vectorFromAngle(ang: number | nil, amp: number | nil): vector2D`
Creates a new vector pointing in a specific angle. **Specify `ang` in degrees.** `ang` defaults to 0, `amp` defaults to 1.
#### `vector2D:length(): number`
Returns the vector's length. Equal to `vector:distance(vector())`.
#### `vector2D:lengthSquared(): number`
Returns the vector's length, squared. Here mainly for optimization purposes; this is a cheaper version of [`length()`](#vectorlength-number) that's less accurate.
#### `vector2D:angle(): number`
Returns the vector's angle in radians. <!--(TODO: why?)-->
#### `vector2D:normalize(): vector2D`
Normalizes the vector, setting its length to 1 but keeping its angle. Equal to `vector:resize(1)`
#### `vector2D:resize(length: number): vector2D`
Resizes the vector, setting its length but keeping its angle.
#### `vector2D:unpack(): number, number`
Unpacks the vector into its X and Y coordinates. Useful for quickly unpacking it into a function call:
```lua
local quad = Quad()
quad:xy(center:unpack())
```
#### `vector2D:distance(vect: vector2D): number`
Gets the distance between one vector and another.
#### `vector2D:distanceSquared(vect: vector2D): number`
Gets the distance between one vector and another, squared. Here mainly for optimization purposes; this is a cheaper version of [`distance()`](#vectordistancevect-vector-number) that's less accurate.
#### Operations
Here are all valid operations for vectors:
- `vector2D + number`: equal to `vector2D + vector2D(number)`
- `vector2D + vector2D`: adds the vectors' X and Y coordinates together, respectively, forming a new vector
- `vector2D - number`: equal to `vector2D - vector2D(number)`
- `vector2D - vector2D`: subtracts the vectors' X and Y coordinates, respectively, forming a new vector
- `vector2D * number`: equal to `vector2D * vector2D(number)`
- `vector2D * vector2D`: multiplies the vectors' X and Y coordinates together, respectively, forming a new vector
- `vector2D / number`: equal to `vector2D / vector2D(number)`
- `vector2D / vector2D`: divides the vectors' X and Y coordinates, respectively, forming a new vector
- `vector2D == vector2D`: checks if the two vectors' X and Y coordinates are equivalent; returns false with any other type
- `-vector2D`: negates the X and Y coordinates of the vector
### `color`
`color` is a simple wrapper around all things color-related.
#### `rgb(r: number, g: number, b: number, a: number | nil): color`
Constructs a new color using the `r`, `g`, `b` and `a` values. Assumes all values are contained in the set [0, 1]. `a` defaults to 1.
#### `hsl(h: number, s: number, l: number, a: number | nil): color`
Constructs a new color using the `h`, `s`, `l` and `a` values using the [HSL color model](https://en.wikipedia.org/wiki/HSL_and_HSV). Assumes all values are contained in the set [0, 1]. `a` defaults to 1; `h` wraps around.
#### `hsv(h: number, s: number, v: number, a: number | nil): color`
Constructs a new color using the `h`, `s`, `v` and `a` values using the [HSV color model](https://en.wikipedia.org/wiki/HSL_and_HSV). Assumes all values are contained in the set [0, 1]. `a` defaults to 1; `h` wraps around.
#### `shsv(h: number, s: number, v: number, a: number | nil): color`
Equal to [`hsv()`](#hsvh-number-s-number-v-number-a-number--nil), except the hue value is smoothed using cubic smoothing. Not accurate, but produces neater-looking color blends for rainbow-shifting colors.
```lua
local rainbow = shsv(t, 1, 0.5)
```
#### `hex(hex: string): color`
Reads in a hex string and parses it into a `color`. Accepted hex string formats are `#ffffff`, `ffffff`, `#fff` and `fff`.
#### `color:unpack(): number, number, number, number`
Unpacks the color into its R, G, B and A values. Useful for `diffuse`:
```lua
local quad = Quad()
quad:diffuse(col:unpack())
```
#### `color:rgb(): number, number, number`
Returns the color's R, G and B values.
#### `color:hsl(): number, number, number`
Returns the color's H, S and L values in the [HSL color model](https://en.wikipedia.org/wiki/HSL_and_HSV).
#### `color:hsv(): number, number, number`
Returns the color's H, S and V values in the [HSV color model](https://en.wikipedia.org/wiki/HSL_and_HSV).
#### `color:hex(): string`
Returns the color's hex string representation in the format `ffffff`.
#### `color:hue(h: number): color`
Sets the color's hue value in the [HSL/HSV color model](https://en.wikipedia.org/wiki/HSL_and_HSV).
#### `color:huesmooth(h: number): color`
Equivalent to [`color:hue()`](#colorhueh-number-color), except the hue value is smoothed using cubic smoothing. Not accurate, but produces neater-looking color blends for rainbow-shifting colors.
#### `color:alpha(a: number): color`
Sets the color's alpha channel.
#### `color:malpha(a: number): color`
Sets the color's alpha channel, multiplying the previous value with `a`.
#### `color:invert(): color`
Inverts the color.
#### `color:grayscale(): color`
Makes the color grayscale using a more accurate formula than just multiplying every value by `0.5`.
#### `color:hueshift(a: number): color`
Shifts the color's hue by `a`.
#### Operations
Here are all valid operations for colors:
- `color + number`: equal to `color + rgb(number, number, number)`
- `color + color`: adds the colors' R, G and B values together, respectively, forming a new color
- `color - number`: equal to `color - rgb(number, number, number)`
- `color - color`: subtracts the colors' R, G and B values, respectively, forming a new color
- `color * number`: equal to `color * rgb(number, number, number)`
- `color * color`: multiplies the colors' R, G and B values together, respectively, forming a new color
- `color / number`: equal to `color / rgb(number, number, number)`
- `color / color`: divides the colors' R, G and B values, respectively, forming a new color
- `color == color`: checks if the two colors' R, G and B values are equivalent; returns false with any other type
### `input`
`input` is the library that handles everything input-related. Its main feature is providing the `press` and `release` callbacks, but you can also access the raw inputs with the `inputs` table (each value is `-1` if the key is not pressed and the time at which it was pressed, estimated with `t` if it is pressed) and the _raw_ inputs (ignoring callback returns) with `rawInputs`. Additionally, for your convinience, it provides a `directions` enum:
```lua
directions = {
Left = {-1, 0},
Down = {0, 1},
Up = {0, -1},
Right = {1, 0}
}
```
#### A note about keyboard inputs
Working with left/down/up/right inputs can be tiring at times and it's hard to always fit designs to work with them. However, if you're willing to take a little compromise, you can also _access all keyboard inputs_. However, it's worth noting that this **depends on NotITG's Simply Love** (any forks will work fine too) both for your development environment and for all players. That being said, if you want to access the keyboard API, this is how you do it:
```lua
-- check if the user is using simply love at all
if not stitch then error('This modfile requires the Simply Love theme! https://github.com/TaroNuke/Simply-love-NotITG-ver.-') end
keyboard = stitch('lua.keyboard')
-- table that contains every keyboard key as the key and a boolean as the value
local buffer = keyboard.buffer
-- for example:
local isDebugKeyHeld = buffer['F3']
-- contains booleans for shift, ctrl, alt, win and altgr
local special = keyboard.special
local isDebugKeyAndShiftHeld = isDebugKeyHeld and special.shift
```
### `bitop`
A Lua 5.0 port of [bitop-lua](https://github.com/AlberTajuelo/bitop-lua). See their repository for documentation.
### `rng`
A xoshiro128** reimplementation in Lua.
#### `rng.init(seed: number[] | nil): rng`
Initializes a new RNG class. `seed` must be a table of size 4; if it is not provided, `os.time()` is used in its place.
#### `rng(a: number | nil, b: number | nil): number`
Acts identical to `math.random()`. Pass in no arguments to get a random float from 0 to 1, pass in one argument to get a random inclusive integer from 1 to `a`, pass in two arguments to get a random integer from `a` to `b`.
#### `rng:int(min: number, max: number | nil): number`
Generates an inclusive random integer. Pass in one argument to get a random integer from 1 to `a`, pass in two arguments to get a random integer from `a` to `b`.
#### `rng:float(max: number | nil): number`
Generates a random fractional number from `0` to `max`. `max` defaults to 1.
#### `rng:bool(): boolean`
Generates either a `true` or a `false` randomly.
#### `rng:seed(seed: number): void`
Sets the seed and advances the state.
#### `rng:next(): number`
Gets the next pseudo-random value. Recommended to use [`int`](#rngintmin-number-max-number-number), [`float`](#rngfloatmax-number--nil-number), etc. over this.
#### `rng:jump(): void`
The jump function:
> This is the jump function for the generator. It is equivalent
> to 2^64 calls to next(); it can be used to generate 2^64
> non-overlapping subsequences for parallel computations.
#### `rng:longJump(): void`
The long-jump function:
> This is the long-jump function for the generator. It is equivalent to
> 2^96 calls to next(); it can be used to generate 2^32 starting points,
> from each of which jump() will generate 2^32 non-overlapping
> subsequences for parallel distributed computations.
### `uwuify`
```lua
print(uwuify('hello, world!')) --> hewwo, wowwd!
```
_A very important library I don't see enough game engines include in their standard libraries._
## Examples
Here are a couple of examples. All of these are standalone `main.lua` files that you can plug in and view the results of!
### Default Uranium Template code
```lua
-- define a basic quad
local quad = Quad()
quad:xy(scx, scy)
quad:zoomto(120, 120)
quad:diffuse(0.8, 1, 0.7, 1)
quad:skewx(0.2)
-- define a sprite
local sprite = Sprite('../docs/uranium.png')
sprite:xy(scx, scy)
sprite:zoom(0.4)
-- let's add some text aswell
local text = BitmapText('common', 'hello, uranium template!')
text:xy(scx, scy + 100)
-- update gets called every frame
-- dt here refers to deltatime - the time that has passed since the last frame!
function uranium.update(dt)
-- let's rotate our quad
quad:rotationz(t * 80)
-- then shove it to the screen - similar to a drawfunction!
quad:Draw()
-- and you can do this multiple times of course!
quad:zoomto(180, 180)
quad:rotationz(t * 100)
quad:diffusealpha(0.4)
quad:skewx(0.1)
quad:Draw()
-- no need to reset properties - uranium resets all properties that you set upon definition!
-- throw in the logo aswell, because why not
sprite:Draw()
-- for the text, get a rainbow color
local col = rgb(1, 0.4, 0.4):huesmooth(t * 0.6)
text:diffuse(col:unpack()) -- the :unpack() is necessary when passing into :diffuse()
-- wag the text
text:rotationz(math.sin(t * 2) * 10)
text:Draw()
end
```
### Simple platformer base
```lua
-- constants are just those that felt nice to me. this is completely valid to do in gamedev
local DAMPING = 1/9500
local SPEED = 2
local JUMP_FORCE = 32
local GRAVITY = 2.3
local PLAYER_SIZE = 50
local groundY = sh * 0.8
local protagActor = Quad()
protagActor:zoomto(PLAYER_SIZE, PLAYER_SIZE)
local ground = Quad()
ground:zoomto(sw, 4)
ground:xy(scx, groundY + PLAYER_SIZE/2 + 4/2)
local pos = vector(scx, groundY)
local vel = vector(0, 0)
local hasHitGround = true -- let's define this so that you can't jump mid-air
-- called whenever the player recieves an input
function uranium.press(direction)
if direction == 'Up' and hasHitGround then
vel.y = vel.y - JUMP_FORCE
hasHitGround = false
return true -- input eaten! further callbacks won't recieve this
end
end
function uranium.update(dt)
-- respond to l/r inputs
if inputs.Left ~= -1 then
vel.x = vel.x - SPEED
end
if inputs.Right ~= -1 then
vel.x = vel.x + SPEED
end
-- apply gravity
vel.y = vel.y + GRAVITY
-- update position, apply damping to velocity
pos = pos + vel
vel = vel * math.pow(DAMPING, dt)
-- make sure the player can't clip through the ground
if pos.y >= groundY then
pos.y = groundY
if vel.y >= 0 then vel.y = 0 end
hasHitGround = true
end
-- make sure the player can't leave the screen on accident
pos.x = math.min(pos.x, sw - PLAYER_SIZE/2)
pos.x = math.max(pos.x, 0 + PLAYER_SIZE/2)
-- draw them!
protagActor:xy(pos.x, pos.y)
protagActor:Draw()
-- draw the ground
ground:Draw()
end
```
## Credits
**XeroOl** - Mirin Template was a massive design inspiration; early stages of this template borrowed lots of code from it<br>
**Mayflower**, **Aura** - Testing, design help<br>
**mangoafterdawn** - The Uranium Template logo!<br>
<br><br><br>
<center>
<img src="jillo_png.png" alt="You didn't delete the included image of Jillo, did you?" title="jillo :)" height="96px">
</center>

2010
Song.sm Normal file

File diff suppressed because it is too large Load Diff

BIN
docs/uranium.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
docs/uranium_full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
jillo_png.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
silence.ogg Normal file

Binary file not shown.

41
src/main.lua Normal file
View File

@ -0,0 +1,41 @@
-- define a basic quad
local quad = Quad()
quad:xy(scx, scy)
quad:zoomto(120, 120)
quad:diffuse(0.8, 1, 0.7, 1)
quad:skewx(0.2)
-- define a sprite
local sprite = Sprite('../docs/uranium.png')
sprite:xy(scx, scy)
sprite:zoom(0.4)
-- let's add some text aswell
local text = BitmapText('common', 'hello, uranium template!')
text:xy(scx, scy + 100)
-- update gets called every frame
-- dt here refers to deltatime - the time that has passed since the last frame!
function uranium.update(dt)
-- let's rotate our quad
quad:rotationz(t * 80)
-- then shove it to the screen - similar to a drawfunction!
quad:Draw()
-- and you can do this multiple times of course!
quad:zoomto(180, 180)
quad:rotationz(t * 100)
quad:diffusealpha(0.4)
quad:skewx(0.1)
quad:Draw()
-- no need to reset properties - uranium resets all properties that you set upon definition!
-- throw in the logo aswell, because why not
sprite:Draw()
-- for the text, get a rainbow color
local col = rgb(1, 0.4, 0.4):huesmooth(t * 0.6)
text:diffuse(col:unpack()) -- the :unpack() is necessary when passing into :diffuse()
-- wag the text
text:rotationz(math.sin(t * 2) * 10)
text:Draw()
end

6
template/actors.xml Normal file
View File

@ -0,0 +1,6 @@
<ActorFrame InitCommand="%oat._actor.initFrame">
<children>
<Layer Condition="oat._actor.cond()" Type="@oat._actor.type()" File="@oat._actor.file()" Font="@oat._actor.font()" InitCommand="%oat._actor.init"/>
<Layer Condition="oat._actor.next()" File="actors.xml" />
</children>
</ActorFrame>

482
template/main.xml Normal file
View File

@ -0,0 +1,482 @@
<Layer Type="ActorFrame" InitCommand="%function(self)
_G.oat = {}
oat._main = self
setmetatable(oat, {
-- if something isn't found in the table, fall back to a global lookup
__index = _G,
-- handle oat() calls to set the environment
__call = function(self, f)
setfenv(f or 2, self)
return f
end
})
oat()
local function copy(src)
local dest = {}
for k, v in pairs(src) do
dest[k] = v
end
return dest
end
oat.oat = _G.oat
oat.type = _G.type
oat.print = _G.print
oat.pairs = _G.pairs
oat.ipairs = _G.ipairs
oat.unpack = _G.unpack
oat.tonumber = _G.tonumber
oat.tostring = _G.tostring
oat.math = copy(_G.math)
oat.table = copy(_G.table)
oat.string = copy(_G.string)
oat.scx = SCREEN_CENTER_X
oat.scy = SCREEN_CENTER_Y
oat.sw = SCREEN_WIDTH
oat.sh = SCREEN_HEIGHT
oat.dw = DISPLAY:GetDisplayWidth()
oat.dh = DISPLAY:GetDisplayHeight()
local uraniumFunc = {}
function uraniumFunc:call(event, ...)
if self._callbacks[event] then
for _, callback in ipairs(self._callbacks[event]) do
callback(unpack(arg))
end
end
end
local uraniumMeta = {}
function uraniumMeta:__newindex(key, value)
if self._callbacks[key] then
table.insert(self._callbacks[key], value)
else
self._callbacks[key] = {value}
end
end
uraniumMeta.__index = uraniumFunc
uranium = setmetatable({_callbacks = {}}, uraniumMeta)
function backToSongWheel(message)
if message then
SCREENMAN:SystemMessage(message)
print(message)
end
GAMESTATE:FinishSong()
-- disable update_command
self:hidden(1)
end
local luaobj
local globalQueue = {} -- for resetting
local function patchFunction(f, obj)
return function(...)
arg[1] = obj
return f(unpack(arg))
end
end
local function onCommand(self)
uranium:call('init')
end
-- runs once during ScreenReadyCommand, before the user code is loaded
-- hides various actors that are placed by the theme
local function hideThemeActors()
for _, element in ipairs {
'Overlay', 'Underlay',
'ScoreP1', 'ScoreP2',
'LifeP1', 'LifeP2',
} do
local child = SCREENMAN(element)
if child then child:hidden(1) end
end
end
local lastt = GAMESTATE:GetSongTime()
local function screen_ready_command(self)
hideThemeActors()
self:hidden(0)
local errored = false
local firstrun = true
self:addcommand('Update', function()
if errored then
return 0
end
errored = true
t = os.clock()
b = GAMESTATE:GetSongBeat()
local dt = t - lastt
lastt = t
if firstrun then
firstrun = false
dt = 0
self:GetChildren()[2]:hidden(1)
end
for _, q in ipairs(globalQueue) do
local actor = q[1]
local v = q[2]
local func = actor[v[1]]
if not func then
-- uhmmm ??? hm. what do we do??
else
patchFunction(func, actor)(unpack(v[2]))
end
end
uranium:call('update', dt)
errored = false
return 0
end)
self:luaeffect('Update')
end
GAMESTATE:ApplyModifiers('clearall')
local function formatError(file, e)
local _, _, err = string.find(e, 'error loading package `[^`\']+\' %((.+)%)')
return (file and err) and ('error loading \'' .. file .. '\':\n' .. err) or e
end
local songName = GAMESTATE:GetCurrentSong():GetSongDir()
local additionalSongFolders = PREFSMAN:GetPreference('AdditionalSongFolders')
local additionalFolders = PREFSMAN:GetPreference('AdditionalFolders')
local function attemptload(f, filename) -- not to be confused with tryload
local err = ''
local lasterr = ''
local func
local s = '.' .. songName .. f
func, lasterr = loadfile(s)
if func then return func end
if not string.find(lasterr, '\' from path `') then return nil, formatError(filename, lasterr), nil end
err = err .. s .. '\n'
s = f
func, lasterr = loadfile(s)
if func then return func end
if not string.find(lasterr, '\' from path `') then return nil, formatError(filename, lasterr), nil end
err = err .. s .. '\n'
-- cut off 'Songs/' from the path
local _,index = string.find(songName,'Songs/')
local songLoc = string.sub(songName,index)
-- for every songfolder in the additionalsongfolders
if additionalSongFolders and additionalSongFolders ~= '' then
for songFolder in string.gfind(additionalSongFolders,'[^,]+') do
s = songFolder .. songLoc .. f
func, lasterr = loadfile(s)
if func then return func end
err = err .. s .. '\n'
end
end
if additionalFolders and additionalFolders ~= '' then
for folder in string.gfind(additionalFolders,'[^,]+') do
s = folder .. songName .. f
func, lasterr = loadfile(s)
if func then return func end
err = err .. s .. '\n'
end
end
return nil, lasterr, string.sub(err, 0, -2)
end
oat._loadPath = ''
function tryload(f)
local func, err, trace = attemptload(oat._loadPath .. f, f)
if not func then
if trace then
backToSongWheel('finding \'' .. f .. '\' failed, check log for more details')
error('uranium: finding \'' .. f .. '\' failed! tried these paths: ' .. '\n' .. trace, 2)
Trace(trace)
else
error(err, 2)
Trace('loading \'' .. f .. '\' failed!' .. '\n' .. err)
end
else
return oat(func)
end
end
local function getFolderStructure(path)
local folders = {}
for folder in string.gfind(path, '[^/]+') do
table.insert(folders, folder)
end
table.remove(folders, #folders)
return table.concat(folders, '/')
end
oat._requirePath = ''
function require(f)
-- . -> /
f = string.gsub(f, '%.', '/')
-- add .lua
f = f .. '.lua'
local oldpath = oat._requirePath
local folder = getFolderStructure(f)
if folder ~= '' then
oat._requirePath = oat._requirePath .. folder .. '/'
end
local res, c = pcall(tryload, oldpath .. f)
if not res then
error(c, 2)
else
local success, s = pcall(c)
if success then
return s
else
error('uranium: error loading \'' .. f .. '\':\n' .. s, 2)
end
end
oat._requirePath = oldpath
end
-- actors
local actorQueue = {}
local currentActor = nil
oat._actor = {}
function oat._actor.next()
local actor = actorQueue[1]
if actor then
table.remove(actorQueue, 1)
currentActor = actor
return true
else
return false
end
end
function oat._actor.cond()
return currentActor ~= nil
end
function oat._actor.type()
return currentActor.type
end
function oat._actor.file()
return currentActor.file
end
function oat._actor.font()
return currentActor.font
end
function oat._actor.init(self)
currentActor.init(self)
end
function oat._actor.initFrame(self)
self:SetDrawFunction(function() end)
end
local function createProxyActor(name)
local queue = {}
local initCommands = {}
local lockedActor
local patchedFunctions = {}
return setmetatable({}, {
__index = function(self, key)
if lockedActor then
local val = lockedActor[key]
if type(val) == 'function' then
if not patchedFunctions[val] then
patchedFunctions[val] = patchFunction(val, lockedActor)
end
return patchedFunctions[val]
end
return val
end
if key == '__lock' then
return function(actor)
for _, v in ipairs(queue) do
local func = actor[v[1]]
if not func then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
'you\'re calling a function \'' .. v[1] .. '\' on a ' .. name .. ' which doesn\'t exist!:\n'
)
else
local success, result = pcall(function()
patchFunction(func, actor)(unpack(v[2]))
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
end
-- now that we know there's no poisonous methods in queue, let's offload them
for _, v in ipairs(queue) do
table.insert(globalQueue, {actor, v})
end
-- let's also properly route everything from the proxied actor to the actual actor
lockedActor = actor
-- and now let's run the initcommands
for _, c in ipairs(initCommands) do
local func = c[1]
local success, result = pcall(function()
func(actor)
end)
if not success then
error(
'uranium: error on \'' .. name .. '\' InitCommand defined on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
result
)
end
end
initCommands = {}
end
else
return function(...)
if key == 'addcommand' and arg[2] == 'Init' then
table.insert(initCommands, {arg[3], debug.getinfo(2, 'Sl')})
else
table.insert(queue, {key, arg, debug.getinfo(2, 'Sl')})
end
end
end
end,
__newindex = function()
error('uranium: cannot set properties on actors!', 2)
end,
__tostring = function() return name end,
__name = name
})
end
local function createGenericFunc(type)
return function()
local actor = createProxyActor(type)
table.insert(actorQueue, {
type = type,
file = nil,
init = function(a)
actor.__lock(a)
end
})
return actor
end
end
Quad = createGenericFunc('Quad')
ActorProxy = createGenericFunc('ActorProxy')
Polygon = createGenericFunc('Polygon')
function Sprite(file)
if not file then error('uranium: cannot create a Sprite without a file', 2) end
local actor = createProxyActor('Sprite')
table.insert(actorQueue, {
type = nil,
file = '../src/' .. oat._requirePath .. file,
init = function(a)
actor.__lock(a)
end
})
return actor
end
function Model(file)
if not file then error('uranium: cannot create a Model without a file', 2) end
local actor = createProxyActor('Model')
table.insert(actorQueue, {
type = nil,
file = '../src/' .. oat._requirePath .. file,
init = function(a)
actor.__lock(a)
end
})
return actor
end
function BitmapText(font, text)
local actor = createProxyActor('BitmapText')
table.insert(actorQueue, {
type = 'BitmapText',
font = font and ('../src/' .. oat._requirePath .. font) or 'common',
init = function(a)
if text then a:settext(text) end
actor.__lock(a)
end
})
return actor
end
function ActorSound(file)
if not file then error('uranium: cannot create an ActorSound without a file', 2) end
local actor = createProxyActor('ActorSound')
table.insert(actorQueue, {
type = 'ActorSound',
file = '../src/' .. oat._requirePath .. file,
init = function(a)
actor.__lock(a)
end
})
return actor
end
oat._loadPath = 'template/stdlib/'
oat.tryload('index')()
oat._loadPath = 'src/'
lua = oat.tryload('main')
if lua then
local success, result = pcall(lua)
if success then
luaobj = result
self:addcommand('On', onCommand)
self:addcommand('Ready', screen_ready_command)
self:queuecommand('Ready')
else
Trace('got an error loading main.lua!')
Trace(result)
backToSongWheel('loading .lua file failed, check log for details')
error('uranium: loading main.lua file failed:\n' .. result)
end
else
Trace('luaobj doesnt exist despite passing checks. thats VERY odd')
backToSongWheel('uranium: loading .lua file failed, check log for details')
error('loading main.lua file failed: \'lua\' variable doesn\'t exist despite passing checks')
end
-- NotITG and OpenITG have a long standing bug where the InitCommand on an actor can run twice in certain cases.
-- By removing the command after it's done, it can only ever run once
self:removecommand('Init')
end"><children>
<Layer Condition="oat._actor.next()" File="actors.xml"/>
<Layer Type="Quad" InitCommand="xywh,SCREEN_CENTER_X,SCREEN_CENTER_Y,SCREEN_WIDTH,SCREEN_HEIGHT;diffuse,#000000;sleep,9e9"/>
</children></Layer>

343
template/stdlib/bitop.lua Normal file
View File

@ -0,0 +1,343 @@
local M = {_TYPE='module', _NAME='bitop.funcs', _VERSION='1.0-0'}
local floor = math.floor
local MOD = math.pow(2, 32)
local MODM = MOD-1
local function memoize(f)
local mt = {}
local t = setmetatable({}, mt)
function mt:__index(k)
local v = f(k)
t[k] = v
return v
end
return t
end
local function make_bitop_uncached(t, m)
local function bitop(a, b)
local res,p = 0,1
while a ~= 0 and b ~= 0 do
local am, bm = a%m, b%m
res = res + t[am][bm]*p
a = (a - am) / m
b = (b - bm) / m
p = p*m
end
res = res + (a+b) * p
return res
end
return bitop
end
local function make_bitop(t)
local op1 = make_bitop_uncached(t, math.pow(2, 1))
local op2 = memoize(function(a)
return memoize(function(b)
return op1(a, b)
end)
end)
return make_bitop_uncached(op2, math.pow(2, (t.n or 1)))
end
-- ok? probably not if running on a 32-bit int Lua number type platform
function M.tobit(x)
return x % math.pow(2, 32)
end
M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4}
local bxor = M.bxor
function M.bnot(a) return MODM - a end
local bnot = M.bnot
function M.band(a,b) return ((a+b) - bxor(a,b))/2 end
local band = M.band
function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end
local bor = M.bor
local lshift, rshift -- forward declare
function M.rshift(a,disp) -- Lua5.2 insipred
if disp < 0 then return lshift(a,-disp) end
return floor(a % math.pow(2, 32) / math.pow(2, disp))
end
rshift = M.rshift
function M.lshift(a,disp) -- Lua5.2 inspired
if disp < 0 then return rshift(a,-disp) end
return (a * math.pow(2, disp)) % math.pow(2, 32)
end
lshift = M.lshift
function M.tohex(x, n) -- BitOp style
n = n or 8
local up
if n <= 0 then
if n == 0 then return '' end
up = true
n = - n
end
x = band(x, math.pow(16, n-1))
return ('%0'..n..(up and 'X' or 'x')):format(x)
end
local tohex = M.tohex
function M.extract(n, field, width) -- Lua5.2 inspired
width = width or 1
return band(rshift(n, field), math.pow(2, width-1))
end
local extract = M.extract
function M.replace(n, v, field, width) -- Lua5.2 inspired
width = width or 1
local mask1 = math.pow(2, width-1)
v = band(v, mask1) -- required by spec?
local mask = bnot(lshift(mask1, field))
return band(n, mask) + lshift(v, field)
end
local replace = M.replace
function M.bswap(x) -- BitOp style
local a = band(x, 0xff); x = rshift(x, 8)
local b = band(x, 0xff); x = rshift(x, 8)
local c = band(x, 0xff); x = rshift(x, 8)
local d = band(x, 0xff)
return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d
end
local bswap = M.bswap
function M.rrotate(x, disp) -- Lua5.2 inspired
disp = disp % 32
local low = band(x, math.pow(2, disp-1))
return rshift(x, disp) + lshift(low, 32-disp)
end
local rrotate = M.rrotate
function M.lrotate(x, disp) -- Lua5.2 inspired
return rrotate(x, -disp)
end
local lrotate = M.lrotate
M.rol = M.lrotate -- LuaOp inspired
M.ror = M.rrotate -- LuaOp insipred
function M.arshift(x, disp) -- Lua5.2 inspired
local z = rshift(x, disp)
if x >= 0x80000000 then z = z + lshift(math.pow(2, disp-1), 32-disp) end
return z
end
local arshift = M.arshift
function M.btest(x, y) -- Lua5.2 inspired
return band(x, y) ~= 0
end
--
-- Start Lua 5.2 "bit32" compat section.
--
M.bit32 = {} -- Lua 5.2 'bit32' compatibility
local function bit32_bnot(x)
return (-1 - x) % MOD
end
M.bit32.bnot = bit32_bnot
-- something here causes a syntax error so im just commenting out since i dont need it anyways
--[[
local function bit32_bxor(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = bxor(a, b)
if c then
z = bit32_bxor(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return 0
end
end
M.bit32.bxor = bit32_bxor
local function bit32_band(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = ((a+b) - bxor(a,b)) / 2
if c then
z = bit32_band(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return MODM
end
end
M.bit32.band = bit32_band
local function bit32_bor(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = MODM - band(MODM - a, MODM - b)
if c then
z = bit32_bor(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return 0
end
end
M.bit32.bor = bit32_bor
function M.bit32.btest(...)
return bit32_band(...) ~= 0
end
function M.bit32.lrotate(x, disp)
return lrotate(x % MOD, disp)
end
function M.bit32.rrotate(x, disp)
return rrotate(x % MOD, disp)
end
function M.bit32.lshift(x,disp)
if disp > 31 or disp < -31 then return 0 end
return lshift(x % MOD, disp)
end
function M.bit32.rshift(x,disp)
if disp > 31 or disp < -31 then return 0 end
return rshift(x % MOD, disp)
end
function M.bit32.arshift(x,disp)
x = x % MOD
if disp >= 0 then
if disp > 31 then
return (x >= 0x80000000) and MODM or 0
else
local z = rshift(x, disp)
if x >= 0x80000000 then z = z + lshift(math.pow(2, disp-1), 32-disp) end
return z
end
else
return lshift(x, -disp)
end
end
function M.bit32.extract(x, field, ...)
local width = ... or 1
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
x = x % MOD
return extract(x, field, ...)
end
function M.bit32.replace(x, v, field, ...)
local width = ... or 1
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
x = x % MOD
v = v % MOD
return replace(x, v, field, ...)
end
--
-- Start LuaBitOp "bit" compat section.
--
M.bit = {} -- LuaBitOp "bit" compatibility
function M.bit.tobit(x)
x = x % MOD
if x >= 0x80000000 then x = x - MOD end
return x
end
local bit_tobit = M.bit.tobit
function M.bit.tohex(x, ...)
return tohex(x % MOD, ...)
end
function M.bit.bnot(x)
return bit_tobit(bnot(x % MOD))
end
local function bit_bor(a, b, c, ...)
if c then
return bit_bor(bit_bor(a, b), c, ...)
elseif b then
return bit_tobit(bor(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.bor = bit_bor
local function bit_band(a, b, c, ...)
if c then
return bit_band(bit_band(a, b), c, ...)
elseif b then
return bit_tobit(band(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.band = bit_band
local function bit_bxor(a, b, c, ...)
if c then
return bit_bxor(bit_bxor(a, b), c, ...)
elseif b then
return bit_tobit(bxor(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.bxor = bit_bxor
function M.bit.lshift(x, n)
return bit_tobit(lshift(x % MOD, n % 32))
end
function M.bit.rshift(x, n)
return bit_tobit(rshift(x % MOD, n % 32))
end
function M.bit.arshift(x, n)
return bit_tobit(arshift(x % MOD, n % 32))
end
function M.bit.rol(x, n)
return bit_tobit(lrotate(x % MOD, n % 32))
end
function M.bit.ror(x, n)
return bit_tobit(rrotate(x % MOD, n % 32))
end
function M.bit.bswap(x)
return bit_tobit(bswap(x % MOD))
end
]]
return M

299
template/stdlib/color.lua Normal file
View File

@ -0,0 +1,299 @@
--[[
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 1].
]]
local function hslToRgb(h, s, l)
local r, g, b
if s == 0 then
r, g, b = l, l, l -- achromatic
else
function hue2rgb(p, q, t)
if t < 0 then t = t + 1 end
if t > 1 then t = t - 1 end
if t < 1/6 then return p + (q - p) * 6 * t end
if t < 1/2 then return q end
if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end
return p
end
local q
if l < 0.5 then q = l * (1 + s) else q = l + s - l * s end
local p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
end
return r, g, b
end
--[[
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 1].
]]
local function rgbToHsl(r, g, b)
local max, min = math.max(r, g, b), math.min(r, g, b)
local h, s, l
l = (max + min) / 2
if max == 0 then s = 0 else s = (max - min) / max end
if max == min then
h, s = 0, 0 -- achromatic
else
local d = max - min
local s
if l > 0.5 then s = d / (2 - max - min) else s = d / (max + min) end
if max == r then
h = (g - b) / d
if g < b then h = h + 6 end
elseif max == g then h = (b - r) / d + 2
elseif max == b then h = (r - g) / d + 4
end
h = h / 6
end
return h, s, l
end
--[[
* Converts an RGB color value to HSV. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
* Assumes r, g, and b are contained in the set [0, 1] and
* returns h, s, and v in the set [0, 1].
]]
local function rgbToHsv(r, g, b)
local max, min = math.max(r, g, b), math.min(r, g, b)
local h, s, v
v = max
local d = max - min
if max == 0 then s = 0 else s = d / max end
if max == min then
h = 0 -- achromatic
else
if max == r then
h = (g - b) / d
if g < b then h = h + 6 end
elseif max == g then h = (b - r) / d + 2
elseif max == b then h = (r - g) / d + 4
end
h = h / 6
end
return h, s, v
end
--[[
* Converts an HSV color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSV_color_space.
* Assumes h, s, and v are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 1].
]]
local function hsvToRgb(h, s, v)
local r, g, b
local i = math.floor(h * 6);
local f = h * 6 - i;
local p = v * (1 - s);
local q = v * (1 - f * s);
local t = v * (1 - (1 - f) * s);
i = i % 6
if i == 0 then r, g, b = v, t, p
elseif i == 1 then r, g, b = q, v, p
elseif i == 2 then r, g, b = p, v, t
elseif i == 3 then r, g, b = p, q, v
elseif i == 4 then r, g, b = t, p, v
elseif i == 5 then r, g, b = v, p, q
end
return r, g, b
end
---@class color
---@field r number @red, 0.0 - 1.0
---@field g number @green, 0.0 - 1.0
---@field b number @blue, 0.0 - 1.0
---@field a number @alpha, 0.0 - 1.0
---@operator add(color): color
---@operator add(number): color
---@operator sub(color): color
---@operator sub(number): color
---@operator mul(color): color
---@operator mul(number): color
---@operator div(color): color
---@operator div(number): color
local col = {}
--- for use in actor:diffuse(col:unpack())
---@return number, number, number, number
function col:unpack()
return self.r, self.g, self.b, self.a
end
-- conversions
---@return number, number, number
function col:rgb()
return self.r, self.g, self.b
end
---@return number, number, number
function col:hsl()
return rgbToHsl(self.r, self.g, self.b)
end
---@return number, number, number
function col:hsv()
return rgbToHsv(self.r, self.g, self.b)
end
---@return string
function col:hex()
return string.format('%02x%02x%02x',
math.floor(self.r * 255),
math.floor(self.g * 255),
math.floor(self.b * 255))
end
-- setters
---@return color
function col:hue(h)
local _, s, v = self:hsv()
return hsv(h % 1, s, v, self.a)
end
---@return color
function col:huesmooth(h)
local _, s, v = self:hsv()
return shsv(h % 1, s, v, self.a)
end
---@return color
function col:alpha(a)
return rgb(self.r, self.g, self.b, a)
end
--- multiplies current alpha by provided value
---@return color
function col:malpha(a)
return rgb(self.r, self.g, self.b, self.a * a)
end
-- effects
---@return color
function col:invert()
return rgb(1 - self.r, 1 - self.g, 1 - self.b, self.a)
end
---@return color
function col:grayscale()
return rgb(self.r * 0.299 + self.g * 0.587 + self.b * 0.114, self.a)
end
---@return color
function col:hueshift(a)
local h, s, v = self:hsv()
return hsv((h + a) % 1, s, v, self.a)
end
local colmeta = {}
function colmeta:__index(i)
if i == 1 then return self.r end
if i == 2 then return self.g end
if i == 3 then return self.b end
if i == 4 then return self.a end
return col[i]
end
local function typ(a)
return (type(a) == 'table' and a.r and a.g and a.b and a.a) and 'color' or type(a)
end
local function genericop(a, b, f, name)
local typea = typ(a)
local typeb = typ(b)
if typea == 'number' then
return rgb(f(b.r, a), f(b.g, a), f(b.b, a), b.a)
elseif typeb == 'number' then
return rgb(f(a.r, b), f(a.g, b), f(a.b, b), a.a)
elseif typea == 'color' and typeb == 'color' then
return rgb(f(a.r, b.r), f(a.g, b.g), f(a.b, b.b), f(a.a, b.a))
end
error('cant apply ' .. name .. ' to ' .. typea .. ' and ' .. typeb, 3)
end
function colmeta.__add(a, b)
return genericop(a, b, function(a, b) return a + b end, 'add')
end
function colmeta.__sub(a, b)
return genericop(a, b, function(a, b) return a - b end, 'sub')
end
function colmeta.__mul(a, b)
return genericop(a, b, function(a, b) return a * b end, 'mul')
end
function colmeta.__div(a, b)
return genericop(a, b, function(a, b) return a / b end, 'div')
end
function colmeta.__eq(a, b)
return (typ(a) == 'color' and typ(b) == 'color') and (a.r == b.r and a.g == b.g and a.b == b.b and a.a == b.a)
end
function colmeta:__tostring()
return '#' .. self:hex()
end
colmeta.__name = 'color'
-- constructors
---@return color
function rgb(r, g, b, a)
a = a or 1
return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta)
end
---@return color
function hsl(h, s, l, a)
a = a or 1
local r, g, b = hslToRgb(h % 1, s, l)
return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta)
end
---@return color
function hsv(h, s, v, a)
a = a or 1
local r, g, b = hsvToRgb(h % 1, s, v)
return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta)
end
--- smoother hsv. not correct but looks nicer
---@return color
function shsv(h, s, v, a)
return hsv(h * h * (3 - 2 * h), s, v, a)
end
---@param hex string
---@return color
function hex(hex)
hex = string.gsub(hex, '#', '')
if string.len(hex) == 3 then
return rgb((tonumber('0x' .. string.sub(hex, 1, 1)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 2, 2)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 3, 3)) * 17) / 255)
else
return rgb(tonumber('0x' .. string.sub(hex, 1, 2)) / 255, tonumber('0x' .. string.sub(hex, 3, 4)) / 255, tonumber('0x' .. string.sub(hex, 5, 6)) / 255)
end
end

View File

@ -0,0 +1,80 @@
---@class easable
---@field public a number @the eased value
---@field public toa number @the target, uneased value
---@field protected onUpdateFuncs fun():nil[]
local eas = {}
---@param new number @New value to ease to
---@return void
function eas:set(new)
self.toa = new
end
---@param new number @New value
---@return void
function eas:reset(new)
self.toa = new
self.a = new
end
---@param new number @How much to add to current value to ease to
---@return void
function eas:add(new)
self.toa = self.toa + new
end
---@param func fun(a: number):void @Adds a callback function that will run each time the eased value changes
---@return void
function eas:onUpdate(func)
table.insert(self.onUpdateFuncs, func)
end
local easmeta = {}
easmeta.__index = eas
easmeta.__name = 'easable'
function easmeta.__add(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) + ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__sub(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) - ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mul(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) * ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__div(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) / ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mod(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) % ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__eq(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) == ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__lt(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) < ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__le(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) <= ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta:__call(dt)
self.a = mix(self.a, self.toa, dt)
for _, callback in ipairs(self.onUpdateFuncs) do
callback(self.a)
end
end
function easmeta:__tostring()
return tostring(self.a)
end
function easmeta:__unm(self)
return -self.a
end
---@param default number
---@return easable
function easable(default)
default = default or 0
return setmetatable({a = default, toa = default, onUpdateFuncs = {}}, easmeta)
end

View File

@ -0,0 +1,90 @@
---@class easable2
---@field public a number @the eased value
---@field public toa number @the target, uneased value
---@field public ease fun(a:number):number @the ease to use
---@field protected onUpdateFuncs fun(a:number):nil[]
---@field protected _a number @the internal value, linearly eased
local eas = {}
---@param new number @New value to ease to
---@return void
function eas:set(new)
self.toa = new
end
---@param new number @New value
---@return void
function eas:reset(new)
self.toa = new
self._a = new
end
---@param new number @How much to add to current value to ease to
---@return void
function eas:add(new)
self.toa = self.toa + new
end
---@param func fun(a: number):void @Adds a callback function that will run each time the eased value changes
---@return void
function eas:onUpdate(func)
table.insert(self.onUpdateFuncs, func)
end
local easmeta = {}
easmeta.__index = eas
easmeta.__name = 'easable2'
function easmeta.__add(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) + ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__sub(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) - ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mul(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) * ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__div(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) / ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__mod(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) % ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__eq(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) == ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__lt(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) < ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta.__le(a, b)
return ((type(a) == 'table' and a.a) and a.a or a) <= ((type(b) == 'table' and b.a) and b.a or b)
end
function easmeta:__call(dt)
if self._a == self.toa then
-- do nothing
elseif self._a < self.toa then
self._a = self._a + math.min(dt, math.abs(self._a - self.toa))
else
self._a = self._a - math.min(dt, math.abs(self._a - self.toa))
end
self.a = self.ease(self._a)
for _, callback in ipairs(self.onUpdateFuncs) do
callback(self.a)
end
end
function easmeta:__tostring()
return tostring(self.a)
end
function easmeta:__unm(self)
return -self.a
end
---@param default number
---@return easable2
function easable2(default, ease)
default = default or 0
return setmetatable({a = default, toa = default, onUpdateFuncs = {}, ease = ease or outSine, _a = default}, easmeta)
end

338
template/stdlib/ease.lua Normal file
View File

@ -0,0 +1,338 @@
-- nabbed straight from mirin template:
-- https://github.com/XeroOl/notitg-mirin/blob/d1e9a8e71026aeabe81c682a114ce265cbd6362a/template/ease.lua
local sqrt = math.sqrt
local sin = math.sin
local asin = math.asin
local cos = math.cos
local pow = math.pow
local exp = math.exp
local pi = math.pi
local abs = math.abs
-- ===================================================================== --
-- Utility functions
--- Flip any easing function, making it go from 1 to 0
-- Example use:
-- ```lua
-- ease {0, 20, flip(outQuad), 50, 'modname'}
-- ```
flip = setmetatable({}, {
__call = function(self, fn)
self[fn] = self[fn] or function(x) return 1 - fn(x) end
return self[fn]
end
})
-- Mix two easing functions together into a new ease
-- the new ease starts by acting like the first argument, and then ends like the second argument
-- Example: ease {0, 20, blendease(inQuad, outQuad), 100, 'modname'}
blendease = setmetatable({}, {
__index = function(self, key)
self[key] = {}
return self[key]
end,
__call = function(self, fn1, fn2)
if not self[fn1][fn2] then
local transient1 = fn1(1) <= 0.5
local transient2 = fn2(1) <= 0.5
if transient1 and not transient2 then
error('blendease: the first argument is a transient ease, but the second argument doesn\'t match')
end
if transient2 and not transient1 then
error('blendease: the second argument is a transient ease, but the first argument doesn\'t match')
end
self[fn1][fn2] = function(x)
local mixFactor = 3*x^2-2*x^3
return (1 - mixFactor) * fn1(x) + mixFactor * fn2(x)
end
end
return self[fn1][fn2]
end
})
local function param1cache(self, param1)
self.cache[param1] = self.cache[param1] or function(x)
return self.fn(x, param1)
end
return self.cache[param1]
end
local param1mt = {
__call = function(self, x, param1)
return self.fn(x, param1 or self.dp1)
end,
__index = {
param = param1cache,
params = param1cache,
}
}
-- Declare an easing function taking one custom parameter
function with1param(fn, defaultparam1)
return setmetatable({
fn = fn,
dp1 = defaultparam1,
cache = {},
}, param1mt)
end
local function param2cache(self, param1, param2)
self.cache[param1] = self.cache[param1] or {}
self.cache[param1][param2] = self.cache[param1][param2] or function(x)
return self.fn(x, param1, param2)
end
return self.cache[param1][param2]
end
local param2mt = {
__call = function(self, x, param1, param2)
return self.fn(x, param1 or self.dp1, param2 or self.dp2)
end,
__index = {
param=param2cache,
params=param2cache,
}
}
-- Declare an easing function taking two custom parameters
function with2params(fn, defaultparam1, defaultparam2)
return setmetatable({
fn = fn,
dp1 = defaultparam1,
dp2 = defaultparam2,
cache = {},
}, param2mt)
end
-- ===================================================================== --
-- Easing functions
function bounce(t) return 4 * t * (1 - t) end
function tri(t) return 1 - abs(2 * t - 1) end
function bell(t) return inOutQuint(tri(t)) end
function pop(t) return 3.5 * (1 - t) * (1 - t) * sqrt(t) end
function tap(t) return 3.5 * t * t * sqrt(1 - t) end
function pulse(t) return t < .5 and tap(t * 2) or -pop(t * 2 - 1) end
function spike(t) return exp(-10 * abs(2 * t - 1)) end
function inverse(t) return t * t * (1 - t) * (1 - t) / (0.5 - t) end
local function popElasticInternal(t, damp, count)
return (1000 ^ -(t ^ damp) - 0.001) * sin(count * pi * t)
end
local function tapElasticInternal(t, damp, count)
return (1000 ^ -((1 - t) ^ damp) - 0.001) * sin(count * pi * (1 - t))
end
local function pulseElasticInternal(t, damp, count)
if t < .5 then
return tapElasticInternal(t * 2, damp, count)
else
return -popElasticInternal(t * 2 - 1, damp, count)
end
end
popElastic = with2params(popElasticInternal, 1.4, 6)
tapElastic = with2params(tapElasticInternal, 1.4, 6)
pulseElastic = with2params(pulseElasticInternal, 1.4, 6)
impulse = with1param(function(t, damp)
t = t ^ damp
return t * (1000 ^ -t - 0.001) * 18.6
end, 0.9)
function instant() return 1 end
function linear(t) return t end
function inQuad(t) return t * t end
function outQuad(t) return -t * (t - 2) end
function inOutQuad(t)
t = t * 2
if t < 1 then
return 0.5 * t ^ 2
else
return 1 - 0.5 * (2 - t) ^ 2
end
end
function outInQuad(t)
t = t * 2
if t < 1 then
return 0.5 - 0.5 * (1 - t) ^ 2
else
return 0.5 + 0.5 * (t - 1) ^ 2
end
end
function inCubic(t) return t * t * t end
function outCubic(t) return 1 - (1 - t) ^ 3 end
function inOutCubic(t)
t = t * 2
if t < 1 then
return 0.5 * t ^ 3
else
return 1 - 0.5 * (2 - t) ^ 3
end
end
function outInCubic(t)
t = t * 2
if t < 1 then
return 0.5 - 0.5 * (1 - t) ^ 3
else
return 0.5 + 0.5 * (t - 1) ^ 3
end
end
function inQuart(t) return t * t * t * t end
function outQuart(t) return 1 - (1 - t) ^ 4 end
function inOutQuart(t)
t = t * 2
if t < 1 then
return 0.5 * t ^ 4
else
return 1 - 0.5 * (2 - t) ^ 4
end
end
function outInQuart(t)
t = t * 2
if t < 1 then
return 0.5 - 0.5 * (1 - t) ^ 4
else
return 0.5 + 0.5 * (t - 1) ^ 4
end
end
function inQuint(t) return t ^ 5 end
function outQuint(t) return 1 - (1 - t) ^ 5 end
function inOutQuint(t)
t = t * 2
if t < 1 then
return 0.5 * t ^ 5
else
return 1 - 0.5 * (2 - t) ^ 5
end
end
function outInQuint(t)
t = t * 2
if t < 1 then
return 0.5 - 0.5 * (1 - t) ^ 5
else
return 0.5 + 0.5 * (t - 1) ^ 5
end
end
function inExpo(t) return 1000 ^ (t - 1) - 0.001 end
function outExpo(t) return 1.001 - 1000 ^ -t end
function inOutExpo(t)
t = t * 2
if t < 1 then
return 0.5 * 1000 ^ (t - 1) - 0.0005
else
return 1.0005 - 0.5 * 1000 ^ (1 - t)
end
end
function outInExpo(t)
if t < 0.5 then
return outExpo(t * 2) * 0.5
else
return inExpo(t * 2 - 1) * 0.5 + 0.5
end
end
function inCirc(t) return 1 - sqrt(1 - t * t) end
function outCirc(t) return sqrt(-t * t + 2 * t) end
function inOutCirc(t)
t = t * 2
if t < 1 then
return 0.5 - 0.5 * sqrt(1 - t * t)
else
t = t - 2
return 0.5 + 0.5 * sqrt(1 - t * t)
end
end
function outInCirc(t)
if t < 0.5 then
return outCirc(t * 2) * 0.5
else
return inCirc(t * 2 - 1) * 0.5 + 0.5
end
end
function outBounce(t)
if t < 1 / 2.75 then
return 7.5625 * t * t
elseif t < 2 / 2.75 then
t = t - 1.5 / 2.75
return 7.5625 * t * t + 0.75
elseif t < 2.5 / 2.75 then
t = t - 2.25 / 2.75
return 7.5625 * t * t + 0.9375
else
t = t - 2.625 / 2.75
return 7.5625 * t * t + 0.984375
end
end
function inBounce(t) return 1 - outBounce(1 - t) end
function inOutBounce(t)
if t < 0.5 then
return inBounce(t * 2) * 0.5
else
return outBounce(t * 2 - 1) * 0.5 + 0.5
end
end
function outInBounce(t)
if t < 0.5 then
return outBounce(t * 2) * 0.5
else
return inBounce(t * 2 - 1) * 0.5 + 0.5
end
end
function inSine(x) return 1 - cos(x * (pi * 0.5)) end
function outSine(x) return sin(x * (pi * 0.5)) end
function inOutSine(x)
return 0.5 - 0.5 * cos(x * pi)
end
function outInSine(t)
if t < 0.5 then
return outSine(t * 2) * 0.5
else
return inSine(t * 2 - 1) * 0.5 + 0.5
end
end
function outElasticInternal(t, a, p)
return a * pow(2, -10 * t) * sin((t - p / (2 * pi) * asin(1/a)) * 2 * pi / p) + 1
end
local function inElasticInternal(t, a, p)
return 1 - outElasticInternal(1 - t, a, p)
end
function inOutElasticInternal(t, a, p)
return t < 0.5
and 0.5 * inElasticInternal(t * 2, a, p)
or 0.5 + 0.5 * outElasticInternal(t * 2 - 1, a, p)
end
function outInElasticInternal(t, a, p)
return t < 0.5
and 0.5 * outElasticInternal(t * 2, a, p)
or 0.5 + 0.5 * inElasticInternal(t * 2 - 1, a, p)
end
inElastic = with2params(inElasticInternal, 1, 0.3)
outElastic = with2params(outElasticInternal, 1, 0.3)
inOutElastic = with2params(inOutElasticInternal, 1, 0.3)
outInElastic = with2params(outInElasticInternal, 1, 0.3)
function inBackInternal(t, a) return t * t * (a * t + t - a) end
function outBackInternal(t, a) t = t - 1 return t * t * ((a + 1) * t + a) + 1 end
function inOutBackInternal(t, a)
return t < 0.5
and 0.5 * inBackInternal(t * 2, a)
or 0.5 + 0.5 * outBackInternal(t * 2 - 1, a)
end
function outInBackInternal(t, a)
return t < 0.5
and 0.5 * outBackInternal(t * 2, a)
or 0.5 + 0.5 * inBackInternal(t * 2 - 1, a)
end
inBack = with1param(inBackInternal, 1.70158)
outBack = with1param(outBackInternal, 1.70158)
inOutBack = with1param(inOutBackInternal, 1.70158)
outInBack = with1param(outInBackInternal, 1.70158)

10
template/stdlib/index.lua Normal file
View File

@ -0,0 +1,10 @@
require('input')
bitop = require('bitop') -- TODO: tons of this is commented out because of '...'. FIX. IT.
require('rng')
require('easable')
require('easable2')
require('color')
require('vector2D')
require('ease')
uwuify = require('uwuify')
require('util')

39
template/stdlib/input.lua Normal file
View File

@ -0,0 +1,39 @@
inputs = { -- -1 for not pressed, time for time of press
Left = -1,
Down = -1,
Up = -1,
Right = -1
}
rawInputs = {
Left = -1,
Down = -1,
Up = -1,
Right = -1
}
directions = {
Left = {-1, 0},
Down = {0, 1},
Up = {0, -1},
Right = {1, 0}
}
function uranium.init()
for pn = 1, 2 do
for i,j in ipairs({'Left', 'Down', 'Up', 'Right'}) do
local i = i -- lua scope funnies
local j = j
_main:addcommand('StepP' .. pn .. j .. 'PressMessage', function()
rawInputs[j] = t
if uranium:call('press', j) then return end
inputs[j] = t
end)
_main:addcommand('StepP' .. pn .. j .. 'LiftMessage', function()
if uranium:call('release', j) then return end
inputs[j] = -1
rawInputs[j] = -1
end)
end
end
end

172
template/stdlib/rng.lua Normal file
View File

@ -0,0 +1,172 @@
-- xoshiro128** 1.1 by David Blackman and Sebastiano Vigna (vigna@acm.org) https://prng.di.unimi.it/xoshiro128starstar.c
-- Lua implementation by Jill "oatmealine" Monoids
-- Licensed under CC-BY-SA
local RAND_MAX = 4294967295
---@param x int
---@param k int
local function rotl(x, k)
return bitop.bor(bitop.lshift(x, k), bitop.rshift(x, (32 - k)))
end
---@param state int[] @array of size 4; will be mutated
local function next(state)
local result = rotl(state[2] * 5, 7) * 9
local t = bitop.lshift(state[2], 9)
state[3] = bitop.bxor(state[3], state[1])
state[4] = bitop.bxor(state[4], state[2])
state[2] = bitop.bxor(state[2], state[3])
state[1] = bitop.bxor(state[1], state[4])
state[2] = bitop.bxor(state[3], t)
state[3] = rotl(state[4], 11)
return result
end
local JUMP = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }
---@param state int[] @array of size 4; will be mutated
local function jump(state)
local s0 = 0
local s1 = 0
local s2 = 0
local s3 = 0
for _, j in ipairs(JUMP) do
for b = 0, 31 do
if bitop.band(j, bitop.lshift(1, b)) ~= 0 then
s0 = bitop.bxor(s0, state[1])
s1 = bitop.bxor(s1, state[2])
s2 = bitop.bxor(s2, state[3])
s3 = bitop.bxor(s3, state[4])
end
next(state)
end
end
state[1] = s0
state[2] = s1
state[3] = s2
state[4] = s3
end
local LONG_JUMP = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 }
---@param state int[] @array of size 4; will be mutated
local function long_jump(state)
local s0 = 0
local s1 = 0
local s2 = 0
local s3 = 0
for _, j in ipairs(LONG_JUMP) do
for b = 0, 31 do
if bitop.band(j, bitop.lshift(1, b)) ~= 0 then
s0 = bitop.bxor(s0, state[1])
s1 = bitop.bxor(s1, state[2])
s2 = bitop.bxor(s2, state[3])
s3 = bitop.bxor(s3, state[4])
end
next(state)
end
end
state[1] = s0
state[2] = s1
state[3] = s2
state[4] = s3
end
---@class rng a xoshiro128** pseudorandom implementation
---@field public state int[] the current state, size of 4
rng = {}
--- gets the next pseudo-random value; recommended to use abstractions (like __call) over this
---@return int
function rng:next()
return next(self.state)
end
--- This is the jump function for the generator. It is equivalent
--- to 2^64 calls to next(); it can be used to generate 2^64
--- non-overlapping subsequences for parallel computations.
---@return void
function rng:jump()
return jump(self.state)
end
--- This is the long-jump function for the generator. It is equivalent to
--- 2^96 calls to next(); it can be used to generate 2^32 starting points,
--- from each of which jump() will generate 2^32 non-overlapping
--- subsequences for parallel distributed computations.
---@return void
function rng:longJump()
return long_jump(self.state)
end
--- if `max` is not given, `min` will be used as the maximum and the minimum will be 1
--- if min is 1 and max is 4, the returned value can be 1, 2, 3 or 4
---@param min int
---@param max int
---@return int
function rng:int(min, max)
if not max then
local m = min
min = 1
max = m
end
local _min = min
local _max = max
min = math.min(_min, _max)
max = math.max(_min, _max)
return min + (self:next() % (max - min + 1))
end
--- if `max` is not given, it will be 1
---@param max float
---@return float
function rng:float(max)
return ((self:next() % RAND_MAX) / RAND_MAX) * (max or 1)
end
---@return boolean
function rng:bool()
return self:next() % 2 == 0
end
function rng:seed(seed)
self.state = {seed, seed, seed, seed}
self:next()
end
local rngmeta = {}
--- acts identical to math.random()
function rngmeta:__call(a, b)
if a then
return self:int(a, b)
end
return self:float()
end
rngmeta.__index = rng
--- creates a new RNG object
---@param seed int[] @array of size 4, will default to os.time() if not given
---@return rng
function rng.init(seed)
seed = seed or os.time()
local state = {seed, seed, seed, seed}
local this = setmetatable({state = state}, rngmeta)
this:next() -- just to prevent the state from being all the same number; i dont know a cleaner way of doing this
return this
end
return rng

157
template/stdlib/util.lua Normal file
View File

@ -0,0 +1,157 @@
---@param a number
---@param b number
---@param x number
---@return number
function mix(a, b, x)
return a * (1 - x) + b * x
end
lerp = mix
---@param x number
---@return number
function sign(x)
if x > 0 then return 1 end
if x < 0 then return -1 end
return 0
end
function deepcopy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[deepcopy(k)] = deepcopy(v) end
return res
end
---@param a number
---@param x number
---@param y number
---@return number
function clamp(a, x, y)
return math.max(math.min(a, math.max(x, y)), math.min(x, y))
end
---@param tab any[]
---@param elem any
function includes(tab, elem)
if not tab then error('bad argument #1 (expected table, got nil)', 2) end
for _, v in pairs(tab) do
if elem == v then return true end
end
return false
end
---@param text string
---@param length int
function truncate(text, length)
local addellipses = false
local firstLine = nil
for line in string.gfind(text, '([^\n]*)\n?') do
if not firstLine then
firstLine = line
elseif line ~= '' then
addellipses = true
break
end
end
text = firstLine
if string.len(text) > length then
text = string.sub(text, 1, length)
addellipses = true
end
if addellipses then text = text .. '...' end
return text
end
function padLeft(str, num, fill)
local s = {}
for i = 1, num - #str do
table.insert(s, fill)
end
table.insert(s, str)
return table.concat(s, '')
end
function padLeft(str, num, fill)
local s = {}
table.insert(s, str)
for i = 1, num - #str do
table.insert(s, fill)
end
return table.concat(s, '')
end
local whitespaces = {' ', '\n', '\r'}
---@param str string
function trimLeft(str)
while includes(whitespaces, string.sub(str, 1, 1)) do
str = string.sub(str, 2)
end
return str
end
---@param str string
function trimRight(str)
while includes(whitespaces, string.sub(str, -1, -1)) do
str = string.sub(str, 1, -2)
end
return str
end
---@param str string
function trim(str)
return trimRight(trimLeft(str))
end
---@param o any
---@return string
--- stringify an object
function fullDump(o, r)
if type(o) == 'table' and (not r or r > 0) then
local s = '{'
local first = true
for k,v in pairs(o) do
if not first then
s = s .. ', '
end
local nr = nil
if r then
nr = r - 1
end
if type(k) ~= 'number' then
s = s .. tostring(k) .. ' = ' .. fullDump(v, nr)
else
s = s .. fullDump(v, nr)
end
first = false
end
return s .. '}'
elseif type(o) == 'string' then
return '"' .. o .. '"'
else
return tostring(o)
end
end
---@param t1 any[]
---@param t2 any[]
---@return any[]
function tableConcat(t1, t2)
for i = 1, #t2 do
t1[#t1 + 1] = t2[i]
end
return t1
end
---@param tab table
function clearMetatables(tab)
setmetatable(tab, nil)
for _, obj in pairs(tab) do
if type(obj) == 'table' then
clearMetatables(obj)
end
end
end

View File

@ -0,0 +1,23 @@
local endings = {'rawr x3', 'OwO', 'UwU', 'o.O', '-.-', '>w<', '(˘ω˘)', 'σωσ', 'ʘwʘ', ':3', 'XD', 'nyaa~~', 'mya', '>_<', 'rawr', '^^', '^^;;', '(^•ω•^)'}
return function(str)
str = string.lower(str)
str = string.gsub(str, 'small', 'smol')
str = string.gsub(str, 'cute', 'kawaii~')
str = string.gsub(str, 'fluff', 'floof')
str = string.gsub(str, 'love', 'luv')
str = string.gsub(str, 'stupid', 'baka')
str = string.gsub(str, 'meow', 'nya~')
str = string.gsub(str, 'l', 'w')
str = string.gsub(str, 'r', 'w')
str = string.gsub(str, 'n([aeiou])', 'ny%1')
str = string.gsub(str, '[.!?]%s', function(e) return e .. endings[math.random(1, #endings)] .. ' ' end)
str = string.gsub(str, '(%s)(%a)(%a)', function(space, rep, other) if math.random() < 0.05 then return space .. rep .. '-' .. rep .. other else return space .. rep .. other end end)
return str
end

View File

@ -0,0 +1,142 @@
---@class vector2D A vector can be defined as a set of 2 coordinates. They can be obtained by doing either vect.x and vect.y or vect[1] and vect[2], for compatibility purposes.
---The reason such a simple class exists is to do simplified math with it - math abstracted as :length(), :angle(), etc is much easier to read.
---@field public x number @x coordinate
---@field public y number @y coordinate
---@operator add(vector2D): vector2D
---@operator add(number): vector2D
---@operator sub(vector2D): vector2D
---@operator sub(number): vector2D
---@operator mul(vector2D): vector2D
---@operator mul(number): vector2D
---@operator div(vector2D): vector2D
---@operator div(number): vector2D
---@operator unm: vector2D
local vect = {}
---@return number
function vect:length()
return math.sqrt(self.x * self.x + self.y * self.y)
end
---@return number
function vect:lengthSquared()
return self.x * self.x + self.y * self.y
end
---@return number
function vect:angle()
return math.atan2(self.y, self.x)
end
---@return vector2D
function vect:normalize()
local len = self:length()
if len ~= 0 and len ~= 1 then
return vector2D(self.x / len, self.y / len)
else
return self
end
end
---@return vector2D
function vect:resize(x)
local n = self:normalize()
return vector2D(n.x * x, n.y * x)
end
---@return vector2D
function vect:rotate(ang)
local a = self:angle()
local len = self:length()
return vectorFromAngle(a + ang, len)
end
---@return number, number
function vect:unpack()
return self.x, self.y
end
---@param v2 vector2D
---@return number
function vect:distance(v2)
return (self - v2):length()
end
---@param v2 vector2D
---@return number
function vect:distanceSquared(v2)
return (self - v2):lengthSquared()
end
local vectmeta = {}
local function typ(a)
return (type(a) == 'table' and a.x and a.y) and 'vector' or type(a)
end
local function genericop(a, b, f, name)
local typea = typ(a)
local typeb = typ(b)
if typea == 'number' then
return vector2D(f(b.x, a), f(b.y, a))
elseif typeb == 'number' then
return vector2D(f(a.x, b), f(a.y, b))
elseif typea == 'vector' and typeb == 'vector' then
return vector2D(f(a.x, b.x), f(a.y, b.y))
end
error('cant apply ' .. name .. ' to ' .. typea .. ' and ' .. typeb, 3)
end
function vectmeta.__add(a, b)
return genericop(a, b, function(a, b) return a + b end, 'add')
end
function vectmeta.__sub(a, b)
return genericop(a, b, function(a, b) return a - b end, 'sub')
end
function vectmeta.__mul(a, b)
return genericop(a, b, function(a, b) return a * b end, 'mul')
end
function vectmeta.__div(a, b)
return genericop(a, b, function(a, b) return a / b end, 'div')
end
function vectmeta.__eq(a, b)
return (typ(a) == 'vector' and typ(b) == 'vector') and (a.x == b.x and a.y == b.y)
end
function vectmeta:__unm()
return vector2D(-self.x, -self.y)
end
function vectmeta:__tostring()
return '(' .. self.x .. ', ' .. self.y .. ')'
end
vectmeta.__name = 'vector'
function vectmeta:__index(i)
if i == 1 then return self.x end
if i == 2 then return self.y end
return vect[i]
end
--- create a new vector
---@param x number | nil
---@param y number | nil
---@return vector2D
function vector2D(x, y)
x = x or 0
y = y or x
return setmetatable({x = x, y = y}, vectmeta)
end
--- create a new vector from an angle
---@param ang number | nil @angle in degrees
---@param amp number | nil
---@return vector2D
function vectorFromAngle(ang, amp)
ang = math.rad(ang or 0)
amp = amp or 1
return vector2D(math.cos(ang) * amp, math.sin(ang) * amp)
end
vector = vector2D

81
template/typings.lua Normal file
View File

@ -0,0 +1,81 @@
---@meta
-- cleaning up some notitg typing jank... ehe
---@alias int number
---@alias float number
---@alias Quad Actor
---@alias void nil
---@return Quad
--- Defines a Quad actor.
function Quad() end
---@return ActorProxy
--- Defines an ActorProxy actor.
function ActorProxy() end
---@return Polygon
--- Defines a Polygon actor.
function Polygon() end
---@param file string
---@return Sprite
--- Defines a Sprite actor.
function Sprite(file) end
---@param file string
---@return Model
--- Defines a Model actor.
function Model(file) end
---@param font string?
---@param text string?
---@return BitmapText
--- Defines a BitmapText actor.
function BitmapText(font, text) end
---@param file string
---@return ActorSound
--- Defines an ActorSound actor.
function ActorSound(file) end
---@type number
--- A simple timer. Ticks upwards at a rate of 1/sec.
---
--- **The start time is undefined!** This uses `os.clock()`, meaning this will be inconsistent between modfile starts.
---
--- It's recommended to only use this for eg. `math.sin`, rotations, and other similar visual effects. If you want a proper timer, see `b`.
t = 0
---@type number
--- The amount of beats that have passed since the start of the file.
b = 0
---@type ActorFrame
--- The root ActorFrame. Use this for `addcommand` and similar!
_main = {}
---@type number
--- The center of the screen on the X axis. Equal to `SCREEN_CENTER_X`.
scx = 0
---@type number
--- The center of the screen on the Y axis. Equal to `SCREEN_CENTER_Y`.
scy = 0
---@type number
--- The screen width. Equal to `SCREEN_WIDTH`.
sw = 0
---@type number
--- The screen height. Equal to `SCREEN_HEIGHT`.
sh = 0
--- The Uranium Template table! Mostly callback-related stuff goes here.
uranium = {}
--- A callback for initialization. Called on `OnCommand`.
uranium.init = function() end
--- A callback for updates. Called every frame. Draw stuff here!
uranium.update = function() end
---@param event string
---@param ... any
---@return any
--- Call a defined callback.
function uranium:call(event, ...) end
--- Equivalent to a modfile-sandboxed `_G`, similar to Mirin's `xero`. You shouldn't need this; and if you do, *what are you doing?*
---@type table<any, any>
oat = {}