@origingame/create-game 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/archetypes/README.md +99 -0
- package/archetypes/idle/.origin/setup +14 -0
- package/archetypes/idle/LICENSES.md +12 -0
- package/archetypes/idle/README.md +234 -0
- package/archetypes/idle/assets/audio/buy.wav +0 -0
- package/archetypes/idle/assets/audio/milestone.wav +0 -0
- package/archetypes/idle/assets/audio/nova.wav +0 -0
- package/archetypes/idle/assets/audio/orbit.wav +0 -0
- package/archetypes/idle/assets/audio/tap.wav +0 -0
- package/archetypes/idle/assets/audio/ui-close.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-error.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-open.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-press.ogt +9 -0
- package/archetypes/idle/assets/audio/ui-success.ogt +9 -0
- package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/idle/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/idle/assets/fonts/OFL.txt +93 -0
- package/archetypes/idle/assets/fx/drift.ogt +21 -0
- package/archetypes/idle/assets/fx/milestone.ogt +21 -0
- package/archetypes/idle/assets/fx/nova.ogt +22 -0
- package/archetypes/idle/assets/fx/stars.ogt +20 -0
- package/archetypes/idle/assets/fx/tap.ogt +23 -0
- package/archetypes/idle/assets/looks/idle.ogt +17 -0
- package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/ChakraPetch-BoldItalic.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/LICENSES.md +19 -0
- package/archetypes/idle/assets/skins/sparkorb/OFL.txt +95 -0
- package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/Orbitron-Medium.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf +0 -0
- package/archetypes/idle/assets/skins/sparkorb/Rajdhani-Bold.ttf.ogt +9 -0
- package/archetypes/idle/assets/skins/sparkorb/atlas.png +0 -0
- package/archetypes/idle/assets/skins/sparkorb/atlas.png.ogt +7 -0
- package/archetypes/idle/assets/skins/sparkorb/icons.png +0 -0
- package/archetypes/idle/assets/skins/sparkorb/icons.png.ogt +7 -0
- package/archetypes/idle/assets/skins/sparkorb/skin.ogt +113 -0
- package/archetypes/idle/assets/sprites/glow.png +0 -0
- package/archetypes/idle/assets/sprites/glow.png.ogt +8 -0
- package/archetypes/idle/assets/sprites/mote-crystal.png +0 -0
- package/archetypes/idle/assets/sprites/mote-crystal.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-ember.png +0 -0
- package/archetypes/idle/assets/sprites/mote-ember.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-star.png +0 -0
- package/archetypes/idle/assets/sprites/mote-star.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/mote-storm.png +0 -0
- package/archetypes/idle/assets/sprites/mote-storm.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/orb.png +0 -0
- package/archetypes/idle/assets/sprites/orb.png.ogt +7 -0
- package/archetypes/idle/assets/sprites/spark.png +0 -0
- package/archetypes/idle/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/idle/assets/strings/en.ogt +62 -0
- package/archetypes/idle/assets/strings/zh-CN.ogt +62 -0
- package/archetypes/idle/gitattributes +22 -0
- package/archetypes/idle/host.d.ts +46 -0
- package/archetypes/idle/package.json +18 -0
- package/archetypes/idle/project.ogt +14 -0
- package/archetypes/idle/references/metal/card.png +0 -0
- package/archetypes/idle/references/metal/orbit.png +0 -0
- package/archetypes/idle/references/metal/title.png +0 -0
- package/archetypes/idle/src/components.ts +139 -0
- package/archetypes/idle/src/main.ts +247 -0
- package/archetypes/idle/src/numbers.ts +126 -0
- package/archetypes/idle/src/systems.ts +340 -0
- package/archetypes/idle/src/ui.ts +412 -0
- package/archetypes/idle/tests/away.og.ts +42 -0
- package/archetypes/idle/tests/flow.og.ts +57 -0
- package/archetypes/idle/tests/frames.og.ts +33 -0
- package/archetypes/idle/tests/frames.ts +134 -0
- package/archetypes/idle/tests/lib.ts +74 -0
- package/archetypes/idle/tests/milestone.og.ts +32 -0
- package/archetypes/idle/tests/nova.og.ts +52 -0
- package/archetypes/idle/tests/offline.og.ts +60 -0
- package/archetypes/idle/tests/pad.og.ts +55 -0
- package/archetypes/idle/tests/pause.og.ts +68 -0
- package/archetypes/idle/tests/phone.og.ts +50 -0
- package/archetypes/idle/tests/replay.og.ts +28 -0
- package/archetypes/idle/tools/make-assets.py +448 -0
- package/archetypes/idle/tools/oglite.py +285 -0
- package/archetypes/idle/tsconfig.json +17 -0
- package/archetypes/match3/.origin/setup +14 -0
- package/archetypes/match3/LICENSES.md +7 -0
- package/archetypes/match3/README.md +205 -0
- package/archetypes/match3/assets/audio/cascade.wav +0 -0
- package/archetypes/match3/assets/audio/clear.wav +0 -0
- package/archetypes/match3/assets/audio/lost.wav +0 -0
- package/archetypes/match3/assets/audio/refused.wav +0 -0
- package/archetypes/match3/assets/audio/select.wav +0 -0
- package/archetypes/match3/assets/audio/swap.wav +0 -0
- package/archetypes/match3/assets/audio/won.wav +0 -0
- package/archetypes/match3/assets/fonts/OFL.txt +93 -0
- package/archetypes/match3/assets/fonts/ui.ttf +0 -0
- package/archetypes/match3/assets/fonts/ui.ttf.ogt +11 -0
- package/archetypes/match3/assets/scenes/main.ogt +24 -0
- package/archetypes/match3/assets/sprites/backdrop.png +0 -0
- package/archetypes/match3/assets/sprites/board.ogt +21 -0
- package/archetypes/match3/assets/sprites/cells.ogt +7 -0
- package/archetypes/match3/assets/sprites/cells.png +0 -0
- package/archetypes/match3/assets/sprites/cells.png.ogt +7 -0
- package/archetypes/match3/assets/sprites/gems.ogt +75 -0
- package/archetypes/match3/assets/sprites/gems.png +0 -0
- package/archetypes/match3/assets/sprites/panel.png +0 -0
- package/archetypes/match3/assets/sprites/ring.png +0 -0
- package/archetypes/match3/gitattributes +22 -0
- package/archetypes/match3/host.d.ts +46 -0
- package/archetypes/match3/package.json +18 -0
- package/archetypes/match3/project.ogt +18 -0
- package/archetypes/match3/references/metal/cascade.png +0 -0
- package/archetypes/match3/references/metal/clearing.png +0 -0
- package/archetypes/match3/references/metal/dealt.png +0 -0
- package/archetypes/match3/src/art.ts +54 -0
- package/archetypes/match3/src/board.ts +112 -0
- package/archetypes/match3/src/components.ts +95 -0
- package/archetypes/match3/src/layout.ts +25 -0
- package/archetypes/match3/src/main.ts +199 -0
- package/archetypes/match3/src/systems.ts +631 -0
- package/archetypes/match3/tests/drag.og.ts +31 -0
- package/archetypes/match3/tests/endings.og.ts +40 -0
- package/archetypes/match3/tests/frames.og.ts +49 -0
- package/archetypes/match3/tests/frames.ts +134 -0
- package/archetypes/match3/tests/hidpi.og.ts +41 -0
- package/archetypes/match3/tests/lib.ts +64 -0
- package/archetypes/match3/tests/replay.og.ts +28 -0
- package/archetypes/match3/tests/sounds.og.ts +50 -0
- package/archetypes/match3/tests/swap.og.ts +67 -0
- package/archetypes/match3/tools/make-assets.py +404 -0
- package/archetypes/match3/tools/oglite.py +285 -0
- package/archetypes/match3/tsconfig.json +17 -0
- package/archetypes/rhythm/.origin/setup +14 -0
- package/archetypes/rhythm/LICENSES.md +13 -0
- package/archetypes/rhythm/README.md +235 -0
- package/archetypes/rhythm/assets/audio/bass0.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass1.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass2.wav +0 -0
- package/archetypes/rhythm/assets/audio/bass3.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords0.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords1.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords2.wav +0 -0
- package/archetypes/rhythm/assets/audio/chords3.wav +0 -0
- package/archetypes/rhythm/assets/audio/clear.wav +0 -0
- package/archetypes/rhythm/assets/audio/click.wav +0 -0
- package/archetypes/rhythm/assets/audio/drums.wav +0 -0
- package/archetypes/rhythm/assets/audio/fail.wav +0 -0
- package/archetypes/rhythm/assets/audio/leadA.wav +0 -0
- package/archetypes/rhythm/assets/audio/leadB.wav +0 -0
- package/archetypes/rhythm/assets/audio/miss.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap0.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap1.wav +0 -0
- package/archetypes/rhythm/assets/audio/tap2.wav +0 -0
- package/archetypes/rhythm/assets/audio/ui-close.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-error.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-open.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-press.ogt +9 -0
- package/archetypes/rhythm/assets/audio/ui-success.ogt +9 -0
- package/archetypes/rhythm/assets/charts/neon-pulse.ogt +919 -0
- package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/rhythm/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/rhythm/assets/fonts/OFL.txt +93 -0
- package/archetypes/rhythm/assets/fx/hit.ogt +22 -0
- package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/ChakraPetch-Medium.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/JetBrainsMono-Regular.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/LICENSES.md +17 -0
- package/archetypes/rhythm/assets/skins/lanebeat/OFL.txt +95 -0
- package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/Orbitron-Bold.ttf.ogt +9 -0
- package/archetypes/rhythm/assets/skins/lanebeat/atlas.png +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/atlas.png.ogt +7 -0
- package/archetypes/rhythm/assets/skins/lanebeat/icons.png +0 -0
- package/archetypes/rhythm/assets/skins/lanebeat/icons.png.ogt +7 -0
- package/archetypes/rhythm/assets/skins/lanebeat/skin.ogt +110 -0
- package/archetypes/rhythm/assets/sprites/lane.png +0 -0
- package/archetypes/rhythm/assets/sprites/lane.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/note.png +0 -0
- package/archetypes/rhythm/assets/sprites/note.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/ring.png +0 -0
- package/archetypes/rhythm/assets/sprites/ring.png.ogt +7 -0
- package/archetypes/rhythm/assets/sprites/spark.png +0 -0
- package/archetypes/rhythm/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/rhythm/assets/strings/en.ogt +61 -0
- package/archetypes/rhythm/assets/strings/zh-CN.ogt +61 -0
- package/archetypes/rhythm/gitattributes +22 -0
- package/archetypes/rhythm/host.d.ts +46 -0
- package/archetypes/rhythm/package.json +18 -0
- package/archetypes/rhythm/project.ogt +14 -0
- package/archetypes/rhythm/references/metal/highway.png +0 -0
- package/archetypes/rhythm/references/metal/results.png +0 -0
- package/archetypes/rhythm/references/metal/title.png +0 -0
- package/archetypes/rhythm/src/components.ts +147 -0
- package/archetypes/rhythm/src/main.ts +202 -0
- package/archetypes/rhythm/src/systems.ts +490 -0
- package/archetypes/rhythm/src/ui.ts +421 -0
- package/archetypes/rhythm/tests/band.og.ts +45 -0
- package/archetypes/rhythm/tests/calibrate.og.ts +49 -0
- package/archetypes/rhythm/tests/clear.og.ts +51 -0
- package/archetypes/rhythm/tests/fail.og.ts +40 -0
- package/archetypes/rhythm/tests/flow.og.ts +54 -0
- package/archetypes/rhythm/tests/frames.og.ts +35 -0
- package/archetypes/rhythm/tests/frames.ts +134 -0
- package/archetypes/rhythm/tests/lib.ts +123 -0
- package/archetypes/rhythm/tests/pad.og.ts +41 -0
- package/archetypes/rhythm/tests/pause.og.ts +77 -0
- package/archetypes/rhythm/tests/phone.og.ts +44 -0
- package/archetypes/rhythm/tests/replay.og.ts +27 -0
- package/archetypes/rhythm/tests/timing.og.ts +37 -0
- package/archetypes/rhythm/tools/make-assets.py +383 -0
- package/archetypes/rhythm/tools/oglite.py +285 -0
- package/archetypes/rhythm/tsconfig.json +17 -0
- package/archetypes/roguelike/.origin/setup +14 -0
- package/archetypes/roguelike/LICENSES.md +14 -0
- package/archetypes/roguelike/README.md +256 -0
- package/archetypes/roguelike/assets/ai/foe.ogt +28 -0
- package/archetypes/roguelike/assets/ai/guardian.ogt +26 -0
- package/archetypes/roguelike/assets/audio/bump.wav +0 -0
- package/archetypes/roguelike/assets/audio/drink.wav +0 -0
- package/archetypes/roguelike/assets/audio/fall.wav +0 -0
- package/archetypes/roguelike/assets/audio/hit.wav +0 -0
- package/archetypes/roguelike/assets/audio/hurt.wav +0 -0
- package/archetypes/roguelike/assets/audio/item.wav +0 -0
- package/archetypes/roguelike/assets/audio/kill.wav +0 -0
- package/archetypes/roguelike/assets/audio/pickup.wav +0 -0
- package/archetypes/roguelike/assets/audio/roar.wav +0 -0
- package/archetypes/roguelike/assets/audio/stairs.wav +0 -0
- package/archetypes/roguelike/assets/audio/step.ogt +9 -0
- package/archetypes/roguelike/assets/audio/telegraph.wav +0 -0
- package/archetypes/roguelike/assets/audio/tomb.wav +0 -0
- package/archetypes/roguelike/assets/audio/ui-close.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-error.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-open.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-press.ogt +9 -0
- package/archetypes/roguelike/assets/audio/ui-success.ogt +9 -0
- package/archetypes/roguelike/assets/audio/won.wav +0 -0
- package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/roguelike/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/roguelike/assets/fonts/OFL.txt +93 -0
- package/archetypes/roguelike/assets/maps/dive.ogt +106 -0
- package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/DotGothic16-Regular.ttf.ogt +9 -0
- package/archetypes/roguelike/assets/skins/lampdive/LICENSES.md +16 -0
- package/archetypes/roguelike/assets/skins/lampdive/OFL.txt +93 -0
- package/archetypes/roguelike/assets/skins/lampdive/atlas.png +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/atlas.png.ogt +7 -0
- package/archetypes/roguelike/assets/skins/lampdive/icons.png +0 -0
- package/archetypes/roguelike/assets/skins/lampdive/icons.png.ogt +7 -0
- package/archetypes/roguelike/assets/skins/lampdive/skin.ogt +115 -0
- package/archetypes/roguelike/assets/sprites/dive.ogt +50 -0
- package/archetypes/roguelike/assets/sprites/dive.png +0 -0
- package/archetypes/roguelike/assets/sprites/dive.png.ogt +7 -0
- package/archetypes/roguelike/assets/sprites/glow.png +0 -0
- package/archetypes/roguelike/assets/sprites/glow.png.ogt +7 -0
- package/archetypes/roguelike/assets/strings/en.ogt +57 -0
- package/archetypes/roguelike/assets/strings/zh-CN.ogt +57 -0
- package/archetypes/roguelike/assets/tiles/tomb.ogt +31 -0
- package/archetypes/roguelike/assets/tiles/tomb.png +0 -0
- package/archetypes/roguelike/assets/tiles/tomb.png.ogt +7 -0
- package/archetypes/roguelike/gitattributes +22 -0
- package/archetypes/roguelike/host.d.ts +46 -0
- package/archetypes/roguelike/package.json +18 -0
- package/archetypes/roguelike/project.ogt +14 -0
- package/archetypes/roguelike/references/metal/dive.png +0 -0
- package/archetypes/roguelike/references/metal/over.png +0 -0
- package/archetypes/roguelike/references/metal/title.png +0 -0
- package/archetypes/roguelike/src/art.ts +23 -0
- package/archetypes/roguelike/src/components.ts +169 -0
- package/archetypes/roguelike/src/dive.ts +144 -0
- package/archetypes/roguelike/src/foes.ts +174 -0
- package/archetypes/roguelike/src/main.ts +258 -0
- package/archetypes/roguelike/src/systems.ts +620 -0
- package/archetypes/roguelike/src/ui.ts +350 -0
- package/archetypes/roguelike/tests/flow.og.ts +54 -0
- package/archetypes/roguelike/tests/foes.og.ts +45 -0
- package/archetypes/roguelike/tests/frames.og.ts +32 -0
- package/archetypes/roguelike/tests/frames.ts +134 -0
- package/archetypes/roguelike/tests/lib.ts +83 -0
- package/archetypes/roguelike/tests/lose.og.ts +44 -0
- package/archetypes/roguelike/tests/pad.og.ts +54 -0
- package/archetypes/roguelike/tests/pause.og.ts +59 -0
- package/archetypes/roguelike/tests/phone.og.ts +54 -0
- package/archetypes/roguelike/tests/replay.og.ts +24 -0
- package/archetypes/roguelike/tests/win.og.ts +60 -0
- package/archetypes/roguelike/tools/make-assets.py +518 -0
- package/archetypes/roguelike/tools/oglite.py +285 -0
- package/archetypes/roguelike/tsconfig.json +17 -0
- package/archetypes/runner/.origin/setup +14 -0
- package/archetypes/runner/LICENSES.md +10 -0
- package/archetypes/runner/README.md +225 -0
- package/archetypes/runner/assets/audio/coin.wav +0 -0
- package/archetypes/runner/assets/audio/crash.wav +0 -0
- package/archetypes/runner/assets/audio/hold.ogt +9 -0
- package/archetypes/runner/assets/audio/jump.wav +0 -0
- package/archetypes/runner/assets/audio/start.wav +0 -0
- package/archetypes/runner/assets/fonts/OFL.txt +93 -0
- package/archetypes/runner/assets/fonts/ui.ttf +0 -0
- package/archetypes/runner/assets/fonts/ui.ttf.ogt +11 -0
- package/archetypes/runner/assets/maps/far.ogt +25 -0
- package/archetypes/runner/assets/maps/near.ogt +25 -0
- package/archetypes/runner/assets/maps/roofs.ogt +19 -0
- package/archetypes/runner/assets/scenes/main.ogt +35 -0
- package/archetypes/runner/assets/sprites/coin.ogt +11 -0
- package/archetypes/runner/assets/sprites/coin.png +0 -0
- package/archetypes/runner/assets/sprites/coin.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/courier.ogt +32 -0
- package/archetypes/runner/assets/sprites/courier.png +0 -0
- package/archetypes/runner/assets/sprites/courier.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/props.ogt +21 -0
- package/archetypes/runner/assets/sprites/props.png +0 -0
- package/archetypes/runner/assets/sprites/props.png.ogt +7 -0
- package/archetypes/runner/assets/sprites/sky.png +0 -0
- package/archetypes/runner/assets/sprites/sky.png.ogt +7 -0
- package/archetypes/runner/assets/tiles/city.ogt +18 -0
- package/archetypes/runner/assets/tiles/city.png +0 -0
- package/archetypes/runner/assets/tiles/city.png.ogt +7 -0
- package/archetypes/runner/gitattributes +22 -0
- package/archetypes/runner/host.d.ts +46 -0
- package/archetypes/runner/package.json +18 -0
- package/archetypes/runner/project.ogt +18 -0
- package/archetypes/runner/references/metal/frames-coin.png +0 -0
- package/archetypes/runner/references/metal/frames-crash.png +0 -0
- package/archetypes/runner/references/metal/frames-title.png +0 -0
- package/archetypes/runner/src/art.ts +57 -0
- package/archetypes/runner/src/components.ts +94 -0
- package/archetypes/runner/src/main.ts +241 -0
- package/archetypes/runner/src/systems.ts +553 -0
- package/archetypes/runner/tests/coins.og.ts +30 -0
- package/archetypes/runner/tests/flow.og.ts +72 -0
- package/archetypes/runner/tests/frames.og.ts +37 -0
- package/archetypes/runner/tests/frames.ts +134 -0
- package/archetypes/runner/tests/jump.og.ts +44 -0
- package/archetypes/runner/tests/lib.ts +42 -0
- package/archetypes/runner/tests/records.og.ts +43 -0
- package/archetypes/runner/tests/replay.og.ts +36 -0
- package/archetypes/runner/tests/sounds.og.ts +43 -0
- package/archetypes/runner/tests/tune.og.ts +36 -0
- package/archetypes/runner/tools/make-assets.py +603 -0
- package/archetypes/runner/tools/oglite.py +285 -0
- package/archetypes/runner/tsconfig.json +17 -0
- package/archetypes/third-person/.origin/setup +14 -0
- package/archetypes/third-person/LICENSES.md +23 -0
- package/archetypes/third-person/README.md +249 -0
- package/archetypes/third-person/assets/ai/sentinel-east.ogt +69 -0
- package/archetypes/third-person/assets/ai/sentinel-north.ogt +69 -0
- package/archetypes/third-person/assets/ai/sentinel-west.ogt +69 -0
- package/archetypes/third-person/assets/anims/humanoid-clips.glb +0 -0
- package/archetypes/third-person/assets/audio/alarm.wav +0 -0
- package/archetypes/third-person/assets/audio/dawn.wav +0 -0
- package/archetypes/third-person/assets/audio/grab.wav +0 -0
- package/archetypes/third-person/assets/audio/ignite.wav +0 -0
- package/archetypes/third-person/assets/audio/jump.wav +0 -0
- package/archetypes/third-person/assets/audio/night.wav +0 -0
- package/archetypes/third-person/assets/audio/splash.wav +0 -0
- package/archetypes/third-person/assets/audio/step.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-close.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-error.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-open.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-press.ogt +9 -0
- package/archetypes/third-person/assets/audio/ui-success.ogt +9 -0
- package/archetypes/third-person/assets/audio/vale.wav +0 -0
- package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/third-person/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/third-person/assets/fonts/OFL.txt +93 -0
- package/archetypes/third-person/assets/fx/burst.ogt +25 -0
- package/archetypes/third-person/assets/fx/fire.ogt +24 -0
- package/archetypes/third-person/assets/fx/smoke.ogt +24 -0
- package/archetypes/third-person/assets/fx/splash.ogt +24 -0
- package/archetypes/third-person/assets/luts/teal-orange.cube +4917 -0
- package/archetypes/third-person/assets/models/stout.glb +0 -0
- package/archetypes/third-person/assets/models/strider.glb +0 -0
- package/archetypes/third-person/assets/models/strider.glb.ogt +9 -0
- package/archetypes/third-person/assets/nav/vale.ogt +43 -0
- package/archetypes/third-person/assets/rigs/strider.ogt +6 -0
- package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf +0 -0
- package/archetypes/third-person/assets/skins/ember/Cinzel-Bold.ttf.ogt +9 -0
- package/archetypes/third-person/assets/skins/ember/LICENSES.md +13 -0
- package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf +0 -0
- package/archetypes/third-person/assets/skins/ember/Newsreader-Medium.ttf.ogt +9 -0
- package/archetypes/third-person/assets/skins/ember/OFL.txt +94 -0
- package/archetypes/third-person/assets/skins/ember/atlas.png +0 -0
- package/archetypes/third-person/assets/skins/ember/atlas.png.ogt +7 -0
- package/archetypes/third-person/assets/skins/ember/icons.png +0 -0
- package/archetypes/third-person/assets/skins/ember/icons.png.ogt +7 -0
- package/archetypes/third-person/assets/skins/ember/skin.ogt +109 -0
- package/archetypes/third-person/assets/strings/en.ogt +47 -0
- package/archetypes/third-person/assets/strings/zh-CN.ogt +47 -0
- package/archetypes/third-person/assets/terrain/vale-control.png +0 -0
- package/archetypes/third-person/assets/terrain/vale.ogt +42 -0
- package/archetypes/third-person/assets/terrain/vale.png +0 -0
- package/archetypes/third-person/assets/textures/decals.png +0 -0
- package/archetypes/third-person/assets/textures/flame.png +0 -0
- package/archetypes/third-person/assets/textures/smoke.png +0 -0
- package/archetypes/third-person/assets/textures/spark.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-grass.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-path.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-rock.png +0 -0
- package/archetypes/third-person/assets/textures/terrain-sand.png +0 -0
- package/archetypes/third-person/gitattributes +22 -0
- package/archetypes/third-person/host.d.ts +46 -0
- package/archetypes/third-person/package.json +18 -0
- package/archetypes/third-person/project.ogt +14 -0
- package/archetypes/third-person/references/metal/beacon.png +0 -0
- package/archetypes/third-person/references/metal/dawn.png +0 -0
- package/archetypes/third-person/references/metal/title.png +0 -0
- package/archetypes/third-person/references/webgl2/beacon.png +0 -0
- package/archetypes/third-person/src/anim.ts +87 -0
- package/archetypes/third-person/src/beacons.ts +80 -0
- package/archetypes/third-person/src/components.ts +148 -0
- package/archetypes/third-person/src/hero.ts +293 -0
- package/archetypes/third-person/src/main.ts +164 -0
- package/archetypes/third-person/src/sentinels.ts +159 -0
- package/archetypes/third-person/src/sound.ts +66 -0
- package/archetypes/third-person/src/ui.ts +451 -0
- package/archetypes/third-person/src/world.ts +212 -0
- package/archetypes/third-person/tests/flow.og.ts +70 -0
- package/archetypes/third-person/tests/frames.og.ts +35 -0
- package/archetypes/third-person/tests/frames.ts +134 -0
- package/archetypes/third-person/tests/hitstop.og.ts +26 -0
- package/archetypes/third-person/tests/lib.ts +87 -0
- package/archetypes/third-person/tests/lose.og.ts +44 -0
- package/archetypes/third-person/tests/pause.og.ts +64 -0
- package/archetypes/third-person/tests/phone.og.ts +65 -0
- package/archetypes/third-person/tests/replay.og.ts +32 -0
- package/archetypes/third-person/tests/restart.og.ts +28 -0
- package/archetypes/third-person/tests/sentinels.og.ts +48 -0
- package/archetypes/third-person/tests/win.og.ts +62 -0
- package/archetypes/third-person/tools/make-assets.py +353 -0
- package/archetypes/third-person/tools/oglite.py +285 -0
- package/archetypes/third-person/tsconfig.json +17 -0
- package/archetypes/topdown/.origin/setup +14 -0
- package/archetypes/topdown/LICENSES.md +10 -0
- package/archetypes/topdown/README.md +235 -0
- package/archetypes/topdown/assets/ai/slime.ogt +49 -0
- package/archetypes/topdown/assets/audio/coin.wav +0 -0
- package/archetypes/topdown/assets/audio/door.wav +0 -0
- package/archetypes/topdown/assets/audio/hit.wav +0 -0
- package/archetypes/topdown/assets/audio/hurt.wav +0 -0
- package/archetypes/topdown/assets/audio/key.wav +0 -0
- package/archetypes/topdown/assets/audio/step.ogt +9 -0
- package/archetypes/topdown/assets/fonts/OFL.txt +93 -0
- package/archetypes/topdown/assets/fonts/ui.ttf +0 -0
- package/archetypes/topdown/assets/fonts/ui.ttf.ogt +9 -0
- package/archetypes/topdown/assets/maps/tomb.tmj +2183 -0
- package/archetypes/topdown/assets/sprites/hero.ogt +58 -0
- package/archetypes/topdown/assets/sprites/hero.png +0 -0
- package/archetypes/topdown/assets/sprites/hero.png.ogt +7 -0
- package/archetypes/topdown/assets/sprites/items.ogt +15 -0
- package/archetypes/topdown/assets/sprites/items.png +0 -0
- package/archetypes/topdown/assets/sprites/items.png.ogt +7 -0
- package/archetypes/topdown/assets/sprites/slime.ogt +17 -0
- package/archetypes/topdown/assets/sprites/slime.png +0 -0
- package/archetypes/topdown/assets/sprites/slime.png.ogt +7 -0
- package/archetypes/topdown/assets/tiles/dungeon.ogt +65 -0
- package/archetypes/topdown/assets/tiles/dungeon.png +0 -0
- package/archetypes/topdown/assets/tiles/dungeon.png.ogt +7 -0
- package/archetypes/topdown/gitattributes +22 -0
- package/archetypes/topdown/host.d.ts +46 -0
- package/archetypes/topdown/package.json +18 -0
- package/archetypes/topdown/project.ogt +14 -0
- package/archetypes/topdown/references/metal/crit.png +0 -0
- package/archetypes/topdown/references/metal/dash.png +0 -0
- package/archetypes/topdown/references/metal/east.png +0 -0
- package/archetypes/topdown/references/metal/start.png +0 -0
- package/archetypes/topdown/src/art.ts +48 -0
- package/archetypes/topdown/src/components.ts +128 -0
- package/archetypes/topdown/src/main.ts +275 -0
- package/archetypes/topdown/src/systems.ts +503 -0
- package/archetypes/topdown/tests/ai.og.ts +64 -0
- package/archetypes/topdown/tests/combat.og.ts +72 -0
- package/archetypes/topdown/tests/door.og.ts +62 -0
- package/archetypes/topdown/tests/flee.og.ts +49 -0
- package/archetypes/topdown/tests/frames.og.ts +45 -0
- package/archetypes/topdown/tests/frames.ts +134 -0
- package/archetypes/topdown/tests/lib.ts +71 -0
- package/archetypes/topdown/tests/replay.og.ts +40 -0
- package/archetypes/topdown/tests/walk.og.ts +46 -0
- package/archetypes/topdown/tools/make-assets.py +623 -0
- package/archetypes/topdown/tools/oglite.py +285 -0
- package/archetypes/topdown/tsconfig.json +17 -0
- package/archetypes/tower-defense/.origin/setup +14 -0
- package/archetypes/tower-defense/LICENSES.md +15 -0
- package/archetypes/tower-defense/README.md +240 -0
- package/archetypes/tower-defense/assets/ai/plant.ogt +16 -0
- package/archetypes/tower-defense/assets/audio/clear.wav +0 -0
- package/archetypes/tower-defense/assets/audio/frost.wav +0 -0
- package/archetypes/tower-defense/assets/audio/leak.wav +0 -0
- package/archetypes/tower-defense/assets/audio/lost.wav +0 -0
- package/archetypes/tower-defense/assets/audio/night.wav +0 -0
- package/archetypes/tower-defense/assets/audio/plant.wav +0 -0
- package/archetypes/tower-defense/assets/audio/pop.wav +0 -0
- package/archetypes/tower-defense/assets/audio/thorn.wav +0 -0
- package/archetypes/tower-defense/assets/audio/ui-close.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-error.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-open.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-press.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/ui-success.ogt +9 -0
- package/archetypes/tower-defense/assets/audio/wave.wav +0 -0
- package/archetypes/tower-defense/assets/audio/won.wav +0 -0
- package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf +0 -0
- package/archetypes/tower-defense/assets/fonts/NotoSansSC-subset.ttf.ogt +10 -0
- package/archetypes/tower-defense/assets/fonts/OFL.txt +93 -0
- package/archetypes/tower-defense/assets/fx/fireflies.ogt +21 -0
- package/archetypes/tower-defense/assets/fx/pop.ogt +21 -0
- package/archetypes/tower-defense/assets/maps/garden.ogt +91 -0
- package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/Fredoka-SemiBold.ttf.ogt +9 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LICENSES.md +13 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/LilitaOne-Regular.ttf.ogt +9 -0
- package/archetypes/tower-defense/assets/skins/pathguard/OFL.txt +94 -0
- package/archetypes/tower-defense/assets/skins/pathguard/atlas.png +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/atlas.png.ogt +7 -0
- package/archetypes/tower-defense/assets/skins/pathguard/icons.png +0 -0
- package/archetypes/tower-defense/assets/skins/pathguard/icons.png.ogt +7 -0
- package/archetypes/tower-defense/assets/skins/pathguard/skin.ogt +111 -0
- package/archetypes/tower-defense/assets/sprites/garden.ogt +66 -0
- package/archetypes/tower-defense/assets/sprites/garden.png +0 -0
- package/archetypes/tower-defense/assets/sprites/garden.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/glow.png +0 -0
- package/archetypes/tower-defense/assets/sprites/glow.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/ring.png +0 -0
- package/archetypes/tower-defense/assets/sprites/ring.png.ogt +7 -0
- package/archetypes/tower-defense/assets/sprites/spark.png +0 -0
- package/archetypes/tower-defense/assets/sprites/spark.png.ogt +7 -0
- package/archetypes/tower-defense/assets/strings/en.ogt +47 -0
- package/archetypes/tower-defense/assets/strings/zh-CN.ogt +47 -0
- package/archetypes/tower-defense/assets/tiles/garden.ogt +10 -0
- package/archetypes/tower-defense/assets/tiles/garden.png +0 -0
- package/archetypes/tower-defense/assets/tiles/garden.png.ogt +7 -0
- package/archetypes/tower-defense/gitattributes +22 -0
- package/archetypes/tower-defense/host.d.ts +46 -0
- package/archetypes/tower-defense/package.json +18 -0
- package/archetypes/tower-defense/project.ogt +14 -0
- package/archetypes/tower-defense/references/metal/end.png +0 -0
- package/archetypes/tower-defense/references/metal/title.png +0 -0
- package/archetypes/tower-defense/references/metal/wave.png +0 -0
- package/archetypes/tower-defense/src/art.ts +24 -0
- package/archetypes/tower-defense/src/components.ts +156 -0
- package/archetypes/tower-defense/src/main.ts +253 -0
- package/archetypes/tower-defense/src/systems.ts +658 -0
- package/archetypes/tower-defense/src/ui.ts +382 -0
- package/archetypes/tower-defense/tests/flow.og.ts +56 -0
- package/archetypes/tower-defense/tests/frames.og.ts +31 -0
- package/archetypes/tower-defense/tests/frames.ts +134 -0
- package/archetypes/tower-defense/tests/lib.ts +81 -0
- package/archetypes/tower-defense/tests/lose.og.ts +40 -0
- package/archetypes/tower-defense/tests/maze.og.ts +53 -0
- package/archetypes/tower-defense/tests/pad.og.ts +41 -0
- package/archetypes/tower-defense/tests/pause.og.ts +59 -0
- package/archetypes/tower-defense/tests/phone.og.ts +49 -0
- package/archetypes/tower-defense/tests/replay.og.ts +28 -0
- package/archetypes/tower-defense/tests/win.og.ts +58 -0
- package/archetypes/tower-defense/tools/make-assets.py +640 -0
- package/archetypes/tower-defense/tools/oglite.py +285 -0
- package/archetypes/tower-defense/tsconfig.json +17 -0
- package/bin/create.ts +496 -0
- package/looks/catalogue.json +1871 -0
- package/package.json +31 -4
- package/template/.origin/setup +14 -0
- package/template/LICENSES.md +23 -0
- package/template/README.md +37 -0
- package/template/assets/ai/guard.ogt +48 -0
- package/template/assets/audio/blip.wav +0 -0
- package/template/assets/audio/click.ogt +9 -0
- package/template/assets/fonts/NotoSansSC-Regular-subset.ttf +0 -0
- package/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/template/assets/fonts/OFL.txt +93 -0
- package/template/assets/looks/origin-game.ogt +12 -0
- package/template/assets/models/dropship.glb +0 -0
- package/template/assets/models/walker.glb +0 -0
- package/template/assets/nav/yard.ogt +27 -0
- package/template/assets/scenes/main.ogt +34 -0
- package/template/assets/strings/en.ogt +14 -0
- package/template/assets/textures/stone.png +0 -0
- package/template/assets/textures/stone_n.png +0 -0
- package/template/assets/textures/stone_n.png.ogt +7 -0
- package/template/gitattributes +22 -0
- package/template/package.json +18 -0
- package/template/project.ogt +25 -0
- package/template/src/main.ts +246 -0
- package/template/tests/guard.og.ts +67 -0
- package/template/tests/smoke.og.ts +43 -0
- package/template/tsconfig.json +17 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
// Tomb Room's systems, in tick order: Control (the hero: walk, swing, dash,
|
|
2
|
+
// hurt), Slimes (take hits, knockback and death; what a slime decides is
|
|
3
|
+
// assets/ai/slime.ogt, run by og.ai before these systems and moved by og.nav
|
|
4
|
+
// after them), Pickups (coins and the key), Door (the locked doorway and the
|
|
5
|
+
// rooms), Sfx (the voices) and Hud (screen text). `biteHero` is the
|
|
6
|
+
// behaviour's `bite` command.
|
|
7
|
+
//
|
|
8
|
+
// Control reads the hero's flipbook events: a walk clip's "step" counts a
|
|
9
|
+
// footstep, an attack clip's "hit" is the sword's strike frame, so the
|
|
10
|
+
// damage lands on the frame the blade is out. Every hit is felt five ways:
|
|
11
|
+
// the slime flashes white and squashes, it is knocked back, the world holds
|
|
12
|
+
// for a few ticks (og.time.hitStop), the camera shakes and kicks, and the
|
|
13
|
+
// damage floats up (the third hit is a crit).
|
|
14
|
+
|
|
15
|
+
import { og, defineSystem } from "@origingame/engine";
|
|
16
|
+
import type { Entity, Value, Vector3, View, World } from "@origingame/engine";
|
|
17
|
+
import { HeroClip, heroClip } from "./art.ts";
|
|
18
|
+
import { Facing, Game, Hero, Pickup, PickupKind, Report, Slime, Sfx, Sound, Tuning } from "./components.ts";
|
|
19
|
+
|
|
20
|
+
/** The swing's length: the attack clips' two frames (0.07 s + 0.18 s). */
|
|
21
|
+
export const ATTACK_TICKS = 15;
|
|
22
|
+
/** Ticks after hp reaches 0 before the hero stands up at the spawn. */
|
|
23
|
+
const DOWN_TICKS = 90;
|
|
24
|
+
/** Damage per hit, in order; later hits repeat the last. */
|
|
25
|
+
const DAMAGE = [12, 12, 26] as const;
|
|
26
|
+
/** A struck slime's squash. */
|
|
27
|
+
const IMPACT = { x: 1.45, y: 0.6 } as const;
|
|
28
|
+
/** The chest: the sword and the pickups measure from this high above the feet. */
|
|
29
|
+
const CHEST = 0.45;
|
|
30
|
+
|
|
31
|
+
const WHITE = { r: 1, g: 1, b: 1 };
|
|
32
|
+
const GOLD = { r: 1, g: 0.72, b: 0.2 };
|
|
33
|
+
const RED = { r: 1, g: 0.25, b: 0.2 };
|
|
34
|
+
const MINT = { r: 0.55, g: 0.95, b: 0.7 };
|
|
35
|
+
|
|
36
|
+
function must(r: { ok: true } | { ok: false; reason: string }): void {
|
|
37
|
+
if (!r.ok) throw new Error(r.reason);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fontOf(g: { font: number }): never {
|
|
41
|
+
return g.font as unknown as never;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A triangle wave of the tick, 0..1 over `period` ticks (tick arithmetic only, so every host agrees). */
|
|
45
|
+
function tri(tick: number, period: number): number {
|
|
46
|
+
const p = (tick % period) / period;
|
|
47
|
+
return p < 0.5 ? p * 2 : 2 - p * 2;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function float(world: World, font: never, text: string, at: Vector3, color: { r: number; g: number; b: number }, crit = false): void {
|
|
51
|
+
must(og.render.floatingText(world, { text, position: at, font, size: 0.42, color, crit, rise: 1.1, lifetime: 0.75 }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The hero: input moves it on the tick it is read; Space swings, Shift dashes; hurt knocks it back and flickers it. */
|
|
55
|
+
export const Control = defineSystem(
|
|
56
|
+
"Control",
|
|
57
|
+
{
|
|
58
|
+
reads: [og.Input, Tuning, og.FlipbookEvent],
|
|
59
|
+
writes: [Game, Hero, og.Transform, og.CharacterController, og.Flipbook, og.Sprite, og.Afterimage],
|
|
60
|
+
},
|
|
61
|
+
(q, time, world) => {
|
|
62
|
+
const game = world.resource(Game);
|
|
63
|
+
const tune = world.resource(Tuning);
|
|
64
|
+
const input = world.resource(og.Input);
|
|
65
|
+
if (!game.ok || !tune.ok || !input.ok) return;
|
|
66
|
+
const g = game.value;
|
|
67
|
+
const t = tune.value;
|
|
68
|
+
const inp = input.value;
|
|
69
|
+
g.swing = false;
|
|
70
|
+
for (const a of q.archetypes()) {
|
|
71
|
+
const hero = a.column(Hero);
|
|
72
|
+
const p = a.column(og.Transform).position;
|
|
73
|
+
const v = a.column(og.CharacterController).move;
|
|
74
|
+
const fb = a.column(og.Flipbook);
|
|
75
|
+
const sprite = a.column(og.Sprite);
|
|
76
|
+
const ghost = a.column(og.Afterimage);
|
|
77
|
+
for (let i = 0; i < a.count; i++) {
|
|
78
|
+
const me = a.entity(i);
|
|
79
|
+
const x = p[i * 3]!;
|
|
80
|
+
const y = p[i * 3 + 1]!;
|
|
81
|
+
g.heroX = x;
|
|
82
|
+
g.heroY = y;
|
|
83
|
+
// The flipbook's frame events from the hero's clips.
|
|
84
|
+
for (const e of world.events(og.FlipbookEvent)) {
|
|
85
|
+
if (e.entity !== me) continue;
|
|
86
|
+
if (e.name === "step") {
|
|
87
|
+
g.steps += 1;
|
|
88
|
+
g.sfx |= Sound.Step;
|
|
89
|
+
}
|
|
90
|
+
if (e.name === "hit") {
|
|
91
|
+
const d = facingVector(g.facing);
|
|
92
|
+
g.swing = true;
|
|
93
|
+
g.swings += 1;
|
|
94
|
+
g.swingX = x + d.x * t.reach;
|
|
95
|
+
g.swingY = y + CHEST + d.y * t.reach;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// A hit stop holds the body (physics is frozen); the timers wait with it.
|
|
99
|
+
if (time.hitStop > 0) continue;
|
|
100
|
+
|
|
101
|
+
if (g.iframes > 0) g.iframes -= 1;
|
|
102
|
+
sprite.opacity[i] = g.iframes > 0 && Math.floor(g.iframes / 4) % 2 === 1 ? 0.35 : 1;
|
|
103
|
+
g.knockX *= 0.8;
|
|
104
|
+
g.knockY *= 0.8;
|
|
105
|
+
if (Math.abs(g.knockX) < 0.05) g.knockX = 0;
|
|
106
|
+
if (Math.abs(g.knockY) < 0.05) g.knockY = 0;
|
|
107
|
+
|
|
108
|
+
// Down: the hero lies still, then stands up at the spawn with full hearts.
|
|
109
|
+
if (g.hp === 0) {
|
|
110
|
+
hero.attack[i] = 0;
|
|
111
|
+
hero.dash[i] = 0;
|
|
112
|
+
ghost.active[i] = 0;
|
|
113
|
+
v[i * 3] = 0;
|
|
114
|
+
v[i * 3 + 1] = 0;
|
|
115
|
+
g.downTicks += 1;
|
|
116
|
+
if (g.downTicks >= DOWN_TICKS) {
|
|
117
|
+
g.hp = g.hpMax;
|
|
118
|
+
g.downTicks = 0;
|
|
119
|
+
g.iframes = t.iframes;
|
|
120
|
+
p[i * 3] = g.spawnX;
|
|
121
|
+
p[i * 3 + 1] = g.spawnY;
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Which way the keys point: WASD and the arrows, summed and clamped.
|
|
127
|
+
const k1 = inp.axis2("KeyA", "KeyD", "KeyS", "KeyW");
|
|
128
|
+
const k2 = inp.axis2("ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp");
|
|
129
|
+
let mx = Math.max(-1, Math.min(1, k1.x + k2.x));
|
|
130
|
+
let my = Math.max(-1, Math.min(1, k1.y + k2.y));
|
|
131
|
+
const len = Math.hypot(mx, my);
|
|
132
|
+
if (len > 0) {
|
|
133
|
+
mx /= len;
|
|
134
|
+
my /= len;
|
|
135
|
+
}
|
|
136
|
+
const moving = len > 0;
|
|
137
|
+
hero.moving[i] = moving ? 1 : 0;
|
|
138
|
+
|
|
139
|
+
if (hero.dashCooldown[i]! > 0) hero.dashCooldown[i] = hero.dashCooldown[i]! - 1;
|
|
140
|
+
const attackPressed = inp.pressed("Space") || inp.pressed("KeyJ");
|
|
141
|
+
const dashPressed = inp.pressed("ShiftLeft") || inp.pressed("ShiftRight") || inp.pressed("KeyK");
|
|
142
|
+
|
|
143
|
+
// Facing follows the stronger axis; a diagonal keeps the old facing when it is one of its two keys.
|
|
144
|
+
if (moving && hero.attack[i] === 0) g.facing = faceOf(mx, my, g.facing);
|
|
145
|
+
|
|
146
|
+
if (attackPressed && hero.attack[i] === 0 && hero.dash[i] === 0) hero.attack[i] = ATTACK_TICKS;
|
|
147
|
+
if (dashPressed && hero.attack[i] === 0 && hero.dash[i] === 0 && hero.dashCooldown[i] === 0) {
|
|
148
|
+
const d = moving ? { x: mx, y: my } : facingVector(g.facing);
|
|
149
|
+
hero.dash[i] = t.dashTicks;
|
|
150
|
+
hero.dashCooldown[i] = t.dashCooldown;
|
|
151
|
+
hero.dashX[i] = d.x;
|
|
152
|
+
hero.dashY[i] = d.y;
|
|
153
|
+
hero.dashes[i] = hero.dashes[i]! + 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let vx = 0;
|
|
157
|
+
let vy = 0;
|
|
158
|
+
if (hero.dash[i]! > 0) {
|
|
159
|
+
hero.dash[i] = hero.dash[i]! - 1;
|
|
160
|
+
vx = hero.dashX[i]! * t.dashSpeed;
|
|
161
|
+
vy = hero.dashY[i]! * t.dashSpeed;
|
|
162
|
+
} else if (hero.attack[i]! > 0) {
|
|
163
|
+
// Rooted while swinging: the blade, not the feet, carries the hit.
|
|
164
|
+
hero.attack[i] = hero.attack[i]! - 1;
|
|
165
|
+
} else if (moving) {
|
|
166
|
+
vx = mx * t.speed;
|
|
167
|
+
vy = my * t.speed;
|
|
168
|
+
}
|
|
169
|
+
ghost.active[i] = hero.dash[i]! > 0 ? 1 : 0;
|
|
170
|
+
v[i * 3] = vx + g.knockX;
|
|
171
|
+
v[i * 3 + 1] = vy + g.knockY;
|
|
172
|
+
v[i * 3 + 2] = 0;
|
|
173
|
+
|
|
174
|
+
// The clip: a swing, a walk or a stand, in the facing's row. A clip change restarts it.
|
|
175
|
+
const swinging = hero.attack[i]! > 0;
|
|
176
|
+
const action = swinging ? HeroClip.Attack : moving || hero.dash[i]! > 0 ? HeroClip.Walk : HeroClip.Idle;
|
|
177
|
+
const want = heroClip(action, g.facing);
|
|
178
|
+
if (fb.clip[i] !== want) fb.clip[i] = want;
|
|
179
|
+
sprite.flipX[i] = g.facing === Facing.Left ? 1 : 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
export function facingVector(f: number): { x: number; y: number } {
|
|
186
|
+
return f === Facing.Up ? { x: 0, y: 1 } : f === Facing.Down ? { x: 0, y: -1 } : f === Facing.Left ? { x: -1, y: 0 } : { x: 1, y: 0 };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function faceOf(x: number, y: number, old: number): number {
|
|
190
|
+
const keeps = (old === Facing.Left && x < 0) || (old === Facing.Right && x > 0) || (old === Facing.Up && y > 0) || (old === Facing.Down && y < 0);
|
|
191
|
+
if (Math.abs(x) > 0.01 && Math.abs(y) > 0.01 && keeps) return old;
|
|
192
|
+
if (Math.abs(x) >= Math.abs(y)) return x < 0 ? Facing.Left : Facing.Right;
|
|
193
|
+
return y < 0 ? Facing.Down : Facing.Up;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Slimes take the sword's hits, are knocked back and stunned, and die. The
|
|
198
|
+
* walking is og.nav's (the agent's velocity) except while stunned, held by a
|
|
199
|
+
* hit stop or dead, when `NavAgent.drive` is off and this system owns the
|
|
200
|
+
* velocity; dead slimes stop thinking. Perception's sight follows the tunable.
|
|
201
|
+
*/
|
|
202
|
+
export const Slimes = defineSystem(
|
|
203
|
+
"Slimes",
|
|
204
|
+
{ reads: [Tuning], writes: [Game, Slime, og.Transform, og.Velocity, og.Flipbook, og.Sprite, og.NavAgent, og.AiAgent, og.Perception], after: ["Control"] },
|
|
205
|
+
(q, time, world) => {
|
|
206
|
+
const game = world.resource(Game);
|
|
207
|
+
const tune = world.resource(Tuning);
|
|
208
|
+
if (!game.ok || !tune.ok) return;
|
|
209
|
+
const g = game.value;
|
|
210
|
+
const t = tune.value;
|
|
211
|
+
const font = fontOf(g);
|
|
212
|
+
for (const a of q.archetypes()) {
|
|
213
|
+
const s = a.column(Slime);
|
|
214
|
+
const p = a.column(og.Transform).position;
|
|
215
|
+
const v = a.column(og.Velocity).linear;
|
|
216
|
+
const fb = a.column(og.Flipbook);
|
|
217
|
+
const sprite = a.column(og.Sprite);
|
|
218
|
+
const nav = a.column(og.NavAgent);
|
|
219
|
+
const ai = a.column(og.AiAgent);
|
|
220
|
+
const eyes = a.column(og.Perception);
|
|
221
|
+
for (let i = 0; i < a.count; i++) {
|
|
222
|
+
const me = a.entity(i);
|
|
223
|
+
const x = p[i * 3]!;
|
|
224
|
+
const y = p[i * 3 + 1]!;
|
|
225
|
+
eyes.sight[i] = g.hp > 0 ? t.slimeSight : 0;
|
|
226
|
+
if (s.dead[i]) {
|
|
227
|
+
nav.drive[i] = 0;
|
|
228
|
+
ai.active[i] = 0;
|
|
229
|
+
v[i * 3] = 0;
|
|
230
|
+
v[i * 3 + 1] = 0;
|
|
231
|
+
if (time.hitStop > 0) continue;
|
|
232
|
+
s.deadTicks[i] = s.deadTicks[i]! + 1;
|
|
233
|
+
if (s.deadTicks[i] === 30) sprite.visible[i] = 0;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// The sword's strike frame: a circle of radius reach around the swing point.
|
|
238
|
+
if (g.swing && Math.hypot(g.swingX - x, g.swingY - (y + 0.3)) < t.reach + 0.35) {
|
|
239
|
+
hitSlime(world, me, g, t, s, i, x, y, font);
|
|
240
|
+
fb.clip[i] = s.hurt[i]!;
|
|
241
|
+
}
|
|
242
|
+
if (s.dead[i] || time.hitStop > 0) {
|
|
243
|
+
nav.drive[i] = 0;
|
|
244
|
+
v[i * 3] = 0;
|
|
245
|
+
v[i * 3 + 1] = 0;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (s.stun[i]! > 0) {
|
|
249
|
+
nav.drive[i] = 0;
|
|
250
|
+
s.stun[i] = s.stun[i]! - 1;
|
|
251
|
+
v[i * 3] = s.knockX[i]!;
|
|
252
|
+
v[i * 3 + 1] = s.knockY[i]!;
|
|
253
|
+
v[i * 3 + 2] = 0;
|
|
254
|
+
s.knockX[i] = s.knockX[i]! * 0.82;
|
|
255
|
+
s.knockY[i] = s.knockY[i]! * 0.82;
|
|
256
|
+
if (s.stun[i] === 0) fb.clip[i] = s.bob[i]!;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
nav.drive[i] = 1;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* `bite:<slime>`: the behaviour's attack (og.ai queues it with the slime's
|
|
267
|
+
* handle when the hero is in reach; it runs before the next tick). The hero
|
|
268
|
+
* is hurt and knocked away unless it is down or still blinking, and the
|
|
269
|
+
* slime bounces back off the bite, so it does not sit on the hero.
|
|
270
|
+
*/
|
|
271
|
+
export function biteHero(world: World, args: readonly string[]): void {
|
|
272
|
+
const me = Number(args[0]) as Entity;
|
|
273
|
+
const game = world.resource(Game);
|
|
274
|
+
const tune = world.resource(Tuning);
|
|
275
|
+
const slime = world.get(me, Slime);
|
|
276
|
+
const at = world.get(me, og.Transform);
|
|
277
|
+
if (!game.ok || !tune.ok || !slime.ok || !at.ok) throw new Error(`bite: ${args[0]} is not a slime`);
|
|
278
|
+
const g = game.value;
|
|
279
|
+
const s = slime.value;
|
|
280
|
+
if (s.dead || s.stun > 0 || g.hp === 0 || g.iframes > 0) return;
|
|
281
|
+
const dx = g.heroX - at.value.position.x;
|
|
282
|
+
const dy = g.heroY - at.value.position.y;
|
|
283
|
+
const d = Math.hypot(dx, dy);
|
|
284
|
+
if (d > 0.85) return;
|
|
285
|
+
const ux = d > 0.01 ? dx / d : 0;
|
|
286
|
+
const uy = d > 0.01 ? dy / d : 1;
|
|
287
|
+
hurtHero(world, g, tune.value, ux, uy, fontOf(g));
|
|
288
|
+
must(world.set(me, Slime, { knockX: -ux * tune.value.knockback * 0.5, knockY: -uy * tune.value.knockback * 0.5, stun: 20, bites: s.bites + 1 }));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
type GameValue = Value<typeof Game>;
|
|
292
|
+
type TuneValue = Value<typeof Tuning>;
|
|
293
|
+
type SlimeView = View<typeof Slime>;
|
|
294
|
+
|
|
295
|
+
function hitSlime(world: World, me: Entity, g: GameValue, t: TuneValue, s: SlimeView, i: number, x: number, y: number, font: never): void {
|
|
296
|
+
g.enemyHits += 1;
|
|
297
|
+
const dmg = DAMAGE[Math.min(g.enemyHits, DAMAGE.length) - 1]!;
|
|
298
|
+
const crit = g.enemyHits >= DAMAGE.length;
|
|
299
|
+
const hp = Math.max(0, s.hp[i]! - dmg);
|
|
300
|
+
s.hp[i] = hp;
|
|
301
|
+
g.enemyHp = hp;
|
|
302
|
+
// Knocked away from the hero.
|
|
303
|
+
let kx = x - g.heroX;
|
|
304
|
+
let ky = y - g.heroY;
|
|
305
|
+
const kd = Math.hypot(kx, ky);
|
|
306
|
+
if (kd > 0.01) {
|
|
307
|
+
kx /= kd;
|
|
308
|
+
ky /= kd;
|
|
309
|
+
} else {
|
|
310
|
+
const f = facingVector(g.facing);
|
|
311
|
+
kx = f.x;
|
|
312
|
+
ky = f.y;
|
|
313
|
+
}
|
|
314
|
+
s.knockX[i] = kx * t.knockback;
|
|
315
|
+
s.knockY[i] = ky * t.knockback;
|
|
316
|
+
s.stun[i] = 16;
|
|
317
|
+
must(og.tween.flash(world, { entity: me, duration: 0.14 }));
|
|
318
|
+
must(og.tween.squash(world, { entity: me, impact: IMPACT, duration: 0.28 }));
|
|
319
|
+
must(og.time.hitStop(world, crit ? t.hitStop + 3 : t.hitStop));
|
|
320
|
+
must(og.camera.shake(world, Math.min(1, crit ? t.shake * 1.8 : t.shake)));
|
|
321
|
+
must(og.camera.kick(world, { x: kx, y: ky, z: 0 }, crit ? 0.25 : 0.12));
|
|
322
|
+
float(world, font, crit ? `${dmg}!` : `${dmg}`, { x, y: y + 1.1, z: 0 }, crit ? GOLD : WHITE, crit);
|
|
323
|
+
g.sfx |= Sound.Hit;
|
|
324
|
+
if (hp === 0) {
|
|
325
|
+
s.dead[i] = 1;
|
|
326
|
+
s.deadTicks[i] = 0;
|
|
327
|
+
// The death: a last stretch upwards and a fade to nothing.
|
|
328
|
+
must(og.tween.to(world, { entity: me, field: "scale", to: [1.7, 0, 1], duration: 0.4, delay: 0.1, ease: "backIn" }));
|
|
329
|
+
must(og.tween.to(world, { entity: me, component: og.Sprite, field: "opacity", to: [0], duration: 0.4, delay: 0.1, ease: "quadIn" }));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function hurtHero(world: World, g: GameValue, t: TuneValue, dx: number, dy: number, font: never): void {
|
|
334
|
+
g.hp = Math.max(0, g.hp - 1);
|
|
335
|
+
g.hurts += 1;
|
|
336
|
+
g.iframes = t.iframes;
|
|
337
|
+
g.knockX = dx * t.knockback;
|
|
338
|
+
g.knockY = dy * t.knockback;
|
|
339
|
+
if (g.hp === 0) g.downTicks = 0;
|
|
340
|
+
const hero = world.find("hero");
|
|
341
|
+
if (hero !== null) {
|
|
342
|
+
must(og.tween.flash(world, { entity: hero, color: RED, duration: 0.2 }));
|
|
343
|
+
must(og.tween.squash(world, { entity: hero, impact: { x: 1.3, y: 0.75 }, duration: 0.22 }));
|
|
344
|
+
}
|
|
345
|
+
// A hit taken holds the world a light hit's length (og-feel §3), shorter
|
|
346
|
+
// than the hero's own blows, with the flash, the shake and the sound.
|
|
347
|
+
must(og.time.hitStop(world, Math.max(2, t.hitStop - 1)));
|
|
348
|
+
must(og.camera.shake(world, 0.4));
|
|
349
|
+
float(world, font, "-1", { x: g.heroX, y: g.heroY + 1.2, z: 0 }, RED);
|
|
350
|
+
g.sfx |= Sound.Hurt;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Coins and the key hover until the hero's chest touches them, then pop up and fade. */
|
|
354
|
+
export const Pickups = defineSystem(
|
|
355
|
+
"Pickups",
|
|
356
|
+
{ writes: [Game, Pickup, og.Transform, og.Sprite], after: ["Slimes"] },
|
|
357
|
+
(q, time, world) => {
|
|
358
|
+
const game = world.resource(Game);
|
|
359
|
+
if (!game.ok) return;
|
|
360
|
+
const g = game.value;
|
|
361
|
+
if (time.hitStop > 0) return;
|
|
362
|
+
const font = fontOf(g);
|
|
363
|
+
for (const a of q.archetypes()) {
|
|
364
|
+
const pk = a.column(Pickup);
|
|
365
|
+
const p = a.column(og.Transform).position;
|
|
366
|
+
const sprite = a.column(og.Sprite);
|
|
367
|
+
for (let i = 0; i < a.count; i++) {
|
|
368
|
+
if (pk.taken[i]) {
|
|
369
|
+
pk.takenTicks[i] = pk.takenTicks[i]! + 1;
|
|
370
|
+
if (pk.takenTicks[i] === 24) sprite.visible[i] = 0;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const me = a.entity(i);
|
|
374
|
+
const x = p[i * 3]!;
|
|
375
|
+
const base = pk.baseY[i]!;
|
|
376
|
+
p[i * 3 + 1] = base + 0.08 * tri(world.tick + i * 13, 70);
|
|
377
|
+
if (g.hp === 0 || Math.hypot(g.heroX - x, g.heroY + CHEST - (base + 0.4)) > 0.7) continue;
|
|
378
|
+
pk.taken[i] = 1;
|
|
379
|
+
pk.takenTicks[i] = 0;
|
|
380
|
+
const at = { x, y: base + 1.1, z: 0 };
|
|
381
|
+
if (pk.kind[i] === PickupKind.Key) {
|
|
382
|
+
g.hasKey = true;
|
|
383
|
+
g.sfx |= Sound.Key;
|
|
384
|
+
float(world, font, "key!", at, GOLD, true);
|
|
385
|
+
} else {
|
|
386
|
+
g.coins += 1;
|
|
387
|
+
g.sfx |= Sound.Coin;
|
|
388
|
+
float(world, font, "+1", at, GOLD);
|
|
389
|
+
}
|
|
390
|
+
must(og.tween.to(world, { entity: me, field: "position.y", to: [base + 0.9], duration: 0.3, ease: "quadOut" }));
|
|
391
|
+
must(og.tween.to(world, { entity: me, component: og.Sprite, field: "opacity", to: [0], duration: 0.3, delay: 0.08, ease: "quadIn" }));
|
|
392
|
+
must(og.tween.squash(world, { entity: me, impact: { x: 0.6, y: 1.6 }, duration: 0.3 }));
|
|
393
|
+
const hero = world.find("hero");
|
|
394
|
+
if (hero !== null) must(og.tween.squash(world, { entity: hero, impact: { x: 0.85, y: 1.2 }, duration: 0.2 }));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
/** The doorway: locked until the key; with it, the wall cells clear (re-tiling their neighbours and dropping their colliders). */
|
|
401
|
+
export const Door = defineSystem("Door", { writes: [Game], after: ["Pickups"] }, (_q, time, world) => {
|
|
402
|
+
const game = world.resource(Game);
|
|
403
|
+
if (!game.ok) return;
|
|
404
|
+
const g = game.value;
|
|
405
|
+
g.room = g.heroX < g.roomSplit ? 1 : 2;
|
|
406
|
+
if (time.hitStop > 0) return;
|
|
407
|
+
if (g.lockedFor > 0) g.lockedFor -= 1;
|
|
408
|
+
if (g.doorOpen || g.hp === 0) return;
|
|
409
|
+
const near = Math.abs(g.heroX - g.doorX) < 1.6 && Math.abs(g.heroY - g.doorY) < 1.3;
|
|
410
|
+
if (!near) return;
|
|
411
|
+
const font = fontOf(g);
|
|
412
|
+
if (!g.hasKey) {
|
|
413
|
+
if (g.lockedFor === 0) {
|
|
414
|
+
float(world, font, "locked", { x: g.doorX, y: g.doorY + 1.4, z: 0 }, WHITE);
|
|
415
|
+
g.lockedFor = 90;
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
openDoor(world, g);
|
|
420
|
+
float(world, font, "open!", { x: g.doorX, y: g.doorY + 1.4, z: 0 }, MINT, true);
|
|
421
|
+
must(og.camera.shake(world, 0.3));
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
/** Clears the doorway's wall and door cells. Also the `give:door` command's work. */
|
|
425
|
+
export function openDoor(world: World, g: GameValue): void {
|
|
426
|
+
const map = world.find("tomb");
|
|
427
|
+
if (map === null) throw new Error("no entity named tomb");
|
|
428
|
+
for (let y = g.doorCellY; y < g.doorCellY + g.doorCellsH; y++) {
|
|
429
|
+
for (let x = g.doorCellX; x < g.doorCellX + g.doorCellsW; x++) {
|
|
430
|
+
must(og.tilemap.setTile(world, { entity: map, layer: "walls", x, y, tile: 0 }));
|
|
431
|
+
must(og.tilemap.setTile(world, { entity: map, layer: "door", x, y, tile: -1 }));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
g.doorOpen = true;
|
|
435
|
+
g.hasKey = false;
|
|
436
|
+
g.sfx |= Sound.Door;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Plays each requested sound once: a voice already playing is stopped this tick and restarted the next. */
|
|
440
|
+
export const Voices = defineSystem("Voices", { reads: [Sfx], writes: [Game, og.Rng, og.AudioSource], after: ["Door"] }, (q, _time, world) => {
|
|
441
|
+
const game = world.resource(Game);
|
|
442
|
+
if (!game.ok) return;
|
|
443
|
+
const g = game.value;
|
|
444
|
+
if (g.sfx === 0) return;
|
|
445
|
+
const rng = world.resource(og.Rng);
|
|
446
|
+
let left = g.sfx;
|
|
447
|
+
for (const a of q.archetypes()) {
|
|
448
|
+
const bit = a.column(Sfx).bit;
|
|
449
|
+
const src = a.column(og.AudioSource);
|
|
450
|
+
for (let i = 0; i < a.count; i++) {
|
|
451
|
+
const b = bit[i]!;
|
|
452
|
+
if ((g.sfx & b) === 0) continue;
|
|
453
|
+
if (src.playing[i]) {
|
|
454
|
+
src.playing[i] = 0;
|
|
455
|
+
} else {
|
|
456
|
+
src.playing[i] = 1;
|
|
457
|
+
// Footsteps vary a little, so a walk does not machine-gun (og-audio §1).
|
|
458
|
+
if (b === Sound.Step && rng.ok) src.pitch[i] = rng.value.range(0.92, 1.08);
|
|
459
|
+
left &= ~b;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
g.sfx = left;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
/** The HUD: hearts, coins, the key and the room, in screen space; the controls for the first seconds. Also copies the facts into the hero's Report. */
|
|
467
|
+
export const Hud = defineSystem("Hud", { reads: [Game], writes: [Report], after: ["Voices"] }, (q, _time, world) => {
|
|
468
|
+
const game = world.resource(Game);
|
|
469
|
+
if (!game.ok) return;
|
|
470
|
+
const g = game.value;
|
|
471
|
+
const map = world.find("tomb");
|
|
472
|
+
for (const a of q.archetypes()) {
|
|
473
|
+
const r = a.column(Report);
|
|
474
|
+
for (let i = 0; i < a.count; i++) {
|
|
475
|
+
r.room[i] = g.room;
|
|
476
|
+
r.hp[i] = g.hp;
|
|
477
|
+
r.coins[i] = g.coins;
|
|
478
|
+
r.hasKey[i] = g.hasKey ? 1 : 0;
|
|
479
|
+
r.doorOpen[i] = g.doorOpen ? 1 : 0;
|
|
480
|
+
r.enemyHp[i] = g.enemyHp;
|
|
481
|
+
r.hits[i] = g.enemyHits;
|
|
482
|
+
r.swings[i] = g.swings;
|
|
483
|
+
r.steps[i] = g.steps;
|
|
484
|
+
r.hurts[i] = g.hurts;
|
|
485
|
+
r.facing[i] = g.facing;
|
|
486
|
+
r.doorWall[i] = map === null ? -1 : og.tilemap.getTile(world, map, "walls", g.doorCellX, g.doorCellY);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const font = fontOf(g);
|
|
490
|
+
const caps = og.render.capabilities(world);
|
|
491
|
+
const w = caps.width > 0 ? caps.width : 1280;
|
|
492
|
+
const h = caps.height > 0 ? caps.height : 720;
|
|
493
|
+
const size = Math.max(16, Math.round(h * 0.034));
|
|
494
|
+
const pad = Math.round(size * 0.9);
|
|
495
|
+
const line = (text: string, x: number, y: number, color: { r: number; g: number; b: number }, align: "start" | "center" | "end" = "start", s = size) =>
|
|
496
|
+
must(og.render.text(world, { font, text, position: { x, y, z: 0 }, size: s, color, align, screen: true, outline: 0.5, outlineColor: { r: 0.008, g: 0.008, b: 0.011 } }));
|
|
497
|
+
line(`HP ${g.hp}/${g.hpMax}`, pad, pad + size, g.hp > 1 ? WHITE : RED);
|
|
498
|
+
line(`COINS ${g.coins}/${g.coinsTotal}`, pad, pad + size * 2.3, GOLD);
|
|
499
|
+
if (g.hasKey) line("KEY", pad, pad + size * 3.6, GOLD);
|
|
500
|
+
line(`ROOM ${g.room}`, w - pad, pad + size, WHITE, "end");
|
|
501
|
+
if (world.tick < 300 && g.room === 1) line("WASD / arrows move · Space attack · Shift dash", w / 2, h - pad, MINT, "center", Math.round(size * 0.75));
|
|
502
|
+
if (g.hp === 0) line("You fell. Back to the start...", w / 2, h / 2, RED, "center", Math.round(size * 1.2));
|
|
503
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// The slime's behaviour tree (assets/ai/slime.ogt): with the hero out of
|
|
2
|
+
// sight it patrols its beat on the map's grid; the hero stepping within
|
|
3
|
+
// Tuning.slimeSight halts the patrol at once (the selector is re-checked
|
|
4
|
+
// every tick), the slime chases at Tuning.slimeSpeed and bites in reach
|
|
5
|
+
// through the game's `bite` command, which costs a heart and bounces it back.
|
|
6
|
+
import { playtest } from "@origingame/engine/playtest";
|
|
7
|
+
import { component, position, report } from "./lib.ts";
|
|
8
|
+
|
|
9
|
+
interface AiDump {
|
|
10
|
+
behaviour: string;
|
|
11
|
+
node: string;
|
|
12
|
+
path: string;
|
|
13
|
+
sees: boolean;
|
|
14
|
+
status: string;
|
|
15
|
+
perception: { sees: boolean; sinceSeen: number };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default playtest({
|
|
19
|
+
name: "the slime patrols its beat, chases the hero on sight and bites in reach",
|
|
20
|
+
commands: [
|
|
21
|
+
// The hero waits at the spawn, 9 m away: out of sight.
|
|
22
|
+
"0.5:dump_ai:slime:patrol-a.json",
|
|
23
|
+
"0.5:dump_entity:slime:patrol-a-at.json",
|
|
24
|
+
"2.5:dump_ai:slime:patrol-b.json",
|
|
25
|
+
"2.5:dump_entity:slime:patrol-b-at.json",
|
|
26
|
+
"2.5:dump_nav:slime:patrol-nav.json",
|
|
27
|
+
// The hero steps 3 m from it, in the open.
|
|
28
|
+
"3.0:slime:13.5:6.5",
|
|
29
|
+
"3.0:place:10.5:6.5",
|
|
30
|
+
"3.05:dump_ai:slime:seen.json",
|
|
31
|
+
"3.6:dump_entity:slime:chase-at.json",
|
|
32
|
+
"3.6:dump_ai:slime:chase.json",
|
|
33
|
+
"5.5:dump_entity:hero:bitten.json",
|
|
34
|
+
"5.5:dump_entity:slime:after.json",
|
|
35
|
+
"5.6:quit",
|
|
36
|
+
].join(";"),
|
|
37
|
+
check(run, t) {
|
|
38
|
+
t.equal(run.warnings.join("; "), "", "the script ran without warnings");
|
|
39
|
+
const a = run.json<AiDump>("patrol-a.json");
|
|
40
|
+
t.equal(a.behaviour, "slime", "the slime runs assets/ai/slime.ogt");
|
|
41
|
+
t.ok(a.path.includes("patrol beat"), `out of sight it patrols (${a.path})`);
|
|
42
|
+
t.equal(a.sees, false, "it does not see the hero at the spawn");
|
|
43
|
+
const pa = position(run, "patrol-a-at.json");
|
|
44
|
+
const pb = position(run, "patrol-b-at.json");
|
|
45
|
+
t.ok(Math.hypot(pb.x - pa.x, pb.y - pa.y) > 0.5, `the patrol moves it (${pa.x.toFixed(2)},${pa.y.toFixed(2)} to ${pb.x.toFixed(2)},${pb.y.toFixed(2)})`);
|
|
46
|
+
t.ok(pb.x >= 13.3 && pb.x <= 17.7 && pb.y >= 3.3 && pb.y <= 6.7, "and keeps it on its beat");
|
|
47
|
+
const nav = run.json<{ status: string; corners: unknown[] }>("patrol-nav.json");
|
|
48
|
+
t.ok(nav.status === "moving" && nav.corners.length >= 2, `og.nav walks it along a path to the next beat point (${nav.status})`);
|
|
49
|
+
|
|
50
|
+
const seen = run.json<AiDump>("seen.json");
|
|
51
|
+
t.equal(seen.sees, true, "three ticks after the hero steps into sight, the slime sees it");
|
|
52
|
+
t.ok(seen.path.includes("chase"), `and the patrol gave way to the chase (${seen.path})`);
|
|
53
|
+
const c = position(run, "chase-at.json");
|
|
54
|
+
t.ok(c.x < 13.0, `it closes in on the hero (x ${c.x.toFixed(2)})`);
|
|
55
|
+
const speed = (13.5 - c.x) / 0.55;
|
|
56
|
+
t.ok(speed > 1.0 && speed < 1.75, `at about Tuning.slimeSpeed, 1.6 m/s (${speed.toFixed(2)})`);
|
|
57
|
+
t.ok(run.json<AiDump>("chase.json").path.includes("chase"), "still chasing");
|
|
58
|
+
|
|
59
|
+
const bitten = report(run, "bitten.json");
|
|
60
|
+
t.ok(bitten.hurts >= 1, `the bite landed (${bitten.hurts} hurt)`);
|
|
61
|
+
t.ok(bitten.hp < 3, "and cost a heart");
|
|
62
|
+
t.ok(component<{ bites: number }>(run, "after.json", "Slime").bites >= 1, "through the slime's bite command");
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Combat: the slime's bite costs a heart and knocks the hero back; the
|
|
2
|
+
// sword's strike frame (the attack clip's "hit" event) lands 12, 12, then a
|
|
3
|
+
// 26 crit, and each hit holds the world for Tuning.hitStop ticks with the
|
|
4
|
+
// slime flashed white and squashed; the third kills it, and it fades and
|
|
5
|
+
// hides.
|
|
6
|
+
//
|
|
7
|
+
// A press at t = 0.65 s (tick 39) reaches the strike frame six ticks later
|
|
8
|
+
// (tick 45): frame 0 of the attack lasts 0.07 s, then Control reads the
|
|
9
|
+
// event on the next tick.
|
|
10
|
+
import { playtest } from "@origingame/engine/playtest";
|
|
11
|
+
import { component, position, press, report, scale } from "./lib.ts";
|
|
12
|
+
|
|
13
|
+
/** Hero at x, slime 1.1 m to its right, facing it. */
|
|
14
|
+
const setup = (t: number) => `${t}:place:12.3:6.5;${t}:slime:13.4:6.5;${t}:face:right`;
|
|
15
|
+
|
|
16
|
+
export default playtest({
|
|
17
|
+
name: "a bite hurts the hero; three sword hits kill the slime with hit stop, flash and a crit",
|
|
18
|
+
commands: [
|
|
19
|
+
// The bite: the slime starts on the hero.
|
|
20
|
+
"0.1:place:12.3:6.5",
|
|
21
|
+
"0.1:slime:12.8:6.5",
|
|
22
|
+
// Read after the hit stop: the bite lands about tick 11, the stop holds the
|
|
23
|
+
// hero four ticks, and the knock-back (9 m/s, decaying) has moved it past
|
|
24
|
+
// 0.3 m by 0.3 s (12.3 to 11.80; 11.70 by 0.6 s).
|
|
25
|
+
"0.3:dump_entity:hero:bitten.json",
|
|
26
|
+
// First hit, then two ticks into the stop and the tick after it.
|
|
27
|
+
setup(0.6),
|
|
28
|
+
press("Space", 0.65),
|
|
29
|
+
"0.768:dump_entity:slime:stop-a.json",
|
|
30
|
+
"0.801:dump_entity:slime:stop-b.json",
|
|
31
|
+
"0.851:dump_entity:slime:after.json",
|
|
32
|
+
"0.9:dump_entity:hero:hit1.json",
|
|
33
|
+
setup(1.3),
|
|
34
|
+
press("Space", 1.35),
|
|
35
|
+
"1.6:dump_entity:hero:hit2.json",
|
|
36
|
+
setup(2.0),
|
|
37
|
+
press("Space", 2.05),
|
|
38
|
+
"2.3:dump_entity:hero:hit3.json",
|
|
39
|
+
"3.0:dump_entity:slime:dead.json",
|
|
40
|
+
"3.0:dump_entity:hero:end.json",
|
|
41
|
+
"3.1:quit",
|
|
42
|
+
].join(";"),
|
|
43
|
+
check(run, t) {
|
|
44
|
+
t.equal(run.warnings.join("; "), "", "the script ran without warnings");
|
|
45
|
+
const bitten = report(run, "bitten.json");
|
|
46
|
+
t.equal(bitten.hp, 2, "the bite cost one heart");
|
|
47
|
+
t.equal(bitten.hurts, 1, "one hurt");
|
|
48
|
+
t.ok(position(run, "bitten.json").x < 12.0, `the bite knocked the hero back (x ${position(run, "bitten.json").x.toFixed(3)})`);
|
|
49
|
+
|
|
50
|
+
// The hit stop: the slime holds its place, white and squashed, then moves off.
|
|
51
|
+
const a = position(run, "stop-a.json");
|
|
52
|
+
const b = position(run, "stop-b.json");
|
|
53
|
+
t.equal(b.x, a.x, "the slime holds still during the hit stop");
|
|
54
|
+
t.equal(component<{ flash: number }>(run, "stop-a.json", "og.Sprite").flash, 1, "the struck slime is white during the stop");
|
|
55
|
+
t.near(scale(run, "stop-a.json").x, 1.45, 0.001, "the struck slime is squashed during the stop");
|
|
56
|
+
t.equal(component<{ hp: number }>(run, "stop-a.json", "Slime").hp, 38, "the first hit dealt 12");
|
|
57
|
+
t.ok(position(run, "after.json").x > b.x + 0.1, "after the stop the knockback carries the slime away");
|
|
58
|
+
t.ok(scale(run, "after.json").x < 1.45, "after the stop the squash recovers");
|
|
59
|
+
|
|
60
|
+
t.equal(report(run, "hit1.json").enemyHp, 38, "Game.enemyHp after one hit");
|
|
61
|
+
t.equal(report(run, "hit2.json").enemyHp, 26, "a second 12");
|
|
62
|
+
const end = report(run, "end.json");
|
|
63
|
+
t.equal(end.enemyHp, 0, "the crit (26) finished it");
|
|
64
|
+
t.equal(end.hits, 3, "three hits");
|
|
65
|
+
t.equal(end.swings, 3, "three strike frames: one per press");
|
|
66
|
+
t.equal(end.hp, 2, "the hero still has two hearts");
|
|
67
|
+
const slime = component<{ dead: boolean }>(run, "dead.json", "Slime");
|
|
68
|
+
t.equal(slime.dead, true, "the slime is dead");
|
|
69
|
+
t.equal(component<{ visible: boolean }>(run, "dead.json", "og.Sprite").visible, false, "the dead slime is hidden");
|
|
70
|
+
t.near(scale(run, "dead.json").y, 0, 0.001, "the death tween squashed it to nothing");
|
|
71
|
+
},
|
|
72
|
+
});
|