361 lines
10 KiB
JavaScript
361 lines
10 KiB
JavaScript
/*
|
||
* helper functions
|
||
*/
|
||
|
||
// roll for initiative with the given reaction and number of ini dice
|
||
function rollForInitiative(dice, rea) {
|
||
let ini = 0;
|
||
for ( let i = 0; i < parseInt(dice); i++ ) {
|
||
roll = Math.floor(Math.random() * 6) + 1;
|
||
ini += roll;
|
||
}
|
||
return ini + parseInt(rea);
|
||
}
|
||
|
||
|
||
// figure out whose action comes first out of two combatants a and b
|
||
function whoGoesFirst(a, b) {
|
||
let comparer = parseInt($(b).find(".combatantIni").text()) - parseInt($(a).find(".combatantIni").text());
|
||
if (comparer != 0) {
|
||
return comparer;
|
||
} else {
|
||
let reaA = parseInt($(a).find(".combatantRea").text());
|
||
let reaB = parseInt($(b).find(".combatantRea").text());
|
||
reaA = isNaN(reaA) ? 0 : reaA;
|
||
if (isNaN(reaB)) { reaB = 0; }
|
||
console.log(reaA, reaB);
|
||
return reaB - reaA;
|
||
}
|
||
}
|
||
|
||
|
||
// sorts the combatants by ini value
|
||
function sortTable() {
|
||
// sort rows and append them in new order
|
||
let $rows = $(".combatantRow").toArray().sort(whoGoesFirst);
|
||
for ( var i = 0; i < $rows.length; i++ ) {
|
||
$("#combatantsTable").append($rows[i]);
|
||
}
|
||
|
||
// add contectual classes to rows – currently for highest ini and ini = 0
|
||
// compute highest ini
|
||
let iniValues = $.map( $(".combatantIni"), function(td, i) {
|
||
return parseInt($(td).text());
|
||
});
|
||
let iniMax = Math.max.apply(null, iniValues);
|
||
|
||
// add contextual classes to rows
|
||
$(".combatantRow").each( function() {
|
||
// always remove previous classes, disable act button
|
||
$(this).removeClass("table-primary table-secondary").find(".act-button").prop("disabled", true).attr("aria-disabled", "true");
|
||
// add class if ini is zero
|
||
if ( parseInt($(this).find(".combatantIni").text()) == 0 ) {
|
||
$(this).addClass("table-secondary");
|
||
}
|
||
// add class, enable act button if ini is max and non-zero
|
||
else if ( parseInt($(this).find(".combatantIni").text()) == iniMax && iniMax > 0 ) {
|
||
$(this).addClass("table-primary").find(".act-button").prop("disabled", false).removeAttr("aria-disabled");
|
||
}
|
||
})
|
||
|
||
return;
|
||
}
|
||
|
||
|
||
/*
|
||
* Event handler functions
|
||
*/
|
||
|
||
// click handler for act buttons
|
||
function handleActButtonClick (e) {
|
||
// find current table row
|
||
let $tr = $(e.target).parents(".combatantRow");
|
||
let ini = $tr.find(".combatantIni").text();
|
||
|
||
// reduce ini by 10 but not lower than 0
|
||
ini = Math.max(parseInt(ini) - 10, 0);
|
||
|
||
// set new ini value
|
||
$tr.find(".combatantIni").text(ini);
|
||
|
||
// resort table
|
||
sortTable();
|
||
}
|
||
|
||
|
||
// click handler for add buttons
|
||
function handleAddButtonClick (e) {
|
||
|
||
// restyle modal
|
||
$("#combatantModal .modal-title").text("Add Combatant");
|
||
$("#combatantModalAddOkButton").show();
|
||
$("#combatantModalEditOkButton").hide();
|
||
|
||
// add handler for enter key
|
||
$("#combatantModal input[id*='combatantModal']").off("keydown");
|
||
$("#combatantModal input[id*='combatantModal']").on("keydown", function (e) {
|
||
if ( e.which == 13 || e.which == 10 ) {
|
||
addCombatant(e);
|
||
}
|
||
});
|
||
|
||
// show modal
|
||
$("#combatantModal").modal("show");
|
||
}
|
||
|
||
|
||
function handleDamageButtonHover (e) {
|
||
|
||
}
|
||
|
||
|
||
// click handler for edit buttons
|
||
function handleEditButtonClick (e) {
|
||
// find current table row
|
||
let $tr = $(e.target).parents(".combatantRow");
|
||
|
||
// restyle modal
|
||
$("#combatantModal .modal-title").text("Edit Combatant");
|
||
$("#combatantModalAddOkButton").hide();
|
||
$("#combatantModalEditOkButton").show();
|
||
|
||
// populate modal with values from row
|
||
$("#combatantModalName").val($tr.find(".combatantName").text());
|
||
$("#combatantModalDice").val($tr.find(".combatantDice").text());
|
||
$("#combatantModalRea").val($tr.find(".combatantRea").text());
|
||
$("#combatantModalIni").val($tr.find(".combatantIni").text());
|
||
|
||
// mark which row is being edited
|
||
$("#combatantModal").attr("data-row", $(".combatantRow").index($tr));
|
||
|
||
// add handler for enter key
|
||
$("#combatantModal input[id*='combatantModal']").off("keydown");
|
||
$("#combatantModal input[id*='combatantModal']").on("keydown", function (e) {
|
||
if ( e.which == 13 || e.which == 10 ) {
|
||
editCombatant(e);
|
||
}
|
||
});
|
||
|
||
// show modal
|
||
$("#combatantModal").modal("show");
|
||
}
|
||
|
||
|
||
// click handler for remove buttons
|
||
function handleRemoveButtonClick (e) {
|
||
// remove table row
|
||
$(e.target).parents(".combatantRow").remove();
|
||
}
|
||
|
||
|
||
/*
|
||
* Validation functions
|
||
*/
|
||
|
||
// validate a combatant row form by checking for all conditions, including regular HTML5 validation
|
||
function validateCombatant() {
|
||
|
||
// get input elements
|
||
let inputElements = {
|
||
name: $("#combatantModalName").get(0),
|
||
ini: $("#combatantModalIni").get(0),
|
||
dice: $("#combatantModalDice").get(0),
|
||
rea: $("#combatantModalRea").get(0)
|
||
};
|
||
|
||
// do standard HTML5 form validation first
|
||
// (makes sure that name is not empty and that all other values are numbers within their individual ranges)
|
||
let valid = true;
|
||
Object.values(inputElements).forEach(function(input) {
|
||
if ( ! input.reportValidity() ) {
|
||
valid = false;
|
||
}
|
||
})
|
||
if ( ! valid ) {
|
||
return false;
|
||
}
|
||
|
||
// now for some custom validation; first we need to get the input values
|
||
let ini = inputElements["ini"].value.trim();
|
||
let dice = inputElements["dice"].value.trim();
|
||
let rea = inputElements["rea"].value.trim();
|
||
|
||
// invalidate if ini, dice and rea are all empty
|
||
if ( ini == "" && ( dice == "" || rea == "" ) ) {
|
||
inputElements["ini"].setCustomValidity("Requiring values for ini dice and reaction, or initiative, or all three");
|
||
inputElements["ini"].reportValidity();
|
||
inputElements["ini"].setCustomValidity("");
|
||
return false;
|
||
}
|
||
|
||
// invalidate if dice or rea is empty but not both
|
||
if ( ( dice == "" ) != ( rea == "" ) ) {
|
||
inputElements["dice"].setCustomValidity("Values required for both dice and reaction, or none (in which case ini is required)");
|
||
inputElements["dice"].reportValidity();
|
||
inputElements["dice"].setCustomValidity("");
|
||
return false;
|
||
}
|
||
|
||
// ok then
|
||
return true;
|
||
}
|
||
|
||
|
||
/*
|
||
* Main functions
|
||
*/
|
||
|
||
// add new combatant
|
||
function addCombatant (e) {
|
||
|
||
e.preventDefault();
|
||
|
||
// validate form
|
||
if ( ! validateCombatant() ) {
|
||
return false;
|
||
}
|
||
|
||
// hide modal
|
||
$("#combatantModal").modal("hide");
|
||
|
||
// get values
|
||
let name = $("#combatantModalName").val().trim();
|
||
let ini = $("#combatantModalIni").val().trim();
|
||
let dice = $("#combatantModalDice").val().trim();
|
||
let rea = $("#combatantModalRea").val().trim();
|
||
|
||
// roll for initiative if ini is empty
|
||
ini = (ini != "") ? ini : rollForInitiative(dice, rea);
|
||
|
||
// construct jQuery object for table row
|
||
let $tr = $($.parseHTML( [
|
||
'<tr class="combatantRow align-middle">\n',
|
||
'<td class="combatantName" title="Combatant\'s name">', name, '</td>\n',
|
||
'<td class="combatantIni text-center" title="Initiative">', ini, '</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',
|
||
'<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 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 remove-button" title="Remove combatant"><img src="img/002-band-aid.png" /></button>\n',
|
||
'</div>\n',
|
||
'</td>\n',
|
||
'</tr>'].join("")
|
||
));
|
||
|
||
// add handlers to table row buttons
|
||
$tr.find("button.edit-button").on("click", handleEditButtonClick);
|
||
$tr.find("button.act-button").on("click", handleActButtonClick);
|
||
$tr.find("button.remove-button").on("click", handleRemoveButtonClick);
|
||
|
||
// add handlers to table cells (click to edit)
|
||
$tr.find(".combatantName, .combatantIni, .combatantDiceAndRea").on("click", handleEditButtonClick);
|
||
|
||
// add row to table and sort
|
||
$("#combatantsTable").append($tr);
|
||
sortTable();
|
||
}
|
||
|
||
|
||
// edit combatant values
|
||
function editCombatant (e) {
|
||
e.preventDefault();
|
||
|
||
// validate form
|
||
if ( ! validateCombatant() ) {
|
||
return false;
|
||
}
|
||
|
||
// hide modal
|
||
$("#combatantModal").modal("hide");
|
||
|
||
// get values
|
||
let name = $("#combatantModalName").val().trim();
|
||
let ini = $("#combatantModalIni").val().trim();
|
||
let dice = $("#combatantModalDice").val().trim();
|
||
let rea = $("#combatantModalRea").val().trim();
|
||
|
||
// roll for initiative if ini is empty
|
||
ini = (ini != "") ? ini : rollForInitiative(dice, rea);
|
||
|
||
// get correct row
|
||
let index = parseInt($("#combatantModal").attr("data-row"));
|
||
$tr = $("tr.combatantRow").eq(index);
|
||
|
||
// set new values
|
||
$tr.find(".combatantName").text(name);
|
||
$tr.find(".combatantDice").text(dice);
|
||
$tr.find(".combatantRea").text(rea);
|
||
$tr.find(".combatantIni").text(ini);
|
||
|
||
// sort table
|
||
sortTable();
|
||
|
||
// clean up
|
||
$("#combatantModal").removeAttr("data-row");
|
||
}
|
||
|
||
|
||
// start a new combat round
|
||
function newRound() {
|
||
// are there rows at all?
|
||
if ( $(".combatantRow").length == 0 ) {
|
||
return;
|
||
}
|
||
|
||
// reset ini values
|
||
$(".combatantRow").each( function() {
|
||
let $ini = $(this).find(".combatantIni");
|
||
let $dice = $(this).find(".combatantDice");
|
||
if ( $dice.text() == "" ) {
|
||
$ini.text(1);
|
||
} else {
|
||
$ini.text(rollForInitiative($dice.text(), $(this).find(".combatantRea").text()));
|
||
}
|
||
});
|
||
|
||
// resort table
|
||
sortTable();
|
||
}
|
||
|
||
|
||
// add test combatant for testing purposes (duh)
|
||
function addTestCombatant() {
|
||
$("#addCombatantButton").click();
|
||
$("#combatantModalName").val("Goon1");
|
||
$("#combatantModalDice").val(2);
|
||
$("#combatantModalRea").val(6);
|
||
$("#combatantModalIni").val(12);
|
||
setTimeout(function(){
|
||
$("#combatantModalAddOkButton").click();
|
||
},500);
|
||
}
|
||
|
||
|
||
/*
|
||
* Initialize document
|
||
*/
|
||
|
||
$(document).ready(function(){
|
||
// add event handlers to navbar buttons
|
||
$("#addCombatantButton").on("click", handleAddButtonClick);
|
||
$("#newroundModalOkButton").on("click", newRound);
|
||
|
||
// add event handlers to modal buttons
|
||
$("#combatantModalAddOkButton").on("click", addCombatant);
|
||
$("#combatantModalEditOkButton").on("click", editCombatant);
|
||
|
||
// always focus name input field when combatant modal appears
|
||
$('#combatantModal').on('shown.bs.modal', function() {
|
||
$('#combatantModalName').focus();
|
||
})
|
||
|
||
// always empty input fields when combatant modal disappears
|
||
$("#combatantModal").on('hidden.bs.modal', function (e) {
|
||
$("#combatantModal input[id*='combatantModal']").val("");
|
||
})
|
||
|
||
addTestCombatant();
|
||
|
||
});
|