summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVittorio Romeo <vittorio.romeo@outlook.com>2020-07-15 12:34:51 +0100
committerVittorio Romeo <vittorio.romeo@outlook.com>2020-07-15 12:34:51 +0100
commit0cf95060522b5ae2fcf61566db80ee675b9abce5 (patch)
treecaa9ee89f8f9b1918322ecabfa769634902ee5f4
parentddfdd610e9a0d40a8879c1823c0fb7c07ad64478 (diff)
parentd9a8e381dbb4215b497c5d3e5943245239c0480d (diff)
Merge branch 'master' of https://github.com/SuperV1234/SSVOpenHexagon into competitive_cubecompetitive_cube
-rw-r--r--README.md8
-rw-r--r--_RELEASE/Assets/assets.json3
-rw-r--r--_RELEASE/Assets/epilepsyWarning.pngbin0 -> 44414 bytes
-rw-r--r--_RELEASE/Packs/cube/Scripts/alternativepatterns.lua40
-rw-r--r--_RELEASE/Packs/cube/Scripts/common.lua6
-rw-r--r--_RELEASE/Packs/cube/Scripts/commonpatterns.lua80
-rw-r--r--_RELEASE/Packs/experimental/Scripts/alternativepatterns.lua40
-rw-r--r--_RELEASE/Packs/experimental/Scripts/common.lua6
-rw-r--r--_RELEASE/Packs/experimental/Scripts/commonpatterns.lua123
-rw-r--r--_RELEASE/Packs/experimental/Scripts/evolutionpatterns.lua100
-rw-r--r--_RELEASE/Packs/experimental/Scripts/nextpatterns.lua86
-rw-r--r--_RELEASE/Packs/hypercube/Scripts/alternativepatterns.lua40
-rw-r--r--_RELEASE/Packs/hypercube/Scripts/common.lua6
-rw-r--r--_RELEASE/Packs/hypercube/Scripts/commonpatterns.lua80
-rw-r--r--_RELEASE/Packs/hypercube/Scripts/evolutionpatterns.lua100
-rw-r--r--_RELEASE/Packs/hypercube/Scripts/nextpatterns.lua90
-rw-r--r--_RELEASE/Packs/workshopexample/Example Workshop Level.workshop.json14
-rw-r--r--_RELEASE/Packs/workshopexample/Levels/examplelevel.json27
-rw-r--r--_RELEASE/Packs/workshopexample/Music/jackRussel.json24
-rw-r--r--_RELEASE/Packs/workshopexample/Scripts/Levels/examplelevel.lua92
-rw-r--r--_RELEASE/Packs/workshopexample/Scripts/alternativepatterns.lua119
-rw-r--r--_RELEASE/Packs/workshopexample/Scripts/common.lua109
-rw-r--r--_RELEASE/Packs/workshopexample/Scripts/commonpatterns.lua209
-rw-r--r--_RELEASE/Packs/workshopexample/Scripts/utils.lua28
-rw-r--r--_RELEASE/Packs/workshopexample/Styles/examplelevel.json57
-rw-r--r--_RELEASE/Packs/workshopexample/pack.json27
-rw-r--r--_RELEASE/Packs/workshopexample/workshop_example_level.pngbin0 -> 138908 bytes
-rw-r--r--_RELEASE/config.json6
-rw-r--r--art/epilepsyWarning.psdbin0 -> 717318 bytes
-rw-r--r--art/githubohlogo.pngbin0 -> 368474 bytes
-rw-r--r--art/libraryhero.psdbin0 -> 881014 bytes
-rw-r--r--include/SSVOpenHexagon/Core/MenuGame.hpp6
-rw-r--r--prepare_release.sh1
-rw-r--r--src/SSVOpenHexagon/Core/HGScripting.cpp11
-rw-r--r--src/SSVOpenHexagon/Core/MenuGame.cpp167
-rw-r--r--src/SSVOpenHexagon/Global/Config.cpp4
-rw-r--r--webpage/github-pandoc.css426
-rw-r--r--webpage/index.html35
-rw-r--r--webpage/mk_workshop_tutorial.sh4
-rw-r--r--webpage/workshop.html283
-rw-r--r--webpage/workshop_tutorial.md436
41 files changed, 2419 insertions, 474 deletions
diff --git a/README.md b/README.md
index fbac102d..a7619828 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
-# [Open Hexagon 2.0](http://www.facebook.com/OpenHexagon) - [by Vittorio Romeo](http://vittorioromeo.info)
+<a href="https://openhexagon.org" target="_blank">
+ <p align="center">
+ <img src="https://vittorioromeo.info/Misc/Linked/githubohlogo.png">
+ </p>
+</a>
+
+> **Open Hexagon is a fast-paced and adrenaline-inducing paced arcade experience by [Vittorio Romeo](https://vittorioromeo.info). Designed for moddability and custom level creation.** Now available on [Steam](https://store.steampowered.com/app/1358090/)!
## How to build on Windows
diff --git a/_RELEASE/Assets/assets.json b/_RELEASE/Assets/assets.json
index 39e6fc6d..99f15dd5 100644
--- a/_RELEASE/Assets/assets.json
+++ b/_RELEASE/Assets/assets.json
@@ -11,7 +11,8 @@
"creditsBar2b.png",
"creditsBar2c.png",
"creditsBar2d.png",
- "titleBar.png"
+ "titleBar.png",
+ "epilepsyWarning.png"
],
"soundBuffers":
[
diff --git a/_RELEASE/Assets/epilepsyWarning.png b/_RELEASE/Assets/epilepsyWarning.png
new file mode 100644
index 00000000..da59f599
--- /dev/null
+++ b/_RELEASE/Assets/epilepsyWarning.png
Binary files differ
diff --git a/_RELEASE/Packs/cube/Scripts/alternativepatterns.lua b/_RELEASE/Packs/cube/Scripts/alternativepatterns.lua
index 4cb9108a..598c0c30 100644
--- a/_RELEASE/Packs/cube/Scripts/alternativepatterns.lua
+++ b/_RELEASE/Packs/cube/Scripts/alternativepatterns.lua
@@ -1,11 +1,11 @@
u_execScript("common.lua")
function pAltMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
for k = 1, #mTimes do
for i = 1, mTimes[k] do
rWallEx(startSide, mExtra)
@@ -24,7 +24,7 @@ function pAltMirrorSpiral(mTimes, mExtra)
end
function randomArray(mNumber,mLower,mUpper)
- a = {}
+ local a = {}
for k = 1, mNumber do
a[k] = math.random(mLower,mUpper)
end
@@ -32,11 +32,11 @@ function randomArray(mNumber,mLower,mUpper)
end
function pAltTunnel(mTimes,mFree)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
@@ -55,8 +55,8 @@ function pAltTunnel(mTimes,mFree)
end
function cycle(mSides)
- eArray = {}
- j = getRandomSide()
+ local eArray = {}
+ local j = getRandomSide()
for i = 1, mSides do
eArray[i] = (i + j) % mSides + 1
end
@@ -65,15 +65,15 @@ end
function pLadder(mTimes,mArray,myThickness)
- delay = getPerfectDelay(myThickness)
+ local delay = getPerfectDelay(myThickness)
local eArray = {}
- l = 1
- s = #mArray/l_getSides()
- t = math.random(0,100)
+ local l = 1
+ local s = #mArray/l_getSides()
+ local t = math.random(0,100)
for i = 1, mTimes do
- q = (i+t) % s + 1
+ local q = (i+t) % s + 1
for k = 1, l_getSides() do
if(mArray[(q-1)*l_getSides() + k] ~= 0) then
eArray[l] = 1
@@ -103,10 +103,10 @@ function pLadder(mTimes,mArray,myThickness)
end
function patternizer(mArray,myThickness)
- delay = getPerfectDelay(myThickness)
- eArray = cycle(l_getSides())
+ local delay = getPerfectDelay(myThickness)
+ local eArray = cycle(l_getSides())
- j = math.floor((#mArray) / l_getSides())
+ local j = math.floor((#mArray) / l_getSides())
for i = 1, j do
for k = 1, l_getSides() do
diff --git a/_RELEASE/Packs/cube/Scripts/common.lua b/_RELEASE/Packs/cube/Scripts/common.lua
index 932fe7b8..120e1fbd 100644
--- a/_RELEASE/Packs/cube/Scripts/common.lua
+++ b/_RELEASE/Packs/cube/Scripts/common.lua
@@ -75,10 +75,10 @@ end
-- cWallEx: creates a wall with mExtra walls attached to it
function cWallEx(mSide, mExtra)
cWall(mSide);
- loopDir = 1;
+ local exLoopDir = 1;
- if mExtra < 0 then loopDir = -1 end
- for i = 0, mExtra, loopDir do cWall(mSide + i) end
+ if mExtra < 0 then exLoopDir = -1 end
+ for i = 0, mExtra, exLoopDir do cWall(mSide + i) end
end
-- oWallEx: creates a wall with mExtra walls opposite to mSide
diff --git a/_RELEASE/Packs/cube/Scripts/commonpatterns.lua b/_RELEASE/Packs/cube/Scripts/commonpatterns.lua
index 29129c8f..6f7258fd 100644
--- a/_RELEASE/Packs/cube/Scripts/commonpatterns.lua
+++ b/_RELEASE/Packs/cube/Scripts/commonpatterns.lua
@@ -2,7 +2,7 @@ u_execScript("common.lua")
-- pAltBarrage: spawns a series of cAltBarrage
function pAltBarrage(mTimes, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6
for i = 0, mTimes do
cAltBarrage(i, mStep)
@@ -12,17 +12,17 @@ function pAltBarrage(mTimes, mStep)
t_wait(delay)
end
--- pSpiral: spawns a spiral of cWall
+-- pSpiral: spawns a spiral of cWallEx
function pSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
- j = 0
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
for i = 0, mTimes do
- cWall(startSide + j, mExtra)
+ cWallEx(startSide + j, mExtra)
j = j + loopDir
t_wait(delay)
end
@@ -34,11 +34,11 @@ end
-- pMirrorSpiral: spawns a spiral of rWallEx
function pMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
j = 0
for i = 0, mTimes do
@@ -54,12 +54,12 @@ end
-- pMirrorSpiralDouble: spawns a spiral of rWallEx where you need to change direction
function pMirrorSpiralDouble(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelayDM(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
for i = 0, mTimes do
rWallEx(startSide + j, mExtra)
@@ -82,10 +82,10 @@ end
-- pBarrageSpiral: spawns a spiral of cBarrage
function pBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -99,10 +99,10 @@ end
-- pDMBarrageSpiral: spawns a spiral of cBarrage, with static delay
function pDMBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -116,10 +116,10 @@ end
-- pWallExVortex: spawns left-left right-right spiral patters
function pWallExVortex(mTimes, mStep, mExtraMult)
- delay = getPerfectDelayDM(THICKNESS) * 5.0
- startSide = getRandomSide()
- loopDir = getRandomDir()
- currentSide = startSide
+ local delay = getPerfectDelayDM(THICKNESS) * 5.0
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local currentSide = startSide
for j = 0, mTimes do
for i = 0, mStep do
@@ -142,8 +142,8 @@ end
-- pInverseBarrage: spawns two barrages who force you to turn 180 degrees
function pInverseBarrage(mTimes)
- delay = getPerfectDelayDM(THICKNESS) * 9.9
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 9.9
+ local startSide = getRandomSide()
for i = 0, mTimes do
cBarrage(startSide)
@@ -158,8 +158,8 @@ end
-- pRandomBarrage: spawns barrages with random side, and waits humanly-possible times depending on the sides distance
function pRandomBarrage(mTimes, mDelayMult)
- side = getRandomSide()
- oldSide = 0
+ local side = getRandomSide()
+ local oldSide = 0
for i = 0, mTimes do
cBarrage(side)
@@ -173,8 +173,8 @@ end
-- pMirrorWallStrip: spawns rWalls close to one another on the same side
function pMirrorWallStrip(mTimes, mExtra)
- delay = getPerfectDelayDM(THICKNESS) * 3.65
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.65
+ local startSide = getRandomSide()
for i = 0, mTimes do
rWallEx(startSide, mExtra)
@@ -186,11 +186,11 @@ end
-- pTunnel: forces you to circle around a very thick wall
function pTunnel(mTimes)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
diff --git a/_RELEASE/Packs/experimental/Scripts/alternativepatterns.lua b/_RELEASE/Packs/experimental/Scripts/alternativepatterns.lua
index 0266f122..598c0c30 100644
--- a/_RELEASE/Packs/experimental/Scripts/alternativepatterns.lua
+++ b/_RELEASE/Packs/experimental/Scripts/alternativepatterns.lua
@@ -1,11 +1,11 @@
u_execScript("common.lua")
function pAltMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
for k = 1, #mTimes do
for i = 1, mTimes[k] do
rWallEx(startSide, mExtra)
@@ -24,7 +24,7 @@ function pAltMirrorSpiral(mTimes, mExtra)
end
function randomArray(mNumber,mLower,mUpper)
- a = {}
+ local a = {}
for k = 1, mNumber do
a[k] = math.random(mLower,mUpper)
end
@@ -32,11 +32,11 @@ function randomArray(mNumber,mLower,mUpper)
end
function pAltTunnel(mTimes,mFree)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
@@ -55,8 +55,8 @@ function pAltTunnel(mTimes,mFree)
end
function cycle(mSides)
- eArray = {}
- j = getRandomSide()
+ local eArray = {}
+ local j = getRandomSide()
for i = 1, mSides do
eArray[i] = (i + j) % mSides + 1
end
@@ -65,15 +65,15 @@ end
function pLadder(mTimes,mArray,myThickness)
- delay = getPerfectDelay(myThickness)
+ local delay = getPerfectDelay(myThickness)
local eArray = {}
- l = 1
- s = (#mArray)/l_getSides()
- t = math.random(0,100)
+ local l = 1
+ local s = #mArray/l_getSides()
+ local t = math.random(0,100)
for i = 1, mTimes do
- q = (i+t) % s + 1
+ local q = (i+t) % s + 1
for k = 1, l_getSides() do
if(mArray[(q-1)*l_getSides() + k] ~= 0) then
eArray[l] = 1
@@ -103,10 +103,10 @@ function pLadder(mTimes,mArray,myThickness)
end
function patternizer(mArray,myThickness)
- delay = getPerfectDelay(myThickness)
- eArray = cycle(l_getSides())
+ local delay = getPerfectDelay(myThickness)
+ local eArray = cycle(l_getSides())
- j = math.floor(#mArray / l_getSides())
+ local j = math.floor((#mArray) / l_getSides())
for i = 1, j do
for k = 1, l_getSides() do
diff --git a/_RELEASE/Packs/experimental/Scripts/common.lua b/_RELEASE/Packs/experimental/Scripts/common.lua
index f10c4b05..70a16ef6 100644
--- a/_RELEASE/Packs/experimental/Scripts/common.lua
+++ b/_RELEASE/Packs/experimental/Scripts/common.lua
@@ -89,10 +89,10 @@ end
-- cWallEx: creates a wall with mExtra walls attached to it
function cWallEx(mSide, mExtra)
cWall(mSide);
- loopDir = 1;
+ local exLoopDir = 1;
- if mExtra < 0 then loopDir = -1 end
- for i = 0, mExtra, loopDir do cWall(mSide + i) end
+ if mExtra < 0 then exLoopDir = -1 end
+ for i = 0, mExtra, exLoopDir do cWall(mSide + i) end
end
-- oWallEx: creates a wall with mExtra walls opposite to mSide
diff --git a/_RELEASE/Packs/experimental/Scripts/commonpatterns.lua b/_RELEASE/Packs/experimental/Scripts/commonpatterns.lua
index 00410137..6f7258fd 100644
--- a/_RELEASE/Packs/experimental/Scripts/commonpatterns.lua
+++ b/_RELEASE/Packs/experimental/Scripts/commonpatterns.lua
@@ -2,7 +2,7 @@ u_execScript("common.lua")
-- pAltBarrage: spawns a series of cAltBarrage
function pAltBarrage(mTimes, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6
for i = 0, mTimes do
cAltBarrage(i, mStep)
@@ -12,17 +12,17 @@ function pAltBarrage(mTimes, mStep)
t_wait(delay)
end
--- pMirrorSpiral: spawns a spiral of rWallEx
-function pMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+-- pSpiral: spawns a spiral of cWallEx
+function pSpiral(mTimes, mExtra)
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
- j = 0
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
for i = 0, mTimes do
- rWallEx(startSide + j, mExtra)
+ cWallEx(startSide + j, mExtra)
j = j + loopDir
t_wait(delay)
end
@@ -32,14 +32,13 @@ function pMirrorSpiral(mTimes, mExtra)
t_wait(getPerfectDelayDM(THICKNESS) * 6.5)
end
--- pMirrorSpiralDouble: spawns a spiral of rWallEx where you need to change direction
-function pMirrorSpiralDouble(mTimes, mExtra)
- oldThickness = THICKNESS
+-- pMirrorSpiral: spawns a spiral of rWallEx
+function pMirrorSpiral(mTimes, mExtra)
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelayDM(THICKNESS)
- startSide = getRandomSide()
- currentSide = startSide
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
j = 0
for i = 0, mTimes do
@@ -48,31 +47,45 @@ function pMirrorSpiralDouble(mTimes, mExtra)
t_wait(delay)
end
- rWallEx(startSide + j, mExtra)
- t_wait(delay * 0.9)
-
- rWallEx(startSide + j, mExtra)
- t_wait(delay * 0.9)
-
- loopDir = loopDir * -1
-
- for i = 0, mTimes + 1 do
- currentSide = currentSide + loopDir;
- rWallEx(currentSide + j - 1, mExtra)
- j = j + loopDir
- t_wait(delay)
- end
-
THICKNESS = oldThickness
- t_wait(getPerfectDelayDM(THICKNESS) * 7.5)
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 6.5)
+end
+
+-- pMirrorSpiralDouble: spawns a spiral of rWallEx where you need to change direction
+function pMirrorSpiralDouble(mTimes, mExtra)
+ local oldThickness = THICKNESS
+ THICKNESS = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelayDM(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
+
+ for i = 0, mTimes do
+ rWallEx(startSide + j, mExtra)
+ j = j + loopDir
+ t_wait(delay)
+ end
+
+ rWallEx(startSide + j, mExtra)
+ t_wait(delay * 0.9)
+
+ for i = 0, mTimes + 1 do
+ rWallEx(startSide + j, mExtra)
+ j = j - loopDir
+ t_wait(delay)
+ end
+
+ THICKNESS = oldThickness
+ t_wait(getPerfectDelayDM(THICKNESS) * 7.5)
end
-- pBarrageSpiral: spawns a spiral of cBarrage
function pBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -86,10 +99,10 @@ end
-- pDMBarrageSpiral: spawns a spiral of cBarrage, with static delay
function pDMBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -103,10 +116,10 @@ end
-- pWallExVortex: spawns left-left right-right spiral patters
function pWallExVortex(mTimes, mStep, mExtraMult)
- delay = getPerfectDelayDM(THICKNESS) * 5.0
- startSide = getRandomSide()
- loopDir = getRandomDir()
- currentSide = startSide
+ local delay = getPerfectDelayDM(THICKNESS) * 5.0
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local currentSide = startSide
for j = 0, mTimes do
for i = 0, mStep do
@@ -129,8 +142,8 @@ end
-- pInverseBarrage: spawns two barrages who force you to turn 180 degrees
function pInverseBarrage(mTimes)
- delay = getPerfectDelayDM(THICKNESS) * 9.9
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 9.9
+ local startSide = getRandomSide()
for i = 0, mTimes do
cBarrage(startSide)
@@ -145,8 +158,8 @@ end
-- pRandomBarrage: spawns barrages with random side, and waits humanly-possible times depending on the sides distance
function pRandomBarrage(mTimes, mDelayMult)
- side = getRandomSide()
- oldSide = 0
+ local side = getRandomSide()
+ local oldSide = 0
for i = 0, mTimes do
cBarrage(side)
@@ -160,8 +173,8 @@ end
-- pMirrorWallStrip: spawns rWalls close to one another on the same side
function pMirrorWallStrip(mTimes, mExtra)
- delay = getPerfectDelayDM(THICKNESS) * 3.65
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.65
+ local startSide = getRandomSide()
for i = 0, mTimes do
rWallEx(startSide, mExtra)
@@ -173,11 +186,11 @@ end
-- pTunnel: forces you to circle around a very thick wall
function pTunnel(mTimes)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
diff --git a/_RELEASE/Packs/experimental/Scripts/evolutionpatterns.lua b/_RELEASE/Packs/experimental/Scripts/evolutionpatterns.lua
index a5aa629f..e6131a04 100644
--- a/_RELEASE/Packs/experimental/Scripts/evolutionpatterns.lua
+++ b/_RELEASE/Packs/experimental/Scripts/evolutionpatterns.lua
@@ -4,12 +4,12 @@ u_execScript("utils.lua")
u_execScript("alternativepatterns.lua")
u_execScript("nextpatterns.lua")
-hueModifier = 0.2
-sync = false
-syncRndMin = 0
-syncRndMax = 0
+local hueModifier = 0.2
+local sync = false
+local syncRndMin = 0
+local syncRndMax = 0
-curveMult = 1
+local curveMult = 1
function syncCurveWithRotationSpeed(mRndMin, mRndMax)
sync = true
@@ -62,13 +62,13 @@ end
function hmcSimpleTwirl(mTimes, mCurve, mCurveAdd)
- startSide = getRandomSide()
- currentSide = startSide
- loopDir = getRandomDir()
- delay = getPerfectDelayDM(THICKNESS) * 5.7
- j = 0
+ local startSide = getRandomSide()
+ local currentSide = startSide
+ local loopDir = getRandomDir()
+ local delay = getPerfectDelayDM(THICKNESS) * 5.7
+ local j = 0
- currentCurve = mCurve
+ local currentCurve = mCurve
for i = 0, mTimes do
hmcSimpleBarrageS(startSide + j, currentCurve)
@@ -79,22 +79,22 @@ function hmcSimpleTwirl(mTimes, mCurve, mCurveAdd)
end
function hmcSimpleCage(mCurve, mDir)
- side = getRandomSide()
- oppositeSide = side + getHalfSides()
+ local side = getRandomSide()
+ local oppositeSide = side + getHalfSides()
wallHMCurve(side, mCurve)
wallHMCurve(oppositeSide, mCurve * mDir)
end
function hmcSimpleCageS(mCurve, mDir, mSide)
- oppositeSide = mSide + getHalfSides()
+ local oppositeSide = mSide + getHalfSides()
wallHMCurve(mSide, mCurve)
wallHMCurve(oppositeSide, mCurve * mDir)
end
function hmcSimpleSpinner(mCurve)
- side = getRandomSide()
+ local side = getRandomSide()
for i = 0, l_getSides() / 2, 1 do
wallHMCurve(side + i * 2, mCurve)
@@ -127,8 +127,8 @@ end
function hmcDef2Cage()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
- rndspd = math.random(10, 20) / 10.0
+ local side = getRandomSide()
+ local rndspd = math.random(10, 20) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
hmcSimpleCageS(rndspd, -1, side)
@@ -142,9 +142,9 @@ end
function hmcDef2CageD()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
- oppositeSide = getHalfSides() + side
- rndspd = math.random(10, 17) / 10.0
+ local side = getRandomSide()
+ local oppositeSide = getHalfSides() + side
+ local rndspd = math.random(10, 17) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
hmcSimpleCageS(rndspd, -1, side)
@@ -162,10 +162,10 @@ function hmcDef2CageD()
end
function hmcSimpleBarrageSpiral(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
- delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(startSide + j, mCurve, mNeighbors)
@@ -178,8 +178,8 @@ function hmcSimpleBarrageSpiral(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
end
function hmcSimpleBarrageSpiralRnd(mTimes, mDelayMult, mCurve, mNeighbors)
- delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
+ local startSide = getRandomSide()
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(getRandomSide(), mCurve, mNeighbors)
@@ -191,10 +191,10 @@ function hmcSimpleBarrageSpiralRnd(mTimes, mDelayMult, mCurve, mNeighbors)
end
function hmcSimpleBarrageSpiralStatic(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
- delay = getPerfectDelay(THICKNESS) * 5.6 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelay(THICKNESS) * 5.6 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(startSide + j, mCurve, mNeighbors)
@@ -224,10 +224,10 @@ end
function hmcDefBarrageInv()
t_wait(getPerfectDelayDM(THICKNESS) * 2.0)
- delay = getPerfectDelay(THICKNESS) * 5.6
- side = getRandomSide()
- rndspd = math.random(10, 20) / 10.0
- oppositeSide = getRandomSide() + getHalfSides()
+ local delay = getPerfectDelay(THICKNESS) * 5.6
+ local side = getRandomSide()
+ local rndspd = math.random(10, 20) / 10.0
+ local oppositeSide = getRandomSide() + getHalfSides()
hmcSimpleBarrageSNeigh(side, rndspd * getRandomDir(), 0)
t_wait(delay)
@@ -238,28 +238,28 @@ end
function hmcDefAccelBarrage()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- c = math.random(50, 100) / 1000.0 * getRandomDir()
- min = math.random(5, 35) / 10.0 * -1
- max = math.random(5, 35) / 10.0
- hmcBarrage(0, c, min, max, true)
+ local c = math.random(50, 100) / 1000.0 * getRandomDir()
+ local minimum = math.random(5, 35) / 10.0 * -1
+ local maximum = math.random(5, 35) / 10.0
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 6.1)
end
function hmcDefAccelBarrageDouble()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- c = math.random(50, 100) / 1000.0 * getRandomDir()
- min = math.random(5, 35) / 10.0 * -1
- max = math.random(5, 35) / 10.0
- hmcBarrage(0, c, min, max, true)
+ local c = math.random(50, 100) / 1000.0 * getRandomDir()
+ local minimum = math.random(5, 35) / 10.0 * -1
+ local maximum = math.random(5, 35) / 10.0
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- hmcBarrage(0, c, min, max, true)
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 6.1)
end
function hmcDefSpinnerSpiral()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- side = getRandomSide()
- c = math.random(10, 20) / 10.0 * getRandomDir()
+ local side = getRandomSide()
+ local c = math.random(10, 20) / 10.0 * getRandomDir()
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
@@ -282,18 +282,18 @@ end
function hmcDefSpinnerSpiralAcc()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
+ local side = getRandomSide()
- acc = math.random(getRndMinDM(50), getRndMaxDM(100)) / 1000.0 * getRandomDir()
- min = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0 * -1
- max = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0
+ local acc = math.random(getRndMinDM(50), getRndMaxDM(100)) / 1000.0 * getRandomDir()
+ local minimum = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0 * -1
+ local maximum = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
for i = 0, math.random(4, 8) do
- hmcSimpleSpinnerSAcc(side, 0, acc, min, max, true)
+ hmcSimpleSpinnerSAcc(side, 0, acc, minimum, maximum, true)
t_wait(getPerfectDelay(THICKNESS) * 0.8)
end
diff --git a/_RELEASE/Packs/experimental/Scripts/nextpatterns.lua b/_RELEASE/Packs/experimental/Scripts/nextpatterns.lua
index 91877a02..8925fc62 100644
--- a/_RELEASE/Packs/experimental/Scripts/nextpatterns.lua
+++ b/_RELEASE/Packs/experimental/Scripts/nextpatterns.lua
@@ -7,7 +7,7 @@ function wallSAdj(mSide, mAdj) w_wallAdj(mSide, THICKNESS, mAdj) end
function wallSAcc(mSide, mAdj, mAcc, mMinSpd, mMaxSpd) w_wallAcc(mSide, THICKNESS, mAdj, mAcc * (u_getDifficultyMult()), mMinSpd, mMaxSpd) end
function pTrapBarrage(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
cBarrage(mSide)
t_wait(delay * 3)
@@ -17,11 +17,11 @@ function pTrapBarrage(mSide)
end
function pTrapBarrageDouble(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- side2 = mSide + getHalfSides();
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local side2 = mSide + getHalfSides();
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if((currentSide ~= mSide) and (currentSide ~= side2)) then cWall(currentSide) end
end
@@ -33,13 +33,13 @@ function pTrapBarrageDouble(mSide)
end
function pTrapBarrageInverse(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
cWall(mSide)
t_wait(delay * 3)
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide ~= mSide) then wallSAdj(currentSide, 1.9) end
end
@@ -47,17 +47,17 @@ function pTrapBarrageInverse(mSide)
end
function pTrapBarrageAlt(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide % 2 ~= 0) then cWall(currentSide) end
end
t_wait(delay * 3)
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide % 2 == 0) then wallSAdj(currentSide, 1.9) end
end
@@ -65,13 +65,13 @@ function pTrapBarrageAlt(mSide)
end
function pTrapSpiral(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- loopDir = getRandomDir()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local loopDir = getRandomDir()
if(l_getSides() < 6) then delay = delay + 4 end
for i = 0, l_getSides() + getHalfSides() do
- currentSide = (mSide + i) * loopDir
+ local currentSide = (mSide + i) * loopDir
for j = 0, getHalfSides() do wallSAdj(currentSide + j, 1.2 + (i / 7.9)) end
t_wait((delay * 0.75) - (i * 0.45) + 3)
end
@@ -80,36 +80,39 @@ function pTrapSpiral(mSide)
end
function pRCBarrage()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
cWall(currentSide)
end
t_wait(delay * 2.5)
end
function pRCBarrageDouble()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
- holeSide = startSide + i + (currentSides / 2)
+ local currentSide = startSide + i
+ local holeSide = startSide + i + (currentSides / 2)
if(i ~= holeSide) then cWall(currentSide) end
end
t_wait(delay * 2.5)
end
function pRCBarrageSpin()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
- loopDir = getRandomDir()
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+ local loopDir = getRandomDir()
+
for j = 0, 2 do
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
cWall(currentSide + (j * loopDir))
end
t_wait(delay + 1)
@@ -118,22 +121,24 @@ function pRCBarrageSpin()
end
function pACBarrage()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
wallSAcc(currentSide, 9 + math.random(0, 1), -1.1, 1, 12)
end
t_wait(delay * 2.5)
end
function pACBarrageMulti()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
wallSAcc(currentSide, 10, -1.09, 0.31, 10)
wallSAcc(currentSide, 0, 0.05, 0, 4.0)
wallSAcc(currentSide, 0, 0.09, 0, 4.0)
@@ -143,13 +148,14 @@ function pACBarrageMulti()
end
function pACBarrageMultiAltDir()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 4
- mdiff = 1 + math.abs(1 - u_getDifficultyMult())
- startSide = math.random(0, 10)
- loopDir = getRandomDir()
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 4
+ local mdiff = 1 + math.abs(1 - u_getDifficultyMult())
+ local startSide = math.random(0, 10)
+ local loopDir = getRandomDir()
+
for i = 0, currentSides + getHalfSides() do
- currentSide = startSide + i * loopDir
+ local currentSide = startSide + i * loopDir
wallSAcc(currentSide, 10, -1.095, 0.40, 10)
t_wait((delay / 2.21) * (mdiff * 1.29))
wallSAcc(currentSide + (getHalfSides() * loopDir), 0, 0.128, 0, 1.4)
diff --git a/_RELEASE/Packs/hypercube/Scripts/alternativepatterns.lua b/_RELEASE/Packs/hypercube/Scripts/alternativepatterns.lua
index 0266f122..598c0c30 100644
--- a/_RELEASE/Packs/hypercube/Scripts/alternativepatterns.lua
+++ b/_RELEASE/Packs/hypercube/Scripts/alternativepatterns.lua
@@ -1,11 +1,11 @@
u_execScript("common.lua")
function pAltMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
for k = 1, #mTimes do
for i = 1, mTimes[k] do
rWallEx(startSide, mExtra)
@@ -24,7 +24,7 @@ function pAltMirrorSpiral(mTimes, mExtra)
end
function randomArray(mNumber,mLower,mUpper)
- a = {}
+ local a = {}
for k = 1, mNumber do
a[k] = math.random(mLower,mUpper)
end
@@ -32,11 +32,11 @@ function randomArray(mNumber,mLower,mUpper)
end
function pAltTunnel(mTimes,mFree)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
@@ -55,8 +55,8 @@ function pAltTunnel(mTimes,mFree)
end
function cycle(mSides)
- eArray = {}
- j = getRandomSide()
+ local eArray = {}
+ local j = getRandomSide()
for i = 1, mSides do
eArray[i] = (i + j) % mSides + 1
end
@@ -65,15 +65,15 @@ end
function pLadder(mTimes,mArray,myThickness)
- delay = getPerfectDelay(myThickness)
+ local delay = getPerfectDelay(myThickness)
local eArray = {}
- l = 1
- s = (#mArray)/l_getSides()
- t = math.random(0,100)
+ local l = 1
+ local s = #mArray/l_getSides()
+ local t = math.random(0,100)
for i = 1, mTimes do
- q = (i+t) % s + 1
+ local q = (i+t) % s + 1
for k = 1, l_getSides() do
if(mArray[(q-1)*l_getSides() + k] ~= 0) then
eArray[l] = 1
@@ -103,10 +103,10 @@ function pLadder(mTimes,mArray,myThickness)
end
function patternizer(mArray,myThickness)
- delay = getPerfectDelay(myThickness)
- eArray = cycle(l_getSides())
+ local delay = getPerfectDelay(myThickness)
+ local eArray = cycle(l_getSides())
- j = math.floor(#mArray / l_getSides())
+ local j = math.floor((#mArray) / l_getSides())
for i = 1, j do
for k = 1, l_getSides() do
diff --git a/_RELEASE/Packs/hypercube/Scripts/common.lua b/_RELEASE/Packs/hypercube/Scripts/common.lua
index 932fe7b8..120e1fbd 100644
--- a/_RELEASE/Packs/hypercube/Scripts/common.lua
+++ b/_RELEASE/Packs/hypercube/Scripts/common.lua
@@ -75,10 +75,10 @@ end
-- cWallEx: creates a wall with mExtra walls attached to it
function cWallEx(mSide, mExtra)
cWall(mSide);
- loopDir = 1;
+ local exLoopDir = 1;
- if mExtra < 0 then loopDir = -1 end
- for i = 0, mExtra, loopDir do cWall(mSide + i) end
+ if mExtra < 0 then exLoopDir = -1 end
+ for i = 0, mExtra, exLoopDir do cWall(mSide + i) end
end
-- oWallEx: creates a wall with mExtra walls opposite to mSide
diff --git a/_RELEASE/Packs/hypercube/Scripts/commonpatterns.lua b/_RELEASE/Packs/hypercube/Scripts/commonpatterns.lua
index 29129c8f..6f7258fd 100644
--- a/_RELEASE/Packs/hypercube/Scripts/commonpatterns.lua
+++ b/_RELEASE/Packs/hypercube/Scripts/commonpatterns.lua
@@ -2,7 +2,7 @@ u_execScript("common.lua")
-- pAltBarrage: spawns a series of cAltBarrage
function pAltBarrage(mTimes, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6
for i = 0, mTimes do
cAltBarrage(i, mStep)
@@ -12,17 +12,17 @@ function pAltBarrage(mTimes, mStep)
t_wait(delay)
end
--- pSpiral: spawns a spiral of cWall
+-- pSpiral: spawns a spiral of cWallEx
function pSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
- j = 0
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
for i = 0, mTimes do
- cWall(startSide + j, mExtra)
+ cWallEx(startSide + j, mExtra)
j = j + loopDir
t_wait(delay)
end
@@ -34,11 +34,11 @@ end
-- pMirrorSpiral: spawns a spiral of rWallEx
function pMirrorSpiral(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local delay = getPerfectDelay(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
j = 0
for i = 0, mTimes do
@@ -54,12 +54,12 @@ end
-- pMirrorSpiralDouble: spawns a spiral of rWallEx where you need to change direction
function pMirrorSpiralDouble(mTimes, mExtra)
- oldThickness = THICKNESS
+ local oldThickness = THICKNESS
THICKNESS = getPerfectThickness(THICKNESS)
- delay = getPerfectDelayDM(THICKNESS)
- startSide = getRandomSide()
- loopDir = getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS)
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local j = 0
for i = 0, mTimes do
rWallEx(startSide + j, mExtra)
@@ -82,10 +82,10 @@ end
-- pBarrageSpiral: spawns a spiral of cBarrage
function pBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -99,10 +99,10 @@ end
-- pDMBarrageSpiral: spawns a spiral of cBarrage, with static delay
function pDMBarrageSpiral(mTimes, mDelayMult, mStep)
- delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
cBarrage(startSide + j)
@@ -116,10 +116,10 @@ end
-- pWallExVortex: spawns left-left right-right spiral patters
function pWallExVortex(mTimes, mStep, mExtraMult)
- delay = getPerfectDelayDM(THICKNESS) * 5.0
- startSide = getRandomSide()
- loopDir = getRandomDir()
- currentSide = startSide
+ local delay = getPerfectDelayDM(THICKNESS) * 5.0
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
+ local currentSide = startSide
for j = 0, mTimes do
for i = 0, mStep do
@@ -142,8 +142,8 @@ end
-- pInverseBarrage: spawns two barrages who force you to turn 180 degrees
function pInverseBarrage(mTimes)
- delay = getPerfectDelayDM(THICKNESS) * 9.9
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 9.9
+ local startSide = getRandomSide()
for i = 0, mTimes do
cBarrage(startSide)
@@ -158,8 +158,8 @@ end
-- pRandomBarrage: spawns barrages with random side, and waits humanly-possible times depending on the sides distance
function pRandomBarrage(mTimes, mDelayMult)
- side = getRandomSide()
- oldSide = 0
+ local side = getRandomSide()
+ local oldSide = 0
for i = 0, mTimes do
cBarrage(side)
@@ -173,8 +173,8 @@ end
-- pMirrorWallStrip: spawns rWalls close to one another on the same side
function pMirrorWallStrip(mTimes, mExtra)
- delay = getPerfectDelayDM(THICKNESS) * 3.65
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.65
+ local startSide = getRandomSide()
for i = 0, mTimes do
rWallEx(startSide, mExtra)
@@ -186,11 +186,11 @@ end
-- pTunnel: forces you to circle around a very thick wall
function pTunnel(mTimes)
- oldThickness = THICKNESS
- myThickness = getPerfectThickness(THICKNESS)
- delay = getPerfectDelay(myThickness) * 5
- startSide = getRandomSide()
- loopDir = getRandomDir()
+ local oldThickness = THICKNESS
+ local myThickness = getPerfectThickness(THICKNESS)
+ local delay = getPerfectDelay(myThickness) * 5
+ local startSide = getRandomSide()
+ local loopDir = getRandomDir()
THICKNESS = myThickness
diff --git a/_RELEASE/Packs/hypercube/Scripts/evolutionpatterns.lua b/_RELEASE/Packs/hypercube/Scripts/evolutionpatterns.lua
index a5aa629f..e6131a04 100644
--- a/_RELEASE/Packs/hypercube/Scripts/evolutionpatterns.lua
+++ b/_RELEASE/Packs/hypercube/Scripts/evolutionpatterns.lua
@@ -4,12 +4,12 @@ u_execScript("utils.lua")
u_execScript("alternativepatterns.lua")
u_execScript("nextpatterns.lua")
-hueModifier = 0.2
-sync = false
-syncRndMin = 0
-syncRndMax = 0
+local hueModifier = 0.2
+local sync = false
+local syncRndMin = 0
+local syncRndMax = 0
-curveMult = 1
+local curveMult = 1
function syncCurveWithRotationSpeed(mRndMin, mRndMax)
sync = true
@@ -62,13 +62,13 @@ end
function hmcSimpleTwirl(mTimes, mCurve, mCurveAdd)
- startSide = getRandomSide()
- currentSide = startSide
- loopDir = getRandomDir()
- delay = getPerfectDelayDM(THICKNESS) * 5.7
- j = 0
+ local startSide = getRandomSide()
+ local currentSide = startSide
+ local loopDir = getRandomDir()
+ local delay = getPerfectDelayDM(THICKNESS) * 5.7
+ local j = 0
- currentCurve = mCurve
+ local currentCurve = mCurve
for i = 0, mTimes do
hmcSimpleBarrageS(startSide + j, currentCurve)
@@ -79,22 +79,22 @@ function hmcSimpleTwirl(mTimes, mCurve, mCurveAdd)
end
function hmcSimpleCage(mCurve, mDir)
- side = getRandomSide()
- oppositeSide = side + getHalfSides()
+ local side = getRandomSide()
+ local oppositeSide = side + getHalfSides()
wallHMCurve(side, mCurve)
wallHMCurve(oppositeSide, mCurve * mDir)
end
function hmcSimpleCageS(mCurve, mDir, mSide)
- oppositeSide = mSide + getHalfSides()
+ local oppositeSide = mSide + getHalfSides()
wallHMCurve(mSide, mCurve)
wallHMCurve(oppositeSide, mCurve * mDir)
end
function hmcSimpleSpinner(mCurve)
- side = getRandomSide()
+ local side = getRandomSide()
for i = 0, l_getSides() / 2, 1 do
wallHMCurve(side + i * 2, mCurve)
@@ -127,8 +127,8 @@ end
function hmcDef2Cage()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
- rndspd = math.random(10, 20) / 10.0
+ local side = getRandomSide()
+ local rndspd = math.random(10, 20) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
hmcSimpleCageS(rndspd, -1, side)
@@ -142,9 +142,9 @@ end
function hmcDef2CageD()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
- oppositeSide = getHalfSides() + side
- rndspd = math.random(10, 17) / 10.0
+ local side = getRandomSide()
+ local oppositeSide = getHalfSides() + side
+ local rndspd = math.random(10, 17) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
hmcSimpleCageS(rndspd, -1, side)
@@ -162,10 +162,10 @@ function hmcDef2CageD()
end
function hmcSimpleBarrageSpiral(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
- delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(startSide + j, mCurve, mNeighbors)
@@ -178,8 +178,8 @@ function hmcSimpleBarrageSpiral(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
end
function hmcSimpleBarrageSpiralRnd(mTimes, mDelayMult, mCurve, mNeighbors)
- delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
- startSide = getRandomSide()
+ local delay = getPerfectDelayDM(THICKNESS) * 6.2 * mDelayMult
+ local startSide = getRandomSide()
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(getRandomSide(), mCurve, mNeighbors)
@@ -191,10 +191,10 @@ function hmcSimpleBarrageSpiralRnd(mTimes, mDelayMult, mCurve, mNeighbors)
end
function hmcSimpleBarrageSpiralStatic(mTimes, mDelayMult, mStep, mCurve, mNeighbors)
- delay = getPerfectDelay(THICKNESS) * 5.6 * mDelayMult
- startSide = getRandomSide()
- loopDir = mStep * getRandomDir()
- j = 0
+ local delay = getPerfectDelay(THICKNESS) * 5.6 * mDelayMult
+ local startSide = getRandomSide()
+ local loopDir = mStep * getRandomDir()
+ local j = 0
for i = 0, mTimes do
hmcSimpleBarrageSNeigh(startSide + j, mCurve, mNeighbors)
@@ -224,10 +224,10 @@ end
function hmcDefBarrageInv()
t_wait(getPerfectDelayDM(THICKNESS) * 2.0)
- delay = getPerfectDelay(THICKNESS) * 5.6
- side = getRandomSide()
- rndspd = math.random(10, 20) / 10.0
- oppositeSide = getRandomSide() + getHalfSides()
+ local delay = getPerfectDelay(THICKNESS) * 5.6
+ local side = getRandomSide()
+ local rndspd = math.random(10, 20) / 10.0
+ local oppositeSide = getRandomSide() + getHalfSides()
hmcSimpleBarrageSNeigh(side, rndspd * getRandomDir(), 0)
t_wait(delay)
@@ -238,28 +238,28 @@ end
function hmcDefAccelBarrage()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- c = math.random(50, 100) / 1000.0 * getRandomDir()
- min = math.random(5, 35) / 10.0 * -1
- max = math.random(5, 35) / 10.0
- hmcBarrage(0, c, min, max, true)
+ local c = math.random(50, 100) / 1000.0 * getRandomDir()
+ local minimum = math.random(5, 35) / 10.0 * -1
+ local maximum = math.random(5, 35) / 10.0
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 6.1)
end
function hmcDefAccelBarrageDouble()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- c = math.random(50, 100) / 1000.0 * getRandomDir()
- min = math.random(5, 35) / 10.0 * -1
- max = math.random(5, 35) / 10.0
- hmcBarrage(0, c, min, max, true)
+ local c = math.random(50, 100) / 1000.0 * getRandomDir()
+ local minimum = math.random(5, 35) / 10.0 * -1
+ local maximum = math.random(5, 35) / 10.0
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- hmcBarrage(0, c, min, max, true)
+ hmcBarrage(0, c, minimum, maximum, true)
t_wait(getPerfectDelayDM(THICKNESS) * 6.1)
end
function hmcDefSpinnerSpiral()
t_wait(getPerfectDelayDM(THICKNESS) * 1.5)
- side = getRandomSide()
- c = math.random(10, 20) / 10.0 * getRandomDir()
+ local side = getRandomSide()
+ local c = math.random(10, 20) / 10.0 * getRandomDir()
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
@@ -282,18 +282,18 @@ end
function hmcDefSpinnerSpiralAcc()
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
t_wait(getPerfectDelayDM(THICKNESS) * 2.1)
- side = getRandomSide()
+ local side = getRandomSide()
- acc = math.random(getRndMinDM(50), getRndMaxDM(100)) / 1000.0 * getRandomDir()
- min = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0 * -1
- max = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0
+ local acc = math.random(getRndMinDM(50), getRndMaxDM(100)) / 1000.0 * getRandomDir()
+ local minimum = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0 * -1
+ local maximum = math.random(getRndMinDM(12), getRndMaxDM(28)) / 10.0
t_wait(getPerfectDelayDM(THICKNESS) * 3.1)
for i = 0, math.random(4, 8) do
- hmcSimpleSpinnerSAcc(side, 0, acc, min, max, true)
+ hmcSimpleSpinnerSAcc(side, 0, acc, minimum, maximum, true)
t_wait(getPerfectDelay(THICKNESS) * 0.8)
end
diff --git a/_RELEASE/Packs/hypercube/Scripts/nextpatterns.lua b/_RELEASE/Packs/hypercube/Scripts/nextpatterns.lua
index a2afe34d..8925fc62 100644
--- a/_RELEASE/Packs/hypercube/Scripts/nextpatterns.lua
+++ b/_RELEASE/Packs/hypercube/Scripts/nextpatterns.lua
@@ -7,7 +7,7 @@ function wallSAdj(mSide, mAdj) w_wallAdj(mSide, THICKNESS, mAdj) end
function wallSAcc(mSide, mAdj, mAcc, mMinSpd, mMaxSpd) w_wallAcc(mSide, THICKNESS, mAdj, mAcc * (u_getDifficultyMult()), mMinSpd, mMaxSpd) end
function pTrapBarrage(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
cBarrage(mSide)
t_wait(delay * 3)
@@ -17,11 +17,11 @@ function pTrapBarrage(mSide)
end
function pTrapBarrageDouble(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- side2 = mSide + getHalfSides();
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local side2 = mSide + getHalfSides();
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if((currentSide ~= mSide) and (currentSide ~= side2)) then cWall(currentSide) end
end
@@ -33,13 +33,13 @@ function pTrapBarrageDouble(mSide)
end
function pTrapBarrageInverse(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
cWall(mSide)
t_wait(delay * 3)
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide ~= mSide) then wallSAdj(currentSide, 1.9) end
end
@@ -47,17 +47,17 @@ function pTrapBarrageInverse(mSide)
end
function pTrapBarrageAlt(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide % 2 ~= 0) then cWall(currentSide) end
end
t_wait(delay * 3)
for i = 0, l_getSides() - 1 do
- currentSide = mSide + i
+ local currentSide = mSide + i
if(currentSide % 2 == 0) then wallSAdj(currentSide, 1.9) end
end
@@ -65,13 +65,13 @@ function pTrapBarrageAlt(mSide)
end
function pTrapSpiral(mSide)
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- loopDir = getRandomDir()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local loopDir = getRandomDir()
if(l_getSides() < 6) then delay = delay + 4 end
for i = 0, l_getSides() + getHalfSides() do
- currentSide = (mSide + i) * loopDir
+ local currentSide = (mSide + i) * loopDir
for j = 0, getHalfSides() do wallSAdj(currentSide + j, 1.2 + (i / 7.9)) end
t_wait((delay * 0.75) - (i * 0.45) + 3)
end
@@ -80,36 +80,39 @@ function pTrapSpiral(mSide)
end
function pRCBarrage()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
cWall(currentSide)
end
t_wait(delay * 2.5)
end
function pRCBarrageDouble()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
- holeSide = startSide + i + (currentSides / 2)
+ local currentSide = startSide + i
+ local holeSide = startSide + i + (currentSides / 2)
if(i ~= holeSide) then cWall(currentSide) end
end
t_wait(delay * 2.5)
end
function pRCBarrageSpin()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
- loopDir = getRandomDir()
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+ local loopDir = getRandomDir()
+
for j = 0, 2 do
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
cWall(currentSide + (j * loopDir))
end
t_wait(delay + 1)
@@ -118,23 +121,25 @@ function pRCBarrageSpin()
end
function pACBarrage()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
+ local currentSide = startSide + i
wallSAcc(currentSide, 9 + math.random(0, 1), -1.1, 1, 12)
end
t_wait(delay * 2.5)
end
function pACBarrageMulti()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 3.7
- startSide = math.random(0, 10)
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 3.7
+ local startSide = math.random(0, 10)
+
for i = 0, currentSides - 2 do
- currentSide = startSide + i
- wallSAcc(currentSide, 10, -1.09, 0.47, 10)
+ local currentSide = startSide + i
+ wallSAcc(currentSide, 10, -1.09, 0.31, 10)
wallSAcc(currentSide, 0, 0.05, 0, 4.0)
wallSAcc(currentSide, 0, 0.09, 0, 4.0)
wallSAcc(currentSide, 0, 0.12, 0, 4.0)
@@ -143,15 +148,16 @@ function pACBarrageMulti()
end
function pACBarrageMultiAltDir()
- currentSides = l_getSides()
- delay = getPerfectDelayDM(THICKNESS) * 4
- mdiff = u_getDifficultyMult() / (u_getDifficultyMult()^1.3)
- startSide = math.random(0, 10)
- loopDir = getRandomDir()
+ local currentSides = l_getSides()
+ local delay = getPerfectDelayDM(THICKNESS) * 4
+ local mdiff = 1 + math.abs(1 - u_getDifficultyMult())
+ local startSide = math.random(0, 10)
+ local loopDir = getRandomDir()
+
for i = 0, currentSides + getHalfSides() do
- currentSide = startSide + i * loopDir
+ local currentSide = startSide + i * loopDir
wallSAcc(currentSide, 10, -1.095, 0.40, 10)
- t_wait((delay / 2.21) / mdiff)
+ t_wait((delay / 2.21) * (mdiff * 1.29))
wallSAcc(currentSide + (getHalfSides() * loopDir), 0, 0.128, 0, 1.4)
end
t_wait(delay * 8)
diff --git a/_RELEASE/Packs/workshopexample/Example Workshop Level.workshop.json b/_RELEASE/Packs/workshopexample/Example Workshop Level.workshop.json
new file mode 100644
index 00000000..1ba718c8
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Example Workshop Level.workshop.json
@@ -0,0 +1,14 @@
+{
+ "publishedfileid": "2157745755",
+ "contentfolder": "",
+ "previewfile": "workshop_example_level.png",
+ "visibility": 2,
+ "title": "Example Workshop Level",
+ "description": "Example custom level for Open Hexagon. Use this as a starting point for your creations!",
+ "metadata": "",
+ "tags": [
+ "example",
+ " tutorial",
+ " template"
+ ]
+} \ No newline at end of file
diff --git a/_RELEASE/Packs/workshopexample/Levels/examplelevel.json b/_RELEASE/Packs/workshopexample/Levels/examplelevel.json
new file mode 100644
index 00000000..e1eb7c05
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Levels/examplelevel.json
@@ -0,0 +1,27 @@
+{
+ // Level id. Should be unique per pack.
+ "id": "examplelevel",
+
+ // Information about the level. These are displayed in the main menu.
+ "name": "examplelevel",
+ "description": "example workshop level",
+ "author": "vittorio romeo",
+
+ // Ordering of the level compared to other levels in the same pack.
+ "menuPriority": 0,
+
+ // Whether the level is available or not.
+ "selectable": true,
+
+ // Level's style and music. These should match ids defined in the `Styles`
+ // and `Music` folder.
+ "styleId": "examplelevel",
+ "musicId": "jackRussel",
+
+ // Relative path to the level's Lua script.
+ "luaFile": "Scripts/Levels/examplelevel.lua",
+
+ // Available difficulty multipliers for the level. `1` is always available
+ // by default, implicitly.
+ "difficultyMults": [1.5, 2, 2.5, 3, 0.5, 4]
+}
diff --git a/_RELEASE/Packs/workshopexample/Music/jackRussel.json b/_RELEASE/Packs/workshopexample/Music/jackRussel.json
new file mode 100644
index 00000000..0f026b0b
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Music/jackRussel.json
@@ -0,0 +1,24 @@
+{
+ // Music data id.
+ "id": "jackRussel",
+
+ // Music file.
+ "file_name": "jackRussel.ogg",
+
+ // Music information. If the music is copyrighted, you must obtain explicit
+ // permission from the authors before including it in your pack.
+ "name": "Jack Russel",
+ "album": "Caps On, Hats Off",
+ "author": "BOSSFIGHT",
+
+ // Music segments. Array of timestamps (in seconds) where the music can
+ // begin from. Useful to avoid repeating the same section over and over
+ // when the level is restarted.
+ "segments":
+ [
+ { "time": 32 },
+ { "time": 47 },
+ { "time": 98 },
+ { "time": 125 }
+ ]
+}
diff --git a/_RELEASE/Packs/workshopexample/Scripts/Levels/examplelevel.lua b/_RELEASE/Packs/workshopexample/Scripts/Levels/examplelevel.lua
new file mode 100644
index 00000000..69cc7fc1
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Scripts/Levels/examplelevel.lua
@@ -0,0 +1,92 @@
+-- Include useful files or existing libraries. These are found in the `Scripts`
+-- folder.
+u_execScript("utils.lua")
+u_execScript("common.lua")
+u_execScript("commonpatterns.lua")
+
+-- This function adds a pattern to the level "timeline" based on a numeric key.
+function addPattern(mKey)
+ if mKey == 0 then pAltBarrage(math.random(2, 4), 2)
+ elseif mKey == 1 then pMirrorSpiral(math.random(2, 5), getHalfSides() - 3)
+ elseif mKey == 2 then pBarrageSpiral(math.random(0, 3), 1, 1)
+ elseif mKey == 3 then pInverseBarrage(0)
+ elseif mKey == 4 then pTunnel(math.random(1, 3))
+ elseif mKey == 5 then pSpiral(l_getSides() * math.random(1, 2), 0)
+ end
+end
+
+-- Shuffle the keys, and then call them to add all the patterns.
+-- Shuffling is better than randomizing - it guarantees all the patterns will
+-- be called.
+keys = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 5, 5 }
+keys = shuffle(keys)
+index = 0
+achievementUnlocked = false
+
+-- `onInit` is an hardcoded function that is called when the level is first
+-- loaded. This can be used to setup initial level parameters.
+function onInit()
+ l_setSpeedMult(1.55)
+ l_setSpeedInc(0.125)
+ l_setSpeedMax(3.5)
+ l_setRotationSpeed(0.07)
+ l_setRotationSpeedMax(0.75)
+ l_setRotationSpeedInc(0.04)
+ l_setDelayMult(1.0)
+ l_setDelayInc(-0.01)
+ l_setFastSpin(0.0)
+ l_setSides(6)
+ l_setSidesMin(5)
+ l_setSidesMax(6)
+ l_setIncTime(15)
+
+ l_setPulseMin(75)
+ l_setPulseMax(91)
+ l_setPulseSpeed(1.2)
+ l_setPulseSpeedR(1)
+ l_setPulseDelayMax(23.9)
+
+ l_setBeatPulseMax(17)
+ l_setBeatPulseDelayMax(24.8)
+
+ enableSwapIfDMGreaterThan(2.5)
+ disableIncIfDMGreaterThan(3)
+end
+
+-- `onLoad` is an hardcoded function that is called when the level is started
+-- or restarted.
+function onLoad()
+ m_messageAdd("welcome to the example level", 130)
+ m_messageAdd("look at the level's files and edit them!", 150)
+end
+
+-- `onStep` is an hardcoded function that is called when the level "timeline"
+-- is empty. The level timeline is a queue of pending actions.
+-- `onStep` should generally contain your pattern spawning logic.
+function onStep()
+ addPattern(keys[index])
+ index = index + 1
+
+ if index - 1 == #keys then
+ index = 1
+ keys = shuffle(keys)
+ end
+end
+
+-- `onIncrement` is an hardcoded function that is called when the level
+-- difficulty is incremented.
+function onIncrement()
+ -- ...
+end
+
+-- `onUnload` is an hardcoded function that is called when the level is
+-- closed/restarted.
+function onUnload()
+ -- ...
+end
+
+-- `onUpdate` is an hardcoded function that is called every frame. `mFrameTime`
+-- represents the time delta between the current and previous frame.
+function onUpdate(mFrameTime)
+ -- ...
+end
diff --git a/_RELEASE/Packs/workshopexample/Scripts/alternativepatterns.lua b/_RELEASE/Packs/workshopexample/Scripts/alternativepatterns.lua
new file mode 100644
index 00000000..4cb9108a
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Scripts/alternativepatterns.lua
@@ -0,0 +1,119 @@
+u_execScript("common.lua")
+
+function pAltMirrorSpiral(mTimes, mExtra)
+ oldThickness = THICKNESS
+ THICKNESS = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelay(THICKNESS)
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+ for k = 1, #mTimes do
+ for i = 1, mTimes[k] do
+ rWallEx(startSide, mExtra)
+ if (k % 2) == 0 then
+ startSide = startSide + loopDir
+ else
+ startSide = startSide - loopDir
+ end
+ t_wait(delay)
+ end
+ end
+
+ THICKNESS = oldThickness
+
+ t_wait(getPerfectDelay(THICKNESS) * 6.5)
+end
+
+function randomArray(mNumber,mLower,mUpper)
+ a = {}
+ for k = 1, mNumber do
+ a[k] = math.random(mLower,mUpper)
+ end
+ return a
+end
+
+function pAltTunnel(mTimes,mFree)
+ oldThickness = THICKNESS
+ myThickness = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelay(myThickness) * 5
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+
+ THICKNESS = myThickness
+
+ for i = 0, mTimes do
+ if i < mTimes then
+ w_wall(startSide, myThickness + 5 * l_getSpeedMult() * delay)
+ end
+
+ cBarrageN(startSide + loopDir,mFree)
+ t_wait(delay)
+
+ loopDir = loopDir * -1
+ end
+
+ THICKNESS = oldThickness
+end
+
+function cycle(mSides)
+ eArray = {}
+ j = getRandomSide()
+ for i = 1, mSides do
+ eArray[i] = (i + j) % mSides + 1
+ end
+ return eArray
+end
+
+function pLadder(mTimes,mArray,myThickness)
+
+ delay = getPerfectDelay(myThickness)
+
+ local eArray = {}
+ l = 1
+ s = #mArray/l_getSides()
+ t = math.random(0,100)
+
+ for i = 1, mTimes do
+ q = (i+t) % s + 1
+ for k = 1, l_getSides() do
+ if(mArray[(q-1)*l_getSides() + k] ~= 0) then
+ eArray[l] = 1
+ else
+ eArray[l] = 0
+ end
+ l = l + 1
+ end
+
+ if i ~= mTimes then
+ for j = 1, 3 do
+ for k = 1,l_getSides() do
+ if(mArray[(q-1)*l_getSides() + k] == 2) then
+ eArray[l] = 1
+ else
+ eArray[l] = 0
+ end
+ l = l + 1
+ end
+ end
+ end
+ end
+
+ patternizer(eArray,myThickness)
+ t_wait(delay*2)
+
+end
+
+function patternizer(mArray,myThickness)
+ delay = getPerfectDelay(myThickness)
+ eArray = cycle(l_getSides())
+
+ j = math.floor((#mArray) / l_getSides())
+
+ for i = 1, j do
+ for k = 1, l_getSides() do
+ if mArray[(i - 1)*l_getSides() + k] == 1 then
+ w_wall(eArray[k], myThickness)
+ end
+ end
+ t_wait(delay)
+ end
+end \ No newline at end of file
diff --git a/_RELEASE/Packs/workshopexample/Scripts/common.lua b/_RELEASE/Packs/workshopexample/Scripts/common.lua
new file mode 100644
index 00000000..ef658ddd
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Scripts/common.lua
@@ -0,0 +1,109 @@
+-- common variables
+THICKNESS = 40.0;
+
+function enableSwapIfDMGreaterThan(mDM)
+ if(u_getDifficultyMult() > mDM) then
+ m_messageAdd(" difficulty > " ..mDM.. "\nswap enabled!", 65)
+ l_setSwapEnabled(true)
+ end
+end
+
+function disableIncIfDMGreaterThan(mDM)
+ if(u_getDifficultyMult() > mDM) then
+ m_messageAdd(" difficulty > " ..mDM.. "\nincrement disabled!", 65)
+ l_setIncEnabled(false)
+ end
+end
+
+-- getHalfSides: returns half the number of sides (integer)
+function getHalfSides() return math.ceil(l_getSides() / 2) end
+
+-- getRandomSide: returns random mSide
+function getRandomSide() return math.random(0, l_getSides() - 1) end
+
+-- getPlayerSide: gets the current side that the player is in
+function getPlayerSide()
+ local playerPosition = math.deg(u_getPlayerAngle())
+ local sideLength = (360 / l_getSides())
+ local offset = sideLength / 2
+
+ return math.floor((playerPosition + offset) % 360 / sideLength)
+end
+
+-- getRandomDir: returns either 1 or -1
+function getRandomDir()
+ return math.random(1, 2) * 2 - 3
+end
+
+-- getPerfectDelay: returns time to wait for two walls to be next to each other
+function getPerfectDelay(mThickness) return mThickness / (5.02 * u_getSpeedMultDM()) * u_getDelayMultDM() end
+
+-- getPerfectDelayDM: returns getPerfectDelay calculated with difficulty mutliplier
+function getPerfectDelayDM(mThickness) return mThickness / (5.02 * u_getSpeedMultDM()) * u_getDelayMultDM() end
+
+-- getPerfectThickness: returns a good THICKNESS value in relation to human reflexes
+function getPerfectThickness(mThickness) return mThickness * u_getSpeedMultDM() end
+
+-- getSideDistance: returns shortest distance from a side to another
+function getSideDistance(mSide1, mSide2)
+ local dist = math.abs(mSide2 % l_getSides() - mSide1 % l_getSides());
+ if (dist > getHalfSides()) then
+ dist = dist - (dist - getHalfSides());
+ end
+ return dist;
+end
+
+-- cWall: creates a wall with the common THICKNESS
+function cWall(mSide) w_wall(mSide, THICKNESS) end
+
+-- oWall: creates a wall opposite to the mSide passed
+function oWall(mSide) cWall(mSide + getHalfSides()) end
+
+-- rWall: union of cwall and owall (created 2 walls facing each other)
+function rWall(mSide)
+ cWall(mSide)
+ oWall(mSide)
+end
+
+-- cWallEx: creates a wall with mExtra walls attached to it
+function cWallEx(mSide, mExtra)
+ cWall(mSide);
+ loopDir = 1;
+
+ if mExtra < 0 then loopDir = -1 end
+ for i = 0, mExtra, loopDir do cWall(mSide + i) end
+end
+
+-- oWallEx: creates a wall with mExtra walls opposite to mSide
+function oWallEx(mSide, mExtra)
+ cWallEx(mSide + getHalfSides(), mExtra)
+end
+
+-- rWallEx: union of cwallex and owallex
+function rWallEx(mSide, mExtra)
+ cWallEx(mSide, mExtra)
+ oWallEx(mSide, mExtra)
+end
+
+-- cBarrageN: spawns a barrage of walls, with a free mSide plus mNeighbors
+function cBarrageN(mSide, mNeighbors)
+ for i = mNeighbors, l_getSides() - 2 - mNeighbors, 1 do
+ cWall(mSide + i + 1)
+ end
+end
+
+-- cBarrage: spawns a barrage of walls, with a single free mSide
+function cBarrage(mSide) cBarrageN(mSide, 0) end
+
+-- cBarrageOnlyN: spawns a barrage of wall, with only free mNeighbors
+function cBarrageOnlyN(mSide, mNeighbors)
+ cWall(mSide)
+ cBarrageN(mSide, mNeighbors)
+end
+
+-- cAltBarrage: spawns a barrage of alternate walls
+function cAltBarrage(mSide, mStep)
+ for i = 0, l_getSides() / mStep, 1 do
+ cWall(mSide + i * mStep)
+ end
+end \ No newline at end of file
diff --git a/_RELEASE/Packs/workshopexample/Scripts/commonpatterns.lua b/_RELEASE/Packs/workshopexample/Scripts/commonpatterns.lua
new file mode 100644
index 00000000..29129c8f
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Scripts/commonpatterns.lua
@@ -0,0 +1,209 @@
+u_execScript("common.lua")
+
+-- pAltBarrage: spawns a series of cAltBarrage
+function pAltBarrage(mTimes, mStep)
+ delay = getPerfectDelayDM(THICKNESS) * 5.6
+
+ for i = 0, mTimes do
+ cAltBarrage(i, mStep)
+ t_wait(delay)
+ end
+
+ t_wait(delay)
+end
+
+-- pSpiral: spawns a spiral of cWall
+function pSpiral(mTimes, mExtra)
+ oldThickness = THICKNESS
+ THICKNESS = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelay(THICKNESS)
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+ j = 0
+
+ for i = 0, mTimes do
+ cWall(startSide + j, mExtra)
+ j = j + loopDir
+ t_wait(delay)
+ end
+
+ THICKNESS = oldThickness
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 6.5)
+end
+
+-- pMirrorSpiral: spawns a spiral of rWallEx
+function pMirrorSpiral(mTimes, mExtra)
+ oldThickness = THICKNESS
+ THICKNESS = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelay(THICKNESS)
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+ j = 0
+
+ for i = 0, mTimes do
+ rWallEx(startSide + j, mExtra)
+ j = j + loopDir
+ t_wait(delay)
+ end
+
+ THICKNESS = oldThickness
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 6.5)
+end
+
+-- pMirrorSpiralDouble: spawns a spiral of rWallEx where you need to change direction
+function pMirrorSpiralDouble(mTimes, mExtra)
+ oldThickness = THICKNESS
+ THICKNESS = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelayDM(THICKNESS)
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+ j = 0
+
+ for i = 0, mTimes do
+ rWallEx(startSide + j, mExtra)
+ j = j + loopDir
+ t_wait(delay)
+ end
+
+ rWallEx(startSide + j, mExtra)
+ t_wait(delay * 0.9)
+
+ for i = 0, mTimes + 1 do
+ rWallEx(startSide + j, mExtra)
+ j = j - loopDir
+ t_wait(delay)
+ end
+
+ THICKNESS = oldThickness
+ t_wait(getPerfectDelayDM(THICKNESS) * 7.5)
+end
+
+-- pBarrageSpiral: spawns a spiral of cBarrage
+function pBarrageSpiral(mTimes, mDelayMult, mStep)
+ delay = getPerfectDelayDM(THICKNESS) * 5.6 * mDelayMult
+ startSide = getRandomSide()
+ loopDir = mStep * getRandomDir()
+ j = 0
+
+ for i = 0, mTimes do
+ cBarrage(startSide + j)
+ j = j + loopDir
+ t_wait(delay)
+ if(l_getSides() < 6) then t_wait(delay * 0.6) end
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 6.1)
+end
+
+-- pDMBarrageSpiral: spawns a spiral of cBarrage, with static delay
+function pDMBarrageSpiral(mTimes, mDelayMult, mStep)
+ delay = (getPerfectDelayDM(THICKNESS) * 5.42) * (mDelayMult / (u_getDifficultyMult() ^ 0.4)) * (u_getSpeedMultDM() ^ 0.35)
+ startSide = getRandomSide()
+ loopDir = mStep * getRandomDir()
+ j = 0
+
+ for i = 0, mTimes do
+ cBarrage(startSide + j)
+ j = j + loopDir
+ t_wait(delay)
+ if(l_getSides() < 6) then t_wait(delay * 0.49) end
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * (6.7 * (u_getDifficultyMult() ^ 0.7)))
+end
+
+-- pWallExVortex: spawns left-left right-right spiral patters
+function pWallExVortex(mTimes, mStep, mExtraMult)
+ delay = getPerfectDelayDM(THICKNESS) * 5.0
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+ currentSide = startSide
+
+ for j = 0, mTimes do
+ for i = 0, mStep do
+ currentSide = currentSide + loopDir
+ rWallEx(currentSide, loopDir * mExtraMult)
+ t_wait(delay)
+ end
+
+ loopDir = loopDir * -1
+
+ for i = 0, mStep + 1 do
+ currentSide = currentSide + loopDir;
+ rWallEx(currentSide, loopDir * mExtraMult)
+ t_wait(delay)
+ end
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 5.5)
+end
+
+-- pInverseBarrage: spawns two barrages who force you to turn 180 degrees
+function pInverseBarrage(mTimes)
+ delay = getPerfectDelayDM(THICKNESS) * 9.9
+ startSide = getRandomSide()
+
+ for i = 0, mTimes do
+ cBarrage(startSide)
+ t_wait(delay)
+ if(l_getSides() < 6) then t_wait(delay * 0.8) end
+ cBarrage(startSide + getHalfSides())
+ t_wait(delay)
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 2.5)
+end
+
+-- pRandomBarrage: spawns barrages with random side, and waits humanly-possible times depending on the sides distance
+function pRandomBarrage(mTimes, mDelayMult)
+ side = getRandomSide()
+ oldSide = 0
+
+ for i = 0, mTimes do
+ cBarrage(side)
+ oldSide = side
+ side = getRandomSide()
+ t_wait(getPerfectDelayDM(THICKNESS) * (2 + (getSideDistance(side, oldSide)*mDelayMult)))
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 5.6)
+end
+
+-- pMirrorWallStrip: spawns rWalls close to one another on the same side
+function pMirrorWallStrip(mTimes, mExtra)
+ delay = getPerfectDelayDM(THICKNESS) * 3.65
+ startSide = getRandomSide()
+
+ for i = 0, mTimes do
+ rWallEx(startSide, mExtra)
+ t_wait(delay)
+ end
+
+ t_wait(getPerfectDelayDM(THICKNESS) * 5.00)
+end
+
+-- pTunnel: forces you to circle around a very thick wall
+function pTunnel(mTimes)
+ oldThickness = THICKNESS
+ myThickness = getPerfectThickness(THICKNESS)
+ delay = getPerfectDelay(myThickness) * 5
+ startSide = getRandomSide()
+ loopDir = getRandomDir()
+
+ THICKNESS = myThickness
+
+ for i = 0, mTimes do
+ if i < mTimes then
+ w_wall(startSide, myThickness + 5 * u_getSpeedMultDM() * delay)
+ end
+
+ cBarrage(startSide + loopDir)
+ t_wait(delay)
+
+ loopDir = loopDir * -1
+ end
+
+ THICKNESS = oldThickness
+end \ No newline at end of file
diff --git a/_RELEASE/Packs/workshopexample/Scripts/utils.lua b/_RELEASE/Packs/workshopexample/Scripts/utils.lua
new file mode 100644
index 00000000..300f63ca
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Scripts/utils.lua
@@ -0,0 +1,28 @@
+-- initialize random seed
+math.randomseed(os.time())
+math.random()
+math.random()
+math.random()
+
+-- shuffle: shuffles an array
+function shuffle(t)
+ math.randomseed(os.time())
+ local iterations = #t
+ local j
+ for i = iterations, 2, -1 do
+ j = math.random(i)
+ t[i], t[j] = t[j], t[i]
+ end
+
+ return t
+end
+
+-- clamp: clamps a number between two values
+function clamp(input, min_val, max_val)
+ if input < min_val then
+ input = min_val
+ elseif input > max_val then
+ input = max_val
+ end
+ return input
+end \ No newline at end of file
diff --git a/_RELEASE/Packs/workshopexample/Styles/examplelevel.json b/_RELEASE/Packs/workshopexample/Styles/examplelevel.json
new file mode 100644
index 00000000..c1844946
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/Styles/examplelevel.json
@@ -0,0 +1,57 @@
+{
+ // Style data id. This id can be used in level JSON files and inside Lua
+ // scripts to set or change the visual style of a level.
+ "id": "examplelevel",
+
+ // Hue options. Continuously changes the hue of the level colors.
+ "hue_min": 0,
+ "hue_max": 360,
+ "hue_ping_pong": false, // Does the hue cycle, or go back and forth?
+ "hue_increment": 0.7, // How fast does the hue change?
+
+ // Pulse options. Periodically affects the level's colors, brightening and
+ // darkening them over a time interval.
+ "pulse_min": 0.0,
+ "pulse_max": 1.5,
+ "pulse_increment": 0.025,
+
+ // Pseudo-3D effects.
+ "3D_depth": 8, // How many "copies" of the level should be rendered?
+ "3D_skew": 0.15, // How intense is the tilt of the pseudo-3D effect?
+ "3D_spacing": 1.5, // How distant are 3D layers between each others?
+
+ // Main color.
+ "main": {
+ "main": true,
+ "dynamic": true, // Is the color affected by the hue options?
+ "value": [255, 0, 0, 255], // RGBA values.
+ "pulse": [-80, 75, 65, 0] // Per-channel pulse factors.
+ },
+
+ // Center polygon color. Can be one of:
+ // - "main"
+ // - "main_darkened"
+ // - { "legacy": true, "index": 0 }
+ // - { "legacy": false, "dynamic": true, "value": [...] ... }
+ "cap_color": "main_darkened",
+
+ // Background colors. Array of colors that are going to be applied to the
+ // background, in an alternate fashion.
+ "colors":
+ [
+ {
+ "dynamic": true,
+ "dynamic_offset": false,
+ "dynamic_darkness": 2.7,
+ "value": [0, 0, 0, 0],
+ "pulse": [0, 0, 0, 0]
+ },
+ {
+ "dynamic": true,
+ "dynamic_offset": false,
+ "dynamic_darkness": 3.5,
+ "value": [0, 0, 0, 0],
+ "pulse": [0, 0, 0, 0]
+ }
+ ]
+}
diff --git a/_RELEASE/Packs/workshopexample/pack.json b/_RELEASE/Packs/workshopexample/pack.json
new file mode 100644
index 00000000..a67d8184
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/pack.json
@@ -0,0 +1,27 @@
+{
+ // Packs must contain a `pack.json` file with the fields you see below.
+
+ // Disambiguator of the pack. This will be used to calculate a unique pack
+ // id. Every published level pack should have a totally unique id.
+ "disambiguator": "ohvrworkshopexample",
+
+ // Name of the pack. This will be displayed in the main menu and will be
+ // used to calculate a unique pack id.
+ "name": "example",
+
+ // Author of the pack. This will be displayed in the main menu and will be
+ // used to calculate a unique pack id.
+ "author": "vittorio romeo",
+
+ // Description of the pack. Currently unused, but will likely be displayed
+ // in-game in a future version of Open Hexagon.
+ "description": "Example pack for the Steam Workshop.",
+
+ // Version of the pack. This will be displayed in the main menu and will be
+ // used to calculate a unique pack id. Increment this to "invalidate" the
+ // previous unique pack id, or to signal the presence of major changes.
+ "version": 1,
+
+ // Priority of the pack in the pack selection list.
+ "priority": 0
+}
diff --git a/_RELEASE/Packs/workshopexample/workshop_example_level.png b/_RELEASE/Packs/workshopexample/workshop_example_level.png
new file mode 100644
index 00000000..d7e9d68d
--- /dev/null
+++ b/_RELEASE/Packs/workshopexample/workshop_example_level.png
Binary files differ
diff --git a/_RELEASE/config.json b/_RELEASE/config.json
index 6bd6154d..f900ca20 100644
--- a/_RELEASE/config.json
+++ b/_RELEASE/config.json
@@ -98,7 +98,7 @@
"timer_static" : true,
"vsync" : false,
"windowed_auto_resolution" : false,
- "windowed_height" : 900,
- "windowed_width" : 1440,
- "zoom_factor" : 0.8533333539962769
+ "windowed_height" : 600,
+ "windowed_width" : 800,
+ "zoom_factor" : 1.279999971389771
}
diff --git a/art/epilepsyWarning.psd b/art/epilepsyWarning.psd
new file mode 100644
index 00000000..6fb7c964
--- /dev/null
+++ b/art/epilepsyWarning.psd
Binary files differ
diff --git a/art/githubohlogo.png b/art/githubohlogo.png
new file mode 100644
index 00000000..46459ba5
--- /dev/null
+++ b/art/githubohlogo.png
Binary files differ
diff --git a/art/libraryhero.psd b/art/libraryhero.psd
new file mode 100644
index 00000000..2e15cef0
--- /dev/null
+++ b/art/libraryhero.psd
Binary files differ
diff --git a/include/SSVOpenHexagon/Core/MenuGame.hpp b/include/SSVOpenHexagon/Core/MenuGame.hpp
index 65404fc2..859a5f83 100644
--- a/include/SSVOpenHexagon/Core/MenuGame.hpp
+++ b/include/SSVOpenHexagon/Core/MenuGame.hpp
@@ -28,6 +28,7 @@ namespace hg
enum class States
{
+ EpilepsyWarning,
ETUser,
ETPass,
ETEmail,
@@ -70,7 +71,7 @@ private:
Config::getHeight() * Config::getZoomFactor()}}};
// TODO: change this to MWlcm when leaderboards are enabled
- States state{States::MWlcm};
+ States state{States::EpilepsyWarning};
ssvms::Menu optionsMenu;
ssvms::Menu welcomeMenu;
@@ -87,7 +88,8 @@ private:
sf::Sprite titleBar{assets.get<sf::Texture>("titleBar.png")},
creditsBar1{assets.get<sf::Texture>("creditsBar1.png")},
creditsBar2{assets.get<sf::Texture>("creditsBar2.png")},
- bottomBar{assets.get<sf::Texture>("bottomBar.png")};
+ bottomBar{assets.get<sf::Texture>("bottomBar.png")},
+ epilepsyWarning{assets.get<sf::Texture>("epilepsyWarning.png")};
std::vector<std::string> levelDataIds;
std::vector<float> diffMults;
diff --git a/prepare_release.sh b/prepare_release.sh
index 97eecb02..101a7ba5 100644
--- a/prepare_release.sh
+++ b/prepare_release.sh
@@ -22,6 +22,7 @@ cp ./_RELEASE/sfml-network-2.dll ./_PREPARED_RELEASE
cp ./_RELEASE/sfml-system-2.dll ./_PREPARED_RELEASE
cp ./_RELEASE/sfml-window-2.dll ./_PREPARED_RELEASE
cp ./_RELEASE/steam_api64.dll ./_PREPARED_RELEASE
+cp ./_RELEASE/discord_game_sdk.dll ./_PREPARED_RELEASE
cp ./misc/default_config.json ./_PREPARED_RELEASE/config.json
cp ./_RELEASE/steam_appid.txt ./_PREPARED_RELEASE
cp ./_RELEASE/windowed.bat ./_PREPARED_RELEASE
diff --git a/src/SSVOpenHexagon/Core/HGScripting.cpp b/src/SSVOpenHexagon/Core/HGScripting.cpp
index 5ea080b5..9ce13661 100644
--- a/src/SSVOpenHexagon/Core/HGScripting.cpp
+++ b/src/SSVOpenHexagon/Core/HGScripting.cpp
@@ -300,7 +300,7 @@ void HexagonGame::initLua_LevelControl()
std::string setDocString = "Set the `";
setDocString += name;
- setDocString += "` field of the level status to `$0`";
+ setDocString += "` field of the level status to `$0`.";
addLuaFn(std::string{"l_get"} + name, //
[this, pmd]() -> Type { return levelStatus.*pmd; })
@@ -415,7 +415,7 @@ void HexagonGame::initLua_StyleControl()
std::string setDocString = "Set the `";
setDocString += name;
- setDocString += "` field of the style data to `$0`";
+ setDocString += "` field of the style data to `$0`.";
addLuaFn(std::string{"s_get"} + name, //
[this, pmd]() -> Type { return styleData.*pmd; })
@@ -697,14 +697,13 @@ void HexagonGame::initLua()
initLua_Steam();
initLua_CustomWalls();
- // TODO: refactor this doc stuff and have a command line option to print
- // this:
- /*
+ // TODO: refactor doc stuff and have a command line option to print this:
+#if 0
ssvu::lo("hg::HexagonGame::initLua") << "Printing Lua Markdown docs\n\n";
printLuaDocs();
std::cout << "\n\n";
ssvu::lo("hg::HexagonGame::initLua") << "Done\n";
- */
+#endif
}
} // namespace hg
diff --git a/src/SSVOpenHexagon/Core/MenuGame.cpp b/src/SSVOpenHexagon/Core/MenuGame.cpp
index 053b71b5..aac22d6d 100644
--- a/src/SSVOpenHexagon/Core/MenuGame.cpp
+++ b/src/SSVOpenHexagon/Core/MenuGame.cpp
@@ -31,7 +31,6 @@ using namespace ssvuj;
namespace hg
{
-using s = States;
using ocs = Online::ConnectStat;
using ols = Online::LoginStat;
@@ -74,9 +73,6 @@ MenuGame::MenuGame(Steam::steam_manager& mSteamManager,
setIndex(0);
initMenus();
initInput();
-
- // TODO: remove when welcome screen is implemented
- playLocally();
}
void MenuGame::init(bool error)
@@ -104,7 +100,7 @@ void MenuGame::initAssets()
{
for(const auto& t : {"titleBar.png", "creditsBar1.png", "creditsBar2.png",
"creditsBar2b.png", "creditsBar2c.png", "creditsBar2d.png",
- "bottomBar.png"})
+ "bottomBar.png", "epilepsyWarning.png"})
{
assets.get<Texture>(t).setSmooth(true);
}
@@ -115,7 +111,8 @@ void MenuGame::playLocally()
assets.pSaveCurrent();
assets.pSetPlayingLocally(true);
enteredStr = "";
- state = assets.getLocalProfilesSize() == 0 ? s::ETLPNew : s::SLPSelect;
+ state = assets.getLocalProfilesSize() == 0 ? States::ETLPNew
+ : States::SLPSelect;
}
void MenuGame::initMenus()
@@ -152,7 +149,7 @@ void MenuGame::initMenus()
assets.pSaveCurrent();
assets.pSetPlayingLocally(false);
enteredStr = "";
- state = s::ETUser;
+ state = States::ETUser;
}) | whenConnectedAndUnlogged;
wlcm.create<i::Single>("logout", [] { Online::logout(); }) |
whenConnectedAndLogged;
@@ -177,12 +174,12 @@ void MenuGame::initMenus()
main.create<i::Goto>("audio", sfx);
main.create<i::Goto>("debug", debug) | whenNotOfficial;
main.create<i::Goto>("local profiles", localProfiles) | whenLocal;
- main.create<i::Single>("login screen", [this] { state = s::MWlcm; });
+ main.create<i::Single>("login screen", [this] { state = States::MWlcm; });
main.create<i::Toggle>("online", &Config::getOnline, &Config::setOnline);
main.create<i::Toggle>(
"official mode", &Config::getOfficial, &Config::setOfficial);
main.create<i::Single>("exit game", [this] { window.stop(); });
- main.create<i::Single>("back", [this] { state = s::SMain; });
+ main.create<i::Single>("back", [this] { state = States::SMain; });
resolution.create<i::Single>(
"auto", [this] { Config::setCurrentResolutionAuto(window); });
@@ -302,11 +299,11 @@ void MenuGame::initMenus()
localProfiles.create<i::Single>("change local profile", [this] {
enteredStr = "";
- state = s::SLPSelect;
+ state = States::SLPSelect;
});
localProfiles.create<i::Single>("new local profile", [this] {
enteredStr = "";
- state = s::SLPSelect;
+ state = States::SLPSelect;
});
localProfiles.create<i::GoBack>("back");
@@ -317,7 +314,7 @@ void MenuGame::initMenus()
friends.create<i::Single>("add friend", [this] {
enteredStr = "";
- state = s::ETFriend;
+ state = States::ETFriend;
});
friends.create<i::Single>(
"clear friends", [this] { assets.pClearTrackedNames(); });
@@ -329,11 +326,16 @@ void MenuGame::leftAction()
assets.playSound("beep.ogg");
touchDelay = 50.f;
- if(state == s::SLPSelect)
+ if(state == States::EpilepsyWarning)
+ {
+ // TODO: remove when welcome screen is implemented
+ playLocally();
+ }
+ else if(state == States::SLPSelect)
{
--profileIdx;
}
- else if(state == s::SMain)
+ else if(state == States::SMain)
{
setIndex(currentIndex - 1);
}
@@ -348,11 +350,16 @@ void MenuGame::rightAction()
assets.playSound("beep.ogg");
touchDelay = 50.f;
- if(state == s::SLPSelect)
+ if(state == States::EpilepsyWarning)
+ {
+ // TODO: remove when welcome screen is implemented
+ playLocally();
+ }
+ else if(state == States::SLPSelect)
{
++profileIdx;
}
- else if(state == s::SMain)
+ else if(state == States::SMain)
{
setIndex(currentIndex + 1);
}
@@ -366,7 +373,12 @@ void MenuGame::upAction()
assets.playSound("beep.ogg");
touchDelay = 50.f;
- if(state == s::SMain)
+ if(state == States::EpilepsyWarning)
+ {
+ // TODO: remove when welcome screen is implemented
+ playLocally();
+ }
+ else if(state == States::SMain)
{
++diffMultIdx;
}
@@ -380,7 +392,12 @@ void MenuGame::downAction()
assets.playSound("beep.ogg");
touchDelay = 50.f;
- if(state == s::SMain)
+ if(state == States::EpilepsyWarning)
+ {
+ // TODO: remove when welcome screen is implemented
+ playLocally();
+ }
+ else if(state == States::SMain)
{
--diffMultIdx;
}
@@ -394,12 +411,17 @@ void MenuGame::okAction()
assets.playSound("beep.ogg");
touchDelay = 50.f;
- if(state == s::SLPSelect)
+ if(state == States::EpilepsyWarning)
+ {
+ // TODO: remove when welcome screen is implemented
+ playLocally();
+ }
+ else if(state == States::SLPSelect)
{
assets.pSetCurrent(enteredStr);
- state = s::SMain;
+ state = States::SMain;
}
- else if(state == s::SMain)
+ else if(state == States::SMain)
{
window.setGameState(hexagonGame.getGame());
@@ -412,46 +434,46 @@ void MenuGame::okAction()
{
getCurrentMenu()->exec();
}
- else if(state == s::ETLPNew)
+ else if(state == States::ETLPNew)
{
if(!enteredStr.empty())
{
assets.pCreate(enteredStr);
assets.pSetCurrent(enteredStr);
- state = s::SMain;
+ state = States::SMain;
enteredStr = "";
}
}
- else if(state == s::ETFriend)
+ else if(state == States::ETFriend)
{
if(!enteredStr.empty() &&
!ssvu::contains(assets.pGetTrackedNames(), enteredStr))
{
assets.pAddTrackedName(enteredStr);
- state = s::SMain;
+ state = States::SMain;
enteredStr = "";
}
}
- else if(state == s::ETUser)
+ else if(state == States::ETUser)
{
if(!enteredStr.empty())
{
lrUser = enteredStr;
- state = s::ETPass;
+ state = States::ETPass;
enteredStr = "";
}
}
- else if(state == s::ETPass)
+ else if(state == States::ETPass)
{
if(!enteredStr.empty())
{
lrPass = enteredStr;
- state = s::SLogging;
+ state = States::SLogging;
enteredStr = "";
Online::tryLogin(lrUser, lrPass);
}
}
- else if(state == s::ETEmail)
+ else if(state == States::ETEmail)
{
if(!enteredStr.empty() && ssvu::contains(enteredStr, '@'))
{
@@ -467,46 +489,46 @@ void MenuGame::createProfileAction()
assets.playSound("beep.ogg");
if(!assets.pIsLocal())
{
- state = s::MWlcm;
+ state = States::MWlcm;
return;
}
- if(state == s::SLPSelect)
+ if(state == States::SLPSelect)
{
enteredStr = "";
- state = s::ETLPNew;
+ state = States::ETLPNew;
}
}
void MenuGame::selectProfileAction()
{
assets.playSound("beep.ogg");
- if(state != s::SMain)
+ if(state != States::SMain)
{
return;
}
if(!assets.pIsLocal())
{
- state = s::MWlcm;
+ state = States::MWlcm;
return;
}
enteredStr = "";
- state = s::SLPSelect;
+ state = States::SLPSelect;
}
void MenuGame::openOptionsAction()
{
assets.playSound("beep.ogg");
- if(state != s::SMain)
+ if(state != States::SMain)
{
return;
}
- state = s::MOpts;
+ state = States::MOpts;
}
void MenuGame::selectPackAction()
{
assets.playSound("beep.ogg");
- if(state == s::SMain)
+ if(state == States::SMain)
{
const auto& p(assets.getPackInfos());
packIdx = ssvu::getMod(packIdx + 1, p.size());
@@ -562,12 +584,13 @@ void MenuGame::initInput()
}
else
{
- state = s::SMain;
+ state = States::SMain;
}
}
- else if((state == s::ETFriend || state == s::SLPSelect) && valid)
+ else if((state == States::ETFriend || state == States::SLPSelect) &&
+ valid)
{
- state = s::SMain;
+ state = States::SMain;
}
},
t::Once);
@@ -575,7 +598,7 @@ void MenuGame::initInput()
game.addInput(
Config::getTriggerExit(),
[this](ssvu::FT mFT) {
- if(state != s::MOpts)
+ if(state != States::MOpts)
{
exitTimer += mFT;
}
@@ -797,7 +820,7 @@ void MenuGame::updateLeaderboard()
void MenuGame::updateFriends()
{
- if(state != s::SMain)
+ if(state != States::SMain)
{
return;
}
@@ -886,6 +909,10 @@ void MenuGame::refreshCamera()
bottomBar.setOrigin({0, 56.f * 2.f});
bottomBar.setScale({scaleFactor / 2.f, scaleFactor / 2.f});
bottomBar.setPosition(sf::Vector2f(0, h));
+
+ epilepsyWarning.setOrigin(getLocalCenter(epilepsyWarning));
+ epilepsyWarning.setPosition({1024 / (2.f / scaleFactor), 768 / 2.f - 50});
+ epilepsyWarning.setScale({0.36f, 0.36f});
}
void MenuGame::update(ssvu::FT mFT)
@@ -987,7 +1014,7 @@ void MenuGame::update(ssvu::FT mFT)
// If connection is lost, kick the player back into welcome screen
if(!assets.pIsLocal() && Online::getConnectionStatus() != ocs::Connected)
{
- state = s::MWlcm;
+ state = States::MWlcm;
}
updateLeaderboard();
@@ -1000,7 +1027,7 @@ void MenuGame::update(ssvu::FT mFT)
if(isEnteringText())
{
- unsigned int limit{state == s::ETEmail ? 40u : 18u};
+ unsigned int limit{state == States::ETEmail ? 40u : 18u};
for(const auto& c : enteredChars)
{
if(enteredStr.size() < limit &&
@@ -1011,12 +1038,12 @@ void MenuGame::update(ssvu::FT mFT)
}
}
}
- else if(state == s::SLPSelect)
+ else if(state == States::SLPSelect)
{
enteredStr =
ssvu::getByModIdx(assets.getLocalProfileNames(), profileIdx);
}
- else if(state == s::SMain)
+ else if(state == States::SMain)
{
styleData.update(mFT);
backgroundCamera.turn(levelStatus.rotationSpeed * 10.f);
@@ -1027,21 +1054,21 @@ void MenuGame::update(ssvu::FT mFT)
Online::requestLeaderboardIfNeeded(levelData->id, diffMult);
}
}
- else if(state == s::SLogging)
+ else if(state == States::SLogging)
{
if(Online::getLoginStatus() == ols::Logged)
{
- state = Online::getNewUserReg() ? s::ETEmail : s::SMain;
+ state = Online::getNewUserReg() ? States::ETEmail : States::SMain;
}
else if(Online::getLoginStatus() == ols::Unlogged)
{
- state = s::MWlcm;
+ state = States::MWlcm;
}
}
- if(state == s::ETEmail && !Online::getNewUserReg())
+ if(state == States::ETEmail && !Online::getNewUserReg())
{
- state = s::SMain;
+ state = States::SMain;
}
enteredChars.clear();
@@ -1050,16 +1077,17 @@ void MenuGame::update(ssvu::FT mFT)
void MenuGame::draw()
{
styleData.computeColors();
- window.clear(state != s::SMain ? Color::Black : styleData.getColors()[0]);
+ window.clear(
+ state != States::SMain ? Color::Black : styleData.getColors()[0]);
backgroundCamera.apply();
- if(state == s::SMain)
+ if(state == States::SMain)
{
styleData.drawBackground(window, ssvs::zeroVec2f, levelStatus);
}
overlayCamera.apply();
- if(state == s::SMain)
+ if(state == States::SMain)
{
drawLevelSelection();
render(bottomBar);
@@ -1068,18 +1096,24 @@ void MenuGame::draw()
{
drawEnteringText();
}
- else if(state == s::SLPSelect)
+ else if(state == States::SLPSelect)
{
drawProfileSelection();
}
- else if(state == s::MOpts)
+ else if(state == States::MOpts)
{
drawOptions();
}
- else if(state == s::MWlcm)
+ else if(state == States::MWlcm)
{
drawWelcome();
}
+ else if(state == States::EpilepsyWarning)
+ {
+ render(epilepsyWarning);
+ renderText("press any key to continue", txtProf, {20, 768 - 35});
+ return;
+ }
render(titleBar);
render(creditsBar1);
@@ -1253,11 +1287,11 @@ void MenuGame::drawEnteringText()
string title;
switch(state)
{
- case s::ETUser: title = "insert username"; break;
- case s::ETPass: title = "insert password"; break;
- case s::ETEmail: title = "insert email"; break;
- case s::ETFriend: title = "add friend"; break;
- case s::ETLPNew: title = "create local profile"; break;
+ case States::ETUser: title = "insert username"; break;
+ case States::ETPass: title = "insert password"; break;
+ case States::ETEmail: title = "insert email"; break;
+ case States::ETFriend: title = "add friend"; break;
+ case States::ETLPNew: title = "create local profile"; break;
default: throw;
}
@@ -1265,8 +1299,9 @@ void MenuGame::drawEnteringText()
renderText("insert text", txtProf, {20, 768 - 375});
renderText("press enter when done", txtProf, {20, 768 - 335});
renderText("keep esc pressed to exit", txtProf, {20, 768 - 315});
- renderText(state == s::ETPass ? string(enteredStr.size(), '*') : enteredStr,
- txtLName, {20, 768 - 245 - 40}, (state == s::ETEmail) ? 32 : 65);
+ renderText(
+ state == States::ETPass ? string(enteredStr.size(), '*') : enteredStr,
+ txtLName, {20, 768 - 245 - 40}, (state == States::ETEmail) ? 32 : 65);
}
void MenuGame::drawProfileSelection()
{
diff --git a/src/SSVOpenHexagon/Global/Config.cpp b/src/SSVOpenHexagon/Global/Config.cpp
index ecdf0d69..2700090b 100644
--- a/src/SSVOpenHexagon/Global/Config.cpp
+++ b/src/SSVOpenHexagon/Global/Config.cpp
@@ -591,12 +591,12 @@ bool SSVU_ATTRIBUTE(pure) getFullscreen()
float SSVU_ATTRIBUTE(const) getVersion()
{
- return 2.f;
+ return 2.01f;
}
const char* SSVU_ATTRIBUTE(const) getVersionString()
{
- return "2.0";
+ return "2.01";
}
bool SSVU_ATTRIBUTE(pure) getWindowedAutoResolution()
diff --git a/webpage/github-pandoc.css b/webpage/github-pandoc.css
new file mode 100644
index 00000000..74582305
--- /dev/null
+++ b/webpage/github-pandoc.css
@@ -0,0 +1,426 @@
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9.
+ * Hide the `template` element in IE, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background: transparent;
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.go-top {
+position: fixed;
+bottom: 2em;
+right: 2em;
+text-decoration: none;
+background-color: #E0E0E0;
+font-size: 12px;
+padding: 1em;
+display: inline;
+}
+
+/* Github css */
+
+html,body{ margin: auto;
+ padding-right: 1em;
+ padding-left: 1em;
+ max-width: 88em; color:black;}*:not('#mkdbuttons'){margin:0;padding:0}body{font:13.34px helvetica,arial,freesans,clean,sans-serif;-webkit-font-smoothing:subpixel-antialiased;line-height:1.4;padding:3px;background:#fff;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px}p{margin:1em 0}a{color:#4183c4;text-decoration:none}body{background-color:#fff;padding:30px;margin:15px;font-size:14px;line-height:1.6}body>*:first-child{margin-top:0!important}body>*:last-child{margin-bottom:0!important}@media screen{body{box-shadow:0 0 0 1px #cacaca,0 0 0 4px #eee}}h1,h2,h3,h4,h5,h6{margin:20px 0 10px;padding:0;font-weight:bold;-webkit-font-smoothing:subpixel-antialiased;cursor:text}h1{font-size:28px;color:#000}h2{font-size:24px;border-bottom:1px solid #ccc;color:#000}h3{font-size:18px;color:#333}h4{font-size:16px;color:#333}h5{font-size:14px;color:#333}h6{color:#777;font-size:14px}p,blockquote,table,pre{margin:15px 0}ul{padding-left:30px}ol{padding-left:30px}ol li ul:first-of-type{margin-top:0}hr{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;border:0 none;color:#ccc;height:4px;padding:0}body>h2:first-child{margin-top:0;padding-top:0}body>h1:first-child{margin-top:0;padding-top:0}body>h1:first-child+h2{margin-top:0;padding-top:0}body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child{margin-top:0;padding-top:0}a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6{margin-top:0;padding-top:0}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,ul li>:first-child,ol li>:first-child{margin-top:0}dl{padding:0}dl dt{font-size:14px;font-weight:bold;font-style:italic;padding:0;margin:15px 0 5px}dl dt:first-child{padding:0}dl dt>:first-child{margin-top:0}dl dt>:last-child{margin-bottom:0}dl dd{margin:0 0 15px;padding:0 15px}dl dd>:first-child{margin-top:0}dl dd>:last-child{margin-bottom:0}blockquote{border-left:4px solid #DDD;padding:0 15px;color:#777}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}table{border-collapse:collapse;border-spacing:0;font-size:100%;font:inherit}table th{font-weight:bold;border:1px solid #ccc;padding:6px 13px}table td{border:1px solid #ccc;padding:6px 13px}table tr{border-top:1px solid #ccc;background-color:#fff}table tr:nth-child(2n){background-color:#f8f8f8}img{max-width:100%}code,tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px;font-family:Consolas,'Liberation Mono',Courier,monospace;font-size:12px;color:#333}pre>code{margin:0;padding:0;white-space:pre;border:0;background:transparent}.highlight pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre code,pre tt{background-color:transparent;border:0}.poetry pre{font-family:Georgia,Garamond,serif!important;font-style:italic;font-size:110%!important;line-height:1.6em;display:block;margin-left:1em}.poetry pre code{font-family:Georgia,Garamond,serif!important;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;white-space:pre-wrap}sup,sub,a.footnote{font-size:1.4ex;height:0;line-height:1;vertical-align:super;position:relative}sub{vertical-align:sub;top:-1px}@media print{body{background:#fff}img,pre,blockquote,table,figure{page-break-inside:avoid}body{background:#fff;border:0}code{background-color:#fff;color:#333!important;padding:0 .2em;border:1px solid #dedede}pre{background:#fff}pre code{background-color:white!important;overflow:visible}}@media screen{body.inverted{color:#eee!important;border-color:#555;box-shadow:none}.inverted body,.inverted hr .inverted p,.inverted td,.inverted li,.inverted h1,.inverted h2,.inverted h3,.inverted h4,.inverted h5,.inverted h6,.inverted th,.inverted .math,.inverted caption,.inverted dd,.inverted dt,.inverted blockquote{color:#eee!important;border-color:#555;box-shadow:none}.inverted td,.inverted th{background:#333}.inverted h2{border-color:#555}.inverted hr{border-color:#777;border-width:1px!important}::selection{background:rgba(157,193,200,0.5)}h1::selection{background-color:rgba(45,156,208,0.3)}h2::selection{background-color:rgba(90,182,224,0.3)}h3::selection,h4::selection,h5::selection,h6::selection,li::selection,ol::selection{background-color:rgba(133,201,232,0.3)}code::selection{background-color:rgba(0,0,0,0.7);color:#eee}code span::selection{background-color:rgba(0,0,0,0.7)!important;color:#eee!important}a::selection{background-color:rgba(255,230,102,0.2)}.inverted a::selection{background-color:rgba(255,230,102,0.6)}td::selection,th::selection,caption::selection{background-color:rgba(180,237,95,0.5)}.inverted{background:#0b2531;background:#252a2a}.inverted body{background:#252a2a}.inverted a{color:#acd1d5}}.highlight .c{color:#998;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .k,.highlight .o{font-weight:bold}.highlight .cm{color:#998;font-style:italic}.highlight .cp{color:#999;font-weight:bold}.highlight .c1{color:#998;font-style:italic}.highlight .cs{color:#999;font-weight:bold;font-style:italic}.highlight .gd{color:#000;background-color:#fdd}.highlight .gd .x{color:#000;background-color:#faa}.highlight .ge{font-style:italic}.highlight .gr{color:#a00}.highlight .gh{color:#999}.highlight .gi{color:#000;background-color:#dfd}.highlight .gi .x{color:#000;background-color:#afa}.highlight .go{color:#888}.highlight .gp{color:#555}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#a00}.highlight .kc,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr{font-weight:bold}.highlight .kt{color:#458;font-weight:bold}.highlight .m{color:#099}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .nb{color:#0086b3}.highlight .nc{color:#458;font-weight:bold}.highlight .no{color:#008080}.highlight .ni{color:#800080}.highlight .ne,.highlight .nf{color:#900;font-weight:bold}.highlight .nn{color:#555}.highlight .nt{color:#000080}.highlight .nv{color:#008080}.highlight .ow{font-weight:bold}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#099}.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .se,.highlight .sh,.highlight .si,.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .bp{color:#999}.highlight .vc,.highlight .vg,.highlight .vi{color:#008080}.highlight .il{color:#099}.highlight .gc{color:#999;background-color:#eaf2f5}.type-csharp .highlight .k,.type-csharp .highlight .kt{color:#00F}.type-csharp .highlight .nf{color:#000;font-weight:normal}.type-csharp .highlight .nc{color:#2b91af}.type-csharp .highlight .nn{color:#000}.type-csharp .highlight .s,.type-csharp .highlight .sc{color:#a31515}
+
+ html{ background-color: #002d54;}
diff --git a/webpage/index.html b/webpage/index.html
index 2c4653a7..340cec71 100644
--- a/webpage/index.html
+++ b/webpage/index.html
@@ -11226,7 +11226,8 @@
<!-- <hr class="divider my-4" /> -->
</div>
<div class="col-lg-8 align-self-baseline">
- <p class="fakestroke text-white font-weight-bold mb-5">The <i>fast-paced</i>, <i>adrenaline-inducing</i>
+ <p class="fakestroke text-white font-weight-bold mb-5">The <i>fast-paced</i>,
+ <i>adrenaline-inducing</i>
arcade experience open to customization.</p>
</div>
</div>
@@ -11296,23 +11297,22 @@
<h2 class="fakestroke text-white mt-0">Master the Art of Rotation</h2>
<p class="fakestroke text-white mb-4">Get Open Hexagon on Steam now!</p>
- <a style="width:240px; margin-right:9px" class="btn btn-light btn-xl js-scroll-trigger" target="_blank" href="https://store.steampowered.com/app/1358090/Open_Hexagon/">
+ <a style="width:420px" class="btn btn-light btn-xl js-scroll-trigger"
+ target="_blank" href="https://store.steampowered.com/app/1358090/Open_Hexagon/">
<i class="fab fa-steam fa-4x mb-3"></i>
- <div>Get Open Hexagon</div>
+ <div style="font-size: x-large">Get Open Hexagon</div>
</a>
- <a style="width:240px; margin-left:9px"class="btn btn-light btn-xl js-scroll-trigger" target="_blank" href="https://discord.me/openhexagon">
- <i class="fab fa-discord fa-4x mb-3"></i>
- <div>Official Discord</div>
- </a>
<!-- Button trigger modal -->
</div>
+
+
<!--
<div class="col-lg-8 text-center" style="zoom:75%; margin-top: 6px">
<a target="_blank" href="https://github.com/SuperV1234/quakevr/blob/master/README.md"
@@ -11323,6 +11323,27 @@
</div>
-->
</div>
+ <div class="row justify-content-center text-center" style="padding-top: 20px; zoom: 65%">
+
+ <div>
+
+ <a style="width:310px; margin-right:9px" class="btn btn-light btn-xl js-scroll-trigger" target="_blank"
+ href="https://discord.me/openhexagon">
+ <i class="fab fa-discord fa-4x mb-3"></i>
+
+ <div style="font-size: large">Official Discord</div>
+ </a>
+
+ <a style="width:310px; margin-left:9px" class="btn btn-light btn-xl js-scroll-trigger" target="_blank"
+ href="https://openhexagon.org/workshop">
+ <i class="fas fa-book fa-4x mb-3"></i>
+
+ <div style="font-size: large">Workshop Tutorial</div>
+ </a>
+ </div>
+
+ <!-- Button trigger modal -->
+ </div>
</div>
</section>
<!--
diff --git a/webpage/mk_workshop_tutorial.sh b/webpage/mk_workshop_tutorial.sh
new file mode 100644
index 00000000..ca37c24a
--- /dev/null
+++ b/webpage/mk_workshop_tutorial.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+pandoc workshop_tutorial.md --css="github-pandoc.css" --toc -o workshop.html --standalone -V title:"" --metadata title="Open Hexagon Workshop Tutorial"
+echo -e "<div><p align=\"center\"><img style=\"width: 100%; height: auto;\" src=\"https://vittorioromeo.info/Misc/Linked/ohworkshopheader.png\"></p></div>\n\n\n$(cat workshop.html)" > workshop.html
diff --git a/webpage/workshop.html b/webpage/workshop.html
new file mode 100644
index 00000000..fb66bfb8
--- /dev/null
+++ b/webpage/workshop.html
@@ -0,0 +1,283 @@
+<div><p align="center"><img style="width: 100%; height: auto;" src="https://vittorioromeo.info/Misc/Linked/ohworkshopheader.png"></p></div>
+
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
+<head>
+ <meta charset="utf-8" />
+ <meta name="generator" content="pandoc" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
+ <title>Open Hexagon Workshop Tutorial</title>
+ <style>
+ code{white-space: pre-wrap;}
+ span.smallcaps{font-variant: small-caps;}
+ span.underline{text-decoration: underline;}
+ div.column{display: inline-block; vertical-align: top; width: 50%;}
+ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
+ ul.task-list{list-style: none;}
+ </style>
+ <link rel="stylesheet" href="github-pandoc.css" />
+ <!--[if lt IE 9]>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
+ <![endif]-->
+</head>
+<body>
+<nav id="TOC" role="doc-toc">
+<ul>
+<li><a href="#workshop-tutorial">Workshop Tutorial</a>
+<ul>
+<li><a href="#getting-started">Getting Started</a>
+<ul>
+<li><a href="#prerequisites">Prerequisites</a></li>
+<li><a href="#content-structure">Content Structure</a></li>
+</ul></li>
+<li><a href="#creating-your-first-custom-level">Creating your First Custom Level</a></li>
+<li><a href="#uploading-to-the-steam-workshop">Uploading to the Steam Workshop</a></li>
+<li><a href="#lua-reference">Lua Reference</a></li>
+<li><a href="#where-to-get-help">Where to get Help</a></li>
+</ul></li>
+</ul>
+</nav>
+<h1 id="workshop-tutorial">Workshop Tutorial</h1>
+<p>This document will guide you through the process of creating a new Open Hexagon level pack and upload it on the Steam Workshop.</p>
+<h2 id="getting-started">Getting Started</h2>
+<h3 id="prerequisites">Prerequisites</h3>
+<p>Open Hexagon uses <a href="http://www.lua.org/"><strong>Lua</strong></a> as the main scripting language for custom levels, alongside <a href="https://www.json.org/json-en.html"><strong>JSON</strong></a> for level metadata and configuration files.</p>
+<p>It is recommended to be somewhat familiar with both languages before diving into Open Hexagon content creation, but if you’re feeling brave and have previous programming experience you can just try to “wing it” by modifying existing levels and learning on the fly. A quick and fun way of learning the syntax of the above languages is the “Learn X in Y minutes” website:</p>
+<ul>
+<li><p><a href="https://learnxinyminutes.com/docs/lua/">Learn X in Y minutes: Lua</a></p></li>
+<li><p><a href="https://learnxinyminutes.com/docs/json/">Learn X in Y minutes: JSON</a></p></li>
+</ul>
+<h3 id="content-structure">Content Structure</h3>
+<p>Open Hexagon custom content is organized in the following hierarchy:</p>
+<ul>
+<li><strong>Level Pack</strong> <em>(folder)</em>
+<ul>
+<li><strong><code>pack.json</code></strong> <em>(main JSON metadata file)</em></li>
+<li><strong>Levels</strong> <em>(folder containing level JSON metadata files)</em>
+<ul>
+<li><strong><code>mylevel.json</code></strong></li>
+</ul></li>
+<li><strong>Music</strong> <em>(folder containing music <code>.ogg</code> and JSON metadata files)</em>
+<ul>
+<li><strong><code>mysong.ogg</code></strong></li>
+<li><strong><code>mysong.json</code></strong></li>
+</ul></li>
+<li><strong>Scripts</strong> <em>(folder containing <code>.lua</code> scripts)</em>
+<ul>
+<li><strong><code>mylevel.lua</code></strong></li>
+</ul></li>
+<li><strong>Styles</strong> <em>(folder containing visual style JSON metadata files)</em>
+<ul>
+<li><strong><code>mystyle.json</code></strong></li>
+</ul></li>
+</ul></li>
+</ul>
+<p>Therefore, the main unit of work that can be published and installed is a <strong>pack</strong>. Inside a pack, you can have an arbitrary number of <strong>levels</strong>, <strong>musics</strong>, <strong>scripts</strong>, and <strong>styles</strong>. A completed level requires a <strong>level <code>.json</code> metadata file</strong>, <strong>music <code>.ogg</code> file</strong>, <strong>music <code>.json</code> metadata file</strong>, <strong>level <code>.lua</code> script</strong>, and a <strong>style <code>.json</code> file</strong>. All the separate files are linked together by <strong>textual identifiers</strong>, specified in the <code>.json</code> metadata files.</p>
+<h2 id="creating-your-first-custom-level">Creating your First Custom Level</h2>
+<p>The easiest way to get started is to subscribe to the <a href="https://steamcommunity.com/sharedfiles/filedetails/?id=2157745755">“Example Workshop Level”</a> on the Steam Workshop, navigate to its contents (usually <code>C:/Program Files (x86)/Steam/steamapps/workshop/content/1358090/2157745755</code>), and copy-paste that folder into your <code>Open Hexagon/Packs/</code> folder<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>.</p>
+<p>After that, rename the pasted <code>2157745755</code> to something else (e.g. <code>MyFirstPack</code>), and start exploring its contents with your favorite text editor (personally, I use <a href="https://code.visualstudio.com/">Visual Studio Code</a>).</p>
+<p>Delete the <code>Example Workshop Level.workshop.json</code> file (as it will be generated automatically when you upload your pack to the workshop), and the preview image <code>.png</code> file, as you will provide one yourself later.</p>
+<p>Start by taking a look at <code>pack.json</code> - there are multiple fields to change. Note that Open Hexagon expects every single level pack to have a unique id - therefore, to minimize the chance of collisions between packs made by different authors, you should specify a sufficiently unique <code>"disambiguator"</code> that is very unlikely someone else might accidentally use. Such field will not be displayed in game, so it can be completely arbitrary. The rest of the fields also participate in the calculation of the final pack id, so all of your packs can share the same <code>"disambiguator"</code>, provided that they do have different names (as they rightfully should). The <code>"version"</code> field is useful for keeping track of changes and updates of your custom level pack. You should bump it up by one whenever you make changes to your pack and re-upload it to the workshop.</p>
+<p>After that, you should explore the other folders and see how things work. The <code>.json</code> and <code>.lua</code> files are commented, so you should have enough context to figure out how things are linked together and what role they play.</p>
+<p>The level logic is stored in <code>Scripts/Levels/examplelevel.lua</code>. Please refer to the <a href="#lua-reference">Lua Reference</a> to figure out what the used functions do, and to discover new functions to play around with. You will notice a bunch of <code>u_execScript</code> calls at the top of the Lua file - these are other scripts that the level script depends on. Dependencies should always be provided as part of your pack folder<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>.</p>
+<p>Now it’s up to you! Explore the dependencies, experiment with the code, add your own music<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>, and - when you’re satisfied - share your pack on the workshop and ask for feedback from other players, in order to perfect it. Keep in mind that you can join our official Discord before or during level development to ask for help and share your ideas - see <a href="#where-to-get-help">Where to get Help</a> for more information.</p>
+<h2 id="uploading-to-the-steam-workshop">Uploading to the Steam Workshop</h2>
+<p>The current recommended way of uploading your level to the Steam Workshop is to use the provided <code>OHWorkshopUploader.exe</code> command-line tool available in your Open Hexagon folder<a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>.</p>
+<p>When running the tool, you’ll see a few options:</p>
+<pre class="text"><code>Enter one of the following options:
+0. Create a new workshop item.
+1. Upload contents of an existing workshop item.
+2. Set preview image of an existing workshop item.
+3. Exit</code></pre>
+<p>If you intend to upload a brand new level, then write <code>0</code> and press <em>enter</em>. On success, the program will give you back a workshop item id:</p>
+<pre class="text"><code>Successfully created new workshop item: 2165863494.</code></pre>
+<p>Copy that id to your clipboard as you’re going to need it for later. Once you have finished creating and testing your custom level, you should have a folder under <code>Packs/</code> containing all its data. To upload it, write <code>1</code> and press <em>enter</em>. You will be prompted to enter the workshop item id you want to upload data to - you should now paste the id you previously copied<a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a>.</p>
+<p>You will then be prompted to enter the path to the folder containing the data. Please insert an absolute path to the pack folder (e.g. <code>C:/MySteamLibrary/common/Open Hexagon/Packs/MyFirstPack</code>) and keep following the instructions on screen. Your data should now be uploaded to the workshop.</p>
+<p>Finally, you can write <code>2</code> and press <em>enter</em> to add a preview image to your workshop item. A 620x620 <code>.png</code> image is recommended for this task - you will need to provide an absolute path to the image similarly to what you have done in the previous step.</p>
+<h2 id="lua-reference">Lua Reference</h2>
+<!-- Generated from Open Hexagon v2.01. -->
+<ul>
+<li><p><strong><code>void u_log(string message)</code></strong>: Print out <code>message</code> to the console.</p></li>
+<li><p><strong><code>void u_execScript(string scriptFilename)</code></strong>: Execute the script located at <code>&lt;pack&gt;/Scripts/scriptFilename</code>.</p></li>
+<li><p><strong><code>void u_playSound(string soundId)</code></strong>: Play the sound with id <code>soundId</code>. The id must be registered in <code>assets.json</code>, under <code>"soundBuffers"</code>.</p></li>
+<li><p><strong><code>void u_setMusic(string musicId)</code></strong>: Stop the current music and play the music with id <code>musicId</code>. The id is defined in the music <code>.json</code> file, under <code>"id"</code>.</p></li>
+<li><p><strong><code>void u_setMusicSegment(string musicId, int segment)</code></strong>: Stop the current music and play the music with id <code>musicId</code>, starting at segment <code>segment</code>. Segments are defined in the music <code>.json</code> file, under <code>"segments"</code>.</p></li>
+<li><p><strong><code>void u_setMusicSeconds(string musicId, float time)</code></strong>: Stop the current music and play the music with id <code>musicId</code>, starting at time <code>time</code> (in seconds).</p></li>
+<li><p><strong><code>bool u_isKeyPressed(int keyCode)</code></strong>: Return <code>true</code> if the keyboard key with code <code>keyCode</code> is being pressed, <code>false</code> otherwise. The key code must match the definition of the SFML <code>sf::Keyboard::Key</code> enumeration.</p></li>
+<li><p><strong><code>void u_haltTime(double duration)</code></strong>: Pause the game timer for <code>duration</code> seconds.</p></li>
+<li><p><strong><code>void u_timelineWait(double duration)</code></strong>: <em>Add to the main timeline</em>: wait for <code>duration</code> sixths of a second.</p></li>
+<li><p><strong><code>void u_clearWalls()</code></strong>: Remove all existing walls.</p></li>
+<li><p><strong><code>float u_getPlayerAngle()</code></strong>: Return the current angle of the player, in radians.</p></li>
+<li><p><strong><code>void u_setPlayerAngle(float angle)</code></strong>: Set the current angle of the player to <code>angle</code>, in radians.</p></li>
+<li><p><strong><code>bool u_isMouseButtonPressed(int buttonCode)</code></strong>: Return <code>true</code> if the mouse button with code <code>buttonCode</code> is being pressed, <code>false</code> otherwise. The button code must match the definition of the SFML <code>sf::Mouse::Button</code> enumeration.</p></li>
+<li><p><strong><code>bool u_isFastSpinning()</code></strong>: Return <code>true</code> if the camera is currently “fast spinning”, <code>false</code> otherwise.</p></li>
+<li><p><strong><code>void u_forceIncrement()</code></strong>: Immediately force a difficulty increment, regardless of the chosen automatic increment parameters.</p></li>
+<li><p><strong><code>void u_kill()</code></strong>: <em>Add to the main timeline</em>: kill the player.</p></li>
+<li><p><strong><code>void u_eventKill()</code></strong>: <em>Add to the event timeline</em>: kill the player.</p></li>
+<li><p><strong><code>float u_getDifficultyMult()</code></strong>: Return the current difficulty multiplier.</p></li>
+<li><p><strong><code>float u_getSpeedMultDM()</code></strong>: Return the current speed multiplier, adjusted for the chosen difficulty multiplier.</p></li>
+<li><p><strong><code>float u_getDelayMultDM()</code></strong>: Return the current delay multiplier, adjusted for the chosen difficulty multiplier.</p></li>
+<li><p><strong><code>void m_messageAdd(string message, double duration)</code></strong>: <em>Add to the event timeline</em>: print a message with text <code>message</code> for <code>duration</code> seconds. The message will only be printed during the first run of the level.</p></li>
+<li><p><strong><code>void m_messageAddImportant(string message, double duration)</code></strong>: <em>Add to the event timeline</em>: print a message with text <code>message</code> for <code>duration</code> seconds. The message will be printed during every run of the level.</p></li>
+<li><p><strong><code>void m_messageAddImportantSilent(string message, double duration)</code></strong>: <em>Add to the event timeline</em>: print a message with text <code>message</code> for <code>duration</code> seconds. The message will only be printed during the first run of the level, and will not produce any sound.</p></li>
+<li><p><strong><code>void m_clearMessages()</code></strong>: Remove all previously scheduled messages.</p></li>
+<li><p><strong><code>void t_wait(double duration)</code></strong>: <em>Add to the main timeline</em>: wait for <code>duration</code> sixths of a second.</p></li>
+<li><p><strong><code>void t_waitS(double duration)</code></strong>: <em>Add to the main timeline</em>: wait for <code>duration</code> seconds.</p></li>
+<li><p><strong><code>void t_waitUntilS(double duration)</code></strong>: <em>Add to the main timeline</em>: wait until the timer reaches <code>duration</code> seconds.</p></li>
+<li><p><strong><code>void e_eventStopTime(double duration)</code></strong>: <em>Add to the event timeline</em>: pause the game timer for <code>duration</code> sixths of a second.</p></li>
+<li><p><strong><code>void e_eventStopTimeS(double duration)</code></strong>: <em>Add to the event timeline</em>: pause the game timer for <code>duration</code> seconds.</p></li>
+<li><p><strong><code>void e_eventWait(double duration)</code></strong>: <em>Add to the event timeline</em>: wait for <code>duration</code> sixths of a second.</p></li>
+<li><p><strong><code>void e_eventWaitS(double duration)</code></strong>: <em>Add to the event timeline</em>: wait for <code>duration</code> seconds.</p></li>
+<li><p><strong><code>void e_eventWaitUntilS(double duration)</code></strong>: <em>Add to the event timeline</em>: wait until the timer reaches <code>duration</code> seconds.</p></li>
+<li><p><strong><code>float l_getSpeedMult()</code></strong>: Return the <code>SpeedMult</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSpeedMult(float value)</code></strong>: Set the <code>SpeedMult</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getSpeedInc()</code></strong>: Return the <code>SpeedInc</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSpeedInc(float value)</code></strong>: Set the <code>SpeedInc</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getSpeedMax()</code></strong>: Return the <code>SpeedMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSpeedMax(float value)</code></strong>: Set the <code>SpeedMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getRotationSpeed()</code></strong>: Return the <code>RotationSpeed</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setRotationSpeed(float value)</code></strong>: Set the <code>RotationSpeed</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getRotationSpeedInc()</code></strong>: Return the <code>RotationSpeedInc</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setRotationSpeedInc(float value)</code></strong>: Set the <code>RotationSpeedInc</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getRotationSpeedMax()</code></strong>: Return the <code>RotationSpeedMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setRotationSpeedMax(float value)</code></strong>: Set the <code>RotationSpeedMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getDelayMult()</code></strong>: Return the <code>DelayMult</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setDelayMult(float value)</code></strong>: Set the <code>DelayMult</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getDelayInc()</code></strong>: Return the <code>DelayInc</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setDelayInc(float value)</code></strong>: Set the <code>DelayInc</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getDelayMin()</code></strong>: Return the <code>DelayMin</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setDelayMin(float value)</code></strong>: Set the <code>DelayMin</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getDelayMax()</code></strong>: Return the <code>DelayMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setDelayMax(float value)</code></strong>: Set the <code>DelayMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getFastSpin()</code></strong>: Return the <code>FastSpin</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setFastSpin(float value)</code></strong>: Set the <code>FastSpin</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getIncTime()</code></strong>: Return the <code>IncTime</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setIncTime(float value)</code></strong>: Set the <code>IncTime</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseMin()</code></strong>: Return the <code>PulseMin</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseMin(float value)</code></strong>: Set the <code>PulseMin</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseMax()</code></strong>: Return the <code>PulseMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseMax(float value)</code></strong>: Set the <code>PulseMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseSpeed()</code></strong>: Return the <code>PulseSpeed</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseSpeed(float value)</code></strong>: Set the <code>PulseSpeed</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseSpeedR()</code></strong>: Return the <code>PulseSpeedR</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseSpeedR(float value)</code></strong>: Set the <code>PulseSpeedR</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseDelayMax()</code></strong>: Return the <code>PulseDelayMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseDelayMax(float value)</code></strong>: Set the <code>PulseDelayMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getPulseDelayHalfMax()</code></strong>: Return the <code>PulseDelayHalfMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setPulseDelayHalfMax(float value)</code></strong>: Set the <code>PulseDelayHalfMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getBeatPulseMax()</code></strong>: Return the <code>BeatPulseMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setBeatPulseMax(float value)</code></strong>: Set the <code>BeatPulseMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getBeatPulseDelayMax()</code></strong>: Return the <code>BeatPulseDelayMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setBeatPulseDelayMax(float value)</code></strong>: Set the <code>BeatPulseDelayMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getRadiusMin()</code></strong>: Return the <code>RadiusMin</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setRadiusMin(float value)</code></strong>: Set the <code>RadiusMin</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getWallSkewLeft()</code></strong>: Return the <code>WallSkewLeft</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setWallSkewLeft(float value)</code></strong>: Set the <code>WallSkewLeft</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getWallSkewRight()</code></strong>: Return the <code>WallSkewRight</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setWallSkewRight(float value)</code></strong>: Set the <code>WallSkewRight</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getWallAngleLeft()</code></strong>: Return the <code>WallAngleLeft</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setWallAngleLeft(float value)</code></strong>: Set the <code>WallAngleLeft</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_getWallAngleRight()</code></strong>: Return the <code>WallAngleRight</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setWallAngleRight(float value)</code></strong>: Set the <code>WallAngleRight</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>float l_get3dEffectMultiplier()</code></strong>: Return the <code>3dEffectMultiplier</code> field of the level status.</p></li>
+<li><p><strong><code>void l_set3dEffectMultiplier(float value)</code></strong>: Set the <code>3dEffectMultiplier</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>int l_getCameraShake()</code></strong>: Return the <code>CameraShake</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setCameraShake(int value)</code></strong>: Set the <code>CameraShake</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>unsigned int l_getSides()</code></strong>: Return the <code>Sides</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSides(unsigned int value)</code></strong>: Set the <code>Sides</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>unsigned int l_getSidesMax()</code></strong>: Return the <code>SidesMax</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSidesMax(unsigned int value)</code></strong>: Set the <code>SidesMax</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>unsigned int l_getSidesMin()</code></strong>: Return the <code>SidesMin</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSidesMin(unsigned int value)</code></strong>: Set the <code>SidesMin</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>bool l_getSwapEnabled()</code></strong>: Return the <code>SwapEnabled</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setSwapEnabled(bool value)</code></strong>: Set the <code>SwapEnabled</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>bool l_getTutorialMode()</code></strong>: Return the <code>TutorialMode</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setTutorialMode(bool value)</code></strong>: Set the <code>TutorialMode</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>bool l_getIncEnabled()</code></strong>: Return the <code>IncEnabled</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setIncEnabled(bool value)</code></strong>: Set the <code>IncEnabled</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>bool l_getDarkenUnevenBackgroundChunk()</code></strong>: Return the <code>DarkenUnevenBackgroundChunk</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setDarkenUnevenBackgroundChunk(bool value)</code></strong>: Set the <code>DarkenUnevenBackgroundChunk</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>size_t l_getCurrentIncrements()</code></strong>: Return the <code>CurrentIncrements</code> field of the level status.</p></li>
+<li><p><strong><code>void l_setCurrentIncrements(size_t value)</code></strong>: Set the <code>CurrentIncrements</code> field of the level status to <code>value</code>.</p></li>
+<li><p><strong><code>void l_enableRndSideChanges(bool enabled)</code></strong>: Set random side changes to <code>enabled</code>.</p></li>
+<li><p><strong><code>void l_darkenUnevenBackgroundChunk(bool enabled)</code></strong>: If <code>enabled</code> is true, one of the background’s chunks will be darkened in case there is an uneven number of sides.</p></li>
+<li><p><strong><code>void l_addTracked(string variable, string name)</code></strong>: Add the variable <code>variable</code> to the list of tracked variables, with name <code>name</code>. Tracked variables are displayed in game, below the game timer.</p></li>
+<li><p><strong><code>void l_setRotation(float angle)</code></strong>: Set the background camera rotation to <code>angle</code> radians.</p></li>
+<li><p><strong><code>float l_getRotation()</code></strong>: Return the background camera rotation, in radians.</p></li>
+<li><p><strong><code>double l_getLevelTime()</code></strong>: Get the current game timer value, in seconds.</p></li>
+<li><p><strong><code>bool l_getOfficial()</code></strong>: Return <code>true</code> if “official mode” is enabled, <code>false</code> otherwise.</p></li>
+<li><p><strong><code>float s_getHueMin()</code></strong>: Return the <code>HueMin</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setHueMin(float value)</code></strong>: Set the <code>HueMin</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getHueMax()</code></strong>: Return the <code>HueMax</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setHueMax(float value)</code></strong>: Set the <code>HueMax</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getHueInc()</code></strong>: Return the <code>HueInc</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setHueInc(float value)</code></strong>: Set the <code>HueInc</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getHueIncrement()</code></strong>: Return the <code>HueIncrement</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setHueIncrement(float value)</code></strong>: Set the <code>HueIncrement</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getPulseMin()</code></strong>: Return the <code>PulseMin</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setPulseMin(float value)</code></strong>: Set the <code>PulseMin</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getPulseMax()</code></strong>: Return the <code>PulseMax</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setPulseMax(float value)</code></strong>: Set the <code>PulseMax</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getPulseInc()</code></strong>: Return the <code>PulseInc</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setPulseInc(float value)</code></strong>: Set the <code>PulseInc</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getPulseIncrement()</code></strong>: Return the <code>PulseIncrement</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setPulseIncrement(float value)</code></strong>: Set the <code>PulseIncrement</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>bool s_getHuePingPong()</code></strong>: Return the <code>HuePingPong</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setHuePingPong(bool value)</code></strong>: Set the <code>HuePingPong</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_getMaxSwapTime()</code></strong>: Return the <code>MaxSwapTime</code> field of the style data.</p></li>
+<li><p><strong><code>void s_setMaxSwapTime(float value)</code></strong>: Set the <code>MaxSwapTime</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dDepth()</code></strong>: Return the <code>3dDepth</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dDepth(float value)</code></strong>: Set the <code>3dDepth</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dSkew()</code></strong>: Return the <code>3dSkew</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dSkew(float value)</code></strong>: Set the <code>3dSkew</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dSpacing()</code></strong>: Return the <code>3dSpacing</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dSpacing(float value)</code></strong>: Set the <code>3dSpacing</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dDarkenMult()</code></strong>: Return the <code>3dDarkenMult</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dDarkenMult(float value)</code></strong>: Set the <code>3dDarkenMult</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dAlphaMult()</code></strong>: Return the <code>3dAlphaMult</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dAlphaMult(float value)</code></strong>: Set the <code>3dAlphaMult</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dAlphaFalloff()</code></strong>: Return the <code>3dAlphaFalloff</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dAlphaFalloff(float value)</code></strong>: Set the <code>3dAlphaFalloff</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dPulseMax()</code></strong>: Return the <code>3dPulseMax</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dPulseMax(float value)</code></strong>: Set the <code>3dPulseMax</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dPulseMin()</code></strong>: Return the <code>3dPulseMin</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dPulseMin(float value)</code></strong>: Set the <code>3dPulseMin</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dPulseSpeed()</code></strong>: Return the <code>3dPulseSpeed</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dPulseSpeed(float value)</code></strong>: Set the <code>3dPulseSpeed</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>float s_get3dPerspectiveMult()</code></strong>: Return the <code>3dPerspectiveMult</code> field of the style data.</p></li>
+<li><p><strong><code>void s_set3dPerspectiveMult(float value)</code></strong>: Set the <code>3dPerspectiveMult</code> field of the style data to <code>value</code>.</p></li>
+<li><p><strong><code>void s_setStyle(string styleId)</code></strong>: Set the currently active style to the style with id <code>styleId</code>. Styles can be defined as <code>.json</code> files in the <code>&lt;pack&gt;/Styles/</code> folder.</p></li>
+<li><p><strong><code>void s_setCameraShake(int value)</code></strong>: Start a camera shake with intensity <code>value</code>.</p></li>
+<li><p><strong><code>int s_getCameraShake()</code></strong>: Return the current camera shake intensity.</p></li>
+<li><p><strong><code>void s_setCapColorMain()</code></strong>: Set the color of the center polygon to match the main style color.</p></li>
+<li><p><strong><code>void s_setCapColorMainDarkened()</code></strong>: Set the color of the center polygon to match the main style color, darkened.</p></li>
+<li><p><strong><code>void s_setCapColorByIndex(int index)</code></strong>: Set the color of the center polygon to match the style color with index <code>index</code>.</p></li>
+<li><p><strong><code>void w_wall(int side, float thickness)</code></strong>: Create a new wall at side <code>side</code>, with thickness <code>thickness</code>. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier.</p></li>
+<li><p><strong><code>void w_wallAdj(int side, float thickness, float speedMult)</code></strong>: Create a new wall at side <code>side</code>, with thickness <code>thickness</code>. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by <code>speedMult</code>.</p></li>
+<li><p><strong><code>void w_wallAcc(int side, float thickness, float speedMult, float acceleration, float minSpeed, float maxSpeed)</code></strong>: Create a new wall at side <code>side</code>, with thickness <code>thickness</code>. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by <code>speedMult</code>. The wall will have a speed acceleration value of <code>acceleration</code>. The minimum and maximum speed of the wall are bounded by <code>minSpeed</code> and <code>maxSpeed</code>, adjusted for the current difficulty multiplier.</p></li>
+<li><p><strong><code>void w_wallHModSpeedData(float hueModifier, int side, float thickness, float speedMult, float acceleration, float minSpeed, float maxSpeed, bool pingPong)</code></strong>: Create a new wall at side <code>side</code>, with thickness <code>thickness</code>. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by <code>speedMult</code>. The wall will have a speed acceleration value of <code>acceleration</code>. The minimum and maximum speed of the wall are bounded by <code>minSpeed</code> and <code>maxSpeed</code>, adjusted for the current difficulty multiplier. The hue of the wall will be adjusted by <code>hueModifier</code>. If <code>pingPong</code> is enabled, the wall will accelerate back and forth between its minimum and maximum speed.</p></li>
+<li><p><strong><code>void w_wallHModCurveData(float hueModifier, int side, float thickness, float curveSpeedMult, float curveAcceleration, float curveMinSpeed, float curveMaxSpeed, bool pingPong)</code></strong>: Create a new curving wall at side <code>side</code>, with thickness <code>thickness</code>. The curving speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by <code>curveSpeedMult</code>. The wall will have a curving speed acceleration value of <code>curveAcceleration</code>. The minimum and maximum curving speed of the wall are bounded by <code>curveMinSpeed</code> and <code>curveMaxSpeed</code>, adjusted for the current difficulty multiplier. The hue of the wall will be adjusted by <code>hueModifier</code>. If <code>pingPong</code> is enabled, the wall will accelerate back and forth between its minimum and maximum speed.</p></li>
+<li><p><strong><code>void steam_unlockAchievement(string achievementId)</code></strong>: Unlock the Steam achievement with id <code>achievementId</code>.</p></li>
+<li><p><strong><code>int cw_create()</code></strong>: Create a new custom wall and return a integer handle to it.</p></li>
+<li><p><strong><code>void cw_destroy(int cwHandle)</code></strong>: Destroy the custom wall represented by <code>cwHandle</code>.</p></li>
+<li><p><strong><code>void cw_setVertexPos(int cwHandle, int vertexIndex, float x, float y)</code></strong>: Given the custom wall represented by <code>cwHandle</code>, set the position of its vertex with index <code>vertexIndex</code> to <code>{x, y}</code>.</p></li>
+<li><p><strong><code>void cw_setVertexColor(int cwHandle, int vertexIndex, int r, int g, int b, int a)</code></strong>: Given the custom wall represented by <code>cwHandle</code>, set the color of its vertex with index <code>vertexIndex</code> to <code>{r, g, b, a}</code>.</p></li>
+<li><p><strong><code>tuple&lt;float, float&gt; cw_getVertexPos(int cwHandle, int vertexIndex)</code></strong>: Given the custom wall represented by <code>cwHandle</code>, set the position of its vertex with index <code>vertexIndex</code> to <code>{x, y}</code>.</p></li>
+<li><p><strong><code>void cw_clear()</code></strong>: Remove all existing custom walls.</p></li>
+</ul>
+<h2 id="where-to-get-help">Where to get Help</h2>
+<p>The Open Hexagon community is active on the <a href="https://discord.me/openhexagon">official Discord server</a>. The server contains many channels, including <code>#pack-dev</code> which is dedicated to level pack developers (and is the recommended place to ask for help and share your progress and ideas), and <code>#source-dev</code> which is dedicated to development on the Open Hexagon engine (which is open-source and available <a href="https://github.com/SuperV1234/SSVOpenHexagon/">on GitHub</a>).</p>
+<p>The official Discord server is also a great place to share your levels and get feedback before uploading them to the Steam Workshop.</p>
+<section class="footnotes" role="doc-endnotes">
+<hr />
+<ol>
+<li id="fn1" role="doc-endnote"><p>If you are not sure where Open Hexagon is installed, you can navigate to the folder by right-clicking on Open Hexagon in your Steam library, selecting “Properties”, going to the “Local Files” tab, and then clicking on the “Browse Local Files” button.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
+<li id="fn2" role="doc-endnote"><p>It is possible that in the future I will implement a solution to share dependencies between different packs, but that is not available yet and it is not a priority. For now, think of a level pack as a standalone self-contained entity.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
+<li id="fn3" role="doc-endnote"><p>Please ensure that the music files you upload to the Steam Workshop are not protected by copyright laws. If they are, you should get explicit permission from the copyright owner for them to be used as a custom Open Hexagon level, and always credit the original artists as part of your workshop item description.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
+<li id="fn4" role="doc-endnote"><p>If you are not sure where Open Hexagon is installed, you can navigate to the folder by right-clicking on Open Hexagon in your Steam library, selecting “Properties”, going to the “Local Files” tab, and then clicking on the “Browse Local Files” button.<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
+<li id="fn5" role="doc-endnote"><p>Or any existing id for a workshop item you own, if you intend to update it rather than creating a new one.<a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
+</ol>
+</section>
+</body>
+</html>
diff --git a/webpage/workshop_tutorial.md b/webpage/workshop_tutorial.md
new file mode 100644
index 00000000..fda52581
--- /dev/null
+++ b/webpage/workshop_tutorial.md
@@ -0,0 +1,436 @@
+# Workshop Tutorial
+
+This document will guide you through the process of creating a new Open Hexagon level pack and upload it on the Steam Workshop.
+
+
+
+## Getting Started
+
+### Prerequisites
+
+Open Hexagon uses [**Lua**](http://www.lua.org/) as the main scripting language for custom levels, alongside [**JSON**](https://www.json.org/json-en.html) for level metadata and configuration files.
+
+It is recommended to be somewhat familiar with both languages before diving into Open Hexagon content creation, but if you're feeling brave and have previous programming experience you can just try to "wing it" by modifying existing levels and learning on the fly. A quick and fun way of learning the syntax of the above languages is the "Learn X in Y minutes" website:
+
+- [Learn X in Y minutes: Lua](https://learnxinyminutes.com/docs/lua/)
+
+- [Learn X in Y minutes: JSON](https://learnxinyminutes.com/docs/json/)
+
+### Content Structure
+
+Open Hexagon custom content is organized in the following hierarchy:
+
+- **Level Pack** *(folder)*
+ - **`pack.json`** *(main JSON metadata file)*
+ - **Levels** *(folder containing level JSON metadata files)*
+ - **`mylevel.json`**
+ - **Music** *(folder containing music `.ogg` and JSON metadata files)*
+ - **`mysong.ogg`**
+ - **`mysong.json`**
+ - **Scripts** *(folder containing `.lua` scripts)*
+ - **`mylevel.lua`**
+ - **Styles** *(folder containing visual style JSON metadata files)*
+ - **`mystyle.json`**
+
+Therefore, the main unit of work that can be published and installed is a **pack**. Inside a pack, you can have an arbitrary number of **levels**, **musics**, **scripts**, and **styles**. A completed level requires a **level `.json` metadata file**, **music `.ogg` file**, **music `.json` metadata file**, **level `.lua` script**, and a **style `.json` file**. All the separate files are linked together by **textual identifiers**, specified in the `.json` metadata files.
+
+## Creating your First Custom Level
+
+The easiest way to get started is to subscribe to the ["Example Workshop Level
+"](https://steamcommunity.com/sharedfiles/filedetails/?id=2157745755) on the Steam Workshop, navigate to its contents (usually `C:/Program Files (x86)/Steam/steamapps/workshop/content/1358090/2157745755`), and copy-paste that folder into your `Open Hexagon/Packs/` folder[^where_is_open_hexagon_folder].
+
+After that, rename the pasted `2157745755` to something else (e.g. `MyFirstPack`), and start exploring its contents with your favorite text editor (personally, I use [Visual Studio Code](https://code.visualstudio.com/)).
+
+Delete the `Example Workshop Level.workshop.json` file (as it will be generated automatically when you upload your pack to the workshop), and the preview image `.png` file, as you will provide one yourself later.
+
+Start by taking a look at `pack.json` - there are multiple fields to change. Note that Open Hexagon expects every single level pack to have a unique id - therefore, to minimize the chance of collisions between packs made by different authors, you should specify a sufficiently unique `"disambiguator"` that is very unlikely someone else might accidentally use. Such field will not be displayed in game, so it can be completely arbitrary. The rest of the fields also participate in the calculation of the final pack id, so all of your packs can share the same `"disambiguator"`, provided that they do have different names (as they rightfully should). The `"version"` field is useful for keeping track of changes and updates of your custom level pack. You should bump it up by one whenever you make changes to your pack and re-upload it to the workshop.
+
+After that, you should explore the other folders and see how things work. The `.json` and `.lua` files are commented, so you should have enough context to figure out how things are linked together and what role they play.
+
+The level logic is stored in `Scripts/Levels/examplelevel.lua`. Please refer to the [Lua Reference](#lua-reference) to figure out what the used functions do, and to discover new functions to play around with. You will notice a bunch of `u_execScript` calls at the top of the Lua file - these are other scripts that the level script depends on. Dependencies should always be provided as part of your pack folder[^will_dependency_management_change].
+
+Now it's up to you! Explore the dependencies, experiment with the code, add your own music[^music_copyright_issues], and - when you're satisfied - share your pack on the workshop and ask for feedback from other players, in order to perfect it. Keep in mind that you can join our official Discord before or during level development to ask for help and share your ideas - see [Where to get Help](#where-to-get-help) for more information.
+
+[^will_dependency_management_change]: It is possible that in the future I will implement a solution to share dependencies between different packs, but that is not available yet and it is not a priority. For now, think of a level pack as a standalone self-contained entity.
+
+[^music_copyright_issues]: Please ensure that the music files you upload to the Steam Workshop are not protected by copyright laws. If they are, you should get explicit permission from the copyright owner for them to be used as a custom Open Hexagon level, and always credit the original artists as part of your workshop item description.
+
+
+
+## Uploading to the Steam Workshop
+
+The current recommended way of uploading your level to the Steam Workshop is to use the provided `OHWorkshopUploader.exe` command-line tool available in your Open Hexagon folder[^where_is_open_hexagon_folder].
+
+[^where_is_open_hexagon_folder]: If you are not sure where Open Hexagon is installed, you can navigate to the folder by right-clicking on Open Hexagon in your Steam library, selecting "Properties", going to the "Local Files" tab, and then clicking on the "Browse Local Files" button.
+
+When running the tool, you'll see a few options:
+
+```text
+Enter one of the following options:
+0. Create a new workshop item.
+1. Upload contents of an existing workshop item.
+2. Set preview image of an existing workshop item.
+3. Exit
+```
+
+If you intend to upload a brand new level, then write `0` and press *enter*. On success, the program will give you back a workshop item id:
+
+```text
+Successfully created new workshop item: 2165863494.
+```
+
+Copy that id to your clipboard as you're going to need it for later. Once you have finished creating and testing your custom level, you should have a folder under `Packs/` containing all its data. To upload it, write `1` and press *enter*. You will be prompted to enter the workshop item id you want to upload data to - you should now paste the id you previously copied[^what_id_to_use].
+
+[^what_id_to_use]: Or any existing id for a workshop item you own, if you intend to update it rather than creating a new one.
+
+You will then be prompted to enter the path to the folder containing the data. Please insert an absolute path to the pack folder (e.g. `C:/MySteamLibrary/common/Open Hexagon/Packs/MyFirstPack`) and keep following the instructions on screen. Your data should now be uploaded to the workshop.
+
+Finally, you can write `2` and press *enter* to add a preview image to your workshop item. A 620x620 `.png` image is recommended for this task - you will need to provide an absolute path to the image similarly to what you have done in the previous step.
+
+
+
+## Lua Reference {#lua-reference}
+
+<!-- Generated from Open Hexagon v2.01. -->
+
+* **`void u_log(string message)`**: Print out `message` to the console.
+
+* **`void u_execScript(string scriptFilename)`**: Execute the script located at `<pack>/Scripts/scriptFilename`.
+
+* **`void u_playSound(string soundId)`**: Play the sound with id `soundId`. The id must be registered in `assets.json`, under `"soundBuffers"`.
+
+* **`void u_setMusic(string musicId)`**: Stop the current music and play the music with id `musicId`. The id is defined in the music `.json` file, under `"id"`.
+
+* **`void u_setMusicSegment(string musicId, int segment)`**: Stop the current music and play the music with id `musicId`, starting at segment `segment`. Segments are defined in the music `.json` file, under `"segments"`.
+
+* **`void u_setMusicSeconds(string musicId, float time)`**: Stop the current music and play the music with id `musicId`, starting at time `time` (in seconds).
+
+* **`bool u_isKeyPressed(int keyCode)`**: Return `true` if the keyboard key with code `keyCode` is being pressed, `false` otherwise. The key code must match the definition of the SFML `sf::Keyboard::Key` enumeration.
+
+* **`void u_haltTime(double duration)`**: Pause the game timer for `duration` seconds.
+
+* **`void u_timelineWait(double duration)`**: *Add to the main timeline*: wait for `duration` sixths of a second.
+
+* **`void u_clearWalls()`**: Remove all existing walls.
+
+* **`float u_getPlayerAngle()`**: Return the current angle of the player, in radians.
+
+* **`void u_setPlayerAngle(float angle)`**: Set the current angle of the player to `angle`, in radians.
+
+* **`bool u_isMouseButtonPressed(int buttonCode)`**: Return `true` if the mouse button with code `buttonCode` is being pressed, `false` otherwise. The button code must match the definition of the SFML `sf::Mouse::Button` enumeration.
+
+* **`bool u_isFastSpinning()`**: Return `true` if the camera is currently "fast spinning", `false` otherwise.
+
+* **`void u_forceIncrement()`**: Immediately force a difficulty increment, regardless of the chosen automatic increment parameters.
+
+* **`void u_kill()`**: *Add to the main timeline*: kill the player.
+
+* **`void u_eventKill()`**: *Add to the event timeline*: kill the player.
+
+* **`float u_getDifficultyMult()`**: Return the current difficulty multiplier.
+
+* **`float u_getSpeedMultDM()`**: Return the current speed multiplier, adjusted for the chosen difficulty multiplier.
+
+* **`float u_getDelayMultDM()`**: Return the current delay multiplier, adjusted for the chosen difficulty multiplier.
+
+* **`void m_messageAdd(string message, double duration)`**: *Add to the event timeline*: print a message with text `message` for `duration` seconds. The message will only be printed during the first run of the level.
+
+* **`void m_messageAddImportant(string message, double duration)`**: *Add to the event timeline*: print a message with text `message` for `duration` seconds. The message will be printed during every run of the level.
+
+* **`void m_messageAddImportantSilent(string message, double duration)`**: *Add to the event timeline*: print a message with text `message` for `duration` seconds. The message will only be printed during the first run of the level, and will not produce any sound.
+
+* **`void m_clearMessages()`**: Remove all previously scheduled messages.
+
+* **`void t_wait(double duration)`**: *Add to the main timeline*: wait for `duration` sixths of a second.
+
+* **`void t_waitS(double duration)`**: *Add to the main timeline*: wait for `duration` seconds.
+
+* **`void t_waitUntilS(double duration)`**: *Add to the main timeline*: wait until the timer reaches `duration` seconds.
+
+* **`void e_eventStopTime(double duration)`**: *Add to the event timeline*: pause the game timer for `duration` sixths of a second.
+
+* **`void e_eventStopTimeS(double duration)`**: *Add to the event timeline*: pause the game timer for `duration` seconds.
+
+* **`void e_eventWait(double duration)`**: *Add to the event timeline*: wait for `duration` sixths of a second.
+
+* **`void e_eventWaitS(double duration)`**: *Add to the event timeline*: wait for `duration` seconds.
+
+* **`void e_eventWaitUntilS(double duration)`**: *Add to the event timeline*: wait until the timer reaches `duration` seconds.
+
+* **`float l_getSpeedMult()`**: Return the `SpeedMult` field of the level status.
+
+* **`void l_setSpeedMult(float value)`**: Set the `SpeedMult` field of the level status to `value`.
+
+* **`float l_getSpeedInc()`**: Return the `SpeedInc` field of the level status.
+
+* **`void l_setSpeedInc(float value)`**: Set the `SpeedInc` field of the level status to `value`.
+
+* **`float l_getSpeedMax()`**: Return the `SpeedMax` field of the level status.
+
+* **`void l_setSpeedMax(float value)`**: Set the `SpeedMax` field of the level status to `value`.
+
+* **`float l_getRotationSpeed()`**: Return the `RotationSpeed` field of the level status.
+
+* **`void l_setRotationSpeed(float value)`**: Set the `RotationSpeed` field of the level status to `value`.
+
+* **`float l_getRotationSpeedInc()`**: Return the `RotationSpeedInc` field of the level status.
+
+* **`void l_setRotationSpeedInc(float value)`**: Set the `RotationSpeedInc` field of the level status to `value`.
+
+* **`float l_getRotationSpeedMax()`**: Return the `RotationSpeedMax` field of the level status.
+
+* **`void l_setRotationSpeedMax(float value)`**: Set the `RotationSpeedMax` field of the level status to `value`.
+
+* **`float l_getDelayMult()`**: Return the `DelayMult` field of the level status.
+
+* **`void l_setDelayMult(float value)`**: Set the `DelayMult` field of the level status to `value`.
+
+* **`float l_getDelayInc()`**: Return the `DelayInc` field of the level status.
+
+* **`void l_setDelayInc(float value)`**: Set the `DelayInc` field of the level status to `value`.
+
+* **`float l_getDelayMin()`**: Return the `DelayMin` field of the level status.
+
+* **`void l_setDelayMin(float value)`**: Set the `DelayMin` field of the level status to `value`.
+
+* **`float l_getDelayMax()`**: Return the `DelayMax` field of the level status.
+
+* **`void l_setDelayMax(float value)`**: Set the `DelayMax` field of the level status to `value`.
+
+* **`float l_getFastSpin()`**: Return the `FastSpin` field of the level status.
+
+* **`void l_setFastSpin(float value)`**: Set the `FastSpin` field of the level status to `value`.
+
+* **`float l_getIncTime()`**: Return the `IncTime` field of the level status.
+
+* **`void l_setIncTime(float value)`**: Set the `IncTime` field of the level status to `value`.
+
+* **`float l_getPulseMin()`**: Return the `PulseMin` field of the level status.
+
+* **`void l_setPulseMin(float value)`**: Set the `PulseMin` field of the level status to `value`.
+
+* **`float l_getPulseMax()`**: Return the `PulseMax` field of the level status.
+
+* **`void l_setPulseMax(float value)`**: Set the `PulseMax` field of the level status to `value`.
+
+* **`float l_getPulseSpeed()`**: Return the `PulseSpeed` field of the level status.
+
+* **`void l_setPulseSpeed(float value)`**: Set the `PulseSpeed` field of the level status to `value`.
+
+* **`float l_getPulseSpeedR()`**: Return the `PulseSpeedR` field of the level status.
+
+* **`void l_setPulseSpeedR(float value)`**: Set the `PulseSpeedR` field of the level status to `value`.
+
+* **`float l_getPulseDelayMax()`**: Return the `PulseDelayMax` field of the level status.
+
+* **`void l_setPulseDelayMax(float value)`**: Set the `PulseDelayMax` field of the level status to `value`.
+
+* **`float l_getPulseDelayHalfMax()`**: Return the `PulseDelayHalfMax` field of the level status.
+
+* **`void l_setPulseDelayHalfMax(float value)`**: Set the `PulseDelayHalfMax` field of the level status to `value`.
+
+* **`float l_getBeatPulseMax()`**: Return the `BeatPulseMax` field of the level status.
+
+* **`void l_setBeatPulseMax(float value)`**: Set the `BeatPulseMax` field of the level status to `value`.
+
+* **`float l_getBeatPulseDelayMax()`**: Return the `BeatPulseDelayMax` field of the level status.
+
+* **`void l_setBeatPulseDelayMax(float value)`**: Set the `BeatPulseDelayMax` field of the level status to `value`.
+
+* **`float l_getRadiusMin()`**: Return the `RadiusMin` field of the level status.
+
+* **`void l_setRadiusMin(float value)`**: Set the `RadiusMin` field of the level status to `value`.
+
+* **`float l_getWallSkewLeft()`**: Return the `WallSkewLeft` field of the level status.
+
+* **`void l_setWallSkewLeft(float value)`**: Set the `WallSkewLeft` field of the level status to `value`.
+
+* **`float l_getWallSkewRight()`**: Return the `WallSkewRight` field of the level status.
+
+* **`void l_setWallSkewRight(float value)`**: Set the `WallSkewRight` field of the level status to `value`.
+
+* **`float l_getWallAngleLeft()`**: Return the `WallAngleLeft` field of the level status.
+
+* **`void l_setWallAngleLeft(float value)`**: Set the `WallAngleLeft` field of the level status to `value`.
+
+* **`float l_getWallAngleRight()`**: Return the `WallAngleRight` field of the level status.
+
+* **`void l_setWallAngleRight(float value)`**: Set the `WallAngleRight` field of the level status to `value`.
+
+* **`float l_get3dEffectMultiplier()`**: Return the `3dEffectMultiplier` field of the level status.
+
+* **`void l_set3dEffectMultiplier(float value)`**: Set the `3dEffectMultiplier` field of the level status to `value`.
+
+* **`int l_getCameraShake()`**: Return the `CameraShake` field of the level status.
+
+* **`void l_setCameraShake(int value)`**: Set the `CameraShake` field of the level status to `value`.
+
+* **`unsigned int l_getSides()`**: Return the `Sides` field of the level status.
+
+* **`void l_setSides(unsigned int value)`**: Set the `Sides` field of the level status to `value`.
+
+* **`unsigned int l_getSidesMax()`**: Return the `SidesMax` field of the level status.
+
+* **`void l_setSidesMax(unsigned int value)`**: Set the `SidesMax` field of the level status to `value`.
+
+* **`unsigned int l_getSidesMin()`**: Return the `SidesMin` field of the level status.
+
+* **`void l_setSidesMin(unsigned int value)`**: Set the `SidesMin` field of the level status to `value`.
+
+* **`bool l_getSwapEnabled()`**: Return the `SwapEnabled` field of the level status.
+
+* **`void l_setSwapEnabled(bool value)`**: Set the `SwapEnabled` field of the level status to `value`.
+
+* **`bool l_getTutorialMode()`**: Return the `TutorialMode` field of the level status.
+
+* **`void l_setTutorialMode(bool value)`**: Set the `TutorialMode` field of the level status to `value`.
+
+* **`bool l_getIncEnabled()`**: Return the `IncEnabled` field of the level status.
+
+* **`void l_setIncEnabled(bool value)`**: Set the `IncEnabled` field of the level status to `value`.
+
+* **`bool l_getDarkenUnevenBackgroundChunk()`**: Return the `DarkenUnevenBackgroundChunk` field of the level status.
+
+* **`void l_setDarkenUnevenBackgroundChunk(bool value)`**: Set the `DarkenUnevenBackgroundChunk` field of the level status to `value`.
+
+* **`size_t l_getCurrentIncrements()`**: Return the `CurrentIncrements` field of the level status.
+
+* **`void l_setCurrentIncrements(size_t value)`**: Set the `CurrentIncrements` field of the level status to `value`.
+
+* **`void l_enableRndSideChanges(bool enabled)`**: Set random side changes to `enabled`.
+
+* **`void l_darkenUnevenBackgroundChunk(bool enabled)`**: If `enabled` is true, one of the background's chunks will be darkened in case there is an uneven number of sides.
+
+* **`void l_addTracked(string variable, string name)`**: Add the variable `variable` to the list of tracked variables, with name `name`. Tracked variables are displayed in game, below the game timer.
+
+* **`void l_setRotation(float angle)`**: Set the background camera rotation to `angle` radians.
+
+* **`float l_getRotation()`**: Return the background camera rotation, in radians.
+
+* **`double l_getLevelTime()`**: Get the current game timer value, in seconds.
+
+* **`bool l_getOfficial()`**: Return `true` if "official mode" is enabled, `false` otherwise.
+
+* **`float s_getHueMin()`**: Return the `HueMin` field of the style data.
+
+* **`void s_setHueMin(float value)`**: Set the `HueMin` field of the style data to `value`.
+
+* **`float s_getHueMax()`**: Return the `HueMax` field of the style data.
+
+* **`void s_setHueMax(float value)`**: Set the `HueMax` field of the style data to `value`.
+
+* **`float s_getHueInc()`**: Return the `HueInc` field of the style data.
+
+* **`void s_setHueInc(float value)`**: Set the `HueInc` field of the style data to `value`.
+
+* **`float s_getHueIncrement()`**: Return the `HueIncrement` field of the style data.
+
+* **`void s_setHueIncrement(float value)`**: Set the `HueIncrement` field of the style data to `value`.
+
+* **`float s_getPulseMin()`**: Return the `PulseMin` field of the style data.
+
+* **`void s_setPulseMin(float value)`**: Set the `PulseMin` field of the style data to `value`.
+
+* **`float s_getPulseMax()`**: Return the `PulseMax` field of the style data.
+
+* **`void s_setPulseMax(float value)`**: Set the `PulseMax` field of the style data to `value`.
+
+* **`float s_getPulseInc()`**: Return the `PulseInc` field of the style data.
+
+* **`void s_setPulseInc(float value)`**: Set the `PulseInc` field of the style data to `value`.
+
+* **`float s_getPulseIncrement()`**: Return the `PulseIncrement` field of the style data.
+
+* **`void s_setPulseIncrement(float value)`**: Set the `PulseIncrement` field of the style data to `value`.
+
+* **`bool s_getHuePingPong()`**: Return the `HuePingPong` field of the style data.
+
+* **`void s_setHuePingPong(bool value)`**: Set the `HuePingPong` field of the style data to `value`.
+
+* **`float s_getMaxSwapTime()`**: Return the `MaxSwapTime` field of the style data.
+
+* **`void s_setMaxSwapTime(float value)`**: Set the `MaxSwapTime` field of the style data to `value`.
+
+* **`float s_get3dDepth()`**: Return the `3dDepth` field of the style data.
+
+* **`void s_set3dDepth(float value)`**: Set the `3dDepth` field of the style data to `value`.
+
+* **`float s_get3dSkew()`**: Return the `3dSkew` field of the style data.
+
+* **`void s_set3dSkew(float value)`**: Set the `3dSkew` field of the style data to `value`.
+
+* **`float s_get3dSpacing()`**: Return the `3dSpacing` field of the style data.
+
+* **`void s_set3dSpacing(float value)`**: Set the `3dSpacing` field of the style data to `value`.
+
+* **`float s_get3dDarkenMult()`**: Return the `3dDarkenMult` field of the style data.
+
+* **`void s_set3dDarkenMult(float value)`**: Set the `3dDarkenMult` field of the style data to `value`.
+
+* **`float s_get3dAlphaMult()`**: Return the `3dAlphaMult` field of the style data.
+
+* **`void s_set3dAlphaMult(float value)`**: Set the `3dAlphaMult` field of the style data to `value`.
+
+* **`float s_get3dAlphaFalloff()`**: Return the `3dAlphaFalloff` field of the style data.
+
+* **`void s_set3dAlphaFalloff(float value)`**: Set the `3dAlphaFalloff` field of the style data to `value`.
+
+* **`float s_get3dPulseMax()`**: Return the `3dPulseMax` field of the style data.
+
+* **`void s_set3dPulseMax(float value)`**: Set the `3dPulseMax` field of the style data to `value`.
+
+* **`float s_get3dPulseMin()`**: Return the `3dPulseMin` field of the style data.
+
+* **`void s_set3dPulseMin(float value)`**: Set the `3dPulseMin` field of the style data to `value`.
+
+* **`float s_get3dPulseSpeed()`**: Return the `3dPulseSpeed` field of the style data.
+
+* **`void s_set3dPulseSpeed(float value)`**: Set the `3dPulseSpeed` field of the style data to `value`.
+
+* **`float s_get3dPerspectiveMult()`**: Return the `3dPerspectiveMult` field of the style data.
+
+* **`void s_set3dPerspectiveMult(float value)`**: Set the `3dPerspectiveMult` field of the style data to `value`.
+
+* **`void s_setStyle(string styleId)`**: Set the currently active style to the style with id `styleId`. Styles can be defined as `.json` files in the `<pack>/Styles/` folder.
+
+* **`void s_setCameraShake(int value)`**: Start a camera shake with intensity `value`.
+
+* **`int s_getCameraShake()`**: Return the current camera shake intensity.
+
+* **`void s_setCapColorMain()`**: Set the color of the center polygon to match the main style color.
+
+* **`void s_setCapColorMainDarkened()`**: Set the color of the center polygon to match the main style color, darkened.
+
+* **`void s_setCapColorByIndex(int index)`**: Set the color of the center polygon to match the style color with index `index`.
+
+* **`void w_wall(int side, float thickness)`**: Create a new wall at side `side`, with thickness `thickness`. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier.
+
+* **`void w_wallAdj(int side, float thickness, float speedMult)`**: Create a new wall at side `side`, with thickness `thickness`. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by `speedMult`.
+
+* **`void w_wallAcc(int side, float thickness, float speedMult, float acceleration, float minSpeed, float maxSpeed)`**: Create a new wall at side `side`, with thickness `thickness`. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by `speedMult`. The wall will have a speed acceleration value of `acceleration`. The minimum and maximum speed of the wall are bounded by `minSpeed` and `maxSpeed`, adjusted for the current difficulty multiplier.
+
+* **`void w_wallHModSpeedData(float hueModifier, int side, float thickness, float speedMult, float acceleration, float minSpeed, float maxSpeed, bool pingPong)`**: Create a new wall at side `side`, with thickness `thickness`. The speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by `speedMult`. The wall will have a speed acceleration value of `acceleration`. The minimum and maximum speed of the wall are bounded by `minSpeed` and `maxSpeed`, adjusted for the current difficulty multiplier. The hue of the wall will be adjusted by `hueModifier`. If `pingPong` is enabled, the wall will accelerate back and forth between its minimum and maximum speed.
+
+* **`void w_wallHModCurveData(float hueModifier, int side, float thickness, float curveSpeedMult, float curveAcceleration, float curveMinSpeed, float curveMaxSpeed, bool pingPong)`**: Create a new curving wall at side `side`, with thickness `thickness`. The curving speed of the wall will be calculated by using the speed multiplier, adjusted for the current difficulty multiplier, and finally multiplied by `curveSpeedMult`. The wall will have a curving speed acceleration value of `curveAcceleration`. The minimum and maximum curving speed of the wall are bounded by `curveMinSpeed` and `curveMaxSpeed`, adjusted for the current difficulty multiplier. The hue of the wall will be adjusted by `hueModifier`. If `pingPong` is enabled, the wall will accelerate back and forth between its minimum and maximum speed.
+
+* **`void steam_unlockAchievement(string achievementId)`**: Unlock the Steam achievement with id `achievementId`.
+
+* **`int cw_create()`**: Create a new custom wall and return a integer handle to it.
+
+* **`void cw_destroy(int cwHandle)`**: Destroy the custom wall represented by `cwHandle`.
+
+* **`void cw_setVertexPos(int cwHandle, int vertexIndex, float x, float y)`**: Given the custom wall represented by `cwHandle`, set the position of its vertex with index `vertexIndex` to `{x, y}`.
+
+* **`void cw_setVertexColor(int cwHandle, int vertexIndex, int r, int g, int b, int a)`**: Given the custom wall represented by `cwHandle`, set the color of its vertex with index `vertexIndex` to `{r, g, b, a}`.
+
+* **`tuple<float, float> cw_getVertexPos(int cwHandle, int vertexIndex)`**: Given the custom wall represented by `cwHandle`, set the position of its vertex with index `vertexIndex` to `{x, y}`.
+
+* **`void cw_clear()`**: Remove all existing custom walls.
+
+
+
+## Where to get Help {#where-to-get-help}
+
+The Open Hexagon community is active on the [official Discord server](https://discord.me/openhexagon). The server contains many channels, including `#pack-dev` which is dedicated to level pack developers (and is the recommended place to ask for help and share your progress and ideas), and `#source-dev` which is dedicated to development on the Open Hexagon engine (which is open-source and available [on GitHub](https://github.com/SuperV1234/SSVOpenHexagon/)).
+
+The official Discord server is also a great place to share your levels and get feedback before uploading them to the Steam Workshop.