define how to roll and how tables work

add some test tables
This commit is contained in:
2019-11-04 00:42:51 -07:00
parent 8e9342d624
commit 9bf7a2788a
15 changed files with 666 additions and 14 deletions

16
ideas.md Normal file
View File

@ -0,0 +1,16 @@
## Search
Full text search for quick lookup. Search both table names and table contents.
## Modules
Create modules for each section of the book for example see Build and NPC below.
## Build an NPC
It would be nice to enable a DM to create NPCs on the fly with one click and be able to change a trait using the tables either randomly or picking.
## Additional features
The ability to define your own random tables.

View File

@ -10,9 +10,12 @@
"dependencies": {
"core-js": "^3.3.2",
"register-service-worker": "^1.6.2",
"rxjs": "^6.5.3",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-property-decorator": "^8.3.0"
"vue-property-decorator": "^8.3.0",
"vue-router": "^3.1.3",
"vue-rx": "^6.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
@ -36,7 +39,10 @@
"eslint:recommended",
"@vue/typescript"
],
"rules": {},
"rules": {
"no-console": "off",
"no-debugger": "off"
},
"parserOptions": {
"parser": "@typescript-eslint/parser"
}

View File

@ -3,8 +3,12 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- <meta name="viewport" content="width=device-width,initial-scale=1.0"> -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script src="https://kit.fontawesome.com/831b98aacd.js"></script>
<title>dungeon-master</title>
</head>
<body>
@ -13,5 +17,8 @@
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,17 +1,14 @@
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
<router-view/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
@Component({
components: {
HelloWorld,
},
})
export default class App extends Vue {}

View File

@ -0,0 +1,41 @@
<template>
<div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
import { d } from '../services/dice';
@Component({
components: {
},
})
export default class TestTable extends Vue {
private rollValue: number = 0;
private diceSize: number[] = [
4,
6,
8,
10,
100,
12,
20,
];
private messages: string[] = [];
private roll(faces: number) {
this.rollValue = d(faces);
this.messages.push(`Rolled a d${faces} and got ${this.rollValue}`);
}
private clearMessages() {
this.messages = [];
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,42 @@
<template>
<div>
<div>
<button class="primary" aria-label="Clear" @click="clearMessages">
<span aria-hidden="true">Clear</span>
</button>
</div>
<div>
<div v-for="message, i in messages" :key="i">
<span>{{message}}</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
import { d } from '../services/dice';
import { messageService } from '../services/messageService';
@Component({
components: {
},
subscriptions() {
return {
messages: messageService.messages$
};
}
})
export default class Messages extends Vue {
private messages: string[] = [];
private clearMessages() {
messageService.clearMessages();
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,9 +1,13 @@
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import Vue from 'vue';
import App from './App.vue';
import VueRx from 'vue-rx';
import router from './router';
import './registerServiceWorker';
Vue.config.productionTip = false
Vue.use(VueRx);
Vue.config.productionTip = false;
new Vue({
let vm = new Vue({
router,
render: h => h(App),
}).$mount('#app')
}).$mount('#app');

17
src/router.ts Normal file
View File

@ -0,0 +1,17 @@
import Vue from 'vue';
import Router from 'vue-router';
import TestTable from './views/test-table.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: TestTable
}
]
});

8
src/services/dice.ts Normal file
View File

@ -0,0 +1,8 @@
function d(faces: number) {
return Math.floor(Math.random() * faces) + 1;
}
export {
d
}

View File

@ -0,0 +1,30 @@
import { Observable, Subject } from 'rxjs';
class MessageService {
private messageStream = new Subject<string[]>();
private messages: string[] = [];
constructor() {
this.messageStream.next([]);
}
get messages$(): Observable<string[]> {
return this.messageStream.asObservable();
}
public addMessage(newMessage: string) {
this.messages.push(newMessage);
this.messageStream.next(this.messages);
}
public clearMessages() {
this.messages = [];
this.messageStream.next([]);
}
}
const messageService: MessageService = new MessageService();
export {
messageService
}

292
src/services/tableData.ts Normal file
View File

@ -0,0 +1,292 @@
import { TableConfig } from './tableService';
let tableData: TableConfig[] = [
{
name: 'Forms of Government',
dice: [100],
possibleResults: [
{
keys: [1, 8],
value: 'Autocracy'
},
{
keys: [9, 13],
value: 'Bureaucracy'
},
{
keys: [14, 19],
value: 'Confederacy'
},
{
keys: [20, 22],
value: 'Democracy'
},
{
keys: [23, 27],
value: 'Dictatorship'
},
{
keys: [28, 42],
value: 'Feudalism'
},
{
keys: [43, 44],
value: 'Gerontocracy'
},
{
keys: [45, 53],
value: 'Hierarchy'
},
{
keys: [54, 56],
value: 'Magocracy'
},
{
keys: [57, 58],
value: 'Matriarchy'
},
{
keys: [59, 64],
value: 'Militocracy'
},
{
keys: [65, 74],
value: 'Monarchy'
},
{
keys: [75, 78],
value: 'Oligarchy'
},
{
keys: [79, 80],
value: 'Patriarchy'
},
{
keys: [81, 83],
value: 'Meritocracy'
},
{
keys: [84, 85],
value: 'Plutocracy'
},
{
keys: [86, 92],
value: 'Republic'
},
{
keys: [93, 94],
value: 'Satrapy'
},
{
keys: [95],
value: 'Kleptocracy'
},
{
keys: [96, 100],
value: 'Theocracy'
},
]
},
{
name: 'World-Shaking Events',
dice: [10],
possibleResults: [
{
keys: [1],
value: 'Rise of a leader or an era'
},
{
keys: [2],
value: 'Fall of a leader or an era'
},
{
keys: [3],
value: 'Cataclysmic disaster'
},
{
keys: [4],
value: 'Assault or invasion'
},
{
keys: [5],
value: 'Rebellion, revolution, overthrow'
},
{
keys: [6],
value: 'Extinction or depletion'
},
{
keys: [7],
value: 'New organization'
},
{
keys: [8],
value: 'Discovery, expansion, invention'
},
{
keys: [9],
value: 'Prediction, omen, prophecy'
},
{
keys: [10],
value: 'Myth and legend'
}
]
},
{
name: 'Leader Types',
dice: [6],
possibleResults: [
{
keys: [1],
value: 'Political'
},
{
keys: [2],
value: 'Religious'
},
{
keys: [3],
value: 'Military'
},
{
keys: [4],
value: 'Crime/underworld'
},
{
keys: [5],
value: 'Art/culture'
},
{
keys: [6],
value: 'Philosophy/learning/magic'
}
]
},
{
name: 'Cataclysmic Disasters',
dice: [10],
possibleResults: [
{
keys: [1],
value: 'Earthquake'
},
{
keys: [2],
value: 'Famine/drought'
},
{
keys: [3],
value: 'Fire'
},
{
keys: [4],
value: 'Flood'
},
{
keys: [5],
value: 'Plague/disease'
},
{
keys: [6],
value: 'Rain of fire (meteoric impact)'
},
{
keys: [7],
value: 'Storm (hurrican, tornado, tsunami)'
},
{
keys: [8],
value: 'Volcanic eruption'
},
{
keys: [9],
value: 'Magic gone awry or a planar warp'
},
{
keys: [10],
value: 'Divine judgment'
}
]
},
{
name: 'Invading Forces',
dice: [8],
possibleResults: [
{
keys: [1],
value: 'A criminal enterprise'
},
{
keys: [2],
value: 'Monsters or a unique monster'
},
{
keys: [3],
value: 'A planar threat'
},
{
keys: [4],
value: 'A past adversary reawakened, reborn, or resurgent'
},
{
keys: [5],
value: 'A splinter faction'
},
{
keys: [6],
value: 'A savage tribe'
},
{
keys: [7],
value: 'A secret society'
},
{
keys: [8],
value: 'A traitorous ally'
}
]
},
{
name: 'Extinction or Depletion',
dice: [8],
possibleResults: [
{
keys: [1],
value: 'A kind of animal (insect, bird, fish, livestock)'
},
{
keys: [2],
value: 'Habitable land'
},
{
keys: [3],
value: 'Magic or magic-users (all magic, or specific kinds or schools of magic)'
},
{
keys: [4],
value: 'A mineral resource (gems, metals, ores)'
},
{
keys: [5],
value: 'A type of monster (unicorn, manticore, dragon)'
},
{
keys: [6],
value: 'A people (family line, clan, culture, race)'
},
{
keys: [7],
value: 'A kind of plant (crop, tree, herb, forest)'
},
{
keys: [8],
value: 'A waterway (river, lake, ocean)'
}
]
},
];
export {
tableData
}

View File

@ -0,0 +1,89 @@
import { d } from './dice';
class TableService {
}
/*
From the DM Guide Book
Example table p.116 in DM Guide
d12 + d8 | Encounter
2 | Sunken ship covered in barnacles (25 percent chance that the ship contains treasure; roll randomly on the treasure tables in chapter 7)
3 | Sunken ship with reef sharks
4 | Bed of giant oysters
5 | Underwater steam vent (25 percent chance that the vent is a portal to the Elemental Plane of Fire)
...
20 | Sunken treasure chest (25 percent chance that it contains something of value; roll treasure randomly using the tables in chapter 7)
A good index of the tables: https://slyflourish.com/random_tables_of_the_dmg.html
Other good tables
https://www.dicegeeks.com/category/random-rpg-tables/
*/
class RandomTable {
public name: string;
private dice: number[];
private indexedResults: TableResult[] = [];
constructor(tableConfig: TableConfig) {
this.name = tableConfig.name;
this.dice = tableConfig.dice;
this.constructIndex(tableConfig.possibleResults);
}
public roll(input?: number) {
let testRoll: number = this.getRoll();
if (!this.isValidInput(testRoll)) {
console.log(testRoll);
debugger;
}
if (!input || !this.isValidInput(input)) {
input = this.getRoll();
}
return this.indexedResults[input - 1];
}
private getRoll(): number {
let answer: number = 0;
for (let i in this.dice) {
answer = answer + d(this.dice[i]);
}
return answer;
}
private isValidInput(input: number) {
return typeof this.indexedResults[input - 1] !== 'undefined';
}
private constructIndex(tableResults: TableResult[]) {
for (let resultIndex in tableResults) {
let keys: number[] = tableResults[resultIndex].keys;
for (let r = Math.min(...keys); r <= Math.max(...keys); r++) {
this.indexedResults.push(tableResults[resultIndex]);
}
}
}
}
interface TableResult {
keys: number[];
value: string;
nextTable?: string;
description?: string;
}
interface TableConfig {
name: string;
dice: number[];
possibleResults: TableResult[];
}
const tableService: TableService = new TableService();
export {
RandomTable,
TableResult,
TableConfig,
tableService
}

2
src/services/test.ts Normal file
View File

@ -0,0 +1,2 @@
import { tableData } from './tableData';
import { tableService, RandomTable, TableResult, TableConfig } from './tableService';

91
src/views/test-table.vue Normal file
View File

@ -0,0 +1,91 @@
<template>
<div class="test-table">
{{rollValue}} <br />
<br />
<div v-for="dice, i in diceSize" :key="i">
<button class="primary" :aria-label="'Roll D' + dice" @click="roll(dice)">
<span>d{{dice}}</span>
</button>
</div>
<br />
<div>
<span>{{tableResult}}</span><br />
<button class="btn-primary" :aria-label="'Get table value'" @click="roleTable">
<span>Test Table</span>
</button>
</div>
<!-- <div>
<button class="btn-primary" :aria-label="'Get table value'" @click="testTable">
<span>Run Table Test</span>
</button>
</div> -->
<div>
<span>{{curTable.name}}</span>
</div>
<messages></messages>
</div>
</template>
<script lang="ts">
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
import { messageService } from '../services/messageService';
import Messages from '../components/messages.vue';
import { d } from '../services/dice';
import { tableData } from '../services/tableData';
import { tableService, RandomTable, TableResult, TableConfig } from '../services/tableService';
@Component({
components: {
'messages': Messages
},
})
export default class TestTable extends Vue {
private rollValue: number = 0;
private diceSize: number[] = [
4,
6,
8,
10,
100,
12,
20,
];
private curTable: RandomTable = new RandomTable(tableData[0]);
private tableResult: string = '';
private roll(faces: number) {
this.rollValue = d(faces);
messageService.addMessage(`Rolled a d${faces} and got ${this.rollValue}`);
}
private clearMessages() {
messageService.clearMessages();
}
private roleTable() {
let tableResponse: TableResult = this.curTable.roll();
this.tableResult = tableResponse.value;
messageService.addMessage(`${this.curTable.name} - ${this.tableResult}`)
}
private testTable() {
for (let i = 1; i <= 100; i++) {
let tableResponse: TableResult = this.curTable.roll(i);
messageService.addMessage(`${i} = ${tableResponse.value}`);
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@ -6985,7 +6985,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^6.4.0:
rxjs@^6.4.0, rxjs@^6.5.3:
version "6.5.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==
@ -8208,6 +8208,16 @@ vue-property-decorator@^8.3.0:
dependencies:
vue-class-component "^7.1.0"
vue-router@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.3.tgz#e6b14fabc0c0ee9fda0e2cbbda74b350e28e412b"
integrity sha512-8iSa4mGNXBjyuSZFCCO4fiKfvzqk+mhL0lnKuGcQtO1eoj8nq3CmbEG8FwK5QqoqwDgsjsf1GDuisDX4cdb/aQ==
vue-rx@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/vue-rx/-/vue-rx-6.2.0.tgz#c3b86462b252626edd16417385bb9054a3a23acb"
integrity sha512-tpKUcqS5IUYS+HsdbR5TlE5LL9PK4zwlplEtmMeydnbqaUTa9ciLMplJXAtFSiQw1vuURoyEJmCXqMxaVEIloQ==
vue-style-loader@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"