From 19abccfcc422e7e0cd012e653b3836b2b7752a5f Mon Sep 17 00:00:00 2001 From: Michael Townsend Date: Wed, 3 Jul 2013 07:56:13 -0700 Subject: [PATCH] Adding ADR to Github --- .project | 11 + browserWarning.html | 27 + css/main.css | 497 ++++++ css/outside.css | 64 + css/path.css | 66 + css/room.css | 79 + css/ship.css | 7 + css/space.css | 134 ++ css/world.css | 74 + doc/Events.xlsx | Bin 0 -> 10337 bytes doc/Zones.txt | 5 + favicon.ico | Bin 0 -> 318 bytes img/Logo1.jpg | Bin 0 -> 36459 bytes img/adr.png | Bin 0 -> 5648 bytes img/chrome.png | Bin 0 -> 6647 bytes img/firefox.png | Bin 0 -> 9395 bytes img/ie.png | Bin 0 -> 5042 bytes index.html | 77 + lib/jquery.color-2.1.2.min.js | 2 + mobileWarning.html | 23 + script/Button.js | 86 ++ script/engine.js | 550 +++++++ script/events.js | 735 +++++++++ script/events/encounters.js | 325 ++++ script/events/global.js | 65 + script/events/outside.js | 127 ++ script/events/room.js | 506 ++++++ script/events/setpieces.js | 2730 +++++++++++++++++++++++++++++++++ script/header.js | 28 + script/notifications.js | 58 + script/outside.js | 611 ++++++++ script/path.js | 280 ++++ script/room.js | 1058 +++++++++++++ script/ship.js | 165 ++ script/space.js | 453 ++++++ script/world.js | 825 ++++++++++ 36 files changed, 9668 insertions(+) create mode 100644 .project create mode 100644 browserWarning.html create mode 100644 css/main.css create mode 100644 css/outside.css create mode 100644 css/path.css create mode 100644 css/room.css create mode 100644 css/ship.css create mode 100644 css/space.css create mode 100644 css/world.css create mode 100644 doc/Events.xlsx create mode 100644 doc/Zones.txt create mode 100644 favicon.ico create mode 100644 img/Logo1.jpg create mode 100644 img/adr.png create mode 100644 img/chrome.png create mode 100644 img/firefox.png create mode 100644 img/ie.png create mode 100644 index.html create mode 100644 lib/jquery.color-2.1.2.min.js create mode 100644 mobileWarning.html create mode 100644 script/Button.js create mode 100644 script/engine.js create mode 100644 script/events.js create mode 100644 script/events/encounters.js create mode 100644 script/events/global.js create mode 100644 script/events/outside.js create mode 100644 script/events/room.js create mode 100644 script/events/setpieces.js create mode 100644 script/header.js create mode 100644 script/notifications.js create mode 100644 script/outside.js create mode 100644 script/path.js create mode 100644 script/room.js create mode 100644 script/ship.js create mode 100644 script/space.js create mode 100644 script/world.js diff --git a/.project b/.project new file mode 100644 index 000000000..d6959e118 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + A Dark Room + + + + + + + + diff --git a/browserWarning.html b/browserWarning.html new file mode 100644 index 000000000..cb6f70ccd --- /dev/null +++ b/browserWarning.html @@ -0,0 +1,27 @@ + + + + A Dark Room + + + +
+ + A Dark Room makes use of HTML5 and CSS3, which your current browser does not appear to support.
+ Please update your browser for the best experience:
+
+ Firefox + Firefox + Firefox +

+ Or you can play anyway, but it probably won't work! +
+ + \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 000000000..9ea256f39 --- /dev/null +++ b/css/main.css @@ -0,0 +1,497 @@ +/* Fonts */ +body, .tooltip { + font-family: "Times New Roman", Times, serif; + font-size: 16px; + font-weight: normal; + line-height: normal; + letter-spacing: normal; +} + +html { + height: 100%; +} + +body { + height: 100%; + margin: 0; +} + +/* Framework stuff */ + +div.clear { + clear: both; +} + +div#wrapper { + margin: auto; + width: 700px; + padding: 20px 0 0 220px; + position: relative; +} + +div#saveNotify { + position: absolute; + top: 20px; + right: 0px; + background: white; + opacity: 0; +} + +div#content { + position: relative; + overflow: hidden; + height: 700px; +} + +div#header { + padding-bottom: 20px; + height: 20px; +} + +.deleteSave { + position: absolute; + right: 10px; + bottom: 10px; + cursor: pointer; +} + +.deleteSave:hover, .share:hover { + text-decoration: underline; +} + +.share { + position: absolute; + right: 70px; + bottom: 10px; + cursor: pointer; +} + +div.headerButton { + font-size: 18px; + cursor: pointer; + float: left; + border-left: 1px solid black; + margin-left: 10px; + padding-left: 10px; +} + +div.headerButton:hover { + text-decoration: underline; +} + +div.headerButton:first-child { + border-left: none; + margin-left: 0px; + padding-left: 0px; +} + +div.headerButton.selected, div.headerButton.selected:hover { + cursor: default; + text-decoration: underline; +} + +div#outerSlider { + position: absolute; +} + +div#outerSlider > div { + position: relative; + float: left; + width: 700px; + height: 700px; + overflow: hidden; +} + +div#locationSlider { + position: absolute; +} + +div.location { + position: relative; + float: left; + width: 700px; +} + +div.row_key { + clear: both; + float: left; +} + +div.row_val { + float: right; +} + +/* Notifications */ + +div#notifications { + position: absolute; + top: 20px; + left: 0px; + height: 700px; + width: 200px; + overflow: hidden; +} + +div#notifications div.notification { + margin-bottom: 10px; +} + +div#notifyGradient { + position: absolute; + top: 0px; + left: 0px; + height: 100%; + width: 100%; + background-color: white; + background: -webkit-linear-gradient( + rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100% + ); + background: linear-gradient( + rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100% + ); + filter: alpha( + Opacity=0, FinishOpacity=100, Style=1, StartX=0, StartY=0, FinishX=0, FinishY=500 + ); +} + +/* Button */ + +div.button { + position: relative; + text-align: center; + border: 1px solid black; + width: 100px; + margin-bottom: 5px; + padding: 5px 10px; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.button:hover { + text-decoration: underline; +} + +div.button.disabled, div.button.disabled:hover { + cursor: default; + border-color: grey; + color: grey; + text-decoration: none; +} + +div.button div.cooldown { + position: absolute; + top: 0px; + left: 0px; + z-index: -1; + height: 100%; + background-color: #DDDDDD; +} + +/* Up/Down buttons. They're complicated! */ + +.upBtn, .dnBtn { + position: absolute; + width: 14px; + height: 15px; + right: 0px; + cursor: pointer; +} + +.upBtn.disabled, .dnBtn.disabled { + cursor: default; +} + +.upBtn { + top: -2px; +} + +.upBtn:after, .upBtn:before { + position: absolute; + border: medium solid transparent; + content: " "; + height: 0; + width: 0; + bottom: 4px; +} + +.upBtn:after { + border-color: transparent transparent white; +} + +.upBtn:before { + border-color: transparent transparent black; +} + +.upBtn.disabled:before { + border-color: transparent transparent #999; +} + + +.dnBtn { + bottom: -3px; +} + +.dnBtn:after, .dnBtn:before { + position: absolute; + border: medium solid transparent; + content: " "; + height: 0; + width: 0; + top: 4px; +} + +.upBtn:after, .dnBtn:after { + border-width: 3px; + left: 50%; + margin-left: -3px; +} + +.upBtn:before, .dnBtn:before { + border-width: 5px; + left: 50%; + margin-left: -5px; +} + +.dnBtn:after { + border-color: white transparent transparent; +} + +.dnBtn:before { + border-color: black transparent transparent; +} + +.dnBtn.disabled:before { + border-color: #999 transparent transparent; +} + +div.button div.tooltip { + width: 100px; +} + +/* Tooltip */ + +div.tooltip { + display: none; + padding: 2px 5px; + border: 1px solid black; + position: absolute; + box-shadow: -1px 3px 2px #666; + background: white; + z-index: 999; +} + +.tooltip.bottom { + top: 30px; +} + +.tooltip.right { + left: 2px; +} + +.tooltip.left { + right: 0px; +} + +.tooltip.top { + bottom: 20px; +} + +*:hover > div.tooltip { + display: block; +} + +div.tooltip:hover { + display: none !important; +} + +.disabled:hover > div.tooltip, .button.free:hover > div.tooltip { + display: none; +} + +#event .button.disabled:hover > div.tooltip { + display: block; +} + +/* Events */ + +.eventPanel { + background: none repeat scroll 0 0 white; + border: 2px solid transparent; + left: 250px; + padding: 20px; + position: absolute; + top: 90px; + width: 335px; + z-index: 20; +} + +body.noMask .eventPanel { + background-color: black; +} + +.eventPanel:before { + background-color:white; + opacity: 0.6; + content: " "; + height: 700px; + left: -252px; + position: absolute; + top: -75px; + width: 920px; + z-index: -2; +} + +body.noMask .eventPanel:before { + opacity: 0; +} + +.eventPanel:after { + position: absolute; + top: -2px; + left: -2px; + width: 100%; + height: 100%; + content: " "; + border: 2px solid black; + box-shadow: 5px 5px 5px #666666; + z-index: -2; +} + +body.noMask .eventPanel:after { + border-color: white; +} + +.eventPanel .button { + float:left; + margin-right: 20px; +} + +body.noMask .eventPanel .button { + border-color: white; + color: white; +} + +.eventTitle { + display: inline-block; + font-weight: bold; + position: absolute; + top: -12px; +} + +body.noMask .eventTitle { + color: white; +} + +.eventTitle:after { + background-color: white; + bottom: 32%; + content: " "; + height: 5px; + left: 0; + position: absolute; + width: 100%; + z-index: -1; +} + +body.noMask .eventTitle:after { + background-color: black; +} + +#description { + position: relative; + min-height: 100px; +} + +body.noMask #description { + color: white; +} + +#description > div { + padding-bottom: 20px; +} + +#buttons > .button { + margin: 0 5px 5px; +} + +/* Combat! */ +#description div.fighter { + padding: 0px; + position: absolute; + bottom: 15px; +} + +#wanderer { + left: 25%; +} + +#enemy { + right: 25%; +} + +.hp { + position: absolute; + top: -15px; + margin-left: -50%; +} + +#description .bullet { + padding: 0px 20px 0px 20px; + bottom: 25px; + position: absolute; + height: 1px; + line-height: 1px; +} + +.damageText { + position: absolute; + bottom: 15px; + left: 50%; + margin-left: -50%; +} + +#lootButtons { + padding-bottom: 0px !important; + margin: 20px 0 0 5px; + position: relative; +} + +#lootButtons:before { + content: "take:"; + position: absolute; + top: -25px; + left: 0px; +} + +#dropMenu { + background: none repeat scroll 0 0 white; + border: 1px solid black; + position: absolute; + z-index: 100; + padding-top: 5px; + text-align: left; + box-shadow: -1px 3px 2px #666; + cursor: default; +} + +#dropMenu:before { + content: "drop:"; + border-bottom: 1px solid black; + display: block; + margin-bottom: 5px; + padding: 0px 0px 5px 5px; +} + +#dropMenu > div { + padding: 0px 5px 5px 5px; + cursor: pointer; +} + +#dropMenu > div:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/css/outside.css b/css/outside.css new file mode 100644 index 000000000..135a13806 --- /dev/null +++ b/css/outside.css @@ -0,0 +1,64 @@ +div#village { + position: absolute; + top: 0px; + right: 0px; + border: 1px solid black; + cursor: default; + padding: 5px 10px; + width: 200px; +} + +div#population { + position: absolute; + top: -13px; + right: 10px; + background-color: white; +} + +.noHuts #population { + display: none; +} + +div#village:before { + position: absolute; + background: white; + content: "village"; + left: 8px; + top: -13px; +} + +div#village.noHuts:before { + content: "forest"; +} + +div#workers { + position:absolute; + top: -4px; + left: 160px; + width: 150px; +} + +.workerRow > .row_val { + position: relative; + padding-right: 20px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.workerRow { + position: relative; + margin: 10px 0px; + cursor: default; +} + +.workerRow .tooltip { + width: 150px; +} + +div.storeRow div.tooltip { + width: 160px; +} \ No newline at end of file diff --git a/css/path.css b/css/path.css new file mode 100644 index 000000000..24faecd6d --- /dev/null +++ b/css/path.css @@ -0,0 +1,66 @@ +#outfitting { + position: relative; + border: 1px solid black; + width: 200px; + margin-bottom: 20px; + padding: 5px 10px; +} + +div#outfitting:before { + position: absolute; + content: "supplies"; + top: -13px; + background-color: white; +} + +div.outfitRow { + position: relative; + cursor: default; + margin: 10px -30px 10px 0px; +} + +div.outfitRow > .row_val { + padding-right: 30px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.outfitRow .tooltip { + width: 150px; +} + +div#bagspace { + background-color: white; + position: absolute; + top:-13px; + right: 10px; +} + +div#perks { + position: absolute; + top: 0px; + right: 0px; + border: 1px solid black; + cursor: default; + padding: 5px 10px; + width: 200px; +} + +div#perks:before { + position: absolute; + content: "perks"; + top: -13px; + background-color: white; +} + +div.perkRow { + position: relative; +} + +div.perkRow .row_key { + float: none; +} \ No newline at end of file diff --git a/css/room.css b/css/room.css new file mode 100644 index 000000000..8641bc7d8 --- /dev/null +++ b/css/room.css @@ -0,0 +1,79 @@ +div#buildBtns { + position: absolute; + top: 50px; + left: 0px; +} + +div#buildBtns:before { + content: "build:"; + position: relative; + top: -5px; +} + +div#craftBtns { + position: absolute; + top: 50px; + left: 150px; +} + +div#craftBtns:before { + content: "craft:"; + position: relative; + top: -5px; +} + +div#buyBtns { + position: absolute; + top: 50px; + left: 300px; +} + +div#buyBtns:before { + content: "buy:"; + position: relative; + top: -5px; +} + +div#storesContainer { + position: absolute; + top: 0px; + right: 0px; +} + +div#stores { + position: relative; + border: 1px solid black; + cursor: default; + padding: 5px 10px; + width: 200px; +} + +div.storeRow { + position: relative; +} + +div#stores:before { + position: absolute; + background: white; + content: "stores"; + left: 8px; + top: -13px; +} + +div#weapons { + margin-top: 15px; + position: relative; + right: 0px; + border: 1px solid black; + cursor: default; + padding: 5px 10px; + width: 200px; +} + +div#weapons:before { + position: absolute; + background: white; + content: "weapons"; + left: 8px; + top: -13px; +} \ No newline at end of file diff --git a/css/ship.css b/css/ship.css new file mode 100644 index 000000000..4a0ce0f16 --- /dev/null +++ b/css/ship.css @@ -0,0 +1,7 @@ +div#hullRow { + width: 70px; +} + +div#engineRow { + width: 70px; + margin-bottom: 20px; diff --git a/css/space.css b/css/space.css new file mode 100644 index 000000000..e74fa0891 --- /dev/null +++ b/css/space.css @@ -0,0 +1,134 @@ +@-ms-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + transform:rotate(0deg); + } + 100% { + -ms-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + transform:rotate(360deg); + } +} + +@-webkit-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + transform:rotate(0deg); + } + 100% { + -ms-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + transform:rotate(360deg); + } +} + +@-moz-keyframes spin { + 0% { + -ms-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + transform:rotate(0deg); + } + 100% { + -ms-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + transform:rotate(360deg); + } +} + +@keyframes spin { + 0% { + -ms-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + transform:rotate(0deg); + } + 100% { + -ms-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + transform:rotate(360deg); + } +} + +#spacePanel { + float: none !important; + position: absolute !important; + top: -700px; + left: 0px; +} + +#starsContainer { + width: 100%; + height: 100%; + position: absolute; + top: 0px; + left: 0px; + overflow: hidden; +} + +#stars, #starsBack { + position: absolute; + z-index: -1; + left: 0px; +} + +#stars > div, #starsBack > div { + position: relative; + height: 3000px; + width: 3000px; + color: white; +} + +#starsBack { + opacity: 0.5; +} + +.star { + position: absolute; +} + +#ship { + cursor: default; + position: absolute; + margin-top: -10px; + margin-left: -7.5px; +} + +#theEnd { + position: relative; + cursor: default; + top: 200px; + margin-left: -220px; + text-align: center; + font-size: 24px; + font-weight: bold; + opacity: 0; + color: white; +} + +.asteroid { + cursor: default; + position: absolute; + top: -40px; + left: 350px; + -webkit-animation: 1s linear 0s normal none infinite spin; + -moz-animation: 1s linear 0s normal none infinite spin; + -ms-animation: 1s linear 0s normal none infinite spin; + animation: 1s linear 0s normal none infinite spin; + font-size: 32px; +} + +#hullRemaining { + width: 70px; + position: absolute; + top: 0px; + left: 0px; +} \ No newline at end of file diff --git a/css/world.css b/css/world.css new file mode 100644 index 000000000..a1b461b8e --- /dev/null +++ b/css/world.css @@ -0,0 +1,74 @@ +#worldOuter { + position: relative; + display: inline-block; +} + +#map { + position: relative; + font-family: "Courier New", Courier, monospace; + border: 1px solid black; + overflow: hidden; + display: inline-block; + line-height: 10px; + letter-spacing: 1px; + color: #999; + cursor: default; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +#map .landmark { + position: relative; + font-weight: bold; + color: black; + line-height: 0px; /* Hack to prevent the boldness from increasing the row's line-height. I hope it works in all browsers... */ +} + +#bagspace-world { + border: 1px solid black; + height: 62px; + margin-bottom: 5px; + margin-top: 13px; + overflow: hidden; +} + +#bagspace-world > div { + padding: 6px 4px; +} + +#backpackTitle { + position: absolute; + top: 0px; + left: 10px; + background-color: white; + z-index: 1; +} + +#backpackSpace { + position: absolute; + top: 0px; + right: 10px; + background-color: white; + z-index: 1; +} + +#healthCounter { + position: absolute; + top: 0px; + left: 80px; + background-color: white; + z-index: 1; +} + +div.supplyItem { + display: inline-block; + border: 1px solid #999; + float: left; + margin: 0px 5px 6px 0px; + padding: 0 5px; + cursor: default; +} \ No newline at end of file diff --git a/doc/Events.xlsx b/doc/Events.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a0b9f78d5efc3a3f2866da60d0be3f6daa346d9c GIT binary patch literal 10337 zcmeHtBM8kKi?^Q_}rM2HMgM@t{!%* zBaGTp$eB54IqXRQ74f19&v0!L5mV@6_^z^L57xm0#z4m$b&;3>MhOk zk3?M-p;WXZA*Hh|I_jtMLA?u~#hqRhSXObUD2whzULitM>&yQYi4-0B99d22wkPsR%$#pl=2v@Tic}`oomuIV?CVy}T zu>u3%WS12@oF`nVz%AcRO(VOky*tyEw^6cOq)-+X?||#3s2Nhp@O*Ka$be)3&RNDd zM*rPQW2h{uIUV`)rv@&^m(pP)5-VqX%i+#KdwcI0vn@)l+lKhoiUL<U#*>0d-~vv;yKvbVSX<<|Z~8VIm&13SR~+gEGcpj8hu za^O)=d%$R$W89;{cOK7IL2V85aLm=1A+k2cC4wGzX$gm~s|u1w@+sKQSbmt@ai6ad zk-IFtqNyQ-Zk|gYsz=njl2^YX4XtNuzCy)`l&C6aWO_Y3V=X6d(jxS-giFaFwL2g| zMVpY_R3Jw>`D>rBGl4N(Km}T}xaQVPW@HCPPMT$SrXsrA7D0~>a{rCJ?J~RWhv;@6 z;K<1fr2a6Z_?#$)8h^`sJzPyl7>}%6o%q%6J}6Bw0Y^f0iX!RH(8~90Ka5x#t-r)f zVhkensR;0VWYcD%bgrdEj`rTYt)^oi6ufj&d)h_%C;Q5h#}I7-)&?c8eMJMS1Xy$a ztTT~H17L2Ux00WrB^|Ms+~t&~sjN}Hg9w!Je0@?_y*m?o5OaB?NsSl?r1o^{t7VHX z&7C+=$7+tHY~PnlCTjqhSVk>5 z$NR03uw(S8E*ah1aTi9Mecrc$>XS%JidUM7c}-{0BJa=mK9-{k@nS>d1X2GD2%92 zkx;z}388!RVx#()X3g~rqzn6s-FP?Afh5TKU@-+#Q0bdX#9Uv(Okz*OVz{HbU{Cwh zk|YEa7qGL%fby>maRzzVm^%NWaDnQw{Tws8Pj=l?*C{VW?g(Z=0f9n+)FOL{%-ko*41-C* zm-BUo}Ve1=D_~+c;Cg9tcuX$&vyN8g)2JWj+XtWWy8E$@qmPc~XPzM9M9= zcNB#57^pQA>N=Jt!Y@d-7~5t1i zd&jT71G$tvP{bY+@3=(guo?~{nj}O`Kj{PoH*wI|M4;=0k*d#v)zi~Y zF#{(`yq*A&ulF14biN{2mdyI~F0E6?Cl9aHDz{9|co2@Y$q#fNZq}2qDk3p82O)izIO)`em;8v(4VMnS+VWQ5jV2|1YUKeF7w@BXV33S^@ zQJ@)-?=y@9q=JY9ZQrXm+Mdh>bd%)O&p>HRmp_Gw%g(NM;1WS}L}4TM#;A~dkkCEL z2V%NCv|#msx{nEJKHVQ}KEA&XtCd2W`(lnDqv9zhpEjXAcU>o!SGW&?G=)7C#Og@K z_}nFVf_D|@h<_!ASjWqZidTlWMbBq|JNP-#=eEo<$}9ekUO`=0+G)jg-dEo$q%9Loqb!&$+Cu1|n4Ue{!?U<@F95wCZ_4eQ?t=_T?RlY}4Msy(_=ykaH zz=g$YU54pBSwgulla*b2*HMk59jgwX^L~DDa2Iw_w|V>cXd`{HGBS1*4_BAVQ1lXV z{Hf8T18i{quKB#StkPXzjTZrrlK(bE&K9PoAm@Mkvp*hK{>PgIMt6mOVa67}f_fBn z_l%zkLKao#eA%SB4P|sY2Rok}bA|ojSxaczxw+{uy!kr)j;{o0h8jyh(F_^1VT@-{ zb?iCE&>>vSwYz|gz`z${IL_Ff1|=sa71kG{frC^Xq)73A-|f>gTOEZ^5+krRD;eg& z+l;n;v5Xh6@{w98K z>SrXp=XRN8?TrduX!~Q}>=NOhR-lw4G&&y4{Si0-0RL|*@WMp|mg}_RXZu)K|yQNv@$8*ty1R<03LsiBDBsuBX{d+h0#kx5U*f zhU+m(ha9j2@lVo9bSCDJpKfm)IJ&=SXl>UiaSR{RabBBUTnwD%oY%hMZiI)g?{~1Y zttIWLij%g2oIEC2!j_V@S}ktuVNV-h0QQIX7;;g@g%dOe1`&5Fh?C*#(>27mHZX{t zg=-xzC*-jZ$=0xNNnV^)d^VDL?KcxDHKUH!D^b(C-k_FdM;m%anWivR{jiG61T+4< z_zSEbd3pXe%W!`Nz_R`ajM&Op6gv~E7m#5}F|yz4C)T<4aeznTTgGMJ$E?MOKE7@; zQshc5=U8&Yfvn}T((lK*$dq?`xm7Tv+?dNpyte_BBWNF-^hR_|vnN)8c=KZ-yfXd> zJ^Ld18n4Dlo{`;z~_R?-NGxI*7&W29Wy@-q@Iz#re*`jizf_1MJ>G zY48THON_IpQvAFqO6t5ws&Y*lCzV`d4qF>$AS1L#ur}vLTeEn5FFY2!Gu~L3RFR}b zgns_2$BJOLSwPDxflT5<{1Q)jqu-AgG;=;UjQzy4T}#+*+u=JZj=mM4@;H0W;!Y~) z$G1Dp)=||J0hc?V3Ez`II1c;7;eai^rLm%qD8N zU}t#eYsj@bU&(u4^~TypB4U)q(<2poaa)C6BnNWjoPuVe1|NIX^=uJpyM& zZth4dPEW<$gacH&c9NsDNkOoh{+-HqMjvexzz(bk_^ZhBM^pi_Fts&h`s4lwfA=-z z?dM-(cc9IE*J(9=i}?%hV(dG*w86x)5*XwC{o!sSOE+8~l^d-fn51XzhWG;m zd}J&?WSc^({M9j3zCw~fEQYF1l}?&beqp~^+ri1`aH5))c$QkZCdkC{%MEO2F8ModPW(1V0IpYEEzRzV-XmB;t|`9!tHEt)cGn z{g}&7{}a&QF?JV?xz^sawz{j+Eo|omYsK`Qi)VdC7KFhvoNPJ0c-qC|oAUTkdcXa3 z)&skG#qySG$cGEcRP9W;qERW+YB7{=0#nJyz)3T2&%rKWOfP5B@x_p9<2>jX*5qW6 z((6g;lcw7VEH*2FeDcU#?t_mMe~1j%JVTH@z$-uP3m#2qFkdr!1VZ#=sYb-HH{&f} zL?{^NLu?wgiNAN~8e`NOf1^QwJ*G}dwP?Ez!B}g|IgG5z{2g;w3$}Hcf-5cfe z-JAIJkl`qjN6`Jw=t(yT1B3Z0V^qM}m*i!i%k62Be8DWAv(pp2O?SE{LDfmS}Er?Gi^e3{NSc8bV38Am%Zp_LKJ9JXo0W|G~57OahVe_N})K2p3!Q! zb43I=Z5gTc5E*!Bd~Jy^?yGOpgj8iilIpu$jNvjmWFw>_j7gy>*uGAP!8|xr+?{cW zzV(+DP~0IFCz6qtWi245j=_y^wS@LbP5y~o;c8RshUdaXH~ub!uvtO^{)bc`3FcPH z2~XB5!o*m@wNM-sAFNMRGT*v%uEllaX^%ld_;1@-Vry&$Z6R)PGkl;#+h|?xh|}LP zD$(#Txms6TXOa&Dg_gbaM2qH#wf!pMqYlV4LEY-6g}s?hUlYP^iIa?TP(u! z=Vf;g(}E7oN)5x**P~ttTkgt`WoA)@m1*&Yt~(!#u$pj ze4~)PtuYYAVj)DhZHSOZ(g$5y#C;+a8JSRrv`RAoTAf4@2UR3PXj$)!Jyxi&04fZV zakbSVSlZH>@BHz5)@$G`p%Gn3k5zQ^!ErP3P!y2+1-YuhP~D4s7ul7vA5)bDV#1Bn zhHB#?nW@@?p=RNE-3;oQ=`$GvT^paRm{YW|c!1Ck=o+69`g{fnhImtAs~cJ_6t{5K zgCan&$R{ZU0%Px(?uyMa5_-SKz0^VjBH!-2hfg9dXr=7~9e5++!}oXTW=xknv6xIT zT8}XmcvGY38L6lvMiu9+Ii?7RPaVfDsT@>-`)D94r59z4a67%@D+5vPl;dYjZoZ_K z%dl`FSUKDo9<&!!DZ^osO`;~{qM)!P1_L5EsOg@xpnkklL`+c0ua&HBhv+)4l!R~v z=}J>!t$oU?MIxVbz2so{=v5esAGl7=Zp7y3*Y&`oBAm^V=&)``>+kb^kK5LkZ4-B! z{bzWx5v+M1i?m2al6GdECY5&6D~Zp@Tjiw0J8HBoiih{5DG42QKI-R^sboz;Ojxmh6Kl0m^fxj?QC+C?clK8b@VR!Sl3K9H zhbH%*k1Sz7KrU`zpAD9n+$>o;uVf#3*{}vE*%7@)ku`hL>1pNH*e`W$)66<(6Bj@4 z$r~&+*M#_is4Yw`nJ!+aT2)A-;`7IQ<=KK-LW zQT5r@57X_9HE{yo`YK~<-iEP>mD%`Lh@$cPq{f8C?Fl%7W+$WG4(F&|uTxkkWtqE* zXy50C5U23@Z8Q=Nj+J-(qZ2znymX)h7gXZGvD*ICI2Xy7%QSe-&-lOI=|V677-95Z`GIbrc3vB+pu^i zDp3q3&kRSyiA*|gRoPBdRr(FSia+t;Bvau+!8h;Co zj=2-9%o!=3zzT{}OMQJs#VMicRha3@B<%dSA(Do~7!zfo1*Vgf5v<0CrW_{jM3FEZ zI3c{|Aib}ANakeB40ta>;AQ4wO+3Pn)^2#@9GY}ypdU8&bF_{m;s?p;@F(d5cnnQW zJ22Mz0VFGx@HB}q_);PqzGhctG)k@EZxyd>vWH!a??@U)%qqtX)|#I*2yyqSH4=A~Eg_H?E8>(Ad+8syrX|6EFrCA>tr-q3byDl^ zR4NbrGqo0ec#vg+)4V<~y|DikA39qYI+>a%gPbhw%>Sr$*eG||%`+pn!F>}%=h<)I zVF)%Hu)(wNGlB}Xk*GN=lZa&KiQ|i%OY6waXrQ6pq-lS9${$y1PjUe8Wr;bdz?fD? zg4juz{_67Ky1vEtNfJ;E(n4J_B$ty|Yseh7dKsLsSB$}b)>KBT#IGf(?jx2Q6M&}X z^e(s}k0obfg1m;PG`)At*z}_#WI}Wl@MKj>2(%^NGpW3?Rcm3*YCCwKrBy~}w`cWE zuvcx#vRjgM^BXD&cl4t87Scq0qtf<5J!c@a;LG??@n!S#^cH9zPTgVXuMk)89UsOY z_4RxPgqu-?i#%os7sOgfs;WRCy40d_KF4no7fR@CW~0RuZJGK^_B9u~0>9`u1%*fS zz#$ja73Jbj(t51mOJQj5MPH9ervLO(89gYgi%_xID>9ty_KDd|@f481{>V%u&)tsX zL@^UGv9vk!rqCOgxZS9#Fvh{xkcaLC`d11m*P!GsI&R%{%-2hJ*4`0cyx#1OPb4{n zLM*x5o5osur?X_s&hxMVXZlvgmc0gx$KKk2`!oL#?laT7;7WnF(-EAzVt~ufCicb( zPWBGYOvd(3roR$aaI5CORcElCN5(12cavfVEz91cp&T)QG6&wwF)G$lC#PuSpM2y1Pk!-#c#_%@AGmSvY! zey$LlgN5eO(nf3Uf`)rTA;cbK6HQEAe(S4|p}SzC{cBWgXGw=0QoBgiA0hdRYr!Y- z4KRFUCc!xFR4L|7P+tcn#NHOHkk{PQxS*jEilQLaxpB!V-S1r(B)0I3(3#6oKI9@~ zMmY}8`lGGwEb(#^%s0r+l|^Hm7F~NkoR6u0_;~2r3z7JFB|+O@X~v1+CQ6t{wQv%( z?%PS~3`33CP-XWJCuYyXYWUL_*0p!$Dh9eDyhI2O^(KR-dzo5sMhtsIzpl)dD$hp~ zVYbsz@oTna7-Gf=mT$hH5m8*Jqd(Uo-60dW+pj4$f7Ph}o^Lv?_WB-y74RB7O#Lr= z;s7=zAX6u0QxNEvMPYjO%))47X}7P|CDkp2?hVw|q056qBj($H3KyFXhZnIfOT$c0 z69aZdXuyc0i?wMTA9e-63HI9`>+jANSR&bWh2j)sg3N2#?DgHK2lkt)48C*;fD#-oj`sB^V^jm&&H}&9M;J%Uqc&7e3!@f!(%`Gjw>y= z=e+yM^)UynuIq;HJE$80uild9PMyu`jZ*ZQ&-#ONrkdM2m7JnEr9v;N^?M$^DE^G9 z8=6|oZ-xwY6FV*%Fd3Ldv?0dt7SV|eh&<+;Z4_k{J7Hrf%Sa6l3@CM1pr5ueVq#>W zer{o!RQrJ(A(YH3?L~a783KksH4l@^YaGT{mJUuuN5^82GA)}MN0hRC48=F$s{S;| zyryLF;hx2LfllE46w^T9i5uiC$=Ww4Y^sAWf^4B(A-2a74aznDdZ%c3xUP9|T6IiB49|m`xFrgYDI5k^(TJYG3n}NEylri=8PABDjb! zql=QY!7ke&J%e>kc=~C}ga)=xQ~Jp|4Q~Eqbzp0Rxk~MxH<(5ZA6stv%Zi_x zq4Q+O)KT%QhgCOMuZ~Qs24G)UhaPD_qSN+6iPkdp)vnJi?pO+T_HG)OGy7FJgf%&; zKnUQ_Vg1za;qUI`UAafhB_7Y@%61K8+_sR*geXP}cpy@oBPbJtKT|%~)&1uv&x@(QQA{!ZKzUwTeGc$Ek^K!o3(jl* zIk qqsxB|`d2UT8xjCOc=@My`nOL|kcI(k@~_4$3cvAk1` literal 0 HcmV?d00001 diff --git a/doc/Zones.txt b/doc/Zones.txt new file mode 100644 index 000000000..08a1c8b43 --- /dev/null +++ b/doc/Zones.txt @@ -0,0 +1,5 @@ +Radius Enemy DPS Player DPS Enemy HP Player HP +===================================================================== +< 10 1 1 5 10 +< 20 3 3 10 15-20 +< 30 6 4 20 30-40 \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..19ecf1ac78ea9df5381498d311b3cc1750278028 GIT binary patch literal 318 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 OQi^6J69GQ||C<0KXBq1N literal 0 HcmV?d00001 diff --git a/img/adr.png b/img/adr.png new file mode 100644 index 0000000000000000000000000000000000000000..61d02141a3eb75593cd797ee2ae80b36ac2c2a0b GIT binary patch literal 5648 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

#wd=z^Xlpvbs@rik*Je&S;GRPZ@q3@JXIq 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 literal 0 HcmV?d00001 diff --git a/img/ie.png b/img/ie.png new file mode 100644 index 0000000000000000000000000000000000000000..445211174c7f271e3db4c2c703e913be80e22714 GIT binary patch literal 5042 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 IM6N<$f + + + + A Dark Room + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + \ No newline at end of file diff --git a/lib/jquery.color-2.1.2.min.js b/lib/jquery.color-2.1.2.min.js new file mode 100644 index 000000000..d77c42234 --- /dev/null +++ b/lib/jquery.color-2.1.2.min.js @@ -0,0 +1,2 @@ +/*! jQuery Color v@2.1.2 http://github.com/jquery/jquery-color | jquery.org/license */ +(function(a,b){function m(a,b,c){var d=h[b.type]||{};return a==null?c||!b.def?null:b.def:(a=d.floor?~~a:parseFloat(a),isNaN(a)?b.def:d.mod?(a+d.mod)%d.mod:0>a?0:d.max")[0],k,l=a.each;j.style.cssText="background-color:rgba(1,1,1,.5)",i.rgba=j.style.backgroundColor.indexOf("rgba")>-1,l(g,function(a,b){b.cache="_"+a,b.props.alpha={idx:3,type:"percent",def:1}}),f.fn=a.extend(f.prototype,{parse:function(c,d,e,h){if(c===b)return this._rgba=[null,null,null,null],this;if(c.jquery||c.nodeType)c=a(c).css(d),d=b;var i=this,j=a.type(c),o=this._rgba=[];d!==b&&(c=[c,d,e,h],j="array");if(j==="string")return this.parse(n(c)||k._default);if(j==="array")return l(g.rgba.props,function(a,b){o[b.idx]=m(c[b.idx],b)}),this;if(j==="object")return c instanceof f?l(g,function(a,b){c[b.cache]&&(i[b.cache]=c[b.cache].slice())}):l(g,function(b,d){var e=d.cache;l(d.props,function(a,b){if(!i[e]&&d.to){if(a==="alpha"||c[a]==null)return;i[e]=d.to(i._rgba)}i[e][b.idx]=m(c[a],b,!0)}),i[e]&&a.inArray(null,i[e].slice(0,3))<0&&(i[e][3]=1,d.from&&(i._rgba=d.from(i[e])))}),this},is:function(a){var b=f(a),c=!0,d=this;return l(g,function(a,e){var f,g=b[e.cache];return g&&(f=d[e.cache]||e.to&&e.to(d._rgba)||[],l(e.props,function(a,b){if(g[b.idx]!=null)return c=g[b.idx]===f[b.idx],c})),c}),c},_space:function(){var a=[],b=this;return l(g,function(c,d){b[d.cache]&&a.push(c)}),a.pop()},transition:function(a,b){var c=f(a),d=c._space(),e=g[d],i=this.alpha()===0?f("transparent"):this,j=i[e.cache]||e.to(i._rgba),k=j.slice();return c=c[e.cache],l(e.props,function(a,d){var e=d.idx,f=j[e],g=c[e],i=h[d.type]||{};if(g===null)return;f===null?k[e]=g:(i.mod&&(g-f>i.mod/2?f+=i.mod:f-g>i.mod/2&&(f-=i.mod)),k[e]=m((g-f)*b+f,d))}),this[d](k)},blend:function(b){if(this._rgba[3]===1)return this;var c=this._rgba.slice(),d=c.pop(),e=f(b)._rgba;return f(a.map(c,function(a,b){return(1-d)*e[b]+d*a}))},toRgbaString:function(){var b="rgba(",c=a.map(this._rgba,function(a,b){return a==null?b>2?1:0:a});return c[3]===1&&(c.pop(),b="rgb("),b+c.join()+")"},toHslaString:function(){var b="hsla(",c=a.map(this.hsla(),function(a,b){return a==null&&(a=b>2?1:0),b&&b<3&&(a=Math.round(a*100)+"%"),a});return c[3]===1&&(c.pop(),b="hsl("),b+c.join()+")"},toHexString:function(b){var c=this._rgba.slice(),d=c.pop();return b&&c.push(~~(d*255)),"#"+a.map(c,function(a){return a=(a||0).toString(16),a.length===1?"0"+a:a}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),f.fn.parse.prototype=f.fn,g.hsla.to=function(a){if(a[0]==null||a[1]==null||a[2]==null)return[null,null,null,a[3]];var b=a[0]/255,c=a[1]/255,d=a[2]/255,e=a[3],f=Math.max(b,c,d),g=Math.min(b,c,d),h=f-g,i=f+g,j=i*.5,k,l;return g===f?k=0:b===f?k=60*(c-d)/h+360:c===f?k=60*(d-b)/h+120:k=60*(b-c)/h+240,h===0?l=0:j<=.5?l=h/i:l=h/(2-i),[Math.round(k)%360,l,j,e==null?1:e]},g.hsla.from=function(a){if(a[0]==null||a[1]==null||a[2]==null)return[null,null,null,a[3]];var b=a[0]/360,c=a[1],d=a[2],e=a[3],f=d<=.5?d*(1+c):d+c-d*c,g=2*d-f;return[Math.round(o(g,f,b+1/3)*255),Math.round(o(g,f,b)*255),Math.round(o(g,f,b-1/3)*255),e]},l(g,function(c,e){var g=e.props,h=e.cache,i=e.to,j=e.from;f.fn[c]=function(c){i&&!this[h]&&(this[h]=i(this._rgba));if(c===b)return this[h].slice();var d,e=a.type(c),k=e==="array"||e==="object"?c:arguments,n=this[h].slice();return l(g,function(a,b){var c=k[e==="object"?a:b.idx];c==null&&(c=n[b.idx]),n[b.idx]=m(c,b)}),j?(d=f(j(n)),d[h]=n,d):f(n)},l(g,function(b,e){if(f.fn[b])return;f.fn[b]=function(f){var g=a.type(f),h=b==="alpha"?this._hsla?"hsla":"rgba":c,i=this[h](),j=i[e.idx],k;return g==="undefined"?j:(g==="function"&&(f=f.call(this,j),g=a.type(f)),f==null&&e.empty?this:(g==="string"&&(k=d.exec(f),k&&(f=j+parseFloat(k[2])*(k[1]==="+"?1:-1))),i[e.idx]=f,this[h](i)))}})}),f.hook=function(b){var c=b.split(" ");l(c,function(b,c){a.cssHooks[c]={set:function(b,d){var e,g,h="";if(d!=="transparent"&&(a.type(d)!=="string"||(e=n(d)))){d=f(e||d);if(!i.rgba&&d._rgba[3]!==1){g=c==="backgroundColor"?b.parentNode:b;while((h===""||h==="transparent")&&g&&g.style)try{h=a.css(g,"backgroundColor"),g=g.parentNode}catch(j){}d=d.blend(h&&h!=="transparent"?h:"_default")}d=d.toRgbaString()}try{b.style[c]=d}catch(j){}}},a.fx.step[c]=function(b){b.colorInit||(b.start=f(b.elem,c),b.end=f(b.end),b.colorInit=!0),a.cssHooks[c].set(b.elem,b.start.transition(b.end,b.pos))}})},f.hook(c),a.cssHooks.borderColor={expand:function(a){var b={};return l(["Top","Right","Bottom","Left"],function(c,d){b["border"+d+"Color"]=a}),b}},k=a.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}})(jQuery); \ No newline at end of file diff --git a/mobileWarning.html b/mobileWarning.html new file mode 100644 index 000000000..0bcb872e8 --- /dev/null +++ b/mobileWarning.html @@ -0,0 +1,23 @@ + + + + A Dark Room + + + + + + \ No newline at end of file diff --git a/script/Button.js b/script/Button.js new file mode 100644 index 000000000..6d10a0d75 --- /dev/null +++ b/script/Button.js @@ -0,0 +1,86 @@ +var Button = { + Button: function(options) { + if(typeof options.cooldown == 'number') { + this.data_cooldown = options.cooldown; + } + this.data_remaining = 0; + if(typeof options.click == 'function') { + this.data_handler = options.click; + } + + var el = $('
') + .attr('id', typeof(options.id) != 'undefined' ? options.id : "BTN_" + Engine.getGuid()) + .addClass('button') + .text(typeof(options.text) != 'undefined' ? options.text : "button") + .click(function() { + if(!$(this).hasClass('disabled')) { + Button.cooldown($(this)); + $(this).data("handler")($(this)); + } + }) + .data("handler", typeof options.click == 'function' ? options.click : function() { Engine.log("click"); }) + .data("remaining", 0) + .data("cooldown", typeof options.cooldown == 'number' ? options.cooldown : 0); + + el.append($("
").addClass('cooldown')); + + if(options.cost) { + var ttPos = options.ttPos ? options.ttPos : "bottom right"; + var costTooltip = $('
').addClass('tooltip ' + ttPos); + for(var k in options.cost) { + $("
").addClass('row_key').text(k).appendTo(costTooltip); + $("
").addClass('row_val').text(options.cost[k]).appendTo(costTooltip); + } + if(costTooltip.children().length > 0) { + costTooltip.appendTo(el); + } + } + + if(options.width) { + el.css('width', options.width); + } + + return el; + }, + + setDisabled: function(btn, disabled) { + if(btn) { + if(!disabled && !btn.data('onCooldown')) { + btn.removeClass('disabled'); + } else if(disabled) { + btn.addClass('disabled'); + } + btn.data('disabled', disabled); + } + }, + + isDisabled: function(btn) { + if(btn) { + return btn.data('disabled') === true; + } + return false; + }, + + cooldown: function(btn) { + var cd = btn.data("cooldown"); + if(cd > 0) { + $('div.cooldown', btn).stop(true, true).width("100%").animate({width: '0%'}, cd * 1000, 'linear', function() { + var b = $(this).closest('.button'); + b.data('onCooldown', false); + if(!b.data('disabled')) { + b.removeClass('disabled'); + } + }); + btn.addClass('disabled'); + btn.data('onCooldown', true); + } + }, + + clearCooldown: function(btn) { + $('div.cooldown', btn).stop(true, true); + btn.data('onCooldown', false); + if(!btn.data('disabled')) { + btn.removeClass('disabled'); + } + } +}; \ No newline at end of file diff --git a/script/engine.js b/script/engine.js new file mode 100644 index 000000000..04557509f --- /dev/null +++ b/script/engine.js @@ -0,0 +1,550 @@ +var Engine = { + + /* TODO *** MICHAEL IS A LAZY BASTARD AND DOES NOT WANT TO REFACTOR *** + * Here is what he should be doing: + * - All updating values (store numbers, incomes, etc...) should be objects that can register listeners to + * value-change events. These events should be fired whenever a value (or group of values, I suppose) is updated. + * That would be so elegant and awesome. + */ + SITE_URL: encodeURIComponent("http://adarkroom.doublespeakgames.com"), + MAX_STORE: 99999999999999, + SAVE_DISPLAY: 30 * 1000, + + Perks: { + 'boxer': { + desc: 'punches do more damage', + notify: 'learned to throw punches with purpose' + }, + 'martial artist': { + desc: 'punches do even more damage.', + notify: 'learned to fight quite effectively without weapons' + }, + 'unarmed master': { + desc: 'punch twice as fast, and with even more force', + notify: 'learned to strike faster without weapons' + }, + 'barbarian': { + desc: 'melee weapons deal more damage', + notify: 'learned to swing weapons with force' + }, + 'slow metabolism': { + desc: 'go twice as far without eating', + notify: 'learned how to ignore the hunger' + }, + 'desert rat': { + desc: 'go twice as far without drinking', + notify: 'learned to love the dry air' + }, + 'evasive': { + desc: 'dodge attacks more effectively', + notify: "learned to be where they're not" + }, + 'precise': { + desc: 'land blows more often', + notify: 'learned to predict their movement' + }, + 'scout': { + desc: 'see farther', + notify: 'learned to look ahead' + }, + 'stealthy': { + desc: 'better avoid conflict in the wild', + notify: 'learned how not to be seen' + }, + 'gastronome': { + desc: 'restore more health when eating', + notify: 'learned to make the most of food' + } + }, + + options: { + state: null, + debug: false, + log: false + }, + + init: function(options) { + this.options = $.extend( + this.options, + options + ); + this._debug = this.options.debug; + this._log = this.options.log; + + // Check for HTML5 support + if(!Engine.browserValid()) { + window.location = 'browserWarning.html'; + } + + // Check for mobile + if(Engine.isMobile()) { + window.location = 'mobileWarning.html'; + } + + if(this.options.state != null) { + window.State = this.options.state; + } else { + Engine.loadGame(); + } + + $('
').attr('id', 'locationSlider').appendTo('#main'); + + $('') + .addClass('deleteSave') + .text('restart.') + .click(Engine.confirmDelete) + .appendTo('body'); + + $('
') + .addClass('share') + .text('share.') + .click(Engine.share) + .appendTo('body'); + + // Register keypress handlers + $('body').off('keydown').keydown(Engine.keyDown); + $('body').off('keyup').keyup(Engine.keyUp); + + Notifications.init(); + Events.init(); + Room.init(); + + if(Engine.storeAvailable('wood')) { + Outside.init(); + } + if(Engine.getStore('compass') > 0) { + Path.init(); + } + if(State.ship) { + Ship.init(); + } + + Engine.travelTo(Room); + + }, + + browserValid: function() { + return location.search.indexOf('ignorebrowser=true') >= 0 || ( + typeof Storage != 'undefined' && + !oldIE); + }, + + isMobile: function() { + return location.search.indexOf('ignorebrowser=true') < 0 && + /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); + }, + + saveGame: function() { + if(typeof Storage != 'undefined' && localStorage) { + if(Engine._saveTimer != null) { + clearTimeout(Engine._saveTimer); + } + if(typeof Engine._lastNotify == 'undefined' || Date.now() - Engine._lastNotify > Engine.SAVE_DISPLAY){ + $('#saveNotify').css('opacity', 1).animate({opacity: 0}, 1000, 'linear'); + Engine._lastNotify = Date.now(); + } + localStorage.gameState = JSON.stringify(State); + } + }, + + loadGame: function() { + try { + var savedState = JSON.parse(localStorage.gameState); + if(savedState) { + State = savedState; + Engine.upgradeState(); + Engine.log("loaded save!"); + } + } catch(e) { + State = { + version: 1.2, + stores: {}, + perks: {} + }; + Engine.event('progress', 'new game'); + } + }, + + upgradeState: function() { + /* Use this function to make old + * save games compatible with newer versions */ + if(typeof State.version != 'number') { + Engine.log('upgraded save to v1.0'); + State.version = 1.0; + } + if(State.version == 1.0) { + // v1.1 introduced the Lodge, so get rid of lodgeless hunters + delete State.outside.workers.hunter; + delete State.income.hunter; + Engine.log('upgraded save to v1.1'); + State.version = 1.1; + } + if(State.version == 1.1) { + //v1.2 added the Swamp to the map, so add it to already generated maps + if(State.world) { + World.placeLandmark(15, World.RADIUS * 1.5, World.TILE.SWAMP, State.world.map); + } + Engine.log('upgraded save to v1.2'); + State.version = 1.2; + } + }, + + event: function(cat, act) { + if(typeof ga === 'function') { + ga('send', 'event', cat, act); + } + }, + + confirmDelete: function() { + Events.startEvent({ + title: 'Restart?', + scenes: { + start: { + text: ['restart the game?'], + buttons: { + 'yes': { + text: 'yes', + nextScene: 'end', + onChoose: Engine.deleteSave + }, + 'no': { + text: 'no', + nextScene: 'end' + } + } + } + } + }); + }, + + deleteSave: function() { + if(typeof Storage != 'undefined' && localStorage) { + localStorage.clear(); + } + location.reload(); + }, + + share: function() { + Events.startEvent({ + title: 'Share', + scenes: { + start: { + text: ['bring your friends.'], + buttons: { + 'facebook': { + text: 'facebook', + nextScene: 'end', + onChoose: function() { + window.open('https://www.facebook.com/sharer/sharer.php?u=' + Engine.SITE_URL, 'sharer', 'width=626,height=436,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no'); + } + }, + 'google': { + text:'google+', + nextScene: 'end', + onChoose: function() { + window.open('https://plus.google.com/share?url=' + Engine.SITE_URL, 'sharer', 'width=480,height=436,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no'); + } + }, + 'twitter': { + text: 'twitter', + onChoose: function() { + window.open('https://twitter.com/intent/tweet?text=A%20Dark%20Room&url=' + Engine.SITE_URL, 'sharer', 'width=660,height=260,location=no,menubar=no,resizable=no,scrollbars=yes,status=no,toolbar=no'); + }, + nextScene: 'end' + }, + 'reddit': { + text: 'reddit', + onChoose: function() { + window.open('http://www.reddit.com/submit?url=' + Engine.SITE_URL, 'sharer', 'width=960,height=700,location=no,menubar=no,resizable=no,scrollbars=yes,status=no,toolbar=no'); + }, + nextScene: 'end' + }, + 'close': { + text: 'close', + nextScene: 'end' + } + } + } + } + }, {width: '400px'}); + }, + + // Gets a guid + getGuid: function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + }, + + activeModule: null, + + travelTo: function(module) { + if(Engine.activeModule != module) { + var currentIndex = Engine.activeModule ? $('.location').index(Engine.activeModule.panel) : 1; + Engine.activeModule = module; + $('div.headerButton').removeClass('selected'); + module.tab.addClass('selected'); + + var slider = $('#locationSlider'); + var panelIndex = $('.location').index(module.panel); + var diff = Math.abs(panelIndex - currentIndex); + slider.animate({left: -(panelIndex * 700) + 'px'}, 300 * diff); + module.onArrival(); + + Notifications.printQueue(module); + } + }, + + addPerk: function(name) { + if(!State.perks) { + State.perks = {}; + } + State.perks[name] = true; + Notifications.notify(null, Engine.Perks[name].notify); + if(Engine.activeModule == Path) { + Path.updatePerks(); + } + }, + + hasPerk: function(name) { + return typeof State.perks == 'object' && State.perks[name] == true; + }, + + setStore: function(name, number) { + if(typeof State.stores == 'undefined') { + State.stores = {}; + } + if(number > Engine.MAX_STORE) number = Engine.MAX_STORE; + State.stores[name] = number; + Room.updateStoresView(); + Room.updateBuildButtons(); + if(State.outside) { + Outside.updateVillage(); + } + Engine.saveGame(); + }, + + setStores: function(list) { + if(typeof State.stores == 'undefined') { + State.stores = {}; + } + for(k in list) { + State.stores[k] = list[k] > Engine.MAX_STORE ? Engine.MAX_STORE : list[k]; + } + Room.updateStoresView(); + Room.updateBuildButtons(); + if(State.outside) { + Outside.updateVillage(); + } + Engine.saveGame(); + }, + + addStore: function(name, number) { + if(typeof State.stores == 'undefined') { + State.stores = {}; + } + var num = State.stores[name]; + if(typeof num != 'number' || isNaN(num) || num < 0) num = 0; + num += number; + if(num > Engine.MAX_STORE) num = Engine.MAX_STORE; + State.stores[name] = num; + Room.updateStoresView(); + Room.updateBuildButtons(); + Outside.updateVillage(); + if(Engine.activeModule == Path) { + Path.updateOutfitting(); + } + Engine.saveGame(); + }, + + addStores: function(list, ignoreCosts) { + if(typeof State.stores == 'undefined') { + State.stores = {}; + } + + // Make sure any income costs can be paid + if(!ignoreCosts) { + for(k in list) { + var num = State.stores[k]; + if(typeof num != 'number' || isNaN(num) || num < 0) num = 0; + if(num + list[k] < 0) { + return false; + } + } + } + + // Actually do the update + for(k in list) { + var num = State.stores[k]; + if(typeof num != 'number') num = 0; + num += list[k]; + num = num < 0 ? 0 : num; + num = num > Engine.MAX_STORE ? Engine.MAX_STORE : num; + State.stores[k] = num; + } + Room.updateStoresView(); + Room.updateBuildButtons(); + Outside.updateVillage(); + if(Engine.activeModule == Path) { + Path.updateOutfitting(); + } + Engine.saveGame(); + return true; + }, + + storeAvailable: function(name) { + return typeof State.stores[name] == 'number'; + }, + + getStore: function(name) { + if(typeof State.stores == 'undefined' || typeof State.stores[name] == 'undefined' ) { + return 0; + } + return State.stores[name]; + }, + + setIncome: function(source, options) { + if(typeof State.income == 'undefined') { + State.income = {}; + } + var existing = State.income[source]; + if(typeof existing != 'undefined') { + options.timeLeft = existing.timeLeft; + } + State.income[source] = options; + }, + + getIncome: function(source) { + if(typeof State.income == 'undefined') { + State.income = {}; + } + var existing = State.income[source]; + if(typeof existing != 'undefined') { + return existing; + } + return {}; + }, + + removeIncome: function(source) { + if(State.income) { + delete State.income[source]; + } + Room.updateIncomeView(); + }, + + collectIncome: function() { + if(typeof State.income != 'undefined' && Engine.activeModule != Space) { + var changed = false; + for(var source in State.income) { + var income = State.income[source]; + if(typeof income.timeLeft != 'number') + { + income.timeLeft = 0; + } + income.timeLeft--; + + if(income.timeLeft <= 0) { + Engine.log('collection income from ' + source); + if(source == 'thieves') { + Engine.addStolen(income.stores); + } + changed = Engine.addStores(income.stores) || changed; + if(typeof income.delay == 'number') { + income.timeLeft = income.delay; + } + } + } + if(changed) { + Room.updateStoresView(); + Room.updateBuildButtons(); + Engine.saveGame(); + if(Events.activeEvent() != null) { + Events.updateButtons(); + } + } + } + Engine._incomeTimeout = setTimeout(Engine.collectIncome, 1000); + }, + + openPath: function() { + Path.init(); + Engine.event('progress', 'path'); + Notifications.notify(Room, 'the compass points ' + World.dir); + }, + + addStolen: function(stores) { + if(!State.stolen) State.stolen = {}; + for(var k in stores) { + if(!State.stolen[k]) State.stolen[k] = 0; + State.stolen[k] -= stores[k]; + } + }, + + startThieves: function() { + State.thieves = 1; + Engine.setIncome('thieves', { + delay: 10, + stores: { + 'wood': -10, + 'fur': -5, + 'meat': -5 + } + }); + Room.updateIncomeView(); + }, + + num: function(name, craftable) { + switch(craftable.type) { + case 'good': + case 'tool': + case 'weapon': + case 'upgrade': + return Engine.getStore(name); + case 'building': + return Outside.numBuilding(name); + } + }, + + log: function(msg) { + if(this._log) { + console.log(msg); + } + }, + + updateSlider: function() { + var slider = $('#locationSlider'); + slider.width((slider.children().length * 700) + 'px'); + }, + + updateOuterSlider: function() { + var slider = $('#outerSlider'); + slider.width((slider.children().length * 700) + 'px'); + }, + + getIncomeMsg: function(num, delay) { + return (num > 0 ? "+" : "") + num + " per " + delay + "s"; + }, + + keyDown: function(e) { + if(!Engine.keyPressed && !Engine.keyLock) { + Engine.pressed = true; + if(Engine.activeModule.keyDown) { + Engine.activeModule.keyDown(e); + } + } + return false; + }, + + keyUp: function(e) { + Engine.pressed = false; + if(Engine.activeModule.keyUp) { + Engine.activeModule.keyUp(e); + } + return false; + } +}; + +$(function() { + Engine.init(); +}); \ No newline at end of file diff --git a/script/events.js b/script/events.js new file mode 100644 index 000000000..9ccfe6f49 --- /dev/null +++ b/script/events.js @@ -0,0 +1,735 @@ +/** + * Module that handles the random event system + */ +var Events = { + + _EVENT_TIME_RANGE: [3, 6], // range, in minutes + _PANEL_FADE: 200, + _FIGHT_SPEED: 100, + _EAT_COOLDOWN: 5, + STUN_DURATION: 4000, + + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + // Build the Event Pool + Events.EventPool = new Array().concat( + Events.Global, + Events.Room, + Events.Outside + ); + + Events.eventStack = []; + + Events.scheduleNextEvent(); + }, + + options: {}, // Nothing for now + + activeEvent: null, + activeScene: null, + eventPanel: null, + + loadScene: function(name) { + Engine.log('loading scene: ' + name); + Events.activeScene = name; + var scene = Events.activeEvent().scenes[name]; + + // Scene reward + if(scene.reward) { + Engine.addStores(scene.reward, true); + } + + // onLoad + if(scene.onLoad) { + scene.onLoad(); + } + + // Notify the scene change + if(scene.notification) { + Notifications.notify(null, scene.notification); + } + + $('#description', Events.eventPanel()).empty(); + $('#buttons', Events.eventPanel()).empty(); + if(scene.combat) { + Events.startCombat(scene); + } else { + Events.startStory(scene); + } + }, + + startCombat: function(scene) { + Engine.event('game event', 'combat'); + Events.won = false; + var desc = $('#description', Events.eventPanel()); + + $('
').text(scene.notification).appendTo(desc); + + // Draw the wanderer + Events.createFighterDiv('@', World.health, World.getMaxHealth()).attr('id', 'wanderer').appendTo(desc); + + // Draw the enemy + Events.createFighterDiv(scene.char, scene.health, scene.health).attr('id', 'enemy').appendTo(desc); + + // Draw the action buttons + var btns = $('#buttons', Events.eventPanel()); + + var numWeapons = 0; + for(var k in World.Weapons) { + var weapon = World.Weapons[k]; + if(typeof Path.outfit[k] == 'number' && Path.outfit[k] > 0) { + if(typeof weapon.damage != 'number' || weapon.damage == 0) { + // Weapons that deal no damage don't count + numWeapons--; + } else if(weapon.cost){ + for(var c in weapon.cost) { + var num = weapon.cost[c]; + if(typeof Path.outfit[c] != 'number' || Path.outfit[c] < num) { + // Can't use this weapon, so don't count it + numWeapons--; + } + } + } + numWeapons++; + Events.createAttackButton(k).appendTo(btns); + } + } + if(numWeapons == 0) { + // No weapons? You can punch stuff! + Events.createAttackButton('fists').prependTo(btns); + } + + var eat = new Button.Button({ + id: 'eat', + text: 'eat meat', + cooldown: Events._EAT_COOLDOWN, + click: Events.eatMeat, + cost: { 'cured meat': 1 } + }).appendTo(btns); + + if(Path.outfit['cured meat'] == 0) { + Button.setDisabled(eat, true); + } + + // Set up the enemy attack timer + Events._enemyAttackTimer = setTimeout(Events.enemyAttack, scene.attackDelay * 1000); + }, + + createAttackButton: function(weaponName) { + var weapon = World.Weapons[weaponName]; + var cd = weapon.cooldown; + if(weapon.type == 'unarmed') { + if(Engine.hasPerk('unarmed master')) { + cd /= 2; + } + } + var btn = new Button.Button({ + id: 'attack_' + weaponName.replace(' ', '-'), + text: weapon.verb, + cooldown: cd, + click: Events.useWeapon, + cost: weapon.cost + }); + if(typeof weapon.damage == 'number' && weapon.damage > 0) { + btn.addClass('weaponButton'); + } + + for(var k in weapon.cost) { + if(typeof Path.outfit[k] != 'number' || Path.outfit[k] < weapon.cost[k]) { + Button.setDisabled(btn, true); + break; + } + } + + return btn; + }, + + drawFloatText: function(text, parent) { + $('
').text(text).addClass('damageText').appendTo(parent).animate({ + 'bottom': '50px', + 'opacity': '0' + }, + 300, + 'linear', + function() { + $(this).remove(); + }); + }, + + eatMeat: function() { + if(Events.activeEvent() && Path.outfit['cured meat'] > 0) { + Path.outfit['cured meat']--; + World.updateSupplies(); + if(Path.outfit['cured meat'] == 0) { + Button.setDisabled($('#eat'), true); + } + var w = $('#wanderer'); + var hp = w.data('hp'); + hp += World.meatHeal(); + hp = hp > World.getMaxHealth() ? World.getMaxHealth() : hp; + w.data('hp', hp); + World.setHp(hp); + Events.updateFighterDiv(w); + Events.drawFloatText('+' + World.meatHeal(), '#wanderer .hp'); + } + }, + + useWeapon: function(btn) { + if(Events.activeEvent()) { + var weaponName = btn.attr('id').substring(7).replace('-', ' '); + var weapon = World.Weapons[weaponName]; + if(weapon.type == 'unarmed') { + if(!State.punches) State.punches = 0; + State.punches++; + if(State.punches == 50 && !Engine.hasPerk('boxer')) { + Engine.addPerk('boxer'); + } else if(State.punches == 150 && !Engine.hasPerk('martial artist')) { + Engine.addPerk('martial artist'); + } else if(State.punches == 300 && !Engine.hasPerk('unarmed master')) { + Engine.addPerk('unarmed master'); + } + + } + if(weapon.cost) { + var mod = {}; + var out = false; + for(var k in weapon.cost) { + if(typeof Path.outfit[k] != 'number' || Path.outfit[k] < weapon.cost[k]) { + return; + } + mod[k] = -weapon.cost[k]; + if(Path.outfit[k] - weapon.cost[k] < weapon.cost[k]) { + out = true; + } + } + for(var k in mod) { + Path.outfit[k] += mod[k]; + } + if(out) { + Button.setDisabled(btn, true); + var validWeapons = false; + $('.weaponButton').each(function(){ + if(!Button.isDisabled($(this)) && $(this).attr('id') != 'attack_fists') { + validWeapons = true; + return false; + } + }); + if(!validWeapons) { + // enable or create the punch button + var fists = $('#attack_fists'); + if(fists.length == 0) { + Events.createAttackButton('fists').prependTo('#buttons', Events.eventPanel()); + } else { + Button.setDisabled(fists, false); + } + } + } + World.updateSupplies(); + } + var dmg = -1; + if(Math.random() <= World.getHitChance()) { + dmg = weapon.damage; + if(typeof dmg == 'number') { + if(weapon.type == 'unarmed' && Engine.hasPerk('boxer')) { + dmg *= 2 + } + if(weapon.type == 'unarmed' && Engine.hasPerk('martial artist')) { + dmg *= 3; + } + if(weapon.type == 'unarmed' && Engine.hasPerk('unarmed master')) { + dmg *= 2; + } + if(weapon.type == 'melee' && Engine.hasPerk('barbarian')) { + dmg = Math.floor(dmg * 1.5); + } + } + } + + var attackFn = weapon.type == 'ranged' ? Events.animateRanged : Events.animateMelee; + attackFn($('#wanderer'), dmg, function() { + if($('#enemy').data('hp') <= 0 && !Events.won) { + // Success! + Events.winFight(); + } + }); + } + }, + + animateMelee: function(fighter, dmg, callback) { + var start, end, enemy; + if(fighter.attr('id') == 'wanderer') { + start = {'left': '50%'}; + end = {'left': '25%'}; + enemy = $('#enemy'); + } else { + start = {'right': '50%'}; + end = {'right': '25%'}; + enemy = $('#wanderer'); + } + + fighter.stop(true, true).animate(start, Events._FIGHT_SPEED, function() { + var enemyHp = enemy.data('hp'); + var msg; + if(typeof dmg == 'number') { + if(dmg < 0) { + msg = 'miss'; + dmg = 0; + } else { + msg = '-' + dmg; + enemyHp -= dmg; + enemy.data('hp', enemyHp); + if(fighter.attr('id') == 'enemy') { + World.setHp(enemyHp); + } + Events.updateFighterDiv(enemy); + } + } else { + if(dmg == 'stun') { + msg = 'stunned'; + enemy.data('stunned', true); + setTimeout(function() { + enemy.data('stunned', false); + }, Events.STUN_DURATION); + } + } + + Events.drawFloatText(msg, $('.hp', enemy)); + + $(this).animate(end, Events._FIGHT_SPEED, callback); + }); + }, + + animateRanged: function(fighter, dmg, callback) { + var start, end, enemy; + if(fighter.attr('id') == 'wanderer') { + start = {'left': '25%'}; + end = {'left': '50%'}; + enemy = $('#enemy'); + } else { + start = {'right': '25%'}; + end = {'right': '50%'}; + enemy = $('#wanderer'); + } + + $('
').css(start).addClass('bullet').text('o').appendTo('#description') + .animate(end, Events._FIGHT_SPEED * 2, 'linear', function() { + var enemyHp = enemy.data('hp'); + var msg; + if(typeof dmg == 'number') { + if(dmg < 0) { + msg = 'miss'; + dmg = 0; + } else { + msg = '-' + dmg; + enemyHp -= dmg; + enemy.data('hp', enemyHp); + if(fighter.attr('id') == 'enemy') { + World.setHp(enemyHp); + } + Events.updateFighterDiv(enemy); + } + } else { + if(dmg == 'stun') { + msg = 'stunned'; + enemy.data('stunned', true); + setTimeout(function() { + enemy.data('stunned', false); + }, Events.STUN_DURATION); + } + } + + Events.drawFloatText(msg, $('.hp', enemy)); + + $(this).remove(); + if(typeof callback == 'function') { + callback(); + } + }); + }, + + enemyAttack: function() { + + var scene = Events.activeEvent().scenes[Events.activeScene]; + + if(!$('#enemy').data('stunned')) { + var toHit = scene.hit; + toHit *= Engine.hasPerk('evasive') ? 0.8 : 1; + var dmg = -1; + if(Math.random() <= toHit) { + dmg = scene.damage; + } + + var attackFn = scene.ranged ? Events.animateRanged : Events.animateMelee; + + attackFn($('#enemy'), dmg, function() { + if($('#wanderer').data('hp') <= 0) { + // Failure! + clearTimeout(Events._enemyAttackTimer); + Events.endEvent(); + World.die(); + } + }); + } + + Events._enemyAttackTimer = + setTimeout(Events.enemyAttack, scene.attackDelay * 1000); + }, + + winFight: function() { + Events.won = true; + clearTimeout(Events._enemyAttackTimer); + $('#enemy').animate({opacity: 0}, 300, 'linear', function() { + setTimeout(function() { + try { + var scene = Events.activeEvent().scenes[Events.activeScene]; + var desc = $('#description', Events.eventPanel()); + var btns = $('#buttons', Events.eventPanel()); + desc.empty(); + btns.empty(); + $('
').text('the ' + scene.enemy + (scene.plural ? ' are' : ' is') + ' dead.').appendTo(desc); + + Events.drawLoot(scene.loot); + + if(scene.buttons) { + // Draw the buttons + Events.drawButtons(scene); + } else { + new Button.Button({ + id: 'leaveBtn', + click: function() { + var scene = Events.activeEvent().scenes[Events.activeScene]; + if(scene.nextScene && scene.nextScene != 'end') { + Events.loadScene(scene.nextScene); + } else { + Events.endEvent(); + } + }, + text: 'leave' + }).appendTo(btns); + } + } catch(e) { + // It is possible to die and win if the timing is perfect. Just let it fail. + } + }, 1000); + }); + }, + + drawLoot: function(lootList) { + var desc = $('#description', Events.eventPanel()); + var lootButtons = $('
').attr('id', 'lootButtons'); + for(var k in lootList) { + var loot = lootList[k]; + if(Math.random() < loot.chance) { + var num = Math.floor(Math.random() * (loot.max - loot.min)) + loot.min; + new Button.Button({ + id: 'loot_' + k.replace(' ', '-'), + text: k + ' [' + num + ']', + click: Events.getLoot + }).data('numLeft', num).appendTo(lootButtons); + } + } + $('
').addClass('clear').appendTo(lootButtons); + if(lootButtons.children().length > 1) { + lootButtons.appendTo(desc); + } + }, + + dropStuff: function(e) { + e.stopPropagation(); + var btn = $(this) + var thing = btn.data('thing'); + var num = btn.data('num'); + var lootButtons = $('#lootButtons'); + Engine.log('dropping ' + num + ' ' + thing); + + var lootBtn = $('#loot_' + thing.replace(' ', '-'), lootButtons); + if(lootBtn.length > 0) { + var curNum = lootBtn.data('numLeft'); + curNum += num; + lootBtn.text(thing + ' [' + curNum + ']').data('numLeft', curNum); + } else { + new Button.Button({ + id: 'loot_' + thing.replace(' ', '-'), + text: thing + ' [' + num + ']', + click: Events.getLoot + }).data('numLeft', num).insertBefore($('.clear', lootButtons)); + } + Path.outfit[thing] -= num; + Events.getLoot(btn.closest('.button')); + World.updateSupplies(); + $('#dropMenu').remove(); + }, + + getLoot: function(btn) { + var name = btn.attr('id').substring(5).replace('-', ' '); + if(btn.data('numLeft') > 0) { + var weight = Path.getWeight(name); + var freeSpace = Path.getFreeSpace(); + if(weight <= freeSpace) { + var loot = Events.activeEvent().scenes[Events.activeScene].loot[name]; + var num = btn.data('numLeft'); + num--; + btn.data('numLeft', num); + if(num == 0) { + Button.setDisabled(btn); + btn.animate({'opacity':0}, 300, 'linear', function() { + $(this).remove(); + if($('#lootButtons').children().length == 1) { + $('#lootButtons').remove(); + } + }); + } else { + btn.text(name + ' [' + num + ']'); + } + var curNum = Path.outfit[name]; + curNum = typeof curNum == 'number' ? curNum : 0; + curNum++; + Path.outfit[name] = curNum; + World.updateSupplies(); + } else { + // Draw the drop menu + Engine.log('drop menu'); + $('#dropMenu').remove(); + var dropMenu = $('
').attr('id', 'dropMenu'); + for(var k in Path.outfit) { + var itemWeight = Path.getWeight(k); + if(itemWeight > 0) { + var numToDrop = Math.ceil((weight - freeSpace) / itemWeight); + if(numToDrop > Path.outfit[k]) { + numToDrop = Path.outfit[k]; + } + if(numToDrop > 0) { + var dropRow = $('
').attr('id', 'drop_' + k.replace(' ', '-')) + .text(k + ' x' + numToDrop) + .data('thing', k) + .data('num', numToDrop) + .click(Events.dropStuff); + dropRow.appendTo(dropMenu); + } + } + } + dropMenu.appendTo(btn); + btn.one("mouseleave", function() { + $('#dropMenu').remove(); + }); + } + } + }, + + createFighterDiv: function(char, hp, maxhp) { + var fighter = $('
').addClass('fighter').text(char).data('hp', hp).data('maxHp', maxhp); + $('
').addClass('hp').text(hp+'/'+maxhp).appendTo(fighter); + return fighter; + }, + + updateFighterDiv: function(fighter) { + $('.hp', fighter).text(fighter.data('hp') + '/' + fighter.data('maxHp')); + }, + + startStory: function(scene) { + // Write the text + var desc = $('#description', Events.eventPanel()); + for(var i in scene.text) { + $('
').text(scene.text[i]).appendTo(desc); + } + + // Draw any loot + if(scene.loot) { + Events.drawLoot(scene.loot); + } + + // Draw the buttons + Events.drawButtons(scene); + }, + + drawButtons: function(scene) { + var btns = $('#buttons', Events.eventPanel()); + for(var id in scene.buttons) { + var info = scene.buttons[id]; + var b = new Button.Button({ + id: id, + text: info.text, + cost: info.cost, + click: Events.buttonClick + }).appendTo(btns); + if(typeof info.available == 'function' && !info.available()) { + Button.setDisabled(b, true); + } + } + + Events.updateButtons(); + }, + + updateButtons: function() { + var btns = Events.activeEvent().scenes[Events.activeScene].buttons; + for(var bId in btns) { + var b = btns[bId]; + var btnEl = $('#'+bId, Events.eventPanel()); + if(typeof b.available == 'function' && !b.available()) { + Button.setDisabled(btnEl, true); + } else if(b.cost) { + var disabled = false; + for(var store in b.cost) { + var num = Engine.activeModule == World ? Path.outfit[store] : Engine.getStore(store); + if(typeof num != 'number') num = 0; + if(num < b.cost[store]) { + // Too expensive + disabled = true; + break; + } + } + Button.setDisabled(btnEl, disabled); + } + } + }, + + buttonClick: function(btn) { + var info = Events.activeEvent().scenes[Events.activeScene].buttons[btn.attr('id')]; + // Cost + var costMod = {}; + if(info.cost) { + for(var store in info.cost) { + var num = Engine.activeModule == World ? Path.outfit[store] : Engine.getStore(store); + if(typeof num != 'number') num = 0; + if(num < info.cost[store]) { + // Too expensive + return; + } + costMod[store] = -info.cost[store]; + } + if(Engine.activeModule == World) { + for(var k in costMod) { + Path.outfit[k] += costMod[k]; + } + World.updateSupplies(); + } else { + Engine.addStores(costMod); + } + } + + if(typeof info.onChoose == 'function') { + info.onChoose(); + } + + // Reward + if(info.reward) { + Engine.addStores(info.reward); + } + + Events.updateButtons(); + + // Notification + if(info.notification) { + Notifications.notify(null, info.notification); + } + + // Next Scene + if(info.nextScene) { + if(info.nextScene == 'end') { + Events.endEvent(); + } else { + var r = Math.random(); + var lowestMatch = null; + for(var i in info.nextScene) { + if(r < i && (lowestMatch == null || i < lowestMatch)) { + lowestMatch = i; + } + } + if(lowestMatch != null) { + Events.loadScene(info.nextScene[lowestMatch]); + return; + } + Engine.log('ERROR: no suitable scene found'); + Events.endEvent(); + } + } + }, + + // Makes an event happen! + triggerEvent: function() { + if(Events.activeEvent() == null) { + var possibleEvents = []; + for(var i in Events.EventPool) { + var event = Events.EventPool[i]; + if(event.isAvailable()) { + possibleEvents.push(event); + } + } + + if(possibleEvents.length == 0) { + Events.scheduleNextEvent(0.5); + return; + } else { + var r = Math.floor(Math.random()*(possibleEvents.length)); + Events.startEvent(possibleEvents[r]); + } + } + + Events.scheduleNextEvent(); + }, + + triggerFight: function() { + var possibleFights = []; + for(var i in Events.Encounters) { + var fight = Events.Encounters[i]; + if(fight.isAvailable()) { + possibleFights.push(fight); + } + } + + var r = Math.floor(Math.random()*(possibleFights.length)); + Events.startEvent(possibleFights[r]); + }, + + activeEvent: function() { + if(Events.eventStack && Events.eventStack.length > 0) { + return Events.eventStack[0]; + } + return null; + }, + + eventPanel: function() { + return Events.activeEvent().eventPanel; + }, + + startEvent: function(event, options) { + if(event) { + Engine.event('game event', 'event'); + Engine.keyLock = true; + Events.eventStack.unshift(event); + event.eventPanel = $('
').attr('id', 'event').addClass('eventPanel').css('opacity', '0'); + if(options != null && options.width != null) { + Events.eventPanel().css('width', options.width); + } + $('
').addClass('eventTitle').text(Events.activeEvent().title).appendTo(Events.eventPanel()); + $('
').attr('id', 'description').appendTo(Events.eventPanel()); + $('
').attr('id', 'buttons').appendTo(Events.eventPanel()); + Events.loadScene('start'); + $('div#wrapper').append(Events.eventPanel()); + Events.eventPanel().animate({opacity: 1}, Events._PANEL_FADE, 'linear'); + } + }, + + scheduleNextEvent: function(scale) { + var nextEvent = Math.floor(Math.random()*(Events._EVENT_TIME_RANGE[1] - Events._EVENT_TIME_RANGE[0])) + Events._EVENT_TIME_RANGE[0]; + if(scale > 0) { nextEvent *= scale } + Engine.log('next event scheduled in ' + nextEvent + ' minutes'); + Events._eventTimeout = setTimeout(Events.triggerEvent, nextEvent * 60 * 1000); + }, + + endEvent: function() { + Events.eventPanel().animate({opacity:0}, Events._PANEL_FADE, 'linear', function() { + Events.eventPanel().remove(); + Events.activeEvent().eventPanel = null; + Events.eventStack.shift(); + Engine.log(Events.eventStack.length + ' events remaining'); + Engine.keyLock = false; + // Force refocus on the body. I hate you, IE. + $('body').focus(); + }); + } +}; \ No newline at end of file diff --git a/script/events/encounters.js b/script/events/encounters.js new file mode 100644 index 000000000..891f48dc7 --- /dev/null +++ b/script/events/encounters.js @@ -0,0 +1,325 @@ +/** + * Events that can occur when wandering around the world + **/ +Events.Encounters = [ + /* Tier 1 */ + { /* Snarling Beast */ + title: 'A Snarling Beast', + isAvailable: function() { + return World.getDistance() <= 10 && World.getTerrain() == World.TILE.FOREST; + }, + scenes: { + 'start': { + combat: true, + enemy: 'snarling beast', + char: 'B', + damage: 1, + hit: 0.8, + attackDelay: 1, + health: 5, + loot: { + 'fur': { + min: 1, + max: 3, + chance: 1 + }, + 'meat': { + min: 1, + max: 3, + chance: 1 + }, + 'teeth': { + min: 1, + max: 3, + chance: 0.8 + } + }, + notification: 'a snarling beast leaps out of the underbrush' + } + } + }, + { /* Gaunt Man */ + title: 'A Gaunt Man', + isAvailable: function() { + return World.getDistance() <= 10 && World.getTerrain() == World.TILE.BARRENS; + }, + scenes: { + 'start': { + combat: true, + enemy: 'gaunt man', + char: 'G', + damage: 2, + hit: 0.8, + attackDelay: 2, + health: 6, + loot: { + 'cloth': { + min: 1, + max: 3, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 2, + chance: 0.8 + }, + 'leather': { + min: 1, + max: 2, + chance: 0.5 + } + }, + notification: 'a gaunt man approaches, a crazed look in his eye' + } + } + }, + { /* Strange Bird */ + title: 'A Strange Bird', + isAvailable: function() { + return World.getDistance() <= 10 && World.getTerrain() == World.TILE.FIELD; + }, + scenes: { + 'start': { + combat: true, + enemy: 'strange bird', + char: 'B', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 4, + loot: { + 'scales': { + min: 1, + max: 3, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 2, + chance: 0.5 + }, + 'meat': { + min: 1, + max: 3, + chance: 0.8 + } + }, + notification: 'a strange looking bird speeds across the plains' + } + } + }, + /* Tier 2*/ + { /* Man-eater */ + title: 'A Man-Eater', + isAvailable: function() { + return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.FOREST; + }, + scenes: { + 'start': { + combat: true, + enemy: 'man-eater', + char: 'E', + damage: 3, + hit: 0.8, + attackDelay: 1, + health: 25, + loot: { + 'fur': { + min: 5, + max: 10, + chance: 1 + }, + 'meat': { + min: 5, + max: 10, + chance: 1 + }, + 'teeth': { + min: 5, + max: 10, + chance: 0.8 + } + }, + notification: 'a large creature attacks, claws freshly bloodied' + } + } + }, + { /* Scavenger */ + title: 'A Scavenger', + isAvailable: function() { + return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.BARRENS; + }, + scenes: { + 'start': { + combat: true, + enemy: 'scavenger', + char: 'S', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'iron': { + min: 1, + max: 5, + chance: 0.5 + } + }, + notification: 'a scavenger draws close, hoping for an easy score' + } + } + }, + { /* Huge Lizard */ + title: 'A Huge Lizard', + isAvailable: function() { + return World.getDistance() > 10 && World.getDistance() <= 20 && World.getTerrain() == World.TILE.FIELD; + }, + scenes: { + 'start': { + combat: true, + enemy: 'lizard', + char: 'L', + damage: 5, + hit: 0.8, + attackDelay: 2, + health: 20, + loot: { + 'scales': { + min: 5, + max: 10, + chance: 0.8 + }, + 'teeth': { + min: 5, + max: 10, + chance: 0.5 + }, + 'meat': { + min: 5, + max: 10, + chance: 0.8 + } + }, + notification: 'the grass thrashes wildly as a huge lizard pushes through' + } + } + }, + /* Tier 3*/ + { /* Feral Terror */ + title: 'A Feral Terror', + isAvailable: function() { + return World.getDistance() > 20 && World.getTerrain() == World.TILE.FOREST; + }, + scenes: { + 'start': { + combat: true, + enemy: 'feral terror', + char: 'F', + damage: 6, + hit: 0.8, + attackDelay: 1, + health: 45, + loot: { + 'fur': { + min: 5, + max: 10, + chance: 1 + }, + 'meat': { + min: 5, + max: 10, + chance: 1 + }, + 'teeth': { + min: 5, + max: 10, + chance: 0.8 + } + }, + notification: 'a beast, wilder than imagining, erupts out of the foliage' + } + } + }, + { /* Soldier */ + title: 'A Soldier', + isAvailable: function() { + return World.getDistance() > 20 && World.getTerrain() == World.TILE.BARRENS; + }, + scenes: { + 'start': { + combat: true, + enemy: 'soldier', + ranged: true, + char: 'D', + damage: 8, + hit: 0.8, + attackDelay: 2, + health: 50, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + notification: 'a soldier opens fire from across the desert' + } + } + }, + { /* Sniper */ + title: 'A Sniper', + isAvailable: function() { + return World.getDistance() > 20 && World.getTerrain() == World.TILE.FIELD; + }, + scenes: { + 'start': { + combat: true, + enemy: 'sniper', + char: 'S', + damage: 15, + hit: 0.8, + attackDelay: 4, + health: 30, + ranged: true, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + notification: 'a shot rings out, from somewhere in the long grass' + } + } + }, +]; \ No newline at end of file diff --git a/script/events/global.js b/script/events/global.js new file mode 100644 index 000000000..64f402158 --- /dev/null +++ b/script/events/global.js @@ -0,0 +1,65 @@ +/** + * Events that can occur when any module is active (Except World. It's special.) + **/ +Events.Global = [ + { /* The Thief */ + title: 'The Thief', + isAvailable: function() { + return (Engine.activeModule == Room || Engine.activeModule == Outside) && State.thieves == 1; + }, + scenes: { + 'start': { + text: [ + 'the villagers haul a filthy man out of the store room.', + "say his folk have been skimming the supplies.", + 'say he should be strung up as an example.' + ], + notification: 'a thief is caught', + buttons: { + 'kill': { + text: 'hang him', + nextScene: {1: 'hang'} + }, + 'spare': { + text: 'spare him', + nextScene: {1: 'spare'} + } + } + }, + 'hang': { + text: [ + 'the villagers hang the thief high in front of the store room.', + 'the point is made. in the next few days, the missing supplies are returned.' + ], + onLoad: function() { + State.thieves = 2; + Engine.removeIncome('thieves'); + Engine.addStores(State.stolen); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'spare': { + text: [ + "the man says he's grateful. says he won't come around any more.", + "shares what he knows about sneaking before he goes." + ], + onLoad: function() { + State.thieves = 2; + Engine.removeIncome('thieves'); + Engine.addPerk('stealthy'); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + } +]; \ No newline at end of file diff --git a/script/events/outside.js b/script/events/outside.js new file mode 100644 index 000000000..c6a1db02e --- /dev/null +++ b/script/events/outside.js @@ -0,0 +1,127 @@ +/** + * Events that can occur when the Outside module is active + **/ +Events.Outside = [ + { /* Ruined traps */ + title: 'A Ruined Trap', + isAvailable: function() { + return Engine.activeModule == Outside && Outside.numBuilding('trap') > 0; + }, + scenes: { + 'start': { + text: [ + 'some of the traps have been torn apart.', + 'large prints lead away, into the forest.' + ], + onLoad: function() { + var numWrecked = Math.floor(Math.random() * Outside.numBuilding('trap')) + 1; + Outside.addBuilding('trap', -numWrecked); + Outside.updateVillage(); + Outside.updateTrapButton(); + }, + notification: 'some traps have been destroyed', + buttons: { + 'track': { + text: 'track them', + nextScene: {0.5: 'nothing', 1: 'catch'} + }, + 'ignore': { + text: 'ignore them', + nextScene: 'end' + } + } + }, + 'nothing': { + text: [ + 'the tracks disappear after just a few minutes.', + 'the forest is silent.' + ], + buttons: { + 'end': { + text: 'go home', + nextScene: 'end' + } + } + }, + 'catch': { + text: [ + 'not far from the village lies a large beast, its fur matted with blood.', + 'it puts up little resistance before the knife.' + ], + reward: { + fur: 100, + meat: 100, + teeth: 10 + }, + buttons: { + 'end': { + text: 'go home', + nextScene: 'end' + } + } + } + } + }, + + { /* Beast attack */ + title: 'A Beast Attack', + isAvailable: function() { + return Engine.activeModule == Outside && Outside.getPopulation() > 0; + }, + scenes: { + 'start': { + text: [ + 'a pack of snarling beasts pours out of the trees.', + 'the fight is short and bloody, but the beasts are repelled.', + 'the villagers retreat to mourn the dead.' + ], + onLoad: function() { + var numKilled = Math.floor(Math.random() * 10) + 1; + Outside.killVillagers(numKilled); + }, + reward: { + fur: 100, + meat: 100, + teeth: 10 + }, + buttons: { + 'end': { + text: 'go home', + nextScene: 'end' + } + } + } + } + }, + + { /* Soldier attack */ + title: 'A Military Raid', + isAvailable: function() { + return Engine.activeModule == Outside && Outside.getPopulation() > 0 && State.cityCleared; + }, + scenes: { + 'start': { + text: [ + 'a gunshot rings through the trees.', + 'well armed men charge out of the forest, firing into the crowd.', + 'after a skirmish they are driven away, but not without losses.' + ], + onLoad: function() { + var numKilled = Math.floor(Math.random() * 40) + 1; + Outside.killVillagers(numKilled); + }, + reward: { + bullets: 10, + 'cured meat': 50 + }, + buttons: { + 'end': { + text: 'go home', + nextScene: 'end' + } + } + } + } + } +]; + \ No newline at end of file diff --git a/script/events/room.js b/script/events/room.js new file mode 100644 index 000000000..242342fbe --- /dev/null +++ b/script/events/room.js @@ -0,0 +1,506 @@ +/** + * Events that can occur when the Room module is active + **/ +Events.Room = [ + { /* The Nomad -- Merchant */ + title: 'The Nomad', + isAvailable: function() { + return Engine.activeModule == Room && Engine.getStore('fur') > 0; + }, + scenes: { + 'start': { + text: [ + 'a nomad shuffles into view, laden with makeshift bags bound with rough twine.', + "won't say from where he came, but it's clear that he's not staying." + ], + notification: 'a nomad arrives, looking to trade', + buttons: { + 'buyScales': { + text: 'buy scales', + cost: { 'fur': 100 }, + reward: { 'scales': 1 } + }, + 'buyTeeth': { + text: 'buy teeth', + cost: { 'fur': 200 }, + reward: { 'teeth': 1 } + }, + 'buyBait': { + text: 'buy bait', + cost: { 'fur': 5 }, + reward: { 'bait': 1 }, + notification: 'traps are more effective with bait.' + }, + 'buyCompass': { + available: function() { + return Engine.getStore('compass') < 1; + }, + text: 'buy compass', + cost: { fur: 300, scales: 15, teeth: 5 }, + reward: { 'compass': 1 }, + notification: 'the old compass is dented and dusty, but it looks to work.', + onChoose: Engine.openPath + }, + 'goodbye': { + text: 'say goodbye', + nextScene: 'end' + } + } + } + } + }, { /* Noises Outside -- gain wood/fur */ + title: 'Noises', + isAvailable: function() { + return Engine.activeModule == Room && Engine.storeAvailable('wood'); + }, + scenes: { + 'start': { + text: [ + 'through the walls, shuffling noises can be heard.', + "can't tell what they're up to." + ], + notification: 'strange noises can be heard through the walls', + buttons: { + 'investigate': { + text: 'investigate', + nextScene: { 0.3: 'stuff', 1: 'nothing' } + }, + 'ignore': { + text: 'ignore them', + nextScene: 'end' + } + } + }, + 'nothing': { + text: [ + 'vague shapes move, just out of sight.', + 'the sounds stop.' + ], + buttons: { + 'backinside': { + text: 'go back inside', + nextScene: 'end' + } + } + }, + 'stuff': { + reward: { wood: 100, fur: 10 }, + text: [ + 'a bundle of stick lies just beyond the threshold, wrapped in course furs.', + 'the night is silent.' + ], + buttons: { + 'backinside': { + text: 'go back inside', + nextScene: 'end' + } + } + } + } + }, + { /* Noises Inside -- trade wood for better good */ + title: 'Noises', + isAvailable: function() { + return Engine.activeModule == Room && Engine.storeAvailable('wood'); + }, + scenes: { + start: { + text: [ + 'scratching noises can be heard from the store room.', + 'something\'s in there.' + ], + notification: 'something\'s in the store room', + buttons: { + 'investigate': { + text: 'investigate', + nextScene: { 0.5: 'scales', 0.8: 'teeth', 1: 'cloth' } + }, + 'ignore': { + text: 'ignore them', + nextScene: 'end' + } + } + }, + scales: { + text: [ + 'some wood is missing.', + 'the ground is littered with small scales' + ], + onLoad: function() { + var numWood = Engine.getStore('wood'); + numWood = Math.floor(numWood * 0.1); + if(numWood == 0) numWood = 1; + var numScales = Math.floor(numWood / 5); + if(numScales == 0) numScales = 1; + Engine.addStores({wood: -numWood, scales: numScales}); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + teeth: { + text: [ + 'some wood is missing.', + 'the ground is littered with small teeth' + ], + onLoad: function() { + var numWood = Engine.getStore('wood'); + numWood = Math.floor(numWood * 0.1); + if(numWood == 0) numWood = 1; + var numTeeth = Math.floor(numWood / 5); + if(numTeeth == 0) numTeeth = 1; + Engine.addStores({wood: -numWood, teeth: numTeeth}); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + cloth: { + text: [ + 'some wood is missing.', + 'the ground is littered with scraps of cloth' + ], + onLoad: function() { + var numWood = Engine.getStore('wood'); + numWood = Math.floor(numWood * 0.1); + if(numWood == 0) numWood = 1; + var numCloth = Math.floor(numWood / 5); + if(numCloth == 0) numCloth = 1; + Engine.addStores({wood: -numWood, cloth: numCloth}); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + { /* The Beggar -- trade fur for better good */ + title: 'The Beggar', + isAvailable: function() { + return Engine.activeModule == Room && Engine.storeAvailable('fur'); + }, + scenes: { + start: { + text: [ + 'a beggar arrives.', + 'asks for any spare furs to keep him warm at night.' + ], + notification: 'a beggar arrives', + buttons: { + '50furs': { + text: 'give 50', + cost: {fur: 50}, + nextScene: { 0.5: 'scales', 0.8: 'teeth', 1: 'cloth' } + }, + '100furs': { + text: 'give 100', + cost: {fur: 100}, + nextScene: { 0.5: 'teeth', 0.8: 'scales', 1: 'cloth' } + }, + 'deny': { + text: 'turn him away', + nextScene: 'end' + } + } + }, + scales: { + reward: { scales: 20 }, + text: [ + 'the beggar thanks you.', + 'leaves a pile of small scales behind.' + ], + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + }, + teeth: { + reward: { teeth: 20 }, + text: [ + 'the beggar thanks you.', + 'leaves a pile of small teeth behind.' + ], + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + }, + cloth: { + reward: { cloth: 20 }, + text: [ + 'the beggar thanks you.', + 'leaves some scraps of cloth behind.' + ], + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + } + } + }, + + { /* Mysterious Wanderer -- wood gambling */ + title: 'The Mysterious Wanderer', + isAvailable: function() { + return Engine.activeModule == Room && Engine.storeAvailable('wood'); + }, + scenes: { + start: { + text: [ + 'a wanderer arrives with an empty cart. says if he leaves with wood, he\'ll be back with more.', + "builder's not sure he's to be trusted." + ], + notification: 'a mysterious wanderer arrives', + buttons: { + '100wood': { + text: 'give 100', + cost: {wood: 100}, + nextScene: { 1: '100wood'} + }, + '500wood': { + text: 'give 500', + cost: {wood: 500}, + nextScene: { 1: '500wood' } + }, + 'deny': { + text: 'turn him away', + nextScene: 'end' + } + } + }, + '100wood': { + text: [ + 'the wanderer leaves, cart loaded with wood', + ], + onLoad: function() { + if(Math.random() < 0.5) { + setTimeout(function() { + Engine.addStore('wood', 300); + Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with wood.'); + }, 60 * 1000); + } + }, + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + }, + '500wood': { + text: [ + 'the wanderer leaves, cart loaded with wood', + ], + onLoad: function() { + if(Math.random() < 0.3) { + setTimeout(function() { + Engine.addStore('wood', 1500); + Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with wood.'); + }, 60 * 1000); + } + }, + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + } + } + }, + + { /* Mysterious Wanderer -- fur gambling */ + title: 'The Mysterious Wanderer', + isAvailable: function() { + return Engine.activeModule == Room && Engine.storeAvailable('fur'); + }, + scenes: { + start: { + text: [ + 'a wanderer arrives with an empty cart. says if she leaves with furs, she\'ll be back with more.', + "builder's not sure she's to be trusted." + ], + notification: 'a mysterious wanderer arrives', + buttons: { + '100fur': { + text: 'give 100', + cost: {fur: 100}, + nextScene: { 1: '100fur'} + }, + '500fur': { + text: 'give 500', + cost: {fur: 500}, + nextScene: { 1: '500fur' } + }, + 'deny': { + text: 'turn her away', + nextScene: 'end' + } + } + }, + '100fur': { + text: [ + 'the wanderer leaves, cart loaded with furs', + ], + onLoad: function() { + if(Math.random() < 0.5) { + setTimeout(function() { + Engine.addStore('fur', 300); + Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with furs.'); + }, 60 * 1000); + } + }, + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + }, + '500fur': { + text: [ + 'the wanderer leaves, cart loaded with furs', + ], + onLoad: function() { + if(Math.random() < 0.3) { + setTimeout(function() { + Engine.addStore('fur', 1500); + Notifications.notify(Room, 'the mysterious wanderer returns, cart piled high with furs.'); + }, 60 * 1000); + } + }, + buttons: { + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + } + } + }, + + { /* The Scout -- Map Merchant */ + title: 'The Scout', + isAvailable: function() { + return Engine.activeModule == Room && typeof State.world == 'object'; + }, + scenes: { + 'start': { + text: [ + "the scout says she's been all over.", + "willing to talk about it, for a price." + ], + notification: 'a scout stops for the night', + buttons: { + 'buyMap': { + text: 'buy map', + cost: { 'fur': 200, 'scales': 10 }, + notification: 'the map uncovers a bit of the world', + onChoose: World.applyMap + }, + 'learn': { + text: 'learn scouting', + cost: { 'fur': 1000, 'scales': 50, 'teeth': 20 }, + available: function() { + return !Engine.hasPerk('scout'); + }, + onChoose: function() { + Engine.addPerk('scout'); + } + }, + 'leave': { + text: 'say goodbye', + nextScene: 'end' + } + } + } + } + }, + + { /* The Wandering Master */ + title: 'The Master', + isAvailable: function() { + return Engine.activeModule == Room && typeof State.world == 'object'; + }, + scenes: { + 'start': { + text: [ + 'an old wanderer arrives.', + 'he smiles warmly and asks for lodgings for the night.' + ], + notification: 'an old wanderer arrives', + buttons: { + 'agree': { + text: 'agree', + cost: { + 'cured meat': 100, + 'fur': 100, + 'torch': 1 + }, + nextScene: {1: 'agree'} + }, + 'deny': { + text: 'turn him away', + nextScene: 'end' + } + } + }, + 'agree': { + text: [ + 'in exchange, the wanderer offers his wisdom.' + ], + buttons: { + 'evasion': { + text: 'evasion', + available: function() { + return !Engine.hasPerk('evasive'); + }, + onChoose: function() { + Engine.addPerk('evasive'); + }, + nextScene: 'end' + }, + 'precision': { + text: 'precision', + available: function() { + return !Engine.hasPerk('precise'); + }, + onChoose: function() { + Engine.addPerk('precise'); + }, + nextScene: 'end' + }, + 'force': { + text: 'force', + available: function() { + return !Engine.hasPerk('barbarian'); + }, + onChoose: function() { + Engine.addPerk('barbarian'); + }, + nextScene: 'end' + }, + 'nothing': { + text: 'nothing', + nextScene: 'end' + } + } + } + } + } +] \ No newline at end of file diff --git a/script/events/setpieces.js b/script/events/setpieces.js new file mode 100644 index 000000000..1cf518d0a --- /dev/null +++ b/script/events/setpieces.js @@ -0,0 +1,2730 @@ +/** + * Events that only occur at specific times. Launched manually. + **/ +Events.Setpieces = { + "outpost": { /* Friendly Outpost */ + title: 'An Outpost', + scenes: { + 'start': { + text: [ + 'a safe place in the wilds.' + ], + notification: 'a safe place in the wilds.', + loot: { + 'cured meat': { + min: 5, + max: 10, + chance: 1 + } + }, + onLoad: function() { + World.useOutpost(); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "swamp": { /* Swamp */ + title: 'A Murky Swamp', + scenes: { + 'start': { + text: [ + 'rotting reeds rise out of the swampy earth.', + 'a lone frog sits in the muck, silently.' + ], + notification: 'a swamp festers in the stagnant air.', + buttons: { + 'enter': { + text: 'enter', + nextScene: {1: 'cabin'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'cabin': { + text: [ + 'deep in the swamp is a moss-covered cabin.', + 'an old wanderer sits inside, in a seeming trance.' + ], + buttons: { + 'talk': { + cost: {'charm': 1}, + text: 'talk', + nextScene: {1: 'talk'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'talk': { + text: [ + 'the wanderer takes the charm and nods slowly.', + 'he speaks of once leading the great fleets to fresh worlds.', + 'unfathomable destruction to fuel wanderer hungers.', + 'his time here, now, is his penance.' + ], + onLoad: function() { + Engine.addPerk('gastronome'); + World.markVisited(World.curPos[0], World.curPos[1]); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "cave": { /* Cave */ + title: 'A Damp Cave', + scenes: { + 'start': { + text: [ + 'the mouth of the cave is wide and dark.', + "can't see what's inside." + ], + notification: 'the earth here is split, as if bearing an ancient wound', + buttons: { + 'enter': { + text: 'go inside', + cost: { torch: 1 }, + nextScene: {0.3: 'a1', 0.6: 'a2', 1: 'a3'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + + 'a1': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 1, + hit: 0.8, + attackDelay: 1, + health: 5, + notification: 'a startled beast defends its home', + loot: { + 'fur': { + min: 1, + max: 10, + chance: 1 + }, + 'teeth': { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b1', 1: 'b2'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'a2': { + text: [ + 'the cave narrows a few feet in.', + "the walls are moist and moss-covered" + ], + buttons: { + 'continue': { + text: 'squeeze', + nextScene: {0.5: 'b2', 1: 'b3'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'a3': { + text: [ + 'the remains of an old camp sits just inside the cave.', + 'bedrolls, torn and blackened, lay beneath a thin layer of dust.' + ], + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 1 + }, + 'torch': { + min: 1, + max: 5, + chance: 0.5 + }, + 'leather': { + min: 1, + max: 5, + chance: 0.3 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b3', 1: 'b4'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'b1': { + text: [ + 'the body of a wanderer lies in a small cavern.', + "rot's been to work on it, and some of the pieces are missing.", + "can't tell what left it here." + ], + loot: { + 'iron sword': { + min: 1, + max: 1, + chance: 1 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'torch': { + min: 1, + max: 3, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'c1' } + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'b2': { + text: [ + 'the torch sputters and dies in the damp air', + 'the darkness is absolute' + ], + notification: 'the torch goes out', + buttons: { + 'continue': { + text: 'continue', + cost: {'torch': 1}, + nextScene: { 1: 'c1' } + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'b3': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 1, + hit: 0.8, + attackDelay: 1, + health: 5, + notification: 'a startled beast defends its home', + loot: { + 'fur': { + min: 1, + max: 3, + chance: 1 + }, + 'teeth': { + min: 1, + max: 2, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'c2'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'b4': { + combat: true, + enemy: 'cave lizard', + char: 'L', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 6, + notification: 'a cave lizard attacks', + loot: { + 'scales': { + min: 1, + max: 3, + chance: 1 + }, + 'teeth': { + min: 1, + max: 2, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'c2'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'c1': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 10, + notification: 'a large beast charges out of the dark', + loot: { + 'fur': { + min: 1, + max: 3, + chance: 1 + }, + 'teeth': { + min: 1, + max: 3, + chance: 1 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end1', 1: 'end2'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'c2': { + combat: true, + enemy: 'lizard', + char: 'L', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 10, + notification: 'a giant lizard shambles forward', + loot: { + 'scales': { + min: 1, + max: 3, + chance: 1 + }, + 'teeth': { + min: 1, + max: 3, + chance: 1 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.7: 'end2', 1: 'end3'} + }, + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'end1': { + text: [ + 'the nest of a large animal lies at the back of the cave.' + ], + onLoad: function() { + World.clearDungeon(); + }, + loot: { + 'meat': { + min: 5, + max: 10, + chance: 1 + }, + 'fur': { + min: 5, + max: 10, + chance: 1 + }, + 'scales': { + min: 5, + max: 10, + chance: 1 + }, + 'teeth': { + min: 5, + max: 10, + chance: 1 + }, + 'cloth': { + min: 5, + max: 10, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'end2': { + text: [ + 'a small supply cache is hidden at the back of the cave.' + ], + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 1 + }, + 'leather': { + min: 5, + max: 10, + chance: 1 + }, + 'iron': { + min: 5, + max: 10, + chance: 1 + }, + 'cured meat': { + min: 5, + max: 10, + chance: 1 + }, + 'steel': { + min: 5, + max: 10, + chance: 0.5 + }, + 'bolas': { + min: 1, + max: 3, + chance: 0.3 + } + }, + onLoad: function() { + World.clearDungeon(); + }, + buttons: { + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + }, + 'end3': { + text: [ + 'an old case is wedged behind a rock, covered in a thick layer of dust.' + ], + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 1 + }, + 'bolas': { + min: 1, + max: 3, + chance: 0.5 + } + }, + onLoad: function() { + World.clearDungeon(); + }, + buttons: { + 'leave': { + text: 'leave cave', + nextScene: 'end' + } + } + } + } + }, + "town": { /* Town */ + title: 'A Deserted Town', + scenes: { + 'start': { + text: [ + 'a small suburb lays ahead, empty houses scorched and peeling.', + "broken streetlights stand, rusting. light hasn't graced this place in a long time." + ], + notification: "the town lies abandoned, its citizens long dead", + buttons: { + 'enter': { + text: 'explore', + nextScene: {0.5: 'a1', 1: 'a2'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + + 'a1': { + text: [ + "where the windows of the schoolhouse aren't shattered, they're blackened with soot.", + 'the double doors creak endlessly in the wind.' + ], + buttons: { + 'enter': { + text: 'enter', + nextScene: {0.5: 'b1', 1: 'b2'}, + cost: {torch: 1} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'a2': { + combat: true, + enemy: 'thug', + char: 'T', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.5 + } + }, + notification: 'ambushed on the street.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b3', 1: 'b4'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'b1': { + text: [ + 'a small cache of supplies is tucked inside a rusting locker.' + ], + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 1 + }, + 'torch': { + min: 1, + max: 3, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.3 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c1', 1: 'c2'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'b2': { + combat: true, + enemy: 'scavenger', + char: 'S', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.5 + } + }, + notification: 'a scavenger waits just inside the door.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c2', 1: 'c3'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'b3': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 3, + hit: 0.8, + attackDelay: 1, + health: 25, + loot: { + 'teeth': { + min: 1, + max: 5, + chance: 1 + }, + 'fur': { + min: 5, + max: 10, + chance: 1 + } + }, + notification: 'a beast stands alone in an overgrown park.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c4', 1: 'c5'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'b4': { + text: [ + 'an overturned caravan is spread across the pockmarked street.', + "it's been picked over by scavengers, but there's still some things worth taking." + ], + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'torch': { + min: 1, + max: 3, + chance: 0.5 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.3 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c5', 1: 'c6' } + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c1': { + combat: true, + enemy: 'thug', + char: 'T', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.5 + } + }, + notification: 'a thug moves out of the shadows.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd1'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c2': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 3, + hit: 0.8, + attackDelay: 1, + health: 25, + loot: { + 'teeth': { + min: 1, + max: 5, + chance: 1 + }, + 'fur': { + min: 5, + max: 10, + chance: 1 + } + }, + notification: 'a beast charges out of a ransacked classroom.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd1'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c3': { + text: [ + 'through the large gymnasium doors, footsteps can be heard.', + 'the torchlight casts a flickering glow down the hallway.', + 'the footsteps stop.' + ], + buttons: { + 'continue': { + text: 'enter', + nextScene: {1: 'd1'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c4': { + combat: true, + enemy: 'beast', + char: 'B', + damage: 4, + hit: 0.8, + attackDelay: 1, + health: 25, + loot: { + 'teeth': { + min: 1, + max: 5, + chance: 1 + }, + 'fur': { + min: 5, + max: 10, + chance: 1 + } + }, + notification: 'another beast, draw by the noise, leaps out of a copse of trees.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd2'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c5': { + text: [ + "something's causing a commotion a ways down the road.", + "a fight, maybe." + ], + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd2'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'c6': { + text: [ + 'a small basket of food is hidden under a park bench, with a note attached.', + "can't read the words." + ], + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 1 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd2'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'd1': { + combat: true, + enemy: 'scavenger', + char: 'S', + damage: 5, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 1 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'steel sword': { + min: 1, + max: 1, + chance: 0.5 + } + }, + notification: 'a panicked scavenger bursts through the door, screaming.', + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end1', 1: 'end2'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'd2': { + combat: true, + enemy: 'vigilante', + char: 'V', + damage: 6, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 1 + }, + 'leather': { + min: 5, + max: 10, + chance: 0.8 + }, + 'steel sword': { + min: 1, + max: 1, + chance: 0.5 + } + }, + notification: "a man stands over a dead wanderer. notices he's not alone.", + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end3', 1: 'end4'} + }, + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'end1': { + text: [ + 'scavenger had a small camp in the school.', + 'collected scraps spread across the floor like they fell from heaven.' + ], + onLoad: function() { + World.clearDungeon(); + }, + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 1 + }, + 'steel': { + min: 5, + max: 10, + chance: 1 + }, + 'cured meat': { + min: 5, + max: 10, + chance: 1 + }, + 'bolas': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'end2': { + text: [ + "scavenger'd been looking for supplies in here, it seems.", + "a shame to let what he'd found go to waste." + ], + onLoad: function() { + World.clearDungeon(); + }, + loot: { + 'coal': { + min: 5, + max: 10, + chance: 1 + }, + 'cured meat': { + min: 5, + max: 10, + chance: 1 + }, + 'leather': { + min: 5, + max: 10, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'end3': { + text: [ + "beneath the wanderer's rags, clutched in one of its many hands, a glint of steel.", + "worth killing for, it seems." + ], + onLoad: function() { + World.clearDungeon(); + }, + loot: { + 'rifle': { + min: 1, + max: 1, + chance: 1 + }, + 'bullets': { + min: 1, + max: 5, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + }, + 'end4': { + text: [ + "eye for an eye seems fair.", + "always worked before, at least.", + "picking the bones finds some useful trinkets." + ], + onLoad: function() { + World.clearDungeon(); + }, + loot: { + 'cured meat': { + min: 5, + max: 10, + chance: 1 + }, + 'iron': { + min: 5, + max: 10, + chance: 1 + }, + 'torch': { + min: 1, + max: 5, + chance: 1 + }, + 'bolas': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave town', + nextScene: 'end' + } + } + } + } + }, + "city": { /* City */ + title: 'A Ruined City', + scenes: { + 'start': { + text: [ + 'a battered highway sign stands guard at the entrance to this once-great city.', + "the towers that haven't crumbled jut from the landscape like the ribcage of some ancient beast.", + 'might be things worth having still inside.' + ], + notification: "the towers of a decaying city dominate the skyline", + buttons: { + 'enter': { + text: 'explore', + nextScene: {0.4: 'a1', 0.8: 'a2', 1: 'a3'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'a1': { + text:[ + 'the streets are empty.', + 'the air is filled with dust, driven relentlessly by the hard winds.' + ], + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b1', 1: 'b2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'a2': { + text:[ + 'orange traffic cones are set across the street, faded and cracked.', + 'lights flash through the alleys between buildings.' + ], + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b3', 1: 'b4'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'a3': { + text: [ + 'a large shanty town sprawls across the streets.', + 'faces, darkened by soot and blood, stare out from crooked huts.', + ], + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'b5', 1: 'b6'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b1': { + text: [ + 'the old tower seems mostly intact.', + 'the shell of a burned out car blocks the entrance.', + 'most of the windows at ground level are busted anyway.' + ], + buttons: { + 'enter': { + text: 'enter', + nextScene: {0.5: 'c1', 1: 'c2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b2': { + combat: true, + notification: 'a huge lizard scrambles up out of the darkness of an old metro station.', + enemy: 'lizard', + char: 'L', + damage: 5, + hit: 0.8, + attackDelay: 2, + health: 20, + loot: { + 'scales': { + min: 5, + max: 10, + chance: 0.8 + }, + 'teeth': { + min: 5, + max: 10, + chance: 0.5 + }, + 'meat': { + min: 5, + max: 10, + chance: 0.8 + } + }, + buttons: { + 'descend': { + text: 'descend', + nextScene: {0.5: 'c2', 1: 'c3'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b3': { + notification: 'the shot echoes in the empty street.', + combat: true, + enemy: 'sniper', + char: 'S', + damage: 15, + hit: 0.8, + attackDelay: 4, + health: 30, + ranged: true, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c4', 1: 'c5'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b4': { + notification: 'the soldier steps out from between the buildings, rifle raised.', + combat: true, + enemy: 'soldier', + ranged: true, + char: 'D', + damage: 8, + hit: 0.8, + attackDelay: 2, + health: 50, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c5', 1: 'c6'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b5': { + notification: 'a frail man stands defiantly, blocking the path.', + combat: true, + enemy: 'frail man', + char: 'M', + damage: 1, + hit: 0.8, + attackDelay: 2, + health: 10, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'cloth': { + min: 1, + max: 5, + chance: 0.5 + }, + 'leather': { + min: 1, + max: 1, + chance: 0.2 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c7', 1: 'c8'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'b6': { + text: [ + 'nothing but downcast eyes.', + 'the people here were broken a long time ago.' + ], + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'c8', 1: 'c9'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + 'c1': { + notification: 'a thug is waiting on the other side of the wall.', + combat: true, + enemy: 'thug', + char: 'T', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 30, + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 3, + chance: 0.5 + }, + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'd1', 1: 'd2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c2': { + notification: 'a snarling beast jumps out from behind a car.', + combat: true, + enemy: 'beast', + char: 'B', + damage: 2, + hit: 0.8, + attackDelay: 1, + health: 30, + loot: { + 'meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'fur': { + min: 1, + max: 5, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'd2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c3': { + text: [ + 'street above the subway platform is blown away.', + 'lets some light down into the dusty haze.', + 'a sound comes from the tunnel, just ahead.' + ], + buttons: { + 'enter': { + text: 'investigate', + cost: { 'torch': 1 }, + nextScene: {0.5: 'd2', 1: 'd3'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c4': { + text: [ + 'looks like a camp of sorts up ahead.', + 'rusted chainlink is pulled across an alleyway.', + 'fires burn in the courtyard beyond.' + ], + buttons: { + 'enter': { + text: 'continue', + nextScene: {0.5: 'd4', 1: 'd5'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c5': { + text: [ + 'more voices can be heard ahead.', + 'they must be here for a reason.' + ], + buttons: { + 'enter': { + text: 'continue', + nextScene: {1: 'd5'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c6': { + text: [ + 'the sound of gunfire carries on the wind.', + 'the street ahead glows with firelight.' + ], + buttons: { + 'enter': { + text: 'continue', + nextScene: {0.5: 'd5', 1: 'd6'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c7': { + text: [ + 'more squatters are crowding around now.', + 'someone throws a stone.' + ], + buttons: { + 'enter': { + text: 'continue', + nextScene: {0.5: 'd7', 1: 'd8'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c8': { + text: [ + 'an improvised shop is set up on the sidewalk.', + 'the owner stands by, stoic.' + ], + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 0.8 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.5 + }, + 'bullets': { + min: 1, + max: 8, + chance: 0.25 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.01 + } + }, + buttons: { + 'enter': { + text: 'continue', + nextScene: {1: 'd8'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'c9': { + text: [ + 'strips of meat hang drying by the side of the street.', + 'the people back away, avoiding eye contact.' + ], + loot: { + 'cured meat': { + min: 5, + max: 10, + chance: 1 + } + }, + buttons: { + 'enter': { + text: 'continue', + nextScene: {0.5: 'd8', 1: 'd9'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd1': { + notification: 'a large bird nests at the top of the stairs.', + combat: true, + enemy: 'bird', + char: 'B', + damage: 5, + hit: 0.7, + attackDelay: 1, + health: 45, + loot: { + 'meat': { + min: 5, + max: 10, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end1', 1: 'end2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd2': { + text: [ + "the debris is denser here.", + "maybe some useful stuff in the rubble." + ], + loot: { + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'steel': { + min: 1, + max: 10, + chance: 0.8 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.01 + }, + 'cloth': { + min: 1, + max: 10, + chance: 1 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'end2'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd3': { + notification: 'a swarm of rats rushes up the tunnel.', + combat: true, + enemy: 'rats', + plural: true, + char: 'RRR', + damage: 1, + hit: 0.8, + attackDelay: 0.25, + health: 60, + loot: { + 'fur': { + min: 5, + max: 10, + chance: 0.8 + }, + 'teeth': { + min: 5, + max: 10, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end2', 1: 'end3'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd4': { + notification: 'a large man attacks, waving a bayonet.', + combat: true, + enemy: 'veteran', + char: 'V', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 45, + loot: { + 'bayonet': { + min: 1, + max: 1, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end4', 1: 'end5'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd5': { + notification: 'a second soldier opens fire.', + combat: true, + enemy: 'soldier', + ranged: true, + char: 'D', + damage: 8, + hit: 0.8, + attackDelay: 2, + health: 50, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'end5'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd6': { + notification: 'a masked soldier rounds the corner, gun drawn', + combat: true, + enemy: 'commando', + char: 'C', + ranged: true, + damage: 3, + hit: 0.9, + attackDelay: 2, + health: 55, + loot: { + 'rifle': { + min: 1, + max: 1, + chance: 0.5 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.8 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end5', 1: 'end6'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd7': { + notification: 'the crowd surges forward.', + combat: true, + enemy: 'squatters', + plural: true, + char: 'SSS', + damage: 2, + hit: 0.7, + attackDelay: 0.5, + health: 40, + loot: { + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end7', 1: 'end8'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd8': { + notification: 'a youth lashes out with a tree branch.', + combat: true, + enemy: 'youth', + char: 'Y', + damage: 2, + hit: 0.7, + attackDelay: 1, + health: 45, + loot: { + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {1: 'end8'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'd9': { + notification: 'a squatter stands firmly in the doorway of a small hut.', + combat: true, + enemy: 'squatter', + char: 'S', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 20, + loot: { + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + }, + 'teeth': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'continue': { + text: 'continue', + nextScene: {0.5: 'end8', 1: 'end9'} + }, + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end1': { + text: [ + 'bird must have liked shiney things.', + 'some good stuff woven into its nest.' + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + bullets: { + min: 5, + max: 10, + chance: 0.8 + }, + bolas: { + min: 1, + max: 5, + chance: 0.5 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end2': { + text: [ + 'not much here.', + 'scavengers much have gotten to this place already.' + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + torch: { + min: 1, + max: 5, + chance: 0.8 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end3': { + text: [ + 'the tunnel opens up at another platform.', + 'the walls are scorched from an old battle.', + 'bodies and supplies from both sides litter the ground.' + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + rifle: { + min: 1, + max: 1, + chance: 0.8 + }, + bullets: { + min: 1, + max: 5, + chance: 0.8 + }, + 'laser rifle': { + min: 1, + max: 1, + chance: 0.3 + }, + 'energy cell': { + min: 1, + max: 5, + chance: 0.3 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.3 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + + 'end4': { + text: [ + 'the small military outpost is well supplied.', + 'arms and munitions, relics from the war, are neatly arranged on the store-room floor.', + 'just as deadly now as they were then.' + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + rifle: { + min: 1, + max: 1, + chance: 1 + }, + bullets: { + min: 1, + max: 10, + chance: 1 + }, + grenade: { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end5': { + text: [ + 'searching the bodies yields a few supplies.', + 'more soldiers will be on their way.', + 'time to move on.' + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + rifle: { + min: 1, + max: 1, + chance: 1 + }, + bullets: { + min: 1, + max: 10, + chance: 1 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end6': { + text: [ + 'the small settlement has clearly been burning a while.', + 'the bodies of the wanderers that lived here are still visible in the flames.', + "still time to rescue a few supplies." + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + 'laser rifle': { + min: 1, + max: 1, + chance: 0.5 + }, + 'energy cell': { + min: 1, + max: 5, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 10, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + + 'end7': { + text: [ + 'the remaining settlers flee from the violence, their belongings forgotten.', + "there's not much, but some useful things can still be found." + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 0.8 + }, + 'energy cell': { + min: 1, + max: 5, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 10, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end8': { + text: [ + 'the young settler was carrying a canvas sack.', + "it contains travelling gear, and a few trinkets.", + "there's nothing else here." + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + 'steel sword': { + min: 1, + max: 1, + chance: 0.8 + }, + 'bolas': { + min: 1, + max: 5, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 10, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + }, + + 'end9': { + text: [ + 'inside the hut, a child cries.', + "a few belongings rest against the walls.", + "there's nothing else here." + ], + onLoad: function() { + World.clearDungeon(); + State.cityCleared = true; + }, + loot: { + 'rifle': { + min: 1, + max: 1, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bolas': { + min: 1, + max: 5, + chance: 0.5 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.2 + } + }, + buttons: { + 'leave': { + text: 'leave city', + nextScene: 'end' + } + } + } + } + }, + "house": { /* Abandoned House */ + title: 'An Old House', + scenes: { + 'start': { + text: [ + 'an old house remains here, once white siding yellowed and peeling.', + 'the door hangs open.' + ], + notification: 'the remains of an old house stand as a monument to simpler times', + buttons: { + 'enter': { + text: 'go inside', + nextScene: { 0.5: 'supplies', 1: 'occupied' } + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + }, + }, + 'supplies': { + text: [ + 'the house is abandoned, but not yet picked over.', + 'still a few drops of water in the old well.' + ], + onLoad: function() { + World.markVisited(World.curPos[0], World.curPos[1]); + World.setWater(World.getMaxWater()); + Notifications.notify(null, 'water replenished'); + }, + loot: { + 'cured meat': { + min: 1, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 1, + max: 10, + chance: 0.2 + }, + 'cloth': { + min: 1, + max: 10, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'occupied': { + combat: true, + enemy: 'squatter', + char: 'S', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 10, + notification: 'a man charges down the hall, a rusty blade in his hand', + onLoad: function() { + World.markVisited(World.curPos[0], World.curPos[1]); + }, + loot: { + 'cured meat': { + min: 1, + max: 10, + chance: 0.8 + }, + 'leather': { + min: 1, + max: 10, + chance: 0.2 + }, + 'cloth': { + min: 1, + max: 10, + chance: 0.5 + } + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "battlefield": { /* Discovering an old battlefield */ + title: 'A Forgotten Battlefield', + scenes: { + 'start': { + text: [ + 'a battle was fought here, long ago.', + 'battered technology from both sides lays dormant on the blasted landscape.' + ], + onLoad: function() { + World.markVisited(World.curPos[0], World.curPos[1]); + }, + loot: { + 'rifle': { + min: 1, + max: 3, + chance: 0.5 + }, + 'bullets': { + min: 5, + max: 20, + chance: 0.8 + }, + 'laser rifle': { + min: 1, + max: 3, + chance: 0.3 + }, + 'energy cell': { + min: 5, + max: 10, + chance: 0.5 + }, + 'grenade': { + min: 1, + max: 5, + chance: 0.5 + }, + 'alien alloy': { + min: 1, + max: 1, + chance: 0.3 + } + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "borehole": { /* Admiring a huge borehole */ + title: 'A Huge Borehole', + scenes: { + 'start': { + text: [ + 'a huge hole is cut deep into the earth, evidence of the past harvest.', + 'they took what they came for, and left.', + 'castoff from the mammoth drills can still be found by the edges of the precipice.' + ], + onLoad: function() { + World.markVisited(World.curPos[0], World.curPos[1]); + }, + loot: { + 'alien alloy': { + min: 1, + max: 3, + chance: 1 + } + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "ship": { /* Finding a way off this rock */ + title: 'A Crashed Ship', + scenes: { + 'start': { + onLoad: function() { + World.markVisited(World.curPos[0], World.curPos[1]); + World.drawRoad(); + World.state.ship = true; + }, + text: [ + 'the familiar curves of a wanderer vessel rise up out of the dust and ash. ', + "lucky that the natives can't work the mechanisms.", + 'with a little effort, it might fly again.' + ], + buttons: { + 'leavel': { + text: 'salvage', + nextScene: 'end' + } + } + } + } + }, + "sulphurmine": { /* Clearing the Sulphur Mine */ + title: 'The Sulphur Mine', + scenes: { + 'start': { + text: [ + "the military is already set up at the mine's entrance.", + 'soldiers patrol the permitter, rifles slung over their shoulders.' + ], + notification: 'a military perimeter is set up around the mine.', + buttons: { + 'attack': { + text: 'attack', + nextScene: {1: 'a1'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'a1': { + combat: true, + enemy: 'soldier', + ranged: true, + char: 'D', + damage: 8, + hit: 0.8, + attackDelay: 2, + health: 50, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + notification: 'a soldier, alerted, opens fire.', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'a2' } + }, + 'run': { + text: 'run', + nextScene: 'end' + } + } + }, + 'a2': { + combat: true, + enemy: 'soldier', + ranged: true, + char: 'D', + damage: 8, + hit: 0.8, + attackDelay: 2, + health: 50, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'bullets': { + min: 1, + max: 5, + chance: 0.5 + }, + 'rifle': { + min: 1, + max: 1, + chance: 0.2 + } + }, + notification: 'a second soldier joins the fight.', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'a3' } + }, + 'run': { + text: 'run', + nextScene: 'end' + } + } + }, + 'a3': { + combat: true, + enemy: 'veteran', + char: 'V', + damage: 10, + hit: 0.8, + attackDelay: 2, + health: 65, + loot: { + 'bayonet': { + min: 1, + max: 1, + chance: 0.5 + }, + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + } + }, + notification: 'a grizzled soldier attacks, waving a bayonet.', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'cleared' } + } + } + }, + 'cleared': { + text: [ + 'the military presence has been cleared.', + 'the mine is now safe for workers.' + ], + notification: 'the sulphur mine is clear of dangers', + onLoad: function() { + World.drawRoad(); + World.state.sulphurmine = true; + World.markVisited(World.curPos[0], World.curPos[1]); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "coalmine": { /* Clearing the Coal Mine */ + title: 'The Coal Mine', + scenes: { + 'start': { + text: [ + 'camp fires burn by the entrance to the mine.', + 'men mill about, weapons at the ready.' + ], + notification: 'this old mine is not abandoned', + buttons: { + 'attack': { + text: 'attack', + nextScene: {1: 'a1'} + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'a1': { + combat: true, + enemy: 'man', + char: 'M', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 10, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + } + }, + notification: 'a man joins the fight', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'a2' } + }, + 'run': { + text: 'run', + nextScene: 'end' + } + } + }, + 'a2': { + combat: true, + enemy: 'man', + char: 'M', + damage: 3, + hit: 0.8, + attackDelay: 2, + health: 10, + loot: { + 'cured meat': { + min: 1, + max: 5, + chance: 0.8 + }, + 'cloth': { + min: 1, + max: 5, + chance: 0.8 + } + }, + notification: 'a man joins the fight', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'a3' } + }, + 'run': { + text: 'run', + nextScene: 'end' + } + } + }, + 'a3': { + combat: true, + enemy: 'chief', + char: 'C', + damage: 5, + hit: 0.8, + attackDelay: 2, + health: 20, + loot: { + 'cured meat': { + min: 5, + max: 10, + chance: 1 + }, + 'cloth': { + min: 5, + max: 10, + chance: 0.8 + }, + 'iron': { + min: 1, + max: 5, + chance: 0.8 + } + }, + notification: 'only the chief remains.', + buttons: { + 'continue': { + text: 'continue', + nextScene: { 1: 'cleared' } + } + } + }, + 'cleared': { + text: [ + 'the camp is still, save for the crackling of the fires.', + 'the mine is now safe for workers.' + ], + notification: 'the coal mine is clear of dangers', + onLoad: function() { + World.drawRoad(); + World.state.coalmine = true; + World.markVisited(World.curPos[0], World.curPos[1]); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + }, + "ironmine": { /* Clearing the Iron Mine */ + title: 'The Iron Mine', + scenes: { + 'start': { + text: [ + 'an old iron mine sits here, tools abandoned and left to rust.', + 'bleached bones are strewn about the entrance. many, deeply scored with jagged grooves.', + 'feral howls echo out of the darkness.' + ], + notification: 'the path leads to an abandoned mine', + buttons: { + 'enter': { + text: 'go inside', + nextScene: { 1: 'enter' }, + cost: { 'torch': 1 } + }, + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + }, + 'enter': { + combat: true, + enemy: 'beastly matriarch', + char: 'M', + damage: 4, + hit: 0.8, + attackDelay: 2, + health: 10, + loot: { + 'teeth': { + min: 5, + max: 10, + chance: 1 + }, + 'scales': { + min: 5, + max: 10, + chance: 0.8 + }, + 'cloth': { + min: 5, + max: 10, + chance: 0.5 + } + }, + notification: 'a large creature lunges, muscles rippling in the torchlight', + buttons: { + 'leave': { + text: 'leave', + nextScene: { 1: 'cleared' } + } + } + }, + 'cleared': { + text: [ + 'the beast is dead.', + 'the mine is now safe for workers.' + ], + notification: 'the iron mine is clear of dangers', + onLoad: function() { + World.drawRoad(); + World.state.ironmine = true; + World.markVisited(World.curPos[0], World.curPos[1]); + }, + buttons: { + 'leave': { + text: 'leave', + nextScene: 'end' + } + } + } + } + } +}; \ No newline at end of file diff --git a/script/header.js b/script/header.js new file mode 100644 index 000000000..8cf8cec92 --- /dev/null +++ b/script/header.js @@ -0,0 +1,28 @@ +/** + * Module that takes care of header buttons + */ +var Header = { + + init: function(options) { + this.options = $.extend( + this.options, + options + ); + }, + + options: {}, // Nothing for now + + canTravel: function() { + return $('div#header div.headerButton').length > 1; + }, + + addLocation: function(text, id, module) { + return $('
').attr('id', "location_" + id) + .addClass('headerButton') + .text(text).click(function() { + if(Header.canTravel()) { + Engine.travelTo(module); + } + }).appendTo($('div#header')); + } +}; \ No newline at end of file diff --git a/script/notifications.js b/script/notifications.js new file mode 100644 index 000000000..283d8599d --- /dev/null +++ b/script/notifications.js @@ -0,0 +1,58 @@ +/** + * Module that registers the notification box and handles messages + */ +var Notifications = { + + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + // Create the notifications box + elem = $('
').attr({ + id: 'notifications', + className: 'notifications' + }); + // Create the transparency gradient + $('
').attr('id', 'notifyGradient').appendTo(elem); + + elem.appendTo('div#wrapper'); + }, + + options: {}, // Nothing for now + + elem: null, + + notifyQueue: {}, + + // Allow notification to the player + notify: function(module, text, noQueue) { + if(typeof text == 'undefined') return; + if(text.slice(-1) != ".") text += "."; + if(module != null && Engine.activeModule != module) { + if(!noQueue) { + if(typeof this.notifyQueue[module] == 'undefined') { + this.notifyQueue[module] = new Array(); + } + this.notifyQueue[module].push(text); + } + } else { + Notifications.printMessage(text); + } + Engine.saveGame(); + }, + + printMessage: function(text) { + var text = $('
').addClass('notification').css('opacity', '0').text(text).prependTo('div#notifications'); + text.animate({opacity: 1}, 500, 'linear'); + }, + + printQueue: function(module) { + if(typeof this.notifyQueue[module] != 'undefined') { + while(this.notifyQueue[module].length > 0) { + Notifications.printMessage(this.notifyQueue[module].shift()); + } + } + } +}; \ No newline at end of file diff --git a/script/outside.js b/script/outside.js new file mode 100644 index 000000000..9a74ca09b --- /dev/null +++ b/script/outside.js @@ -0,0 +1,611 @@ +/** + * Module that registers the outdoors functionality + */ +var Outside = { + name: "Outside", + + _GATHER_DELAY: 60, + _TRAPS_DELAY: 90, + _POP_DELAY: [0.5, 3], + + _INCOME: { + 'gatherer': { + delay: 10, + stores: { + 'wood': 1 + } + }, + 'hunter': { + delay: 10, + stores: { + 'fur': 0.5, + 'meat': 0.5 + } + }, + 'trapper': { + delay: 10, + stores: { + 'meat': -1, + 'bait': 1 + } + }, + 'tanner': { + delay: 10, + stores: { + 'fur': -5, + 'leather': 1 + } + }, + 'charcutier': { + delay: 10, + stores: { + 'meat': -5, + 'wood': -5, + 'cured meat': 1 + } + }, + 'iron miner': { + delay: 10, + stores: { + 'cured meat': -1, + 'iron': 1 + } + }, + 'coal miner': { + delay: 10, + stores: { + 'cured meat': -1, + 'coal': 1 + } + }, + 'sulphur miner': { + delay: 10, + stores: { + 'cured meat': -1, + 'sulphur': 1 + } + }, + 'steelworker': { + delay: 10, + stores: { + 'iron': -1, + 'coal': -1, + 'steel': 1 + } + }, + 'armourer': { + delay: 10, + stores: { + 'steel': -1, + 'sulphur': -1, + 'bullets': 1 + } + } + }, + + TrapDrops: [ + { + rollUnder: 0.5, + name: 'fur', + message: 'scraps of fur' + }, + { + rollUnder: 0.75, + name: 'meat', + message: 'bits of meat' + }, + { + rollUnder: 0.85, + name: 'scales', + message: 'strange scales' + }, + { + rollUnder: 0.93, + name: 'teeth', + message: 'scattered teeth' + }, + { + rollUnder: 0.995, + name: 'cloth', + message: 'tattered cloth' + }, + { + rollUnder: 1.0, + name: 'charm', + message: 'a crudely made charm' + } + ], + + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + if(Engine._debug) { + this._GATHER_DELAY = 0; + this._TRAPS_DELAY = 0; + } + + // Create the outside tab + this.tab = Header.addLocation("A Silent Forest", "outside", Outside); + + // Create the Outside panel + this.panel = $('
').attr('id', "outsidePanel") + .addClass('location') + .appendTo('div#locationSlider'); + + if(typeof State.outside == 'undefined') { + State.outside = { + buildings: {}, + population: 0, + workers: {} + } + } + + this.updateVillage(); + Outside.updateWorkersView(); + + Engine.updateSlider(); + + // Create the gather button + new Button.Button({ + id: 'gatherButton', + text: "gather wood", + click: Outside.gatherWood, + cooldown: Outside._GATHER_DELAY, + width: '80px' + }).appendTo('div#outsidePanel'); + }, + + numBuilding: function(bName) { + return State.outside && + State.outside.buildings && + State.outside.buildings[bName] ? State.outside.buildings[bName] : 0; + }, + + addBuilding: function(bName, num) { + var cur = State.outside.buildings[bName]; + if(typeof cur != 'number') cur = 0; + cur += num; + if(cur < 0) cur = 0; + State.outside.buildings[bName] = cur; + this.updateVillage(); + Engine.saveGame(); + }, + + addBuildings: function(list) { + for(k in list) { + var num = State.outside.buildings[k]; + if(typeof num != 'number') num = 0; + num += list[k]; + State.outside.buildings[k] = num; + } + this.updateVillage(); + Engine.saveGame(); + }, + + getMaxPopulation: function() { + return Outside.numBuilding('hut') * 4; + }, + + getPopulation: function() { + if(State.outside && State.outside.population) { + return State.outside.population; + } + return 0; + }, + + increasePopulation: function() { + var space = Outside.getMaxPopulation() - State.outside.population; + if(space > 0) { + var num = Math.floor(Math.random()*(space/2) + space/2); + if(num == 0) num = 1; + if(num == 1) { + Notifications.notify(null, 'a stranger arrives in the night'); + } else if(num < 5) { + Notifications.notify(null, 'a weathered family takes up in one of the huts.'); + } else if(num < 10) { + Notifications.notify(null, 'a small group arrives, all dust and bones.'); + } else if(num < 30) { + Notifications.notify(null, 'a convoy lurches in, equal parts worry and hope.'); + } else { + Notifications.notify(null, "the town's booming. word does get around."); + } + Engine.log('population increased by ' + num); + State.outside.population += num; + Outside.updateVillage(); + Outside.updateWorkersView(); + Outside.updateVillageIncome(); + } + Outside.schedulePopIncrease(); + }, + + killVillagers: function(num) { + State.outside.population -= num; + if(State.outside.population < 0) { + State.outside.population = 0; + } + var remaining = Outside.getNumGatherers(); + if(remaining < 0) { + var gap = -remaining; + for(var k in State.outside.workers) { + var num = State.outside.workers[k]; + if(num < gap) { + gap -= num; + State.outside.workers[k] = 0; + } else { + State.outside.workers[k] -= gap; + break; + } + } + } + Outside.updateVillage(); + Outside.updateWorkersView(); + Outside.updateVillageIncome(); + }, + + schedulePopIncrease: function() { + var nextIncrease = Math.floor(Math.random()*(Outside._POP_DELAY[1] - Outside._POP_DELAY[0])) + Outside._POP_DELAY[0]; + Engine.log('next population increase scheduled in ' + nextIncrease + ' minutes'); + Outside._popTimeout = setTimeout(Outside.increasePopulation, nextIncrease * 60 * 1000); + }, + + updateWorkersView: function() { + if(State.outside.population == 0) return; + var workers = $('div#workers'); + var needsAppend = false; + if(workers.length == 0) { + needsAppend = true; + workers = $('
').attr('id', 'workers').css('opacity', 0); + } + + var numGatherers = State.outside.population; + var gatherer = $('div#workers_row_gatherer', workers); + + for(var k in State.outside.workers) { + var row = $('div#workers_row_' + k.replace(' ', '-'), workers); + if(row.length == 0) { + row = Outside.makeWorkerRow(k, State.outside.workers[k]); + + var curPrev = null; + workers.children().each(function(i) { + var child = $(this); + var cName = child.attr('id').substring(12).replace('-', ' '); + if(cName != 'gatherer') { + if(cName < k && (curPrev == null || cName > curPrev)) { + curPrev = cName; + } + } + }); + if(curPrev == null && gatherer.length == 0) { + row.prependTo(workers); + } + else if(curPrev == null) + { + row.insertAfter(gatherer); + } + else + { + row.insertAfter(workers.find('#workers_row_' + curPrev.replace(' ', '-'))); + } + + } else { + $('div#' + row.attr('id') + ' > div.row_val > span', workers).text(State.outside.workers[k]); + } + numGatherers -= State.outside.workers[k]; + if(State.outside.workers[k] == 0) { + $('.dnBtn', row).addClass('disabled'); + } else { + $('.dnBtn', row).removeClass('disabled'); + } + } + + if(gatherer.length == 0) { + gatherer = Outside.makeWorkerRow('gatherer', numGatherers); + gatherer.prependTo(workers); + } else { + $('div#workers_row_gatherer > div.row_val > span', workers).text(numGatherers); + } + + if(numGatherers == 0) { + $('.upBtn', '#workers').addClass('disabled'); + } else { + $('.upBtn', '#workers').removeClass('disabled'); + } + + + if(needsAppend && workers.children().length > 0) { + workers.appendTo('#outsidePanel').animate({opacity:1}, 300, 'linear'); + } + }, + + getNumGatherers: function() { + var num = State.outside.population; + for(var k in State.outside.workers) { + num -= State.outside.workers[k]; + } + return num; + }, + + makeWorkerRow: function(name, num) { + var row = $('
') + .attr('id', 'workers_row_' + name.replace(' ','-')) + .addClass('workerRow'); + $('
').addClass('row_key').text(name).appendTo(row); + var val = $('
').addClass('row_val').appendTo(row); + + $('').text(num).appendTo(val); + + if(name != 'gatherer') { + $('
').addClass('upBtn').appendTo(val).click(Outside.increaseWorker); + $('
').addClass('dnBtn').appendTo(val).click(Outside.decreaseWorker); + } + + $('
').addClass('clear').appendTo(row); + + var tooltip = $('
').addClass('tooltip bottom right').appendTo(row); + var income = Outside._INCOME[name]; + for(var s in income.stores) { + var r = $('
').addClass('storeRow'); + $('
').addClass('row_key').text(s).appendTo(r); + $('
').addClass('row_val').text(Engine.getIncomeMsg(income.stores[s], income.delay)).appendTo(r); + r.appendTo(tooltip); + } + + return row; + }, + + increaseWorker: function(btn) { + var worker = $(this).closest('.workerRow').children('.row_key').text(); + if(Outside.getNumGatherers() > 0) { + Engine.log('increasing ' + worker); + State.outside.workers[worker]++; + Outside.updateVillageIncome(); + Outside.updateWorkersView(); + } + }, + + decreaseWorker: function(btn) { + var worker = $(this).closest('.workerRow').children('.row_key').text(); + if(State.outside.workers[worker] > 0) { + Engine.log('decreasing ' + worker); + State.outside.workers[worker]--; + Outside.updateVillageIncome(); + Outside.updateWorkersView(); + } + }, + + updateVillageRow: function(name, num, village) { + var id = 'building_row_' + name.replace(' ', '-'); + var row = $('div#' + id, village); + if(row.length == 0 && num > 0) { + var row = $('
').attr('id', id).addClass('storeRow'); + $('
').addClass('row_key').text(name).appendTo(row); + $('
').addClass('row_val').text(num).appendTo(row); + $('
').addClass('clear').appendTo(row); + var curPrev = null; + village.children().each(function(i) { + var child = $(this); + if(child.attr('id') != 'population') { + var cName = child.attr('id').substring(13).replace('-', ' '); + if(cName < name && (curPrev == null || cName > curPrev)) { + curPrev = cName; + } + } + }); + if(curPrev == null) { + row.prependTo(village); + } else { + row.insertAfter('#building_row_' + curPrev.replace(' ', '-')); + } + } else if(num > 0) { + $('div#' + row.attr('id') + ' > div.row_val', village).text(num); + } else if(num == 0) { + row.remove(); + } + }, + + updateVillage: function() { + var village = $('div#village'); + var pop = $('div#population'); + var needsAppend = false; + if(village.length == 0) { + needsAppend = true; + village = $('
').attr('id', 'village').css('opacity', 0); + population = $('
').attr('id', 'population').appendTo(village); + } + + for(var k in State.outside.buildings) { + if(k == 'trap') { + var numTraps = State.outside.buildings[k]; + var numBait = Engine.getStore('bait'); + var traps = numTraps - numBait; + traps = traps < 0 ? 0 : traps; + Outside.updateVillageRow(k, traps, village); + Outside.updateVillageRow('baited trap', numBait > numTraps ? numTraps : numBait, village); + } else { + if(Outside.checkWorker(k)) { + Outside.updateWorkersView(); + } + Outside.updateVillageRow(k, State.outside.buildings[k], village); + } + } + + population.text('pop ' + State.outside.population + '/' + this.getMaxPopulation()); + + var hasPeeps; + if(Outside.numBuilding('hut') == 0) { + hasPeeps = false; + village.addClass('noHuts'); + } else { + hasPeeps = true; + village.removeClass('noHuts'); + } + + if(needsAppend && village.children().length > 1) { + village.appendTo('#outsidePanel'); + village.animate({opacity:1}, 300, 'linear'); + } + + if(hasPeeps && typeof Outside._popTimeout == 'undefined') { + Outside.schedulePopIncrease(); + } + + this.setTitle(); + }, + + checkWorker: function(name) { + var jobMap = { + 'lodge': ['hunter', 'trapper'], + 'tannery': ['tanner'], + 'smokehouse': ['charcutier'], + 'iron mine': ['iron miner'], + 'coal mine': ['coal miner'], + 'sulphur mine': ['sulphur miner'], + 'steelworks': ['steelworker'], + 'armoury' : ['armourer'] + } + + var jobs = jobMap[name]; + var added = false; + if(typeof jobs == 'object') { + for(var i = 0, len = jobs.length; i < len; i++) { + var job = jobs[i]; + if(typeof State.outside.buildings[name] == 'number' && + typeof State.outside.workers[job] != 'number') { + Engine.log('adding ' + job + ' to the workers list') + State.outside.workers[job] = 0; + added = true; + } + } + } + return added; + }, + + updateVillageIncome: function() { + for(var worker in Outside._INCOME) { + var income = Outside._INCOME[worker]; + var num = worker == 'gatherer' ? Outside.getNumGatherers() : State.outside.workers[worker]; + if(typeof num == 'number') { + var stores = {}; + if(num < 0) num = 0; + var tooltip = $('.tooltip', 'div#workers_row_' + worker.replace(' ', '-')); + tooltip.empty(); + var needsUpdate = false; + var curIncome = Engine.getIncome(worker); + for(var store in income.stores) { + stores[store] = income.stores[store] * num; + if(curIncome[store] != stores[store]) needsUpdate = true; + var row = $('
').addClass('storeRow'); + $('
').addClass('row_key').text(store).appendTo(row); + $('
').addClass('row_val').text(Engine.getIncomeMsg(stores[store], income.delay)).appendTo(row); + row.appendTo(tooltip); + } + if(needsUpdate) { + Engine.setIncome(worker, { + delay: income.delay, + stores: stores + }); + } + } + } + Room.updateIncomeView(); + }, + + updateTrapButton: function() { + var btn = $('div#trapsButton'); + if(Outside.numBuilding('trap') > 0) { + if(btn.length == 0) { + new Button.Button({ + id: 'trapsButton', + text: "check traps", + click: Outside.checkTraps, + cooldown: Outside._TRAPS_DELAY, + width: '80px' + }).appendTo('div#outsidePanel'); + } else { + Button.setDisabled(btn, false); + } + } else { + if(btn.length > 0) { + Button.setDisabled(btn, true); + } + } + }, + + setTitle: function() { + var numHuts = this.numBuilding('hut'); + var title; + if(numHuts == 0) { + title = "A Silent Forest"; + } else if(numHuts == 1) { + title = "A Lonely Hut"; + } else if(numHuts <= 4) { + title = "A Tiny Village"; + } else if(numHuts <= 8) { + title = "A Modest Village"; + } else if(numHuts <= 14) { + title = "A Large Village"; + } else { + title = "A Raucous Village"; + } + + if(Engine.activeModule == this) { + document.title = title; + } + $('#location_outside').text(title); + }, + + onArrival: function() { + Outside.setTitle(); + if(!State.seenForest) { + Notifications.notify(Outside, "the sky is grey and the wind blows relentlessly"); + State.seenForest = true; + } + Outside.updateTrapButton(); + }, + + gatherWood: function() { + Notifications.notify(Outside, "dry brush and dead branches litter the forest floor") + Engine.setStore('wood', Engine.getStore('wood') + (Outside.numBuilding('cart') > 0 ? 50 : 10)); + }, + + checkTraps: function() { + var drops = {}; + var msg = []; + var numTraps = Outside.numBuilding('trap'); + var numBait = Engine.getStore('bait'); + var numDrops = numTraps + (numBait < numTraps ? numBait : numTraps); + for(var i = 0; i < numDrops; i++) { + var roll = Math.random(); + for(var j in Outside.TrapDrops) { + var drop = Outside.TrapDrops[j]; + if(roll < drop.rollUnder) { + var num = drops[drop.name] + if(typeof num == 'undefined') { + num = 0; + msg.push(drop.message); + } + drops[drop.name] = num + 1; + break; + } + } + } + var s = 'the traps contain '; + for(var i = 0, len = msg.length; i < len; i++) { + if(len > 1 && i > 0 && i < len - 1) { + s += ", "; + } else if(len > 1 && i == len - 1) { + s += " and "; + } + s += msg[i]; + } + + var baitUsed = numBait < numTraps ? numBait : numTraps; + drops['bait'] = -baitUsed; + + Notifications.notify(Outside, s); + Engine.addStores(drops); + } +} \ No newline at end of file diff --git a/script/path.js b/script/path.js new file mode 100644 index 000000000..762cac5a4 --- /dev/null +++ b/script/path.js @@ -0,0 +1,280 @@ +var Path = { + + DEFAULT_BAG_SPACE: 10, + + // Everything not in this list weighs 1 + Weight: { + 'bone spear': 2, + 'iron sword': 3, + 'steel sword': 5, + 'rifle': 5, + 'bullets': 0.1, + 'energy cell': 0.2, + 'laser rifle': 5, + 'bolas': 0.5 + }, + + name: 'Path', + options: {}, // Nuthin' + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + // Init the World + World.init(); + + // Create the path tab + this.tab = Header.addLocation("A Dusty Path", "path", Path); + + // Create the Path panel + this.panel = $('
').attr('id', "pathPanel") + .addClass('location') + .appendTo('div#locationSlider'); + + // Add the outfitting area + var outfitting = $('
').attr('id', 'outfitting').appendTo(this.panel); + var bagspace = $('
').attr('id', 'bagspace').appendTo(outfitting); + + // Add the embark button + new Button.Button({ + id: 'embarkButton', + text: "embark", + click: Path.embark, + width: '80px', + cooldown: World.DEATH_COOLDOWN + }).appendTo(this.panel); + + Path.outfit = {}; + + Engine.updateSlider(); + }, + + getWeight: function(thing) { + var w = Path.Weight[thing]; + if(typeof w != 'number') w = 1; + + return w; + }, + + getCapacity: function() { + if(Engine.getStore('convoy') > 0) { + return Path.DEFAULT_BAG_SPACE + 60; + } else if(Engine.getStore('wagon') > 0) { + return Path.DEFAULT_BAG_SPACE + 30; + } else if(Engine.getStore('rucksack') > 0) { + return Path.DEFAULT_BAG_SPACE + 10; + } + return Path.DEFAULT_BAG_SPACE; + }, + + getFreeSpace: function() { + var num = 0; + if(Path.outfit) { + for(var k in Path.outfit) { + var n = Path.outfit[k]; + if(isNaN(n)) { + // No idea how this happens, but I will fix it here! + Path.outfit[k] = n = 0; + } + num += n * Path.getWeight(k); + } + } + return Path.getCapacity() - num; + }, + + updatePerks: function() { + if(State.perks) { + var perks = $('#perks'); + var needsAppend = false; + if(perks.length == 0) { + needsAppend = true; + perks = $('
').attr('id', 'perks'); + } + for(var k in State.perks) { + var id = 'perk_' + k.replace(' ', '-'); + var r = $('#' + id); + if(State.perks[k] && r.length == 0) { + r = $('
').attr('id', id).addClass('perkRow').appendTo(perks); + $('
').addClass('row_key').text(k).appendTo(r); + $('
').addClass('tooltip bottom right').text(Engine.Perks[k].desc).appendTo(r); + } + } + + if(needsAppend && perks.children().length > 0) { + perks.appendTo(Path.panel); + } + } + }, + + updateOutfitting: function() { + var outfit = $('div#outfitting'); + + if(!Path.outfit) { + Path.outfit = {}; + } + + // Add the armour row + var armour = "none"; + if(Engine.getStore('s armour') > 0) + armour = "steel"; + else if(Engine.getStore('i armour') > 0) + armour = "iron"; + else if(Engine.getStore('l armour') > 0) + armour = "leather"; + var aRow = $('#armourRow'); + if(aRow.length == 0) { + aRow = $('
').attr('id', 'armourRow').addClass('outfitRow').prependTo(outfit); + $('
').addClass('row_key').text('armour').appendTo(aRow); + $('
').addClass('row_val').text(armour).appendTo(aRow); + $('
').addClass('clear').appendTo(aRow); + } else { + $('.row_val', aRow).text(armour); + } + + // Add the water row + var wRow = $('#waterRow'); + if(wRow.length == 0) { + wRow = $('
').attr('id', 'waterRow').addClass('outfitRow').insertAfter(aRow); + $('
').addClass('row_key').text('water').appendTo(wRow); + $('
').addClass('row_val').text(World.getMaxWater()).appendTo(wRow); + $('
').addClass('clear').appendTo(wRow); + } else { + $('.row_val', wRow).text(World.getMaxWater()); + } + + + var space = Path.getFreeSpace(); + var total = 0; + // Add the non-craftables to the craftables + var carryable = $.extend({ + 'cured meat': { type: 'tool' }, + 'bullets': { type: 'tool' }, + 'grenade': {type: 'weapon' }, + 'bolas': {type: 'weapon' }, + 'laser rifle': {type: 'weapon' }, + 'energy cell': {type: 'tool' }, + 'bayonet': {type: 'weapon' }, + 'charm': {type: 'tool'} + }, Room.Craftables); + + for(var k in carryable) { + var store = carryable[k]; + var have = State.stores[k]; + var num = Path.outfit[k]; + num = typeof num == 'number' ? num : 0; + var numAvailable = Engine.getStore(k); + var row = $('div#outfit_row_' + k.replace(' ', '-'), outfit); + if((store.type == 'tool' || store.type == 'weapon') && have > 0) { + total += num * Path.getWeight(k); + if(row.length == 0) { + row = Path.createOutfittingRow(k, num); + + var curPrev = null; + outfit.children().each(function(i) { + var child = $(this); + if(child.attr('id').indexOf('outfit_row_') == 0) { + var cName = child.attr('id').substring(11).replace('-', ' '); + if(cName < k && (curPrev == null || cName > curPrev)) { + curPrev = cName; + } + } + }); + if(curPrev == null) { + row.insertAfter(wRow); + } + else + { + row.insertAfter(outfit.find('#outfit_row_' + curPrev.replace(' ', '-'))); + } + } else { + $('div#' + row.attr('id') + ' > div.row_val > span', outfit).text(num); + $('div#' + row.attr('id') + ' .tooltip .numAvailable', outfit).text(numAvailable - num); + } + if(num == 0) { + $('.dnBtn', row).addClass('disabled'); + } else { + $('.dnBtn', row).removeClass('disabled'); + } + if(num >= numAvailable || space < Path.getWeight(k)) { + $('.upBtn', row).addClass('disabled'); + } else if(space >= Path.getWeight(k)) { + $('.upBtn', row).removeClass('disabled'); + } + } else if(have == 0 && row.length > 0) { + row.remove(); + } + } + + // Update bagspace + $('#bagspace').text('free ' + Math.floor(Path.getCapacity() - total) + '/' + Path.getCapacity()); + + if(Path.outfit['cured meat'] > 0) { + Button.setDisabled($('#embarkButton'), false); + } else { + Button.setDisabled($('#embarkButton'), true); + } + }, + + createOutfittingRow: function(name, num) { + var row = $('
').attr('id', 'outfit_row_' + name.replace(' ', '-')).addClass('outfitRow'); + $('
').addClass('row_key').text(name).appendTo(row); + var val = $('
').addClass('row_val').appendTo(row); + + $('').text(num).appendTo(val); + $('
').addClass('upBtn').appendTo(val).click(Path.increaseSupply); + $('
').addClass('dnBtn').appendTo(val).click(Path.decreaseSupply); + $('
').addClass('clear').appendTo(row); + + var numAvailable = Engine.getStore(name); + var tt = $('
').addClass('tooltip bottom right').appendTo(row); + $('
').addClass('row_key').text('weight').appendTo(tt); + $('
').addClass('row_val').text(Path.getWeight(name)).appendTo(tt); + $('
').addClass('row_key').text('available').appendTo(tt); + $('
').addClass('row_val').addClass('numAvailable').text(numAvailable).appendTo(tt); + + return row; + }, + + increaseSupply: function() { + var supply = $(this).closest('.outfitRow').children('.row_key').text().replace('-', ' '); + Engine.log('increasing ' + supply); + var cur = Path.outfit[supply]; + cur = typeof cur == 'number' ? cur : 0; + if(Path.getFreeSpace() >= Path.getWeight(supply) && cur < Engine.getStore(supply)) { + Path.outfit[supply] = cur + 1; + Path.updateOutfitting(); + } + }, + + decreaseSupply: function() { + var supply = $(this).closest('.outfitRow').children('.row_key').text().replace('-', ' '); + Engine.log('decreasing ' + supply); + var cur = Path.outfit[supply]; + cur = typeof cur == 'number' ? cur : 0; + if(cur > 0) { + Path.outfit[supply] = cur - 1; + Path.updateOutfitting(); + } + }, + + onArrival: function() { + Path.setTitle(); + Path.updateOutfitting(); + Path.updatePerks(); + }, + + setTitle: function() { + document.title = 'A Dusty Path'; + }, + + embark: function() { + for(var k in Path.outfit) { + Engine.addStore(k, -Path.outfit[k]); + } + World.onArrival(); + $('#outerSlider').animate({left: '-700px'}, 300); + Engine.activeModule = World; + } +} \ No newline at end of file diff --git a/script/room.js b/script/room.js new file mode 100644 index 000000000..6b2e27f16 --- /dev/null +++ b/script/room.js @@ -0,0 +1,1058 @@ +/** + * Module that registers the simple room functionality + */ +var Room = { + // times in (minutes * seconds * milliseconds) + _FIRE_COOL_DELAY: 5 * 60 * 1000, // time after a stoke before the fire cools + _ROOM_WARM_DELAY: 30 * 1000, // time between room temperature updates + _BUILDER_STATE_DELAY: 0.5 * 60 * 1000, // time between builder state updates + _STOKE_COOLDOWN: 10, // cooldown to stoke the fire + _NEED_WOOD_DELAY: 15 * 1000, // from when the stranger shows up, to when you need wood + + Craftables: { + 'trap': { + button: null, + maximum: 10, + availableMsg: 'builder says she can make traps to catch any creatures might still be alive out there', + buildMsg: 'more traps to catch more creatures', + maxMsg: "more traps won't help now", + type: 'building', + cost: function() { + var n = Outside.numBuilding('trap'); + return { + 'wood': 10 + (n*10) + }; + } + }, + 'cart': { + button: null, + maximum: 1, + availableMsg: 'builder says she can make a cart for carrying wood', + buildMsg: 'the rickety cart will carry more wood from the forest', + type: 'building', + cost: function() { + return { + 'wood': 30 + }; + } + }, + 'hut': { + button: null, + maximum: 20, + availableMsg: "builder says there are more wanderers. says they'll work, too.", + buildMsg: 'builder puts up a hut, out in the forest. says word will get around.', + maxMsg: 'no more room for huts.', + type: 'building', + cost: function() { + var n = Outside.numBuilding('hut'); + return { + 'wood': 100 + (n*50) + }; + } + }, + 'lodge': { + button: null, + maximum: 1, + availableMsg: 'villagers could help hunt, given the means', + buildMsg: 'the hunting lodge stands in the forest, a ways out of town', + type: 'building', + cost: function() { + return { + wood: 200, + fur: 10, + meat: 5 + } + } + }, + 'trading post': { + button: null, + maximum: 1, + availableMsg: "a trading post would make commerce easier", + buildMsg: "now the nomads have a place to set up shop, they might stick around a while", + type: 'building', + cost: function() { + return { + 'wood': 400, + 'fur': 100 + }; + } + }, + 'tannery': { + button: null, + maximum: 1, + availableMsg: "builder says leather could be useful. says the villagers could make it.", + buildMsg: 'tannery goes up quick, on the edge of the village', + type: 'building', + cost: function() { + return { + 'wood': 500, + 'fur': 50 + }; + } + }, + 'smokehouse': { + button: null, + maximum: 1, + availableMsg: "should cure the meat, or it'll spoil. builder says she can fix something up.", + buildMsg: 'builder finishes the smokehouse. she looks hungry.', + type: 'building', + cost: function() { + return { + 'wood': 600, + 'meat': 50 + }; + } + }, + 'workshop': { + button: null, + maximum: 1, + availableMsg: "builder says she could make finer things, if she had the tools", + buildMsg: "workshop's finally ready. builder's excited to get to it", + type: 'building', + cost: function() { + return { + 'wood': 800, + 'leather': 100, + 'scales': 10 + }; + } + }, + 'steelworks': { + button: null, + maximum: 1, + availableMsg: "builder says the villagers could make steel, given the tools", + buildMsg: "a haze falls over the village as the steelworks fires up", + type: 'building', + cost: function() { + return { + 'wood': 1500, + 'iron': 100, + 'coal': 100 + }; + } + }, + 'armoury': { + button: null, + maximum: 1, + availableMsg: "builder says it'd be useful to have a steady source of bullets", + buildMsg: "armoury's done, welcoming back the weapons of the past.", + type: 'building', + cost: function() { + return { + 'wood': 3000, + 'steel': 100, + 'sulphur': 50 + }; + } + }, + 'torch': { + button: null, + type: 'tool', + buildMsg: 'a torch to keep the dark away', + cost: function() { + return { + 'wood': 1, + 'cloth': 1 + }; + } + }, + 'waterskin': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'this waterskin\'ll hold a bit of water, at least', + cost: function() { + return { + 'leather': 50 + }; + } + }, + 'cask': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'the cask holds enough water for longer expeditions', + cost: function() { + return { + 'leather': 100, + 'iron': 20 + }; + } + }, + 'water tank': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'never go thirsty again', + cost: function() { + return { + 'iron': 100, + 'steel': 50 + }; + } + }, + 'bone spear': { + button: null, + type: 'weapon', + buildMsg: "this spear's not elegant, but it's pretty good at stabbing", + cost: function() { + return { + 'wood': 100, + 'teeth': 5 + }; + } + }, + 'rucksack': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'carrying more means longer expeditions to the wilds', + cost: function() { + return { + 'leather': 200 + }; + } + }, + 'wagon': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'the wagon can carry a lot of supplies', + cost: function() { + return { + 'wood': 500, + 'iron': 100 + }; + } + }, + 'convoy': { + button: null, + type: 'upgrade', + maximum: 1, + buildMsg: 'the convoy can haul mostly everything', + cost: function() { + return { + 'wood': 1000, + 'iron': 200, + 'steel': 100 + }; + } + }, + 'l armour': { + type: 'upgrade', + maximum: 1, + buildMsg: "leather's not strong. better than rags, though.", + cost: function() { + return { + 'leather': 200, + 'scales': 20 + }; + } + }, + 'i armour': { + type: 'upgrade', + maximum: 1, + buildMsg: "iron's stronger than leather", + cost: function() { + return { + 'leather': 200, + 'iron': 100 + } + } + }, + 's armour': { + type: 'upgrade', + maximum: 1, + buildMsg: "steel's stronger than iron", + cost: function() { + return { + 'leather': 200, + 'steel': 100 + } + } + }, + 'iron sword': { + button: null, + type: 'weapon', + buildMsg: "sword is sharp. good protection out in the wilds.", + cost: function() { + return { + 'wood': 200, + 'leather': 50, + 'iron': 20 + }; + } + }, + 'steel sword': { + button: null, + type: 'weapon', + buildMsg: "the steel is strong, and the blade true.", + cost: function() { + return { + 'wood': 500, + 'leather': 100, + 'steel': 20 + }; + } + }, + 'rifle': { + type: 'weapon', + buildMsg: "black powder and bullets, like the old days.", + cost: function() { + return { + 'wood': 200, + 'steel': 50, + 'sulphur': 50 + } + } + } + }, + + TradeGoods: { + 'scales': { + type: 'good', + cost: function() { + return { fur: 150 }; + } + }, + 'teeth': { + type: 'good', + cost: function() { + return { fur: 300 }; + } + }, + 'iron': { + type: 'good', + cost: function() { + return { + 'fur': 150, + 'scales': 50 + } + } + }, + 'coal': { + type: 'good', + cost: function() { + return { + 'fur': 200, + 'teeth': 50 + } + } + }, + 'steel': { + type: 'good', + cost: function() { + return { + 'fur': 300, + 'scales': 50, + 'teeth': 50 + } + } + }, + 'bullets': { + type: 'good', + cost: function() { + return { + 'scales': 10 + } + } + }, + 'energy cell': { + type: 'good', + cost: function() { + return { + 'scales': 10, + 'teeth': 10 + } + } + }, + 'bolas': { + type: 'weapon', + cost: function() { + return { + 'teeth': 10 + } + } + }, + 'grenade': { + type: 'weapon', + cost: function() { + return { + 'scales': 100, + 'teeth': 50 + } + } + }, + 'bayonet': { + type: 'weapon', + cost: function() { + return { + 'scales': 500, + 'teeth': 250 + } + } + }, + 'alien alloy': { + type: 'good', + cost: function() { + return { + 'fur': 1500, + 'scales': 750, + 'teeth': 300 + } + } + }, + 'compass': { + type: 'upgrade', + maximum: 1, + cost: function() { + return { + fur: 400, + scales: 20, + teeth: 10 + }; + } + } + }, + + name: "Room", + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + if(Engine._debug) { + this._ROOM_WARM_DELAY = 1; + this._BUILDER_STATE_DELAY = 1; + this._STOKE_COOLDOWN = 0; + this._NEED_WOOD_DELAY = 1; + } + + if(typeof State.room == 'undefined') { + State.room = { + temperature: this.TempEnum.Cold, + fire: this.FireEnum.Dead, + buttons: {}, + builder: -1 + }; + } + + // Create the room tab + this.tab = Header.addLocation("A Dark Room", "room", Room); + + // Create the Room panel + this.panel = $('
') + .attr('id', "roomPanel") + .addClass('location') + .appendTo('div#locationSlider'); + + Engine.updateSlider(); + + // Create the light button + var lbtn = new Button.Button({ + id: 'lightButton', + text: 'light fire', + click: Room.lightFire, + cooldown: Room._STOKE_COOLDOWN, + width: '80px', + cost: {'wood': 5} + }).appendTo('div#roomPanel'); + + // Create the stoke button + var btn = new Button.Button({ + id: 'stokeButton', + text: "stoke fire", + click: Room.stokeFire, + cooldown: Room._STOKE_COOLDOWN, + width: '80px', + cost: {'wood': 1} + }).appendTo('div#roomPanel'); + + // Create the stores container + $('
').attr('id', 'storesContainer').appendTo('div#roomPanel'); + + Room.updateButton(); + Room.updateStoresView(); + Room.updateIncomeView(); + Room.updateBuildButtons(); + + Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY); + Room._tempTimer = setTimeout(Room.adjustTemp, Room._ROOM_WARM_DELAY); + + /* + * Builder states: + * 0 - Approaching + * 1 - Collapsed + * 2 - Shivering + * 3 - Sleeping + * 4 - Helping + */ + if(State.room.builder >= 0 && State.room.builder < 3) { + Room._builderTimer = setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY); + } + if(State.room.builder == 1 && Engine.getStore('wood') < 0) { + setTimeout(Room.unlockForest, Room._NEED_WOOD_DELAY); + } + setTimeout(Engine.collectIncome, 1000); + + Notifications.notify(Room, "the room is " + State.room.temperature.text); + Notifications.notify(Room, "the fire is " + State.room.fire.text); + }, + + options: {}, // Nothing for now + + onArrival: function() { + Room.setTitle(); + if(Room.changed) { + Notifications.notify(Room, "the fire is " + State.room.fire.text); + Notifications.notify(Room, "the room is " + State.room.temperature.text); + Room.changed = false; + } + if(State.room.builder == 3) { + State.room.builder++; + Engine.setIncome('builder', { + delay: 10, + stores: {'wood' : 2 } + }); + Room.updateIncomeView(); + Notifications.notify(Room, "the stranger is standing by the fire. she says she can help. says she builds things.") + } + }, + + TempEnum: { + fromInt: function(value) { + for(var k in this) { + if(typeof this[k].value != 'undefined' && this[k].value == value) { + return this[k]; + } + } + return null; + }, + Freezing: { value: 0, text: 'freezing' }, + Cold: { value: 1, text: 'cold' }, + Mild: { value: 2, text: 'mild' }, + Warm: { value: 3, text: 'warm' }, + Hot: { value: 4, text: 'hot' } + }, + + FireEnum: { + fromInt: function(value) { + for(var k in this) { + if(typeof this[k].value != 'undefined' && this[k].value == value) { + return this[k]; + } + } + return null; + }, + Dead: { value: 0, text: 'dead' }, + Smoldering: { value: 1, text: 'smoldering' }, + Flickering: { value: 2, text: 'flickering' }, + Burning: { value: 3, text: 'burning' }, + Roaring: { value: 4, text: 'roaring' } + }, + + setTitle: function() { + var title = State.room.fire.value < 2 ? "A Dark Room" : "A Firelit Room"; + if(Engine.activeModule == this) { + document.title = title; + } + $('div#location_room').text(title); + }, + + updateButton: function() { + var light = $('#lightButton.button'); + var stoke = $('#stokeButton.button'); + if(State.room.fire.value == Room.FireEnum.Dead.value && stoke.css('display') != 'none') { + stoke.hide(); + light.show(); + if(stoke.hasClass('disabled')) { + Button.cooldown(light); + } + } else if(light.css('display') != 'none') { + stoke.show(); + light.hide(); + if(light.hasClass('disabled')) { + Button.cooldown(stoke); + } + } + + if(!Engine.storeAvailable('wood')) { + light.addClass('free'); + stoke.addClass('free'); + } else { + light.removeClass('free'); + stoke.removeClass('free'); + } + }, + + _fireTimer: null, + _tempTimer: null, + lightFire: function() { + var wood = Engine.getStore('wood'); + if(Engine.storeAvailable('wood') && wood < 5) { + Notifications.notify(Room, "not enough wood to get the fire going"); + Button.clearCooldown($('#lightButton.button')); + return; + } else if(wood > 4) { + Engine.setStore('wood', wood - 5); + } + State.room.fire = Room.FireEnum.Burning; + Room.onFireChange(); + }, + + stokeFire: function() { + var wood = Engine.getStore('wood'); + if(Engine.storeAvailable('wood') && wood == 0) { + Notifications.notify(Room, "the wood has run out"); + Button.clearCooldown($('#stokeButton.button')); + return; + } + if(wood > 0) { + Engine.setStore('wood', wood - 1); + } + if(State.room.fire.value < 4) { + State.room.fire = Room.FireEnum.fromInt(State.room.fire.value + 1); + } + Room.onFireChange(); + }, + + onFireChange: function() { + if(Engine.activeModule != Room) { + Room.changed = true; + } + Notifications.notify(Room, "the fire is " + State.room.fire.text, true); + if(State.room.fire.value > 1 && State.room.builder < 0) { + State.room.builder = 0; + Notifications.notify(Room, "the light from the fire spills from the windows, out into the dark"); + setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY); + } + window.clearTimeout(Room._fireTimer); + Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY); + Room.updateButton(); + Room.setTitle(); + }, + + coolFire: function() { + if(State.room.fire.value <= Room.FireEnum.Flickering.value && + State.room.builder > 3 && Engine.getStore('wood') > 0) { + Notifications.notify(Room, "builder stokes the fire", true); + Engine.setStore('wood', Engine.getStore('wood') - 1); + State.room.fire = Room.FireEnum.fromInt(State.room.fire.value + 1); + } + if(State.room.fire.value > 0) { + State.room.fire = Room.FireEnum.fromInt(State.room.fire.value - 1); + Room._fireTimer = setTimeout(Room.coolFire, Room._FIRE_COOL_DELAY); + Room.onFireChange(); + } + }, + + adjustTemp: function() { + var old = State.room.temperature.value; + if(State.room.temperature.value > 0 && State.room.temperature.value > State.room.fire.value) { + State.room.temperature = Room.TempEnum.fromInt(State.room.temperature.value - 1); + Notifications.notify(Room, "the room is " + State.room.temperature.text, true); + } + if(State.room.temperature.value < 4 && State.room.temperature.value < State.room.fire.value) { + State.room.temperature = Room.TempEnum.fromInt(State.room.temperature.value + 1); + Notifications.notify(Room, "the room is " + State.room.temperature.text, true); + } + if(State.room.temperature.value != old) { + Room.changed = true; + } + Room._tempTimer = setTimeout(Room.adjustTemp, Room._ROOM_WARM_DELAY); + }, + + unlockForest: function() { + Engine.setStore('wood', 4); + Room.updateButton(); + Outside.init(); + Room.updateStoresView(); + Notifications.notify(Room, "the wind howls outside"); + Notifications.notify(Room, "the wood is running out"); + Engine.event('progress', 'outside'); + }, + + updateBuilderState: function() { + if(State.room.builder == 0) { + Notifications.notify(Room, "a ragged stranger stumbles through the door and collapses in the corner"); + State.room.builder = 1; + setTimeout(Room.unlockForest, Room._NEED_WOOD_DELAY); + } + else if(State.room.builder < 3 && State.room.temperature.value >= Room.TempEnum.Warm.value) { + var msg; + switch(State.room.builder) { + case 1: + msg = "the stranger shivers, and mumbles quietly. her words are unintelligible."; + break; + case 2: + msg = "the stranger in the corner stops shivering. her breathing calms."; + break; + } + Notifications.notify(Room, msg); + if(State.room.builder < 3) { + State.room.builder++; + } + } + if(State.room.builder < 3) { + setTimeout(Room.updateBuilderState, Room._BUILDER_STATE_DELAY); + } + Engine.saveGame(); + }, + + updateStoresView: function() { + var stores = $('div#stores'); + var weapons = $('div#weapons'); + var needsAppend = false, wNeedsAppend = false, newRow = false; + if(stores.length == 0) { + stores = $('
').attr({ + id: 'stores' + }).css('opacity', 0); + needsAppend = true; + } + if(weapons.length == 0) { + weapons = $('
').attr({ + id: 'weapons' + }).css('opacity', 0); + wNeedsAppend = true; + } + for(var k in State.stores) { + + var type = null; + if(Room.Craftables[k]) { + type = Room.Craftables[k].type; + } else if(Room.TradeGoods[k]) { + type = Room.TradeGoods[k].type; + } + + var location; + switch(type) { + case 'upgrade': + // Don't display upgrades on the Room screen + continue; + case 'weapon': + location = weapons; + break; + default: + location = stores; + break; + } + + var id = "row_" + k.replace(' ', '-'); + var row = $('div#' + id, location); + var num = State.stores[k]; + + if(typeof num != 'number' || isNaN(num)) { + // No idea how counts get corrupted, but I have reason to believe that they occassionally do. + // Build a little fence around it! + num = State.stores[k] = 0; + } + + + // thieves? + if(typeof State.thieves == 'undefined' && num > 5000 && State.world) { + Engine.startThieves(); + } + + if(row.length == 0 && num > 0) { + var row = $('
').attr('id', id).addClass('storeRow'); + $('
').addClass('row_key').text(k).appendTo(row); + $('
').addClass('row_val').text(Math.floor(num)).appendTo(row); + $('
').addClass('clear').appendTo(row); + var curPrev = null; + location.children().each(function(i) { + var child = $(this); + var cName = child.attr('id').substring(4).replace('-', ' '); + if(cName < k && (curPrev == null || cName > curPrev)) { + curPrev = cName; + } + }); + if(curPrev == null) { + row.prependTo(location); + } else { + row.insertAfter(location.find('#row_' + curPrev.replace(' ', '-'))); + } + newRow = true; + } else if(num> 0){ + $('div#' + row.attr('id') + ' > div.row_val', location).text(Math.floor(num)); + } else if(num == 0) { + row.remove(); + } + } + + if(needsAppend && stores.children().length > 0) { + stores.appendTo('div#storesContainer'); + stores.animate({opacity: 1}, 300, 'linear'); + } + + if(wNeedsAppend && weapons.children().length > 0) { + weapons.appendTo('div#storesContainer'); + weapons.animate({opacity: 1}, 300, 'linear'); + } + + if(newRow) { + Room.updateIncomeView(); + } + }, + + updateIncomeView: function() { + var stores = $('div#stores'); + if(stores.length == 0 || typeof State.income == 'undefined') return; + $('div.storeRow', stores).each(function(index, el) { + el = $(el); + $('div.tooltip', el).remove(); + var tt = $('
').addClass('tooltip bottom right'); + var storeName = el.attr('id').substring(4).replace('-', ' '); + for(var incomeSource in State.income) { + var income = State.income[incomeSource]; + for(var store in income.stores) { + if(store == storeName && income.stores[store] != 0) { + $('
').addClass('row_key').text(incomeSource).appendTo(tt); + $('
') + .addClass('row_val') + .text(Engine.getIncomeMsg(income.stores[store], income.delay)) + .appendTo(tt); + } + } + } + if(tt.children().length > 0) { + tt.appendTo(el); + } + }); + }, + + buy: function(buyBtn) { + var thing = $(buyBtn).attr('buildThing'); + var good = Room.TradeGoods[thing]; + var numThings = Engine.getStore(thing); + if(numThings < 0) numThings = 0; + if(good.maximum <= numThings) { + return; + } + + var storeMod = {}; + var cost = good.cost(); + for(var k in cost) { + var have = Engine.getStore(k) + if(have < cost[k]) { + Notifications.notify(Room, "not enough " + k); + return false; + } else { + storeMod[k] = have - cost[k]; + } + } + Engine.setStores(storeMod); + + Notifications.notify(Room, good.buildMsg); + + Engine.addStore(thing, 1); + + Room.updateBuildButtons(); + + if(thing == 'compass') { + Engine.openPath(); + } + }, + + build: function(buildBtn) { + var thing = $(buildBtn).attr('buildThing'); + if(State.room.temperature.value <= Room.TempEnum.Cold.value) { + Notifications.notify(Room, "builder just shivers"); + return false; + } + var craftable = Room.Craftables[thing]; + + var numThings = 0; + switch(craftable.type) { + case 'good': + case 'weapon': + case 'tool': + case 'upgrade': + numThings = Engine.getStore(thing); + break; + case 'building': + numThings = Outside.numBuilding(thing); + break; + } + + if(numThings < 0) numThings = 0; + if(craftable.maximum <= numThings) { + return; + } + + var storeMod = {}; + var cost = craftable.cost(); + for(var k in cost) { + var have = Engine.getStore(k) + if(have < cost[k]) { + Notifications.notify(Room, "not enough " + k); + return false; + } else { + storeMod[k] = have - cost[k]; + } + } + Engine.setStores(storeMod); + + Notifications.notify(Room, craftable.buildMsg); + + switch(craftable.type) { + case 'good': + case 'weapon': + case 'upgrade': + case 'tool': + Engine.addStore(thing, 1); + break; + case 'building': + Outside.addBuilding(thing, 1); + break; + } + + Room.updateBuildButtons(); + + }, + + needsWorkshop: function(type) { + return type == 'weapon' || type == 'upgrade' || type =='tool'; + }, + + craftUnlocked: function(thing) { + if(typeof State.room != 'undefined' && + typeof State.room.buttons != 'undefined' && + State.room.buttons[thing]) { + return true; + } + if(State.room.builder < 4) return false; + var craftable = Room.Craftables[thing]; + if(Room.needsWorkshop(craftable.type) && Outside.numBuilding('workshop') == 0) return false; + var cost = craftable.cost(); + + // Show buttons if we have at least 1/2 the wood, and all other components have been seen. + if(Engine.getStore('wood') < cost['wood'] * 0.5) { + return false; + } + for(var c in cost) { + if(!Engine.storeAvailable(c)) { + return false; + } + } + + State.room.buttons[thing] = true; + Notifications.notify(Room, craftable.availableMsg); + return true; + }, + + buyUnlocked: function(thing) { + if(typeof State.room != 'undefined' && + typeof State.room.buttons != 'undefined' && + State.room.buttons[thing]) { + return true; + } else if(Outside.numBuilding('trading post') > 0) { + if(thing == 'compass' || Engine.storeAvailable(thing)) { + // Allow the purchase of stuff once you've seen it + return true; + } + } + return false; + }, + + updateBuildButtons: function() { + var buildSection = $('#buildBtns'); + var needsAppend = false; + if(buildSection.length == 0) { + buildSection = $('
').attr('id', 'buildBtns').css('opacity', 0); + needsAppend = true; + } + + var craftSection = $('#craftBtns'); + var cNeedsAppend = false; + if(craftSection.length == 0 && Outside.numBuilding('workshop') > 0) { + craftSection = $('
').attr('id', 'craftBtns').css('opacity', 0); + cNeedsAppend = true; + } + + var buySection = $('#buyBtns'); + var bNeedsAppend = false; + if(buySection.length == 0 && Outside.numBuilding('trading post') > 0) { + buySection = $('
').attr('id', 'buyBtns').css('opacity', 0); + bNeedsAppend = true; + } + + for(var k in Room.Craftables) { + craftable = Room.Craftables[k]; + var max = Engine.num(k, craftable) + 1 > craftable.maximum; + if(craftable.button == null) { + if(Room.craftUnlocked(k)) { + var loc = Room.needsWorkshop(craftable.type) ? craftSection : buildSection; + craftable.button = new Button.Button({ + id: 'build_' + k, + cost: craftable.cost(), + text: k, + click: Room.build, + width: '80px', + ttPos: loc.children().length > 10 ? 'top right' : 'bottom right' + }).css('opacity', 0).attr('buildThing', k).appendTo(loc).animate({opacity: 1}, 300, 'linear'); + } + } else { + // refresh the tooltip + var costTooltip = $('.tooltip', craftable.button); + costTooltip.empty(); + var cost = craftable.cost(); + for(var k in cost) { + $("
").addClass('row_key').text(k).appendTo(costTooltip); + $("
").addClass('row_val').text(cost[k]).appendTo(costTooltip); + } + if(max && !craftable.button.hasClass('disabled')) { + Notifications.notify(Room, craftable.maxMsg); + } + } + if(max) { + Button.setDisabled(craftable.button, true); + } else { + Button.setDisabled(craftable.button, false); + } + } + + for(var k in Room.TradeGoods) { + good = Room.TradeGoods[k]; + var max = Engine.num(k, good) + 1 > good.maximum; + if(good.button == null) { + if(Room.buyUnlocked(k)) { + good.button = new Button.Button({ + id: 'build_' + k, + cost: good.cost(), + text: k, + click: Room.buy, + width: '80px' + }).css('opacity', 0).attr('buildThing', k).appendTo(buySection).animate({opacity:1}, 300, 'linear'); + } + } else { + // refresh the tooltip + var costTooltip = $('.tooltip', good.button); + costTooltip.empty(); + var cost = good.cost(); + for(var k in cost) { + $("
").addClass('row_key').text(k).appendTo(costTooltip); + $("
").addClass('row_val').text(cost[k]).appendTo(costTooltip); + } + if(max && !good.button.hasClass('disabled')) { + Notifications.notify(Room, good.maxMsg); + } + } + if(max) { + Button.setDisabled(good.button, true); + } else { + Button.setDisabled(good.button, false); + } + } + + if(needsAppend && buildSection.children().length > 0) { + buildSection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear'); + } + if(cNeedsAppend && craftSection.children().length > 0) { + craftSection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear'); + } + if(bNeedsAppend && buildSection.children().length > 0) { + buySection.appendTo('div#roomPanel').animate({opacity: 1}, 300, 'linear'); + } + } +}; \ No newline at end of file diff --git a/script/ship.js b/script/ship.js new file mode 100644 index 000000000..cff19cf8f --- /dev/null +++ b/script/ship.js @@ -0,0 +1,165 @@ +/** + * Module that registers the starship! + */ +var Ship = { + LIFTOFF_COOLDOWN: 120, + ALLOY_PER_HULL: 1, + ALLOY_PER_THRUSTER: 1, + BASE_HULL: 0, + BASE_THRUSTERS: 1, + + name: "Ship", + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + if(!State.ship) { + State.ship = { + hull: Ship.BASE_HULL, + thrusters: Ship.BASE_THRUSTERS + } + } + + // Create the Ship tab + this.tab = Header.addLocation("An Old Starship", "ship", Ship); + + // Create the Ship panel + this.panel = $('
').attr('id', "shipPanel") + .addClass('location') + .appendTo('div#locationSlider'); + + Engine.updateSlider(); + + // Draw the hull label + var hullRow = $('
').attr('id', 'hullRow').appendTo('div#shipPanel'); + $('
').addClass('row_key').text('hull:').appendTo(hullRow); + $('
').addClass('row_val').text(State.ship.hull).appendTo(hullRow); + $('
').addClass('clear').appendTo(hullRow); + + // Draw the thrusters label + var engineRow = $('
').attr('id', 'engineRow').appendTo('div#shipPanel'); + $('
').addClass('row_key').text('engine:').appendTo(engineRow); + $('
').addClass('row_val').text(State.ship.thrusters).appendTo(engineRow); + $('
').addClass('clear').appendTo(engineRow); + + // Draw the reinforce button + new Button.Button({ + id: 'reinforceButton', + text: 'reinforce hull', + click: Ship.reinforceHull, + width: '100px', + cost: {'alien alloy': Ship.ALLOY_PER_HULL} + }).appendTo('div#shipPanel'); + + // Draw the engine button + new Button.Button({ + id: 'engineButton', + text: 'upgrade engine', + click: Ship.upgradeEngine, + width: '100px', + cost: {'alien alloy': Ship.ALLOY_PER_THRUSTER} + }).appendTo('div#shipPanel'); + + // Draw the lift off button + var b = new Button.Button({ + id: 'liftoffButton', + text: 'lift off', + click: Ship.checkLiftOff, + width: '100px', + cooldown: Ship.LIFTOFF_COOLDOWN + }).appendTo('div#shipPanel'); + + if(State.ship.hull <= 0) { + Button.setDisabled(b, true); + } + + // Init Space + Space.init(); + }, + + options: {}, // Nothing for now + + onArrival: function() { + Ship.setTitle(); + if(!State.seenShip) { + Notifications.notify(Ship, 'somewhere above the debris cloud, the wanderer fleet hovers. been on this rock too long.'); + State.seenShip = true; + Engine.saveGame(); + } + }, + + setTitle: function() { + if(Engine.activeModule == this) { + document.title = "An Old Starship"; + } + }, + + reinforceHull: function() { + if(Engine.getStore('alien alloy') < Ship.ALLOY_PER_HULL) { + Notifications.notify(Ship, "not enough alien alloy"); + return false; + } + Engine.addStore('alien alloy', -Ship.ALLOY_PER_HULL); + State.ship.hull++; + if(State.ship.hull > 0) { + Button.setDisabled($('#liftoffButton', Ship.panel), false); + } + $('#hullRow .row_val', Ship.panel).text(State.ship.hull); + }, + + upgradeEngine: function() { + if(Engine.getStore('alien alloy') < Ship.ALLOY_PER_THRUSTER) { + Notifications.notify(Ship, "not enough alien alloy"); + return false; + } + Engine.addStore('alien alloy', -Ship.ALLOY_PER_THRUSTER); + State.ship.thrusters++; + $('#engineRow .row_val', Ship.panel).text(State.ship.thrusters); + }, + + getMaxHull: function() { + return State.ship.hull; + }, + + checkLiftOff: function() { + if(!State.ship.seenWarning) { + Events.startEvent({ + title: 'Ready to Leave?', + scenes: { + 'start': { + text: [ + "time to get out of this place. won't be coming back." + ], + buttons: { + 'fly': { + text: 'lift off', + onChoose: function() { + State.ship.seenWarning = true; + Ship.liftOff(); + }, + nextScene: 'end' + }, + 'wait': { + text: 'linger', + onChoose: function() { + Button.clearCooldown($('#liftoffButton')); + }, + nextScene: 'end' + } + } + } + } + }); + } else { + Ship.liftOff(); + } + }, + + liftOff: function () { + $('#outerSlider').animate({top: '700px'}, 300); + Space.onArrival(); + Engine.activeModule = Space; + } +}; \ No newline at end of file diff --git a/script/space.js b/script/space.js new file mode 100644 index 000000000..4a3bf440d --- /dev/null +++ b/script/space.js @@ -0,0 +1,453 @@ +/** + * Module that registers spaaaaaaaaace! + */ +var Space = { + SHIP_SPEED: 3, + BASE_ASTEROID_DELAY: 500, + BASE_ASTEROID_SPEED: 1500, + FTB_SPEED: 60000, + STAR_WIDTH: 3000, + STAR_HEIGHT: 3000, + NUM_STARS: 200, + STAR_SPEED: 60000, + FRAME_DELAY: 100, + + stars: null, + backStars: null, + ship: null, + lastMove: null, + done: false, + shipX: null, + shipY: null, + + hull: 0, + + name: "Space", + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + // Create the Space panel + this.panel = $('
').attr('id', "spacePanel") + .addClass('location') + .appendTo('#outerSlider'); + + // Create the ship + Space.ship = $('
').text("@").attr('id', 'ship').appendTo(this.panel); + + // Create the hull display + var h = $('
').attr('id', 'hullRemaining').appendTo(this.panel); + $('
').addClass('row_key').text('hull: ').appendTo(h); + $('
').addClass('row_val').appendTo(h); + }, + + options: {}, // Nothing for now + + onArrival: function() { + Space.done = false; + Engine.keyLock = false; + Space.hull = Ship.getMaxHull(); + Space.altitude = 0; + Space.setTitle(); + Space.updateHull(); + + Space.up = + Space.down = + Space.left = + Space.right = false; + + Space.ship.css({ + top: '350px', + left: '350px' + }); + Space.startAscent(); + Space._shipTimer = setInterval(Space.moveShip, 33); + }, + + setTitle: function() { + if(Engine.activeModule == this) { + var t; + if(Space.altitude < 10) { + t = "Troposphere"; + } else if(Space.altitude < 20) { + t = "Stratosphere"; + } else if(Space.altitude < 30) { + t = "Mesosphere"; + } else if(Space.altitude < 45) { + t = "Thermosphere"; + } else if(Space.altitude < 60){ + t = "Exosphere"; + } else { + t = "Space"; + } + document.title = t; + } + }, + + getSpeed: function() { + return Space.SHIP_SPEED + State.ship.thrusters; + }, + + updateHull: function() { + $('div#hullRemaining div.row_val', Space.panel).text(Space.hull + '/' + Ship.getMaxHull()); + }, + + createAsteroid: function(noNext) { + var r = Math.random(); + var c; + if(r < 0.2) + c = '#'; + else if(r < 0.4) + c = '$' + else if(r < 0.6) + c = '%'; + else if(r < 0.8) + c = '&'; + else + c = 'H'; + + var x = Math.floor(Math.random() * 700); + var a = $('
').addClass('asteroid').text(c).appendTo('#spacePanel').css('left', x + 'px') + a.data({ + xMin: x, + xMax: x + a.width(), + height: a.height() + }); + a.animate({ + top: '740px' + }, { + duration: Space.BASE_ASTEROID_SPEED - Math.floor(Math.random() * (Space.BASE_ASTEROID_SPEED * 0.65)), + easing: 'linear', + progress: function() { + // Collision detection + var t = $(this); + if(t.data('xMin') <= Space.shipX && t.data('xMax') >= Space.shipX) { + var aY = t.css('top'); + aY = parseFloat(aY.substring(0, aY.length - 2)); + + if(aY <= Space.shipY && aY + t.data('height') >= Space.shipY) { + // Collision + Engine.log('collision'); + t.remove(); + Space.hull--; + Space.updateHull(); + if(Space.hull == 0) { + Space.crash(); + } + } + } + }, + complete: function() { + $(this).remove(); + } + }); + if(!noNext) { + + // Harder + if(Space.altitude > 10) { + Space.createAsteroid(true); + } + + // HARDER + if(Space.altitude > 20) { + Space.createAsteroid(true); + Space.createAsteroid(true); + } + + // HAAAAAARDERRRRR!!!!1 + if(Space.altitude > 40) { + Space.createAsteroid(true); + Space.createAsteroid(true); + } + + if(!Space.done) { + setTimeout(Space.createAsteroid, 1000 - (Space.altitude * 10)); + } + } + }, + + moveShip: function() { + var x = Space.ship.css('left'); + x = parseFloat(x.substring(0, x.length - 2)); + var y = Space.ship.css('top'); + y = parseFloat(y.substring(0, y.length - 2)); + + var dx = 0, dy = 0; + + if(Space.up) { + dy -= Space.getSpeed(); + } else if(Space.down) { + dy += Space.getSpeed(); + } + if(Space.left) { + dx -= Space.getSpeed(); + } else if(Space.right) { + dx += Space.getSpeed(); + } + + if(dx != 0 && dy != 0) { + dx = dx / Math.sqrt(2); + dy = dy / Math.sqrt(2); + } + + if(Space.lastMove != null) { + var dt = Date.now() - Space.lastMove; + dx *= dt / 33; + dy *= dt / 33; + } + + x = x + dx; + y = y + dy; + if(x < 10) { + x = 10; + } else if(x > 690) { + x = 690; + } + if(y < 10) { + y = 10; + } else if(y > 690) { + y = 690; + } + + Space.shipX = x; + Space.shipY = y; + + Space.ship.css({ + left: x + 'px', + top: y + 'px', + }); + + Space.lastMove = Date.now(); + }, + + startAscent: function() { + $('body').addClass('noMask').css({backgroundColor: '#FFFFFF'}).animate({ + backgroundColor: '#000000' + }, { + duration: Space.FTB_SPEED, + easing: 'linear', + progress: function() { + var cur = $('body').css('background-color'); + var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' + + cur.substring(3, cur.length - 1) + ', 1) 100%)'; + $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s); + }, + complete: Space.endGame + }); + Space.drawStars(); + Space._timer = setInterval(function() { + Space.altitude += 1; + if(Space.altitude % 10 == 0) { + Space.setTitle(); + } + if(Space.altitude > 60) { + clearInterval(Space._timer); + } + }, 1000); + + setTimeout(function() { + $('#spacePanel, .deleteSave, .share').animate({color: 'white'}, 500, 'linear'); + }, Space.FTB_SPEED / 2); + + Space.createAsteroid(); + }, + + drawStars: function(duration) { + var starsContainer = $('
').attr('id', 'starsContainer').appendTo('body'); + Space.stars = $('
').css('bottom', '0px').attr('id', 'stars').appendTo(starsContainer); + var s1 = $('
').css({ + width: Space.STAR_WIDTH + 'px', + height: Space.STAR_HEIGHT + 'px' + }); + var s2 = s1.clone(); + Space.stars.append(s1).append(s2); + Space.drawStarAsync(s1, s2, 0); + Space.stars.data('speed', Space.STAR_SPEED); + Space.startAnimation(Space.stars); + + Space.starsBack = $('
').css('bottom', '0px').attr('id', 'starsBack').appendTo(starsContainer); + s1 = $('
').css({ + width: Space.STAR_WIDTH + 'px', + height: Space.STAR_HEIGHT + 'px' + }); + s2 = s1.clone(); + Space.starsBack.append(s1).append(s2); + Space.drawStarAsync(s1, s2, 0); + Space.starsBack.data('speed', Space.STAR_SPEED * 2); + Space.startAnimation(Space.starsBack); + }, + + startAnimation: function(el) { + el.animate({bottom: '-3000px'}, el.data('speed'), 'linear', function() { + $(this).css('bottom', '0px'); + Space.startAnimation($(this)); + }); + }, + + drawStarAsync: function(el, el2, num) { + var top = Math.floor(Math.random() * Space.STAR_HEIGHT) + 'px'; + var left = Math.floor(Math.random() * Space.STAR_WIDTH) + 'px'; + $('
').text('.').addClass('star').css({ + top: top, + left: left + }).appendTo(el); + $('
').text('.').addClass('star').css({ + top: top, + left: left + }).appendTo(el2); + if(num < Space.NUM_STARS) { + setTimeout(function() { Space.drawStarAsync(el, el2, num + 1); }, 100); + } + }, + + crash: function() { + if(Space.done) return; + Engine.keyLock = true; + Space.done = true; + clearInterval(Space._timer); + clearInterval(Space._shipTimer); + + // Craaaaash! + $('body').removeClass('noMask').stop().animate({ + backgroundColor: '#FFFFFF' + }, { + duration: 300, + progress: function() { + var cur = $('body').css('background-color'); + var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' + + cur.substring(3, cur.length - 1) + ', 1) 100%)'; + $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s); + }, + complete: function() { + Space.stars.remove(); + Space.starsBack.remove(); + Space.stars = Space.starsBack = null; + $('#starsContainer').remove(); + } + }); + $('#spacePanel, .deleteSave, .share').animate({color: 'black'}, 300, 'linear'); + $('#outerSlider').animate({top: '0px'}, 300, 'linear'); + Engine.activeModule = Ship; + Ship.onArrival(); + Button.cooldown($('#liftoffButton')); + Engine.event('progress', 'crash'); + }, + + endGame: function() { + if(Space.done) return; + Engine.event('progress', 'win'); + Space.done = true; + clearInterval(Space._timer); + clearInterval(Space._shipTimer); + clearTimeout(Engine._saveTimer); + clearTimeout(Outside._popTimeout); + clearTimeout(Engine._incomeTimeout); + clearTimeout(Events._eventTimeout); + clearTimeout(Room._fireTimer); + clearTimeout(Room._tempTimer); + for(var k in Room.Craftables) { + Room.Craftables[k].button = null; + } + for(var k in Room.TradeGoods) { + Room.TradeGoods[k].button = null; + } + delete Outside._popTimeout; + + $('#hullRemaining', Space.panel).animate({opacity: 0}, 500, 'linear'); + Space.ship.animate({ + top: '350px', + left: '240px' + }, 3000, 'linear', function() { + setTimeout(function() { + Space.ship.animate({ + top: '-100px' + }, 200, 'linear', function() { + // Restart everything! Play FOREVER! + $('#outerSlider').css({'left': '0px', 'top': '0px'}); + $('#locationSlider, #worldPanel, #spacePanel, #notifications').remove(); + $('#header').empty(); + setTimeout(function() { + $('body').removeClass('noMask').stop().animate({ + opacity: 0, + 'background-color': '#FFF' + }, { + duration: 2000, + progress: function() { + var cur = $('body').css('background-color'); + var s = 'linear-gradient(rgba' + cur.substring(3, cur.length - 1) + ', 0) 0%, rgba' + + cur.substring(3, cur.length - 1) + ', 1) 100%)'; + $('#notifyGradient').attr('style', 'background-color:'+cur+';background:-webkit-' + s + ';background:' + s); + }, + complete: function() { + $('#starsContainer, .deleteSave, .share').remove(); + if(typeof Storage != 'undefined' && localStorage) { + localStorage.clear(); + } + delete window.State; + Engine.options = {}; + setTimeout(function() { + Engine.init(); + $('body').animate({ + opacity: 1 + }, 500, 'linear'); + }, 2000); + } + }); + }, 2000); + }); + }, 2000); + }); + }, + + keyDown: function(event) { + switch(event.which) { + case 38: // Up + case 87: + Space.up = true; + Engine.log('up on'); + break; + case 40: // Down + case 83: + Space.down = true; + Engine.log('down on'); + break; + case 37: // Left + case 65: + Space.left = true; + Engine.log('left on'); + break; + case 39: // Right + case 68: + Space.right = true; + Engine.log('right on'); + break; + } + }, + + keyUp: function(event) { + switch(event.which) { + case 38: // Up + case 87: + Space.up = false; + Engine.log('up off'); + break; + case 40: // Down + case 83: + Space.down = false; + Engine.log('down off'); + break; + case 37: // Left + case 65: + Space.left = false; + Engine.log('left off'); + break; + case 39: // Right + case 68: + Space.right = false; + Engine.log('right off'); + break; + } + } +}; \ No newline at end of file diff --git a/script/world.js b/script/world.js new file mode 100644 index 000000000..0e8146523 --- /dev/null +++ b/script/world.js @@ -0,0 +1,825 @@ +var World = { + + RADIUS: 30, + TILE: { + VILLAGE: 'A', + IRON_MINE: 'I', + COAL_MINE: 'C', + SULPHUR_MINE: 'S', + FOREST: 'T', + FIELD: ',', + BARRENS: '.', + ROAD: '#', + HOUSE: 'H', + CAVE: 'V', + TOWN: 'O', + CITY: 'Y', + OUTPOST: 'P', + SHIP: 'W', + BOREHOLE: 'B', + BATTLEFIELD: 'F', + SWAMP: 'M' + }, + TILE_PROBS: {}, + LANDMARKS: {}, + STICKINESS: 0.5, // 0 <= x <= 1 + LIGHT_RADIUS: 2, + BASE_WATER: 10, + MOVES_PER_FOOD: 2, + MOVES_PER_WATER: 1, + DEATH_COOLDOWN: 120, + FIGHT_CHANCE: 0.20, + BASE_HEALTH: 10, + BASE_HIT_CHANCE: 0.8, + MEAT_HEAL: 10, + FIGHT_DELAY: 3, // At least three moves between fights + + Weapons: { + 'fists': { + verb: 'punch', + type: 'unarmed', + damage: 1, + cooldown: 2 + }, + 'bone spear': { + verb: 'stab', + type: 'melee', + damage: 2, + cooldown: 2 + }, + 'iron sword': { + verb: 'swing', + type: 'melee', + damage: 4, + cooldown: 2 + }, + 'steel sword': { + verb: 'slash', + type: 'melee', + damage: 6, + cooldown: 2 + }, + 'bayonet': { + verb: 'thrust', + type: 'melee', + damage: 8, + cooldown: 2 + }, + 'rifle': { + verb: 'shoot', + type: 'ranged', + damage: 5, + cooldown: 1, + cost: { 'bullets': 1 } + }, + 'laser rifle': { + verb: 'blast', + type: 'ranged', + damage: 8, + cooldown: 1, + cost: { 'energy cell': 1 } + }, + 'grenade': { + verb: 'lob', + type: 'ranged', + damage: 15, + cooldown: 5, + cost: { 'grenade': 1 } + }, + 'bolas': { + verb: 'tangle', + type: 'ranged', + damage: 'stun', + cooldown: 15, + cost: { 'bolas': 1 } + } + }, + + name: 'World', + options: {}, // Nothing for now + init: function(options) { + this.options = $.extend( + this.options, + options + ); + + // Setup probabilities. Sum must equal 1. + World.TILE_PROBS[World.TILE.FOREST] = 0.15; + World.TILE_PROBS[World.TILE.FIELD] = 0.35; + World.TILE_PROBS[World.TILE.BARRENS] = 0.5; + + // Setpiece definitions + World.LANDMARKS[World.TILE.OUTPOST] = { num: 0, minRadius: 0, maxRadius: 0, scene: 'outpost', label: 'An Outpost' }; + World.LANDMARKS[World.TILE.IRON_MINE] = { num: 1, minRadius: 5, maxRadius: 5, scene: 'ironmine', label: 'Iron Mine' }; + World.LANDMARKS[World.TILE.COAL_MINE] = { num: 1, minRadius: 10, maxRadius: 10, scene: 'coalmine', label: 'Coal Mine' }; + World.LANDMARKS[World.TILE.SULPHUR_MINE] = { num: 1, minRadius: 20, maxRadius: 20, scene: 'sulphurmine', label: 'Sulphur Mine' }; + World.LANDMARKS[World.TILE.HOUSE] = { num: 10, minRadius: 0, maxRadius: World.RADIUS * 1.5, scene: 'house', label: 'An Old House' }; + World.LANDMARKS[World.TILE.CAVE] = { num: 5, minRadius: 3, maxRadius: 10, scene: 'cave', label: 'A Damp Cave' }; + World.LANDMARKS[World.TILE.TOWN] = { num: 10, minRadius: 10, maxRadius: 20, scene: 'town', label: 'An Abandoned Town' }; + World.LANDMARKS[World.TILE.CITY] = { num: 20, minRadius: 20, maxRadius: World.RADIUS * 1.5, scene: 'city', label: 'A Ruined City' }; + World.LANDMARKS[World.TILE.SHIP] = {num: 1, minRadius: 28, maxRadius: 28, scene: 'ship', label: 'A Crashed Starship'}; + World.LANDMARKS[World.TILE.BOREHOLE] = {num: 10, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'borehole', label: 'A Borehole'}; + World.LANDMARKS[World.TILE.BATTLEFIELD] = {num: 5, minRadius: 18, maxRadius: World.RADIUS * 1.5, scene: 'battlefield', label: 'A Battlefield'}; + World.LANDMARKS[World.TILE.SWAMP] = {num: 1, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'swamp', label: 'A Murky Swamp'}; + + if(typeof State.world == 'undefined') { + State.world = { + map: World.generateMap(), + mask: World.newMask() + }; + } + + // Create the World panel + this.panel = $('
').attr('id', "worldPanel").addClass('location').appendTo('#outerSlider'); + + // Create the shrink wrapper + var outer = $('
').attr('id', 'worldOuter').appendTo(this.panel); + + // Create the bag panel + $('
').attr('id', 'bagspace-world').append($('
')).appendTo(outer); + $('
').attr('id', 'backpackTitle').appendTo(outer); + $('
').attr('id', 'backpackSpace').appendTo(outer); + $('
').attr('id', 'healthCounter').appendTo(outer); + + Engine.updateOuterSlider(); + }, + + clearDungeon: function() { + Engine.event('progress', 'dungeon cleared'); + World.state.map[World.curPos[0]][World.curPos[1]] = World.TILE.OUTPOST; + World.drawRoad(); + }, + + drawRoad: function() { + var xDist = World.curPos[0] - World.RADIUS; + var yDist = World.curPos[1] - World.RADIUS; + var xDir = Math.abs(xDist)/xDist; + var yDir = Math.abs(yDist)/yDist; + var xIntersect, yIntersect; + if(Math.abs(xDist) > Math.abs(yDist)) { + xIntersect = World.RADIUS; + yIntersect = World.RADIUS + yDist; + } else { + xIntersect = World.RADIUS + xDist; + yIntersect = World.RADIUS; + } + + for(var x = 0; x < Math.abs(xDist); x++) { + if(World.isTerrain(World.state.map[World.RADIUS + (xDir*x)][yIntersect])) { + World.state.map[World.RADIUS + (xDir*x)][yIntersect] = World.TILE.ROAD; + } + } + for(var y = 0; y < Math.abs(yDist); y++) { + if(World.isTerrain(World.state.map[xIntersect][World.RADIUS + (yDir*y)])) { + World.state.map[xIntersect][World.RADIUS + (yDir*y)] = World.TILE.ROAD; + } + } + World.drawMap(); + }, + + updateSupplies: function() { + var supplies = $('div#bagspace-world > div'); + + if(!Path.outfit) { + Path.outfit = {}; + } + + // Add water + var water = $('div#supply_water'); + if(World.water > 0 && water.length == 0) { + water = World.createItemDiv('water', World.water); + water.prependTo(supplies); + } else if(World.water > 0) { + $('div#supply_water', supplies).text('water:' + World.water); + } else { + water.remove(); + } + + var total = 0; + for(var k in Path.outfit) { + var item = $('div#supply_' + k.replace(' ', '-'), supplies); + var num = Path.outfit[k]; + total += num * Path.getWeight(k); + if(num > 0 && item.length == 0) { + item = World.createItemDiv(k, num); + if(k == 'cured meat' && World.water > 0) { + item.insertAfter(water); + } else if(k == 'cured meat') { + item.prependTo(supplies); + } else { + item.appendTo(supplies); + } + } else if(num > 0) { + $('div#' + item.attr('id'), supplies).text(k + ':' + num); + } else { + item.remove(); + } + } + + // Update label + var t = 'pockets'; + if(Engine.getStore('rucksack') > 0) { + t = 'rucksack'; + } + $('#backpackTitle').text(t); + + // Update bagspace + $('#backpackSpace').text('free ' + Math.floor(Path.getCapacity() - total) + '/' + Path.getCapacity()); + }, + + setWater: function(w) { + World.water = w; + if(World.water > World.getMaxWater()) { + World.water = World.getMaxWater(); + } + World.updateSupplies(); + }, + + setHp: function(hp) { + if(typeof hp == 'number' && !isNaN(hp)) { + World.health = hp; + if(World.health > World.getMaxHealth()) { + World.health = World.getMaxHealth(); + } + $('#healthCounter').text('hp: ' + World.health + '/' + World.getMaxHealth()); + } + }, + + createItemDiv: function(name, num) { + var div = $('
').attr('id', 'supply_' + name.replace(' ', '-')) + .addClass('supplyItem') + .text(name + ':' + num); + + return div; + }, + + keyDown: function(event) { + var moved = true; + var oldTile = World.state.map[World.curPos[0]][World.curPos[1]]; + switch(event.which) { + case 38: // Up + case 87: + Engine.log('up'); + if(World.curPos[1] > 0) World.curPos[1]--; + break; + case 40: // Down + case 83: + Engine.log('down'); + if(World.curPos[1] < World.RADIUS * 2) World.curPos[1]++; + break; + case 37: // Left + case 65: + Engine.log('left'); + if(World.curPos[0] > 0) World.curPos[0]--; + break; + case 39: // Right + case 68: + Engine.log('right'); + if(World.curPos[0] < World.RADIUS * 2) World.curPos[0]++; + break; + default: + moved = false; + break; + } + if(moved) { + World.narrateMove(oldTile, World.state.map[World.curPos[0]][World.curPos[1]]); + World.lightMap(World.curPos[0], World.curPos[1], World.state.mask); + World.drawMap(); + World.doSpace(); + if(World.checkDanger()) { + if(World.danger) { + Notifications.notify(World, 'dangerous to be this far from the village without proper protection') + } else { + Notifications.notify(World, 'safer here'); + } + } + } + }, + + checkDanger: function() { + World.danger = typeof World.danger == 'undefined' ? false: World.danger; + if(!World.danger) { + if(!Engine.getStore('i armour') > 0 && World.getDistance() >= 8) { + World.danger = true; + return true; + } + if(!Engine.getStore('s armour') > 0 && World.getDistance() >= 18) { + World.danger = true; + return true; + } + } else { + if(World.getDistance() < 8) { + World.danger = false; + return true; + } + if(World.getDistance < 18 && Engine.getStore('i armour') > 0) { + World.danger = false; + return true; + } + } + return false; + }, + + useSupplies: function() { + World.foodMove++; + World.waterMove++; + // Food + var movesPerFood = World.MOVES_PER_FOOD; + movesPerFood *= Engine.hasPerk('slow metabolism') ? 2 : 1; + if(World.foodMove >= movesPerFood) { + World.foodMove = 0; + var num = Path.outfit['cured meat']; + num--; + if(num == 0) { + Notifications.notify(World, 'the meat has run out'); + } else if(num < 0) { + // Starvation! Hooray! + num = 0; + if(!World.starvation) { + Notifications.notify(World, 'starvation sets in') + World.starvation = true; + } else { + State.starved = State.starved ? State.starved : 0; + State.starved++; + if(State.starved >= 10 && !Engine.hasPerk('slow metabolism')) { + Engine.addPerk('slow metabolism'); + } + World.die(); + return false; + } + } else { + World.starvation = false; + World.setHp(World.health + World.meatHeal()); + } + Path.outfit['cured meat'] = num; + } + // Water + var movesPerWater = World.MOVES_PER_WATER; + movesPerWater *= Engine.hasPerk('desert rat') ? 2 : 1; + if(World.waterMove >= movesPerWater) { + World.waterMove = 0; + var water = World.water; + water--; + if(water == 0) { + Notifications.notify(World, 'there is no more water'); + } else if(water < 0) { + water = 0; + if(!World.thirst) { + Notifications.notify(World, 'the thirst becomes unbearable'); + World.thirst = true; + } else { + State.dehydrated = State.dehydrated ? State.dehydrated : 0; + State.dehydrated++; + if(State.dehydrated >= 10 && !Engine.hasPerk('desert rat')) { + Engine.addPerk('desert rat'); + } + World.die(); + return false; + } + } else { + World.thirst = false; + } + World.setWater(water); + World.updateSupplies(); + } + return true; + }, + + meatHeal: function() { + return World.MEAT_HEAL * (Engine.hasPerk('gastronome') ? 2 : 1); + }, + + checkFight: function() { + World.fightMove = typeof World.fightMove == 'number' ? World.fightMove : 0; + World.fightMove++; + if(World.fightMove > World.FIGHT_DELAY) { + var chance = World.FIGHT_CHANCE; + chance *= Engine.hasPerk('stealthy') ? 0.5 : 1; + if(Math.random() < chance) { + World.fightMove = 0; + Events.triggerFight(); + } + } + }, + + doSpace: function() { + var curTile = World.state.map[World.curPos[0]][World.curPos[1]]; + + if(curTile == World.TILE.VILLAGE) { + World.goHome(); + } else if(typeof World.LANDMARKS[curTile] != 'undefined') { + if(curTile != World.TILE.OUTPOST || !World.outpostUsed()) { + Events.startEvent(Events.Setpieces[World.LANDMARKS[curTile].scene]); + } + } else { + if(World.useSupplies()) { + World.checkFight(); + } + } + }, + + getDistance: function() { + return Math.abs(World.curPos[0] - World.RADIUS) + Math.abs(World.curPos[1] - World.RADIUS); + }, + + getTerrain: function() { + return World.state.map[World.curPos[0]][World.curPos[1]]; + }, + + narrateMove: function(oldTile, newTile) { + var msg = null; + switch(oldTile) { + case World.TILE.FOREST: + switch(newTile) { + case World.TILE.FIELD: + msg = "the trees yield to dry grass. the yellowed brush rustles in the wind."; + break; + case World.TILE.BARRENS: + msg = "the trees are gone. parched earth and blowing dust are poor replacements."; + break; + } + break; + case World.TILE.FIELD: + switch(newTile) { + case World.TILE.FOREST: + msg = "trees loom on the horizon. grasses gradually yield to a forest floor of dry branches and fallen leaves."; + break; + case World.TILE.BARRENS: + msg = "the grasses thin. soon, only dust remains."; + break; + } + break; + case World.TILE.BARRENS: + switch(newTile) { + case World.TILE.FIELD: + msg = "the barrens break at a sea of dying grass, swaying in the arid breeze."; + break; + case World.TILE.FOREST: + msg = "a wall of gnarled trees rises from the dust. their branches twist into a skeletal canopy overhead."; + break; + } + break; + } + if(msg != null) { + Notifications.notify(World, msg); + } + }, + + newMask: function() { + var mask = new Array(World.RADIUS * 2 + 1); + for(var i = 0; i <= World.RADIUS * 2; i++) { + mask[i] = new Array(World.RADIUS * 2 + 1); + } + World.lightMap(World.RADIUS, World.RADIUS, mask); + return mask; + }, + + lightMap: function(x, y, mask) { + var r = World.LIGHT_RADIUS; + r *= Engine.hasPerk('scout') ? 2 : 1; + World.uncoverMap(x, y, r, mask); + return mask; + }, + + uncoverMap: function(x, y, r, mask) { + mask[x][y] = true; + for(var i = -r; i <= r; i++) { + for(var j = -r + Math.abs(i); j <= r - Math.abs(i); j++) { + if(y + j >= 0 && y + j <= World.RADIUS * 2 && + x + i <= World.RADIUS * 2 && + x + i >= 0) { + mask[x+i][y+j] = true; + } + } + } + }, + + applyMap: function() { + var x = Math.floor(Math.random() * (World.RADIUS * 2) + 1); + var y = Math.floor(Math.random() * (World.RADIUS * 2) + 1); + World.uncoverMap(x, y, 5, State.world.mask); + }, + + generateMap: function() { + var map = new Array(World.RADIUS * 2 + 1); + for(var i = 0; i <= World.RADIUS * 2; i++) { + map[i] = new Array(World.RADIUS * 2 + 1); + } + // The Village is always at the exact center + // Spiral out from there + map[World.RADIUS][World.RADIUS] = World.TILE.VILLAGE; + for(var r = 1; r <= World.RADIUS; r++) { + for(t = 0; t < r * 8; t++) { + var x, y; + if(t < 2 * r) { + x = World.RADIUS - r + t; + y = World.RADIUS - r; + } else if(t < 4 * r) { + x = World.RADIUS + r; + y = World.RADIUS - (3 * r) + t; + } else if(t < 6 * r) { + x = World.RADIUS + (5 * r) - t; + y = World.RADIUS + r; + } else { + x = World.RADIUS - r; + y = World.RADIUS + (7 * r) - t; + } + + map[x][y] = World.chooseTile(x, y, map); + } + } + + // Place landmarks + for(var k in World.LANDMARKS) { + var landmark = World.LANDMARKS[k]; + for(var i = 0; i < landmark.num; i++) { + var pos = World.placeLandmark(landmark.minRadius, landmark.maxRadius, k, map); + if(k == World.TILE.SHIP) { + var dx = pos[0] - World.RADIUS, dy = pos[1] - World.RADIUS; + var horz = dx < 0 ? 'west' : 'east'; + var vert = dy < 0 ? 'north' : 'south'; + if(Math.abs(dx) / 2 > Math.abs(dy)) { + World.dir = horz; + } else if(Math.abs(dy) / 2 > Math.abs(dx)){ + World.dir = vert; + } else { + World.dir = vert + horz; + } + } + } + } + + return map; + }, + + placeLandmark: function(minRadius, maxRadius, landmark, map) { + + var x = World.RADIUS, y = World.RADIUS; + while(!World.isTerrain(map[x][y])) { + var r = Math.floor(Math.random() * (maxRadius - minRadius)) + minRadius; + var xDist = Math.floor(Math.random() * r); + var yDist = r - xDist; + if(Math.random() < 0.5) xDist = -xDist; + if(Math.random() < 0.5) yDist = -yDist; + x = World.RADIUS + xDist; + if(x < 0) x = 0; + if(x > World.RADIUS * 2) x = World.RADIUS * 2; + y = World.RADIUS + yDist; + if(y < 0) y = 0; + if(y > World.RADIUS * 2) y = World.RADIUS * 2; + } + map[x][y] = landmark; + return [x, y]; + }, + + isTerrain: function(tile) { + return tile == World.TILE.FOREST || tile == World.TILE.FIELD || tile == World.TILE.BARRENS; + }, + + chooseTile: function(x, y, map) { + + var log = x == World.RADIUS + 1 && y == World.RADIUS + 1; + + var adjacent = [ + y > 0 ? map[x][y-1] : null, + y < World.RADIUS * 2 ? map[x][y+1] : null, + x < World.RADIUS * 2 ? map[x+1][y] : null, + x > 0 ? map[x-1][y] : null + ]; + + var chances = {}; + var nonSticky = 1; + for(var i in adjacent) { + if(adjacent[i] == World.TILE.VILLAGE) { + // Village must be in a forest to maintain thematic consistency, yo. + return World.TILE.FOREST; + } else if(typeof adjacent[i] == 'string') { + var cur = chances[adjacent[i]]; + cur = typeof cur == 'number' ? cur : 0; + chances[adjacent[i]] = cur + World.STICKINESS; + nonSticky -= World.STICKINESS; + } + } + for(var t in World.TILE) { + var tile = World.TILE[t]; + if(World.isTerrain(tile)) { + var cur = chances[tile]; + cur = typeof cur == 'number' ? cur : 0; + cur += World.TILE_PROBS[tile] * nonSticky; + chances[tile] = cur; + } + } + + var list = []; + for(var t in chances) { + list.push(chances[t] + '' + t); + } + list.sort(function(a, b) { + var n1 = parseFloat(a.substring(0, a.length - 1)); + var n2 = parseFloat(b.substring(0, b.length - 1)); + return n2 - n1; + }); + + var c = 0; + var r = Math.random(); + for(var i in list) { + var prob = list[i]; + c += parseFloat(prob.substring(0,prob.length - 1)); + if(r < c) { + return prob.charAt(prob.length - 1); + } + } + + return World.TILE.BARRENS; + }, + + markVisited: function(x, y) { + World.state.map[x][y] = World.state.map[x][y] + '!'; + }, + + drawMap: function() { + var map = $('#map'); + if(map.length == 0) { + map = new $('
').attr('id', 'map').appendTo('#worldOuter'); + } + var mapString = ""; + for(var j = 0; j <= World.RADIUS * 2; j++) { + for(var i = 0; i <= World.RADIUS * 2; i++) { + var ttClass = ""; + if(i > World.RADIUS) { + ttClass += " left"; + } else { + ttClass += " right"; + } + if(j > World.RADIUS) { + ttClass += " top"; + } else { + ttClass += " bottom"; + } + if(World.curPos[0] == i && World.curPos[1] == j) { + mapString += '@
Wanderer
'; + } else if(World.state.mask[i][j]) { + var c = World.state.map[i][j]; + switch(c) { + case World.TILE.VILLAGE: + mapString += '' + c + '
The Village
'; + break; + default: + if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) { + mapString += '' + c + '
' + World.LANDMARKS[c].label + '
'; + } else { + if(c.length > 1) { + c = c[0]; + } + mapString += c; + } + break; + } + } else { + mapString += ' '; + } + } + mapString += '
'; + } + map.html(mapString); + }, + + die: function() { + if(!World.dead) { + World.dead = true; + Engine.log('player death'); + Engine.event('game event', 'death'); + Engine.keyLock = true; + // Dead! Discard any world changes and go home + Notifications.notify(World, 'the world fades'); + World.state = null; + Path.outfit = {}; + $('#outerSlider').animate({opacity: '0'}, 600, 'linear', function() { + $('#outerSlider').css('left', '0px'); + $('#locationSlider').css('left', '0px'); + Engine.activeModule = Room; + $('div.headerButton').removeClass('selected'); + Room.tab.addClass('selected'); + setTimeout(function(){ + Room.onArrival(); + $('#outerSlider').animate({opacity:'1'}, 600, 'linear'); + Button.cooldown($('#embarkButton')); + Engine.keyLock = false; + }, 2000); + }); + } + }, + + goHome: function() { + // Home safe! Commit the changes. + State.world = World.state; + if(World.state.sulphurmine && Outside.numBuilding('sulphur mine') == 0) { + Outside.addBuilding('sulphur mine', 1); + Engine.event('progress', 'sulphur mine'); + } + if(World.state.ironmine && Outside.numBuilding('iron mine') == 0) { + Outside.addBuilding('iron mine', 1); + Engine.event('progress', 'iron mine'); + } + if(World.state.coalmine && Outside.numBuilding('coal mine') == 0) { + Outside.addBuilding('coal mine', 1); + Engine.event('progress', 'coal mine'); + } + if(World.state.ship && !State.ship) { + Ship.init(); + Engine.event('progress', 'ship'); + } + World.state = null; + + // Clear the embark cooldown + var btn = Button.clearCooldown($('#embarkButton')); + if(Path.outfit['cured meat'] > 0) { + Button.setDisabled(btn, false); + } + + for(var k in Path.outfit) { + Engine.addStore(k, Path.outfit[k]); + if(World.leaveItAtHome(k)) { + Path.outfit[k] = 0; + } + } + + $('#outerSlider').animate({left: '0px'}, 300); + Engine.activeModule = Path; + Path.onArrival(); + }, + + leaveItAtHome: function(thing) { + return thing != 'cured meat' && thing != 'bullets' && thing != 'energy cell' && thing != 'charm' + && typeof World.Weapons[thing] == 'undefined' && typeof Room.Craftables[thing] == 'undefined'; + }, + + getMaxHealth: function() { + if(Engine.getStore('s armour') > 0) { + return World.BASE_HEALTH + 35; + } else if(Engine.getStore('i armour') > 0) { + return World.BASE_HEALTH + 15; + } else if(Engine.getStore('l armour') > 0) { + return World.BASE_HEALTH + 5; + } + return World.BASE_HEALTH; + }, + + getHitChance: function() { + if(Engine.hasPerk('precise')) { + return World.BASE_HIT_CHANCE + 0.1; + } + return World.BASE_HIT_CHANCE; + }, + + getMaxWater: function() { + if(Engine.getStore('water tank') > 0) { + return World.BASE_WATER + 50; + } else if(Engine.getStore('cask') > 0) { + return World.BASE_WATER + 20; + } else if(Engine.getStore('waterskin') > 0) { + return World.BASE_WATER + 10; + } + return World.BASE_WATER; + }, + + outpostUsed: function(x, y) { + x = typeof x == 'number' ? x : World.curPos[0]; + y = typeof y == 'number' ? y : World.curPos[1]; + var used = World.usedOutposts[x + ',' + y]; + return typeof used != 'undefined' && used == true; + }, + + useOutpost: function() { + Notifications.notify(null, 'water replenished'); + World.setWater(World.getMaxWater()); + // Save progress at outposts + State.world = World.state; + // Mark this outpost as used + World.usedOutposts[World.curPos[0] + ',' + World.curPos[1]] = true; + }, + + onArrival: function() { + Engine.keyLock = false; + // Explore in a temporary world-state. We'll commit the changes if you return home safe. + World.state = $.extend(true, {}, State.world); + World.setWater(World.getMaxWater()); + World.setHp(World.getMaxHealth()); + World.foodMove = 0; + World.waterMove = 0; + World.starvation = false; + World.thirst = false; + World.usedOutposts = {}; + World.curPos = [World.RADIUS, World.RADIUS]; + World.drawMap(); + World.setTitle(); + World.dead = false; + $('div#bagspace-world > div').empty(); + World.updateSupplies(); + $('#bagspace-world').width($('#map').width()); + }, + + setTitle: function() { + document.title = 'A Barren World'; + } +}; \ No newline at end of file