From 7392647a3d6c75e0ade9c54670f0967113f8deb1 Mon Sep 17 00:00:00 2001 From: Russell Champoux Date: Sun, 25 Jul 2021 01:52:33 -0400 Subject: [PATCH] make more changes to rollers --- .vscode/settings.json | 3 +- README.md | 106 +---------- src/App.svelte | 6 + src/componenets/Footer.svelte | 12 ++ src/rollers/FollowerRoller.svelte | 125 +++++++++++++ src/rollers/KingdomRoller.svelte | 41 ++--- src/rollers/PlaceRoller.svelte | 4 +- src/rollers/RegionRoller.svelte | 4 +- src/rollers/data.ts | 10 +- src/rollers/followers.ts | 281 ++++++++++++++++++++++++++++++ 10 files changed, 462 insertions(+), 130 deletions(-) create mode 100644 src/componenets/Footer.svelte create mode 100644 src/rollers/FollowerRoller.svelte create mode 100644 src/rollers/followers.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index ecab325..374ea18 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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 } \ No newline at end of file diff --git a/README.md b/README.md index 7b1ba83..971126e 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file diff --git a/src/App.svelte b/src/App.svelte index 8e54572..0a48db8 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -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";
@@ -11,6 +13,10 @@
+
+ +
+
diff --git a/src/rollers/FollowerRoller.svelte b/src/rollers/FollowerRoller.svelte new file mode 100644 index 0000000..51a01b6 --- /dev/null +++ b/src/rollers/FollowerRoller.svelte @@ -0,0 +1,125 @@ + + +
+

Random Follower

+ + + + + {#each rollResults as result} + + + {result.value.name} ({result.value.gender}) + + + + From {result.value.homeSteading.homesteadName} in {result.value.homeSteading.kingdomName} + + + + + Quality: +

+ They are {result.value.quality.description} (Quality: {result.value.quality.value}). They + {result.value.background.description}. +

+
+ + Tags: +
    + {#each result.value.tags as tag} +
  • {tag.name}: {tag.description}
  • + {/each} +
+
+ + Moves: +
    + {#each result.value.moves as move} +
  • {move.description}
  • + {/each} +
+
+ + Loyalty: +

+ They have a loyalty score of {result.value.loyalty.value}{#if !!result.value.loyalty.description} + and {result.value.loyalty.description}{/if}. +

+
+ + Instinct: +

+ When left to their own devices, they will {result.value.instinct.description}. +

+
+ + Cost: +

+ They are motivated by {result.value.cost.name}. They will want {result.value.cost.description} +

+
+ + Hit Points: +

+ They are {result.value.hitPoints.description} and have {result.value.hitPoints.value} HP. +

+
+ + Armor & Shield: +

+ They are equipped with {result.value.armor.description} ({result.value.armor.value} armor). +

+
+ + Damage: +

+ In a fight, they {result.value.damage.description} dealing d{result.value.damage.value} damage. +

+
+ + Load & Gear: +
    + {#each result.value.loadAndGear.gear as gear} +
  • {gear}
  • + {/each} +
+
+
+
+
+ {result.timestamp} +
+ {/each} +
+ + diff --git a/src/rollers/KingdomRoller.svelte b/src/rollers/KingdomRoller.svelte index 739bdc7..ca26f02 100644 --- a/src/rollers/KingdomRoller.svelte +++ b/src/rollers/KingdomRoller.svelte @@ -1,45 +1,46 @@
-

+

-

-

{kingdoms[kingdom].subtitle}

-
+ +

{kingdoms[kingdom].subtitle}

+ + {#each categories as category} + + + + {/each} - - {#each rollResults as result} -

{result.timestamp}: {result.value}

+

{result.timestamp}: {result.value}

{/each}
diff --git a/src/rollers/PlaceRoller.svelte b/src/rollers/PlaceRoller.svelte index 9dcb95e..82f2723 100644 --- a/src/rollers/PlaceRoller.svelte +++ b/src/rollers/PlaceRoller.svelte @@ -18,11 +18,11 @@
-

Random Places

+

Random Places

{#each rollResults as result} -

{result.timestamp}: {result.value}

+

{result.timestamp}: {result.value}

{/each}
diff --git a/src/rollers/RegionRoller.svelte b/src/rollers/RegionRoller.svelte index c2ab4a6..c2dab3e 100644 --- a/src/rollers/RegionRoller.svelte +++ b/src/rollers/RegionRoller.svelte @@ -18,11 +18,11 @@
-

Random Regions

+

Random Regions

{#each rollResults as result} -

{result.timestamp}: {result.value}

+

{result.timestamp}: {result.value}

{/each}
diff --git a/src/rollers/data.ts b/src/rollers/data.ts index de63a72..f57b7f9 100644 --- a/src/rollers/data.ts +++ b/src/rollers/data.ts @@ -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', diff --git a/src/rollers/followers.ts b/src/rollers/followers.ts new file mode 100644 index 0000000..62b776a --- /dev/null +++ b/src/rollers/followers.ts @@ -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)); +} \ No newline at end of file