add dinosaurs, dragons, and werebeasts
add some more campaign info add encounter generators (alpha)
This commit is contained in:
50
src/assets/fragments/aeryon/calendar.md
Normal file
50
src/assets/fragments/aeryon/calendar.md
Normal file
@@ -0,0 +1,50 @@
|
||||
<div class="calendarWrapper">
|
||||
|
||||
#### Trama, 78th Year Since Eradication
|
||||
|
||||
<ol class="calendar">
|
||||
|
||||
<li class="dayName">Undo</li>
|
||||
<li class="dayName">Tudo</li>
|
||||
<li class="dayName">Trado</li>
|
||||
<li class="dayName">Pordo</li>
|
||||
<li class="dayName">Fendo</li>
|
||||
<li class="dayName">Saedo</li>
|
||||
|
||||
<li class="firstDay">1</li>
|
||||
<li>2</li>
|
||||
<li>3</li>
|
||||
<li>4</li>
|
||||
<li>5</li>
|
||||
<li>6</li>
|
||||
<li>7</li>
|
||||
<li>8</li>
|
||||
<li>9</li>
|
||||
<li>10</li>
|
||||
<li>11</li>
|
||||
<li>12</li>
|
||||
<li>13</li>
|
||||
<li class="currDay">14</li>
|
||||
<li>15</li>
|
||||
<li>16</li>
|
||||
<li>17</li>
|
||||
<li>18</li>
|
||||
<li>19</li>
|
||||
<li>20</li>
|
||||
<li>21</li>
|
||||
<li>22</li>
|
||||
<li>23</li>
|
||||
<li>24</li>
|
||||
<li>25</li>
|
||||
<li>26</li>
|
||||
<li>27</li>
|
||||
<li>28</li>
|
||||
<li>29</li>
|
||||
<li>30</li>
|
||||
</ol>
|
||||
|
||||
<p><em>
|
||||
The month of Trama, in the 78th year since the Eradication of King Ranulf the Just and his court.
|
||||
</em></p>
|
||||
|
||||
</div>
|
5
src/assets/fragments/aeryon/today.md
Normal file
5
src/assets/fragments/aeryon/today.md
Normal file
@@ -0,0 +1,5 @@
|
||||
<div class="todayWrapper">
|
||||
|
||||
As of last session, it is **the morning of Saedo, the 30th day of Trama, in the 78th year since the Eradication of King Ranulf the Just and his court**.
|
||||
|
||||
</div>
|
6
src/assets/fragments/bestiary/bats/bite-and-drain.md
Normal file
6
src/assets/fragments/bestiary/bats/bite-and-drain.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Bite victim must **Save vs Paralyze** or fall unconscious for 1d10 rounds.
|
||||
|
||||
### Drain Blood
|
||||
|
||||
- May drain blood from an unconscious victim, inflicting 1d4 hp damage each round automatically.
|
||||
- Victim killed this way must **Save vs Spells** or become undead after 24 hours.
|
@@ -1,9 +1,3 @@
|
||||
### Breath Weapon
|
||||
|
||||
- Can be used up to 3x per day.
|
||||
- All caught in area take damage equal to dragon's current hit points, **Save vs Blasts** for half.
|
||||
- Shapes:
|
||||
- **Cloud** - 50’ long, 40’ wide, 20’ high.
|
||||
- **Cone** - 2’ wide at the mouth, 30’ wide at far end.
|
||||
- **Line** - 5’ wide along whole length.
|
||||
- Immune to their own breath weapon, automatically save vs related attacks.
|
||||
- Immune to their own breath weapon, automatically saves vs related attacks.
|
||||
|
@@ -1,9 +1,11 @@
|
||||
- Immune to mundane, non-silver weapons.
|
||||
- Capable of shifting between their (natural) humanoid form, animal form, and a hybrid form.
|
||||
- Takes one combat round to shift form, can attack as new form at end of round.
|
||||
- Must change into and remain in hybrid or animal form during a full moon.
|
||||
- Humanoid form usually retains some characteristics from animal form.
|
||||
- While in animal form, can only speak with animals of that type.
|
||||
- Cannot wear armor, as it limits their ability to shapeshift.
|
||||
- Cannot use beak, bite, claw, or talon attacks when in humanoid form.
|
||||
- Humanoid form usually retains some characteristics from animal form.
|
||||
- While in animal form, can only speak with animals of that type (unless otherwise noted).
|
||||
- Cannot use weapons while in animal form.
|
||||
- May summon 1d2 animals associated with their animal form from the surrounding area.
|
||||
- Animals arrive in 1d4 rounds.
|
||||
|
@@ -12,6 +12,15 @@
|
||||
// adding a dice looks for a matching die in the pattern and increments how many there are, or appends (if no match)
|
||||
// so clicking "d6" 3x gives you "3d6" in box
|
||||
|
||||
// utilities
|
||||
const getRandomValue = (max = 1, min = 1) =>
|
||||
Math.round(Math.random() * (max - min)) + min,
|
||||
rollDice = (sides = 1, count = 1) => {
|
||||
const vals = [...Array(count).keys()].map(() => getRandomValue(sides)),
|
||||
total = vals.reduce((v, a) => a + v, 0);
|
||||
return [total, ...vals];
|
||||
};
|
||||
|
||||
// Die Roller script
|
||||
const addRollerForm = () => {
|
||||
const rollerForm = document.getElementById('js-rollerForm'),
|
||||
@@ -81,8 +90,6 @@ const RoomTypes = {
|
||||
if (room <= RoomTypes.SPECIAL) return false;
|
||||
return treasure <= 2;
|
||||
},
|
||||
getRandomValue = (max = 1, min = 1) =>
|
||||
Math.round(Math.random() * (max - min)) + min,
|
||||
addRoomForm = () => {
|
||||
const roomForm = document.getElementById('js-roomForm'),
|
||||
roomOutput = document.getElementById('js-roomOutput');
|
||||
@@ -104,8 +111,8 @@ const RoomTypes = {
|
||||
};
|
||||
|
||||
// Complication Randomizer scripts
|
||||
const shuffleContainer = (parent) => {
|
||||
const container = document.getElementById(parent),
|
||||
const shuffleContainer = (parentId) => {
|
||||
const container = document.getElementById(parentId),
|
||||
children = container.children,
|
||||
length = children.length,
|
||||
shuffled = [...children];
|
||||
@@ -114,12 +121,60 @@ const shuffleContainer = (parent) => {
|
||||
shuffled.sort(() => 0.5 - Math.random());
|
||||
for (let i = 0; i < length; i++) container.appendChild(shuffled[i]);
|
||||
},
|
||||
setContainerContents = (parentId, content = '') => {
|
||||
const container = document.getElementById(parentId);
|
||||
if (container?.innerHTML) container.innerHTML = content;
|
||||
},
|
||||
addComplicationForm = () => {
|
||||
const complicationForm = document.getElementById('js-complicationForm');
|
||||
const complicationForm = document.getElementById('js-complicationForm'),
|
||||
formControls = document.createElement('div');
|
||||
formControls.innerHTML = [
|
||||
'<button id="js-btnSetDungeon">Dungeon</button>',
|
||||
'<button id="js-btnSetWilderness">Wilderness</button>',
|
||||
'<input type="submit" value="Randomize!" />',
|
||||
].join('\n');
|
||||
|
||||
complicationForm?.appendChild(formControls);
|
||||
|
||||
complicationForm?.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
shuffleContainer('js-complicationList');
|
||||
});
|
||||
|
||||
const setDungeonButton = document.getElementById('js-btnSetDungeon'),
|
||||
setWildernessButton = document.getElementById(
|
||||
'js-btnSetWilderness'
|
||||
);
|
||||
|
||||
setDungeonButton?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
setContainerContents(
|
||||
'js-complicationList',
|
||||
[
|
||||
'<li>Encounter</li>',
|
||||
'<li>Signs / Portents</li>',
|
||||
'<li>Locality</li>',
|
||||
'<li>Exhaustion *</li>',
|
||||
'<li>Light Source *</li>',
|
||||
'<li>No Complications</li>',
|
||||
].join('\n')
|
||||
);
|
||||
});
|
||||
|
||||
setWildernessButton?.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
setContainerContents(
|
||||
'js-complicationList',
|
||||
[
|
||||
'<li>Encounter</li>',
|
||||
'<li>Encounter *</li>',
|
||||
'<li>Signs / Portents</li>',
|
||||
'<li>Locality / Weather (2d4)</li>',
|
||||
'<li>Lose Way (1d3 hrs)</li>',
|
||||
'<li>No Complications</li>',
|
||||
].join('\n')
|
||||
);
|
||||
});
|
||||
},
|
||||
addAstralComplicationForm = () => {
|
||||
const complicationForm = document.getElementById(
|
||||
@@ -131,11 +186,146 @@ const shuffleContainer = (parent) => {
|
||||
});
|
||||
};
|
||||
|
||||
const reactionButtonVal = [
|
||||
'Attack!',
|
||||
'Hateful',
|
||||
'Leery',
|
||||
'Rude',
|
||||
'Aloof',
|
||||
'Uncertain',
|
||||
'Confused',
|
||||
'Indifferent',
|
||||
'Cordial',
|
||||
'Amiable',
|
||||
'Friendly!',
|
||||
],
|
||||
rollEncounter = (sides = 1) => {
|
||||
// roll 3d8 for monsters, note doubles (first 2 / last 2 / first & last) / triples
|
||||
const [monsterTotal, ...monsterRolls] = rollDice(sides, 3),
|
||||
hasStealthParty =
|
||||
monsterRolls.length > 1 && monsterRolls[0] === monsterRolls[1],
|
||||
hasStealthMonster =
|
||||
monsterRolls.length > 2 && monsterRolls[1] === monsterRolls[2],
|
||||
hasDoubleSurprise = hasStealthParty && hasStealthMonster,
|
||||
hasDoubles = hasStealthParty || hasStealthMonster,
|
||||
// roll 2d6 for reaction, note string & numeric value
|
||||
[reactionTotal, ...reactionRolls] = rollDice(6, 2),
|
||||
// roll 4d6 (or 1d4, if any doubles above) for starting distance
|
||||
[distanceTotal, ...distanceRolls] = hasDoubles
|
||||
? rollDice(4)
|
||||
: rollDice(6, 4),
|
||||
// Derived string and scores
|
||||
reactionText = `<strong>${
|
||||
reactionButtonVal[reactionTotal - 2]
|
||||
} (${reactionTotal})</strong><br /><em>[${reactionRolls}]</em>`,
|
||||
distanceText = `<strong>${
|
||||
distanceTotal * 10
|
||||
} yards</strong><br /><em>[${distanceRolls}]</em>`,
|
||||
encounterOutput = document.getElementById('js-encounterOutput'),
|
||||
outputTable = document.createElement('table');
|
||||
|
||||
// additional text denoting surprise results.
|
||||
let surpriseText = '';
|
||||
if (hasDoubleSurprise) surpriseText = 'Double Surprise';
|
||||
else if (hasStealthMonster) surpriseText = 'Monster has stealth';
|
||||
else if (hasStealthParty) surpriseText = 'Party has stealth';
|
||||
if (surpriseText) surpriseText = `<br />${surpriseText}!`;
|
||||
|
||||
// combine monster roll total + surprise + roll data
|
||||
const monsterRollText = `<strong>${monsterTotal}${surpriseText}</strong><br /><em>[${monsterRolls}]</em>`;
|
||||
|
||||
outputTable.innerHTML = [
|
||||
`<thead><th colspan="2">Encounter (3d${sides}): ${dayjs().format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
)}</th></thead>`,
|
||||
'<tbody>',
|
||||
`<tr><th>Monster</th><td>${monsterRollText}</td></tr>`,
|
||||
`<tr><th>Distance</th><td>${distanceText}</td></tr>`,
|
||||
`<tr><th>Reaction</th><td>${reactionText}</td></tr>`,
|
||||
'</tbody>',
|
||||
].join('\n');
|
||||
outputTable.classList.add('encounterResultTable');
|
||||
|
||||
encounterOutput.prepend(outputTable);
|
||||
},
|
||||
addEncounterRoller = () => {
|
||||
const complicationForm = document.getElementById('js-encounterForm'),
|
||||
formControls = document.createElement('div');
|
||||
formControls.innerHTML = [
|
||||
'<div class="encounterButtonsWrapper">',
|
||||
/*
|
||||
'<div class="encounterButtonsDungeonWrapper"><em>Dungeon</em><br />',
|
||||
'<button id="js-btnRollDungeonEncounter3d4">3d4</button>',
|
||||
'<button id="js-btnRollDungeonEncounter3d6">3d6</button>',
|
||||
'<button id="js-btnRollDungeonEncounter3d8">3d8</button>',
|
||||
'</div>',
|
||||
*/
|
||||
'<div class="encounterButtonsWildernessWrapper"><em>Wilderness</em><br />',
|
||||
'<button id="js-btnRollWildernessEncounter3d4">3d4</button>',
|
||||
'<button id="js-btnRollWildernessEncounter3d6">3d6</button>',
|
||||
'<button id="js-btnRollWildernessEncounter3d8">3d8</button>',
|
||||
'</div></div>',
|
||||
].join('\n');
|
||||
|
||||
complicationForm?.appendChild(formControls);
|
||||
|
||||
const rollDungeonEncounter3d4Button = document.getElementById(
|
||||
'js-btnRollDungeonEncounter3d4'
|
||||
),
|
||||
rollDungeonEncounter3d6Button = document.getElementById(
|
||||
'js-btnRollDungeonEncounter3d6'
|
||||
),
|
||||
rollDungeonEncounter3d8Button = document.getElementById(
|
||||
'js-btnRollDungeonEncounter3d8'
|
||||
),
|
||||
rollWildernessEncounter3d4Button = document.getElementById(
|
||||
'js-btnRollWildernessEncounter3d4'
|
||||
),
|
||||
rollWildernessEncounter3d6Button = document.getElementById(
|
||||
'js-btnRollWildernessEncounter3d6'
|
||||
),
|
||||
rollWildernessEncounter3d8Button = document.getElementById(
|
||||
'js-btnRollWildernessEncounter3d8'
|
||||
);
|
||||
|
||||
if (rollDungeonEncounter3d4Button)
|
||||
rollDungeonEncounter3d4Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(4);
|
||||
});
|
||||
if (rollDungeonEncounter3d6Button)
|
||||
rollDungeonEncounter3d6Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(6);
|
||||
});
|
||||
if (rollDungeonEncounter3d8Button)
|
||||
rollDungeonEncounter3d8Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(8);
|
||||
});
|
||||
if (rollWildernessEncounter3d4Button)
|
||||
rollWildernessEncounter3d4Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(4);
|
||||
});
|
||||
if (rollWildernessEncounter3d6Button)
|
||||
rollWildernessEncounter3d6Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(6);
|
||||
});
|
||||
if (rollWildernessEncounter3d8Button)
|
||||
rollWildernessEncounter3d8Button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
rollEncounter(8);
|
||||
});
|
||||
};
|
||||
|
||||
export default (() => {
|
||||
addRollerForm();
|
||||
addRoomForm();
|
||||
addComplicationForm();
|
||||
addAstralComplicationForm();
|
||||
addEncounterRoller();
|
||||
})();
|
||||
|
||||
// @license-end
|
||||
|
@@ -321,6 +321,11 @@ table th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendarWrapper p {
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.calendarWrapper .calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
@@ -366,6 +371,38 @@ table th {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.encounterResultTable {
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.encounterResultTable td {
|
||||
text-align: center;
|
||||
border: 1px dashed #f6bc43;
|
||||
}
|
||||
|
||||
.encounterResultTable th {
|
||||
border: 1px dashed #f6bc43;
|
||||
border-bottom-style: solid;
|
||||
border-collapse: collapse;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.encounterButtonsWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
.encounterButtonsWildernessWrapper {
|
||||
/* margin-left: auto; */
|
||||
border: 1px solid #e94e5c;
|
||||
border-radius: .3rem;
|
||||
padding: .25rem .5rem .5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature hr {
|
||||
border: 1px 0 0 0;
|
||||
border-color: #885c68;
|
||||
|
Reference in New Issue
Block a user