| @@ -1,8 +1,8 @@ | |||||
| <template> | <template> | ||||
| <div id="app"> | <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> | </div> | ||||
| </template> | </template> | ||||
| @@ -15,8 +15,10 @@ import * as Creatures from '@/game/creatures' | |||||
| import * as Items from '@/game/items' | import * as Items from '@/game/items' | ||||
| import { Creature } from '@/game/creature' | import { Creature } from '@/game/creature' | ||||
| import { ProperNoun, TheyPronouns, FemalePronouns, MalePronouns, ImproperNoun, POV } from '@/game/language' | 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({ | @Component({ | ||||
| components: { | components: { | ||||
| @@ -26,7 +28,8 @@ import { Encounter } from './game/combat' | |||||
| return { | return { | ||||
| encounter: null, | encounter: null, | ||||
| encounters: null, | encounters: null, | ||||
| world: null | |||||
| world: null, | |||||
| mode: 'explore' | |||||
| } | } | ||||
| } | } | ||||
| }) | }) | ||||
| @@ -35,9 +38,10 @@ export default class App extends Vue { | |||||
| super() | super() | ||||
| } | } | ||||
| @Emit('selectEncounter') | |||||
| selectEncounter (encounter: Encounter) { | |||||
| @Emit('startFight') | |||||
| startFight (encounter: Encounter) { | |||||
| this.$data.encounter = encounter | this.$data.encounter = encounter | ||||
| this.$data.mode = 'combat' | |||||
| } | } | ||||
| created () { | created () { | ||||
| @@ -51,16 +55,29 @@ export default class App extends Vue { | |||||
| this.$data.encounter = this.$data.encounters[0] | 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() | const player = new Creatures.Wolf() | ||||
| player.perspective = POV.Second | player.perspective = POV.Second | ||||
| player.location = foo | |||||
| player.side = Side.Heroes | |||||
| player.location = home | |||||
| this.$data.world = new World(player) | 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> | ||||
| <div class="explore-worldinfo"> | <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> | </div> | ||||
| <Statblock :subject="world.player" :initiative="0" /> | |||||
| <div class="explore-info"> | <div class="explore-info"> | ||||
| <h2 class="location-name">{{ location.name }}</h2> | <h2 class="location-name">{{ location.name }}</h2> | ||||
| <p class="location-desc">{{ location.desc }}</p> | <p class="location-desc">{{ location.desc }}</p> | ||||
| </div> | </div> | ||||
| <div class="explore-nav"> | <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> | ||||
| <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> | ||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -25,10 +27,18 @@ | |||||
| import { Component, Prop, Vue } from 'vue-property-decorator' | import { Component, Prop, Vue } from 'vue-property-decorator' | ||||
| import { Direction, World, Place } from '@/game/world' | import { Direction, World, Place } from '@/game/world' | ||||
| import NavButton from './NavButton.vue' | import NavButton from './NavButton.vue' | ||||
| import ChoiceButton from './ChoiceButton.vue' | |||||
| import Statblock from './Statblock.vue' | |||||
| import { LogEntry } from '@/game/interface' | |||||
| @Component({ | @Component({ | ||||
| components: { | components: { | ||||
| NavButton | |||||
| NavButton, ChoiceButton, Statblock | |||||
| }, | |||||
| data () { | |||||
| return { | |||||
| directions: Direction | |||||
| } | |||||
| } | } | ||||
| }) | }) | ||||
| @@ -49,6 +59,27 @@ export default class Explore extends Vue { | |||||
| '--nav-direction': dir | '--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> | </script> | ||||
| @@ -56,21 +87,25 @@ export default class Explore extends Vue { | |||||
| <style scoped> | <style scoped> | ||||
| .explore-layout { | .explore-layout { | ||||
| flex: 10; | |||||
| position: relative; | position: relative; | ||||
| display: grid; | display: grid; | ||||
| grid-template-areas: "log worldinfo" | grid-template-areas: "log worldinfo" | ||||
| "log statblock" | |||||
| "log info " | "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; | grid-template-columns: 2fr 1fr; | ||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| overflow: hidden; | |||||
| } | } | ||||
| .explore-log { | .explore-log { | ||||
| grid-area: log; | grid-area: log; | ||||
| background: #222; | background: #222; | ||||
| overflow-y: scroll; | |||||
| } | } | ||||
| .explore-worldinfo { | .explore-worldinfo { | ||||
| @@ -82,6 +117,7 @@ export default class Explore extends Vue { | |||||
| .worldinfo-time { | .worldinfo-time { | ||||
| font-size: 125%; | font-size: 125%; | ||||
| } | } | ||||
| .explore-info { | .explore-info { | ||||
| grid-area: info; | grid-area: info; | ||||
| background: #333; | background: #333; | ||||
| @@ -99,6 +135,7 @@ export default class Explore extends Vue { | |||||
| .location-desc { | .location-desc { | ||||
| font-size: 150%; | font-size: 150%; | ||||
| } | } | ||||
| .explore-nav { | .explore-nav { | ||||
| grid-area: nav; | grid-area: nav; | ||||
| background: #444; | background: #444; | ||||
| @@ -112,8 +149,17 @@ export default class Explore extends Vue { | |||||
| grid-template-columns: 1fr 1fr 1fr; | 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; | background: #555; | ||||
| display: flex; | |||||
| flex-direction: column; | |||||
| overflow-y: scroll; | |||||
| } | } | ||||
| </style> | </style> | ||||
| @@ -7,9 +7,6 @@ | |||||
| <div> | <div> | ||||
| <a href="https://discord.gg/2DmtgEM"><i class="fab fa-discord" /></a> | <a href="https://discord.gg/2DmtgEM"><i class="fab fa-discord" /></a> | ||||
| </div> | </div> | ||||
| <div> | |||||
| <button @click="select(encounter)" v-for="(encounter, index) in encounters" :key="'encounter-'+index">{{encounter.desc.name.toString()}}</button> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -1,6 +1,6 @@ | |||||
| <template> | <template> | ||||
| <button class="nav-button"> | <button class="nav-button"> | ||||
| {{location.connections[direction].dst.name}} | |||||
| {{ location.connections[direction].dst.name }} | |||||
| <div class="tooltip-template"> | <div class="tooltip-template"> | ||||
| <div class="tooltip-title">{{ location.connections[direction].name }}</div> | <div class="tooltip-title">{{ location.connections[direction].name }}</div> | ||||
| <div class="tooltip-body">{{ location.connections[direction].desc }}</div> | <div class="tooltip-body">{{ location.connections[direction].desc }}</div> | ||||
| @@ -2,6 +2,7 @@ import { TextLike } from './language' | |||||
| import { Entity } from './entity' | import { Entity } from './entity' | ||||
| import { Creature } from './creature' | import { Creature } from './creature' | ||||
| import moment, { Moment, Duration } from 'moment' | import moment, { Moment, Duration } from 'moment' | ||||
| import { LogEntry, LogLine } from './interface' | |||||
| export enum Direction { | export enum Direction { | ||||
| Northwest = "Northwest", | 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 { | export class Connection { | ||||
| constructor (public src: Place, public dst: Place, public name: TextLike = "Travel", public desc: TextLike = "Go there lol") { | 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 { | export class Place { | ||||
| connections: {[key in Direction]?: Connection} = {} | connections: {[key in Direction]?: Connection} = {} | ||||
| choices: Choice[] = [] | |||||
| constructor (public name: TextLike, public desc: TextLike) { | constructor (public name: TextLike, public desc: TextLike) { | ||||