- added damage monitor functionality to track stun/phys damage levels and automatically apply ini penalties accordingly

- mostly working, but there's now way yet to enter/edit damage levels in the add/edit modal
- changed some of the icons
This commit is contained in:
Tobias 2023-02-07 11:29:29 +01:00
parent 380f98fa4d
commit 7d5455fdfd
13 changed files with 189 additions and 39 deletions

View File

@ -8,3 +8,51 @@
input:invalid { input:invalid {
border: 2px solid red; border: 2px solid red;
} }
/* Dropdown Content (Hidden by Default) */
.damage-monitor {
display: none;
position: absolute;
z-index: 20;
top: 40px;
right: -8px;
width: 60px;
}
.damage-monitor th {
width: 30px;
height: 30px;
background-color: white;
border: solid darkgray 1px;
padding: 3px;
}
.damage-monitor td {
width: 30px;
height: 24px;
border: solid darkgray 1px;
font-size: smaller;
user-select: none;
}
.damage-monitor td::selection { background: none; }
.damage-monitor td::-moz-selection { background: none; }
td.damage-stun { background-color: cornflowerblue; }
/*td.damage-stun:hover { background-color: steelblue; }*/
td.damage-stun.selected { background-color: dodgerblue; }
td.damage-physical { background-color: indianred; }
/*td.damage-physical:hover { background-color: coral; }*/
td.damage-physical.selected { background-color: tomato; }
/* bgcolors: default, active, hover
phys: darkred, red, tomato, coral, lightcoral, indianred, orangered
stun: cornflowerblue, lightskyblue, mediumlateblue, steelblue, royalblue
*/
/* Change color of dropdown links on hover */
.damage-monitor td:hover {
background-color: gray;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 B

View File

Before

Width:  |  Height:  |  Size: 576 B

After

Width:  |  Height:  |  Size: 576 B

BIN
img/check.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

View File

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 712 B

View File

Before

Width:  |  Height:  |  Size: 575 B

After

Width:  |  Height:  |  Size: 575 B

BIN
img/explosion.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

View File

Before

Width:  |  Height:  |  Size: 646 B

After

Width:  |  Height:  |  Size: 646 B

BIN
img/skull.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

BIN
img/zzz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

View File

@ -27,18 +27,18 @@
<header class="navbar navbar-expand bg-success bg-opacity-25"> <header class="navbar navbar-expand bg-success bg-opacity-25">
<span class="navbar-brand text-bold ps-4">SR2 Initiative Tracker</span> <span class="navbar-brand text-bold ps-4">SR2 Initiative Tracker</span>
<nav class="container-fluid justify-content-end" aria-label="Main navigation"> <nav class="container-fluid justify-content-end" aria-label="Main navigation">
<button type="submit" class="btn btn-light btn-rounded mx-1 p-1" id="addCombatantButton" title="Add combatant"><img src="img/005-add.png" /></button> <button type="submit" class="btn btn-light btn-rounded mx-1 p-1" id="addCombatantButton" title="Add combatant"><img src="img/add.png" /></button>
<button type="submit" class="btn btn-light btn-rounded mx-1 p-1" id="newRoundButton" data-bs-toggle="modal" data-bs-target="#newRoundModal" title="Begin new round"><img src="img/001-refresh.png" /></button> <button type="submit" class="btn btn-light btn-rounded mx-1 p-1" id="newRoundButton" data-bs-toggle="modal" data-bs-target="#newRoundModal" title="Begin new round"><img src="img/refresh.png" /></button>
</nav> </nav>
</header> </header>
</br> </br>
<div class="table-responsive"> <div class="table-responsive overflow-visible">
<table class="table table-sm" id="combatantsTable"> <table class="table table-sm" id="combatantsTable">
<thead> <thead>
<tr> <tr>
<th class="col-4 text-start" title="Name">Name</th> <th class="col-4 text-start" title="Name">Name</th>
<th class="col-2 text-center" title="Initiative">Ini</th> <th class="col-2 text-center" title="Initiative">Ini</th>
<th class="col-2 text-center" title="Initiative Dice and Reaction"><img src="img/006-dice.png" />+R</th> <th class="col-2 text-center" title="Initiative Dice and Reaction"><img src="img/dice.png" />+R</th>
<th class="col-4 text-center" title="Actions">Actions</th> <th class="col-4 text-center" title="Actions">Actions</th>
</tr> </tr>
</thead> </thead>
@ -81,12 +81,11 @@
<div class="my-2"> <div class="my-2">
<input type="text" maxlength="40" class="form-control form-control-sm" id="combatantModalName" form="combatantModalForm" id="newCombName" placeholder="Name" required /> <input type="text" maxlength="40" class="form-control form-control-sm" id="combatantModalName" form="combatantModalForm" id="newCombName" placeholder="Name" required />
</div> </div>
<div class="my-2"> <div class="input-group my-2">
<div class="input-group"> <input type="number" min="1" max="5" class="form-control form-control-sm" id="combatantModalDice" form="combatantModalForm" placeholder="Dice" />
<input type="number" min="1" max="5" class="form-control form-control-sm" id="combatantModalDice" form="combatantModalForm" placeholder="Dice" /> <span class="input-group-text">D+</span>
<span class="input-group-text">D+</span> <input type="number" min="1" max="25" class="form-control form-control-sm" id="combatantModalRea" form="combatantModalForm" placeholder="REA" />
<input type="number" min="1" max="25" class="form-control form-control-sm" id="combatantModalRea" form="combatantModalForm" placeholder="REA" /> </div>
</div>
<div class="my-2"> <div class="my-2">
<input type="number" min="0" max="55" class="form-control form-control-sm" id="combatantModalIni" form="combatantModalForm" placeholder="Ini" /> <input type="number" min="0" max="55" class="form-control form-control-sm" id="combatantModalIni" form="combatantModalForm" placeholder="Ini" />
</div> </div>

View File

@ -2,6 +2,28 @@
* helper functions * helper functions
*/ */
const penalty = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4];
const damageMonitorHTML = ['<table class="damage-monitor text-center">\n',
'<thead>\n',
'<tr><th title="Stun damage"><img src="img/zzz.png" /></th><th title="Physical Damage"><img src="img/skull.png" /></th></tr>\n',
'</thead>\n',
'<tbody>\n',
'<tr><td class="damage-stun selected" title="0">0</td><td class="damage-physical selected" title="0">0</td></tr>\n',
'<tr><td class="damage-stun" title="-1">L</td><td class="damage-physical" title="-1">L</td></tr>\n',
'<tr><td class="damage-stun" title="-1"></td><td class="damage-physical" title="-1"></td></tr>\n',
'<tr><td class="damage-stun" title="-2">M</td><td class="damage-physical" title="-2">M</td></tr>\n',
'<tr><td class="damage-stun" title="-2"></td><td class="damage-physical" title="-2"></td></tr>\n',
'<tr><td class="damage-stun" title="-2"></td><td class="damage-physical" title="-2"></td></tr>\n',
'<tr><td class="damage-stun" title="-3">S</td><td class="damage-physical" title="-3">S</td></tr>\n',
'<tr><td class="damage-stun" title="-3"></td><td class="damage-physical" title="-3"></td></tr>\n',
'<tr><td class="damage-stun" title="-3"></td><td class="damage-physical" title="-3"></td></tr>\n',
'<tr><td class="damage-stun" title="-3"></td><td class="damage-physical" title="-3"></td></tr>\n',
'<tr><td class="damage-stun" title="knocked out">D</td><td class="damage-physical" title="dead">D</td></tr>\n',
'</tbody>\n',
'</table>'].join("");
// roll for initiative with the given reaction and number of ini dice // roll for initiative with the given reaction and number of ini dice
function rollForInitiative(dice, rea) { function rollForInitiative(dice, rea) {
let ini = 0; let ini = 0;
@ -62,6 +84,28 @@ function sortTable() {
} }
// returns a combatant's effective ini value (modified by wound penalties)
function getEffectiveIni(value, dmgLvl1, dmgLvl2) {
let effectiveIni;
// was function called with 1 argument (tr jQuery object)?
if ( arguments.length == 1 && $(value).is("tr.combatantRow") ) {
let trueIni = parseInt($(value).attr("data-true-ini"));
let dmgStun = parseInt($(value).attr("data-damage-stun")) || 0;
let dmgPhysical = parseInt($(value).attr("data-damage-physical")) || 0;
effectiveIni = trueIni - penalty[dmgStun] - penalty[dmgPhysical];
}
// or with 3 arguments (ini and dmg levels)?
else if ( arguments.length == 3 ) {
effectiveIni = parseInt(value) - penalty[parseInt(dmgLvl1)] - penalty[parseInt(dmgLvl2)];
}
//
else { return NaN; }
console.log("effectiveIni is ", effectiveIni);
return effectiveIni < 0 ? 0 : effectiveIni;
}
/* /*
* Event handler functions * Event handler functions
*/ */
@ -70,13 +114,14 @@ function sortTable() {
function handleActButtonClick (e) { function handleActButtonClick (e) {
// find current table row // find current table row
let $tr = $(e.target).parents(".combatantRow"); let $tr = $(e.target).parents(".combatantRow");
let ini = $tr.find(".combatantIni").text(); let ini = $tr.attr("data-true-ini");
// reduce ini by 10 but not lower than 0 // reduce ini by 10 but not lower than 0
ini = Math.max(parseInt(ini) - 10, 0); ini = Math.max(parseInt(ini) - 10, 0);
// set new ini value // set new ini value
$tr.find(".combatantIni").text(ini); $tr.attr("data-true-ini", ini);
$tr.find(".combatantIni").text(getEffectiveIni($tr));
// resort table // resort table
sortTable(); sortTable();
@ -103,12 +148,49 @@ function handleAddButtonClick (e) {
$("#combatantModal").modal("show"); $("#combatantModal").modal("show");
} }
// click handler for damage buttons
function handleDamageButtonHover (e) { function handleDamageButtonClick (e) {
let display = $(e.target).parents(".damage-dropdown").find(".damage-monitor").css("display");
$(e.target).parents(".damage-dropdown").find(".damage-monitor").css("display", display == "block" ? "none" : "block");
return false;
} }
// click handler for damage monitor fields
function handleDamageMonitorClick (e) {
let $td = $(e.target);
let $tr = $td.parents("tr.combatantRow");
let damageType;
let otherDamageLevel
// calculate new damage level and type
let damageLevel = $td.parent().index();
if ( $td.hasClass("damage-stun") ) {
damageType = "stun";
otherDamageLevel = $tr.attr("data-damage-physical") ? parseInt($tr.attr("data-damage-physical")) : 0;
} else if ( $td.hasClass("damage-physical") ) {
damageType = "physical";
otherDamageLevel = $tr.attr("data-damage-stun") ? parseInt($tr.attr("data-damage-stun")) : 0;
} else {
return false;
}
// add damage level to table row as as data attribute
$tr.attr("data-damage-" + damageType, damageLevel);
// select/unselect damage boxes
$td.addClass("selected");
$td.parent().nextAll().children("td.damage-" + damageType).removeClass("selected");
$td.parent().prevAll().children("td.damage-" + damageType).addClass("selected");
// recalculate effective ini and resort
$tr.find(".combatantIni").text(getEffectiveIni($tr));
sortTable();
return false;
}
// click handler for edit buttons // click handler for edit buttons
function handleEditButtonClick (e) { function handleEditButtonClick (e) {
// find current table row // find current table row
@ -123,7 +205,8 @@ function handleEditButtonClick (e) {
$("#combatantModalName").val($tr.find(".combatantName").text()); $("#combatantModalName").val($tr.find(".combatantName").text());
$("#combatantModalDice").val($tr.find(".combatantDice").text()); $("#combatantModalDice").val($tr.find(".combatantDice").text());
$("#combatantModalRea").val($tr.find(".combatantRea").text()); $("#combatantModalRea").val($tr.find(".combatantRea").text());
$("#combatantModalIni").val($tr.find(".combatantIni").text()); $("#combatantModalIni").val($tr.attr("data-true-ini"));
//TODO: show effective ini in modal
// mark which row is being edited // mark which row is being edited
$("#combatantModal").attr("data-row", $(".combatantRow").index($tr)); $("#combatantModal").attr("data-row", $(".combatantRow").index($tr));
@ -223,34 +306,48 @@ function addCombatant (e) {
let ini = $("#combatantModalIni").val().trim(); let ini = $("#combatantModalIni").val().trim();
let dice = $("#combatantModalDice").val().trim(); let dice = $("#combatantModalDice").val().trim();
let rea = $("#combatantModalRea").val().trim(); let rea = $("#combatantModalRea").val().trim();
//TODO: retrieve initial damage levels
// roll for initiative if ini is empty // roll for initiative if necessary
ini = (ini != "") ? ini : rollForInitiative(dice, rea); ini = (ini != "") ? ini : rollForInitiative(dice, rea);
// TODO: actually calculate effective ini
let effectiveIni = getEffectiveIni(ini, 0, 0);
console.log("effective ini = ", effectiveIni);
// construct jQuery object for table row // construct jQuery object for table row
let $tr = $($.parseHTML( [ let $tr = $($.parseHTML( [
'<tr class="combatantRow align-middle">\n', '<tr class="combatantRow align-middle" data-true-ini="', ini, '">\n', //TODO: add data-damage-* attributes with initial damage levels
'<td class="combatantName" title="Combatant\'s name">', name, '</td>\n', '<td class="combatantName" title="Combatant\'s name">', name, '</td>\n',
'<td class="combatantIni text-center" title="Initiative">', ini, '</td>\n', '<td class="combatantIni text-center" title="Initiative">', effectiveIni, '</td>\n',
'<td class="text-center combatantDiceAndRea" title="Iniative dice and reaction"><span class="combatantDice">', dice, '</span>D+<span class="combatantRea">', rea, '</span></td>\n', '<td class="text-center combatantDiceAndRea" title="Iniative dice and reaction"><span class="combatantDice">', dice, '</span>D+<span class="combatantRea">', rea, '</span></td>\n',
'<td class="text-end">\n', '<td class="text-end">\n',
'<div class="btn-group">\n', '<div class="btn-group">\n',
'<button type="button" class="btn btn-light btn-rounded mx-1 p-1 edit-button" title="Edit combatant\'s values"><img src="img/004-edit-button.png" /></button>\n', '<button type="button" class="btn btn-light btn-rounded mx-1 p-1 edit-button" title="Edit combatant\'s values"><img src="img/edit.png" /></button>\n',
'<button type="button" class="btn btn-light btn-rounded mx-1 p-1 act-button" title="Act and reduce ini by 10"><img src="img/003-explosion.png" /></button>\n', '<button type="button" class="btn btn-light btn-rounded mx-1 p-1 act-button" title="Act and reduce ini by 10"><img src="img/check.png" /></button>\n',
'<button type="button" class="btn btn-light btn-rounded mx-1 p-1 remove-button" title="Remove combatant"><img src="img/002-band-aid.png" /></button>\n', '<div class="damage-dropdown">\n',
'</div>\n', '<button type="button" class="btn btn-light btn-rounded mx-1 p-1 damage-button" title="Take damage"><img src="img/explosion.png" /></button>\n',
'</td>\n', damageMonitorHTML + "\n",
'</tr>'].join("") '</div>\n',
'</div>\n',
'</td>\n',
'</tr>'].join("")
)); ));
//TODO: mark initial damage levels with .selected class
// add handlers to table row buttons // add handlers to table row buttons
$tr.find("button.edit-button").on("click", handleEditButtonClick); $tr.find("button.edit-button").on("click", handleEditButtonClick);
$tr.find("button.act-button").on("click", handleActButtonClick); $tr.find("button.act-button").on("click", handleActButtonClick);
$tr.find("button.remove-button").on("click", handleRemoveButtonClick); $tr.find("button.remove-button").on("click", handleRemoveButtonClick);
$tr.find("button.damage-button").on("click", handleDamageButtonClick);
// add handlers to table cells (click to edit) // add handler to table cells (click to edit)
$tr.find(".combatantName, .combatantIni, .combatantDiceAndRea").on("click", handleEditButtonClick); $tr.find(".combatantName, .combatantIni, .combatantDiceAndRea").on("click", handleEditButtonClick);
// add handler to damage monitor
$tr.find(".damage-stun, .damage-physical").on("click", handleDamageMonitorClick);
// add row to table and sort // add row to table and sort
$("#combatantsTable").append($tr); $("#combatantsTable").append($tr);
sortTable(); sortTable();
@ -286,7 +383,8 @@ function editCombatant (e) {
$tr.find(".combatantName").text(name); $tr.find(".combatantName").text(name);
$tr.find(".combatantDice").text(dice); $tr.find(".combatantDice").text(dice);
$tr.find(".combatantRea").text(rea); $tr.find(".combatantRea").text(rea);
$tr.find(".combatantIni").text(ini); $tr.attr("data-true-ini", ini);
$tr.find(".combatantIni").text(getEffectiveIni($tr));
// sort table // sort table
sortTable(); sortTable();
@ -305,13 +403,14 @@ function newRound() {
// reset ini values // reset ini values
$(".combatantRow").each( function() { $(".combatantRow").each( function() {
let $ini = $(this).find(".combatantIni"); let effectiveIni = $(this).find(".combatantIni").text();
let $dice = $(this).find(".combatantDice"); let dice = $(this).find(".combatantDice").text();
if ( $dice.text() == "" ) { if ( dice == "" ) {
$ini.text(1); $(this).attr("data-true-ini", "1");
} else { } else {
$ini.text(rollForInitiative($dice.text(), $(this).find(".combatantRea").text())); $(this).attr("data-true-ini", rollForInitiative(dice, $(this).find(".combatantRea").text()));
} }
$(this).find(".combatantIni").text(getEffectiveIni($(this)));
}); });
// resort table // resort table
@ -348,12 +447,16 @@ $(document).ready(function(){
// always focus name input field when combatant modal appears // always focus name input field when combatant modal appears
$('#combatantModal').on('shown.bs.modal', function() { $('#combatantModal').on('shown.bs.modal', function() {
$('#combatantModalName').focus(); $('#combatantModalName').focus();
}) });
// always empty input fields when combatant modal disappears // always empty input fields when combatant modal disappears
$("#combatantModal").on('hidden.bs.modal', function (e) { $("#combatantModal").on('hidden.bs.modal', function (e) {
$("#combatantModal input[id*='combatantModal']").val(""); $("#combatantModal input[id*='combatantModal']").val("");
}) });
// Hide damage monitors if mouse is clicked outside
$("html").on("click", function(e) {
$(".damage-monitor:visible").css("display", "none");
});
addTestCombatant(); addTestCombatant();