Adding ADR to Github

This commit is contained in:
Michael Townsend
2013-07-03 07:56:13 -07:00
parent 9616c79816
commit 19abccfcc4
36 changed files with 9668 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>A Dark Room</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>
+27
View File
@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<title>A Dark Room</title>
<style>
div {
width: 960px;
margin: auto;
text-align: center;
margin-top: 100px;
}
</style>
</head>
<body>
<div>
<strong>
A Dark Room makes use of HTML5 and CSS3, which your current browser does not appear to support.<br/>
Please update your browser for the best experience:<br/>
</strong>
<a href='http://www.mozilla.org/en-US/firefox/new/'><img src='img/firefox.png' alt='Firefox' /></a>
<a href='https://www.google.com/intl/en/chrome/browser/'><img src='img/chrome.png' alt='Firefox' /></a>
<a href='http://windows.microsoft.com/en-CA/internet-explorer/download-ie'><img src='img/ie.png' alt='Firefox' /></a>
<br/><br/>
Or you can <a href='index.html?ignorebrowser=true'>play anyway</a>, but it probably won't work!
</div>
</body>
</html>
+497
View File
@@ -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;
}
+64
View File
@@ -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;
}
+66
View File
@@ -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;
}
+79
View File
@@ -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;
}
+7
View File
@@ -0,0 +1,7 @@
div#hullRow {
width: 70px;
}
div#engineRow {
width: 70px;
margin-bottom: 20px;
+134
View File
@@ -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;
}
+74
View File
@@ -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;
}
BIN
View File
Binary file not shown.
+5
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

+77
View File
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/CreativeWork">
<head>
<!--
A Dark Room (v1.2)
==================
A minimalist text adventure by Michael Townsend.
Inspired by Candy Box (http://candies.aniwey.net/)
Please don't steal me.
-->
<title>A Dark Room</title>
<meta itemprop="description" name="description" property="og:description" content="A minimalist text adventure">
<meta itemprop="image" property="og:image" content="img/adr.png" />
<meta itemprop="name" property="og:title" content="A Dark Room" />
<link rel="shortcut icon" href="favicon.ico" />
<link rel="image_src" href="img/adr.png" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="lib/jquery.color-2.1.2.min.js"></script>
<script src="script/Button.js"></script>
<script src="script/engine.js"></script>
<script src="script/header.js"></script>
<script src="script/notifications.js"></script>
<script src="script/events.js"></script>
<script src="script/room.js"></script>
<script src="script/outside.js"></script>
<script src="script/world.js"></script>
<script src="script/path.js"></script>
<script src="script/ship.js"></script>
<script src="script/space.js"></script>
<!-- Event modules -->
<script src="script/events/global.js"></script>
<script src="script/events/room.js"></script>
<script src="script/events/outside.js"></script>
<script src="script/events/encounters.js"></script>
<script src="script/events/setpieces.js"></script>
<script type='text/javascript'>
var oldIE = false;
</script>
<!-- [if lt IE 9]>
<script type="text/javascript">oldIE = true;</script>
<![endif]-->
<link rel="stylesheet" type="text/css" href="css/main.css" />
<link rel="stylesheet" type="text/css" href="css/room.css" />
<link rel="stylesheet" type="text/css" href="css/outside.css" />
<link rel="stylesheet" type="text/css" href="css/path.css" />
<link rel="stylesheet" type="text/css" href="css/world.css" />
<link rel="stylesheet" type="text/css" href="css/ship.css" />
<link rel="stylesheet" type="text/css" href="css/space.css" />
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-41314886-1', 'doublespeakgames.com');
ga('send', 'pageview');
</script>
</head>
<body>
<div id="wrapper">
<div id="saveNotify">saved.</div>
<div id="content">
<div id="outerSlider">
<div id="main">
<div id="header"></div>
</div>
</div>
</div>
</div>
</body>
</html>
+2
View File
File diff suppressed because one or more lines are too long
+23
View File
@@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<title>A Dark Room</title>
<style>
div {
width: 960px;
margin: auto;
text-align: center;
margin-top: 100px;
}
</style>
</head>
<body>
<div>
<strong>
A Dark Room isn't really mobile-friendly<br/>
Sorry about that!<br/>
</strong><br/>
Of course you can <a href='index.html?ignorebrowser=true'>play anyway</a>, but it probably won't work!
</div>
</body>
</html>
+86
View File
@@ -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 = $('<div>')
.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($("<div>").addClass('cooldown'));
if(options.cost) {
var ttPos = options.ttPos ? options.ttPos : "bottom right";
var costTooltip = $('<div>').addClass('tooltip ' + ttPos);
for(var k in options.cost) {
$("<div>").addClass('row_key').text(k).appendTo(costTooltip);
$("<div>").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');
}
}
};
+550
View File
@@ -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();
}
$('<div>').attr('id', 'locationSlider').appendTo('#main');
$('<span>')
.addClass('deleteSave')
.text('restart.')
.click(Engine.confirmDelete)
.appendTo('body');
$('<div>')
.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();
});
+735
View File
@@ -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());
$('<div>').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) {
$('<div>').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');
}
$('<div>').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();
$('<div>').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 = $('<div>').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);
}
}
$('<div>').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 = $('<div>').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 = $('<div>').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 = $('<div>').addClass('fighter').text(char).data('hp', hp).data('maxHp', maxhp);
$('<div>').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) {
$('<div>').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 = $('<div>').attr('id', 'event').addClass('eventPanel').css('opacity', '0');
if(options != null && options.width != null) {
Events.eventPanel().css('width', options.width);
}
$('<div>').addClass('eventTitle').text(Events.activeEvent().title).appendTo(Events.eventPanel());
$('<div>').attr('id', 'description').appendTo(Events.eventPanel());
$('<div>').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();
});
}
};
+325
View File
@@ -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'
}
}
},
];
+65
View File
@@ -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'
}
}
}
}
}
];
+127
View File
@@ -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'
}
}
}
}
}
];
+506
View File
@@ -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'
}
}
}
}
}
]
File diff suppressed because it is too large Load Diff
+28
View File
@@ -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 $('<div>').attr('id', "location_" + id)
.addClass('headerButton')
.text(text).click(function() {
if(Header.canTravel()) {
Engine.travelTo(module);
}
}).appendTo($('div#header'));
}
};
+58
View File
@@ -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 = $('<div>').attr({
id: 'notifications',
className: 'notifications'
});
// Create the transparency gradient
$('<div>').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 = $('<div>').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());
}
}
}
};
+611
View File
@@ -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 = $('<div>').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 = $('<div>').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 = $('<div>')
.attr('id', 'workers_row_' + name.replace(' ','-'))
.addClass('workerRow');
$('<div>').addClass('row_key').text(name).appendTo(row);
var val = $('<div>').addClass('row_val').appendTo(row);
$('<span>').text(num).appendTo(val);
if(name != 'gatherer') {
$('<div>').addClass('upBtn').appendTo(val).click(Outside.increaseWorker);
$('<div>').addClass('dnBtn').appendTo(val).click(Outside.decreaseWorker);
}
$('<div>').addClass('clear').appendTo(row);
var tooltip = $('<div>').addClass('tooltip bottom right').appendTo(row);
var income = Outside._INCOME[name];
for(var s in income.stores) {
var r = $('<div>').addClass('storeRow');
$('<div>').addClass('row_key').text(s).appendTo(r);
$('<div>').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 = $('<div>').attr('id', id).addClass('storeRow');
$('<div>').addClass('row_key').text(name).appendTo(row);
$('<div>').addClass('row_val').text(num).appendTo(row);
$('<div>').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 = $('<div>').attr('id', 'village').css('opacity', 0);
population = $('<div>').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 = $('<div>').addClass('storeRow');
$('<div>').addClass('row_key').text(store).appendTo(row);
$('<div>').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);
}
}
+280
View File
@@ -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 = $('<div>').attr('id', "pathPanel")
.addClass('location')
.appendTo('div#locationSlider');
// Add the outfitting area
var outfitting = $('<div>').attr('id', 'outfitting').appendTo(this.panel);
var bagspace = $('<div>').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 = $('<div>').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 = $('<div>').attr('id', id).addClass('perkRow').appendTo(perks);
$('<div>').addClass('row_key').text(k).appendTo(r);
$('<div>').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 = $('<div>').attr('id', 'armourRow').addClass('outfitRow').prependTo(outfit);
$('<div>').addClass('row_key').text('armour').appendTo(aRow);
$('<div>').addClass('row_val').text(armour).appendTo(aRow);
$('<div>').addClass('clear').appendTo(aRow);
} else {
$('.row_val', aRow).text(armour);
}
// Add the water row
var wRow = $('#waterRow');
if(wRow.length == 0) {
wRow = $('<div>').attr('id', 'waterRow').addClass('outfitRow').insertAfter(aRow);
$('<div>').addClass('row_key').text('water').appendTo(wRow);
$('<div>').addClass('row_val').text(World.getMaxWater()).appendTo(wRow);
$('<div>').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 = $('<div>').attr('id', 'outfit_row_' + name.replace(' ', '-')).addClass('outfitRow');
$('<div>').addClass('row_key').text(name).appendTo(row);
var val = $('<div>').addClass('row_val').appendTo(row);
$('<span>').text(num).appendTo(val);
$('<div>').addClass('upBtn').appendTo(val).click(Path.increaseSupply);
$('<div>').addClass('dnBtn').appendTo(val).click(Path.decreaseSupply);
$('<div>').addClass('clear').appendTo(row);
var numAvailable = Engine.getStore(name);
var tt = $('<div>').addClass('tooltip bottom right').appendTo(row);
$('<div>').addClass('row_key').text('weight').appendTo(tt);
$('<div>').addClass('row_val').text(Path.getWeight(name)).appendTo(tt);
$('<div>').addClass('row_key').text('available').appendTo(tt);
$('<div>').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;
}
}
+1058
View File
File diff suppressed because it is too large Load Diff
+165
View File
@@ -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 = $('<div>').attr('id', "shipPanel")
.addClass('location')
.appendTo('div#locationSlider');
Engine.updateSlider();
// Draw the hull label
var hullRow = $('<div>').attr('id', 'hullRow').appendTo('div#shipPanel');
$('<div>').addClass('row_key').text('hull:').appendTo(hullRow);
$('<div>').addClass('row_val').text(State.ship.hull).appendTo(hullRow);
$('<div>').addClass('clear').appendTo(hullRow);
// Draw the thrusters label
var engineRow = $('<div>').attr('id', 'engineRow').appendTo('div#shipPanel');
$('<div>').addClass('row_key').text('engine:').appendTo(engineRow);
$('<div>').addClass('row_val').text(State.ship.thrusters).appendTo(engineRow);
$('<div>').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;
}
};
+453
View File
@@ -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 = $('<div>').attr('id', "spacePanel")
.addClass('location')
.appendTo('#outerSlider');
// Create the ship
Space.ship = $('<div>').text("@").attr('id', 'ship').appendTo(this.panel);
// Create the hull display
var h = $('<div>').attr('id', 'hullRemaining').appendTo(this.panel);
$('<div>').addClass('row_key').text('hull: ').appendTo(h);
$('<div>').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 = $('<div>').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 = $('<div>').attr('id', 'starsContainer').appendTo('body');
Space.stars = $('<div>').css('bottom', '0px').attr('id', 'stars').appendTo(starsContainer);
var s1 = $('<div>').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 = $('<div>').css('bottom', '0px').attr('id', 'starsBack').appendTo(starsContainer);
s1 = $('<div>').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';
$('<div>').text('.').addClass('star').css({
top: top,
left: left
}).appendTo(el);
$('<div>').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;
}
}
};
+825
View File
@@ -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&nbsp;Outpost' };
World.LANDMARKS[World.TILE.IRON_MINE] = { num: 1, minRadius: 5, maxRadius: 5, scene: 'ironmine', label: 'Iron&nbsp;Mine' };
World.LANDMARKS[World.TILE.COAL_MINE] = { num: 1, minRadius: 10, maxRadius: 10, scene: 'coalmine', label: 'Coal&nbsp;Mine' };
World.LANDMARKS[World.TILE.SULPHUR_MINE] = { num: 1, minRadius: 20, maxRadius: 20, scene: 'sulphurmine', label: 'Sulphur&nbsp;Mine' };
World.LANDMARKS[World.TILE.HOUSE] = { num: 10, minRadius: 0, maxRadius: World.RADIUS * 1.5, scene: 'house', label: 'An&nbsp;Old&nbsp;House' };
World.LANDMARKS[World.TILE.CAVE] = { num: 5, minRadius: 3, maxRadius: 10, scene: 'cave', label: 'A&nbsp;Damp&nbsp;Cave' };
World.LANDMARKS[World.TILE.TOWN] = { num: 10, minRadius: 10, maxRadius: 20, scene: 'town', label: 'An&nbsp;Abandoned&nbsp;Town' };
World.LANDMARKS[World.TILE.CITY] = { num: 20, minRadius: 20, maxRadius: World.RADIUS * 1.5, scene: 'city', label: 'A&nbsp;Ruined&nbsp;City' };
World.LANDMARKS[World.TILE.SHIP] = {num: 1, minRadius: 28, maxRadius: 28, scene: 'ship', label: 'A&nbsp;Crashed&nbsp;Starship'};
World.LANDMARKS[World.TILE.BOREHOLE] = {num: 10, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'borehole', label: 'A&nbsp;Borehole'};
World.LANDMARKS[World.TILE.BATTLEFIELD] = {num: 5, minRadius: 18, maxRadius: World.RADIUS * 1.5, scene: 'battlefield', label: 'A&nbsp;Battlefield'};
World.LANDMARKS[World.TILE.SWAMP] = {num: 1, minRadius: 15, maxRadius: World.RADIUS * 1.5, scene: 'swamp', label: 'A&nbsp;Murky&nbsp;Swamp'};
if(typeof State.world == 'undefined') {
State.world = {
map: World.generateMap(),
mask: World.newMask()
};
}
// Create the World panel
this.panel = $('<div>').attr('id', "worldPanel").addClass('location').appendTo('#outerSlider');
// Create the shrink wrapper
var outer = $('<div>').attr('id', 'worldOuter').appendTo(this.panel);
// Create the bag panel
$('<div>').attr('id', 'bagspace-world').append($('<div>')).appendTo(outer);
$('<div>').attr('id', 'backpackTitle').appendTo(outer);
$('<div>').attr('id', 'backpackSpace').appendTo(outer);
$('<div>').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 = $('<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 $('<div>').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 += '<span class="landmark">@<div class="tooltip ' + ttClass + '">Wanderer</div></span>';
} else if(World.state.mask[i][j]) {
var c = World.state.map[i][j];
switch(c) {
case World.TILE.VILLAGE:
mapString += '<span class="landmark">' + c + '<div class="tooltip' + ttClass + '">The&nbsp;Village</div></span>';
break;
default:
if(typeof World.LANDMARKS[c] != 'undefined' && (c != World.TILE.OUTPOST || !World.outpostUsed(i, j))) {
mapString += '<span class="landmark">' + c + '<div class="tooltip' + ttClass + '">' + World.LANDMARKS[c].label + '</div></span>';
} else {
if(c.length > 1) {
c = c[0];
}
mapString += c;
}
break;
}
} else {
mapString += '&nbsp;';
}
}
mapString += '<br/>';
}
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';
}
};