make more changes to rollers

This commit is contained in:
RussellChamp committed 2021-07-25 01:52:33 -04:00
1 parent 08aa49d727
commit 7392647a3d
10 files changed
+462 -130

No files matched your search

+2 -1
View File
@@ -4,5 +4,6 @@
140
],
"editor.minimap.maxColumn": 140,
"svelte.plugin.svelte.format.config.printWidth": 140
"svelte.plugin.svelte.format.config.printWidth": 140,
"editor.tabSize": 2
}
+2 -104
View File
@@ -1,105 +1,3 @@
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
Interactive roller for The Perilous Wilds overworld adventure supplement for Dungeon World
---
# svelte app
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Using TypeScript
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
```bash
node scripts/setupTypeScript.js
```
Or remove the script via:
```bash
rm scripts/setupTypeScript.js
```
## Deploying to the web
### With [Vercel](https://vercel.com)
Install `vercel` if you haven't already:
```bash
npm install -g vercel
```
Then, from within your project folder:
```bash
cd public
vercel deploy --name my-project
```
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```
With content from The Perilous Wilds by Jason Lutes Distributed under the Creative Commons Attribution-ShareAlike 3.0 Unported license
+6
View File
@@ -2,6 +2,8 @@
import KingdomRoller from "./rollers/KingdomRoller.svelte";
import RegionRoller from "./rollers/RegionRoller.svelte";
import PlaceRoller from "./rollers/PlaceRoller.svelte";
import FollowerRoller from "./rollers/FollowerRoller.svelte";
import Footer from "./componenets/Footer.svelte";
</script>
<main>
@@ -11,6 +13,10 @@
<RegionRoller />
<hr />
<PlaceRoller />
<hr />
<FollowerRoller />
<hr />
<Footer />
</main>
<style>
+12
View File
@@ -0,0 +1,12 @@
<script lang="ts">
</script>
<main>
<footer>
<div>With content from The Perilous Wilds by Jason Lutes</div>
<div>Distributed under the Creative Commons Attribution-ShareAlike 3.0 Unported license</div>
</footer>
</main>
<style>
</style>
+125
View File
@@ -0,0 +1,125 @@
<script lang="ts">
import { Button, Card, CardHeader, CardTitle, CardBody, CardSubtitle, CardText, CardFooter, Container, Col, Row } from "sveltestrap";
import * as _ from "lodash";
import { format as formatDate } from "date-fns";
import { rollFollower, Follower } from "./followers";
export let rollResults: { value: Follower; timestamp: string }[] = [];
function roll(event: Event) {
let follower = rollFollower();
rollResults = [{ value: follower, timestamp: formatDate(new Date(), "P kk:mm:ss") }, ...rollResults];
}
</script>
<main>
<h2>Random Follower</h2>
<span class="btn">
<Button color="primary" on:click={roll}>Roll Follower</Button>
</span>
{#each rollResults as result}
<Card class="mb-3">
<CardHeader>
<CardTitle>{result.value.name} ({result.value.gender})</CardTitle>
</CardHeader>
<CardBody>
<CardSubtitle>
From <em>{result.value.homeSteading.homesteadName}</em> in <em>{result.value.homeSteading.kingdomName}</em>
</CardSubtitle>
<CardText>
<Container>
<Row>
<strong>Quality:</strong>
<p>
They are <em>{result.value.quality.description}</em> (Quality: {result.value.quality.value}). They
<em>{result.value.background.description}</em>.
</p>
</Row>
<Row>
<strong>Tags:</strong>
<ul>
{#each result.value.tags as tag}
<li>{tag.name}: {tag.description}</li>
{/each}
</ul>
</Row>
<Row>
<strong>Moves:</strong>
<ul>
{#each result.value.moves as move}
<li>{move.description}</li>
{/each}
</ul>
</Row>
<Row>
<strong>Loyalty:</strong>
<p>
They have a loyalty score of {result.value.loyalty.value}{#if !!result.value.loyalty.description}
and <em>{result.value.loyalty.description}</em>{/if}.
</p>
</Row>
<Row>
<strong>Instinct:</strong>
<p>
When left to their own devices, they will <em>{result.value.instinct.description}</em>.
</p>
</Row>
<Row>
<strong>Cost:</strong>
<p>
They are motivated by <em>{result.value.cost.name}</em>. They will want <em>{result.value.cost.description}</em>
</p>
</Row>
<Row>
<strong>Hit Points:</strong>
<p>
They are <em>{result.value.hitPoints.description}</em> and have {result.value.hitPoints.value} HP.
</p>
</Row>
<Row>
<strong>Armor & Shield:</strong>
<p>
They are equipped with <em>{result.value.armor.description}</em> ({result.value.armor.value} armor).
</p>
</Row>
<Row>
<strong>Damage:</strong>
<p>
In a fight, they <em>{result.value.damage.description}</em> dealing <em>d{result.value.damage.value}</em> damage.
</p>
</Row>
<Row>
<strong>Load & Gear:</strong>
<ul>
{#each result.value.loadAndGear.gear as gear}
<li>{gear}</li>
{/each}
</ul>
</Row>
</Container>
</CardText>
</CardBody>
<CardFooter>{result.timestamp}</CardFooter>
</Card>
{/each}
</main>
<style>
main {
text-align: left;
}
h2 {
text-align: center;
}
p {
margin-left: 1em;
}
ul {
margin-left: 2em;
}
.btn {
text-align: center;
}
</style>
+21 -20
View File
@@ -1,45 +1,46 @@
<script lang="ts">
import { kingdoms } from "./data";
import * as _ from "lodash";
import { format as formatDate } from "date-fns";
import { Button, Icon } from "sveltestrap";
export let rollResults: { value: string; timestamp: string }[] = [];
import { kingdoms } from "./data";
export let rollResults: { value: string; icon: string; timestamp: string }[] = [];
let kingdom = "arpad";
let category = "male";
let categories: string[];
let values: string[];
function addResult(result: string) {
rollResults = [{ value: result, timestamp: formatDate(new Date(), "P kk:mm:ss") }, ...rollResults];
function addResult(value: string, category: string) {
let icon = { male: "person-fill", female: "person", steading: "house-door-fill", mount: "bicycle" }[category] || "";
rollResults = [{ value, icon, timestamp: formatDate(new Date(), "P kk:mm:ss") }, ...rollResults];
}
function rollName(event: Event) {
addResult(_.sample(values));
function rollName(category: string) {
let values = kingdoms[kingdom][category];
addResult(_.sample(values), category);
}
$: categories = kingdoms[kingdom].categories;
$: values = kingdoms[kingdom][category];
</script>
<main>
<h1>
<h2>
<select bind:value={kingdom}>
{#each Object.keys(kingdoms) as k}
<option value={k}>{kingdoms[k].description}</option>
{/each}
</select>
</h1>
<h2>{kingdoms[kingdom].subtitle}</h2>
<hr />
</h2>
<h3>{kingdoms[kingdom].subtitle}</h3>
{#each categories as category}
<span class="btn">
<Button color="primary" on:click={() => rollName(category)}>Roll {_.capitalize(category)}</Button>
</span>
{/each}
<select bind:value={category}>
{#each categories as c}
<option value={c}>{_.capitalize(c)}</option>
{/each}
</select>
<button type="button" class="btn btn-primary" on:click={rollName}>Roll {_.capitalize(category)}</button>
{#each rollResults as result}
<h3>{result.timestamp}: {result.value}</h3>
<h4>{result.timestamp}: <Icon name={result.icon} /> {result.value}</h4>
{/each}
</main>
+2 -2
View File
@@ -18,11 +18,11 @@
</script>
<main>
<h1>Random Places</h1>
<h2>Random Places</h2>
<button type="button" class="btn btn-primary" on:click={roll}>Roll Region</button>
{#each rollResults as result}
<h3>{result.timestamp}: {result.value}</h3>
<h4>{result.timestamp}: {result.value}</h4>
{/each}
</main>
+2 -2
View File
@@ -18,11 +18,11 @@
</script>
<main>
<h1>Random Regions</h1>
<h2>Random Regions</h2>
<button type="button" class="btn btn-primary" on:click={roll}>Roll Region</button>
{#each rollResults as result}
<h3>{result.timestamp}: {result.value}</h3>
<h4>{result.timestamp}: {result.value}</h4>
{/each}
</main>
+9 -1
View File
@@ -1,4 +1,12 @@
export const kingdoms = {
export class Kingdom {
constructor(public description: string = "", public subtitle: string = "") { };
categories: string[];
male: string[];
female: string[];
steading: string[];
mount: string[];
}
export const kingdoms: { [kingdom: string]: Kingdom } = {
arpad: {
description: 'The Kingdom of Arpad',
subtitle: 'Based loosely on Hungarian',
+281
View File
@@ -0,0 +1,281 @@
import { random, sample, sampleSize, times } from 'lodash';
import { Kingdom, kingdoms } from './data';
export class Follower {
constructor(public name: string = "") { };
gender: "male" | "female";
quality: Quality;
background: Background;
tags: Tag[];
moves: Move[];
loyalty: Loyalty;
instinct: Instinct;
cost: Cost;
hitPoints: HitPoints;
armor: Armor;
damage: Damage;
loadAndGear: LoadAndGear;
homeSteading: Homesteading;
}
class Quality {
constructor(public description: string = "", public value: number = 0, public bonusTags: number = 0) { };
}
class Background {
constructor(public description: string = "", public bonusTags: number = 0, public tags: Tag[] = [], public bonusQuality: number = 0) { };
}
class Tag {
constructor(public name: string = "", public description: string = "") { };
}
class Move {
constructor(public description: string = "") { };
}
class Loyalty {
constructor(public description: string = "", public value: number = 1) { };
}
class Instinct {
constructor(public description: string = "") { };
}
class Cost {
constructor(public name: string = "", public description: string = "") { };
}
class HitPoints {
constructor(public description: string = "", public value: number = 0) { };
}
class Armor {
constructor(public description: string = "", public value: number = 0) { };
}
class Damage {
constructor(public description: string = "", public value: number = 0) { };
}
class LoadAndGear {
constructor(public limit: number = 2, public gear: string[] = []) { };
}
class Homesteading {
constructor(public kingdomName: string = "", public homesteadName: string = "") { };
}
export function rollFollower(): Follower {
let follower = new Follower();
let kingdom = sample(Object.keys(kingdoms));
follower.gender = sample(["male", "female"]);
follower.name = sample(kingdoms[kingdom][follower.gender]);
follower.quality = rollQuality();
follower.background = rollBackground();
follower.quality.value += follower.background.bonusQuality; // this is probably not the best way to do this
follower.tags = rollTags(follower.background.tags, follower.quality.bonusTags + follower.background.bonusTags);
follower.moves = rollMoves();
follower.loyalty = rollLoyalty();
follower.instinct = rollInstinct();
follower.cost = rollCost();
follower.hitPoints = rollHitPoints();
follower.armor = rollArmor();
follower.damage = rollDamage();
follower.loadAndGear = rollLoadAndGear();
follower.homeSteading = rollHomesteading(kingdoms[kingdom]);
return follower;
}
function rollQuality(): Quality {
let roll = random(1, 12);
if (roll <= 3) {
return new Quality("a liability", -1, 0);
} else if (roll <= 9) {
return new Quality("reasonably competent", 0, 1);
} else if (roll <= 11) {
return new Quality("fully capable", 1, 2);
} else if (roll <= 12) {
return new Quality("an exception individual", 2, 4);
}
return new Quality();
}
function rollBackground(doingBonus: boolean = false): Background {
let roll = random(1, doingBonus ? 11 : 12);
if (roll <= 2) {
return new Background("have lived a life of servitude and oppression", 0, [new Tag("Meek")]);
} else if (roll <= 3) {
return new Background("are past their prime", 0, [new Tag("+1 Wise")], -1); // this is probably incorrect. revisit later
} else if (roll <= 5) {
return new Background("have lived a life of danger", 2);
} else if (roll <= 9) {
return new Background("are otherwise unremarkable");
} else if (roll <= 10) {
return new Background("have lived a life of privilige", 1);
} else if (roll <= 11) {
return new Background("are a specialist", -2);
} else if (roll <= 12) {
let bg1 = rollBackground(true);
let bg2 = rollBackground(true);
// this could result in the same bg being selected twice but that's ok for now
return new Background(`${bg1.description} and ${bg1.description}`, bg1.bonusTags + bg2.bonusTags, [...bg1.tags, ...bg2.tags], bg1.bonusQuality + bg2.bonusQuality);
}
}
const followerTags: Tag[] = [
{ name: "___-wise", description: "They know all about and can roll +Quality to Spout Lore about it on your behalf. Examples: Woods-wise, Sea-wise, Herb-wise" },
{ name: "Archer", description: "They use ranged attacks (not necessarily bows) effectively and can roll +Quality to Volley on your behalf" },
{ name: "Athletic", description: "They are good at running, jumping, climbing and the like" },
{ name: "Beautiful", description: "They make an impression, drawing admiration and attention" },
{ name: "Cautious", description: "They are careful and methodical; they avoid acting rashly" },
{ name: "Connected (___)", description: "They have friends or contacts in ___, a steading or group" },
{ name: "Cunning", description: "They are clever and observant. They can roll +Quality to Discern Realities on your behalf" },
{ name: "Devious", description: "They are deceitful, treacherous, tricky. Just the sort you want working for you" },
{ name: "Group", description: "They are a team, gang, or mob, with all the strengths and disadvantages that come with greater numbers" },
{ name: "Guide (___)", description: "They knows a particular steading or region and can roll +Quality to Spout Lore, Scout Ahead, or Navigate for the party therein" },
{ name: "Hardy", description: "They are tough, hard-working, and willing to put up with discomfort. They get +2 Load" },
{ name: "Healer", description: "They have the knowledge and ability to provide aid to the injured and the sick. When they tend wounds with bandages or herbs & poultice, add their Quality to damage healed" },
{ name: "Meek", description: "They accept their lowly lot, and will do degrading or unreasonable things without needing to Follow Orders" },
{ name: "Magical", description: "They are magical by nature or training, and sensitive to the workings of magic. Use at least one of their moves to elaborate on their powers" },
{ name: "Organized", description: "They make and follow plans well, and work well in a group. Once they agree to a plan, you do not need to Order Followers as long as the plan doesn't go south" },
{ name: "Self-sufficient", description: "They can live off the land and roll +Quality to Forage" },
{ name: "Stealthy", description: "They can avoid detection and often get the drop on foes" },
{ name: "Warrior", description: "They take +1 to their damage die roll, and you don't need to Order Follower to get them to fight (as long as the opposition is not terrifying or overwhelming)" },
];
function rollTags(backgroundTags: Tag[], tagsCount: number): Tag[] {
if (tagsCount == 0) {
return backgroundTags;
} else {
// return one "_-wise", any tags added from the background, and then additional tags
return [followerTags[0], ...backgroundTags, ...sampleSize(followerTags, tagsCount - 1)];
}
}
function rollMoves(): Move[] {
// This is really up to the player and GM to determine
return [
new Move("1-3 moves determined by the player and GM"),
new Move("1-3 moves determined by the player and GM"),
new Move("1-3 moves determined by the player and GM")
];
}
function rollLoyalty(): Loyalty {
// Up to GM
return new Loyalty();
}
function rollInstinct(): Instinct {
let roll = random(1, 12);
if (roll <= 1) {
return new Instinct("loot, pillage, and burn");
} else if (roll <= 2) {
return new Instinct("hold a grudge and seek payback");
} else if (roll <= 3) {
return new Instinct("question leadership or authority");
} else if (roll <= 5) {
return new Instinct("lord over others");
} else if (roll <= 7) {
return new Instinct("act impulsively");
} else if (roll <= 9) {
return new Instinct("give in to temptation");
} else if (roll <= 11) {
return new Instinct("slack off");
} else if (roll <= 12) {
return new Instinct("avoid danger or punishment");
}
return new Instinct();
}
function rollCost(): Cost {
let roll = random(1, 12);
if (roll <= 1) {
return new Cost("debauchery", "food, drink, gambling, sex, and mischief. The higher their Loyalty, the more extreme the cost.");
} else if (roll <= 2) {
return new Cost("vengeance", "payback against those that have wronged them or their loved ones. The higher their Quality, the more direct and tangible the payback they require.");
} else if (roll <= 5) {
return new Cost("lucre", "coin appropriate to their steading's Prosperity; highly skilled followers might demand more coin.");
} else if (roll <= 7) {
return new Cost("renown", "public recognition for their deeds and skills. The higher their Quality, the larger the audience required.");
} else if (roll <= 9) {
return new Cost("glory", "to defeat a worthy foe in battle. The higher their Quality, the more worthy the foe must be. Possibly limited to certain enemy types (demons, barbarians, etc.).");
} else if (roll <= 10) {
return new Cost("affection", "kind words and deeds, quality time and attention. The more extreme their Loyalty, the more affection they require.");
} else if (roll <= 11) {
return new Cost("knowledge", "secrets, mysteries, and wonders only found by exploring the wider world.");
} else if (roll <= 12) {
return new Cost("good", "suffering alleviated, innocents defended, wrongs righted. The higher their Quality, the greater the good required.");
}
return new Cost();
}
function rollHitPoints(): HitPoints {
let roll = random(1, 12);
if (roll <= 3) {
return new HitPoints(sample(["weak", "frail", "soft"]), 3);
} else if (roll <= 9) {
return new HitPoints("able-bodied", 6);
} else if (roll <= 12) {
return new HitPoints(sample(["tough", "strong", "hard"]), 9);
}
return new HitPoints();
}
const armors: Armor[] = [
{ description: "no armor", value: 0 },
{ description: "hide armor", value: 1 },
{ description: "leather armor", value: 1 },
{ description: "scale mail", value: 2 },
{ description: "chain mail", value: 2 },
{ description: "plate mail", value: 3 },
];
function rollArmor(): Armor {
let armor = sample(armors);
if (random(1, 2) == 2) {
armor.description += " and a shield";
armor.value += 1;
}
return armor;
}
function rollDamage(): Damage {
let roll = random(1, 12);
if (roll <= 4) {
return new Damage("are not very dangerous", 4);
} else if (roll <= 10) {
return new Damage("can defend themselves", 6);
} else if (roll <= 12) {
return new Damage("are a veteran fighter", 8);
}
return new Damage();
}
function rollLoadAndGear(limit: number = 2): LoadAndGear {
// Equip them with whatever is appropriate for the circumstances and prosperity of their home steading
let load = new LoadAndGear(limit, ["Tools of their trade"]);
times(limit, () => load.gear.push("Appropriate equipment for the circumstance and homesteading"));
return load;
}
function rollHomesteading(kingdom: Kingdom): Homesteading {
// I dunno what this should be but the load&gear section mentioned homesteading
return new Homesteading(kingdom.description, sample(kingdom.steading));
}