Made Dream Hero
442
package-lock.json
generated
|
@ -9,7 +9,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^2.6.11"
|
||||
"pad-end": "^1.0.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-panzoom": "^1.1.3",
|
||||
"vue2-perfect-scrollbar": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
|
|
BIN
public/assets/bat.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/assets/city.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
public/assets/default.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/discord.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/assets/dollar.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
public/assets/gold.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/assets/graveyard.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/assets/hero.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
public/assets/logo.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
public/assets/potion.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/assets/savanna.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/assets/shield.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/skeleton.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/slime.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
public/assets/witch.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
|
@ -5,11 +5,12 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap" rel="stylesheet">
|
||||
<title>Dream Hero</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong>We're sorry but Dream Hero doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
|
175
src/App.vue
|
@ -1,28 +1,173 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
||||
</div>
|
||||
<transition name="victory" v-if="$store.cycle >= 5 && !$store.keepPlaying">
|
||||
<div class="victory">
|
||||
<h1>You Win!</h1>
|
||||
<h2>Congratulations, you beat the game in:<br/>{{ formatTime($store.timePlayed) }}</h2>
|
||||
<h3>You can keep going if you'd like, but things might get weird</h3>
|
||||
<button v-on:click="keepGoing">Keep Going</button>
|
||||
</div>
|
||||
</transition>
|
||||
<div id="app" v-else-if="$store.started">
|
||||
<Header />
|
||||
<Town />
|
||||
<Dream ref="dream" />
|
||||
</div>
|
||||
<transition name="app" v-else>
|
||||
<div class="welcome" v-on:click="start">
|
||||
<img src="assets/logo.png" alt="Dream Hero" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
import Header from './components/Header.vue'
|
||||
import Town from './components/Town.vue'
|
||||
import Dream from './components/Dream.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld
|
||||
}
|
||||
name: 'App',
|
||||
components: {
|
||||
Header,
|
||||
Town,
|
||||
Dream
|
||||
},
|
||||
methods: {
|
||||
start() {
|
||||
this.$store.started = true;
|
||||
},
|
||||
keepGoing() {
|
||||
this.$store.keepPlaying = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--fg-color: #292831;
|
||||
--bg-color: #ee8695;
|
||||
--hi-color: #333f58;
|
||||
--raised-color: #fbbbad;
|
||||
--other-color: #4a7a96;
|
||||
}
|
||||
|
||||
* {
|
||||
transition-duration: 0.5s;
|
||||
font-family: "Roboto Mono", monospace;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--fg-color);
|
||||
background-color: var(--bg-color);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
button {
|
||||
outline: none;
|
||||
border: solid 2px var(--fg-color);
|
||||
background: var(--bg-color);
|
||||
}
|
||||
|
||||
#app .ps__thumb-y {
|
||||
background-color: var(--fg-color);
|
||||
}
|
||||
|
||||
#app .ps .ps__rail-x:hover,
|
||||
#app .ps .ps__rail-y:hover,
|
||||
#app .ps .ps__rail-x:focus,
|
||||
#app .ps .ps__rail-y:focus,
|
||||
#app .ps .ps__rail-x.ps--clicking,
|
||||
#app .ps .ps__rail-y.ps--clicking {
|
||||
background-color: var(--other-color);
|
||||
}
|
||||
|
||||
img, [background-image] {
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
.victory-enter {
|
||||
opacity: 0;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
.victory-leave-active {
|
||||
opacity: 0;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
.victory {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: var(--fg-color);
|
||||
color: var(--bg-color);
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
transition-duration: 2s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.victory button {
|
||||
font-size: large;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.welcome-leave-active {
|
||||
opacity: 0;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
.welcome {
|
||||
transition-duration: 2s;
|
||||
background: var(--fg-color);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.welcome img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: blur 5s infinite;
|
||||
}
|
||||
|
||||
.dream img {
|
||||
filter: drop-shadow(4px 4px 4px var(--fg-color));
|
||||
}
|
||||
|
||||
@keyframes blur {
|
||||
from {
|
||||
filter: blur(0px);
|
||||
}
|
||||
|
||||
33% {
|
||||
filter: blur(4px);
|
||||
}
|
||||
|
||||
66%, to {
|
||||
filter: blur(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Before Width: | Height: | Size: 6.7 KiB |
2
src/break_eternity.js
Normal file
63
src/common.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
import Decimal from './break_eternity.js'
|
||||
|
||||
global.Decimal = Decimal
|
||||
|
||||
const bgColor = "#ee8695";
|
||||
const fgColor = "#292831";
|
||||
const hiColor = "#333f58";
|
||||
const raisedColor = "#fbbbad";
|
||||
const otherColor = "#4a7a96";
|
||||
|
||||
const decimalZero = new Decimal(0);
|
||||
const decimalOne = new Decimal(1);
|
||||
const decimalNaN = new Decimal(NaN);
|
||||
|
||||
const buildingInfo = {
|
||||
Cot: {
|
||||
background: "default",
|
||||
enemies: [ "bat" ],
|
||||
upgrades: [
|
||||
{ description: "I'd sleep better on something comfier", cost: new Decimal(2) },
|
||||
{ description: "An even comfier bed could give me better control on when I wake up", cost: new Decimal(2500) },
|
||||
// TODO upgrade to select order of dream path
|
||||
]
|
||||
},
|
||||
Bank: {
|
||||
background: "city",
|
||||
enemies: [ "slime" ],
|
||||
upgrades: [
|
||||
{ description: "Building a bank allows me to adventure to cities in my dreams, with increased riches", cost: new Decimal(100) }
|
||||
],
|
||||
infinite: {
|
||||
description: "Improve the bank to double all gold gain",
|
||||
r: 5,
|
||||
base: 100
|
||||
}
|
||||
},
|
||||
Apothecary: {
|
||||
background: "savanna",
|
||||
enemies: [ "witch" ],
|
||||
upgrades: [
|
||||
{ description: "Building an apothecary will allow me to find potions in my dreams", cost: new Decimal(10000) }
|
||||
],
|
||||
infinite: {
|
||||
description: "Improve the apothecary to increase how much potions heal",
|
||||
r: 3,
|
||||
base: 10000
|
||||
}
|
||||
},
|
||||
Armory: {
|
||||
background: "graveyard",
|
||||
enemies: [ "skeleton" ],
|
||||
upgrades: [
|
||||
{ description: "Building an armory will help my gear up in my dreams", cost: new Decimal(10) }
|
||||
],
|
||||
infinite: {
|
||||
description: "Improve the armory to increase starting gear level",
|
||||
r: 8,
|
||||
base: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default { bgColor, fgColor, hiColor, raisedColor, otherColor, decimalZero, decimalOne, decimalNaN, buildingInfo };
|
147
src/components/Action.vue
Normal file
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div class="action" v-bind:style="{ backgroundImage: 'url(assets/' + tile.type + '.png)' }">
|
||||
<img class="shake left" src="assets/hero.png" alt="hero" />
|
||||
<div class="health left">
|
||||
<span v-bind:style="{ color: $store.hp.gt(getMaxHealth()) ? 'var(--raised-color)' : ''}">{{ formatWhole($store.hp) }}</span>
|
||||
<div class="health-fill"
|
||||
v-bind:style="{ width: 100 * $store.hp / getMaxHealth() + '%' }"></div>
|
||||
</div>
|
||||
<div class="shake right">
|
||||
<img v-if="tile.actions[$store.currentAction].type === 'gold'"
|
||||
v-bind:src="'assets/' + (tile.actions[$store.currentAction].image || 'gold') + '.png'"
|
||||
v-bind:alt="tile.actions[$store.currentAction].image || 'gold'" />
|
||||
<img v-else-if="tile.actions[$store.currentAction].type === 'enemy'"
|
||||
v-bind:src="'assets/' + tile.actions[$store.currentAction].enemy + '.png'"
|
||||
v-bind:alt="tile.actions[$store.currentAction].enemy"/>
|
||||
<img v-else-if="tile.actions[$store.currentAction].type === 'potion'"
|
||||
src="assets/potion.png" alt="potion"/>
|
||||
<img v-else-if="tile.actions[$store.currentAction].type === 'gear'"
|
||||
src="assets/shield.png" alt="shield"/>
|
||||
</div>
|
||||
<span v-if="tile.actions[$store.currentAction].type === 'gold'" class="amount right">
|
||||
{{ formatWhole(tile.actions[$store.currentAction].amount) }}
|
||||
</span>
|
||||
<div class="health right" v-if="tile.actions[$store.currentAction].type === 'enemy'">
|
||||
<span>{{ formatWhole(tile.actions[$store.currentAction].hp) }}</span>
|
||||
<div class="health-fill"
|
||||
v-bind:style="{ width: 100 * tile.actions[$store.currentAction].hp / tile.actions[$store.currentAction].maxHp + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Action',
|
||||
props: {
|
||||
tile: Object
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.action {
|
||||
border-top: solid var(--bg-color) 0;
|
||||
height: 0;
|
||||
box-sizing: border-box;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tile.active .action {
|
||||
height: 200px;
|
||||
border-top-width: 10px;
|
||||
}
|
||||
|
||||
.action img {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.left {
|
||||
position: absolute;
|
||||
left: 30%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition-duration: 0s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.right {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition-duration: 0s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tile:not(.active) .left,
|
||||
.tile:not(.active) .right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 1.5s infinite;
|
||||
}
|
||||
|
||||
.health {
|
||||
width: 150px;
|
||||
height: 16px;
|
||||
background: var(--bg-color);
|
||||
border: solid 2px var(--fg-color);
|
||||
position: absolute;
|
||||
top: 90%;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.health span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: 900;
|
||||
font-size: small;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.health-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: var(--other-color);
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.right.amount {
|
||||
position: absolute;
|
||||
left: unset;
|
||||
transform: unset;
|
||||
right: calc(30% - 60px);
|
||||
top: calc(50% - 60px);
|
||||
font-size: x-large;
|
||||
font-weight: 900;
|
||||
color: var(--other-color);
|
||||
background: var(--fg-color);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
from, 45% {
|
||||
transform: translate(-50%, -50%) rotateZ(-15deg);
|
||||
}
|
||||
|
||||
55%, 90% {
|
||||
transform: translate(-50%, -50%) rotateZ(15deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotateZ(-15deg);
|
||||
}
|
||||
}
|
||||
</style>
|
55
src/components/ActionPreview.vue
Normal file
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<img v-if="action.type === 'enemy'" v-bind:class="{ actionPreview: true, active: index === $store.currentAction }"
|
||||
v-bind:src="'assets/' + action.enemy + '.png'"
|
||||
v-bind:alt="action.enemy"/>
|
||||
<div v-else-if="action.type === 'gold'" class="amount-container">
|
||||
<img class="actionPreview"
|
||||
v-bind:src="'assets/' + (action.image || 'gold') + '.png'"
|
||||
v-bind:alt="action.image || 'gold'"
|
||||
v-bind:class="{ actionPreview: true, active: index === $store.currentAction }" />
|
||||
<span class="amount">{{ formatWhole(action.amount) }}</span>
|
||||
</div>
|
||||
<img v-else-if="action.type === 'potion'" v-bind:class="{ actionPreview: true, active: index === $store.currentAction }"
|
||||
src="assets/potion.png" alt="potion"/>
|
||||
<img v-else-if="action.type === 'gear'" v-bind:class="{ actionPreview: true, active: index === $store.currentAction }"
|
||||
src="assets/shield.png" alt="shield"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ActionPreview',
|
||||
props: {
|
||||
action: Object,
|
||||
index: Number
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actionPreview {
|
||||
margin: 9px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.tile.active .actionPreview.active {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.amount-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.amount {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
font-weight: 900;
|
||||
color: var(--other-color);
|
||||
background: var(--fg-color);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
font-size: small;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
78
src/components/Dream.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<scroll class="dream" ref="scroll">
|
||||
<Floor v-for="(tile, index) in $store.path" :key="index" :index="index" />
|
||||
<div v-if="$store.upgrades.Cot >= 1" class="endAtLoop" v-on:click="toggleEndAtLoop">
|
||||
<h2 v-if="$store.endAtLoop">Waking up at end of this sleep cycle</h2>
|
||||
<h2 v-else>Entering deeper sleep at end of this sleep cycle</h2>
|
||||
<span>Click to toggle</span>
|
||||
</div>
|
||||
<Modal :show="$store.endingDream" @close="$actions.endDream">
|
||||
<h3 slot="header">Time to wake up</h3>
|
||||
<div slot="body">
|
||||
<span v-if="$store.endingDreamStatus === 'death'">
|
||||
Unfortunately, your dream has met an untimely end. You will only receive a portion of your coins:<br/>+{{ formatWhole($store.tempPoints.pow(0.8)) }}
|
||||
</span>
|
||||
<span v-else-if="$store.endingDreamStatus === 'floor'">
|
||||
You wake up early, avoiding potential death at the cost of some of your potential coins:<br/>+{{ formatWhole($store.tempPoints.pow(0.9)) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
You wake up feeling refreshed, with a heavier wallet:<br/>+{{ formatWhole($store.tempPoints) }}
|
||||
</span>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<button v-on:click="$actions.endDream">Wake Up</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</scroll>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Floor from './Floor.vue'
|
||||
import Modal from './Modal.vue'
|
||||
|
||||
export default {
|
||||
name: 'Dream',
|
||||
components: {
|
||||
Floor,
|
||||
Modal
|
||||
},
|
||||
methods: {
|
||||
toggleEndAtLoop() {
|
||||
this.$store.endAtLoop = !this.$store.endAtLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dream {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bg-color);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.endAtLoop {
|
||||
width: 600px;
|
||||
max-width: 90vw;
|
||||
margin: 10px auto;
|
||||
background: var(--raised-color);
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.endAtLoop > * {
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
125
src/components/Floor.vue
Normal file
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div v-bind:class="{ tile: true, blur: $store.position < index, active: $store.position === index }">
|
||||
<span class="indicator">
|
||||
<img v-if="$store.position === index" class="indicator-hero" src="assets/hero.png" alt="hero" />
|
||||
<div v-else class="indicator-index">{{ index + 1 }}</div>
|
||||
</span>
|
||||
<span class="actions-container"
|
||||
v-bind:style="{
|
||||
backgroundImage: 'url(assets/' + $store.path[index].type + '.png)',
|
||||
width: $store.upgrades.Cot >= 2 && $store.position === index ? '70%' : '85%'
|
||||
}">
|
||||
<ActionPreview v-for="(action, index) in $store.path[index].actions"
|
||||
:key="index" :action="action" :index="index" />
|
||||
</span>
|
||||
<span v-bind:style="{ width: $store.upgrades.Cot >= 2 && $store.position === index ? '15%' : '0%' }"
|
||||
class="endAtFloor" v-on:click="toggleEndAtFloor">
|
||||
Wake up early:<br/><b>{{ $store.endAtFloor ? "On" : "Off" }}</b>
|
||||
</span>
|
||||
<Action :tile="$store.path[index]" />
|
||||
<div class="actionProgress">
|
||||
<div class="actionProgress-fill"
|
||||
v-bind:style="{ width: 100 * $store.actionProgress / getActionDuration() + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Action from './Action.vue'
|
||||
import ActionPreview from './ActionPreview.vue'
|
||||
|
||||
export default {
|
||||
name: 'Floor',
|
||||
props: {
|
||||
index: Number
|
||||
},
|
||||
components: {
|
||||
Action,
|
||||
ActionPreview
|
||||
},
|
||||
methods: {
|
||||
toggleEndAtFloor() {
|
||||
this.$store.endAtFloor = !this.$store.endAtFloor
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tile {
|
||||
width: 600px;
|
||||
max-width: 90vw;
|
||||
margin: 10px auto;
|
||||
background: var(--raised-color);
|
||||
}
|
||||
|
||||
.tile.blur {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.tile.active {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
width: 15%;
|
||||
height: 100px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--other-color);
|
||||
}
|
||||
|
||||
.indicator-hero {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.indicator-index {
|
||||
font-size: xx-large;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
width: 85%;
|
||||
display: inline-flex;
|
||||
height: 100px;
|
||||
vertical-align: bottom;
|
||||
padding: 9px 16px;
|
||||
box-sizing: border-box;
|
||||
background-size: cover;
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
.actionProgress {
|
||||
height: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tile.active .actionProgress {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.actionProgress-fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: var(--other-color);
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.endAtFloor {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 15%;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
43
src/components/Header.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div class="header">
|
||||
<h2>Dream Hero</h2>
|
||||
<h2>{{ formatWhole($store.points) }}</h2>
|
||||
<h2 v-if="$store.dreaming" style="color: var(--hi-color);">+{{ formatWhole($store.tempPoints) }}</h2>
|
||||
<h2 v-if="$store.dreaming" style="color: var(--hi-color);">Cycle {{ $store.cycle + 1 }}</h2>
|
||||
<a href="https://discord.gg/WzejVAx" target="_blank"><img src="assets/discord.png" /></a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Header'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.header {
|
||||
background: var(--raised-color);
|
||||
padding: 8px;
|
||||
border-bottom: solid 2px var(--fg-color);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
padding-right: 8px;
|
||||
margin-right: 8px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
h2:not(:last-of-type) {
|
||||
border-right: solid 2px var(--fg-color);
|
||||
}
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
|
@ -1,58 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
100
src/components/Modal.vue
Normal file
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<transition name="modal">
|
||||
<div class="modal-mask" v-if="show">
|
||||
<div class="modal-wrapper" v-on:click.self="$emit('close')">
|
||||
<div class="modal-container">
|
||||
|
||||
<div class="modal-header">
|
||||
<slot name="header">
|
||||
default header
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<slot name="body">
|
||||
default body
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<button class="modal-default-button" @click="$emit('close')">
|
||||
Close
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Modal',
|
||||
props: {
|
||||
show: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: table;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
width: 300px;
|
||||
margin: 0px auto;
|
||||
padding: 20px 30px;
|
||||
background-color: var(--raised-color);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
|
||||
transition: all 0.3s ease;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin-top: 0;
|
||||
color: var(--hi-color);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.modal-default-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.modal-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-enter .modal-container,
|
||||
.modal-leave-active .modal-container {
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
202
src/components/Town.vue
Normal file
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<transition name="town">
|
||||
<div class="town-container" v-if="!this.$store.dreaming">
|
||||
<panZoom @init="onInit">
|
||||
<div class="town">
|
||||
<h1 class="background">World Map</h1>
|
||||
<div v-bind:class="{ building: true, highlight: $store.tutorialOne }" style="top: 500px; left: 700px;"
|
||||
v-on:click="$actions.openBuilding('Cot')">
|
||||
Cot
|
||||
</div>
|
||||
<div class="building" v-if="!$store.tutorialOne" style="top: 200px; left: 600px;"
|
||||
v-on:click="$actions.openBuilding('Bank')">
|
||||
Bank
|
||||
</div>
|
||||
<div class="building" v-if="!$store.tutorialOne" style="top: 800px; left: 200px;"
|
||||
v-on:click="$actions.openBuilding('Apothecary')">
|
||||
Apothecary
|
||||
</div>
|
||||
<div class="building" v-if="!$store.tutorialOne" style="top: 750px; left: 800px;"
|
||||
v-on:click="$actions.openBuilding('Armory')">
|
||||
Armory
|
||||
</div>
|
||||
</div>
|
||||
</panZoom>
|
||||
<Modal :show="$store.openBuilding !== ''" @close="$actions.closeBuilding">
|
||||
<div slot="header" style="position: relative;">
|
||||
<img v-bind:src="'assets/' + buildingInfo.background + '.png'" alt="$store.openBuilding" class="header"/>
|
||||
<div class="header-enemies">
|
||||
<img v-for="enemy in buildingInfo.enemies" v-bind:src="'assets/' + enemy + '.png'" v-bind:alt="enemy" v-bind:key="enemy" />
|
||||
</div>
|
||||
<h3>{{ $store.openBuilding }}</h3>
|
||||
</div>
|
||||
<div slot="body">
|
||||
<div v-if="$store.openBuilding === 'Cot'" style="display: flex; margin-bottom: 8px; border-bottom: solid 2px var(--fg-color); padding-bottom: 8px;">
|
||||
<span style="flex-grow: 1;">I'm feeling tired...</span>
|
||||
<button @click="$actions.startDream()" style="float: right">Dream</button>
|
||||
</div>
|
||||
<div v-if="!$store.tutorialOne && upgradeInfo" style="display: flex;">
|
||||
<span style="flex-grow: 1;">{{ upgradeInfo.description }}</span>
|
||||
<button @click="upgradeBuilding()" style="float: right; margin-left: 4px;"
|
||||
v-bind:disabled="$store.points.lt(upgradeInfo.cost)">
|
||||
Cost: {{ formatWhole(upgradeInfo.cost) }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
You've fully upgraded this!
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" style="margin-bottom: -24px"></div>
|
||||
</Modal>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from './Modal.vue'
|
||||
import Common from '../common.js'
|
||||
import Decimal from '../break_eternity.js'
|
||||
|
||||
export default {
|
||||
name: 'Town',
|
||||
components: {
|
||||
Modal
|
||||
},
|
||||
computed: {
|
||||
buildingInfo() {
|
||||
return this.$store.openBuilding && Common.buildingInfo[this.$store.openBuilding];
|
||||
},
|
||||
upgradeInfo() {
|
||||
if (!this.$store.openBuilding) {
|
||||
return null;
|
||||
}
|
||||
const buildingInfo = Common.buildingInfo[this.$store.openBuilding];
|
||||
let upgrade = buildingInfo.upgrades[this.$store.upgrades[this.$store.openBuilding]];
|
||||
if (!upgrade && buildingInfo.infinite) {
|
||||
upgrade = {
|
||||
description: buildingInfo.infinite.description,
|
||||
cost: Decimal.times(buildingInfo.infinite.base, Decimal.pow(buildingInfo.infinite.r, this.$store.upgrades[this.$store.openBuilding]))
|
||||
};
|
||||
}
|
||||
return upgrade;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInit: function(panzoomInstance) {
|
||||
panzoomInstance.setTransformOrigin(null);
|
||||
},
|
||||
upgradeBuilding: function() {
|
||||
const buildingInfo = Common.buildingInfo[this.$store.openBuilding];
|
||||
let cost;
|
||||
if (this.$store.upgrades[this.$store.openBuilding] in buildingInfo.upgrades) {
|
||||
cost = buildingInfo.upgrades[this.$store.upgrades[this.$store.openBuilding]].cost;
|
||||
} else if (buildingInfo.infinite) {
|
||||
cost = Decimal.times(buildingInfo.infinite.base, Decimal.pow(buildingInfo.infinite.r, this.$store.upgrades[this.$store.openBuilding]));
|
||||
}
|
||||
if (cost.lte(this.$store.points)) {
|
||||
this.$store.points = this.$store.points.sub(cost);
|
||||
this.$store.upgrades[this.$store.openBuilding]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.town-container {
|
||||
flex-grow: 1;
|
||||
transition-duration: 2s;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bg-color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.town-enter {
|
||||
opacity: 0;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
.town-leave-active {
|
||||
opacity: 0;
|
||||
filter: blur(100px);
|
||||
}
|
||||
|
||||
.vue-pan-zoom-item {
|
||||
overflow: hidden;
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.town {
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
position: relative;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.town:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
bottom: 0%;
|
||||
left: 0%;
|
||||
right: 0%;
|
||||
background: var(--hi-color);
|
||||
filter: blur(10px);
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 500px;
|
||||
left: 500px;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 160px;
|
||||
font-weight: 900;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: var(--other-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.building {
|
||||
position: absolute;
|
||||
height: 50px;
|
||||
color: var(--other-color);
|
||||
font-size: xx-large;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
padding: 8px;
|
||||
background: var(--fg-color);
|
||||
}
|
||||
|
||||
.building.highlight {
|
||||
box-shadow: var(--bg-color) 0 0 8px 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: -30px;
|
||||
margin-bottom: 0;
|
||||
width: calc(100% + 60px);
|
||||
}
|
||||
|
||||
.header-enemies {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
height: 120px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-enemies img {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
filter: drop-shadow(4px 4px 4px var(--fg-color));
|
||||
}
|
||||
</style>
|
370
src/main.js
|
@ -1,8 +1,368 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import panZoom from 'vue-panzoom';
|
||||
import PerfectScrollbar from 'vue2-perfect-scrollbar';
|
||||
import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css';
|
||||
import Decimal from './break_eternity.js'
|
||||
import { } from './common.js'
|
||||
import { format, formatWhole, formatTime } from './numberFormatting.js'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
const storageKey = "thepaperpilot-dream";
|
||||
|
||||
new Vue({
|
||||
// Load data from localStorage
|
||||
const startData = {
|
||||
timePlayed: 0,
|
||||
keepPlaying: false,
|
||||
points: new Decimal(0),
|
||||
tempPoints: new Decimal(0),
|
||||
dreaming: false,
|
||||
autoSave: true,
|
||||
openBuilding: '',
|
||||
tutorialOne: true,
|
||||
path: new Array(10).fill(0).map(() => ({
|
||||
actions: new Array(100).fill(0).map(() => ({
|
||||
type: "",
|
||||
enemy: "",
|
||||
maxHp: new Decimal(0),
|
||||
hp: new Decimal(0),
|
||||
attackDuration: 0,
|
||||
damage: new Decimal(0),
|
||||
progress: 0
|
||||
})),
|
||||
type: ""
|
||||
})),
|
||||
currentAction: 0,
|
||||
actionProgress: -1,
|
||||
attackProgress: 0,
|
||||
cycle: 0,
|
||||
currentTime: performance.now(),
|
||||
hp: new Decimal(0),
|
||||
paused: false,
|
||||
upgrades: {
|
||||
Cot: 0,
|
||||
Bank: 0,
|
||||
Apothecary: 0,
|
||||
Armory: 0
|
||||
},
|
||||
gearLevel: 0,
|
||||
started: false,
|
||||
endAtLoop: false,
|
||||
endAtFloor: false,
|
||||
endingDream: false,
|
||||
endingDreamStatus: "death" // "loop", "floor"
|
||||
};
|
||||
function fixData(data, startData) {
|
||||
for (let dataKey in startData) {
|
||||
if (startData[dataKey] == null) {
|
||||
if (data[dataKey] === undefined) {
|
||||
data[dataKey] = null;
|
||||
}
|
||||
} else if (Array.isArray(startData[dataKey])) {
|
||||
if (data[dataKey] === undefined) {
|
||||
data[dataKey] = startData[dataKey];
|
||||
} else {
|
||||
fixData(startData[dataKey], data[dataKey]);
|
||||
}
|
||||
} else if (startData[dataKey] instanceof Decimal) { // Convert to Decimal
|
||||
if (data[dataKey] == undefined) {
|
||||
data[dataKey] = startData[dataKey];
|
||||
} else {
|
||||
data[dataKey] = new Decimal(data[dataKey]);
|
||||
}
|
||||
} else if ((!!startData[dataKey]) && (typeof startData[dataKey] === "object")) {
|
||||
if (data[dataKey] == undefined || (typeof data[dataKey] !== "object")) {
|
||||
data[dataKey] = startData[dataKey];
|
||||
} else {
|
||||
fixData(startData[dataKey], data[dataKey]);
|
||||
}
|
||||
} else {
|
||||
if (data[dataKey] == undefined) {
|
||||
data[dataKey] = startData[dataKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let loadedData = localStorage.getItem(storageKey);
|
||||
if (loadedData == null) {
|
||||
loadedData = startData;
|
||||
} else {
|
||||
loadedData = Object.assign({}, startData, JSON.parse(atob(loadedData)));
|
||||
fixData(loadedData, startData);
|
||||
}
|
||||
const store = window.player = Vue.observable(loadedData);
|
||||
Vue.prototype.$store = store;
|
||||
|
||||
// Set up auto-saving every 5s
|
||||
window.save = function() {
|
||||
if (store.autoSave) {
|
||||
localStorage.setItem(storageKey, btoa(JSON.stringify(window.player)));
|
||||
}
|
||||
}
|
||||
setInterval(window.save, 5000);
|
||||
|
||||
// Add getters to Vue
|
||||
function getAttackDuration() {
|
||||
return Decimal.times(1, Decimal.pow(.95, store.gearLevel)).clamp(Number.MIN_VALUE, Number.MAX_VALUE).toNumber();
|
||||
}
|
||||
Vue.prototype.getAttackDuration = window.getAttackDuration = getAttackDuration;
|
||||
function getAttackDamage() {
|
||||
let damage = Decimal.add(2, store.gearLevel).pow(2);
|
||||
if (store.hp.gt(getMaxHealth())) {
|
||||
damage = damage.times(2);
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
Vue.prototype.getAttackDamage = window.getAttackDamage = getAttackDamage;
|
||||
function getActionDuration() {
|
||||
return Decimal.times(2, Decimal.pow(.98, store.gearLevel)).clamp(Number.MIN_VALUE, Number.MAX_VALUE).toNumber();
|
||||
}
|
||||
Vue.prototype.getActionDuration = window.getActionDuration = getActionDuration;
|
||||
function getMaxHealth(gearLevel) {
|
||||
return new Decimal(25).times(Decimal.add(1, gearLevel || store.gearLevel).pow(2));
|
||||
}
|
||||
Vue.prototype.getMaxHealth = window.getMaxHealth = getMaxHealth;
|
||||
function isCombatActive() {
|
||||
if (!store.dreaming) {
|
||||
return false;
|
||||
}
|
||||
if (store.path[store.position].actions[store.currentAction].type !== "enemy") {
|
||||
return false;
|
||||
}
|
||||
if (store.actionProgress < getActionDuration()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Vue.prototype.isCombatActive = window.isCombatActive = isCombatActive;
|
||||
|
||||
// Set up actions
|
||||
function getRandomModifier(cycle) {
|
||||
return (Math.random() * 0.2 + 0.8) * (cycle * 1.5);
|
||||
}
|
||||
const tiles = {
|
||||
default: [
|
||||
cycle => { // Bat
|
||||
const hp = new Decimal(getRandomModifier(cycle) + 3).factorial().floor();
|
||||
return Vue.observable({
|
||||
type: "enemy",
|
||||
enemy: "bat",
|
||||
maxHp: hp,
|
||||
hp,
|
||||
attackDuration: Decimal.times(2, Decimal.pow(.9, cycle)).toNumber(),
|
||||
damage: new Decimal(getRandomModifier(cycle) + 1.5).factorial().floor(),
|
||||
progress: 0
|
||||
});
|
||||
},
|
||||
cycle => { // Gold
|
||||
return Vue.observable({ type: "gold", amount: new Decimal(getRandomModifier(cycle) + 1).factorial().times(Decimal.pow(2, store.upgrades.Bank)).floor() });
|
||||
}
|
||||
],
|
||||
city: [
|
||||
cycle => { // Slime
|
||||
const hp = new Decimal(getRandomModifier(cycle) + 2.75).factorial().floor();
|
||||
return Vue.observable({
|
||||
type: "enemy",
|
||||
enemy: "slime",
|
||||
maxHp: hp,
|
||||
hp,
|
||||
attackDuration: Decimal.times(1, Decimal.pow(.5, cycle + 1)).toNumber(),
|
||||
damage: new Decimal(cycle + 1).sqrt(),
|
||||
progress: 0
|
||||
});
|
||||
},
|
||||
cycle => { // Gold
|
||||
return Vue.observable({ type: "gold", image: "dollar", amount: new Decimal(getRandomModifier(cycle) + 2).factorial().times(Decimal.pow(2, store.upgrades.Bank)).floor() });
|
||||
}
|
||||
],
|
||||
savanna: [
|
||||
cycle => { // Witch
|
||||
const hp = new Decimal(getRandomModifier(cycle) + 3).factorial().floor();
|
||||
return Vue.observable({
|
||||
type: "enemy",
|
||||
enemy: "witch",
|
||||
maxHp: hp,
|
||||
hp,
|
||||
attackDuration: Decimal.times(2, Decimal.pow(.95, cycle)).toNumber(),
|
||||
damage: new Decimal(getRandomModifier(cycle) + 2).factorial().floor(),
|
||||
progress: 0
|
||||
});
|
||||
},
|
||||
() => { // Potion
|
||||
return Vue.observable({ type: "potion" });
|
||||
}
|
||||
],
|
||||
graveyard: [
|
||||
cycle => { // Skeleton
|
||||
const hp = new Decimal(getRandomModifier(cycle) + 2.5).factorial().floor();
|
||||
return Vue.observable({
|
||||
type: "enemy",
|
||||
enemy: "skeleton",
|
||||
maxHp: hp,
|
||||
hp,
|
||||
attackDuration: Decimal.times(3, Decimal.pow(.98, cycle)).toNumber(),
|
||||
damage: new Decimal(getRandomModifier(cycle) + 2.5).factorial().floor(),
|
||||
progress: 0
|
||||
});
|
||||
},
|
||||
cycle => { // Gear
|
||||
return Vue.observable({ type: "gear", amount: (cycle + 1) / 10 });
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const actions = window.actions = {
|
||||
startDream() {
|
||||
store.endAtLoop = false;
|
||||
store.endAtFloor = false;
|
||||
store.tutorialOne = false;
|
||||
store.openBuilding = '';
|
||||
store.cycle = -1;
|
||||
let tiles = [ "default" ];
|
||||
if (store.upgrades["Bank"] >= 1) {
|
||||
tiles.push("city");
|
||||
}
|
||||
if (store.upgrades["Apothecary"] >= 1) {
|
||||
tiles.push("savanna");
|
||||
}
|
||||
if (store.upgrades["Armory"] >= 1) {
|
||||
tiles.push("graveyard");
|
||||
}
|
||||
store.path = new Array(10).fill(0).map(() => ({ type: tiles[Math.floor(Math.random() * tiles.length)] }));
|
||||
store.position = 0;
|
||||
store.tempPoints = new Decimal(0);
|
||||
store.gearLevel = store.upgrades.Armory;
|
||||
store.hp = getMaxHealth();
|
||||
this.startLoop();
|
||||
store.dreaming = true;
|
||||
},
|
||||
endDream() {
|
||||
let modifier = 1;
|
||||
if (store.endingDreamStatus === "death") {
|
||||
modifier = 0.8;
|
||||
} else if (store.endingDreamStatus === "floor") {
|
||||
modifier = 0.9;
|
||||
}
|
||||
store.points = store.points.add(store.tempPoints.pow(modifier));
|
||||
store.dreaming = false;
|
||||
store.endingDream = false;
|
||||
},
|
||||
startLoop() {
|
||||
store.cycle++;
|
||||
store.position = -1;
|
||||
store.path.forEach(tile => {
|
||||
tile.actions = new Array(store.cycle + 1).fill(0).map(() => tiles[tile.type][Math.floor(Math.random() * tiles[tile.type].length)](store.cycle));
|
||||
});
|
||||
window.vue.$root.$children[0].$refs.dream.$refs.scroll.$el.scrollTo({top: 0, behavior: 'smooth'});
|
||||
this.nextFloor();
|
||||
},
|
||||
nextFloor() {
|
||||
store.position = store.position + 1;
|
||||
if (store.position >= 10) {
|
||||
if (store.upgrades.Cot >= 1 && !store.endAtLoop) {
|
||||
this.startLoop();
|
||||
} else {
|
||||
store.endingDreamStatus = "loop";
|
||||
store.endingDream = true;
|
||||
store.position = store.points - 1;
|
||||
store.currentAction = store.currentAction - 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
store.currentAction = -1;
|
||||
const scrollTarget = window.vue.$root.$children[0].$refs.dream.$refs.scroll.$el.children[store.position].offsetTop - 250;
|
||||
window.vue.$root.$children[0].$refs.dream.$refs.scroll.$el.scrollTo({ top: scrollTarget, behavior: 'smooth' });
|
||||
this.nextAction();
|
||||
},
|
||||
nextAction() {
|
||||
store.currentAction++;
|
||||
if (store.currentAction >= store.path[store.position].actions.length) {
|
||||
if (store.upgrades.Cot < 2 || !store.endAtFloor) {
|
||||
this.nextFloor();
|
||||
} else {
|
||||
store.endingDreamStatus = "floor";
|
||||
store.endingDream = true;
|
||||
store.currentAction = store.currentAction - 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
store.actionProgress = 0;
|
||||
store.attackProgress = 0;
|
||||
},
|
||||
openBuilding(building) {
|
||||
store.openBuilding = building;
|
||||
},
|
||||
closeBuilding() {
|
||||
store.openBuilding = '';
|
||||
}
|
||||
};
|
||||
Vue.prototype.$actions = actions;
|
||||
|
||||
// Add utility functions to Vue
|
||||
Vue.prototype.format = format;
|
||||
Vue.prototype.formatWhole = formatWhole;
|
||||
Vue.prototype.formatTime = formatTime;
|
||||
|
||||
// Setup Vue
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(panZoom);
|
||||
Vue.use(PerfectScrollbar, { name: 'scroll' });
|
||||
|
||||
// Start Vue
|
||||
window.vue = new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
}).$mount('#app');
|
||||
|
||||
// Setup update loop
|
||||
function update(currTime) {
|
||||
// TODO offline time doesn't work if using performance.now()
|
||||
const delta = (currTime - store.currentTime) / 1000;
|
||||
if (delta > 0 && !store.paused && store.started && (store.cycle < 5 || store.keepPlaying)) {
|
||||
store.timePlayed += delta;
|
||||
if (store.dreaming && !store.endingDream) {
|
||||
store.actionProgress += delta;
|
||||
if (isCombatActive()) {
|
||||
store.attackProgress += delta;
|
||||
store.path[store.position].actions[store.currentAction].progress += delta;
|
||||
let alive = true;
|
||||
if (store.attackProgress >= getAttackDuration()) {
|
||||
store.attackProgress = 0;
|
||||
store.path[store.position].actions[store.currentAction].hp =
|
||||
store.path[store.position].actions[store.currentAction].hp.sub(getAttackDamage());
|
||||
if (store.path[store.position].actions[store.currentAction].hp.lte(0)) {
|
||||
actions.nextAction();
|
||||
alive = false;
|
||||
}
|
||||
}
|
||||
if (alive && store.path[store.position].actions[store.currentAction].progress >= store.path[store.position].actions[store.currentAction].attackDuration) {
|
||||
store.path[store.position].actions[store.currentAction].progress = 0;
|
||||
store.hp = store.hp.sub(store.path[store.position].actions[store.currentAction].damage);
|
||||
if (store.hp.lte(0)) {
|
||||
store.endingDream = true;
|
||||
store.endingDreamStatus = "death";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (store.actionProgress >= getActionDuration()) {
|
||||
switch (store.path[store.position].actions[store.currentAction].type) {
|
||||
case "gold":
|
||||
store.tempPoints = store.tempPoints.add(store.path[store.position].actions[store.currentAction].amount);
|
||||
break;
|
||||
case "gear": {
|
||||
const oldGearLevel = store.gearLevel;
|
||||
store.gearLevel += store.path[store.position].actions[store.currentAction].amount * store.upgrades.Armory;
|
||||
store.hp = store.hp.add(getMaxHealth().sub(getMaxHealth(oldGearLevel)));
|
||||
break;
|
||||
}
|
||||
case "potion":
|
||||
store.hp = store.hp.add(getMaxHealth().times(0.25).times(store.upgrades.Apothecary + 1));
|
||||
break;
|
||||
}
|
||||
actions.nextAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
store.currentTime = currTime;
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
update(performance.now());
|
||||
|
|
115
src/numberFormatting.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
import Decimal from './break_eternity.js'
|
||||
|
||||
function exponentialFormat(num, precision, mantissa = true) {
|
||||
let e = num.log10().floor();
|
||||
let m = num.div(Decimal.pow(10, e));
|
||||
if(m.toStringWithDecimalPlaces(precision) === 10) {
|
||||
m = new Decimal(1);
|
||||
e = e.add(1);
|
||||
}
|
||||
e = commaFormat(e);
|
||||
if (mantissa) {
|
||||
return m.toStringWithDecimalPlaces(precision)+"e"+e;
|
||||
} else {
|
||||
return "e"+e;
|
||||
}
|
||||
}
|
||||
|
||||
function commaFormat(num, precision) {
|
||||
if (num === null || num === undefined) {
|
||||
return "NaN";
|
||||
}
|
||||
if (num.mag < 0.001) {
|
||||
return (0).toFixed(precision);
|
||||
}
|
||||
if (precision === null || precision === undefined) {
|
||||
if (num.layer > 1) {
|
||||
let firstPart = new Decimal(num);
|
||||
firstPart.mag = Math.floor(num.mag);
|
||||
let secondPart = new Decimal(num);
|
||||
secondPart.layer = 0;
|
||||
secondPart.mag = num.mag - firstPart.mag;
|
||||
return firstPart.floor().toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") + secondPart.toStringWithDecimalPlaces(2).substr(1);
|
||||
}
|
||||
return num.floor().toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
|
||||
}
|
||||
return num.toStringWithDecimalPlaces(precision).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
|
||||
}
|
||||
|
||||
function regularFormat(num, precision) {
|
||||
if (num === null || num === undefined) {
|
||||
return "NaN";
|
||||
}
|
||||
if (num.eq(0)) {
|
||||
return (0).toFixed(precision);
|
||||
}
|
||||
if (num.mag < 0.001) {
|
||||
return num.toExponential(precision);
|
||||
}
|
||||
return num.toStringWithDecimalPlaces(precision);
|
||||
}
|
||||
|
||||
function format(decimal, precision=2,) {
|
||||
decimal = new Decimal(decimal);
|
||||
if (isNaN(decimal.sign)||isNaN(decimal.layer)||isNaN(decimal.mag)) {
|
||||
return "NaN";
|
||||
}
|
||||
if (decimal.sign<0) {
|
||||
return "-"+format(decimal.neg(), precision);
|
||||
}
|
||||
if (decimal.mag === Number.POSITIVE_INFINITY) {
|
||||
return "Infinity";
|
||||
}
|
||||
if (decimal.gte("eeee1000")) {
|
||||
const slog = decimal.slog();
|
||||
if (slog.gte(1e6)) {
|
||||
return "F" + format(slog.floor());
|
||||
} else {
|
||||
return Decimal.pow(10, slog.sub(slog.floor())).toStringWithDecimalPlaces(3) + "F" + commaFormat(slog.floor(), 0);
|
||||
}
|
||||
} else if (decimal.gte("1e100000")) {
|
||||
return exponentialFormat(decimal, 0, false);
|
||||
} else if (decimal.gte("1e1000")) {
|
||||
return exponentialFormat(decimal, 0);
|
||||
} else if (decimal.gte(1e6)) {
|
||||
return exponentialFormat(decimal, precision);
|
||||
} else if (decimal.gte(1e3)) {
|
||||
return commaFormat(decimal, 0);
|
||||
} else {
|
||||
return regularFormat(decimal, precision);
|
||||
}
|
||||
}
|
||||
|
||||
function formatWhole(decimal) {
|
||||
decimal = new Decimal(decimal).floor();
|
||||
if (decimal.gte(1e6)) {
|
||||
return format(decimal, 2);
|
||||
}
|
||||
if (decimal.lte(0.98) && !decimal.eq(0)) {
|
||||
return format(decimal, 2);
|
||||
}
|
||||
return format(decimal, 0);
|
||||
}
|
||||
|
||||
function formatTime(s) {
|
||||
if (s<60) {
|
||||
return format(s)+"s";
|
||||
} else if (s<3600) {
|
||||
return formatWhole(Math.floor(s/60))+"m "+format(s%60)+"s";
|
||||
} else if (s<86400) {
|
||||
return formatWhole(Math.floor(s/3600))+"h "+formatWhole(Math.floor(s/60)%60)+"m "+format(s%60)+"s";
|
||||
} else if (s<31536000) {
|
||||
return formatWhole(Math.floor(s/84600)%365)+"d " + formatWhole(Math.floor(s/3600)%24)+"h "+formatWhole(Math.floor(s/60)%60)+"m "+format(s%60)+"s";
|
||||
} else {
|
||||
return formatWhole(Math.floor(s/31536000))+"y "+formatWhole(Math.floor(s/84600)%365)+"d " + formatWhole(Math.floor(s/3600)%24)+"h "+formatWhole(Math.floor(s/60)%60)+"m "+format(s%60)+"s";
|
||||
}
|
||||
}
|
||||
|
||||
window.format = format;
|
||||
window.formatWhole = formatWhole;
|
||||
window.formatTime = formatTime;
|
||||
window.regularFormat = regularFormat;
|
||||
window.commaFormat = commaFormat;
|
||||
window.exponentialFormat = exponentialFormat;
|
||||
|
||||
export { format, formatWhole, formatTime, regularFormat, commaFormat, exponentialFormat };
|
3
vue.config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
publicPath: ''
|
||||
};
|