add dinosaurs, dragons, and werebeasts

add some more campaign info
add encounter generators (alpha)
This commit is contained in:
2024-04-27 17:09:53 -04:00
parent f9a3003d70
commit a58d40278b
86 changed files with 2354 additions and 345 deletions

View 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>

View 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>

View 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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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;