Browse Source

Wandering monster page for dungeon/wilderness

master
Brandon Cornejo 4 years ago
parent
commit
5a64f3bb2e
  1. 9
      acks/templates/base.html
  2. 49
      acks/templates/srd/Chapter00.html
  3. 381
      acks/templates/wandering_monster.html
  4. 18
      acks/views.py
  5. 267
      acks/wandering_monster.py
  6. 1
      roll20/macros/initiative.macro
  7. 1
      roll20/macros/roll-stats.macro
  8. 11
      roll20/macros/scavenge.macro

9
acks/templates/base.html

@ -7,6 +7,7 @@
{% set judge_bar = [ {% set judge_bar = [
('/npc/party', 'npcparty', 'Generate Party', false), ('/npc/party', 'npcparty', 'Generate Party', false),
('/npc/single', 'npcsingle', 'Generate NPC', false), ('/npc/single', 'npcsingle', 'Generate NPC', false),
('/wandering_monster', 'wandering', 'Wandering Monster', false),
('/lairs', 'lairs', 'Lairs', false), ('/lairs', 'lairs', 'Lairs', false),
('http://autarch.co/treasure', 'treasure', 'Treasure', true), ('http://autarch.co/treasure', 'treasure', 'Treasure', true),
('/quest/list', 'questlist', 'Quests', false), ('/quest/list', 'questlist', 'Quests', false),
@ -34,11 +35,11 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- UIkit CSS --> <!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/css/uikit.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.6.8/dist/css/uikit.min.css" />
<!-- UIkit JS --> <!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.2.4/dist/js/uikit-icons.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.6.8/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.6.8/dist/js/uikit-icons.min.js"></script>
<style> <style>
h1 strong { h1 strong {
@ -107,7 +108,7 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="uk-container uk-margin-medium-top">
<div id="base_page_content" class="uk-container uk-margin-medium-top">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</div> </div>
<footer class="uk-flex uk-flex-top uk-flex-center uk-margin-small uk-margin-medium-top"> <footer class="uk-flex uk-flex-top uk-flex-center uk-margin-small uk-margin-medium-top">

49
acks/templates/srd/Chapter00.html

@ -1,25 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Adventurer Conqueror King System</title>
<style>
table {
border-collapse: collapse;
}
table, td, th {
border: 1px solid black;
padding: 4px;
}
table + table {
margin-top: 4ex;
}
</style>
</head>
<body>
{% extends "base.html" %}
{% set active_page = "handbook" %}
{% block title %}Handbook - Index{% endblock %}
{% block content %}
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Handbook</h1>
</div>
<div id="handbook-container">
<h1 id="adventurer-conqueror-king-system">Adventurer Conqueror King System</h1> <h1 id="adventurer-conqueror-king-system">Adventurer Conqueror King System</h1>
<p><strong>Rules for Roleplaying in a World of Swords, Sorcery, and Strongholds</strong></p> <p><strong>Rules for Roleplaying in a World of Swords, Sorcery, and Strongholds</strong></p>
@ -637,8 +624,22 @@
<h3 id="open-game-licenseoglhtmlopen-game-license"><a href="OGL.html#open-game-license">Open Game License</a></h3> <h3 id="open-game-licenseoglhtmlopen-game-license"><a href="OGL.html#open-game-license">Open Game License</a></h3>
<hr /> <hr />
</div>
{% endblock %}
{% block head %}
<style>
table {
border-collapse: collapse;
}
table, td, th {
border: 1px solid black;
padding: 4px;
}
</body>
</html>
table + table {
margin-top: 4ex;
}
</style>
{% endblock %}

381
acks/templates/wandering_monster.html

@ -0,0 +1,381 @@
{% extends "base.html" %}
{% set active_page = "wandering_monster" %}
{% block title %}Wandering Monsters{% endblock %}
{% block content %}
<div class="uk-flex uk-flex-center uk-margin-bottom uk-margin-top">
<h1 class="uk-text-center"><strong>Adventurer Conqueror King</strong>Wandering Monsters</h1>
</div>
<form id="wandering_form" autocomplete="off">
<div class="uk-margin uk-text-center">
<span class="uk-margin-right">Where is the party?</span>
<button class="uk-button uk-button-default" type="button" id="dungeon_select">Dungeon</button>
<button class="uk-button uk-button-default" type="button" id="wilderness_select">Wilderness</button>
</div>
<div class="uk-margin">
<div id="dungeon_content" class="uk-hidden">
<hr class="uk-divider-icon"/>
<div class="uk-margin" uk-grid>
<div class="uk-width-1-3">
<h2 class="uk-text-left">Dungeon Monster</h2>
<h3>Configure</h3>
<div class="uk-margin uk-text-meta">
<strong>Frequency:</strong> When in a dungeon, the Judge should make an encounter throw every 2 turns. A throw of 6+ on a 1d6 indicates a wandering monster is encountered.
</div>
<div class="uk-margin">
<div>
<label for="dungeon_level" class="uk-margin-right"><strong>Current dungeon level:</strong></label>
<select name="dungeon_level" id="dungeon_level" class="uk-select uk-width-1-5">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6+</option>
</select>
</div>
<div class="uk-margin">
<button type="button" class="uk-button uk-button-primary" id="dungeon_generate_button">Roll</button>
</div>
</div>
</div>
<div class="uk-width-1-5"> &nbsp; </div>
<div id="dungeon_output" class="uk-width-expand uk-hidden">
<div id="roll_result" class="uk-margin uk-text-muted"></div>
<div class="uk-card uk-card-secondary uk-card-body">
<h3 id="wm_entity" class="uk-card-title uk-text-center uk-margin uk-text-success uk-margin-medium-bottom"></h3>
<div class="uk-margin uk-column-1-2 uk-column-divider uk-text-meta">
<div class="uk-margin"><strong>Quantity:</strong> <span id="wm_quantity"/></div>
<div class="uk-margin"><strong>Reaction:</strong> <span id="wm_reaction"/></div>
<div class="uk-margin"><strong>Dungeon Level:</strong> <span id="wm_dungeon_level"/></div>
<div class="uk-margin"><strong>Distance:</strong> <span id="wm_distance"/></div>
<div class="uk-margin"><strong>Surprise:</strong> <span id="wm_surprise"/></div>
<div class="uk-margin"><strong>Roll/Adjust:</strong> <span id="wm_adjust"/></div>
<div class="uk-margin"><strong>Monster Table:</strong> <span id="wm_monster_table"/></div>
</div>
</div>
</div>
</div>
</div>
<div id="wilderness_content" class="uk-hidden">
<hr class="uk-divider-icon">
<div class="uk-margin" uk-grid>
<div class="uk-width-1-3">
<h2 class="uk-text-left">Wilderness Monster</h2>
<h3>Configure</h3>
<div class="uk-margin uk-text-meta">
<strong>Frequency:</strong> When the characters in the wilderness, the Judge should make an encounter throw once per day if they are stationary or in settled terrain. Otherwise, the Judge should make an encounter throw each time the adventurers enter a new 6-mile hex.
</div>
<div class="uk-margin">
<div>
<label for="terrain_type" class="uk-margin-right"><strong>Current terrain type:</strong></label>
<select name="terrain_type" id="terrain_type" class="uk-select uk-width-1-1">
<option value="1" selected>Clear, Grass, Scrub</option>
<option value="2">Woods</option>
<option value="3">River</option>
<option value="4">Swamp</option>
<option value="5">Mountains, Hills</option>
<option value="6">Barren, Desert</option>
<option value="7">Inhabited</option>
<option value="8">City</option>
<option value="9">Ocean</option>
<option value="10">Jungle</option>
</select>
</div>
<div class="uk-margin">
<button type="button" class="uk-button uk-button-primary" id="wilderness_generate_button">Roll</button>
</div>
</div>
</div>
<div class="uk-width-1-5">
&nbsp;
</div>
<div id="wilderness_output" class="uk-width-expand uk-hidden">
<div id="roll_result" class="uk-margin uk-text-muted"></div>
<div class="uk-card uk-card-secondary uk-card-body">
<h3 id="wm_entity" class="uk-card-title uk-text-center uk-margin uk-text-success"></h3>
<div class="uk-margin uk-column-1-2 uk-column-divider uk-text-meta">
<div><strong>Surprise:</strong> <span id="wm_surprise"/></div>
<div><strong>Reaction:</strong> <span id="wm_reaction"/></div>
<div><strong>Monster Table:</strong> <span id="wm_monster_table"/></div>
<div><strong>Terrain Type:</strong> <span id="wm_terrain_type"/></div>
</div>
<div id="wilderness_distance_table" class="uk-margin uk-text-meta uk-flex uk-flex-center uk-margin-large-top">
<table>
<thead>
<tr>
<th style="text-align: left">Terrain</th>
<th style="text-align: left">Encounter Distance (yards)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">Badlands</td>
<td style="text-align: left">2d6x10</td>
</tr>
<tr>
<td style="text-align: left">Desert</td>
<td style="text-align: left">4d6x10</td>
</tr>
<tr>
<td style="text-align: left">Fields, Fallow</td>
<td style="text-align: left">4d6x10</td>
</tr>
<tr>
<td style="text-align: left">Fields, Ripe</td>
<td style="text-align: left">5d10</td>
</tr>
<tr>
<td style="text-align: left">Fields, Wild</td>
<td style="text-align: left">3d6x5</td>
</tr>
<tr>
<td style="text-align: left">Forest, Heavy or Jungle</td>
<td style="text-align: left">5d4</td>
</tr>
<tr>
<td style="text-align: left">Forest, Light</td>
<td style="text-align: left">5d8</td>
</tr>
<tr>
<td style="text-align: left">Marsh</td>
<td style="text-align: left">8d10</td>
</tr>
<tr>
<td style="text-align: left">Mountains</td>
<td style="text-align: left">4d6x10</td>
</tr>
<tr>
<td style="text-align: left">Plains</td>
<td style="text-align: left">5d20x10</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<hr class="uk-divider-icon">
<div>
<h3 class="uk-text-center">Roll Modifiers</h3>
<div class="uk-margin-bottom uk-margin-xlarge-left uk-margin-xlarge-right" uk-grid>
<div class="uk-width-1-3 uk-flex-column uk-padding">
<div class="uk-margin-right">Encounter: <input type="number" id="check_mod" min="-10" max="10" class="uk-input uk-width-1-4 uk-margin-left" value="0" onclick="this.select();"></div>
<div class="uk-text-meta uk-margin-small-top">Adjustment to chance of encountering a wandering monster at all.</div>
</div>
<div class="uk-width-1-3 uk-flex-column uk-padding">
<div class="uk-margin-right">Surprise: <input type="number" id="surprise_mod" min="-10" max="10" class="uk-input uk-width-1-4 uk-margin-left" value="0" onclick="this.select();"></div>
<div class="uk-text-meta uk-margin-small-top">Adjustments to surprise for vigilance, lighting, noises, and any other bonuses/penalties.</div>
</div>
<div class="uk-width-1-3 uk-flex-column uk-padding">
<div class="uk-margin-right">Reaction: <input type="number" id="reaction_mod" min="-10" max="10" class="uk-input uk-width-1-4 uk-margin-left" value="0" onclick="this.select()"></div>
<div class="uk-text-meta uk-margin-small-top">Charisma modifier of "lead" character, other situational bonuses/penalties.</div>
</div>
</div>
</div>
</form>
{% endblock %}
{% block head %}
<style>
div#wilderness_distance_table {
font-size: .835rem;
}
</style>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', () => {
var dungeon_select = document.querySelector('button#dungeon_select');
var wilderness_select = document.querySelector('button#wilderness_select');
dungeon_select.addEventListener("click", toggleLocation);
wilderness_select.addEventListener("click", toggleLocation);
var dungeon_roll = document.querySelector('button#dungeon_generate_button');
dungeon_roll.addEventListener("click", generateWanderingDungeonMonster);
var wilderness_roll = document.querySelector('button#wilderness_generate_button');
wilderness_roll.addEventListener("click", generateWanderingWildernessMonster);
});
function toggleLocation(e) {
var dungeon_select = document.querySelector('button#dungeon_select');
var wilderness_select = document.querySelector('button#wilderness_select');
var dungeon_content = document.querySelector('div#dungeon_content');
var wilderness_content = document.querySelector('div#wilderness_content');
if(e.target.id === "dungeon_select") {
wilderness_content.classList.add("uk-hidden");
dungeon_content.classList.remove("uk-hidden");
dungeon_select.classList.add("uk-button-secondary");
dungeon_select.classList.remove("uk-button-default");
wilderness_select.classList.add("uk-button-default");
wilderness_select.classList.remove("uk-button-secondary");
}
if(e.target.id === "wilderness_select") {
dungeon_content.classList.add("uk-hidden");
wilderness_content.classList.remove("uk-hidden");
wilderness_select.classList.add("uk-button-secondary");
wilderness_select.classList.remove("uk-button-default");
dungeon_select.classList.add("uk-button-default");
dungeon_select.classList.remove("uk-button-secondary");
}
}
function generateWanderingDungeonMonster() {
resetOutputAreas();
var dungeon_level_select = document.querySelector('select#dungeon_level');
var surprise_mod = document.querySelector('input#surprise_mod');
var reaction_mod = document.querySelector('input#reaction_mod');
var check_mod = document.querySelector('input#check_mod');
var payload = {
type: "dungeon",
dungeon_level: parseInt(dungeon_level_select.value),
surprise_mod: parseInt(surprise_mod.value) || 0,
reaction_mod: parseInt(reaction_mod.value) || 0,
check_mod: parseInt(check_mod.value) || 0
}
fetch("{{ url_for('default_views.generate_wandering_monster') }}", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
}).then(response => response.json()).then(populateDungeonResponse);
}
function generateWanderingWildernessMonster() {
resetOutputAreas();
var terrain_type_select = document.querySelector('select#terrain_type');
var surprise_mod = document.querySelector('input#surprise_mod');
var reaction_mod = document.querySelector('input#reaction_mod');
var check_mod = document.querySelector('input#check_mod');
var payload = {
type: "wilderness",
terrain_type: parseInt(terrain_type_select.value),
surprise_mod: parseInt(surprise_mod.value) || 0,
reaction_mod: parseInt(reaction_mod.value) || 0,
check_mod: parseInt(check_mod.value) || 0
}
fetch("{{ url_for('default_views.generate_wandering_monster') }}", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
}).then(response => response.json()).then(populateWildernessResponse);
}
function resetOutputAreas() {
document.querySelector('div#dungeon_output').classList.remove('uk-animation-fade');
document.querySelector('div#wilderness_output').classList.remove('uk-animation-fade');
document.querySelector('div#dungeon_output').classList.add('uk-hidden');
document.querySelector('div#wilderness_output').classList.add('uk-hidden');
}
function populateDungeonResponse(data) {
var result_flag = document.querySelector('div#dungeon_output div#roll_result');
// If we didn't get a Monster back
if(data.check) {
result_flag.innerText = `Danger! The party has encountered a foe! (${data.check_roll})`;
result_flag.classList.remove('uk-text-success');
result_flag.classList.add('uk-text-warning');
// Populate data-points
document.querySelector('div#dungeon_output #wm_entity').innerText = data.entity;
document.querySelector('div#dungeon_output #wm_monster_table').innerText = data.monster_table;
document.querySelector('div#dungeon_output #wm_dungeon_level').innerText = data.dungeon_level;
document.querySelector('div#dungeon_output #wm_quantity').innerText = data.quantity;
document.querySelector('div#dungeon_output #wm_adjust').innerText = `${data.roll} (${data.initial}) / ${data.adjustment}`;;
document.querySelector('div#dungeon_output #wm_distance').innerText = data.distance + '\'';;
document.querySelector('div#dungeon_output #wm_surprise').innerText = `${data.surprise[0]} (${data.surprise[1]})`;
document.querySelector('div#dungeon_output #wm_reaction').innerText = `${data.reaction[0]} (${data.reaction[1]})`;
document.querySelector('div#dungeon_output > div.uk-card').classList.remove('uk-hidden');
} else {
result_flag.innerText = `Safety! The party hasn't encountered a monster... this time. (${data.check_roll})`;
result_flag.classList.remove('uk-text-warning');
result_flag.classList.add('uk-text-success');
document.querySelector('div#dungeon_output > div.uk-card').classList.add('uk-hidden');
}
// Unhide the appropriate content box
document.querySelector('div#wilderness_output').classList.add('uk-hidden');
document.querySelector('div#dungeon_output').classList.remove('uk-hidden');
// Hit a fancy animation
document.querySelector('div#dungeon_output').classList.add('uk-animation-fade');
// Reset the form to prepare for next values
document.querySelector('form#wandering_form').reset();
}
function populateWildernessResponse(data) {
var result_flag = document.querySelector('div#wilderness_output div#roll_result');
// Did we get a monster returned?
if(data.check) {
result_flag.innerText = `Danger! The party has encountered a foe! (${data.check_roll})`;
result_flag.classList.remove('uk-text-success');
result_flag.classList.add('uk-text-warning');
document.querySelector('#wilderness_output h3#wm_entity').innerText = data.entity;
document.querySelector('#wilderness_output span#wm_surprise').innerText = `${data.surprise[0]} (${data.surprise[1]})`;
document.querySelector('#wilderness_output span#wm_reaction').innerText = `${data.reaction[0]} (${data.reaction[1]})`;
document.querySelector('#wilderness_output span#wm_monster_table').innerText = data.monster_table;
document.querySelector('#wilderness_output span#wm_terrain_type').innerText = data.terrain_type;
document.querySelector('div#wilderness_output > div.uk-card').classList.remove('uk-hidden');
} else {
result_flag.innerText = `Safety! The party hasn't encountered a monster... this time. (${data.check_roll})`;
result_flag.classList.remove('uk-text-warning');
result_flag.classList.add('uk-text-success');
document.querySelector('div#wilderness_output > div.uk-card').classList.add('uk-hidden');
}
// Unhide the appropriate content box
document.querySelector('div#dungeon_output').classList.add('uk-hidden');
document.querySelector('div#wilderness_output').classList.remove('uk-hidden');
// Hit a fancy animation
document.querySelector('div#wilderness_output').classList.add('uk-animation-fade');
// Reset the form to prepare for next values
document.querySelector('form#wandering_form').reset();
}
</script>
{% endblock %}

18
acks/views.py

@ -1,8 +1,10 @@
from pathlib import Path from pathlib import Path
from flask import Blueprint, render_template, url_for, redirect
from flask import Blueprint, render_template, url_for, redirect, request
from flask_basicauth import BasicAuth from flask_basicauth import BasicAuth
from . import wandering_monster
class PlayerAuth(BasicAuth): class PlayerAuth(BasicAuth):
@ -79,3 +81,17 @@ def token_gallery():
categories = [(k, v) for k, v in tokens.items()] categories = [(k, v) for k, v in tokens.items()]
categories = sorted(categories, key=lambda category: len(category[1]), reverse=True) categories = sorted(categories, key=lambda category: len(category[1]), reverse=True)
return render_template('player_tokens.html', categories=categories, data=tokens) return render_template('player_tokens.html', categories=categories, data=tokens)
@default_views.route('/wandering_monster')
def view_wandering_monster():
return render_template('wandering_monster.html')
@default_views.route('/wandering_monster/generate', methods=['POST'])
def generate_wandering_monster():
data = request.get_json()
if data['type'] == "dungeon":
monster = wandering_monster.generate_dungeon_monster(data['check_mod'], data['dungeon_level'], data['reaction_mod'], data['surprise_mod'])
if data['type'] == "wilderness":
monster = wandering_monster.generate_wilderness_monster(data['check_mod'], data['terrain_type'], data['reaction_mod'], data['surprise_mod'])
return monster

267
acks/wandering_monster.py

@ -0,0 +1,267 @@
import math
import random
import dice
DUNGEON_WANDERING_MONSTER_LEVEL = {
# 1 2 3 4 5 6 7 8 9 0 1 2
1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3],
2: [1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4],
3: [1, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5],
4: [2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 6],
5: [3, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6],
6: [4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6],
}
DUNGEON_RANDOM_MONSTERS_BY_LEVEL = {
1: [
("Goblin", "2d4"), ("Kobold", "4d4"), ("Morlock", "1d12"),
("Orc", "2d4"), ("Beetle, Fire", "1d8"), ("Centipede, Giant", "2d4"),
("Ferret, Giant", "1d8"), ("Rat, Giant", "3d6"), ("Men, Brigand", "2d4"),
("Skeleton", "3d4"), ("Stirge", "1d10"), ("NPC Party (Lvl 1)", "1d4+2")
],
2: [
("Gnoll", "1d6"), ("Hobgoblin", "1d6"), ("Lizardman", "2d4"),
("Troglodyte", "1d8"), ("Bat, Giant", "1d10"),
("Fly, Giant Carnivorous", "1d8"), ("Locust, Cavern", "1d10"),
("Snake, Pit Viper", "1d8"), ("Ghoul", "1d6"), ("Men, Berserker", "1d6"),
("Zombie", "2d4"), ("NPC Party (Lvl 2)", "1d4+2")
],
3: [
("Bugbear", "2d4"), ("Lycanthrope, Werewolf", "1d6"), ("Ogre", "1d6"),
("Throghrin", "1d6"), ("Ant, Giant", "2d4"), ("Lizard, Drago", "1d3"),
("Scorpion, Giant", "1d6"), ("Wolf, Dire", "1d4"),
("Carcass Scavenger", "1d3"), ("Gargoyle", "1d6"), ("Wight", "1d6"),
("NPC Party (Lvl 4)", "1d4+2")
],
4: [
("Lycanthrope, Werebear", "1d4"), ("Lycanthrope, Weretiger", "1d4"),
("Minotaur", "1d6"), ("Boar, Giant", "1d4"), ("Owl Bear", "1d4"),
("Phase Tiger", "1d4"), ("Rhagodessa, Giant", "1d4"),
("Snake, Giant Python", "1d4"), ("Cockatrice", "1d4"), ("Medusa", "1d3"),
("Wraith", "1d4"), ("NPC Party (Lvl 5)", "1d4+2")
],
5: [
("Ettin", "1d2"), ("Giant, Hill", "1d4"), ("Giant, Stone", "1d2"),
("Troll", "1d8"), ("Ankheg", "1d6"), ("Caecilian", "1d3"),
("Basilisk", "1d6"), ("Hell Hound, Greater", "2d4"),
("Salamander, Flame", "1d4+1"), ("Spectre", "1d4"), ("Wyvern", "1d2"),
("NPC Party (Lvl 8)", "1d4+3")
],
6: [
("Cyclops", "1`"), ("Giant, Cloud", "1d2"), ("Purple Worm", "1d2"),
("Demon Boar", "1d4"), ("Dragon (20 HD)", "1"), ("Hydra (12 HD)", "1"),
("Gorgon", "1d2"), ("Lamia", "1"), ("Remorhaz (15 HD)", "1"),
("Skittering Maw", "1"), ("Vampire (9 HD)", "1d4"),
("NPC Party (Lvl 14)", "1d4+3")
]
}
WILDERNESS_TYPE_BY_ID = {
1: ["Clear", "Grass", "Scrub"],
2: ["Woods"],
3: ["River"],
4: ["Swamp"],
5: ["Mountains", "Hills"],
6: ["Barren", "Desert"],
7: ["Inhabited"],
8: ["City"],
9: ["Ocean"],
10: ["Jungle"],
}
WILDERNESS_FREQUENCY_BY_TERRAIN = {
1: 6, # Clear, Grass, Scrub
2: 5, # Woods
3: 5, # River
4: 4, # Swamp
5: 4, # Mountains, Hills
6: 4, # Barren, Desert
7: 6, # Inhabited
8: 6, # City
9: 5, # Ocean
10: 4, # Jungle
}
WILDERNESS_ENCOUNTER_BY_TERRAIN = {
1: ["Men", "Flyer", "Humanoid", "Animal", "Animal", "Unusual", "Dragon", "Insect"],
2: ["Men", "Flyer", "Humanoid", "Insect", "Unusual", "Animal", "Animal", "Dragon"],
3: ["Men", "Flyer", "Humanoid", "Insect", "Swimmer", "Swimmer", "Animal", "Dragon"],
4: ["Men", "Flyer", "Humanoid", "Swimmer", "Undead", "Undead", "Insect", "Dragon"],
5: ["Men", "Flyer", "Humanoid", "Unusual", "Animal", "Humanoid", "Dragon", "Flyer"],
6: ["Men", "Flyer", "Humanoid", "Humanoid", "Animanl", "Dragon", "Undead", "Unusual"],
7: ["Men", "Flyer", "Humanoid", "Men", "Men", "Insect", "Animal", "Dragon"],
8: ["Men", "Undead", "Humanoid", "Men", "Men", "Men", "Men", "Men", "Men", "Men"],
9: ["Men", "Flyer", "Insect", "Insect", "Humanoid", "Animal", "Animal", "Dragon"],
10: ["Men", "Flyer", "Insect", "Insect", "Humanoid", "Animal", "Animal", "Dragon"],
}
WILDERNESS_ENCOUNTER_SUBTABLES = {
"Men": {
"Clear, Grass, Scrub": ["Brigand", "Noble", "Mage", "Fighter", "Thief", "Cleric", "Nomad", "Thief", "NPC Party", "Merchant", "Berserker", "Venturer"],
"Woods": ["Brigand", "Thief", "NPC Party", "Merchant", "Berserker", "Brigand", "Cleric", "Mage", "Fighter", "Thief", "Brigand", "NPC Party"],
"River": ["Brigand", "Thief", "NPC Party", "Merchant", "Buccaneer", "Buccaneer", "Cleric", "Mage", "Fighter", "Merchant", "Buccaneer", "NPC Party"],
"Swamp": ["Brigand", "Thief", "NPC Party", "NPC Party", "Merchant", "Cleric", "Venturer", "Berserker", "Fighter", "Mage", "NPC Party", "Thief"],
"Barrens, Mountains, Hills": ["Brigand", "Thief", "NPC Party", "Venturer", "Berserker", "NPC Party", "Cleric", "Mage", "Fighter", "Brigand", "NPC Party", "Barbarian"],
"Desert": ["Nomad", "Nomad", "NPC Party", "Merchant", "Nomad", "Nomad", "Cleric", "Mage", "Fighter", "Noble", "Nomad", "Nomad"],
"Inhabited": ["Thief", "Venturer", "NPC Party", "NPC Party", "Merchant", "Fighter", "Merchant", "Fighter", "Mage", "Cleric", "Cleric", "Noble"],
"City": ["Thief", "Venterer", "NPC Party", "NPC Party", "Merchant", "Fighter", "Merchant", "Fighter", "Mage", "Cleric", "Cleric", "Noble"],
"Ocean": ["Buccaneer", "Pirate", "Merchant", "NPC Party", "Pirate", "Merchant", "Merchant", "Merchant", "Buccaneer", "Pirate", "Merchant", "Pirate"],
"Jungle": ["Brigand", "Venturer", "Thief", "NPC Party", "Cleric", "Fighter", "Medium", "Berserker", "Brigand", "Barbarian", "NPC Party", "Brigand"],
},
"Humanoid": {
"Clear, Grass, Scrub": ["Bugbear", "Elf", "Gnoll", "Goblin", "Halfling", "Hobgoblin", "Kobold", "Ogre", "Orc", "Pixie", "Throghrin", "Troll"],
"Woods": ["Bugbear", "Cyclops", "Dryad", "Elf", "Giant, Hill", "Gnoll", "Goblin", "Hobgoblin", "Ogre", "Orc", "Pixie", "Troll"],
"River": ["Bugbear", "Elf", "Gnoll", "Hobgoblin", "Lizard Man", "Lizard Man", "Naiad", "Naiad", "Ogre", "Orc", "Sprite", "Troll"],
"Swamp": ["Gnoll", "Goblin", "Hobgoblin", "Lizard Man", "Lizard Man", "Lizard Man", "Naiad", "Ogre", "Orc", "Troglodyte", "Troll", "Troll"],
"Hills, Mountain": ["Dwarf", "Giant, Cloud", "Giant, Frost", "Giant, Hill", "Giant, Stone", "Giant, Storm", "Goblin", "Kobold", "Ogre", "Orc", "Troglodyte", "Troll"],
"Barrens": ["Bugbear", "Giant, Hill", "Goblin", "Gnoll", "Hobgoblin", "Hobgoblin", "Ogre", "Orc", "Orc", "Throghrin", "Troll"],
"Desert": ["Bugbear", "Gnoll", "Giant, Fire", "Hobgoblin", "Hobgoblin", "Minotaur", "Ogre", "Ogre", "Orc", "Orc", "Troll", "Troll"],
"City": ["Doppelganger", "Dwarf", "Elf", "Gnome", "Halfling", "Pixie", "Sprite", "Werebear", "Wereboar", "Wererat", "Weretiger", "Werewolf"],
"Inhabited": ["Doppelganger", "Dwarf", "Elf", "Gnome", "Goblin", "Halfling", "Kobold", "Ogre", "Orc", "Pixie", "Sprite", "Wererat"],
"Jungle": ["Bugbear", "Cyclops", "Elf", "Giant, Fire", "Giant, Hill", "Gnoll", "Goblin", "Lizard Man", "Ogre", "Orc", "Troglodyte", "Troll"],
},
"Animal": {
"Clear, Grass, Scrub": ["Herd Animal (Antelope)", "Boar*", "Cat, Lion", "Cat, Panther", "Elephant", "Hawk*", "Horse, Light", "Lizard, Tuatara", "Mule (Donkey)", "Snake, Pit Viper", "Snake, Giant Rattler", "Weasel, Giant"],
"Woods": ["Herd Animal (Antelope)", "Bat*", "Bear, Grizzly", "Boar*", "Cat, Panther", "Hawk*", "Owl*", "Snake, Pit Viper", "Spider, Black Widow", "Unicorn", "Wolf", "Wolf, Dire"],
"River": ["Herd Animal (Antelope)", "Bear, Black", "Boar*", "Cat, Panther", "Crab, Giant", "Crocodile*", "Leech, Giant", "Piranha, Giant", "Rat*", "Shrew, Giant", "Swan", "Toad, Giant"],
"Hills": ["Herd Animal (Antelope)", "Bear, Grizzly", "Boar*", "Cat, Mountain Lion", "Eagle*", "Hawk*", "Horse, Light", "Herd Animal (Sheep)", "Snake, Pit Viper", "Owl*", "Wolf", "Wolf, Dire"],
"Mountains": ["Herd Animal (Antelope)", "Bear, Cave", "Cat, Mountain Lion", "Eagle*", "Herd Animal (Goat)", "Hawk*", "Mule (Donkey)", "Baboon, Rock", "Snake, Pit Viper", "Snake, Giant Rattler", "Wolf", "Wolf, Dire"],
"Barrens": ["Herd Animal (Antelope)", "Bear, Cave", "Cat, Mountain Lion", "Eagle*", "Goat, Wild", "Hawk*", "Rock Babooon", "Snake, Pit Viper", "Snake, Giant Rattler", "Spider, Crab", "Wolf, Dire", "Vulture"],
"Desert": ["Herd Animal (Antelope)", "Herd Animal (Antelope)", "Camel", "Camel", "Cat, Lion", "Hawk*", "Lizard, Gecko", "Lizard, Tuatara", "Snake, Giant Rattler", "Wolf (Wild Dog)", "Wolf, Dire", "Vulture"],
"Inhabited": ["Herd Animal (Antelope)", "Boar*", "Cat, Panther", "Lizard, Draco", "Lizard, Gecko", "Lizard, Horned", "Monkey", "Shrew, Giant", "Snake, Pit Viper", "Snake, Python", "Snake, Spitting Cobra", "Spider, Crab"],
"Jungle": ["Goat", "Boar*", "Dog", "Ferret, Giant", "Hawk*", "Horse, Light", "Mule (Donkey)", "Rat*", "Snake, Pit Viper", "Herd Animal (Sheep)", "Weasel, Giant", "Wolf"],
"Other": ["Bear, Cave", "Cat, Sabre-Tooth Tiger", "Crocodile, Giant", "Mastodon", "Pteranodon", "Rhino, Woolly", "Snake, Python", "Stegosaurus", "Titanothere", "Triceratops", "Tyrannosaurus Rex", "Wolf, Dire"],
"*": "Roll 1d6: 1-4 Normal or Swarm, 5-6 Giant"
},
"Flyer": {
"Barrens": ["Cockatrice", "Gargoyle", "Griffon", "Harpy", "Hawk, Giant", "Hippogriff", "Lammasu", "Manticore", "Pegasus", "Roc, Small", "Stirge", "Wyvern"],
"Desert": ["Chimera", "Cockatrice", "Gargoyle", "Griffon", "Hawk, Giant", "Lammasu", "Manticore", "Pterodactyl", "Roc, Small", "Sphinx", "Wyvern", "Vulture"],
"Mountains": ["Swarm, Bat", "Chimera", "Cockatrice", "Gargoyle", "Griffon", "Harpy", "Hawk, Giant", "Hippogriff", "Manticore", "Pegasus", "Roc*", "Wyvern"],
"Woods": ["Bat, Giant", "Swarm, Bat", "Cockatrice", "Griffon", "Hawk, Giant", "Hippogriff", "Pegasus", "Owl, Giant", "Pixie", "Roc, Small", "Sprite", "Stirge"],
"Other": ["Cockatrice", "Fly, Giant Carnivorous", "Gargoyle", "Griffon", "Hawk, Giant", "Hippogriff", "Bee, Killer Giant", "Pegasus", "Pixie", "Roc, Small", "Sprite", "Stirge"],
"*": "Roll 1d6: 1-3 Small, 4-5 Large, 6 Giant"
},
"Swimmer": {
"River, Lake": ["Crab, Giant", "Crocodile", "Crocodile", "Crocodile, Large", "Fish, Catfish", "Fish, Piranha", "Fish, Sturgeon", "Leech, Giant", "Lizard Man", "Merman", "Naiad", "Skittering Maw"],
"Ocean": ["Dragon Turtle", "Hydra, Sea", "Merman", "Octopus, Giant", "Sea Dragon", "Sea Serpent", "Shark*", "Shark*", "Skittering Maw", "Snake, Sea", "Squid, Giant", "Whale"],
"Swamp": ["Crab, Giant", "Crocodile", "Crocodile, Large", "Crocodile, Giant", "Fish, Catfish", "Insect Swarm", "Insect Swawrm", "Leech, Giant", "Leech, Giant", "Lizard Man", "Lizard Man", "Skittering Maw"],
"*": "Roll 1d6: 1-2 Bull Shark, 3-4 Mako Shark, 5-6 Great White Shark"
},
"Dragon": {
"Other": ["Basilisk", "Caecilian", "Chimera", "Dragon*", "Dragon*", "Sphinx", "Hydra/Sea Hydra", "Lamia", "Purple Worm", "Snake, Python", "Salamander", "Wyvern"],
"*": "Always Black in Swamp, Blue in Mountains, Brown in Desert, Green in Woods, Red in Barrens, Sea in Oceans/Rivers/Lake. Otherwise roll 1d10: 1 Black, 2 Blue, 3 Brown, 4 Green, 5 Metallic, 6 Red, 7 Sea, 8 White, 9 Wyrm, 10 Judge’s Choice.",
},
"Insect": {
"Other": ["Beetle, Fire", "Beetle, Giant Bombardier", "Beetle, Tiger", "Carcass Scavenger", "Centipede, Giant", "Ant, Giant", "Fly, Giant Carnivorous", "Killer Bee", "Rhagodessa", "Scorpion, Giant", "Spider, Black Widow", "Spider, Crab"],
},
"Undead": {
"Other": ["Ghoul", "Ghoul", "Mummy", "Mummy", "Skeleton", "Skeleton", "Spectre", "Wight", "Wraith", "Vampire", "Zombie", "Zombie"],
},
"Unusual": {
"Other": ["Basilisk", "Blink Dog", "Centaur", "Gorgon", "Hellhound", "Lycanthrope", "Medusa", "Phase Tiger", "Rust Monster", "Skittering Maw", "Treant", "Ape, White"],
}
}
def surprise_roll(mod):
roll = int(dice.roll("1d6 + {}".format(mod)))
status = "Surprised" if roll < 3 else "Ready"
return (status, roll)
def reaction_roll(mod, difference):
roll = int(dice.roll("2d6 + {}".format(mod)))
roll += difference
if roll <= 2:
status = "Hostile, attacks"
elif roll >= 3 and roll <= 5:
status = "Unfriendly, may attack"
elif roll >= 6 and roll <= 8:
status = "Neutral, uncertain"
elif roll >= 9 and roll <= 11:
status = "Indifferent, uninterested"
elif roll >= 12:
status = "Friendly, helpful"
return (status, roll)
def check_encounter(mod=0, throw=6):
roll = int(dice.roll("1d6 + {}".format(mod)))
return ((roll >= throw), roll)
def generate_dungeon_monster(check_mod, dungeon_level, reaction_mod, surprise_mod):
check = check_encounter(check_mod)
if not check[0]:
return {"check": False, "check_roll": check[1]}
if not dungeon_level in DUNGEON_WANDERING_MONSTER_LEVEL:
return None
monster_table = random.choice(DUNGEON_WANDERING_MONSTER_LEVEL[dungeon_level])
encounter = random.choice(DUNGEON_RANDOM_MONSTERS_BY_LEVEL[monster_table])
difference = monster_table - dungeon_level
quantity = int(dice.roll(encounter[1]))
adjusted = quantity
for _ in range(abs(difference)):
adjusted = adjusted + (adjusted * 0.5) if difference > 0 else adjusted - (adjusted * 0.5)
adjusted = math.floor(adjusted) if adjusted > 1 else 1
distance = int(dice.roll("2d6")) * 10
surprise = surprise_roll(surprise_mod)
reaction = reaction_roll(reaction_mod, difference)
return {
"dungeon_level": dungeon_level,
"monster_table": monster_table,
"entity": encounter[0],
"quantity": adjusted,
"initial": quantity,
"roll": encounter[1],
"adjustment": difference,
"distance": distance,
"surprise": surprise,
"reaction": reaction,
"check": True,
"check_roll": check[1]
}
def generate_wilderness_monster(check_mod, terrain_type, reaction_mod, surprise_mod):
check_throw = WILDERNESS_FREQUENCY_BY_TERRAIN[terrain_type]
check = check_encounter(check_mod, check_throw)
if not check[0]:
return {"check": False, "check_roll": check[1]}
encounter_type_roll = int(dice.roll("1d8"))
encounter_type = WILDERNESS_ENCOUNTER_BY_TERRAIN[terrain_type][encounter_type_roll - 1]
subtable = WILDERNESS_ENCOUNTER_SUBTABLES[encounter_type]
terrain_type_labels = WILDERNESS_TYPE_BY_ID[terrain_type]
endtable = None
for k in subtable.keys():
for l in terrain_type_labels:
if l in k:
endtable = subtable[k]
if endtable == None and "Other" in subtable:
endtable = subtable["Other"]
entity_roll = int(dice.roll("1d12"))
entity = endtable[entity_roll - 1]
surprise = surprise_roll(surprise_mod)
reaction = reaction_roll(reaction_mod, 0)
return {
"terrain_type": ', '.join(terrain_type_labels),
"monster_table": encounter_type,
"entity": entity,
"surprise": surprise,
"reaction": reaction,
"check": True,
"check_roll": check[1]
}

1
roll20/macros/initiative.macro

@ -0,0 +1 @@
&{template:acks} {{name=Initiative}} {{roll=[[1d6+@{selected|initiative_mod} &{tracker}]]}}

1
roll20/macros/roll-stats.macro

@ -0,0 +1 @@
&{template:acks} {{name=Character Stat Roll}} {{Strength = [[3d6]]}} {{Dexterity = [[3d6]]}} {{Intelligence = [[3d6]]}} {{Constitution = [[3d6]]}} {{Wisdom = [[3d6]]}} {{Charisma = [[3d6]]}}

11
roll20/macros/scavenge.macro

@ -0,0 +1,11 @@
?{Equipment Type|
Bladed Weapon,
&{template:acks&#125;{{name=Scavenge Gear&#125;&#125;{{subheader=Bladed Weapon&#125;&#125;{{desc=[[1t[Scavenge-Blade]]]&#125;&#125;
|
Blunt Weapon,
&{template:acks&#125;{{name=Scavenge Gear&#125;&#125;{{subheader=Blunt Weapon&#125;&#125;{{desc=[[1t[Scavenge-Blunt]]]&#125;&#125;
|
Armor and Equipment,
&{template:acks&#125;{{name=Scavenge Gear&#125;&#125;{{subheader=Armour and Equipment&#125;&#125;{{desc=[[1t[Scavenge-Other]]]&#125;&#125;
}
Loading…
Cancel
Save