├── src
├── img
│ ├── m0.png
│ ├── m1.png
│ ├── m10.png
│ ├── m11.png
│ ├── m12.png
│ ├── m13.png
│ ├── m14.png
│ ├── m15.png
│ ├── m16.png
│ ├── m17.png
│ ├── m18.png
│ ├── m19.png
│ ├── m1u.png
│ ├── m2.png
│ ├── m20.png
│ ├── m21.png
│ ├── m22.png
│ ├── m23.png
│ ├── m24.png
│ ├── m25.png
│ ├── m26.png
│ ├── m27.png
│ ├── m28.png
│ ├── m29.png
│ ├── m3.png
│ ├── m30.png
│ ├── m31.png
│ ├── m32.png
│ ├── m33.png
│ ├── m34.png
│ ├── m35.png
│ ├── m36.png
│ ├── m37.png
│ ├── m38.png
│ ├── m39.png
│ ├── m4.png
│ ├── m40.png
│ ├── m41.png
│ ├── m42.png
│ ├── m43.png
│ ├── m44.png
│ ├── m45.png
│ ├── m46.png
│ ├── m47.png
│ ├── m48.png
│ ├── m49.png
│ ├── m5.png
│ ├── m50.png
│ ├── m51.png
│ ├── m52.png
│ ├── m53.png
│ ├── m54.png
│ ├── m55.png
│ ├── m56.png
│ ├── m57.png
│ ├── m58.png
│ ├── m59.png
│ ├── m6.png
│ ├── m60.png
│ ├── m61.png
│ ├── m62.png
│ ├── m63.png
│ ├── m64.png
│ ├── m65.png
│ ├── m7.png
│ ├── m8.png
│ ├── m9.png
│ ├── o0.png
│ ├── o1.png
│ ├── o10.png
│ ├── o11.png
│ ├── o12.png
│ ├── o13.png
│ ├── o14.png
│ ├── o15.png
│ ├── o16.png
│ ├── o17.png
│ ├── o18.png
│ ├── o19.png
│ ├── o2.png
│ ├── o20.png
│ ├── o21.png
│ ├── o22.png
│ ├── o23.png
│ ├── o24.png
│ ├── o25.png
│ ├── o26.png
│ ├── o27.png
│ ├── o28.png
│ ├── o29.png
│ ├── o3.png
│ ├── o30.png
│ ├── o31.png
│ ├── o32.png
│ ├── o33.png
│ ├── o34.png
│ ├── o35.png
│ ├── o36.png
│ ├── o37.png
│ ├── o38.png
│ ├── o39.png
│ ├── o4.png
│ ├── o40.png
│ ├── o41.png
│ ├── o42.png
│ ├── o43.png
│ ├── o44.png
│ ├── o45.png
│ ├── o46.png
│ ├── o47.png
│ ├── o48.png
│ ├── o49.png
│ ├── o5.png
│ ├── o50.png
│ ├── o51.png
│ ├── o52.png
│ ├── o53.png
│ ├── o54.png
│ ├── o55.png
│ ├── o56.png
│ ├── o57.png
│ ├── o58.png
│ ├── o59.png
│ ├── o6.png
│ ├── o60.png
│ ├── o61.png
│ ├── o62.png
│ ├── o63.png
│ ├── o64.png
│ ├── o65.png
│ ├── o66.png
│ ├── o67.png
│ ├── o68.png
│ ├── o69.png
│ ├── o7.png
│ ├── o70.png
│ ├── o71.png
│ ├── o72.png
│ ├── o73.png
│ ├── o74.png
│ ├── o75.png
│ ├── o76.png
│ ├── o77.png
│ ├── o78.png
│ ├── o79.png
│ ├── o8.png
│ ├── o80.png
│ ├── o81.png
│ ├── o82.png
│ ├── o83.png
│ ├── o84.png
│ ├── o85.png
│ ├── o86.png
│ ├── o87.png
│ ├── o88.png
│ ├── o89.png
│ ├── o9.png
│ ├── o90.png
│ ├── o91.png
│ ├── o92.png
│ ├── o93.png
│ ├── o94.png
│ ├── o95.png
│ ├── o96.png
│ ├── o97.png
│ ├── o98.png
│ ├── o99.png
│ ├── w0.png
│ ├── w10.png
│ ├── w12.png
│ ├── w14.png
│ ├── w16.png
│ ├── w18.png
│ ├── w2.png
│ ├── w20.png
│ ├── w22.png
│ ├── w24.png
│ ├── w26.png
│ ├── w28.png
│ ├── w30.png
│ ├── w4.png
│ ├── w6.png
│ ├── w8.png
│ ├── m19u.png
│ ├── m34u.png
│ ├── m39v.png
│ ├── m57v.png
│ ├── m58v.png
│ ├── m59v.png
│ ├── m60v.png
│ ├── m61v.png
│ ├── m62v.png
│ ├── m63v.png
│ ├── m64v.png
│ ├── m65v.png
│ ├── o100.png
│ ├── o101.png
│ ├── dos437.ttf
│ ├── favicon.png
│ ├── player.png
│ ├── NotoSansMono-Regular.ttf
│ ├── TopazPlus_a1200_v1.0.ttf
│ └── TopazPlus_a500_v1.0.ttf
├── level.js
├── common
│ ├── frame.js
│ ├── roll.js
│ ├── larn_config.js
│ ├── patch.js
│ ├── live.js
│ └── lambda.js
├── workers
│ ├── patchWorker.js
│ └── compressionWorker.js
├── fullstory.js
├── tv
│ ├── larntv.css
│ ├── index_local.html
│ ├── index.html
│ ├── watchlive.js
│ ├── _TODO.txt
│ └── index.js
├── score
│ ├── index.html
│ ├── score.css
│ └── index_local.html
├── devmode.js
├── gotw.js
├── throne.js
├── larn.html
├── stairs.js
├── larn.css
├── lib
│ ├── lz-string.min.js
│ ├── mousetrap.min.js
│ └── rollbar.js
├── regen.js
├── state.js
├── config.js
├── io.js
├── larn_local.html
├── mcdopes.js
├── spheres.js
├── bill.js
├── altar.js
├── fountain.js
├── help.js
└── storedata.js
├── .gitignore
├── babel.config.json
├── LICENSE
├── README.md
├── package.json
└── ULARN_SPOILERS.md
/src/img/m0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m0.png
--------------------------------------------------------------------------------
/src/img/m1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m1.png
--------------------------------------------------------------------------------
/src/img/m10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m10.png
--------------------------------------------------------------------------------
/src/img/m11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m11.png
--------------------------------------------------------------------------------
/src/img/m12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m12.png
--------------------------------------------------------------------------------
/src/img/m13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m13.png
--------------------------------------------------------------------------------
/src/img/m14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m14.png
--------------------------------------------------------------------------------
/src/img/m15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m15.png
--------------------------------------------------------------------------------
/src/img/m16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m16.png
--------------------------------------------------------------------------------
/src/img/m17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m17.png
--------------------------------------------------------------------------------
/src/img/m18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m18.png
--------------------------------------------------------------------------------
/src/img/m19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m19.png
--------------------------------------------------------------------------------
/src/img/m1u.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m1u.png
--------------------------------------------------------------------------------
/src/img/m2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m2.png
--------------------------------------------------------------------------------
/src/img/m20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m20.png
--------------------------------------------------------------------------------
/src/img/m21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m21.png
--------------------------------------------------------------------------------
/src/img/m22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m22.png
--------------------------------------------------------------------------------
/src/img/m23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m23.png
--------------------------------------------------------------------------------
/src/img/m24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m24.png
--------------------------------------------------------------------------------
/src/img/m25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m25.png
--------------------------------------------------------------------------------
/src/img/m26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m26.png
--------------------------------------------------------------------------------
/src/img/m27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m27.png
--------------------------------------------------------------------------------
/src/img/m28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m28.png
--------------------------------------------------------------------------------
/src/img/m29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m29.png
--------------------------------------------------------------------------------
/src/img/m3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m3.png
--------------------------------------------------------------------------------
/src/img/m30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m30.png
--------------------------------------------------------------------------------
/src/img/m31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m31.png
--------------------------------------------------------------------------------
/src/img/m32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m32.png
--------------------------------------------------------------------------------
/src/img/m33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m33.png
--------------------------------------------------------------------------------
/src/img/m34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m34.png
--------------------------------------------------------------------------------
/src/img/m35.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m35.png
--------------------------------------------------------------------------------
/src/img/m36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m36.png
--------------------------------------------------------------------------------
/src/img/m37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m37.png
--------------------------------------------------------------------------------
/src/img/m38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m38.png
--------------------------------------------------------------------------------
/src/img/m39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m39.png
--------------------------------------------------------------------------------
/src/img/m4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m4.png
--------------------------------------------------------------------------------
/src/img/m40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m40.png
--------------------------------------------------------------------------------
/src/img/m41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m41.png
--------------------------------------------------------------------------------
/src/img/m42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m42.png
--------------------------------------------------------------------------------
/src/img/m43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m43.png
--------------------------------------------------------------------------------
/src/img/m44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m44.png
--------------------------------------------------------------------------------
/src/img/m45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m45.png
--------------------------------------------------------------------------------
/src/img/m46.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m46.png
--------------------------------------------------------------------------------
/src/img/m47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m47.png
--------------------------------------------------------------------------------
/src/img/m48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m48.png
--------------------------------------------------------------------------------
/src/img/m49.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m49.png
--------------------------------------------------------------------------------
/src/img/m5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m5.png
--------------------------------------------------------------------------------
/src/img/m50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m50.png
--------------------------------------------------------------------------------
/src/img/m51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m51.png
--------------------------------------------------------------------------------
/src/img/m52.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m52.png
--------------------------------------------------------------------------------
/src/img/m53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m53.png
--------------------------------------------------------------------------------
/src/img/m54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m54.png
--------------------------------------------------------------------------------
/src/img/m55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m55.png
--------------------------------------------------------------------------------
/src/img/m56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m56.png
--------------------------------------------------------------------------------
/src/img/m57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m57.png
--------------------------------------------------------------------------------
/src/img/m58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m58.png
--------------------------------------------------------------------------------
/src/img/m59.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m59.png
--------------------------------------------------------------------------------
/src/img/m6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m6.png
--------------------------------------------------------------------------------
/src/img/m60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m60.png
--------------------------------------------------------------------------------
/src/img/m61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m61.png
--------------------------------------------------------------------------------
/src/img/m62.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m62.png
--------------------------------------------------------------------------------
/src/img/m63.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m63.png
--------------------------------------------------------------------------------
/src/img/m64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m64.png
--------------------------------------------------------------------------------
/src/img/m65.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m65.png
--------------------------------------------------------------------------------
/src/img/m7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m7.png
--------------------------------------------------------------------------------
/src/img/m8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m8.png
--------------------------------------------------------------------------------
/src/img/m9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m9.png
--------------------------------------------------------------------------------
/src/img/o0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o0.png
--------------------------------------------------------------------------------
/src/img/o1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o1.png
--------------------------------------------------------------------------------
/src/img/o10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o10.png
--------------------------------------------------------------------------------
/src/img/o11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o11.png
--------------------------------------------------------------------------------
/src/img/o12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o12.png
--------------------------------------------------------------------------------
/src/img/o13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o13.png
--------------------------------------------------------------------------------
/src/img/o14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o14.png
--------------------------------------------------------------------------------
/src/img/o15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o15.png
--------------------------------------------------------------------------------
/src/img/o16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o16.png
--------------------------------------------------------------------------------
/src/img/o17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o17.png
--------------------------------------------------------------------------------
/src/img/o18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o18.png
--------------------------------------------------------------------------------
/src/img/o19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o19.png
--------------------------------------------------------------------------------
/src/img/o2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o2.png
--------------------------------------------------------------------------------
/src/img/o20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o20.png
--------------------------------------------------------------------------------
/src/img/o21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o21.png
--------------------------------------------------------------------------------
/src/img/o22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o22.png
--------------------------------------------------------------------------------
/src/img/o23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o23.png
--------------------------------------------------------------------------------
/src/img/o24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o24.png
--------------------------------------------------------------------------------
/src/img/o25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o25.png
--------------------------------------------------------------------------------
/src/img/o26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o26.png
--------------------------------------------------------------------------------
/src/img/o27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o27.png
--------------------------------------------------------------------------------
/src/img/o28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o28.png
--------------------------------------------------------------------------------
/src/img/o29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o29.png
--------------------------------------------------------------------------------
/src/img/o3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o3.png
--------------------------------------------------------------------------------
/src/img/o30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o30.png
--------------------------------------------------------------------------------
/src/img/o31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o31.png
--------------------------------------------------------------------------------
/src/img/o32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o32.png
--------------------------------------------------------------------------------
/src/img/o33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o33.png
--------------------------------------------------------------------------------
/src/img/o34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o34.png
--------------------------------------------------------------------------------
/src/img/o35.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o35.png
--------------------------------------------------------------------------------
/src/img/o36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o36.png
--------------------------------------------------------------------------------
/src/img/o37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o37.png
--------------------------------------------------------------------------------
/src/img/o38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o38.png
--------------------------------------------------------------------------------
/src/img/o39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o39.png
--------------------------------------------------------------------------------
/src/img/o4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o4.png
--------------------------------------------------------------------------------
/src/img/o40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o40.png
--------------------------------------------------------------------------------
/src/img/o41.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o41.png
--------------------------------------------------------------------------------
/src/img/o42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o42.png
--------------------------------------------------------------------------------
/src/img/o43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o43.png
--------------------------------------------------------------------------------
/src/img/o44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o44.png
--------------------------------------------------------------------------------
/src/img/o45.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o45.png
--------------------------------------------------------------------------------
/src/img/o46.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o46.png
--------------------------------------------------------------------------------
/src/img/o47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o47.png
--------------------------------------------------------------------------------
/src/img/o48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o48.png
--------------------------------------------------------------------------------
/src/img/o49.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o49.png
--------------------------------------------------------------------------------
/src/img/o5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o5.png
--------------------------------------------------------------------------------
/src/img/o50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o50.png
--------------------------------------------------------------------------------
/src/img/o51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o51.png
--------------------------------------------------------------------------------
/src/img/o52.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o52.png
--------------------------------------------------------------------------------
/src/img/o53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o53.png
--------------------------------------------------------------------------------
/src/img/o54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o54.png
--------------------------------------------------------------------------------
/src/img/o55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o55.png
--------------------------------------------------------------------------------
/src/img/o56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o56.png
--------------------------------------------------------------------------------
/src/img/o57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o57.png
--------------------------------------------------------------------------------
/src/img/o58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o58.png
--------------------------------------------------------------------------------
/src/img/o59.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o59.png
--------------------------------------------------------------------------------
/src/img/o6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o6.png
--------------------------------------------------------------------------------
/src/img/o60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o60.png
--------------------------------------------------------------------------------
/src/img/o61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o61.png
--------------------------------------------------------------------------------
/src/img/o62.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o62.png
--------------------------------------------------------------------------------
/src/img/o63.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o63.png
--------------------------------------------------------------------------------
/src/img/o64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o64.png
--------------------------------------------------------------------------------
/src/img/o65.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o65.png
--------------------------------------------------------------------------------
/src/img/o66.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o66.png
--------------------------------------------------------------------------------
/src/img/o67.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o67.png
--------------------------------------------------------------------------------
/src/img/o68.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o68.png
--------------------------------------------------------------------------------
/src/img/o69.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o69.png
--------------------------------------------------------------------------------
/src/img/o7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o7.png
--------------------------------------------------------------------------------
/src/img/o70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o70.png
--------------------------------------------------------------------------------
/src/img/o71.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o71.png
--------------------------------------------------------------------------------
/src/img/o72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o72.png
--------------------------------------------------------------------------------
/src/img/o73.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o73.png
--------------------------------------------------------------------------------
/src/img/o74.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o74.png
--------------------------------------------------------------------------------
/src/img/o75.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o75.png
--------------------------------------------------------------------------------
/src/img/o76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o76.png
--------------------------------------------------------------------------------
/src/img/o77.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o77.png
--------------------------------------------------------------------------------
/src/img/o78.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o78.png
--------------------------------------------------------------------------------
/src/img/o79.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o79.png
--------------------------------------------------------------------------------
/src/img/o8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o8.png
--------------------------------------------------------------------------------
/src/img/o80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o80.png
--------------------------------------------------------------------------------
/src/img/o81.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o81.png
--------------------------------------------------------------------------------
/src/img/o82.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o82.png
--------------------------------------------------------------------------------
/src/img/o83.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o83.png
--------------------------------------------------------------------------------
/src/img/o84.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o84.png
--------------------------------------------------------------------------------
/src/img/o85.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o85.png
--------------------------------------------------------------------------------
/src/img/o86.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o86.png
--------------------------------------------------------------------------------
/src/img/o87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o87.png
--------------------------------------------------------------------------------
/src/img/o88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o88.png
--------------------------------------------------------------------------------
/src/img/o89.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o89.png
--------------------------------------------------------------------------------
/src/img/o9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o9.png
--------------------------------------------------------------------------------
/src/img/o90.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o90.png
--------------------------------------------------------------------------------
/src/img/o91.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o91.png
--------------------------------------------------------------------------------
/src/img/o92.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o92.png
--------------------------------------------------------------------------------
/src/img/o93.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o93.png
--------------------------------------------------------------------------------
/src/img/o94.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o94.png
--------------------------------------------------------------------------------
/src/img/o95.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o95.png
--------------------------------------------------------------------------------
/src/img/o96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o96.png
--------------------------------------------------------------------------------
/src/img/o97.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o97.png
--------------------------------------------------------------------------------
/src/img/o98.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o98.png
--------------------------------------------------------------------------------
/src/img/o99.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o99.png
--------------------------------------------------------------------------------
/src/img/w0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w0.png
--------------------------------------------------------------------------------
/src/img/w10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w10.png
--------------------------------------------------------------------------------
/src/img/w12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w12.png
--------------------------------------------------------------------------------
/src/img/w14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w14.png
--------------------------------------------------------------------------------
/src/img/w16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w16.png
--------------------------------------------------------------------------------
/src/img/w18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w18.png
--------------------------------------------------------------------------------
/src/img/w2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w2.png
--------------------------------------------------------------------------------
/src/img/w20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w20.png
--------------------------------------------------------------------------------
/src/img/w22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w22.png
--------------------------------------------------------------------------------
/src/img/w24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w24.png
--------------------------------------------------------------------------------
/src/img/w26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w26.png
--------------------------------------------------------------------------------
/src/img/w28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w28.png
--------------------------------------------------------------------------------
/src/img/w30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w30.png
--------------------------------------------------------------------------------
/src/img/w4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w4.png
--------------------------------------------------------------------------------
/src/img/w6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w6.png
--------------------------------------------------------------------------------
/src/img/w8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/w8.png
--------------------------------------------------------------------------------
/src/img/m19u.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m19u.png
--------------------------------------------------------------------------------
/src/img/m34u.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m34u.png
--------------------------------------------------------------------------------
/src/img/m39v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m39v.png
--------------------------------------------------------------------------------
/src/img/m57v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m57v.png
--------------------------------------------------------------------------------
/src/img/m58v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m58v.png
--------------------------------------------------------------------------------
/src/img/m59v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m59v.png
--------------------------------------------------------------------------------
/src/img/m60v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m60v.png
--------------------------------------------------------------------------------
/src/img/m61v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m61v.png
--------------------------------------------------------------------------------
/src/img/m62v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m62v.png
--------------------------------------------------------------------------------
/src/img/m63v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m63v.png
--------------------------------------------------------------------------------
/src/img/m64v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m64v.png
--------------------------------------------------------------------------------
/src/img/m65v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/m65v.png
--------------------------------------------------------------------------------
/src/img/o100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o100.png
--------------------------------------------------------------------------------
/src/img/o101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/o101.png
--------------------------------------------------------------------------------
/src/img/dos437.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/dos437.ttf
--------------------------------------------------------------------------------
/src/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/favicon.png
--------------------------------------------------------------------------------
/src/img/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/player.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .dropbox
2 | .DS_Store
3 | .vscode
4 | .eslintrc.json
5 | Icon
6 | node_modules
7 | secrets.sh
--------------------------------------------------------------------------------
/src/img/NotoSansMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/NotoSansMono-Regular.ttf
--------------------------------------------------------------------------------
/src/img/TopazPlus_a1200_v1.0.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/TopazPlus_a1200_v1.0.ttf
--------------------------------------------------------------------------------
/src/img/TopazPlus_a500_v1.0.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/primeau/Larn/HEAD/src/img/TopazPlus_a500_v1.0.ttf
--------------------------------------------------------------------------------
/src/level.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | var Level = {
5 | items: [],
6 | monsters: [],
7 | know: [],
8 | }; // Level
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"],
3 | "targets": {
4 | "edge": "15",
5 | "firefox": "53",
6 | "chrome": "55",
7 | "safari": "11.1"
8 | },
9 | "ignore": ["*/lib/*", "*/aws/*", "*/node_modules/*", "*/cloudflare/broadcast/*" ]
10 | }
--------------------------------------------------------------------------------
/src/common/frame.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // a single film frame
4 | // this is the base unit of a recording
5 | class Frame {
6 | constructor() {
7 | this.id = 0;
8 | this.ts = 0;
9 | this.divs = {};
10 | this.compressed = false;
11 | this.deflated = false;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/common/roll.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // a collection of patches
4 | class Roll {
5 | constructor(patches) {
6 | this.patches = patches;
7 | }
8 |
9 | addPatch(patch) {
10 | this.patches.push(patch);
11 | }
12 |
13 | isFull() {
14 | return this.patches.length >= MAX_ROLL_LENGTH;
15 | }
16 | }
17 |
18 |
19 |
20 | function createRoll(roll) {
21 | return new Roll(roll.patches);
22 | }
23 |
24 |
25 |
26 | function decompressRoll(compressed) {
27 | if (!compressed) return null;
28 | // TODO: there's gotta be a way to use decompressFromUTF16
29 | let decompressed = LZString.decompressFromEncodedURIComponent(compressed);
30 | return createRoll(JSON.parse(decompressed));
31 | }
--------------------------------------------------------------------------------
/src/workers/patchWorker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | importScripts('../lib/diff_match_patch.js');
4 | try { importScripts('../tv/common/patch.js'); } catch (error) { } // prod location
5 | try { importScripts('../common/patch.js'); } catch (error) { } // local testing
6 |
7 | // WORKER STEP 2 - build patch
8 | onmessage = function (event) {
9 | let prevFrame = event.data[0];
10 | let newFrame = event.data[1];
11 | let source = event.data[2] || `none`;
12 | let newPatch = buildPatch(prevFrame, newFrame);
13 | // console.log(`${source}:${newFrame.id} worker.buildpatch`);
14 |
15 | // memory management
16 | event.data[0] = null;
17 | event.data[1] = null;
18 | event.data[2] = null;
19 | event.data.length = 0;
20 |
21 | postMessage([newPatch, newFrame]);
22 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2015-Present Jason Primeau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/workers/compressionWorker.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | importScripts('../lib/lz-string.min.js');
4 |
5 | // WORKER STEP 2 - do compression
6 | onmessage = function (event) {
7 | let id = event.data[0];
8 | let uncompressed = event.data[1];
9 | let algo = event.data[2];
10 | let source = event.data[3] || `none`;
11 | let metadata = event.data[4] || null;
12 | // console.log(`${source}:${id} worker.compress: start size`, uncompressed.length);
13 | let compressed = ``;
14 | if (algo === `UTF16`) {
15 | compressed = LZString.compressToUTF16(uncompressed);
16 | }
17 | else if (algo === `ENCODED_URI`) {
18 | compressed = LZString.compressToEncodedURIComponent(uncompressed);
19 | }
20 | else {
21 | console.error(`worker.compress: no compression algorithm selected`);
22 | }
23 | // console.log(`${source}:${id} worker.compress: end size`, compressed.length);
24 |
25 | // memory management
26 | event.data[0] = null;
27 | event.data[1] = null;
28 | event.data[2] = null;
29 | event.data[3] = null;
30 | event.data[4] = null;
31 | event.data.length = 0;
32 |
33 | postMessage([id, compressed, metadata]);
34 | };
--------------------------------------------------------------------------------
/src/common/larn_config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const VERSION = '12.5.3';
4 | const BUILD = '552';
5 |
6 | const ENABLE_DEVMODE = false; // this must be set to false for production releases
7 |
8 | const CF_SCORE_ENDPOINT = 'score';
9 | const CF_HIGHSCORE_ENDPOINT = 'highscore';
10 | const CF_ACTIVEGAME_ENDPOINT = 'active';
11 | const CF_COMPLETEDGAME_ENDPOINT = 'completed';
12 | const CF_GOTW_ENDPOINT = 'gotw';
13 | const CF_HIGHSCORES_TABLE = 'highscores';
14 |
15 | // recorded games
16 | let ENABLE_RECORDING = true; // IDEA: RELEASE A VERSION WITH THIS SET TO FALSE
17 | const MIN_FRAMES_TO_LIST = 1000;
18 | const MAX_ROLL_LENGTH = 200;
19 | const AWS_SCORE_FUNCTION = 'score';
20 | const AWS_RECORD_FUNCTION = `movie_test`;
21 | function initLambdaCredentials() {
22 | AWS.config.accessKeyId = 'AWS_CONFIG_ACCESSKEYID';
23 | AWS.config.secretAccessKey = 'AWS_CONFIG_SECRETACCESSKEY';
24 | }
25 |
26 | // live games
27 | const CF_LOCAL = false;
28 | let ENABLE_RECORDING_REALTIME = true; // IDEA: RELEASE A VERSION WITH THIS SET TO FALSE
29 | const CF_BROADCAST_HOST = CF_LOCAL ? `localhost:8787` : `broadcast.larn.workers.dev`;
30 | const CF_BROADCAST_PROTOCOL = CF_LOCAL ? `http://` : `https://`;
31 | const LIVE_LIST_REFRESH = CF_LOCAL ? 1 : 10; // seconds
32 | const LIVE_METADATA_WAIT = CF_LOCAL ? 1 : 10; // seconds
33 | const LIVE_METADATA_MOVES = CF_LOCAL ? 1 : 11; // moves
34 |
--------------------------------------------------------------------------------
/src/fullstory.js:
--------------------------------------------------------------------------------
1 | function fsfunc() {
2 | window['_fs_debug'] = false;
3 | window['_fs_host'] = 'fullstory.com';
4 | window['_fs_script'] = 'edge.fullstory.com/s/fs.js';
5 | window['_fs_org'] = 'FULLSTORY_ORG';
6 | window['_fs_namespace'] = 'FS';
7 | (function(m,n,e,t,l,o,g,y){
8 | if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;}
9 | g=m[e]=function(a,b,s){g.q?g.q.push([a,b,s]):g._api(a,b,s);};g.q=[];
10 | o=n.createElement(t);o.async=1;o.crossOrigin='anonymous';o.src='https://'+_fs_script;
11 | y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y);
12 | g.identify=function(i,v,s){g(l,{uid:i},s);if(v)g(l,v,s)};g.setUserVars=function(v,s){g(l,v,s)};g.event=function(i,v,s){g('event',{n:i,p:v},s)};
13 | g.anonymize=function(){g.identify(!!0)};
14 | g.shutdown=function(){g("rec",!1)};g.restart=function(){g("rec",!0)};
15 | g.log = function(a,b){g("log",[a,b])};
16 | g.consent=function(a){g("consent",!arguments.length||a)};
17 | g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)};
18 | g.clearUserCookie=function(){};
19 | g.setVars=function(n, p){g('setVars',[n,p]);};
20 | g._w={};y='XMLHttpRequest';g._w[y]=m[y];y='fetch';g._w[y]=m[y];
21 | if(m[y])m[y]=function(){return g._w[y].apply(this,arguments)};
22 | g._v="1.3.0";
23 | })(window,document,window['_fs_namespace'],'script','user');}
--------------------------------------------------------------------------------
/src/tv/larntv.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: modern;
3 | src: url('../img/NotoSansMono-Regular.ttf');
4 | }
5 |
6 | @font-face {
7 | font-family: dos437;
8 | src: url('../img/dos437.ttf');
9 | }
10 |
11 | @font-face {
12 | font-family: amiga500;
13 | src: url('../img/TopazPlus_a500_v1.0.ttf');
14 | }
15 |
16 | @font-face {
17 | font-family: amiga1200;
18 | src: url('../img/TopazPlus_a1200_v1.0.ttf');
19 | }
20 |
21 | a:link {
22 | color: #bbbbbb;
23 | text-decoration: none;
24 | }
25 |
26 | a:visited {
27 | color: #FF00FF;
28 | text-decoration: none;
29 | }
30 |
31 | a:hover {
32 | color: #FF77FF;
33 | text-decoration: none;
34 | }
35 |
36 | a:active {
37 | color: #EE0000;
38 | text-decoration: none;
39 | }
40 |
41 | b,
42 | strong {
43 | color: lightgray;
44 | }
45 |
46 | body {
47 | font-family: 'dos', 'Courier New', Courier, monospace;
48 | font-size: 22px;
49 | color: #ABABAB;
50 | margin: 25px;
51 | background-color: #000000;
52 | }
53 |
54 | /* body {
55 | font-family: 'dos', 'Courier New', Courier, monospace;
56 | margin: 0px;
57 | background-color: #000000;
58 | } */
59 |
60 |
61 | div {
62 | background-position: center center;
63 | background-size: 100% 100%;
64 | background-repeat: no-repeat;
65 | white-space: pre;
66 | }
67 |
68 | div.image {
69 | display: inline-block;
70 | background-image: url('../img/o94.png');
71 | }
72 |
73 | mark {
74 | background-color: lightgrey;
75 | color: black;
76 | }
77 |
78 | table,
79 | th,
80 | td {
81 | vertical-align: top;
82 | padding-top: 0px;
83 | }
--------------------------------------------------------------------------------
/src/score/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Larn Scores
6 |
7 |
8 |
9 |
10 | Larn High Scores
11 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/score/score.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | text-align: center;
3 | font-size: 110%;
4 | }
5 |
6 | th.pr2ch,
7 | td.pr2ch {
8 | padding-right: 2ch;
9 | }
10 | body {
11 | margin: 2em;
12 | color: #d3d3d3;
13 | font-size: 18px;
14 | }
15 | h1 {
16 | text-align: center;
17 | font-size: 110%;
18 | }
19 | .radio-group label,
20 | .radio-group input[type='radio'] {
21 | font-size: inherit;
22 | }
23 | .radio-group label:hover {
24 | background: #003366;
25 | color: #fff;
26 | border-radius: 4px;
27 | }
28 | .score-list {
29 | list-style: none;
30 | padding: 0;
31 | }
32 | .score-item {
33 | padding: 0;
34 | margin-bottom: 0;
35 | border-bottom: none;
36 | cursor: pointer;
37 | }
38 | .score-item:hover {
39 | background: #003366;
40 | }
41 | .score-item:last-child {
42 | border-bottom: none;
43 | }
44 | .overlay {
45 | position: fixed;
46 | top: 0;
47 | left: 0;
48 | right: 0;
49 | bottom: 0;
50 | background: transparent;
51 | display: flex;
52 | align-items: flex-start;
53 | justify-content: flex-end;
54 | z-index: 1000;
55 | /* pointer-events: none; */
56 | }
57 | .overlay-content {
58 | position: relative;
59 | background: #222;
60 | padding: 2em 3em;
61 | border-radius: 8px;
62 | box-shadow: 0 2px 16px #000a;
63 | text-align: left;
64 | height: 100vh;
65 | max-width: 75vw;
66 | max-height: 100vh;
67 | overflow-y: auto;
68 | pointer-events: auto;
69 | font-size: 17px;
70 | }
71 | .overlay-close {
72 | margin-top: 1em;
73 | color: #ccc;
74 | font-size: 1.1em;
75 | position: absolute;
76 | top: 10px;
77 | right: 16px;
78 | background: none;
79 | border: none;
80 | }
81 | .overlay-close:hover {
82 | cursor: pointer;
83 | }
84 | #overlayCloseX {
85 | z-index: 1001;
86 | }
87 | #yearSelect,
88 | #gotwSelect {
89 | background: #000;
90 | color: #d3d3d3;
91 | border: 1px solid #444;
92 | width: 140px;
93 | min-width: 140px;
94 | max-width: 140px;
95 | font-family: inherit;
96 | font-size: inherit;
97 | }
98 |
--------------------------------------------------------------------------------
/src/devmode.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function enableDevmode() {
4 |
5 | try {
6 | Rollbar.configure({ enabled: false });
7 | } catch (error) {
8 | //
9 | }
10 |
11 | console.error("DEVMODE IN USE");
12 |
13 | enableDebug();
14 | eventToggleDebugWTW();
15 | eventToggleDebugStairs();
16 | eventToggleDebugOutput();
17 | eventToggleDebugKnowAll();
18 | eventToggleDebugStats();
19 | eventToggleDebugImmortal();
20 | eventToggleDebugAwareness();
21 | // eventToggleDebugNoMonsters();
22 | // player.updateStealth(100000);
23 | // player.updateCancellation(100000);
24 |
25 | wizardmode(`pvnert(x)`);
26 |
27 | // var startShield = createObject(OSHIELD);
28 | // take(startShield);
29 | // player.SHIELD = startShield;
30 | // var startArmor = createObject(OSSPLATE, 25);
31 | // take(startArmor);
32 | // player.WEAR = startArmor;
33 | // var startWeapon = createObject(OLANCE);
34 | // take(startWeapon);
35 | // player.WIELD = startWeapon;
36 |
37 | // player.raiseexperience(5000000);
38 |
39 | // take(createObject(OPOTION, 2));
40 | // take(createObject(OPOTION, 9));
41 | // take(createObject(OPOTION, 11));
42 | // take(createObject(OPOTION, 10));
43 | // take(createObject(OPOTION, 21));
44 | // take(createObject(OPOTION, 23));
45 |
46 | // take(createObject(OSPHTALISMAN));
47 | // take(createObject(OHANDofFEAR));
48 | // take(createObject(OLARNEYE));
49 | // take(createObject(ONOTHEFT));
50 | // take(createObject(OBRASSLAMP));
51 | // gtime = 30001;
52 | player.GOLD = 250000;
53 |
54 | // newcavelevel(level + 1);
55 | // setItem(player.x, player.y, createObject(OTRAPDOOR));
56 |
57 | // createmonster(NYMPH);
58 | // for (let index = 1; index < monsterlist.length; index++) {
59 | // createmonster(index, 1+index, 2);
60 | // }
61 | // revealLevel();
62 |
63 | // winner:
64 | // take(createObject(OPOTION, 21));
65 | // gtime = 50001; // out of time
66 |
67 | }
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/score/index_local.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Larn Scores
6 |
7 |
8 |
9 |
10 | Larn High Scores
11 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/tv/index_local.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Larn TV
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | |
24 |
25 |
26 |
27 |
28 | |
29 |
30 |
31 |
32 | |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/common/patch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const dmp = new diff_match_patch();
4 |
5 |
6 |
7 | // the difference between framenum and framenum + 1
8 | class Patch {
9 | constructor() {
10 | this.id;
11 | this.ts;
12 | this.deflated = false;
13 | this.divs = {};
14 | }
15 | }
16 |
17 |
18 |
19 | // construct a frame from a patch
20 | function buildFrame(patch, frame) {
21 | let newFrame = new Frame();
22 |
23 | // frame number
24 | newFrame.id = patch.id;
25 |
26 | // timestamp
27 | newFrame.ts = patch.ts;
28 |
29 | // amiga frames are deflated by removing css
30 | newFrame.deflated = patch.deflated;
31 |
32 | // shouldn't happen but just in case
33 | if (frame.compressed) {
34 | decompressFrame(frame);
35 | }
36 |
37 | let divs = Object.entries(patch.divs);
38 | // console.error(`buildFrame(): divs: ${patch.id} ${divs}`);
39 |
40 | for (const [key, value] of divs) {
41 | // console.log(`buildFrame(): k: ${key}`);
42 | // console.log(`buildFrame(): v: ${value}`);
43 | let patches = dmp.patch_fromText(value);
44 | // console.log(`buildFrame(): patch: ${dmp.patch_toText(patches)}`);
45 | let results = dmp.patch_apply(patches, frame.divs[key]);
46 | newFrame.divs[key] = results[0];
47 | // console.log(`buildFrame(): frame:\`${newFrame.divs[key]}\``);
48 | }
49 |
50 | // console.log(`patch`, patch);
51 | // console.log(`newframe`, newFrame);
52 |
53 | return newFrame;
54 | }
55 |
56 |
57 |
58 | function buildPatch(prevFrame, currentFrame) {
59 | let newPatch = new Patch();
60 |
61 | newPatch.id = currentFrame.id;
62 | newPatch.ts = currentFrame.ts;
63 | newPatch.deflated = currentFrame.deflated;
64 |
65 | let divs = Object.keys(currentFrame.divs);
66 |
67 | divs.forEach(div => {
68 | // console.log(`buildPatch(): div: ${JSON.stringify(div)}`);
69 | let diff = dmp.diff_main(prevFrame.divs[div] || ``, currentFrame.divs[div]);
70 | if (diff.length > 2) {
71 | dmp.diff_cleanupEfficiency(diff);
72 | }
73 | let patchList = dmp.patch_make(prevFrame.divs[div] || ``, diff);
74 | newPatch.divs[div] = dmp.patch_toText(patchList);
75 | });
76 |
77 | // console.log(`buildPatch(): patch: ${JSON.stringify(newPatch)}`);
78 |
79 | // console.log(`prevframe`, prevFrame);
80 | // console.log(`newpatch`, newPatch);
81 |
82 | return newPatch;
83 | }
--------------------------------------------------------------------------------
/src/tv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Larn TV
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | |
24 |
25 |
26 |
27 |
28 | |
29 |
30 |
31 |
32 | |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/tv/watchlive.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | let liveMetadata;
4 | let liveFrameCache;
5 | let GET_LIVE_INTERVAL = null;
6 |
7 |
8 | function watchLive() {
9 | initLivePlayer();
10 |
11 | let liveFrame = new Frame();
12 | liveFrame.divs = {
13 | LARN: `Waiting for our Hero's next move. This could take a minute...`,
14 | STATS: ``
15 | };
16 |
17 | setStyle();
18 | bltFrame(liveFrame);
19 | }
20 |
21 |
22 |
23 | function initLivePlayer() {
24 | let body = document.getElementById('TV_FOOTER');
25 | if (!body) return;
26 | while (body.firstChild) {
27 | body.firstChild.remove();
28 | }
29 | }
30 |
31 |
32 |
33 | // cloudlare callback
34 | function setLiveStyleCallback(styleIn) {
35 | setStyle(JSON.parse(styleIn));
36 | }
37 |
38 |
39 |
40 | function bltLiveFrame(data) {
41 | if (!data) return;
42 |
43 | try {
44 | if (data.message) {
45 | let decompressedFrame = LZString.decompressFromUTF16(data.message);
46 | let newFrame = JSON.parse(decompressedFrame);
47 |
48 | setStyle(liveMetadata);
49 | bltFrame(newFrame);
50 |
51 | liveFrameCache = data;
52 | liveMetadata = newFrame.metadata; // TODO: WHY IS THIS DONE *AFTER*?
53 | let exp = liveMetadata.explored.replaceAll(/\s/g, ``);
54 | document.title = `LarnTV: ${liveMetadata.who} ${exp}`;
55 |
56 | }
57 | }
58 | catch (error) {
59 | console.error(`bltLiveFrame: `, error);
60 | }
61 | }
62 |
63 |
64 |
65 | function downloadliveGamesList(downloadCompleteCallback) {
66 | if (!navigator.onLine) {
67 | console.error(`downloadliveGamesList(): offline`);
68 | return;
69 | }
70 | getLive(downloadCompleteCallback);
71 | GET_LIVE_INTERVAL = setInterval(getLive, LIVE_LIST_REFRESH * 1000, downloadCompleteCallback);
72 | }
73 |
74 |
75 |
76 | async function getLive(downloadCompleteCallback) {
77 | if (!navigator.onLine) {
78 | console.error(`getLive(): offline`);
79 | return;
80 | }
81 | try {
82 | const response = await fetch(`${CF_BROADCAST_PROTOCOL}${CF_BROADCAST_HOST}/api/${CF_ACTIVEGAME_ENDPOINT}`);
83 | if (response.ok) {
84 | const liveGames = await response.json();
85 | downloadCompleteCallback(liveGames);
86 | } else if (response.status == 403) { // for blocking scrapers
87 | console.error(`getLive(): 403 forbidden, stopping requests`);
88 | clearInterval(GET_LIVE_INTERVAL);
89 | }
90 | } catch (error) {
91 | console.error(`getLive(): no data`);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/gotw.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | async function doGOTWStuff() {
4 | console.log(`GOTW enabled`);
5 |
6 | // let date = new Date();
7 | // for (let i = 0; i < 12; i++) {
8 | // date.setDate(date.getDate() + 7);
9 | // await uploadGOTW(date);
10 | // }
11 |
12 | return await downloadGOTW();
13 | }
14 |
15 | //
16 | //
17 | //
18 | // UPLOAD_GOTW
19 | //
20 | //
21 | //
22 | async function uploadGOTW(dateIn) {
23 | console.log(`uploadGOTW()`);
24 |
25 | // clear out old game state
26 | LEVELS = new Array(MAXLEVEL + MAXVLEVEL);
27 | EXPLORED_LEVELS = new Array(MAXLEVEL + MAXVLEVEL).fill(false); // cache needed for GOTW games
28 | USED_MAZES = [];
29 |
30 | // generate levels
31 | for (let level = 0; level < LEVELS.length; level++) {
32 | newcavelevel(level);
33 | }
34 | // clear out unneeded data
35 | gameID = 'GOTW';
36 | logname = 'GOTW';
37 | player = {
38 | x: rnd(MAXX - 2),
39 | y: rnd(MAXY - 2),
40 | };
41 |
42 | const filename = getGotwFilename(dateIn);
43 | let state = JSON.stringify(new GameState(true));
44 |
45 | console.log(`uploadGOTW(): uploading ${filename} (${state.length})`);
46 | const response = await fetch(`${CF_BROADCAST_PROTOCOL}${CF_BROADCAST_HOST}/admin/CLOUDFLARE_ADMIN_PASSWORD/gotw`, {
47 | method: 'PUT',
48 | body: JSON.stringify({
49 | filename: encodeURIComponent(filename),
50 | state: state,
51 | }),
52 | headers: {
53 | 'Content-Type': 'application/json',
54 | },
55 | }).catch((err) => console.error('Error uploading GOTW:', err));
56 | console.log(`GOTW uploaded:`, response);
57 | }
58 |
59 | //
60 | //
61 | //
62 | // DOWNLOAD_GOTW
63 | //
64 | //
65 | //
66 | async function downloadGOTW() {
67 | try {
68 | // no filename given - the server decides what gotw file you are going to get
69 | const response = await fetch(`${CF_BROADCAST_PROTOCOL}${CF_BROADCAST_HOST}/api/gotw/${GAMENAME.toLocaleLowerCase()},${logname},${playerID}`);
70 | const data = await response.json();
71 | data.status = response.status;
72 | return data;
73 | } catch (error) {
74 | console.error(`downloadGOTW(): Error downloading GOTW:`, error);
75 | return { error: `Error downloading GOTW`, status: 500 };
76 | }
77 | }
78 |
79 | // totally duplicated in cf_tools.mjs
80 | function getGotwFilename(dateIn) {
81 | let date = dateIn || new Date();
82 | const year = getISOYear(date);
83 | return `${year}/${GAMENAME}_${getGotwLabel(date)}.json`.toLocaleLowerCase();
84 | }
85 |
86 | // totally duplicated in cf_tools.mjs
87 | function getGotwLabel(date) {
88 | const weekNumber = getISOWeek(date);
89 | const year = getISOYear(date);
90 | return `${year}_${weekNumber}`;
91 | }
92 |
--------------------------------------------------------------------------------
/src/common/live.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 |
5 | // send live frame and style metadata from larn to larntv
6 | let lastLiveFrame;
7 | let lastLiveDataTime = 0;
8 | async function processLiveFrame(frame) {
9 | if (!ENABLE_RECORDING_REALTIME) return;
10 | if (!navigator.onLine) return;
11 | if (!game_started) return;
12 |
13 | try {
14 |
15 | // don't send duplicate frames
16 | if (lastLiveFrame && lastLiveFrame.divs.LARN === frame.divs.LARN && lastLiveFrame.divs.STATS === frame.divs.STATS) {
17 | // console.error(`processLiveFrame() dupe`);
18 | return;
19 | }
20 |
21 | // write game metadata to live game list on cloudflare
22 | let nowSeconds = Date.now() / 1000;
23 | let metadata = getGameData();
24 | if (GAMEOVER || nowSeconds - lastLiveDataTime > LIVE_METADATA_WAIT && player.MOVESMADE % LIVE_METADATA_MOVES === 0) {
25 | let someoneIsWatchingYou = await writeGameData(metadata, gameID);
26 | lastLiveDataTime = nowSeconds;
27 |
28 | // someone has started watching, open a websocket
29 | if (numWatchers === 0 && someoneIsWatchingYou > 0) {
30 | initCloudFlare(gameID, gameID, null);
31 | }
32 | // nobody's watching any more, shut down websocket
33 | if (numWatchers > 0 && someoneIsWatchingYou === 0) {
34 | closeCloudflare();
35 | }
36 |
37 | numWatchers = someoneIsWatchingYou;
38 | }
39 |
40 | // write current frame to cloudflare
41 | if (numWatchers > 0) {
42 | frame.metadata = metadata;
43 | lastLiveFrame = frame;
44 | sendLiveFrame(frame, true);
45 | }
46 | } catch (error) {
47 | console.error(`processLiveFrame(): disabling realtime recording`, error);
48 | ENABLE_RECORDING_REALTIME = false;
49 | }
50 |
51 | }
52 |
53 |
54 |
55 | // get live game and style metadata
56 | function getGameData() {
57 | if (!player) return;
58 |
59 | let metadata = {};
60 |
61 | // game data
62 | metadata.gameID = gameID;
63 | metadata.ularn = ULARN;
64 | metadata.build = BUILD;
65 | metadata.difficulty = getDifficulty();
66 | metadata.mobuls = elapsedtime();
67 | metadata.who = logname;
68 | metadata.level = LEVELNAMES[level];
69 | metadata.lastmove = Date.now(); // this gets overwritten by the server because client clocks can be wrong
70 | metadata.framenum = video ? video.currentFrameNum : 0;
71 | metadata.gameover = game_started && GAMEOVER;
72 | let deadreason = player.reason === DIED_SAVED_GAME ? `saved game` : `dead`;
73 | metadata.explored = metadata.gameover ? (player.winner ? `winner` : deadreason) : getExploredLevels(true);
74 |
75 | // display data
76 | let larnElement = document.getElementById(`LARN`);
77 | metadata.fontFamily = getComputedStyle(larnElement).fontFamily;
78 |
79 | return metadata;
80 | }
81 |
--------------------------------------------------------------------------------
/src/throne.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | /* For command mode. Perform removal of gems from a jeweled throne */
5 | function remove_gems() {
6 | cursors();
7 | var item = itemAt(player.x, player.y);
8 | if (item.matches(ODEADTHRONE)) {
9 | updateLog(`There are no gems to remove!`);
10 | } else if (item.matches(OTHRONE)) {
11 | act_remove_gems(item.arg);
12 | } else {
13 | updateLog(`I see no throne here to remove gems from!`);
14 | }
15 | }
16 |
17 |
18 |
19 | /*
20 | act_remove_gems
21 |
22 | Remove gems from a throne.
23 |
24 | arg is zero if there is a gnome king associated with the throne
25 |
26 | Assumes that cursors() has been called previously, and that a check
27 | has been made that the throne actually has gems.
28 | */
29 | function act_remove_gems(arg) {
30 | var k = rnd(101);
31 | if (k < 25) {
32 | for (var i = 0; i < rnd(4); i++) {
33 | dropItemNearPlayer(createGem()); /* gems pop off the throne */
34 | }
35 | var throneArg = itemAt(player.x, player.y).arg;
36 | setItem(player.x, player.y, createObject(ODEADTHRONE, throneArg));
37 | player.level.know[player.x][player.y] = 0;
38 | } else if (k < 40 && arg == 0 && !isGenocided(GNOMEKING)) {
39 | createmonster(GNOMEKING);
40 | itemAt(player.x, player.y).arg = 1;
41 | player.level.know[player.x][player.y] = 0;
42 | } else {
43 | updateLog(` Nothing happens${period}`);
44 | }
45 | return;
46 | }
47 |
48 |
49 |
50 | /*
51 | For command mode. Perform sitting on a throne.
52 | */
53 | function sit_on_throne() {
54 | cursors();
55 | var item = itemAt(player.x, player.y);
56 | if (item.matches(OTHRONE)) {
57 | act_sit_throne(item.arg);
58 | } else if (item.matches(ODEADTHRONE)) {
59 | act_sit_dead_throne(item.arg);
60 | } else {
61 | updateLog(`I see no throne to sit on here!`);
62 | }
63 | }
64 |
65 |
66 |
67 | /*
68 | act_sit_throne
69 |
70 | Sit on a throne.
71 |
72 | arg is zero if there is a gnome king associated with the throne
73 |
74 | Assumes that cursors() has been called previously.
75 | */
76 | function act_sit_throne(arg) {
77 | var k = rnd(101);
78 | if (k < 30 && arg == 0 && !isGenocided(GNOMEKING)) {
79 | createmonster(GNOMEKING);
80 | itemAt(player.x, player.y).arg = 1;
81 | player.level.know[player.x][player.y] = 0;
82 | } else if (k < 35) {
83 | updateLog(` Zaaaappp! You've been teleported!`);
84 | beep();
85 | oteleport(0);
86 | } else {
87 | updateLog(` Nothing happens${period}`);
88 | }
89 | }
90 |
91 |
92 |
93 | function act_sit_dead_throne(arg) {
94 | if (ULARN) {
95 | var k = rnd(101);
96 | if (k < 5) {
97 | player.raiselevel();
98 | } else if (k < 25) {
99 | updateLog(` Zaaaappp! You've been teleported!`);
100 | beep();
101 | oteleport(0);
102 | } else {
103 | updateLog(` Nothing happens${period}`);
104 | }
105 | return;
106 | } else {
107 | act_sit_throne(arg);
108 | }
109 | }
--------------------------------------------------------------------------------
/src/tv/_TODO.txt:
--------------------------------------------------------------------------------
1 |
2 | big, amiga, performance is still shit
3 | http://localhost:8000/tv/index_local.html?gameid=sqcfaiblsz
4 | http://localhost:8001/alpha/tv/?gameid=sqcfaiblsz
5 |
6 | first file doesn't exist
7 | http://localhost:8001/alpha/tv/?gameid=jorz4hhxes
8 |
9 | small, good, savegame at 444
10 | http://localhost:8001/alpha/tv/?gameid=jr0ldn7viw
11 |
12 | small, amiga, runs really slow at times
13 | https://larn.org/larn/tv/index.html?gameid=qjrydqx99l
14 | http://localhost:8001/alpha/tv/?gameid=qjrydqx99l
15 |
16 | big, non-amiga, causes RangeError when scrubbing
17 | https://larn.org/alpha/tv/?gameid=uu5wf8u9al
18 | http://localhost:8001/alpha/tv/?gameid=uu5wf8u9al
19 |
20 | amiga, win by me --> use to test "seeking..."
21 | http://localhost:8000/tv/index_local.html?gameid=q73yvndje4
22 | http://localhost:8001/alpha/tv/?gameid=q73yvndje4
23 |
24 |
25 |
26 | bugs:
27 | *** saving a game 3(?) from the end of a buffer causes a null patch, then error on reload
28 | - scrolling on safari is wonky. seems to be easier when paused
29 | - probably need to await fonts loading on replay
30 | - modern font is too wide on replay
31 | - broken replay: https://larn.org/larn/tv/?gameid=d9vaatk563
32 | - can't load alpha mobile game https://larn.org/larn/tv/?gameid=o0fni21j87
33 | - double screen https://larn.org/larn/tv/?gameid=ve9np2m30v
34 | - made game list but doesn't load 53.json empty file also double screen https://larn.org/larn/tv/?gameid=jch32olu09
35 | - final frames are botched: https://larn.org/larn/tv/?gameid=ai2n1hrgnq
36 | - 106.json is broken: https://larn.org/larn/tv/?gameid=n8l1xbhg2j -> coming back from save game
37 | --> bug mitigated, but cause is a missing frame on save (or frame # is one too high on reload)
38 |
39 | todo:
40 | * amiga games are much bigger. don't record them for now. it also totally doesn't work...
41 | - download multiple .json in parallel
42 | - Link from watch screen back to list
43 | - Save playback speed via cookie
44 | - move game replays to cloudflare
45 | - add play options to larntv list
46 | - restrict aws uploads to .json/etc
47 | - compress completed games into a single large file, or a few larger files
48 | - https://dev.to/lineup-ninja/zip-files-on-s3-with-aws-lambda-and-node-1nm1
49 | - https://stackoverflow.com/questions/38633577/create-a-zip-file-on-s3-from-files-on-s3-using-lambda-node/50397276#50397276
50 | - put a size limit on uploads in the lambda
51 | - VIDEO.WRITETOS3() <- TODO this could be basis of cloud savegames too
52 | - hold inventory views (and others?) for a little longer somehow?
53 | - clean up s3/dynamo boilerplate
54 |
55 | live mode:
56 | - support classic fonts
57 | - mobile fonts have big gaps in the walls
58 | - message to viewer when player disconnects?
59 | - # of watchers doesn't reset when cf worker restarted
60 |
61 |
62 | alternative data structure
63 | --------------------------
64 | recording > bundles > diffs > frames > divs
65 |
66 | div
67 | - id
68 | - content
69 |
70 | frame
71 | - num
72 | - timestamp
73 | - divs[]
74 | - compressedFormat???
75 |
76 | diff
77 | - num
78 | - timestamp
79 | - divs[]
80 | - uncompressedFormat???
81 |
82 | bundle
83 | - id
84 | - diffs[]
85 | - compress()
86 | - decompress()
87 |
88 | recording
89 | - gameid
90 | - bundles[]
--------------------------------------------------------------------------------
/src/larn.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Play Larn Online - A classic DOS/Amiga/Unix Roguelike Video Game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | LARN is a dungeon type adventure game similar in concept to HACK, ROGUE or
32 | MORIA, but with a different feel and winning criteria.
33 |
34 | Unfortunately if you're seeing this page it means that there was an error
35 | loading the game.
36 |
37 | This version of Larn requires a browser from circa 2016 to work properly.
38 | Please check to make sure you are running Firefox 53+, Chrome 55+, Edge 15+,
39 | or Safari 11+. Internet Explorer is not supported.
40 |
41 | Sometimes, you'll get this message when a new version is released. You can
42 | usually get the game working again with a full refresh of your browser.
43 | - On Windows: Hold down Ctrl and press F5
44 | - On Mac:
45 | - Chrome & Firefox: Hold down Shift and click the Reload button
46 | - Safari: Hold down the Option and Command key then press the ‘E’ key
47 |
48 | If you're still having trouble, please send a message to eye@larn.org
49 | and I'll get to the bottom of it!
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JS LARN Version 12.5.3
2 |
3 | ## Introduction
4 |
5 | LARN is a dungeon type adventure game similar in concept to HACK, ROGUE
6 | or MORIA, but with a different feel and winning criteria.
7 |
8 |
9 | ## Play Online
10 | [You can play Larn online at larn.org](https://larn.org "You can play Larn online here")
11 |
12 |
13 | ## Play Locally
14 | - [Download a zipfile of the repository](https://github.com/primeau/Larn/archive/refs/heads/master.zip "download a zipfile of the repository")
15 | - Open the `src` folder then open `larn_local.html` in your browser and play
16 |
17 |
18 | ## Play Locally on a Mobile Device
19 | 1. Install the ***iSH Shell*** app
20 | 2. Open iSH Shell and type the following commands:
21 | ```
22 | # apk update
23 | # apk add python3
24 | # apk add git
25 | # git clone https://github.com/primeau/Larn.git
26 | # python3 -m http.server
27 | ```
28 | 3. Switch to your browser and view this link (and wait, it might take a while): http://127.0.0.1:8000/Larn/src/larn_local.html
29 |
30 |
31 | ## Build Instructions For Development
32 | ```
33 | # git clone https://github.com/primeau/Larn.git
34 | # cd Larn
35 | # npm install
36 | # npm run build
37 | # python3 -m http.server 8000
38 | # http://localhost:8000/dist/larn/larn.html
39 | ```
40 |
41 | ## History and Other Information
42 | Noah Morgan originally created LARN 12.0 and released the UNIX
43 | version to the USENET in 1986. Don Kneller ported the UNIX
44 | version to MSDOS (both IBM PCs and DEC Rainbows).
45 |
46 | Other contributors from this era include:
47 | - James McNamara: UNIX install notes, source and patch distribution
48 | - Fred Fish: Termcap support for VMS port
49 | - Daniel Kegel: Enhanced ansi terminal decoding for DOS
50 | - Alexander Perry: Port for Linux 2.x kernel and GCC 3.x
51 |
52 | Kevin Routley contributed various LARN enhancements. Version 12.1 had
53 | a limited distribution. Version 12.2 was distributed to the Usenet
54 | community. Version 12.3 was the last version released by Kevin.
55 |
56 | Someone made 12.4 through 12.4.2, possibly copx according to
57 | roguebasin. Edwin Denicholas took 12.4 alpha 2 and caressed it into
58 | 12.4.3 for Win32. Version 12.4.4 includes a bugfix from Joe Neff.
59 |
60 | Ularn was written in 1987 by Phil Cordier at UC Santa Cruz.
61 | Version 1.5.1 was released by David Richerby and future 1.5.x versions
62 | were developed by Josh Brandt. Version 1.6 was a Windows 32 conversion
63 | and refactor by Julian Olds. Ularn is currently maintained by Josh Bressers.
64 |
65 | Other editions of Larn have been distributed by others, namely
66 | LARN13, dLarn, NLarn, ReLarn, and XLarn.
67 |
68 | JSLarn 12.5.3 is a JavaScript port of Larn and Ularn, by Jason Primeau.
69 | It primarily references the Larn 12.4.4 and Ularn 1.5.4 codebases but takes
70 | inspiration from other versions as well. It includes bug fixes, a global
71 | scoreboard, watching live replays, a weekly challenge mode, mobile support
72 | and updates to balance the game at higher difficulties.
73 |
74 | I hope you enjoy this version of LARN.
75 |
76 | [Larn's version history can be found here](https://github.com/primeau/Larn/blob/master/history.md "Larn's version history can be found here")
77 |
78 |
79 | ## WIZARD mode
80 | There is a WIZARD mode for testing features of the game. To get into WIZARD
81 | mode, type in an underscore '_' and answer the prompt for the password with
82 | '`pvnert(x)`' (do not enter the quotes). Wizards are non-scoring characters that
83 | get magic mapping, everlasting expanded awareness and one of every object in
84 | the game.
85 |
86 | Since it's easy to lose your game due to a browser crash, accidental back
87 | button press, or closed tab, there are 2 new 'wizard' passwords. Using the
88 | password 'checkpoint' will load a backup of a checkpoint file, and 'savegame'
89 | will resurrect a previous save game. These games are excluded from the
90 | scoreboard due to rampant cheating.
--------------------------------------------------------------------------------
/src/stairs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | /*
5 | For command mode. Checks that player is actually standing at a set of
6 | up stairs or volcanic shaft.
7 | */
8 | function up_stairs() {
9 | var item = itemAt(player.x, player.y);
10 |
11 | if (item.matches(OSTAIRSDOWN)) {
12 | updateLog(` The stairs don't go up!`);
13 | dropflag = 1;
14 | } else if (item.matches(OVOLUP))
15 | act_up_shaft();
16 |
17 | else if (!item.matches(OSTAIRSUP)) {
18 | updateLog(` I see no way to go up here!`);
19 | dropflag = 1;
20 | } else
21 | act_up_stairs();
22 | }
23 |
24 |
25 |
26 | /*
27 | For command mode. Checks that player is actually standing at a set of
28 | down stairs or volcanic shaft.
29 | */
30 | function down_stairs() {
31 | var item = itemAt(player.x, player.y);
32 |
33 | if (item.matches(OSTAIRSUP)) {
34 | updateLog(` The stairs don't go down!`);
35 | dropflag = 1;
36 | } else if (item.matches(OVOLDOWN))
37 | act_down_shaft();
38 |
39 | else if (item.matches(OENTRANCE))
40 | enter();
41 |
42 | else if (!item.matches(OSTAIRSDOWN)) {
43 | updateLog(` I see no way to go down here!`);
44 | dropflag = 1;
45 | } else
46 | act_down_stairs();
47 | }
48 |
49 |
50 |
51 | /*
52 | assumes that cursors() has been called and that a check has been made that
53 | the user is actually standing at a set of up stairs.
54 | */
55 | function act_up_stairs() {
56 | let deadend = level <= 1; // no up on H, D1
57 | deadend |= level == MAXLEVEL; // no up on V1
58 | if (ULARN) deadend |= level == DBOTTOM; // no up on D15
59 | if (!deadend) {
60 | newcavelevel(level - 1);
61 | } else {
62 | updateLog(` The stairs lead to a dead end!`);
63 | dropflag = 1;
64 | }
65 | }
66 |
67 |
68 |
69 | /*
70 | assumes that cursors() has been called and that a check has been made that
71 | the user is actually standing at a set of down stairs.
72 | */
73 | function act_down_stairs() {
74 | let deadend = level == 0;
75 | deadend |= level == DBOTTOM; // no down at bottom of dungeon
76 | deadend |= level == VBOTTOM; // no down at bottom of volcano
77 | if (ULARN) deadend |= level >= VBOTTOM - 2; // ularn no down stairs on V3/V4
78 | if (!deadend) {
79 | newcavelevel(level + 1);
80 | } else {
81 | updateLog(` The stairs lead to a dead end!`);
82 | dropflag = 1;
83 | }
84 | }
85 |
86 |
87 |
88 | /*
89 | Perform the act of climbing down the volcanic shaft. Assumes
90 | cursors() has been called and that a check has been made that
91 | are actually at a down shaft.
92 | */
93 | function act_down_shaft() {
94 | setMazeMode(true);
95 |
96 | if (level != 0) {
97 | updateLog(` The shaft only extends 5 feet downward!`);
98 | return;
99 | }
100 |
101 | /*
102 | v12.4.5 - far too many newbie players go into the volcanic shaft, die,
103 | and never play again. this seemed like the least restrictive
104 | way to prevent that from happening.
105 | */
106 | // if (LEVELS[1] == null && !wizard) { // always true in GOTW games
107 | if (!isLevelVisited(1) && !wizard) {
108 | updateLog(` You feel a foreboding sense of doom, and back away`);
109 | return;
110 | }
111 |
112 | if (packweight() > 45 + 3 * (player.STRENGTH + player.STREXTRA)) {
113 | updateLog(` You slip and fall down the shaft${period}`);
114 | lastnum = DIED_VOLCANO_SLIP; /* slipped on a volcano shaft */
115 | player.losehp(30 + rnd(20));
116 | }
117 |
118 | newcavelevel(MAXLEVEL);
119 | // moveNear(OVOLUP, false); // this is a larn 12.0 `feature`
120 |
121 | }
122 |
123 |
124 |
125 | /*
126 | Perform the action of climbing up the volcanic shaft. Assumes
127 | cursors() has been called and that a check has been made that
128 | are actually at an up shaft.
129 | */
130 | function act_up_shaft() {
131 | setMazeMode(true);
132 |
133 | if (level != MAXLEVEL) {
134 | updateLog(` The shaft only extends 8 feet upwards before you find a blockage!`);
135 | return;
136 | }
137 |
138 | if (packweight() > (ULARN ? 40 : 45) + 5 * (player.STRENGTH + player.STREXTRA)) {
139 | updateLog(` You slip and fall down the shaft${period}`);
140 | lastnum = DIED_VOLCANO_SLIP; /* slipped on a volcano shaft */
141 | player.losehp(15 + rnd(20));
142 | return;
143 | }
144 |
145 | newcavelevel(0);
146 | moveNear(OVOLDOWN, false);
147 | }
148 |
--------------------------------------------------------------------------------
/src/larn.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: modern;
3 |
4 | /* experiments to try fixing too-wide fonts on non-chrome browsers https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Fundamentals */
5 | /* for future font-noodling https://fonts.google.com/?category=Monospace&preview.text=a%E2%96%92%E2%96%92%E2%96%92%C2%B7@%C2%B7%E2%96%92%C2%B7%E2%96%92A!!0%3F&preview.text_type=custom */
6 |
7 | /* works in firefox, not safari */
8 | /* src: url('img/PTMono-Regular.ttf'); */
9 |
10 | /* no firefox or safari */
11 | src: url('img/NotoSansMono-Regular.ttf');
12 | /* src: url('img/NotoSansMono-Light.ttf'); */
13 | }
14 |
15 | @font-face {
16 | font-family: dos437;
17 | src: url('img/dos437.ttf');
18 | }
19 |
20 | @font-face {
21 | font-family: amiga500;
22 | src: url('img/TopazPlus_a500_v1.0.ttf');
23 | }
24 |
25 | @font-face {
26 | font-family: amiga1200;
27 | src: url('img/TopazPlus_a1200_v1.0.ttf');
28 | }
29 |
30 | a:link {
31 | color: #bbbbbb;
32 | text-decoration: none;
33 | }
34 |
35 | a:visited {
36 | color: #FF00FF;
37 | text-decoration: none;
38 | }
39 |
40 | a:hover {
41 | color: #FF77FF;
42 | text-decoration: none;
43 | }
44 |
45 | a:active {
46 | color: #EE0000;
47 | text-decoration: none;
48 | }
49 |
50 | b,
51 | strong {
52 | color: lightgray;
53 | }
54 |
55 | body {
56 | /* border: 1px solid orange; */
57 | font-family: 'dos', 'Courier New', Courier, monospace;
58 | margin: 0px;
59 | background-color: #000000;
60 | }
61 |
62 | div {
63 | background-position: center center;
64 | background-size: 100% 100%;
65 | background-repeat: no-repeat;
66 | }
67 |
68 | .image {
69 | display: inline-block;
70 | background-image: url('img/o94.png');
71 | }
72 |
73 | mark {
74 | background-color: lightgrey;
75 | color: black;
76 | }
77 |
78 | .button,
79 | .narrowbutton,
80 | .variablebutton,
81 | .verticalbutton {
82 | /* color: black;
83 | background-color: #FFD700; */
84 | color: lightgrey;
85 | background-color: #0071e3;
86 | border: none;
87 | text-align: center;
88 | text-decoration: none;
89 | display: inline-block;
90 | cursor: pointer;
91 | -webkit-touch-callout: none;
92 | -webkit-user-select: none;
93 | -khtml-user-select: none;
94 | -moz-user-select: none;
95 | -ms-user-select: none;
96 | appearance: none;
97 | -moz-appearance: none;
98 | -webkit-appearance: none;
99 | user-select: none;
100 | }
101 |
102 | .narrowbutton {
103 | width: 45px;
104 | }
105 |
106 | .button {
107 | width: 70px;
108 | }
109 |
110 | .verticalbutton {
111 | display: block;
112 | width: 100%;
113 | height: 100%;
114 | }
115 |
116 | .button:disabled,
117 | .narrowbutton:disabled,
118 | .variablebutton:disabled,
119 | .verticalbutton:disabled,
120 | .button[disabled],
121 | .narrowbutton[disabled],
122 | .variablebutton[disabled],
123 | .verticalbutton[disabled] {
124 | border: 1px solid #999999;
125 | background-color: #cccccc;
126 | color: #666666;
127 | cursor: not-allowed;
128 | pointer-events: none;
129 | }
130 |
131 |
132 |
133 | .box,
134 | .larngrid {
135 | position: absolute;
136 | /* border: 1px solid white; */
137 | -webkit-user-select: none;
138 | -webkit-touch-callout: none;
139 | -moz-user-select: none;
140 | -ms-user-select: none;
141 | user-select: none;
142 | }
143 |
144 |
145 | larn {
146 | white-space: pre;
147 | margin: 0px;
148 | /* border: 1px solid green; */
149 | }
150 |
151 | larngrid {
152 | /* border: 1px solid blue; */
153 | width: 100%;
154 | height: 100%;
155 | }
156 |
157 | foot {
158 | /* border: 1px solid cyan; */
159 | }
160 |
161 | inventorybuttons {
162 | /* border: 1px solid pink; */
163 | position: static;
164 | }
165 |
166 | keyboard {
167 | /* border: 1px solid lightblue; */
168 | }
169 |
170 | help {
171 | /* border: 1px solid violet; */
172 | }
173 |
174 | stats {
175 | white-space: pre;
176 | /* border: 1px solid yellow; */
177 | }
178 |
179 | movebuttons {
180 | /* border: 1px solid orange; */
181 | }
182 |
183 | contextbuttons {
184 | /* border: 1px solid brown; */
185 | }
186 |
187 | run {
188 | /* border: 1px solid red; */
189 | /* background: white; */
190 | transform-origin: top left;
191 | rotate: 90deg;
192 | }
--------------------------------------------------------------------------------
/src/lib/lz-string.min.js:
--------------------------------------------------------------------------------
1 | var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);
--------------------------------------------------------------------------------
/src/regen.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* subroutine to regenerate player hp and spells */
4 | function regen() {
5 |
6 | if (!player) return;
7 |
8 | player.MOVESMADE++;
9 |
10 | /* for stop time spell */
11 | if (player.TIMESTOP > 0) {
12 | if (player.updateTimeStop(-1) <= 0) recalc();
13 | return;
14 | }
15 |
16 | // is this where gtime should be incremented?
17 |
18 | if (player.HP != player.HPMAX) {
19 | if (player.REGENCOUNTER-- <= 0) /* regenerate hit points */ {
20 | player.REGENCOUNTER = 22 + (getDifficulty() << 1) - player.LEVEL;
21 | player.raisehp(player.REGEN);
22 | }
23 | }
24 |
25 | /* regenerate spells */
26 | if (player.SPELLS < player.SPELLMAX) {
27 | if (player.ECOUNTER-- <= 0) {
28 | player.ECOUNTER = 100 + 4 * (getDifficulty() - player.LEVEL - player.ENERGY);
29 | player.setSpells(player.SPELLS + 1);
30 | }
31 | }
32 |
33 | if (player.HERO) {
34 | if (--player.HERO <= 0) {
35 | player.setStrength(player.STRENGTH - 10);
36 | player.setIntelligence(player.INTELLIGENCE - 10);
37 | player.setWisdom(player.WISDOM - 10);
38 | player.setConstitution(player.CONSTITUTION - 10);
39 | player.setDexterity(player.DEXTERITY - 10);
40 | player.setCharisma(player.CHARISMA - 10);
41 | }
42 | }
43 |
44 | if (player.COKED) {
45 | if (--player.COKED <= 0) {
46 | player.setStrength(player.STRENGTH - 34);
47 | player.setIntelligence(player.INTELLIGENCE - 34);
48 | player.setWisdom(player.WISDOM - 34);
49 | player.setConstitution(player.CONSTITUTION - 34);
50 | player.setDexterity(player.DEXTERITY - 34);
51 | player.setCharisma(player.CHARISMA - 34);
52 | }
53 | }
54 |
55 | if (player.STEALTH) player.updateStealth(-1);
56 | if (player.UNDEADPRO) player.updateUndeadPro(-1);
57 | if (player.SPIRITPRO) player.updateSpiritPro(-1);
58 | if (player.CHARMCOUNT) player.updateCharmCount(-1);
59 | if (player.HOLDMONST) player.updateHoldMonst(-1);
60 | if (player.FIRERESISTANCE) player.updateFireResistance(-1);
61 | if (player.SCAREMONST) player.updateScareMonst(-1);
62 | if (player.HASTESELF) player.updateHasteSelf(-1);
63 | if (player.CANCELLATION) player.updateCancellation(-1);
64 | if (player.INVISIBILITY) player.updateInvisibility(-1);
65 | if (player.WTW) player.updateWTW(-1);
66 |
67 | if (player.GIANTSTR) player.updateGiantStr(-1);
68 | if (player.DEXCOUNT) player.updateDexCount(-1);
69 | if (player.STRCOUNT) player.updateStrCount(-1);
70 | if (player.ALTPRO) player.updateAltPro(-1);
71 | if (player.PROTECTIONTIME) player.updateProtectionTime(-1);
72 |
73 | if (player.GLOBE) if (--player.GLOBE <= 0) player.setMoreDefenses(player.MOREDEFENSES - 10);
74 | if (player.BLINDCOUNT) if (--player.BLINDCOUNT <= 0) updateLog(`The blindness lifts${period}`);
75 | if (player.CONFUSE) if (--player.CONFUSE <= 0) updateLog(`You regain your senses${period}`);
76 | if (player.HALFDAM) if (--player.HALFDAM <= 0) updateLog(`You now feel better${period}`);
77 |
78 | if (player.AGGRAVATE) --player.AGGRAVATE;
79 | if (player.AWARENESS) if (!isCarrying(OORB)) --player.AWARENESS;
80 | if (player.HASTEMONST) --player.HASTEMONST;
81 |
82 | if (player.SEEINVISIBLE) {
83 | if (ULARN) {
84 | if (isCarrying(OAMULET)) {
85 | /* See inv doesn't wear off if player has amulet of invisibility */
86 | player.SEEINVISIBLE++;
87 | }
88 | }
89 | if (--player.SEEINVISIBLE <= 0) {
90 | if (!player.BLINDCOUNT) {
91 | updateLog(`You feel your vision return to normal${period}`);
92 | }
93 | }
94 | }
95 |
96 | if (player.ITCHING) {
97 | if (player.ITCHING > 1)
98 | if ((player.WEAR) || (player.SHIELD))
99 | if (rnd(100) < 50) {
100 | player.WEAR = null;
101 | player.SHIELD = null;
102 | updateLog(`The hysteria of itching forces you to remove your armor!`);
103 | }
104 | if (--player.ITCHING <= 0) {
105 | updateLog(`You now feel the irritation subside!`);
106 | }
107 | }
108 |
109 | if (player.CLUMSINESS) {
110 | if (player.WIELD)
111 | if (player.CLUMSINESS > 1)
112 | if (itemAt(player.x, player.y).matches(OEMPTY)) /* only if nothing there */
113 | if (rnd(100) < 33) {/* drop your weapon due to clumsiness */
114 | var dropindex = getCharFromIndex(player.inventory.indexOf(player.WIELD));
115 | drop_object(dropindex);
116 | }
117 | if (--player.CLUMSINESS <= 0) {
118 | updateLog(`You now feel less awkward!`);
119 | }
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/lib/mousetrap.min.js:
--------------------------------------------------------------------------------
1 | /* mousetrap v1.6.3 craig.is/killing/mice */
2 | (function(q,u,c){function v(a,b,g){a.addEventListener?a.addEventListener(b,g,!1):a.attachEvent("on"+b,g)}function z(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return n[a.which]?n[a.which]:r[a.which]?r[a.which]:String.fromCharCode(a.which).toLowerCase()}function F(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function w(a){return"shift"==a||"ctrl"==a||"alt"==a||
3 | "meta"==a}function A(a,b){var g,d=[];var e=a;"+"===e?e=["+"]:(e=e.replace(/\+{2}/g,"+plus"),e=e.split("+"));for(g=0;gc||n.hasOwnProperty(c)&&(p[n[c]]=c)}g=p[e]?"keydown":"keypress"}"keypress"==g&&d.length&&(g="keydown");return{key:m,modifiers:d,action:g}}function D(a,b){return null===a||a===u?!1:a===b?!0:D(a.parentNode,b)}function d(a){function b(a){a=
4 | a||{};var b=!1,l;for(l in p)a[l]?b=!0:p[l]=0;b||(x=!1)}function g(a,b,t,f,g,d){var l,E=[],h=t.type;if(!k._callbacks[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(l=0;l":".","?":"/","|":"\\"},B={option:"alt",command:"meta","return":"enter",
9 | escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p;for(c=1;20>c;++c)n[111+c]="f"+c;for(c=0;9>=c;++c)n[c+96]=c.toString();d.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};d.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};d.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};d.prototype.reset=function(){this._callbacks={};
10 | this._directMap={};return this};d.prototype.stopCallback=function(a,b){if(-1<(" "+b.className+" ").indexOf(" mousetrap ")||D(b,this.target))return!1;if("composedPath"in a&&"function"===typeof a.composedPath){var c=a.composedPath()[0];c!==a.target&&(b=c)}return"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};d.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};d.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b]);p=null};
11 | d.init=function(){var a=d(u),b;for(b in a)"_"!==b.charAt(0)&&(d[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};d.init();q.Mousetrap=d;"undefined"!==typeof module&&module.exports&&(module.exports=d);"function"===typeof define&&define.amd&&define(function(){return d})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null);
--------------------------------------------------------------------------------
/src/state.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // TODO, make this the canonical source for data?
4 | // i.e. replace cheat = ... with status.cheat etc.
5 |
6 |
7 | /* additions for JS Larn */
8 | var LEVELS;
9 | let EXPLORED_LEVELS;
10 | var LOG;
11 | var player;
12 | var playerID;
13 | var playerIP = `0`;
14 | var PARAMS = {};
15 | var recording = {};
16 | var showConfigButtons = true;
17 |
18 | var newsphereflag = false; /* JRP hack to not move sphere twice after cast */
19 | var GAMEOVER = true;
20 | var game_started = false;
21 | var mazeMode = false;
22 | var napping = false; /* prevent keyboard input while a nap event is happening */
23 | var original_objects = true;
24 | var keyboard_hints = false;
25 | var auto_pickup = false;
26 | var side_inventory = true;
27 | var show_color = true;
28 | var bold_objects = true;
29 | var retro_mode = false;
30 | var dnd_item = null;
31 | var genocide = [];
32 | var amiga_mode = false;
33 | var gameID = Math.random().toString(36).substr(2, 10);
34 | // var gameID = `testgameid`;
35 | var debug_used = 0;
36 |
37 | var logname = `Adventurer`;
38 | var cheat = 0; /* 1 if the player has fudged save file */
39 | var level = -1; /* cavelevel player is on = cdesc[CAVELEVEL] */
40 | var wizard = 0; /* the wizard mode flag */
41 | var gtime = 0; /* the clock for the game */
42 | var HARDGAME = 0; /* game difficulty */
43 |
44 | /* these function were added as a defensive measure to find a pesky bug,
45 | and now it's easier to just leave them here
46 | */
47 | function getDifficulty() {
48 | if (HARDGAME == null || HARDGAME === `` || isNaN(Number(HARDGAME))) {
49 | console.log('get: invalid difficulty: ' + HARDGAME);
50 | console.trace();
51 | }
52 | return Math.min(128, HARDGAME);
53 | }
54 |
55 | function setDifficulty(diff) {
56 | if (diff == null || diff === `` || isNaN(Number(diff))) {
57 | console.log('set: invalid difficulty: ' + diff);
58 | console.trace();
59 | }
60 | if (diff > 128) {
61 | console.log(`capping difficulty at 128`);
62 | diff = 128;
63 | }
64 | HARDGAME = diff;
65 | }
66 |
67 | var lastmonst = ``; /* name of the last monster to hit the player */
68 | var lastnum = 0; /* the number of the monster last hitting player */
69 | var hitflag = 0; /* flag for if player has been hit when running */
70 | var lastpx = 0;
71 | var lastpy = 0;
72 | var lasthx = 0; /* location of monster last hit by player */
73 | var lasthy = 0; /* location of monster last hit by player */
74 | var prayed = 1; /* did player pray at an altar? */
75 | var course = []; /* the list of courses taken */
76 | var outstanding_taxes = 0; /* present tax bill from score file */
77 | var dropflag = 0; /* if 1 then don't lookforobject() next round */
78 | var rmst = 120; /* random monster creation counter */
79 | var nomove = 0; /* if (nomove) then don't count next iteration as a move */
80 | var viewflag = 0; /* if viewflag then we have done a 99 stay here and don't showcell in the main loop */
81 | var lasttime = 0; /* last time in bank */
82 | var spheres = [];
83 |
84 |
85 |
86 | function GameState(save) {
87 | this.LEVELS = LEVELS;
88 | this.LOG = LOG;
89 | this.player = player;
90 |
91 | if (ENABLE_RECORDING) {
92 | this.recording = getRecordingInfo();
93 | if (save) {
94 | this.recording.frames += 3; // hack because three more frames get added before a game is done saving
95 | } else {
96 | // this.recording.frames -= 1; // hack because 1 frame is added before a game is loaded
97 | this.recording.rolls -= 1; // hack because 1 roll is added before a game is loaded
98 | }
99 | }
100 |
101 | this.newsphereflag = newsphereflag;
102 | this.GAMEOVER = GAMEOVER;
103 | this.mazeMode = mazeMode;
104 | this.napping = napping;
105 | this.original_objects = original_objects;
106 | this.keyboard_hints = keyboard_hints;
107 | this.auto_pickup = auto_pickup;
108 | this.side_inventory = side_inventory;
109 | this.show_color = show_color;
110 | this.bold_objects = bold_objects;
111 | this.dnd_item = dnd_item;
112 | this.genocide = genocide;
113 | this.amiga_mode = amiga_mode;
114 | this.gameID = gameID;
115 | this.debug_used = debug_used;
116 |
117 | this.logname = logname;
118 | this.cheat = cheat;
119 | this.level = level;
120 | this.wizard = wizard;
121 | this.gtime = gtime;
122 | this.HARDGAME = getDifficulty();
123 | this.lastmonst = lastmonst;
124 | this.lastnum = lastnum;
125 | this.hitflag = hitflag;
126 | this.lastpx = lastpx;
127 | this.lastpy = lastpy;
128 | this.lasthx = lasthx;
129 | this.lasthy = lasthy;
130 | this.prayed = prayed;
131 | this.course = course;
132 | this.outstanding_taxes = outstanding_taxes;
133 | this.dropflag = dropflag;
134 | this.rmst = rmst;
135 | this.nomove = nomove;
136 | this.viewflag = viewflag;
137 | this.lasttime = lasttime;
138 | this.spheres = spheres;
139 | }
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function setGameConfig() {
4 |
5 | // GAME NAME
6 | GAMENAME = ULARN ? `Ularn` : `Larn`;
7 |
8 | // TIME
9 | TIMELIMIT = ULARN ? 40000 : 30000;
10 |
11 | // DUNGEON AND VOLCANO LEVELS
12 | MAXLEVEL = ULARN ? 16 : 11;
13 | MAXVLEVEL = ULARN ? 5 : 3;
14 | DBOTTOM = (MAXLEVEL - 1);
15 | VBOTTOM = (MAXLEVEL + MAXVLEVEL - 1);
16 |
17 | // MAZES
18 | LEVELS = new Array(MAXLEVEL + MAXVLEVEL);
19 | EXPLORED_LEVELS = new Array(MAXLEVEL + MAXVLEVEL).fill(false); // cache needed for GOTW games
20 |
21 | MAZES = COMMON_MAZES.concat(ULARN ? ULARN_MAZES : LARN_MAZES);
22 |
23 | // MONSTERS
24 | monsterlist = ULARN ? ULARN_monsterlist : LARN_monsterlist;
25 |
26 | // SPELLS
27 | splev = ULARN ? ULARN_splev : LARN_splev;
28 | spelweird = ULARN ? ULARN_spelweird : LARN_spelweird;
29 |
30 | // BUILDINGS
31 | STORE_INVENTORY = ULARN ? ULARN_STORE_INVENTORY : LARN_STORE_INVENTORY;
32 | MAXITM = STORE_INVENTORY.length;
33 |
34 | MAX_BANK_BALANCE = ULARN ? 1000000 : 500000;
35 | OBANK.desc = `the bank of ${GAMENAME}`;
36 | OBANK2.desc = `the ${ULARN ? 8 : 5}th branch of the Bank of ${GAMENAME}`;
37 |
38 | OSCHOOL.desc = `the College of ${GAMENAME}`;
39 |
40 | OLRS.desc = `the ${GAMENAME} Revenue Service`;
41 |
42 | if (ULARN) OTRADEPOST.desc = `the Ularn trading Post`;
43 |
44 | // ITEMS
45 | FORTUNES = COMMON_FORTUNES.concat(ULARN ? ULARN_FORTUNES : LARN_FORTUNES);
46 |
47 | if (ULARN) {
48 | DEATH_REASONS.set(DIED_BOTTOMLESS_TRAPDOOR, `fell through a trap door to HELL`);
49 | DEATH_REASONS.set(DIED_BOTTOMLESS_PIT, `fell into a pit to HELL`);
50 | }
51 |
52 | // MISC NIT-PICKING FOR MESSAGES
53 | youFound = ULARN ? `You find` : `You have found`;
54 | period = ULARN ? `.` : ``;
55 |
56 | // MONSTER COLOURS
57 | if (ULARN) monsterlist[LEMMING].color = `rosybrown`; else monsterlist[BAT].color = `brown`;
58 | monsterlist[GNOME].color = `darkkhaki`;
59 | monsterlist[HOBGOBLIN].color = `salmon`;
60 | monsterlist[JACKAL].color = `sandybrown`;
61 | if (ULARN) monsterlist[KOBOLD].color = `brown`; else monsterlist[KOBOLD].color = `rosybrown`;
62 | monsterlist[ORC].color = `tan`;
63 | monsterlist[SNAKE].color = `olivedrab`;
64 | monsterlist[CENTIPEDE].color = `orangered`;
65 | monsterlist[JACULI].color = `burlywood`;
66 | monsterlist[TROGLODYTE].color = `navajowhite`;
67 | monsterlist[ANT].color = `indianred`;
68 | monsterlist[EYE].color = `mediumorchid`;
69 | monsterlist[LEPRECHAUN].color = `mediumseagreen`;
70 | monsterlist[NYMPH].color = `lightpink`;
71 | monsterlist[QUASIT].color = `yellowgreen`;
72 | monsterlist[RUSTMONSTER].color = `goldenrod`;
73 | monsterlist[ZOMBIE].color = `gray`;
74 | monsterlist[ASSASSINBUG].color = `forestgreen`;
75 | if (ULARN) monsterlist[BITBUG].color = `chocolate`; else monsterlist[BUGBEAR].color = `chocolate`;
76 | monsterlist[HELLHOUND].color = `crimson`;
77 | monsterlist[ICELIZARD].color = `white`;
78 | monsterlist[CENTAUR].color = `sandybrown`;
79 | monsterlist[TROLL].color = `seagreen`;
80 | monsterlist[YETI].color = `mintcream`;
81 | monsterlist[WHITEDRAGON].color = `snow`;
82 | monsterlist[ELF].color = `mediumseagreen`;
83 | monsterlist[CUBE].color = `turquoise`;
84 | monsterlist[METAMORPH].color = `moccasin`;
85 | monsterlist[VORTEX].color = `lightcyan`;
86 | monsterlist[ZILLER].color = `cadetblue`;
87 | monsterlist[VIOLETFUNGI].color = `violet`;
88 | monsterlist[WRAITH].color = `dimgray`;
89 | monsterlist[FORVALAKA].color = `thistle`;
90 | if (ULARN) monsterlist[LAMANOBE].color = `powderblue`; else monsterlist[LAWLESS].color = `powderblue`;
91 | monsterlist[OSEQUIP].color = null;
92 | monsterlist[ROTHE].color = `sienna`;
93 | monsterlist[XORN].color = `brown`;
94 | monsterlist[VAMPIRE].color = `slategray`;
95 | monsterlist[INVISIBLESTALKER].color = null;
96 | monsterlist[POLTERGEIST].color = `lavender`;
97 | monsterlist[DISENCHANTRESS].color = `antiquewhite`;
98 | monsterlist[SHAMBLINGMOUND].color = `olivedrab`;
99 | monsterlist[YELLOWMOLD].color = `palegoldenrod`;
100 | monsterlist[UMBERHULK].color = `peru`;
101 | monsterlist[GNOMEKING].color = `steelblue`;
102 | monsterlist[MIMIC].color = `beige`;
103 | monsterlist[WATERLORD].color = `cornflowerblue`;
104 | monsterlist[BRONZEDRAGON].color = `goldenrod`;
105 | monsterlist[GREENDRAGON].color = `palegreen`;
106 | monsterlist[PURPLEWORM].color = `purple`;
107 | monsterlist[XVART].color = `teal`;
108 | monsterlist[SPIRITNAGA].color = `mediumslateblue`;
109 | monsterlist[SILVERDRAGON].color = `paleturquoise`;
110 | monsterlist[PLATINUMDRAGON].color = `lightsteelblue`;
111 | monsterlist[GREENURCHIN].color = `lime`;
112 | monsterlist[REDDRAGON].color = `indianred`;
113 |
114 | }
--------------------------------------------------------------------------------
/src/common/lambda.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function invokeLambda(requestPayload, successCallback, failCallback) {
4 | if (AWS.config.accessKeyId === `AWS_CONFIG_ACCESSKEYID`) {
5 | console.log(`AWS credentials not set`);
6 | return;
7 | }
8 |
9 | let params = {
10 | FunctionName: AWS_RECORD_FUNCTION,
11 | Payload: JSON.stringify(requestPayload),
12 | InvocationType: 'RequestResponse',
13 | LogType: 'None'
14 | };
15 |
16 | try {
17 | lambda.invoke(params, function (error, data) {
18 | if (!error) {
19 | if (data) {
20 | if (data.StatusCode == 200) {
21 | if (successCallback) {
22 | let responsePayload = JSON.parse(data.Payload);
23 | successCallback(responsePayload.body, responsePayload.File, responsePayload.Metadata);
24 | }
25 | } else {
26 | console.error(`invokeLambda(): error ${data.StatusCode}`);
27 | updateMessage(`couldn't load: ${data.StatusCode}`);
28 | if (failCallback) failCallback(data.StatusCode);
29 | }
30 | }
31 | } else {
32 | console.error(`invokeLambda(): error: ${error}`);
33 | updateMessage(`${error}`);
34 | if (failCallback) failCallback(error);
35 | }
36 | });
37 | } catch (error) {
38 | console.error(`invokeLambda():`, error);
39 | }
40 |
41 | }
42 |
43 |
44 |
45 |
46 | async function invokeLambdaAsync(payload) {
47 | if (AWS.config.accessKeyId === `AWS_CONFIG_ACCESSKEYID`) {
48 | console.log(`AWS credentials not set`);
49 | return;
50 | }
51 |
52 | const params = {
53 | FunctionName: AWS_RECORD_FUNCTION,
54 | Payload: JSON.stringify(payload),
55 | InvocationType: 'RequestResponse',
56 | LogType: 'None'
57 | };
58 |
59 | try {
60 | const response = await lambda.invoke(params).promise();
61 |
62 | let payload = {};
63 | if (response.StatusCode === 200 && response.Payload) {
64 | try {
65 | payload = JSON.parse(response.Payload);
66 | } catch (parseError) {
67 | payload = { rawPayload: response.Payload };
68 | }
69 | if (payload.statusCode === 200) {
70 | return payload;
71 | } else {
72 | console.error(`invokeLambdaAsync(): payload error: ${payload.statusCode}`);
73 | return null;
74 | }
75 | } else {
76 | console.error(`invokeLambdaAsync(): response error: ${response.StatusCode}`);
77 | return null;
78 | }
79 | } catch (error) {
80 | console.error(`invokeLambdaAsync():`, error);
81 | }
82 | }
83 |
84 |
85 |
86 | function lambdaFail(err) {
87 | console.log(`lambdaFail(): `, err);
88 | }
89 |
90 |
91 |
92 | function loadStyles(gameID, successCallback) {
93 | console.log(`loadStyles()`);
94 | let requestPayload = {
95 | action: `loadstyle`,
96 | gameID: gameID
97 | };
98 | invokeLambda(requestPayload, successCallback, null);
99 | }
100 |
101 |
102 |
103 | function downloadFile(gameID, filename, successCallback, failCallback) {
104 | console.log(`downloadFile(): ${gameID}/${filename}`);
105 | let requestPayload = {
106 | action: `read`,
107 | gameID: gameID,
108 | filename: filename,
109 | };
110 | invokeLambda(requestPayload, successCallback, failCallback);
111 | }
112 |
113 | async function downloadFileAsync(gameID, filename) {
114 | console.log(`downloadFileAsync(): ${gameID}/${filename}`);
115 | let requestPayload = {
116 | action: `read`,
117 | gameID: gameID,
118 | filename: filename,
119 | };
120 | const response = await invokeLambdaAsync(requestPayload);
121 | if (!response) {
122 | console.error(`downloadFileAsync(): ${gameID}/${filename} empty file`);
123 | }
124 | return response;
125 | }
126 |
127 |
128 |
129 | async function downloadRoll(gameID, rollNum) {
130 | let filename = `${rollNum}.json`;
131 | // console.log(`downloadRoll(): ${gameID}/${filename}`);
132 | let data = await downloadFileAsync(gameID, filename);
133 | if (data && data.File) {
134 | let roll = decompressRoll(data.File);
135 | roll.metadata = data.Metadata;
136 | return roll;
137 | } else {
138 | console.error(`downloadRoll(): ${gameID}/${filename} empty file`);
139 | return null;
140 | }
141 | }
142 |
143 |
144 | async function downloadStyleInfo(gameID) {
145 | let filename = `${gameID}.css`;
146 | let data = await downloadFileAsync(gameID, filename);
147 | if (data && data.File) {
148 | const styleInfo = JSON.parse(data.File);
149 | return styleInfo;
150 | } else {
151 | console.error(`downloadStyleInfo(): ${gameID}/${filename} empty file`);
152 | return null;
153 | }
154 | }
155 |
156 |
157 |
158 | function uploadFile(gameID, filename, filecontents, isLastFile, metadata) {
159 | if (!filecontents) {
160 | console.error(`uploadFile(): no file contents for ${gameID}/${JSON.stringify(filename)}`);
161 | return;
162 | }
163 | console.log(`uploadFile(): ${gameID}/${filename} (${filecontents.length})`);
164 | let requestPayload = {
165 | action: isLastFile ? `writelast` : `write`,
166 | gameID: gameID,
167 | filename: filename,
168 | file: filecontents,
169 | Metadata: metadata
170 | };
171 | invokeLambda(requestPayload, null, null);
172 | }
--------------------------------------------------------------------------------
/src/io.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var cursorx = 1;
4 | var cursory = 1;
5 |
6 | var display = initGrid(80, 24);
7 |
8 |
9 |
10 | function lprintf(str, width) {
11 | if (width != null) {
12 | lprcat(padString(str, width));
13 | } else {
14 | lprcat(str);
15 | }
16 | }
17 |
18 |
19 | var START_MARK = ``;
20 | var END_MARK = ``;
21 | var START_BOLD = ``;
22 | var END_BOLD = ``;
23 |
24 |
25 |
26 | function lprint(str) {
27 | lprcat(str);
28 | blt();
29 | }
30 |
31 |
32 |
33 | function lprcat(str, width) {
34 | DEBUG_LPRCAT++;
35 |
36 | if (alternativeDisplay) {
37 | alternativeDisplay += str;
38 | return;
39 | }
40 |
41 | if (width) {
42 | lprintf(str, width);
43 | return;
44 | }
45 |
46 | // Some people, when confronted with a problem, think, “I know,
47 | // I'll use regular expressions.” Now they have two problems.
48 |
49 | var markup = null;
50 | var len = str.length;
51 |
52 | for (var i = 0; i < len; i++) {
53 | var c = str[i];
54 |
55 | if (c === '<') {
56 | if (str.substr(i, 3).toLowerCase() === START_BOLD) {
57 | markup = START_BOLD;
58 | i += 2;
59 | if (amiga_mode) {
60 | continue;
61 | } else {
62 | c = START_BOLD;
63 | }
64 | } else if (str.substr(i, 4).toLowerCase() === END_BOLD) {
65 | markup = null;
66 | i += 3;
67 | if (amiga_mode) {
68 | continue;
69 | } else {
70 | let diff = 1;
71 | if (cursorx > 80) diff = cursorx - 80;
72 | cursorx -= diff;
73 | c = str.substr(i - 3 - diff, diff) + END_BOLD;
74 | }
75 | } else if (str.substr(i, 6).toLowerCase() === START_MARK) {
76 | markup = START_MARK;
77 | i += 5;
78 | if (amiga_mode) {
79 | continue;
80 | } else {
81 | c = START_MARK;
82 | }
83 | } else if (str.substr(i, 7).toLowerCase() === END_MARK) {
84 | markup = null;
85 | i += 6;
86 | if (amiga_mode) {
87 | continue;
88 | } else {
89 | let diff = 1;
90 | if (cursorx > 80) diff = cursorx - 80;
91 | cursorx -= diff;
92 | c = str.substr(i - 6 - diff, diff) + END_MARK;
93 | }
94 | }
95 | }
96 |
97 | lprc(c, markup);
98 |
99 | }
100 | }
101 |
102 |
103 |
104 | function cursor(x, y) {
105 | cursorx = x;
106 | cursory = y;
107 | }
108 |
109 |
110 |
111 | function cursors() {
112 | cursor(1, 24);
113 | }
114 |
115 |
116 |
117 | function lprc(ch, markup) {
118 | DEBUG_LPRC++;
119 |
120 | if (alternativeDisplay) {
121 | alternativeDisplay += ch;
122 | return;
123 | }
124 |
125 | if (ch == '\b') {
126 | cursorx--;
127 | os_put_font(' ', cursorx - 1, cursory - 1);
128 | } else if (ch == '\n') {
129 | cursorx = 1;
130 | cursory++;
131 | } else {
132 | // var n = 1;
133 | // if (ch == '\t') {
134 | // ch = ' ';
135 | // n = 4;
136 | // }
137 | // while (n--) {
138 | os_put_font(ch, cursorx - 1, cursory - 1, markup);
139 | cursorx++;
140 | // }
141 | }
142 | }
143 |
144 |
145 | var HACK_URL_TEXT = `url`;
146 |
147 | function os_put_font(ch, x, y, markup) {
148 | if (x >= 0 && x < 80 && y >= 0 && y < 24) {
149 | if (!amiga_mode) {
150 |
151 | if (DEBUG_PROXIMITY) {
152 | if (!screen[x] || !screen[x][y] || screen[x][y] == 127 || screen[x][y] == 0) {
153 | // do nothing
154 | } else {
155 | if (!monsterAt(x, y)) {
156 | ch = (screen[x][y] < 10) ? `` + screen[x][y] : `` + (screen[x][y] % 10);
157 | }
158 | else {
159 | ch = `${ch}`;
160 | }
161 | }
162 | if (x == player.x && y == player.y) ch = `#`;
163 | }
164 |
165 | display[x][y] = ch;
166 |
167 | // TODO: setup for not repainting in text mode
168 | // TODO: need to update io.js:os_put_font(), display.js:blt(), larn.js:play()
169 | // TODO: this will break scoreboard rendering
170 | if (altrender) {
171 | setChar(x, y, ch, markup);
172 | }
173 |
174 | } else {
175 | ch = `${ch}`; // workaround: amiga larn bank buttons are numbers, not strings
176 |
177 | // HACK HACk HAck Hack hack
178 | if (ch.substring(0, 3) === HACK_URL_TEXT) {
179 | setImage(x, y, ch);
180 | } else {
181 | setChar(x, y, ch, markup);
182 | }
183 | }
184 | }
185 | }
186 |
187 |
188 |
189 | function clear() {
190 | cl_dn(1, 1);
191 | }
192 |
193 |
194 |
195 | function cltoeoln() {
196 | var x = cursorx;
197 | var n = 80 + 1 - x;
198 | while (n-- > 0) {
199 | lprc(' ', null);
200 | setImage(80 - n - 1, cursory - 1, OUNKNOWN.getChar());
201 | }
202 | cursorx = x;
203 | }
204 |
205 |
206 |
207 | function cl_up(x, y) {
208 | for (var i = 1; i <= y; i++) {
209 | cursor(1, i);
210 | cltoeoln();
211 | }
212 | cursor(x, y);
213 | }
214 |
215 |
216 |
217 | function cl_dn(x, y) {
218 | for (var i = y; i <= 24; i++) {
219 | cursor(1, i);
220 | cltoeoln();
221 | }
222 | cursor(x, y);
223 | }
224 |
225 |
226 |
227 | function lflush() {
228 | LOG = Array(LOG_SAVE_SIZE).join(' ').split('');
229 | }
230 |
--------------------------------------------------------------------------------
/src/larn_local.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Play Larn Online - A classic DOS/Amiga/Unix Roguelike Video Game
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | LARN is a dungeon type adventure game similar in concept to HACK, ROGUE or
84 | MORIA, but with a different feel and winning criteria.
85 |
86 | Unfortunately if you're seeing this page it means that there was an error
87 | loading the game.
88 |
89 | This version of Larn requires a browser from circa 2016 to work properly.
90 | Please check to make sure you are running Firefox 53+, Chrome 55+, Edge 15+,
91 | or Safari 11+. Internet Explorer is not supported.
92 |
93 | Sometimes, you'll get this message when a new version is released. You can
94 | usually get the game working again with a full refresh of your browser.
95 | - On Windows: Hold down Ctrl and press F5
96 | - On Mac:
97 | - Chrome & Firefox: Hold down Shift and click the Reload button
98 | - Safari: Hold down the Option and Command key then press the ‘E’ key
99 |
100 | If you're still having trouble, please send a message to eye@larn.org
101 | and I'll get to the bottom of it!
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "larn",
3 | "version": "12.5.2",
4 | "description": "JS Larn",
5 | "main": "larn.js",
6 | "scripts": {
7 | "clean": "npm run tempclean ; npm run distclean",
8 | "tempclean": "rm -rf larn-temp || true",
9 | "distclean": "rm -rf dist || true",
10 | "alphaclean": "rm -rf ../larn-deploy/alpha || true",
11 | "betaclean": "rm -rf ../larn-deploy/beta || true",
12 | "prodclean": "rm -rf ../larn-deploy/larn || true",
13 | "histclean": "rm -rf ../larn-deploy/history || true",
14 | "imgclean": "rm -rf ../larn-deploy/img || true",
15 | "webclean": "rm ../larn-deploy/*.xml ; rm ../larn-deploy/*.png ; rm ../larn-deploy/*.html ; rm ../larn-deploy/*.css ; rm ../larn-deploy/*.txt ; rm ../larn-deploy/*.md || true",
16 | "deployclean": "npm run clean ; npm run alphaclean ; npm run betaclean ; npm run prodclean ; npm run webclean ; npm run histclean ; npm run imgclean",
17 |
18 | "transpile": "babel src --out-dir larn-temp/ ; ./secrets.sh 'larn-temp' || true",
19 | "minify": "uglifyjs --mangle --compress -o larn-temp/larn.min.js larn-temp/common/util.js larn-temp/common/larn_config.js larn-temp/common/frame.js larn-temp/common/patch.js larn-temp/common/roll.js larn-temp/common/lambda.js larn-temp/common/cloudflare.js larn-temp/common/movie.js larn-temp/common/live.js larn-temp/config.js larn-temp/larn.js larn-temp/main.js larn-temp/object.js larn-temp/global.js larn-temp/monster.js larn-temp/monsterdata.js larn-temp/player.js larn-temp/mazes.js larn-temp/level.js larn-temp/create.js larn-temp/data.js larn-temp/parse.js larn-temp/buttons.js larn-temp/scores.js larn-temp/inventory.js larn-temp/movem.js larn-temp/action.js larn-temp/io.js larn-temp/display.js larn-temp/storedata.js larn-temp/store.js larn-temp/mcdopes.js larn-temp/savelev.js larn-temp/spells.js larn-temp/spellsinfo.js larn-temp/regen.js larn-temp/spheres.js larn-temp/help.js larn-temp/state.js larn-temp/bill.js larn-temp/altar.js larn-temp/fountain.js larn-temp/potion.js larn-temp/scroll.js larn-temp/stairs.js larn-temp/throne.js larn-temp/devmode.js larn-temp/fullstory.js larn-temp/gotw.js",
20 | "minifyscore": "uglifyjs --mangle --compress -o larn-temp/score/score.min.js larn-temp/common/util.js larn-temp/common/larn_config.js larn-temp/larn.js larn-temp/player.js larn-temp/data.js larn-temp/object.js larn-temp/state.js larn-temp/inventory.js larn-temp/potion.js larn-temp/scroll.js larn-temp/savelev.js larn-temp/score/score.js",
21 |
22 | "_copy": "echo $envt ; mkdir -p dist/$envt/lib ; mkdir dist/$envt/img ; mkdir dist/$envt/workers ; cp src/lib/*.js dist/$envt/lib ; cp src/img/* dist/$envt/img ; cp src/larn.html dist/$envt ; cp src/larn.css dist/$envt ; cp larn-temp/larn.min.js dist/$envt ; cp larn-temp/workers/*.js dist/$envt/workers || true",
23 | "alphacopy": "export envt=alpha ; npm run _copy",
24 | "betacopy": "export envt=beta ; npm run _copy",
25 | "prodcopy": "export envt=larn ; npm run _copy",
26 | "webcopy": "cp -rv ../larn-www/* ../larn-deploy || true",
27 | "_tvcopy": "mkdir -p dist/$envt/tv/common ; cp larn-temp/tv/* dist/$envt/tv ; cp larn-temp/common/* dist/$envt/tv/common ; cp src/tv/index.html dist/$envt/tv ; cp src/tv/larntv.css dist/$envt/tv || true",
28 | "_scorecopy": "mkdir -p dist/$envt/score ; cp larn-temp/score/* dist/$envt/score ; cp src/score/index.html dist/$envt/score ; cp src/score/score.css dist/$envt/score || true",
29 |
30 | "alphatv": "export envt=alpha ; npm run _tvcopy",
31 | "betatv": "export envt=beta ; npm run _tvcopy",
32 | "prodtv": "export envt=larn ; npm run _tvcopy",
33 |
34 | "alphascore": "export envt=alpha ; npm run _scorecopy",
35 | "betascore": "export envt=beta ; npm run _scorecopy",
36 | "prodscore": "export envt=larn ; npm run _scorecopy",
37 |
38 | "_dist": "echo $envt ; rm -rf ../larn-deploy/$envt ; mv dist/$envt ../larn-deploy || true",
39 | "distalpha": "export envt=alpha ; npm run _dist",
40 | "distbeta": "export envt=beta ; npm run _dist",
41 | "distprod": "export envt=larn ; npm run _dist",
42 | "distweb": "npm run webclean ; npm run webcopy || true",
43 | "dist": "npm run distalpha ; npm run distbeta ; npm run distprod ; npm run distweb || true",
44 |
45 | "buildalpha": "npm run alphaclean ; npm run transpile ; npm run minify ; npm run minifyscore ; npm run alphacopy ; npm run alphatv ; npm run alphascore ; npm run distalpha",
46 | "buildbeta": "npm run betaclean ; npm run transpile ; npm run minify ; npm run minifyscore ; npm run betacopy ; npm run betatv ; npm run betascore ; npm run distbeta",
47 | "buildprod": "npm run prodclean ; npm run transpile ; npm run minify ; npm run minifyscore ; npm run prodcopy ; npm run prodtv ; npm run prodscore ; npm run distprod",
48 |
49 | "build": "echo '=== STARTING BUILD ==' ; npm run distclean ; npm run transpile ; npm run minify ; npm run minifyscore ; npm run prodcopy ; npm run tempclean",
50 | "buildall": "echo '=== STARTING FULL BUILD ==' ; npm run deployclean ; npm run transpile ; npm run minify ; npm run minifyscore ; npm run alphacopy ; npm run betacopy ; npm run prodcopy ; npm run alphatv ; npm run betatv ; npm run prodtv ; npm run alphascore ; npm run betascore ; npm run prodscore ; npm run dist ; npm run clean",
51 |
52 | "watch": "npm run tempclean ; babel src --watch --out-dir larn-temp/",
53 | "localserver": "cd ../larn-local ; python3 -m http.server 8000",
54 | "deployserver": "cd ../larn-deploy ; python3 -m http.server 8001"
55 | },
56 | "repository": {
57 | "type": "git",
58 | "url": "git+https://github.com/primeau/Larn.git"
59 | },
60 | "keywords": [
61 | "larn",
62 | "ularn",
63 | "roguelike",
64 | "game"
65 | ],
66 | "author": "Jason Primeau",
67 | "license": "MIT",
68 | "bugs": {
69 | "url": "https://github.com/primeau/Larn/issues"
70 | },
71 | "homepage": "https://github.com/primeau/Larn#readme",
72 | "dependencies": {},
73 | "devDependencies": {
74 | "@babel/cli": "^7.14.5",
75 | "@babel/core": "^7.14.5",
76 | "@babel/preset-env": "^7.14.5",
77 | "uglify-js": "^3.13.9"
78 | }
79 | }
--------------------------------------------------------------------------------
/src/mcdopes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* inventory */
4 | var drug = [true, true, true, true, true];
5 |
6 |
7 |
8 | /*
9 | function to display the header info for the pad
10 | */
11 | function pad_hd() {
12 | cl_up(1, 18);
13 | cursor(1, 1);
14 | lprcat(`Hey man, welcome to Dealer McDope's Pad! I gots the some of the finest shit\n`);
15 | lprcat(`you'll find anywhere in Ularn -- check it out...\n\n\n`);
16 | lprcat(` The Stash The Cash\n\n`);
17 |
18 | if (drug[0]) lprcat(` a) Killer Speed 100 bucks`);
19 | lprc(`\n`);
20 | if (drug[1]) lprcat(` b) Groovy Acid 250 bucks`);
21 | lprc(`\n`);
22 | if (drug[2]) lprcat(` c) Monster Hash 500 bucks`);
23 | lprc(`\n`);
24 | if (drug[3]) lprcat(` d) Trippy Shrooms 1000 bucks`);
25 | lprc(`\n`);
26 | if (drug[4]) lprcat(` e) Cool Coke 5000 bucks`);
27 | lprc(`\n`);
28 |
29 | let plural = player.GOLD == 1 ? `` : `s`;
30 | cursor(30, 18);
31 | lprcat(`Looks like you got about ${player.GOLD} buck${plural} on you. `);
32 |
33 | lprcat(`\n\nSo, whaddya want [escape to split] ?`);
34 | }
35 |
36 |
37 |
38 | function opad() {
39 | setMazeMode(false);
40 |
41 | // clear();
42 | pad_hd();
43 | setCharCallback(parse_mcdopes);
44 | }
45 |
46 |
47 |
48 | function parse_mcdopes(key) {
49 | if (key == ESC) {
50 | return exitbuilding();
51 | }
52 |
53 | if (!isalpha(key)) return false;
54 |
55 | cursor(39, 20);
56 | lprc(`${key}\n`);
57 |
58 | switch (key) {
59 | case `a`:
60 | /* speed */
61 | dodeal(OSPEED, 100, 0);
62 | break;
63 | case `b`:
64 | /* acid */
65 | dodeal(OACID, 250, 1);
66 | break;
67 | case `c`:
68 | /* hash */
69 | dodeal(OHASH, 500, 2);
70 | break;
71 | case `d`:
72 | /* shrooms */
73 | dodeal(OSHROOMS, 1000, 3);
74 | break;
75 | case `e`:
76 | /* coke */
77 | dodeal(OCOKE, 5000, 4);
78 | break;
79 | } /* end switch */
80 |
81 | pad_hd();
82 | return false;
83 | } /* end pad() */
84 |
85 |
86 |
87 | function dodeal(whichdrug, price, index) {
88 | if (!drug[index]) {
89 | nomore();
90 | return;
91 | }
92 | if (player.GOLD < price) {
93 | nocash();
94 | return;
95 | }
96 | else if (snag(whichdrug)) {
97 | player.GOLD -= price;
98 | drug[index] = false;
99 | }
100 | }
101 |
102 |
103 |
104 | function snag(itm) {
105 | if (pocketfull()) {
106 | lprcat(`\nHey, you can't carry any more.`);
107 | cltoeoln();
108 | return false;
109 | }
110 | let contraband = createObject(itm);
111 | take(contraband);
112 | lprcat(`\nOk, here ya go.`);
113 | cltoeoln();
114 | return true;
115 | }
116 |
117 |
118 |
119 | function nomore() {
120 | lprcat(`\nSorry man, I ain't got no more of that shit.`);
121 | cltoeoln();
122 | // nap(2200);
123 | }
124 |
125 |
126 |
127 | function nocash() {
128 | lprcat(`\nWhattaya trying to pull on me? You aint got the cash!`);
129 | cltoeoln();
130 | // nap(1200);
131 | }
132 |
133 |
134 |
135 | function doSpeed() {
136 | appendLog(` snort!`);
137 | updateLog(`Ohwowmanlikethingstotallyseemtoslowdown!`);
138 | player.updateHasteSelf(200 + player.LEVEL);
139 | player.HALFDAM += 300 + rnd(200);
140 | player.setIntelligence(player.INTELLIGENCE - 2);
141 | player.setWisdom(player.WISDOM - 2);
142 | player.setConstitution(player.CONSTITUTION - 2);
143 | player.setDexterity(player.DEXTERITY - 2);
144 | player.setStrength(player.STRENGTH - 2);
145 | }
146 |
147 |
148 |
149 | function eatShrooms() {
150 | appendLog(` eat!`);
151 | updateLog(`Things start to get real spacey...`);
152 | player.HASTEMONST += rnd(75) + 25;
153 | player.CONFUSE += 30 + rnd(10);
154 | player.setWisdom(player.WISDOM + 2);
155 | player.setCharisma(player.CHARISMA + 2);
156 |
157 | }
158 |
159 |
160 |
161 | function dropAcid() {
162 | appendLog(` eat!`);
163 | updateLog(`You are now frying your ass off!`);
164 | player.CONFUSE += 30 + rnd(10);
165 | player.setIntelligence(player.INTELLIGENCE + 2);
166 | player.setWisdom(player.WISDOM + 2);
167 | player.AWARENESS += 1500;
168 | player.AGGRAVATE += 1500;
169 | // heal monsters
170 | for (let j = 0; j < MAXY; j++)
171 | for (let i = 0; i < MAXX; i++)
172 | if (player.level.monsters[i][j])
173 | player.level.monsters[i][j].hitpoints = monsterlist[player.level.monsters[i][j].arg].hitpoints;
174 | }
175 |
176 |
177 |
178 | function smokeHash() {
179 | appendLog(` smoke!`);
180 | updateLog(`WOW! You feel stooooooned...`);
181 | player.HASTEMONST += rnd(75) + 25;
182 | player.setIntelligence(player.INTELLIGENCE + 2);
183 | player.setWisdom(player.WISDOM + 2);
184 | player.setConstitution(player.CONSTITUTION - 2);
185 | player.setDexterity(player.DEXTERITY - 2);
186 | player.HALFDAM += 300 + rnd(200);
187 | player.CLUMSINESS += rnd(1800) + 200;
188 | }
189 |
190 |
191 |
192 | function doCoke() {
193 | appendLog(` snort!`);
194 | updateLog(`Your nose begins to bleed!`);
195 | player.setDexterity(player.DEXTERITY - 2);
196 | player.setConstitution(player.CONSTITUTION - 2);
197 | player.setCharisma(player.CHARISMA + 3);
198 |
199 | player.setStrength(player.STRENGTH + 33);
200 | player.setIntelligence(player.INTELLIGENCE + 33);
201 | player.setWisdom(player.WISDOM + 33);
202 | player.setConstitution(player.CONSTITUTION + 33);
203 | player.setDexterity(player.DEXTERITY + 33);
204 | player.setCharisma(player.CHARISMA + 33);
205 |
206 | player.COKED += 10;
207 | }
208 |
209 |
210 |
--------------------------------------------------------------------------------
/src/lib/rollbar.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var _rollbarConfig = {
3 | accessToken: "3ff371a699404d4c9ec0d02f09252ee8",
4 | captureUncaught: true,
5 | captureUnhandledRejections: true,
6 | payload: {
7 | environment: "production"
8 | }
9 | };
10 | // Rollbar Snippet
11 | (()=>{"use strict";var r={349:r=>{r.exports={captureUncaughtExceptions:function(r,o,e){if(r){var n;if("function"==typeof o._rollbarOldOnError)n=o._rollbarOldOnError;else if(r.onerror){for(n=r.onerror;n._rollbarOldOnError;)n=n._rollbarOldOnError;o._rollbarOldOnError=n}o.handleAnonymousErrors();var t=function(){var e=Array.prototype.slice.call(arguments,0);!function(r,o,e,n){r._rollbarWrappedError&&(n[4]||(n[4]=r._rollbarWrappedError),n[5]||(n[5]=r._rollbarWrappedError._rollbarContext),r._rollbarWrappedError=null);var t=o.handleUncaughtException.apply(o,n);e&&e.apply(r,n),"anonymous"===t&&(o.anonymousErrorsPending+=1)}(r,o,n,e)};e&&(t._rollbarOldOnError=n),r.onerror=t}},captureUnhandledRejections:function(r,o,e){if(r){"function"==typeof r._rollbarURH&&r._rollbarURH.belongsToShim&&r.removeEventListener("unhandledrejection",r._rollbarURH);var n=function(r){var e,n,t;try{e=r.reason}catch(r){e=void 0}try{n=r.promise}catch(r){n="[unhandledrejection] error getting `promise` from event"}try{t=r.detail,!e&&t&&(e=t.reason,n=t.promise)}catch(r){}e||(e="[unhandledrejection] error getting `reason` from event"),o&&o.handleUnhandledRejection&&o.handleUnhandledRejection(e,n)};n.belongsToShim=e,r._rollbarURH=n,r.addEventListener("unhandledrejection",n)}}}},202:r=>{function o(r,e){this.impl=r(e,this),this.options=e,function(r){for(var o=function(r){return function(){var o=Array.prototype.slice.call(arguments,0);if(this.impl[r])return this.impl[r].apply(this.impl,o)}},e="log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad".split(","),n=0;n{var n=e(349),t=e(965);function a(r){return function(){try{return r.apply(this,arguments)}catch(r){try{console.error("[Rollbar]: Internal error",r)}catch(r){}}}}var l=0;function i(r,o){this.options=r,this._rollbarOldOnError=null;var e=l++;this.shimId=function(){return e},"undefined"!=typeof window&&window._rollbarShims&&(window._rollbarShims[e]={handler:o,messages:[]})}var s=e(202),d=function(r,o){return new i(r,o)},p=function(r){return new s(d,r)};function c(r){return a((function(){var o=Array.prototype.slice.call(arguments,0),e={shim:this,method:r,args:o,ts:new Date};window._rollbarShims[this.shimId()].messages.push(e)}))}i.prototype.loadFull=function(r,o,e,n,t){var l=!1,i=o.createElement("script"),s=o.getElementsByTagName("script")[0],d=s.parentNode;i.crossOrigin="",i.src=n.rollbarJsUrl,e||(i.async=!0),i.onload=i.onreadystatechange=a((function(){if(!(l||this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)){i.onload=i.onreadystatechange=null;try{d.removeChild(i)}catch(r){}l=!0,function(){var o;if(void 0===r._rollbarDidLoad){o=new Error("rollbar.js did not load");for(var e,n,a,l,i=0;e=r._rollbarShims[i++];)for(e=e.messages||[];n=e.shift();)for(a=n.args||[],i=0;i{r.exports=function(r){return function(o){if(!o&&!window._rollbarInitialized){for(var e,n,t=(r=r||{}).globalAlias||"Rollbar",a=window.rollbar,l=function(r){return new a(r)},i=0;e=window._rollbarShims[i++];)n||(n=e.handler),e.handler._swapAndProcessMessages(l,e.messages);window[t]=n,window._rollbarInitialized=!0}}}},965:r=>{function o(r,o,e){if(o.hasOwnProperty&&o.hasOwnProperty("addEventListener")){for(var n=o.addEventListener;n._rollbarOldAdd&&n.belongsToShim;)n=n._rollbarOldAdd;var t=function(o,e,t){n.call(this,o,r.wrap(e),t)};t._rollbarOldAdd=n,t.belongsToShim=e,o.addEventListener=t;for(var a=o.removeEventListener;a._rollbarOldRemove&&a.belongsToShim;)a=a._rollbarOldRemove;var l=function(r,o,e){a.call(this,r,o&&o._rollbar_wrapped||o,e)};l._rollbarOldRemove=a,l.belongsToShim=e,o.removeEventListener=l}}r.exports=function(r,e,n){if(r){var t,a,l="EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(",");for(t=0;t{var r=e(758),o=e(157);_rollbarConfig=_rollbarConfig||{},_rollbarConfig.rollbarJsUrl=_rollbarConfig.rollbarJsUrl||"https://cdn.rollbar.com/rollbarjs/refs/tags/v2.26.4/rollbar.min.js",_rollbarConfig.async=void 0===_rollbarConfig.async||_rollbarConfig.async;var n=r.setupShim(window,_rollbarConfig),t=o(_rollbarConfig);window.rollbar=r.Rollbar,n.loadFull(window,document,!_rollbarConfig.async,_rollbarConfig,t)})()})();
12 | // End Rollbar Snippet
--------------------------------------------------------------------------------
/ULARN_SPOILERS.md:
--------------------------------------------------------------------------------
1 | # Ularn Spoiler Guide (Updated 2020)
2 |
3 | Adapted from Phil Cordier's web site:
4 | http://www.cordier.com/ularn/index.html
5 |
6 |
7 | ## A Note on Lemmings - 2020
8 | Lemmings are annoying, but they have been part of the game for 30+ years and
9 | they aren't being changed now. Your best strategy is to fight them in a
10 | dead-end hallway so they can't reproduce. Also, don't forget that they can
11 | be Rambo's best friend.
12 |
13 | ## Lance of Death & Strategy
14 |
15 | In Larn, the basic strategy was to get enough money to buy a Lance of Death,
16 | usually by getting to dungeon level 10, killing or getting around the demon
17 | lord, and getting and then selling the Eye of Larn. This would usually get
18 | you enough money to buy the Lance from the dnd store. You could then go into
19 | the volcano and kill everything in sight with one hit from the Lance.
20 | Well, the Lance is still deadly to all monsters. Demons, on the other hand,
21 | are merely tickled. And there are a LOT more demons (and relatives) in Ularn
22 | than in Larn.
23 |
24 | ## Dungeon and Volcano levels
25 |
26 | There are now 15 levels to the Dungeon, and 5 to the Volcano. Larn had 10
27 | and 3.
28 |
29 | Levels 11 - 15 of the dungeon contain, in addition to all the other nasty
30 | monsters that show up at low levels:
31 | - level 11: 1 demon lord
32 | - level 12: 2 demon lords
33 | - level 13: 3 demon lords
34 | - level 14: 4 demon lords
35 | - level 15: 5 demon lords + 1 demon prince guarding the Eye of Larn.
36 |
37 | The stairs up from level 15 lead to a dead end. You'll need to find another
38 | way up.
39 |
40 | Levels 1 - 5 of the Volcano contain:
41 | - level 1: 1 demon prince
42 | - level 2: 2 demon princes
43 | - level 3: 3 demon princes
44 | - level 4: 4 demon princes
45 | - level 5: 5 demon princes + 1 God of Hellfire guarding the potion of cure
46 | dianthroritis.
47 |
48 | In addition, there are no stairs from level 3 to 4, or from 4 to 5. The only
49 | way to go down is via a pit or trap door. There is guaranteed at least 1
50 | bottomless pit and bottomless trap door on level 5. The rock on levels 3, 4,
51 | and 5 cannot be destroyed via a vaporize rock spell, or in any other way.
52 |
53 | ## Magic Items
54 |
55 | Many of the following magic items will appear in the game:
56 |
57 | #### Eye of Larn
58 |
59 | The Eye now has a very beneficial magic property. If you are carrying the
60 | Eye, demon lords, demon princes, and the God of Hellfire are visible to you.
61 | Otherwise, they are invisible. They will show as follows:
62 |
63 | Monster Character on screen
64 | - Type I Demon Lord: 1
65 | - Type II DemonLord: 2
66 | - Type III Demon Lord: 3
67 | - Type IV Demon Lord: 4
68 | - Type V Demon Lord: 5
69 | - Type VI Demon Lord: 6
70 | - Type VII Demon Lord: 7
71 | - Demon Prince: 9
72 | - God of Hellfire: 0
73 |
74 | #### Sword of Slashing
75 | The Sword of Slashing is quite strong and light, and is impervious to rust.
76 | It raises dexterity by 5.
77 |
78 | #### Bessman's Flailing Hammer
79 | The Hammer is the strongest weapon in the game (excluding the Lance of
80 | Death). It lowers your intelligence by 10, but raises your dexterity and
81 | strength by 10.
82 |
83 | #### Vorpal Blade
84 | The Vorpal Blade is a good all-around weapon that has a 1/20 chance of
85 | beheading any monster that has a head.
86 |
87 | #### Orb of Enlightenment
88 | Carrying the Orb gives permanent expanded awareness.
89 |
90 | #### Orb of Dragon Slaying
91 | Causes attacks against dragons to be much more effective.
92 |
93 | #### Scarab of Negate Spirit
94 | Spirit Nagas and Poltergeists damage is halved if you have the Scarab of
95 | Negate spirit.
96 |
97 | #### Amulet of Invisibility
98 | Causes the 'inv' spell to last much longer. Also extends see invisible while
99 | being carried.
100 |
101 | #### Cube of Undead Control
102 | Vampires, Wraiths, and Zombies damage is halved you have the Cube of Undead
103 | Control. In addition, they can perform no special attacks upon you, like
104 | draining your experience level.
105 |
106 | #### Device of Theft Prevention
107 | If you have this, Nymphs and Disenchantresses cannot steal anything from
108 | you.
109 |
110 | #### Brass Lamp
111 | A genie lives in the brass lamp. If you are lucky, by rubbing the lamp, the
112 | genie will grant you a spell. Be warned though, the genie does not usually
113 | like to be disturbed, and may react unpleasantly. If the genie disappears
114 | (along with the lamp) without granting you a wish, it is still possible to
115 | find him again. But if you are granted a wish, the genie will not wish to be
116 | disturbed again (you only get one wish).
117 |
118 | #### Hand of Fear
119 | Causes the scare monster spell to last much longer.
120 |
121 | #### Talisman of the Sphere
122 | Normally, demons can dispel spheres of annihilation, disenchantresses can
123 | cancel them, and if you have the spell 'cancel' cast, that may cancel the
124 | sphere too. You can also die by touching the sphere.
125 | Carrying the Talisman revokes all of the above
126 |
127 | #### Wand of Wonder
128 | You will not fall down any pits or trap doors if you are carrying the Wand
129 | of Wonder.
130 |
131 | #### Staff of Power
132 | Carrying the staff of power will cancel any attack by a demon lord,
133 | demon prince, wraith, or vampire 75% of the time. It increases wisdom by 10.
134 |
135 | #### Slayer
136 | Demon lords and demon princes attacks are halved if you are wielding the sword
137 | Slayer. Slayer essentially acts as a Lance of Death, but only against
138 | demons. It is otherwise a good strong weapon against other monsters. You
139 | will only find Slayer somewhere below dungeon level 10, or in the volcano.
140 | It increases intelligence by 10.
141 |
142 | #### Elven Chain
143 | Strong and light, impervious to rust.
144 |
145 | #### Amulet of Life Preservation (added in Ularn 1.6)
146 | Prevents drain life attacks.
147 |
148 | ## New spell
149 | I've added a 'make wall' spell, that creates a wall in the place you
150 | specify. The code is 'mkw'.
151 |
152 | ## Elevators
153 |
154 | Watch out for express elevators. They can be good or bad, depending on which
155 | direction they go (up or down).
156 |
157 | ## Time limit
158 |
159 | The time limit for solving the game has been increased from 300 mobuls to
160 | 400 mobuls.
161 |
162 | ## Dealer McDope's
163 | On request from several unsavory types here at UC Santa Cruz, I added Dealer
164 | McDope's Pad. He will show up usually somewhere in the lower level of the
165 | dungeon. Visiting Dealer McDope's will offer you the chance to buy such
166 | interesting and stimulating items as:
167 | - Killer Speed
168 | - Groovy Acid
169 | - Monster Hash
170 | - Trippy Shrooms
171 | - Cool Coke
172 |
173 | You "use" a drug by dropping it, and then moving on top of it for more
174 | instructions. Drugs have varied effects. They can be fun, but are generally
175 | not worth the trouble.
176 |
177 | ## NOTES
178 | - NEVER NEVER cast 'vpr' when you are next to an altar!
179 | - Interesting things may happen when you sit on a dead throne.
180 | - Ularn hates a cheapskate at the altar.
181 |
--------------------------------------------------------------------------------
/src/spheres.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /* this is the structure for maintaining & moving the spheres of annihilation */
4 | var Sphere = function (x, y, dir, lifetime, lev) {
5 | this.x = x; /* location of the sphere */
6 | this.y = y;
7 | this.level = lev;
8 | this.direction = dir; /* direction sphere is going in */
9 | this.lifetime = lifetime; /* duration of the sphere */
10 | //console.log(`${x} ${y} ${this.level} ${lifetime}`)
11 | };
12 |
13 |
14 |
15 | /*
16 | * newsphere(x,y,dir,lifetime) Function to create a new sphere of annihilation
17 | * int x,y,dir,lifetime;
18 | *
19 | * Enter with the coordinates of the sphere in x,y
20 | * the direction (0-8 diroffx format) in dir, and the lifespan of the
21 | * sphere in lifetime (in turns)
22 | * Returns the number of spheres currently in existence
23 | */
24 | function newsphere(x, y, dir, life, lev) {
25 | if (dir >= 9) dir = 0; /* no movement if direction not found */
26 | if (lev == 0) {
27 | /* don't go out of bounds */
28 | x = vx(x);
29 | y = vy(y);
30 | } else {
31 | if (x < 1) x = 1;
32 | if (x >= MAXX - 1) x = MAXX - 2;
33 | if (y < 1) y = 1;
34 | if (y >= MAXY - 1) y = MAXY - 2;
35 | }
36 |
37 | var monster = monsterAt(x, y);
38 |
39 | if (!isCarrying(OSPHTALISMAN)) { // talisman of the sphere negates many things
40 | if (monster && monster.isDemon()) /* demons dispel spheres */ {
41 | show1cell(x, y); /* show the demon (ha ha) */
42 | cursors();
43 | updateLog(`The ${monster} dispels the sphere!`);
44 | rmsphere(x, y); /* remove any spheres that are here */
45 | return;
46 | }
47 | if (monster && monster.matches(DISENCHANTRESS)) /* disenchantress cancels spheres */ {
48 | cursors();
49 | updateLog(`The ${monster} causes cancellation of the sphere!`);
50 | sphboom(x, y); /* blow up stuff around sphere */
51 | rmsphere(x, y); /* remove any spheres that are here */
52 | return;
53 | }
54 | if (player.CANCELLATION) /* cancellation cancels spheres */ {
55 | cursors();
56 | updateLog(`As the cancellation spell takes effect, you hear a great earth shaking blast!`);
57 | sphboom(x, y); /* blow up stuff around sphere */
58 | rmsphere(x, y); /* remove any spheres that are here */
59 | return;
60 | }
61 | if (player.x == x && player.y == y) /* collision of sphere and player! */ {
62 | cursors();
63 | updateLog(`You have been enveloped by the zone of nothingness!`);
64 | rmsphere(x, y); /* remove any spheres that are here */
65 | //nap(2000);
66 | died(DIED_ANNIHILATED_SELF, false); /* self - annihilated */
67 | return;
68 | }
69 | }
70 |
71 | if (itemAt(x, y).matches(OANNIHILATION)) /* collision of spheres detonates spheres */ {
72 | cursors();
73 | updateLog(`Two spheres of annihilation collide! You hear a great earth shaking blast!`);
74 | sphboom(x, y); /* blow up stuff around sphere */
75 | rmsphere(x, y); /* remove any spheres that are here */
76 | return;
77 | }
78 |
79 | setItem(x, y, OANNIHILATION);
80 |
81 | updateWalls(x, y, 1);
82 |
83 | /* talisman protects monsters too */
84 | if (monster && monster.isCarrying(OSPHTALISMAN)) {
85 | updateLog(`The ${monster} is unaffected by the sphere of annihilation!`);
86 | }
87 | else {
88 | killMonster(x,y);
89 | }
90 |
91 | player.level.know[x][y] = 1;
92 | show1cell(x, y); /* show the new sphere */
93 |
94 | /* one more sphere in the world */
95 | var sp = new Sphere(x, y, dir, life, lev);
96 | spheres.push(sp);
97 |
98 | return;
99 | }
100 |
101 |
102 |
103 | /*
104 | * rmsphere(x,y) Function to delete a sphere of annihilation from list
105 | * int x,y;
106 | *
107 | * Enter with the coordinates of the sphere (on current level)
108 | * Returns the number of spheres currently in existence
109 | */
110 | function rmsphere(x, y) {
111 | for (var i = 0; i < spheres.length; i++) {
112 | var sp = spheres[i];
113 | if (!sp) continue;
114 | if (sp.level == level) {
115 | /* is sphere on this level? */
116 | if (x == sp.x && y == sp.y) /* locate sphere at this location */ {
117 | setItem(x, y, OEMPTY);
118 | // player.level.monsters[x][y] = null;
119 | player.level.know[x][y] = 1;
120 | show1cell(x, y); /* show the now missing sphere */
121 | spheres.splice(i, 1); // remove the sphere from the list
122 | //break;
123 | }
124 | }
125 | }
126 |
127 | /* return number of spheres in the world */
128 | return spheres.length;
129 | }
130 |
131 |
132 |
133 | /*
134 | * sphboom(x,y) Function to perform the effects of a sphere detonation
135 | * int x,y;
136 | *
137 | * Enter with the coordinates of the blast, Returns no value
138 | */
139 | function sphboom(x, y) {
140 | if (player.HOLDMONST) player.updateHoldMonst(-player.HOLDMONST); // set to 0
141 | if (player.CANCELLATION) player.updateCancellation(-player.CANCELLATION); // set to 0
142 | for (var j = Math.max(1, x - 2); j < Math.min(x + 3, MAXX - 1); j++) {
143 | for (var i = Math.max(1, y - 2); i < Math.min(y + 3, MAXY - 1); i++) {
144 | setItem(j, i, OEMPTY);
145 | let monster = monsterAt(j, i);
146 | if (monster && monster.isCarrying(OSPHTALISMAN)) {
147 | updateLog(`The ${monster} is unaffected by the blast!`);
148 | } else {
149 | killMonster(j, i);
150 | }
151 | show1cell(j, i);
152 | if (!isCarrying(OSPHTALISMAN)) { // sphere of talisman protects
153 | if (player.x == j && player.y == i) {
154 | cursors();
155 | updateLog(`You were too close to the sphere!`);
156 | //nap(2000);
157 | died(DIED_ANNIHILATED, false); /* player killed in explosion */
158 | }
159 | }
160 | }
161 | }
162 | updateWalls(x, y, 3);
163 | }
164 |
165 |
166 |
167 | /*
168 | * movsphere() Function to look for and move spheres of annihilation
169 | *
170 | * This function works on the sphere linked list, first duplicating the list
171 | * (the act of moving changes the list), then processing each sphere in order
172 | * to move it. They eat anything in their way, including stairs, volcanic
173 | * shafts, potions, etc, except for upper level demons, who can dispel
174 | * spheres.
175 | * No value is returned.
176 | */
177 | function movsphere() {
178 | for (var i = spheres.length; i >= 0; --i) /* look through sphere list */ {
179 | var sp = spheres[i];
180 | if (!sp) continue;
181 | if (sp.level != level) continue;
182 | if (!itemAt(sp.x, sp.y).matches(OANNIHILATION)) continue; /* not really there */
183 | if (--sp.lifetime < 0) /* has sphere run out of gas? */ {
184 | rmsphere(sp.x, sp.y); /* delete sphere */
185 | continue;
186 | }
187 | switch (rnd(Math.max(7, player.INTELLIGENCE >> 1))) /* time to move the sphere */ {
188 | case 1:
189 | case 2:
190 | /* change direction to a random one */
191 | sp.direction = rnd(8);
192 | // deliberate fall through
193 | // eslint-disable-next-line no-fallthrough
194 | default:
195 | /* move in normal direction */
196 | rmsphere(sp.x, sp.y);
197 | newsphere(sp.x + diroffx[sp.direction], sp.y + diroffy[sp.direction], sp.direction, sp.lifetime, sp.level);
198 | }
199 | }
200 | }
--------------------------------------------------------------------------------
/src/bill.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | var winnerHardlev;
5 | var winnerLetterGender = 'Male';
6 |
7 | function readmail() {
8 | var gold = 0;
9 | winnerHardlev = 0;
10 | try {
11 | var winner = localStorageGetObject(logname);
12 | if (winner) {
13 | winnerLetterGender = winner.gender;
14 | gold = winner.score;
15 | winnerHardlev = winner.hardlev + 1;
16 | }
17 | } catch (error) {
18 | console.error(`readmail: caught ${error}`);
19 | }
20 | letter1(gold);
21 | }
22 |
23 |
24 |
25 | /*
26 | * function to create the tax bill for the user
27 | */
28 | function letter1(gold) {
29 | clear();
30 |
31 | const TAX_OWED = amiga_mode ? `0`: `${Math.round(gold * TAXRATE).toLocaleString()}`;
32 |
33 | lprcat(`From: The LRS (${GAMENAME} Revenue Service)`);
34 | lprcat(` ${lt}lrs@larn.org${gt}`);
35 | lprcat(`\n`);
36 | lprcat(`\nSubject: Undeclared income\n`);
37 |
38 | lprcat(`\n We heard you survived the caverns of ${GAMENAME}. Let me be the`);
39 | lprcat(`\nfirst to congratulate you on your success. It is quite a feat.`);
40 | lprcat(`\nIt must also have been very profitable for you.`);
41 | lprcat(`\n\n The Dungeon Master has informed us that you brought`);
42 | lprcat(`\n${Number(gold).toLocaleString()} gold pieces back with you from your journey. As the`);
43 | lprcat(`\ncounty of ${GAMENAME} is in dire need of funds, we have spared no time`);
44 | lprcat(`\nin preparing your tax bill. You owe ${TAX_OWED} gold pieces as`);
45 | lprcat(`\nof this notice, and is due within 5 days. Failure to pay will`);
46 | lprcat(`\nmean penalties. Once again, congratulations, We look forward`);
47 | lprcat(`\nto your future successful expeditions.\n`);
48 |
49 | cursors();
50 | lprcat(` --- press space to continue ---\n`);
51 | setCharCallback(letter2);
52 |
53 | return (1);
54 | }
55 |
56 |
57 |
58 | function letter2(key) {
59 |
60 | let letter2title = `Champion`;
61 | if (winnerLetterGender === `Male`) letter2title = `Knight`;
62 | if (winnerLetterGender === `Female`) letter2title = `Lady`;
63 |
64 | if (key != ' ') return 0;
65 |
66 | clear();
67 |
68 | lprcat(`From: His Majesty King Wilfred of Larndom`);
69 | lprcat(` ${lt}wilfred@larn.org${gt}`);
70 | lprcat(`\n`);
71 | lprcat(`\nSubject: A noble deed\n`);
72 |
73 | lprcat(`\n I have heard of your magnificent feat, and I, King Wilfred,`);
74 | lprcat(`\nforthwith declare today to be a national holiday. Furthermore,`);
75 | lprcat(`\nhence three days, Ye be invited to the castle to receive the`);
76 | lprcat(`\nhonour of ${letter2title} of the realm. Upon thy name shall it be written. . .`);
77 | lprcat(`\nBravery and courage be yours.`);
78 | lprcat(`\nMay you live in happiness forevermore . . .\n`);
79 |
80 | cursors();
81 | lprcat(` --- press space to continue ---\n`);
82 | setCharCallback(letter3);
83 |
84 | return (1);
85 | }
86 |
87 |
88 |
89 | function letter3(key) {
90 |
91 | let letter3insult = `Jerk`;
92 | if (winnerLetterGender === `Male`) letter3insult = `Bastard`;
93 | if (winnerLetterGender === `Female`) letter3insult = `Bitch`;
94 |
95 | if (key != ' ') return 0;
96 |
97 | clear();
98 |
99 | lprcat(`From: Count Endelford`);
100 | lprcat(` ${lt}endelford@larn.org${gt}`);
101 | lprcat(`\n`);
102 | lprcat(`\nSubject: You ${letter3insult}!\n`);
103 |
104 | lprcat(`\n I heard (from sources) of your journey. Congratulations!`);
105 | lprcat(`\nYou ${letter3insult}! With several attempts I have yet to endure the`);
106 | lprcat(` caves,\nand you, a nobody, makes the journey! From this time`);
107 | lprcat(` onward, bewarned\nupon our meeting you shall pay the price!\n`);
108 |
109 | cursors();
110 | lprcat(` --- press space to continue ---\n`);
111 | setCharCallback(letter4);
112 |
113 | return (1);
114 | }
115 |
116 |
117 |
118 | function letter4(key) {
119 |
120 | if (key != ' ') return 0;
121 |
122 | clear();
123 |
124 | lprcat(`From: Mainair, Duke of Larnty`);
125 | lprcat(` ${lt}mainair@larn.org${gt}`);
126 | lprcat(`\n`);
127 | lprcat(`\nSubject: High Praise\n`);
128 |
129 | lprcat(`\n With a certainty a hero I declare to be amongst us! A nod of`);
130 | lprcat(`\nfavour I send to thee. Me thinks Count Endelford this day of`);
131 | lprcat(`\nright breath'eth fire as of dragon of whom ye are slayer. I`);
132 | lprcat(`\nyearn to behold his anger and jealously. Should ye choose to`);
133 | lprcat(`\nunleash some of thy wealth upon those who be unfortunate, I,`);
134 | lprcat(`\nDuke Mainair, Shall equal thy gift also.\n`);
135 |
136 | cursors();
137 | lprcat(` --- press space to continue ---\n`);
138 | setCharCallback(letter5);
139 |
140 | return (1);
141 | }
142 |
143 |
144 |
145 | function letter5(key) {
146 |
147 | let letter5plea = `person's`;
148 | if (winnerLetterGender === `Male`) letter5plea = `man's`;
149 | if (winnerLetterGender === `Female`) letter5plea = `woman's`;
150 |
151 | if (key != ' ') return 0;
152 |
153 | clear();
154 |
155 | lprcat(`From: St. Mary's Children's Home`);
156 | lprcat(` ${lt}stmarys@larn.org${gt}`);
157 | lprcat(`\n`);
158 | lprcat(`\nSubject: These poor children\n`);
159 |
160 | lprcat(`\n News of your great conquests has spread to all of Larndom.`);
161 | lprcat(`\nMight I have a moment of a great ${letter5plea} time. We here at St.`);
162 | lprcat(`\nMary's Children's Home are very poor, and many children are`);
163 | lprcat(`\nstarving. Disease is widespread and very often fatal without`);
164 | lprcat(`\ngood food. Could you possibly find it in your heart to help us`);
165 | lprcat(`\nin our plight? Whatever you could give will help much.`);
166 | lprcat(`\n(your gift is tax deductible)\n`);
167 |
168 | cursors();
169 | lprcat(` --- press space to continue ---\n`);
170 | setCharCallback(letter6);
171 |
172 | return (1);
173 | }
174 |
175 |
176 |
177 | function letter6(key) {
178 |
179 | let letter6disease = `Cancer`;
180 | if (ULARN) letter6disease = `Dianthroritis`
181 |
182 | if (key != ' ') return 0;
183 |
184 | clear();
185 |
186 | lprcat(`From: The National ${letter6disease} Society of ${GAMENAME}`);
187 | lprcat(` ${lt}nds@larn.org${gt}`);
188 | lprcat(`\n`);
189 | lprcat(`\nSubject: Hope\n`);
190 |
191 | lprcat(`\n Congratulations on your successful expedition. We are sure much`);
192 | lprcat(`\ncourage and determination were needed on your quest. There are`);
193 | lprcat(`\nmany though, that could never hope to undertake such a journey`);
194 | lprcat(`\ndue to an enfeebling disease -- ${letter6disease}. We at the National`);
195 | lprcat(`\n${letter6disease} Society of ${GAMENAME} wish to appeal to your philanthropy in`);
196 | lprcat(`\norder to save many good people -- possibly even yourself a few`);
197 | lprcat(`\nyears from now. Much work needs to be done in researching this`);
198 | lprcat(`\ndreaded disease, and you can help today. Could you please see it`);
199 | lprcat(`\nin your heart to give generously? Your continued good health`);
200 | lprcat(`\ncan be your everlasting reward.\n`);
201 |
202 | cursors();
203 | lprcat(` --- press space to continue ---\n`);
204 |
205 | setCharCallback(setdiff);
206 |
207 | return (1);
208 | }
209 |
--------------------------------------------------------------------------------
/src/altar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | /* For command mode. Perform the act of descecrating an altar */
5 | function desecrate_altar() {
6 | cursors();
7 | if (itemAt(player.x, player.y).matches(OALTAR)) {
8 | act_desecrate_altar();
9 | } else {
10 | updateLog(`I see no altar to desecrate here!`);
11 | }
12 | }
13 |
14 |
15 |
16 | /* For command mode. Perform the act of praying at an altar */
17 | function pray_at_altar() {
18 | cursors();
19 | if (!itemAt(player.x, player.y).matches(OALTAR)) {
20 | updateLog(`I see no altar to pray at here!`);
21 | } else {
22 | updateLog(` How much do you donate? `);
23 | setNumberCallback(act_donation_pray, true);
24 | nomove = 1;
25 | }
26 | }
27 |
28 |
29 |
30 | /*
31 | Perform the actions associated with praying at an altar and giving a
32 | donation.
33 | */
34 | function act_donation_pray(offering) {
35 | if (offering == ESC) {
36 | appendLog(` cancelled${period}`);
37 | nomove = 1;
38 | prayed = 0;
39 | return 1;
40 | }
41 |
42 | if (offering == '*') {
43 | offering = player.GOLD;
44 | }
45 | offering = Number(offering);
46 |
47 | /* Player donates more gold than they have. Loop back around so
48 | player can't escape the altar for free. */
49 | if (offering > player.GOLD) {
50 | updateLog(` You don't have that much!`);
51 | prayed = 0;
52 | dropflag = 1;
53 | return 1;
54 | }
55 |
56 | /* make giving zero gold equivalent to 'just pray'ing. Allows player to
57 | 'just pray' in command mode, without having to add yet another command. */
58 | if (offering == 0) {
59 | // // for testing
60 | // var outcome;
61 | // var cnt = 0;
62 | // while(outcome != 'crumble') {
63 | // outcome = act_just_pray();
64 | // cnt++;
65 | // if (outcome) console.log(cnt, outcome);
66 | // }
67 | act_just_pray();
68 | dropflag = 1;
69 | prayed = 1;
70 | return 1;
71 | }
72 |
73 | if (player.GOLD >= offering) {
74 | prayed = 1;
75 | dropflag = 1;
76 |
77 | var oneTenth = player.GOLD / 10;
78 | player.setGold(player.GOLD - offering);
79 |
80 | if (ULARN) {
81 | // less than 10% of post offering gold
82 | if (offering < (player.GOLD / 10) && rnd(60) < 30) {
83 | updateLog(`Cheapskate! The Gods are insulted by such a tiny offering!`);
84 | forget(); /* remember to destroy the altar */
85 | // ularn does NOT appreciate cheapskates
86 | createmonster(DEMONPRINCE);
87 | player.AGGRAVATE += 1500;
88 | return 1;
89 | }
90 | // less than 10% of original gold
91 | else if (offering < oneTenth || offering < rnd(50)) {
92 | createmonster(makemonst(level + 2));
93 | player.AGGRAVATE += 500;
94 | return 1;
95 | }
96 | //
97 | else {
98 | var p = rund(16);
99 | if (p < 4) {
100 | updateLog(`Thank you${period}`);
101 | return 1;
102 | } else if (p < 6) {
103 | enchantarmor(ENCH_ALTAR);
104 | enchantarmor(ENCH_ALTAR);
105 | } else if (p < 8) {
106 | enchweapon(ENCH_ALTAR);
107 | enchweapon(ENCH_ALTAR);
108 | } else {
109 | act_prayer_heard();
110 | }
111 |
112 | /*
113 | v12.4.5 - prevents our hero from buying too many +AC/WC
114 | */
115 | if (rnd(43) == 13) {
116 | crumble_altar();
117 | }
118 |
119 | return 1;
120 | }
121 | } // end ULARN
122 | else {
123 | //if player gave less than 10% of _original_ gold, make a monster
124 | if (offering < oneTenth || offering < rnd(50)) {
125 | createmonster(makemonst(level + 1));
126 | player.AGGRAVATE += 200;
127 | return 1;
128 | }
129 | if (rnd(101) > 50) {
130 | act_prayer_heard();
131 | return 1;
132 | }
133 | if (rnd(43) == 5) {
134 | enchantarmor(ENCH_ALTAR);
135 | return 1;
136 | }
137 | if (rnd(43) == 8) {
138 | enchweapon(ENCH_ALTAR);
139 | return 1;
140 | }
141 |
142 | /*
143 | v12.4.5 - prevents our hero from buying too many +AC/WC
144 | */
145 | if (rnd(43) == 13) {
146 | crumble_altar();
147 | return 1;
148 | }
149 |
150 | updateLog(` Thank You${period}`);
151 | return 1;
152 |
153 | }
154 | }
155 |
156 | }
157 |
158 |
159 |
160 | /*
161 | Performs the actions associated with 'just praying' at the altar. Called
162 | when the user responds 'just pray' when in prompt mode, or enters 0 to
163 | the money prompt when praying.
164 | */
165 | function act_just_pray() {
166 |
167 | /*
168 | v12.4.5 - prevents our hero from getting too many free +AC/WC
169 | */
170 | if (rnd(43) == 10) {
171 | crumble_altar();
172 | return 'crumble';
173 | }
174 |
175 | if (ULARN) {
176 | var p = rund(100);
177 | if (p < 12) {
178 | createmonster(makemonst(level + 2));
179 | } else if (p < 17) {
180 | enchweapon(ENCH_ALTAR);
181 | } else if (p < 22) {
182 | enchantarmor(ENCH_ALTAR);
183 | } else if (p < 27) {
184 | act_prayer_heard();
185 | } else {
186 | updateLog(` Nothing happens${period}`);
187 | }
188 | } else {
189 | if (rnd(100) < 75) {
190 | updateLog(` Nothing happens${period}`);
191 | } else if (rnd(43) == 10) {
192 | enchantarmor(ENCH_ALTAR);
193 | return 'ac';
194 | } else if (rnd(43) == 10) {
195 | if (player.WIELD) {
196 | updateLog(` You feel your weapon vibrate for a moment${period}`);
197 | }
198 | enchweapon();
199 | return 'wc';
200 | } else {
201 | createmonster(makemonst(level + 1));
202 | }
203 | return;
204 | }
205 |
206 | }
207 |
208 |
209 |
210 | /*
211 | Perform the actions associated with Altar desecration.
212 | */
213 | function act_desecrate_altar() {
214 | if (rnd(100) < 60) {
215 | var monstBoost = (ULARN ? 3 : 2);
216 | createmonster(makemonst(level + monstBoost) + 8);
217 | player.AGGRAVATE += 2500;
218 | } else if (ULARN && rnd(100) < 5) {
219 | player.raiselevel();
220 | } else if (rnd(101) < 30) {
221 | crumble_altar();
222 | } else
223 | updateLog(` Nothing happens${period}`);
224 | return;
225 | }
226 |
227 |
228 | /*
229 | Destroys the Altar
230 | */
231 | function crumble_altar() {
232 | updateLog(` The altar crumbles into a pile of dust before your eyes${period}`);
233 | forget(); /* remember to destroy the altar */
234 | }
235 |
236 |
237 |
238 | /*
239 | Performs the act of ignoring an altar.
240 |
241 | Assumptions: cursors() has been called.
242 | */
243 | function act_ignore_altar(x, y) {
244 | if (rnd(100) < 30) {
245 | var monstBoost = (ULARN ? 2 : 1);
246 | createmonster(makemonst(level + monstBoost), x, y);
247 | player.AGGRAVATE += rnd(450);
248 | } else
249 | updateLog(` Nothing happens${period}`);
250 | return;
251 | }
252 |
253 |
254 |
255 | /*
256 | function to cast a +3 protection on the player
257 | */
258 | function act_prayer_heard() {
259 | updateLog(` You have been heard!`);
260 | var protime;
261 | if (ULARN) {
262 | protime = 800;
263 | } else {
264 | protime = 500;
265 | }
266 | player.updateAltPro(protime); /* protection field */
267 | }
268 |
269 |
270 |
271 | function autoPray() {
272 | if (player.GOLD < 50 || !itemAt(player.x, player.y).matches(OALTAR)) {
273 | return;
274 | }
275 | let donation = `${Math.max(50, Math.ceil(player.GOLD / 10))}`
276 | simulateKeypress('p');
277 | for (let char of donation) {
278 | simulateKeypress(char);
279 | }
280 | simulateKeypress(ENTER);
281 | }
--------------------------------------------------------------------------------
/src/fountain.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | /*
5 | Code to perform the action of drinking at a fountain. Assumes that
6 | cursors() has already been called, and that a check has been made that
7 | the player is actually standing at a live fountain.
8 | */
9 | function act_drink_fountain() {
10 |
11 | if (rnd(1501) < (ULARN ? 4 : 2)) {
12 | var sleepExclaim = ULARN ? `OH MY GOD!! You` : `Oops! You seem to`;
13 | updateLog(` ${sleepExclaim} have caught the dreadful sleep!`);
14 | beep();
15 | died(DIED_DREADFUL_SLEEP, false); /* fell into the dreadful sleep */
16 | return;
17 | }
18 |
19 | var x = rnd(100);
20 |
21 | if (ULARN) {
22 | if (x == 1) {
23 | player.raiselevel();
24 | } else if (x < 11) {
25 | var hitloss = rnd((level << 2) + 2);
26 | updateLog(` Bleah! The water tasted like stale gatorade! You lose ${hitloss} hit point`);
27 | exclaim(hitloss);
28 | lastnum = DIED_BAD_WATER;
29 | player.losehp(hitloss);
30 | } else if (x < 14) {
31 | player.HALFDAM += 200 + rnd(200);
32 | updateLog(` The water makes you vomit${period}`);
33 | } else if (x < 17) {
34 | quaffpotion(createObject(OPOTION, 17), false); /* giant strength, but don't know the potion */
35 | } else if (x < 45) {
36 | updateLog(` Nothing seems to have happened${period}`);
37 | } else if (rnd(3) != 2) {
38 | fntchange(1); /* change char levels upward */
39 | } else {
40 | fntchange(-1); /* change char levels downward */
41 | }
42 | } else {
43 | if (x < 7) {
44 | player.HALFDAM += 200 + rnd(200);
45 | updateLog(` You feel a sickness coming on${period}`);
46 | } else if (x < 13) {
47 | quaffpotion(createObject(OPOTION, 23), false); /* see invisible, but don't know the potion */
48 | } else if (x < 45) {
49 | updateLog(` Nothing seems to have happened${period}`);
50 | } else if (rnd(3) != 2) {
51 | fntchange(1); /* change char levels upward */
52 | } else {
53 | fntchange(-1); /* change char levels downward */
54 | }
55 | }
56 |
57 | if (rnd(12) < 3) {
58 | updateLog(` The fountains bubbling slowly quiets${period}`);
59 | setItem(player.x, player.y, ODEADFOUNTAIN); /* dead fountain */
60 | player.level.know[player.x][player.y] = 0;
61 | }
62 | }
63 |
64 |
65 |
66 | /*
67 | Code to perform the action of washing at a fountain. Assumes that
68 | cursors() has already been called and that a check has been made that
69 | the player is actually standing at a live fountain.
70 | */
71 | function act_wash_fountain() {
72 | if (rnd(100) < 11) {
73 | var hitloss = rnd((level << 2) + 2);
74 | var washExclaim = ` Oh no! The water was foul! You suffer ${hitloss} hit point`;
75 | if (ULARN) washExclaim = ` The water burns like acid! You lose ${hitloss} hit point`;
76 | updateLog(washExclaim);
77 | exclaim(hitloss);
78 | lastnum = DIED_BAD_WATER; /* drank some poisonous water */
79 | player.losehp(hitloss);
80 | } else if (rnd(100) < 29) {
81 | if (ULARN) updateLog(` You are now clean${period}`);
82 | else updateLog(` You got the dirt off!`);
83 |
84 | /* 12.4.5
85 | remove one negative wc/ac point, or remove itchyness
86 | */
87 | if (player.ITCHING) {
88 | player.ITCHING = 1; // interestingly, this was also added in ularn 1.6.3!
89 | } else {
90 | if (rnd(100) < 50) {
91 | enchantarmor(ENCH_FOUNTAIN);
92 | } else {
93 | enchweapon(ENCH_FOUNTAIN);
94 | }
95 | }
96 |
97 | } else if (rnd(100) < 31) {
98 | if (ULARN) updateLog(` This water seems to be hard water! The dirt didn't come off!`);
99 | else updateLog(` This water needs soap -- the dirt didn't come off${period}`);
100 | } else if (rnd(100) < 34 && !isGenocided(WATERLORD)) {
101 | createmonster(WATERLORD); /* make water lord */
102 | } else {
103 | updateLog(` Nothing seems to have happened${period}`);
104 | }
105 |
106 | /* 12.4.5
107 | since getting the dirt off is a good bonus, need to limit it somehow
108 | */
109 | if (rnd(12) < 3) {
110 | updateLog(` The fountains bubbling slowly quiets${period}`);
111 | setItem(player.x, player.y, ODEADFOUNTAIN); /* dead fountain */
112 | player.level.know[player.x][player.y] = 0;
113 | }
114 | }
115 |
116 |
117 |
118 | /*
119 | a subroutine to raise or lower character levels
120 | if x > 0 they are raised if x < 0 they are lowered
121 | */
122 | function fntchange(how) {
123 | how = how / Math.abs(how);
124 | switch (rnd(9)) {
125 | case 1:
126 | updateLog(` Your strength`);
127 | player.setStrength(player.STRENGTH + how);
128 | fch(how);
129 | break;
130 | case 2:
131 | updateLog(` Your intelligence`);
132 | player.setIntelligence(player.INTELLIGENCE + how);
133 | fch(how);
134 | break;
135 | case 3:
136 | updateLog(` Your wisdom`);
137 | player.setWisdom(player.WISDOM + how);
138 | fch(how);
139 | break;
140 | case 4:
141 | updateLog(` Your constitution`);
142 | player.setConstitution(player.CONSTITUTION + how);
143 | fch(how);
144 | break;
145 | case 5:
146 | updateLog(` Your dexterity`);
147 | player.setDexterity(player.DEXTERITY + how);
148 | fch(how);
149 | break;
150 | case 6:
151 | updateLog(` Your charm`);
152 | player.setCharisma(player.CHARISMA + how);
153 | fch(how);
154 | break;
155 | case 7:
156 | var hpchange = rnd(level + 1);
157 | if (how < 0) {
158 | updateLog(` You lose ${hpchange} hit point`);
159 | exclaim(hpchange);
160 | player.losemhp(hpchange);
161 | } else {
162 | updateLog(` You gain ${hpchange} hit point`);
163 | exclaim(hpchange);
164 | player.raisemhp(hpchange);
165 | }
166 | break;
167 |
168 | case 8:
169 | var spellchange = rnd(level + 1);
170 | if (how > 0) {
171 | updateLog(` You just gained ${spellchange} spell`);
172 | exclaim(spellchange);
173 | player.raisemspells(spellchange);
174 | } else {
175 | updateLog(` You just lost ${spellchange} spell`);
176 | exclaim(spellchange);
177 | player.losemspells(spellchange);
178 | }
179 | break;
180 |
181 | case 9:
182 | var xpchange = 5 * rnd((level + 1) * (level + 1));
183 | if (how < 0) {
184 | updateLog(` You just lost ${xpchange} experience point`);
185 | exclaim(xpchange);
186 | player.loseexperience(xpchange);
187 | } else {
188 | updateLog(` You just gained ${xpchange} experience point`);
189 | exclaim(xpchange);
190 | player.raiseexperience(xpchange);
191 | }
192 | break;
193 | }
194 | }
195 |
196 |
197 |
198 | /*
199 | subroutine to process an up/down of a character attribute for ofountain
200 | */
201 | function fch(how) {
202 | if (how < 0) {
203 | appendLog(` went down by one!`);
204 | } else {
205 | appendLog(` went up by one!`);
206 | }
207 | }
208 |
209 |
210 |
211 | function exclaim(num) {
212 | if (num > 1) {
213 | appendLog(`s!`);
214 | } else {
215 | appendLog(`!`);
216 | }
217 | }
218 |
219 |
220 |
221 | /*
222 | For command mode. Perform drinking at a fountain.
223 | */
224 | function drink_fountain() {
225 | var item = itemAt(player.x, player.y);
226 | if (item.matches(ODEADFOUNTAIN)) {
227 | updateLog(`There is no water to drink!`);
228 | } else if (!item.matches(OFOUNTAIN)) {
229 | updateLog(`I see no fountain to drink from here!`);
230 | } else {
231 | act_drink_fountain();
232 | }
233 | return;
234 | }
235 |
236 |
237 |
238 | /*
239 | For command mode. Perform washing (tidying up) at a fountain.
240 | */
241 | function wash_fountain() {
242 | var item = itemAt(player.x, player.y);
243 | if (item.matches(ODEADFOUNTAIN)) {
244 | updateLog(`There is no water to wash in!`);
245 | } else if (!item.matches(OFOUNTAIN)) {
246 | updateLog(`I see no fountain to wash at here!`);
247 | } else {
248 | act_wash_fountain();
249 | }
250 | return;
251 | }
--------------------------------------------------------------------------------
/src/help.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const helppages = [];
4 |
5 | let currentpage = 0;
6 |
7 |
8 |
9 | function parse_help(key) {
10 | nomove = 1;
11 | if (key == ESC) {
12 | return exitbuilding();
13 | } else if (key == ' ') {
14 | print_help();
15 | }
16 | }
17 |
18 |
19 |
20 | function print_help() {
21 | mazeMode = false;
22 |
23 | clear();
24 |
25 | if (++currentpage > helppages.length - 1) {
26 | currentpage = 1;
27 | }
28 | lprcat(helppages[currentpage]);
29 | cursors();
30 | lprcat(` ---- Press space for more help, escape to exit ----`);
31 |
32 | blt();
33 | }
34 |
35 | function initHelpPages() {
36 |
37 | helppages[0] = !GOTW ?
38 | `Welcome to the game of ${GAMENAME}. At this moment, you face a great problem.\n\
39 | Your daughter has contracted a strange disease, and none of your home remedies\n\
40 | seem to have any effect. You sense that she is in mortal danger, and you must\n\
41 | try to save her. Time ago you heard of a land of great danger and opportunity.\n\
42 | Perhaps here is the solution you need.\n\
43 | \n\
44 | It has been said that there once was a great magician who called himself\n\
45 | Polinneaus. Many years ago, after having many miraculous successes, Polinneaus\n\
46 | retired to the caverns of ${GAMENAME}, where he devoted most of his time to the\n\
47 | creation of magic. Rumors have it that one day Polinneaus set out to dispel\n\
48 | an attacking army in a forest some distance to the north. It is believed that\n\
49 | here he met his demise.\n\
50 | \n\
51 | The caverns of ${GAMENAME}, it is thought, must be magnificent in design,\n\
52 | and contain much magic and treasure. One option you have is to undertake a\n\
53 | journey into these caverns.\n\
54 | \n\
55 | \n\
56 | Good Luck! You're going to need it!\n\
57 | `
58 | :
59 | // Subject: A Weekly Ordeal for One So Unworthy\n\
60 | ` Count Endelford's Weekly Dungeon \n\
61 | \n\
62 | From: Count Endelford ${lt}endelford@larn.org${gt}\n\
63 | Subject: Thinking of you\n\
64 | \n\
65 | I have devised a trial for you, worm. Your reputation precedes you though \n\
66 | I find it laughable.\n\
67 | \n\
68 | The rules for this challenge are as follows:\n\
69 | o The contest resets as Sunday falls (23:59 UTC).\n\
70 | o You may play only once. Should you attempt a second journey, expect no mercy.\n\
71 | o Saving progress is forbidden. Only the weak seek refuge in such cowardice.\n\
72 | o Wizard mode is strictly and severely forbidden. Invoke it, and my wrath will\n\
73 | be swift and absolute.\n\
74 | \n\
75 | I present this challenge because I despise you and the opportunity to watch\n\
76 | you fail weekly delights me. I eagerly await your inevitable demise.\n\
77 | \n\
78 | With deepest contempt,\n\
79 | Endelford\n\
80 | `;
81 |
82 | helppages[1] =
83 | ` Help File for The Caverns of ${GAMENAME} \n\
84 | shift+ \n\
85 | y|Y move|run northwest k|K move|run up u|U move|run northeast ↖↑↗ \n\
86 | h|H move|run left . stay here l|L move|run right ← → \n\
87 | b|B move|run southwest j|J move|run down n|N move|run southeast ↙↓↘ \n\
88 | to run \n\
89 | A desecrate an altar < go up stairs \n\
90 | c cast a spell C close a door > go down stairs \n\
91 | d drop an item D drink at a fountain \n\
92 | e eat something E enter a store, dungeon ^ identify a trap \n\
93 | f tidy up at a fountain or volcanic shaft \n\
94 | g get present pack weight ! toggle key hints \n\
95 | i inventory your pockets I list all known items @ toggle auto-pickup \n\
96 | o open a door or chest # toggle inventory \n\
97 | p pray at an altar P give tax status \n\
98 | q quaff a potion Q quit the game { toggle retro fonts \n\
99 | r read a scroll or book R remove gems from throne } toggle between \n\
100 | s sit on a throne S save the game classic, hack, \n\
101 | t take an item T take off armor and amiga graphics \n\
102 | v print program version V view conducts \n\
103 | w wield a weapon W wear armor \n\
104 | z show scores Z teleport yourself ? this help screen \n\
105 | `;
106 |
107 |
108 | helppages[2] =
109 | ` Special Notes \n\
110 | \n\
111 | To drop gold, press '.' after pressing 'd'. When dropping gold, if you\n\
112 | type '*' as your amount, all your gold is dropped.\n\
113 | \n\
114 | In general, typing in '*' means all of what you're interested in. This is true\n\
115 | when visiting the bank, or when contributing at altars.\n\
116 | \n\
117 | When in the store, trading post, school, or home, an escape will get you out.\n\
118 | \n\
119 | When casting a spell, if you need a list of spells you can cast, type space \n\
120 | at any time and the available list of spells will be shown.\n\
121 | \n\
122 | When an inventory list is on the screen from a drop, quaff, read, or similar\n\
123 | command, you can type the letter of the object that you wish to act apon,\n\
124 | without having to type a space to get back to the prompt.\n\
125 | \n\
126 | If NumLock is off, the Keypad functions in the obvious way for movement. Hold\n\
127 | Shift when pressing any direction on the Keypad to run in that direction. The\n\
128 | 5 key on the Keypad is the same as 'stay here', which really means skip your\n\
129 | turn.\n\
130 | `;
131 |
132 | helppages[3] =
133 | ` Background Information for ${GAMENAME} \n\
134 | \n\
135 | Welcome to the game of ${GAMENAME}. At this moment, you face a great problem.\n\
136 | Your daughter has contracted a strange disease, and none of your home remedies\n\
137 | seem to have any effect. You sense that she is in mortal danger, and you must\n\
138 | try to save her. Time ago you heard of a land of great danger and opportunity.\n\
139 | Perhaps here is the solution you need.\n\
140 | \n\
141 | It has been said that there once was a great magician who called himself\n\
142 | Polinneaus. Many years ago, after having many miraculous successes, Polinneaus\n\
143 | retired to the caverns of ${GAMENAME}, where he devoted most of his time to the\n\
144 | creation of magic. Rumors have it that one day Polinneaus set out to dispel\n\
145 | an attacking army in a forest some distance to the north. It is believed that\n\
146 | here he met his demise.\n\
147 | \n\
148 | The caverns of ${GAMENAME}, it is thought, must be magnificent in design,\n\
149 | and contain much magic and treasure. One option you have is to undertake a\n\
150 | journey into these caverns.\n\
151 | \n\
152 | Good Luck! You're going to need it!\n\
153 | `;
154 |
155 | helppages[4] =
156 | ` Explanation of the ${GAMENAME} scoreboard facility \n\
157 | \n\
158 | ${GAMENAME} supports TWO scoreboards, one for winners, and one for deceased\n\
159 | characters. Each player (by the name entered when you start the game)\n\
160 | is allowed one slot on each scoreboard. This design helps ensure that \n\
161 | frequent players of ${GAMENAME} do not hog the scoreboard, and gives more\n\
162 | players a chance for glory. Level of difficulty is also noted on the\n\
163 | scoreboards, and this takes precedence over score for determining what\n\
164 | entry is on the scoreboard. For example: if 'Yar, the Bug Slayer' has\n\
165 | a score of 128003 on the scoreboard at diff 0, then a game at diff 1\n\
166 | and a score of 4112 would replace the previous entry on the scoreboard.\n\
167 | Note that when a player dies, the inventory is stored in the scoreboard\n\
168 | so that everyone can see what items the player had at the time of death.\n\
169 | `;
170 |
171 | }
--------------------------------------------------------------------------------
/src/storedata.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /************************************************/
4 | /* never, ever, never use a code formatter here */
5 | /************************************************/
6 |
7 | var DNDItem = function DNDItem(price, itemId, arg, qty) {
8 | this.price = price;
9 | this.itemId = itemId;
10 | this.arg = arg;
11 | this.qty = qty;
12 | }
13 |
14 | var STORE_INVENTORY; // set in config.js
15 | var MAXITM; // set in config.js now
16 |
17 | const LARN_STORE_INVENTORY = [
18 | /* cost name arg how many */
19 | [20, OLEATHER.id, 0, 3],
20 | [100, OSTUDLEATHER.id, 0, 2],
21 | [400, ORING.id, 0, 2],
22 | [850, OCHAIN.id, 0, 2],
23 | [2200, OSPLINT.id, 0, 1],
24 | [4000, OPLATE.id, 0, 1],
25 | [9000, OPLATEARMOR.id, 0, 1],
26 | [26000, OSSPLATE.id, 0, 1],
27 | [1500, OSHIELD.id, 0, 1],
28 | [20, ODAGGER.id, 0, 3],
29 | [200, OSPEAR.id, 0, 3],
30 | [800, OFLAIL.id, 0, 2],
31 | [1500, OBATTLEAXE.id, 0, 2],
32 | [4500, OLONGSWORD.id, 0, 2],
33 | [10000, O2SWORD.id, 0, 2],
34 | [50000, OSWORD.id, 0, 1],
35 | [165000, OLANCE.id, 0, 1],
36 | [60000, OSWORDofSLASHING.id, 0, 0],
37 | [100000, OHAMMER.id, 0, 0],
38 | [1500, OPROTRING.id, 1, 1],
39 | [850, OSTRRING.id, 1, 1],
40 | [1200, ODEXRING.id, 1, 1],
41 | [1200, OCLEVERRING.id, 1, 1],
42 | [1800, OENERGYRING.id, 0, 1],
43 | [1250, ODAMRING.id, 0, 1],
44 | [2200, OREGENRING.id, 0, 1],
45 | [10000, ORINGOFEXTRA.id, 0, 1],
46 | [2800, OBELT.id, 0, 1],
47 | [4000, OAMULET.id, 0, 1],
48 | [65000, OORBOFDRAGON.id, 0, 0],
49 | [55000, OSPIRITSCARAB.id, 0, 0],
50 | [50000, OCUBEofUNDEAD.id, 0, 0],
51 | [60000, ONOTHEFT.id, 0, 0],
52 | [5900, OCHEST.id, 6, 1],
53 | [2000, OBOOK.id, 8, 1],
54 | [100, OCOOKIE.id, 0, 3],
55 | [200, OPOTION.id, 0, 6],
56 | [900, OPOTION.id, 1, 5],
57 | [5200, OPOTION.id, 2, 1],
58 | [1000, OPOTION.id, 3, 2],
59 | [500, OPOTION.id, 4, 2],
60 | [1500, OPOTION.id, 5, 2],
61 | [700, OPOTION.id, 6, 1],
62 | [300, OPOTION.id, 7, 7],
63 | [2000, OPOTION.id, 8, 1],
64 | [500, OPOTION.id, 9, 1],
65 | [800, OPOTION.id, 10, 1],
66 | [300, OPOTION.id, 11, 3],
67 | [200, OPOTION.id, 12, 5],
68 | [400, OPOTION.id, 13, 3],
69 | [350, OPOTION.id, 14, 2],
70 | [5200, OPOTION.id, 15, 1],
71 | [900, OPOTION.id, 16, 2],
72 | [2000, OPOTION.id, 17, 2],
73 | [2200, OPOTION.id, 18, 4],
74 | [800, OPOTION.id, 19, 6],
75 | [3700, OPOTION.id, 20, 3],
76 | [500, OPOTION.id, 22, 1],
77 | [1500, OPOTION.id, 23, 3],
78 | [1000, OSCROLL.id, 0, 2],
79 | [1250, OSCROLL.id, 1, 2],
80 | [600, OSCROLL.id, 2, 4],
81 | [100, OSCROLL.id, 3, 4],
82 | [1000, OSCROLL.id, 4, 3],
83 | [2000, OSCROLL.id, 5, 2],
84 | [1100, OSCROLL.id, 6, 1],
85 | [5000, OSCROLL.id, 7, 2],
86 | [2000, OSCROLL.id, 8, 2],
87 | [2500, OSCROLL.id, 9, 4],
88 | [200, OSCROLL.id, 10, 5],
89 | [300, OSCROLL.id, 11, 3],
90 | [3400, OSCROLL.id, 12, 1],
91 | [3400, OSCROLL.id, 13, 1],
92 | [3000, OSCROLL.id, 14, 2],
93 | [4000, OSCROLL.id, 15, 2],
94 | [5000, OSCROLL.id, 16, 2],
95 | [10000, OSCROLL.id, 17, 1],
96 | [5000, OSCROLL.id, 18, 1],
97 | [3400, OSCROLL.id, 19, 2],
98 | [2200, OSCROLL.id, 20, 3],
99 | [39000, OSCROLL.id, 21, 0],
100 | [6100, OSCROLL.id, 22, 1],
101 | [30000, OSCROLL.id, 23, 0]
102 | ];
103 |
104 | const ULARN_STORE_INVENTORY = [
105 | /* cost name arg how many */
106 | [20, OLEATHER.id, 0, 3],
107 | [100, OSTUDLEATHER.id, 0, 2],
108 | [400, ORING.id, 0, 2],
109 | [850, OCHAIN.id, 0, 2],
110 | [2200, OSPLINT.id, 0, 1],
111 | [4000, OPLATE.id, 0, 1],
112 | [9000, OPLATEARMOR.id, 0, 1],
113 | [26000, OSSPLATE.id, 0, 1],
114 | [1500, OSHIELD.id, 0, 1],
115 | [50000, OELVENCHAIN.id, 0, 0],
116 | [10000, OORB.id, 0, 0],
117 | [100000, OSLAYER.id, 0, 0],
118 | [20, ODAGGER.id, 0, 3],
119 | [200, OSPEAR.id, 0, 3],
120 | [800, OFLAIL.id, 0, 2],
121 | [1500, OBATTLEAXE.id, 0, 2],
122 | [4500, OLONGSWORD.id, 0, 2],
123 | [10000, O2SWORD.id, 0, 2],
124 | [50000, OSWORD.id, 0, 1],
125 | [200000, OLANCE.id, 0, 1],
126 | [20000, OSWORDofSLASHING.id, 0, 0],
127 | [75000, OHAMMER.id, 0, 0],
128 | [1500, OPROTRING.id, 1, 1],
129 | [850, OSTRRING.id, 1, 1],
130 | [1200, ODEXRING.id, 1, 1],
131 | [1200, OCLEVERRING.id, 1, 1],
132 | [1800, OENERGYRING.id, 0, 1],
133 | [1250, ODAMRING.id, 0, 1],
134 | [2200, OREGENRING.id, 0, 1],
135 | [10000, ORINGOFEXTRA.id, 0, 1],
136 | [2800, OBELT.id, 0, 1],
137 | [4000, OAMULET.id, 0 /*5*/, 1],
138 | [5000, OCUBEofUNDEAD.id, 0, 0],
139 | [6000, ONOTHEFT.id, 0, 0],
140 | [5900, OCHEST.id, 3, 1],
141 | [2000, OBOOK.id, 2, 1],
142 | [100, OCOOKIE.id, 0, 3],
143 | [6660, OHANDofFEAR.id, 0, 0],
144 | [200, OPOTION.id, 0, 6],
145 | [900, OPOTION.id, 1, 5],
146 | [5200, OPOTION.id, 2, 1],
147 | [1000, OPOTION.id, 3, 2],
148 | [500, OPOTION.id, 4, 2],
149 | [1500, OPOTION.id, 5, 2],
150 | [700, OPOTION.id, 6, 1],
151 | [300, OPOTION.id, 7, 7],
152 | [2000, OPOTION.id, 8, 1],
153 | [500, OPOTION.id, 9, 1],
154 | [800, OPOTION.id, 10, 1],
155 | [300, OPOTION.id, 11, 3],
156 | [200, OPOTION.id, 12, 5],
157 | [400, OPOTION.id, 13, 3],
158 | [350, OPOTION.id, 14, 2],
159 | [5200, OPOTION.id, 15, 1],
160 | [900, OPOTION.id, 16, 2],
161 | [2000, OPOTION.id, 17, 2],
162 | [2200, OPOTION.id, 18, 4],
163 | [800, OPOTION.id, 19, 6],
164 | [3700, OPOTION.id, 20, 3],
165 | [500, OPOTION.id, 22, 1],
166 | [1500, OPOTION.id, 23, 3],
167 | [8500, OORBOFDRAGON.id, 0, 0],
168 | [7500, OSPIRITSCARAB.id, 0, 0],
169 | [80000, OVORPAL.id, 0, 0],
170 | [1000, OSCROLL.id, 0, 2],
171 | [1250, OSCROLL.id, 1, 2],
172 | [600, OSCROLL.id, 2, 4],
173 | [100, OSCROLL.id, 3, 4],
174 | [1000, OSCROLL.id, 4, 3],
175 | [2000, OSCROLL.id, 5, 2],
176 | [1100, OSCROLL.id, 6, 1],
177 | [5000, OSCROLL.id, 7, 2],
178 | [2000, OSCROLL.id, 8, 2],
179 | [2500, OSCROLL.id, 9, 4],
180 | [200, OSCROLL.id, 10, 5],
181 | [300, OSCROLL.id, 11, 3],
182 | [3400, OSCROLL.id, 12, 1],
183 | [3400, OSCROLL.id, 13, 1],
184 | [3000, OSCROLL.id, 14, 2],
185 | [4000, OSCROLL.id, 15, 2],
186 | [5000, OSCROLL.id, 16, 2],
187 | [10000, OSCROLL.id, 17, 1],
188 | [5000, OSCROLL.id, 18, 1],
189 | [3400, OSCROLL.id, 19, 2],
190 | [2200, OSCROLL.id, 20, 3],
191 | [39000, OSCROLL.id, 21, 0],
192 | [6100, OSCROLL.id, 22, 1],
193 | [30000, OSCROLL.id, 23, 0],
194 | [3000, OSPHTALISMAN.id, 0, 0],
195 | [1500, OWWAND.id, 0, 0],
196 | [500, OBRASSLAMP.id, 0, 0],
197 | [95000, OPSTAFF.id, 0, 0],
198 | [100000, OLIFEPRESERVER.id, 0, 0]
199 | ];
200 |
--------------------------------------------------------------------------------
/src/tv/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | let lambda;
4 |
5 | let GAMENAME = `TV`;
6 | let ULARN = false; // this is a hack to get around localstoragegetobject issues
7 |
8 | const TV_CHANNEL_LIST = `list`;
9 | const TV_CHANNEL_RECORDED = `recorded`;
10 | const TV_CHANNEL_LIVE = `live`;
11 | let TV_CHANNEL;
12 |
13 | let frameCompressionWorker; /* web worker to compress live frames */
14 |
15 | go();
16 |
17 |
18 |
19 | async function go() {
20 | initRollbar();
21 | initAWS();
22 | console.log(`build`, BUILD);
23 | console.log(`cloudflare`, CF_BROADCAST_HOST);
24 |
25 | const urlParams = loadURLParameters();
26 | window.addEventListener('resize', onResize);
27 |
28 | // WORKER STEP 0 - initialization
29 | if (window.Worker) {
30 | frameCompressionWorker = new Worker('../workers/compressionWorker.js');
31 | frameCompressionWorker.onmessage = frameCompressionCallback;
32 | }
33 |
34 | await loadFonts();
35 |
36 | if (urlParams.gameid) {
37 | TV_CHANNEL = TV_CHANNEL_RECORDED;
38 | watchRecorded(urlParams.gameid);
39 | } else if (urlParams.live) {
40 | TV_CHANNEL = TV_CHANNEL_LIVE;
41 | const logname = localStorageGetObject('logname', '-');
42 | initCloudFlare(`larntv:${logname}:${rund(1000000)}`, urlParams.live, bltLiveFrame);
43 | watchLive(urlParams.live);
44 | } else {
45 | TV_CHANNEL = TV_CHANNEL_LIST;
46 | initList();
47 | downloadRecordings(recordedGamesLoaded, MIN_FRAMES_TO_LIST);
48 | downloadliveGamesList(liveGamesLoaded);
49 | }
50 |
51 | onResize();
52 | }
53 |
54 |
55 |
56 | function initRollbar() {
57 | try {
58 | let enableRollbar = !isLocal() && !isFile();
59 | if (Rollbar) Rollbar.configure({
60 | enabled: (enableRollbar),
61 | payload: {
62 | code_version: BUILD,
63 | client: {
64 | javascript: {
65 | code_version: BUILD,
66 | }
67 | }
68 | }
69 | });
70 | } catch (error) {
71 | console.log(`caught`, error);
72 | }
73 | }
74 |
75 |
76 |
77 | function initAWS() {
78 | try {
79 | initLambdaCredentials();
80 | lambda = new AWS.Lambda({
81 | region: 'us-east-1',
82 | apiVersion: '2015-03-31'
83 | });
84 | } catch (error) {
85 | console.error(`go(): not loading aws credentials: ${error}`);
86 | }
87 | }
88 |
89 |
90 |
91 | function setIP(ip) {
92 | // do nothing for larntv
93 | }
94 |
95 |
96 |
97 | function onResize() {
98 |
99 | switch (TV_CHANNEL) {
100 |
101 | case TV_CHANNEL_LIST: {
102 | let spriteWidth = computeSpriteWidth();
103 | let widthMultiple = 1.66;
104 | let fontSize = spriteWidth * widthMultiple;
105 | let fontFamily = `Courier New`;
106 | let font = `${fontSize}px ${fontFamily}`;
107 | document.body.style.font = font;
108 | break;
109 | }
110 |
111 | case TV_CHANNEL_RECORDED: {
112 | if (video && video.currentFrameNum && video.currentFrameNum >= 0) {
113 | bltRecordedFrame(video.getCurrentFrame());
114 | } else {
115 | return;
116 | }
117 | break;
118 | }
119 |
120 | case TV_CHANNEL_LIVE: {
121 | if (liveFrameCache) {
122 | bltLiveFrame(liveFrameCache);
123 | }
124 | break;
125 | }
126 |
127 | default: {
128 | console.error(`unknown channel, have you tried adjusting the antenna?`);
129 | }
130 | }
131 |
132 | }
133 |
134 |
135 | let bltFrameCache = -1;
136 | function bltFrame(frame) {
137 | if (!frame) return;
138 |
139 | if (frame.compressed) {
140 | decompressFrame(frame);
141 | // console.log(`bltFrame(): decompressed`, frame.id);
142 | }
143 |
144 | let larnDivs = frame.divs.LARN;
145 |
146 | if (frame.deflated) {
147 | larnDivs = inflate(larnDivs);
148 | }
149 |
150 | if (larnDivs === ``) frame.divs.LARN = EMPTY_LARN_FRAME;
151 |
152 | // TODO: if bltFrameCache is set to a value it prevents rewinding
153 | if (frame.id > bltFrameCache) {
154 | // if (frame.ts > bltFrameCache) {
155 | setDiv(`TV_LARN`, larnDivs);
156 | setDiv(`TV_STATS`, frame.divs.STATS);
157 | // bltFrameCache = frame.id;
158 | //bltFrameCache = frame.ts;
159 |
160 | // compressFrame(frame, true); // handled by frameCompressionJob
161 | } else {
162 | console.log(`bltFrame(): out of order frame`, frame.id, bltFrameCache);
163 | // console.log(`bltFrame(): out of order frame`, frame.ts, bltFrameCache);
164 | return;
165 | }
166 |
167 | // for amiga mode
168 | let sw = computeSpriteWidth();
169 | document.querySelectorAll(`.image`).forEach((div) => {
170 | // set new width and height
171 | div.style.width = `${sw}px`;
172 | div.style.height = `${sw * 2}px`;
173 | // adjust background image location
174 | div.style.backgroundImage = div.style.backgroundImage.replace(/img\//, `../img/`);
175 | }); // this takes ~0.003 seconds on an m1 mac...
176 | }
177 |
178 |
179 |
180 | // shares most code with setMode(amiga, retro, original)
181 | // todo: consolidate, probable with some sort of a "setFont()" option
182 | function setStyle(styleIn) {
183 | if (!styleIn) {
184 | // console.log(`setStyle(): no style data available`);
185 | styleIn = { fontFamily: `Courier New` };
186 | // return;
187 | }
188 |
189 | // defaults for courier new
190 | let fontFamily = `Courier New`;
191 | let textColour = `lightgrey`;
192 | let heightMultiple = 1.88;
193 | let letterSpacing = `normal`;
194 | let spacing = 0;
195 | let fontSize = 10;
196 |
197 | let spriteWidth = computeSpriteWidth();
198 |
199 | if (styleIn.fontFamily.includes(`amiga`)) {
200 | fontFamily = styleIn.fontFamily;
201 | textColour = `lightgrey`;
202 | heightMultiple = 2;
203 | letterSpacing = `normal`;
204 | spacing = 0;
205 | fontSize = spriteWidth * 2;
206 | }
207 | else {
208 | const testfont = `12px ${styleIn.fontFamily}`;
209 | const testtext = `ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
210 | const isBoldWider = getTextWidth(testtext, testfont, true) != getTextWidth(testtext, testfont, false);
211 | fontFamily = isBoldWider ? `Courier New` : styleIn.fontFamily;
212 |
213 | // console.log(testfont, isBoldWider, getTextWidth(testtext, testfont, true), getTextWidth(testtext, testfont, false));
214 |
215 | if (fontFamily === `modern`) {
216 | fontFamily = `modern`;
217 | textColour = `lightgrey`;
218 | heightMultiple = 1.88;
219 | letterSpacing = `normal`;
220 | spacing = 0;
221 | } else if (fontFamily === `dos437`) {
222 | fontFamily = `dos437`;
223 | textColour = `#ABABAB`;
224 | heightMultiple = 1.93;
225 | letterSpacing = '-1px';
226 | spacing = -1;
227 | }
228 | fontSize = computeFontSize(fontFamily, spriteWidth, spacing);
229 | }
230 |
231 | let font = `${fontSize}px ${fontFamily}`;
232 | document.body.style.font = font;
233 | document.body.style.fontFamily = fontFamily;
234 | document.body.style.color = textColour;
235 | document.body.style.letterSpacing = letterSpacing;
236 |
237 | if (TV_CHANNEL_LIST) {
238 | let bar = document.getElementById('progressbar');
239 | if (bar) bar.style.font = `${fontSize}px Courier New`;
240 | let box = document.getElementById('realtime');
241 | if (box) box.style.font = `${fontSize}px Courier New`;
242 | if (box && box.label) box.label.style.font = `${fontSize}px Courier New`;
243 | }
244 | // console.log(font, fontFamily, textColour, letterSpacing, heightMultiple);
245 |
246 | // do this last for some reason
247 | document.body.style.lineHeight = `${spriteWidth * heightMultiple}px`;
248 | }
249 |
250 |
251 |
252 | // copied & adapted from display.js
253 | function computeSpriteWidth() {
254 | let browserWidth = window.innerWidth;
255 | let browserHeight = window.innerHeight;
256 |
257 | /*
258 | "a) a magic potion of cure dianthroritis" -> 39 characters
259 | width: 80 for game area, 39 for side inventory
260 | height: 24 for game area, 6 for the playback buttons and scrollbar
261 | */
262 | let rawSpriteW = (browserWidth) / (80 + 39);
263 | let rawSpriteH = (browserHeight) / (24 + 6);
264 |
265 | let spriteWidth = Math.min(rawSpriteW, rawSpriteH / 2);
266 | spriteWidth *= 10;
267 | spriteWidth = Math.floor(spriteWidth);
268 | spriteWidth /= 10;
269 |
270 | return Math.max(4, spriteWidth);
271 | }
272 |
273 |
274 |
275 | function isAmigaMode() {
276 | if (recordedMetadata && recordedMetadata.fontFamily) {
277 | return recordedMetadata.fontFamily.includes(`amiga`);
278 | } else {
279 | return false;
280 | }
281 | }
--------------------------------------------------------------------------------