| @@ -1,8 +1,8 @@ | |||
| <template> | |||
| <div id="app"> | |||
| <Header version="pre-alpha" @selectEncounter="selectEncounter" :encounters="encounters" /> | |||
| <Explore :world="world" /> | |||
| <!-- <Combat v-show="$data.encounter === encounter" v-for="(encounter, index) in encounters" :key="'encounter-' + index" :encounter="encounter" /> --> | |||
| <Header /> | |||
| <Explore v-if="mode === 'explore'" :world="world" /> | |||
| <Combat v-if="mode === 'combat'" :encounter="encounter" /> | |||
| </div> | |||
| </template> | |||
| @@ -15,8 +15,10 @@ import * as Creatures from '@/game/creatures' | |||
| import * as Items from '@/game/items' | |||
| import { Creature } from '@/game/creature' | |||
| import { ProperNoun, TheyPronouns, FemalePronouns, MalePronouns, ImproperNoun, POV } from '@/game/language' | |||
| import { Place, Direction, World } from '@/game/world' | |||
| import { Encounter } from './game/combat' | |||
| import { Place, Direction, World, Choice } from '@/game/world' | |||
| import { Encounter, Side } from './game/combat' | |||
| import { LogLine, nilLog } from './game/interface' | |||
| import { InstantKillEffect } from './game/combat/effects' | |||
| @Component({ | |||
| components: { | |||
| @@ -26,7 +28,8 @@ import { Encounter } from './game/combat' | |||
| return { | |||
| encounter: null, | |||
| encounters: null, | |||
| world: null | |||
| world: null, | |||
| mode: 'explore' | |||
| } | |||
| } | |||
| }) | |||
| @@ -35,9 +38,10 @@ export default class App extends Vue { | |||
| super() | |||
| } | |||
| @Emit('selectEncounter') | |||
| selectEncounter (encounter: Encounter) { | |||
| @Emit('startFight') | |||
| startFight (encounter: Encounter) { | |||
| this.$data.encounter = encounter | |||
| this.$data.mode = 'combat' | |||
| } | |||
| created () { | |||
| @@ -51,16 +55,29 @@ export default class App extends Vue { | |||
| this.$data.encounter = this.$data.encounters[0] | |||
| const foo = new Place('Foo', 'A very foo-y place') | |||
| const other = new Place('Bar', 'The bar') | |||
| foo.biconnect(Direction.North, other) | |||
| const home = new Place('Home', 'This is not not home') | |||
| const something = new Place('Baz', 'Despacito 3') | |||
| foo.biconnect(Direction.East, something) | |||
| const street = new Place('Street', 'The street') | |||
| home.biconnect(Direction.North, street) | |||
| this.$data.encounters.forEach((encounter: Encounter) => home.choices.push(new Choice( | |||
| encounter.desc.name, | |||
| 'Fight time!', | |||
| (world, executor) => { | |||
| this.startFight( | |||
| encounter | |||
| ) | |||
| return nilLog | |||
| } | |||
| ))) | |||
| const bar = new Place('Bar', 'This is the bar') | |||
| street.biconnect(Direction.East, bar) | |||
| const player = new Creatures.Wolf() | |||
| player.perspective = POV.Second | |||
| player.location = foo | |||
| player.side = Side.Heroes | |||
| player.location = home | |||
| this.$data.world = new World(player) | |||
| } | |||
| @@ -0,0 +1,75 @@ | |||
| <template> | |||
| <button :class="{ 'choice-button': true, 'enabled': choice.accessible(world) }"> | |||
| {{ choice.name }} | |||
| <div class="tooltip-template"> | |||
| <div class="tooltip-title">{{ choice.name }}</div> | |||
| <div class="tooltip-body">{{ choice.desc }}</div> | |||
| </div> | |||
| </button> | |||
| </template> | |||
| <script lang="ts"> | |||
| import { Component, Prop, Vue, Watch, Emit } from 'vue-property-decorator' | |||
| import { Action, GroupAction } from '@/game/combat' | |||
| import { Creature } from '@/game/creature' | |||
| import { Place, Direction, Choice, World } from '@/game/world' | |||
| import tippy from 'tippy.js' | |||
| @Component({}) | |||
| export default class ChoiceButton extends Vue { | |||
| @Prop() | |||
| choice!: Choice | |||
| @Prop() | |||
| world!: World | |||
| mounted () { | |||
| const elem = this.$el | |||
| const tooltip = this.$el.querySelector(".tooltip-template") as HTMLElement | |||
| tippy( | |||
| elem, | |||
| { | |||
| content: tooltip | |||
| } | |||
| ) | |||
| } | |||
| } | |||
| </script> | |||
| <style scoped> | |||
| .choice-button, | |||
| .choice-button:hover, | |||
| .choice-button:active { | |||
| width: 90%; | |||
| margin: 5%; | |||
| background: repeating-linear-gradient(45deg, #333, #333 20px, #222 20px, #222 40px); | |||
| color: #ccc; | |||
| font-size: 200%; | |||
| border-color: #ccc; | |||
| border-width: 3px; | |||
| border-radius: 8px; | |||
| border-style: outset; | |||
| outline: none; | |||
| padding: 0; | |||
| } | |||
| .choice-button:focus { | |||
| background: repeating-linear-gradient(45deg, #444, #444 20px, #333 20px, #333 40px); | |||
| } | |||
| .choice-button.enabled { | |||
| background: #555; | |||
| } | |||
| .choice-button.enabled:hover { | |||
| background: #777; | |||
| } | |||
| .choice-button.enabled:active { | |||
| background: #888; | |||
| border-style: inset; | |||
| } | |||
| </style> | |||
| @@ -4,18 +4,20 @@ | |||
| </div> | |||
| <div class="explore-worldinfo"> | |||
| <p class="worldinfo-date">{{ world.time.format("MMMM Do YYYY") }}</p> | |||
| <p class="worldinfo-time">{{ world.time.format("h:mm:ss A")}}</p> | |||
| <p class="worldinfo-date">{{ world.time.format("MMMM Do yyy") }}</p> | |||
| <p class="worldinfo-time">{{ world.time.format("h:mm A")}}</p> | |||
| </div> | |||
| <Statblock :subject="world.player" :initiative="0" /> | |||
| <div class="explore-info"> | |||
| <h2 class="location-name">{{ location.name }}</h2> | |||
| <p class="location-desc">{{ location.desc }}</p> | |||
| </div> | |||
| <div class="explore-nav"> | |||
| <NavButton @click.native="location=location.connections[direction]" v-for="direction in Object.keys(location.connections)" :key="direction" :style="navBtnCss(direction)" :location="location" :direction="direction" /> | |||
| <NavButton @click.native="location.connections[direction].travel(world.player, world)" v-for="direction in Object.keys(location.connections)" :key="direction" :style="navBtnCss(direction)" :location="location" :direction="direction" /> | |||
| <div class="nav-filler" :Style="navBtnCss(direction)" v-for="direction in Object.keys(directions)" :key="direction"></div> | |||
| </div> | |||
| <div class="explore-actions"> | |||
| <div class="explore-choices"> | |||
| <ChoiceButton @click.native="writeLog(choice.execute(world, world.player))" v-for="(choice, index) in location.choices.filter(choice => choice.visible(world))" :key="'choice' + index" :choice="choice" :world="world" /> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| @@ -25,10 +27,18 @@ | |||
| import { Component, Prop, Vue } from 'vue-property-decorator' | |||
| import { Direction, World, Place } from '@/game/world' | |||
| import NavButton from './NavButton.vue' | |||
| import ChoiceButton from './ChoiceButton.vue' | |||
| import Statblock from './Statblock.vue' | |||
| import { LogEntry } from '@/game/interface' | |||
| @Component({ | |||
| components: { | |||
| NavButton | |||
| NavButton, ChoiceButton, Statblock | |||
| }, | |||
| data () { | |||
| return { | |||
| directions: Direction | |||
| } | |||
| } | |||
| }) | |||
| @@ -49,6 +59,27 @@ export default class Explore extends Vue { | |||
| '--nav-direction': dir | |||
| } | |||
| } | |||
| writeLog (entry: LogEntry) { | |||
| const log = this.$el.querySelector(".explore-log") | |||
| if (log !== null) { | |||
| const before = log.querySelector("div.log-entry") | |||
| const holder = document.createElement("div") | |||
| holder.classList.add("log-entry") | |||
| entry.render().forEach(element => { | |||
| holder.appendChild(element) | |||
| }) | |||
| holder.classList.add("left-move") | |||
| const hline = document.createElement("div") | |||
| hline.classList.add("log-separator") | |||
| log.insertBefore(hline, before) | |||
| log.insertBefore(holder, hline) | |||
| log.scrollTo({ top: 0, left: 0 }) | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| @@ -56,21 +87,25 @@ export default class Explore extends Vue { | |||
| <style scoped> | |||
| .explore-layout { | |||
| flex: 10; | |||
| position: relative; | |||
| display: grid; | |||
| grid-template-areas: "log worldinfo" | |||
| "log statblock" | |||
| "log info " | |||
| "log actions " | |||
| "nav actions "; | |||
| grid-template-rows: 0.5fr 2fr 1fr 1fr; | |||
| "log choices " | |||
| "nav choices "; | |||
| grid-template-rows: 0.5fr fit-content(250pt) 2fr 1fr 1fr; | |||
| grid-template-columns: 2fr 1fr; | |||
| width: 100%; | |||
| height: 100%; | |||
| overflow: hidden; | |||
| } | |||
| .explore-log { | |||
| grid-area: log; | |||
| background: #222; | |||
| overflow-y: scroll; | |||
| } | |||
| .explore-worldinfo { | |||
| @@ -82,6 +117,7 @@ export default class Explore extends Vue { | |||
| .worldinfo-time { | |||
| font-size: 125%; | |||
| } | |||
| .explore-info { | |||
| grid-area: info; | |||
| background: #333; | |||
| @@ -99,6 +135,7 @@ export default class Explore extends Vue { | |||
| .location-desc { | |||
| font-size: 150%; | |||
| } | |||
| .explore-nav { | |||
| grid-area: nav; | |||
| background: #444; | |||
| @@ -112,8 +149,17 @@ export default class Explore extends Vue { | |||
| grid-template-columns: 1fr 1fr 1fr; | |||
| } | |||
| .explore-actions { | |||
| grid-area: actions; | |||
| .nav-filler { | |||
| grid-area: var(--nav-direction); | |||
| background: #222; | |||
| margin: 10px; | |||
| } | |||
| .explore-choices { | |||
| grid-area: choices; | |||
| background: #555; | |||
| display: flex; | |||
| flex-direction: column; | |||
| overflow-y: scroll; | |||
| } | |||
| </style> | |||
| @@ -7,9 +7,6 @@ | |||
| <div> | |||
| <a href="https://discord.gg/2DmtgEM"><i class="fab fa-discord" /></a> | |||
| </div> | |||
| <div> | |||
| <button @click="select(encounter)" v-for="(encounter, index) in encounters" :key="'encounter-'+index">{{encounter.desc.name.toString()}}</button> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| @@ -1,6 +1,6 @@ | |||
| <template> | |||
| <button class="nav-button"> | |||
| {{location.connections[direction].dst.name}} | |||
| {{ location.connections[direction].dst.name }} | |||
| <div class="tooltip-template"> | |||
| <div class="tooltip-title">{{ location.connections[direction].name }}</div> | |||
| <div class="tooltip-body">{{ location.connections[direction].desc }}</div> | |||
| @@ -2,6 +2,7 @@ import { TextLike } from './language' | |||
| import { Entity } from './entity' | |||
| import { Creature } from './creature' | |||
| import moment, { Moment, Duration } from 'moment' | |||
| import { LogEntry, LogLine } from './interface' | |||
| export enum Direction { | |||
| Northwest = "Northwest", | |||
| @@ -27,14 +28,34 @@ export function reverse (dir: Direction): Direction { | |||
| } | |||
| } | |||
| export class Choice { | |||
| constructor (public name: TextLike, public desc: TextLike, public execute: (world: World, executor: Creature) => LogEntry) { | |||
| } | |||
| visible (world: World): boolean { | |||
| return true | |||
| } | |||
| accessible (world: World): boolean { | |||
| return true | |||
| } | |||
| } | |||
| export class Connection { | |||
| constructor (public src: Place, public dst: Place, public name: TextLike = "Travel", public desc: TextLike = "Go there lol") { | |||
| } | |||
| travel (traveler: Creature, world: World) { | |||
| world.advance(moment.duration(5, "minutes")) | |||
| traveler.location = this.dst | |||
| } | |||
| } | |||
| export class Place { | |||
| connections: {[key in Direction]?: Connection} = {} | |||
| choices: Choice[] = [] | |||
| constructor (public name: TextLike, public desc: TextLike) { | |||