working new plugin

This commit is contained in:
Fantino Davide 2022-08-13 12:05:40 +02:00
parent 42c1552f0c
commit 0721661af3
8 changed files with 196 additions and 1296 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,333 +0,0 @@
/*
Viz.js 2.1.2 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36)
Copyright (c) 2014-2018 Michael Daines
Licensed under MIT license
This distribution contains other software in object code form:
Graphviz
Licensed under Eclipse Public License - v 1.0
http://www.graphviz.org
Expat
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.
Licensed under MIT license
http://www.libexpat.org
zlib
Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
http://www.zlib.net/zlib_license.html
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Viz = factory());
}(this, (function () { 'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var WorkerWrapper = function () {
function WorkerWrapper(worker) {
var _this = this;
classCallCheck(this, WorkerWrapper);
this.worker = worker;
this.listeners = [];
this.nextId = 0;
this.worker.addEventListener('message', function (event) {
var id = event.data.id;
var error = event.data.error;
var result = event.data.result;
_this.listeners[id](error, result);
delete _this.listeners[id];
});
}
createClass(WorkerWrapper, [{
key: 'render',
value: function render(src, options) {
var _this2 = this;
return new Promise(function (resolve, reject) {
var id = _this2.nextId++;
_this2.listeners[id] = function (error, result) {
if (error) {
reject(new Error(error.message, error.fileName, error.lineNumber));
return;
}
resolve(result);
};
_this2.worker.postMessage({ id: id, src: src, options: options });
});
}
}]);
return WorkerWrapper;
}();
var ModuleWrapper = function ModuleWrapper(module, render) {
classCallCheck(this, ModuleWrapper);
var instance = module();
this.render = function (src, options) {
return new Promise(function (resolve, reject) {
try {
resolve(render(instance, src, options));
} catch (error) {
reject(error);
}
});
};
};
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
function defaultScale() {
if ('devicePixelRatio' in window && window.devicePixelRatio > 1) {
return window.devicePixelRatio;
} else {
return 1;
}
}
function svgXmlToImageElement(svgXml) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$scale = _ref.scale,
scale = _ref$scale === undefined ? defaultScale() : _ref$scale,
_ref$mimeType = _ref.mimeType,
mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType,
_ref$quality = _ref.quality,
quality = _ref$quality === undefined ? 1 : _ref$quality;
return new Promise(function (resolve, reject) {
var svgImage = new Image();
svgImage.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = svgImage.width * scale;
canvas.height = svgImage.height * scale;
var context = canvas.getContext("2d");
context.drawImage(svgImage, 0, 0, canvas.width, canvas.height);
canvas.toBlob(function (blob) {
var image = new Image();
image.src = URL.createObjectURL(blob);
image.width = svgImage.width;
image.height = svgImage.height;
resolve(image);
}, mimeType, quality);
};
svgImage.onerror = function (e) {
var error;
if ('error' in e) {
error = e.error;
} else {
error = new Error('Error loading SVG');
}
reject(error);
};
svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml);
});
}
function svgXmlToImageElementFabric(svgXml) {
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref2$scale = _ref2.scale,
scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale,
_ref2$mimeType = _ref2.mimeType,
mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType,
_ref2$quality = _ref2.quality,
quality = _ref2$quality === undefined ? 1 : _ref2$quality;
var multiplier = scale;
var format = void 0;
if (mimeType == 'image/jpeg') {
format = 'jpeg';
} else if (mimeType == 'image/png') {
format = 'png';
}
return new Promise(function (resolve, reject) {
fabric.loadSVGFromString(svgXml, function (objects, options) {
// If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one <g> element back even given an empty graph, so we will assume an error in this case.
if (objects.length == 0) {
reject(new Error('Error loading SVG with Fabric'));
}
var element = document.createElement("canvas");
element.width = options.width;
element.height = options.height;
var canvas = new fabric.Canvas(element, { enableRetinaScaling: false });
var obj = fabric.util.groupSVGElements(objects, options);
canvas.add(obj).renderAll();
var image = new Image();
image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality });
image.width = options.width;
image.height = options.height;
resolve(image);
});
});
}
var Viz = function () {
function Viz() {
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
workerURL = _ref3.workerURL,
worker = _ref3.worker,
Module = _ref3.Module,
render = _ref3.render;
classCallCheck(this, Viz);
if (typeof workerURL !== 'undefined') {
this.wrapper = new WorkerWrapper(new Worker(workerURL));
} else if (typeof worker !== 'undefined') {
this.wrapper = new WorkerWrapper(worker);
} else if (typeof Module !== 'undefined' && typeof render !== 'undefined') {
this.wrapper = new ModuleWrapper(Module, render);
} else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') {
this.wrapper = new ModuleWrapper(Viz.Module, Viz.render);
} else {
throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.');
}
}
createClass(Viz, [{
key: 'renderString',
value: function renderString(src) {
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref4$format = _ref4.format,
format = _ref4$format === undefined ? 'svg' : _ref4$format,
_ref4$engine = _ref4.engine,
engine = _ref4$engine === undefined ? 'dot' : _ref4$engine,
_ref4$files = _ref4.files,
files = _ref4$files === undefined ? [] : _ref4$files,
_ref4$images = _ref4.images,
images = _ref4$images === undefined ? [] : _ref4$images,
_ref4$yInvert = _ref4.yInvert,
yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert,
_ref4$nop = _ref4.nop,
nop = _ref4$nop === undefined ? 0 : _ref4$nop;
for (var i = 0; i < images.length; i++) {
files.push({
path: images[i].path,
data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>'
});
}
return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert, nop: nop });
}
}, {
key: 'renderSVGElement',
value: function renderSVGElement(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
var parser = new DOMParser();
return parser.parseFromString(str, 'image/svg+xml').documentElement;
});
}
}, {
key: 'renderImageElement',
value: function renderImageElement(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var scale = options.scale,
mimeType = options.mimeType,
quality = options.quality;
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) {
return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality });
} else {
return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality });
}
});
}
}, {
key: 'renderJSONObject',
value: function renderJSONObject(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var format = options.format;
if (format !== 'json' || format !== 'json0') {
format = 'json';
}
return this.renderString(src, _extends({}, options, { format: format })).then(function (str) {
return JSON.parse(str);
});
}
}]);
return Viz;
}();
return Viz;
})));

View File

@ -1 +0,0 @@
{"modes":["AAS","RAAS","Invasion"],"rules":{"default":[{"map":"Yehorivka","modes":["Any"],"versions":["Any"]},{"map":"Chora","modes":["Any"],"versions":["Any"]},{"map":"Narva","modes":["Any"],"versions":["Any"]}],"Albasrah":[{"map":"Mutaha","modes":["Any"],"versions":["Any"]},{"map":"Skorpo","modes":["RAAS"],"versions":["v2"]},{"map":"Narva","modes":["Any"],"versions":["Any"]}],"Anvil":[{"map":"Mutaha","modes":["Any"],"versions":["Any"]},{"map":"Logar","modes":["Any"],"versions":["Any"]},{"map":"FoolsRoad","modes":["Any"],"versions":["Any"]}],"Belaya":[{"map":"Sumari","modes":["Any"],"versions":["Any"]},{"map":"Mutaha","modes":["Any"],"versions":["Any"]},{"map":"Narva","modes":["Any"],"versions":["Any"]}],"Fallujah":[{"map":"Yehorivka","modes":["RAAS"],"versions":["v4"]},{"map":"Narva","modes":["Any"],"versions":["Any"]},{"map":"Mestia","modes":["AAS","RAAS"],"versions":["Any"]}],"Gorodok":[{"map":"Kokan","modes":["RAAS"],"versions":["Any"]},{"map":"Logar","modes":["AAS","RAAS"],"versions":["Any"]},{"map":"Mestia","modes":["Any"],"versions":["Any"]}],"Kamdesh":[{"map":"Gorodok","modes":["Any"],"versions":["Any"]},{"map":"Skorpo","modes":["Any"],"versions":["Any"]}],"Kokan":[{"map":"Fallujah","modes":["Any"],"versions":["Any"]},{"map":"Belaya","modes":["Any"],"versions":["Any"]},{"map":"Logar","modes":["Any"],"versions":["Any"]}],"LashkarValley":[{"map":"Mestia","modes":["Any"],"versions":["Any"]},{"map":"Kokan","modes":["Any"],"versions":["Any"]},{"map":"FoolsRoad","modes":["Any"],"versions":["Any"]}],"Narva":[{"map":"Kohat","modes":["Any"],"versions":["Any"]},{"map":"Tallil","modes":["Any"],"versions":["Any"]},{"map":"Albasrah","modes":["Invasion"],"versions":["v2"]}],"Skorpo":[{"map":"Chora","modes":["Any"],"versions":["Any"]},{"map":"Mutaha","modes":["Any"],"versions":["Any"]},{"map":"Kokan","modes":["AAS","RAAS"],"versions":["Any"]}],"Sumari":[{"map":"Yehorivka","modes":["Any"],"versions":["Any"]},{"map":"Belaya","modes":["Any"],"versions":["Any"]},{"map":"CAF_Manic","modes":["Any"],"versions":["Any"]}],"Tallil":[{"map":"FoolsRoad","modes":["Any"],"versions":["Any"]},{"map":"CAF_Manic","modes":["AAS","RAAS"],"versions":["Any"]},{"map":"Mestia","modes":["AAS","RAAS"],"versions":["Any"]}],"Yehorivka":[{"map":"Yehorivka","modes":["Any"],"versions":["Any"]},{"map":"Narva","modes":["AAS","RAAS"],"versions":["Any"]},{"map":"Mutaha","modes":["Any"],"versions":["Any"]}],"Chora":[{"map":"Gorodok","modes":["Any"],"versions":["Any"]},{"map":"CAF_Manic","modes":["AAS","RAAS"],"versions":["Any"]},{"map":"Anvil","modes":["RAAS","Invasion"],"versions":["Any"]}],"Kohat":[{"map":"Gorodok","modes":["Any"],"versions":["Any"]},{"map":"Yehorivka","modes":["Any"],"versions":["Any"]},{"map":"Kokan","modes":["Any"],"versions":["Any"]}],"Logar":[{"map":"Tallil","modes":["Any"],"versions":["Any"]},{"map":"CAF_GooseBay","modes":["Any"],"versions":["Any"]},{"map":"Kohat","modes":["Any"],"versions":["Any"]}]},"addon_map_layer_strings":[], "mode_repeat_blacklist":[]}

View File

@ -1,47 +0,0 @@
{
"modes":
[
"AAS",
"RAAS",
"Invasion"
],
"mode_repeat_blacklist":
[
"Invasion"
],
"rules":
{
"Gorodok":
[
{"map": "Yehorivka", "modes": ["Any"], "versions": ["Any"]},
{"map": "Gorodok", "modes": ["Any"], "versions": ["Any"]}
],
"Gorodok_Invasion":
[
{"map": "Yehorivka", "modes": ["Any"], "versions": ["Any"]}
],
"Yehorivka_RAAS_v1":
[
{"map": "Gorodok", "modes": ["AAS"], "versions": ["Any"]}
],
"default":
[
{"map": "Yehorivka", "modes": ["Any"], "versions": ["Any"]},
{"map": "Gorodok", "modes": ["Any"], "versions": ["Any"]}
]
},
"addon_map_layer_strings":
{
"example":
[
"string1",
"string2"
]
}
}

View File

@ -1,507 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Map vote plugin configuration generator</title>
<style>
*
{
background-color: lightgray;
}
div#main
{
display: flex;
justify-content: space-around;
margin: auto;
}
div.pannel
{
border: black;
border-radius: 15px;
border-style: solid;
padding: 10px;
padding-bottom: 30px;
}
li.rule
{
border: 1px solid black;
border-radius: 10px;
padding: 5px;
margin: 2px;
}
ul.rules
{
margin: 5px;
padding: 2px;
overflow: clip;
overflow-y: scroll;
width: 82ch;
height: 100ch;
border: solid black;
}
ul.nomination_list
{
list-style-type: none;
padding: 2px;
margin-top: 2px;
margin-bottom: 2px;
width: 75ch;
min-height: 5ch;
border: solid black;
}
ul.border_list
{
list-style-type: none;
margin: 5px;
padding: 2px;
overflow: clip;
overflow-y: scroll;
width: 75ch;
height: 10ch;
border: solid black;
}
li.selected
{
background-color: cadetblue;
}
div#info
{
margin: 2%;
}
li.nomination
{
border: 1px solid black;
margin: 5px;
padding: 5px;
}
input.short_input
{
width: 10ch;
}
input.rule_input
{
width: 30ch;
}
.graph_container
{
overflow: hidden;
}
</style>
</head>
<body>
<div id="main">
<div id="left" class="pannel">
<div id="controls">
<button id="save">save</button>
<input type="file" id="load" accept=".json">
</div>
<div id="rules_pannel">
<h3>vote rules:</h3>
<button id="add_rule">add rule</button>
<ul id="rules_list" class="rules"></ul>
</div>
<div id="modes">
<h3>mode strings (case sensitive):</h3>
<ul id="modes_list" class="border_list"></ul>
<input type="text" id="mode_input">
<button id="add_mode">add</button>
<button id="remove_mode">remove</button>
</div>
<div id="addons">
<h3>addon layer strings:</h3>
(not currently implemented)
<ul id="addons_list"></ul>
<input type="text">
<button>add</button>
<button>remove</button>
</div>
<div id="blacklist">
<h3>mode repeat blacklist strings (case sensitive):</h3>
<ul id="blacklist_list" class="border_list"></ul>
<input type="text" id="blacklist_input">
<button id="add_blacklist">add</button>
<button id="remove_blacklist">remove</button>
</div>
</div>
<div id="info">
<button onclick="render_graph()">render graph</button>
<div class="pannel" style="width: 95%;">
<div id="graph_container" class="graph_container"></div>
</div>
<h3>info:</h3>
tool by maskedmonkyman <br>
<h4>warning:</h4>
<p>
this tool is a thin wrapper around
the Squad JS pluggin config schemea. This tool
does <b>NO INPUT VALIDATION</b> and is just a graphical utility.
It is very possible to create broken configs using
this tool. Be sure to check your inputs.
</p>
<h4>notes:</h4>
<ol>
<li>
be sure to always have a "defualt" vote rule.
</li>
<li>
the Any option for modes, will select modes from the
mode strings section so be sure to fill it out.
</li>
<li>
The plugin looks for maps from specific to general so if the
current layer is Yehorivka_RAAS_v1 then the plugin will look for rules: <br>
Yehorivka_RAAS_v1 <br>
Yehorivka_RAAS <br>
Yehorivka <br>
defualt <br>
then load from the first one it finds
</li>
</ol>
</p>
</div>
</div>
<script src="configTool/viz.js"></script>
<script src="configTool/full.render.js"></script>
<script src="configTool/svg-pan-zoom.min.js" type="text/javascript" charset="utf-8"></script>
<script>
"use strict";
//TODO addon layers
const save_button = document.getElementById("save");
const load_button = document.getElementById("load");
//rules
const add_rule_button = document.getElementById("add_rule");
const remove_rule_button = document.getElementById("remove_rule");
const rules_list = document.getElementById("rules_list");
let selected_rule = null;
//mode
const add_mode_button = document.getElementById("add_mode");
const remove_mode_button = document.getElementById("remove_mode");
const mode_input = document.getElementById("mode_input");
const modes_list = document.getElementById("modes_list");
//blacklist
const add_blacklist_button = document.getElementById("add_blacklist");
const remove_blacklist_button = document.getElementById("remove_blacklist");
const blacklist_input = document.getElementById("blacklist_input");
const blacklist_list = document.getElementById("blacklist_list");
let selected_mode = null;
let selected_blacklist_item = null;
let svg_element = null;
function fetch_layer_strings()
{
const wiki_url = "https://raw.githubusercontent.com/Squad-Wiki-Editorial/squad-wiki-pipeline-map-data/master/completed_output/_Current%20Version/finished.json";
let layer_strings = [];
fetch(wiki_url).then(response=>response.json()).then(function(response){
for(const layer of response.Maps)
layer_strings.push(layer.rawName);
});
return layer_strings;
}
function render_graph()
{
const graph = build_graph_string(build_vote_rules());
//let layer_strings = fetch_layer_strings();
var viz = new Viz();
if (svg_element)
svg_element.parentNode.removeChild(svg_element);
viz.renderSVGElement(graph, {"engine" : "circo"})
.then(function(element) {
svg_element = element;
//set svg to the size of it's container
svg_element.setAttribute("width", "100%");
//remove height initially to auto scale
svg_element.removeAttribute("height");
document.getElementById("graph_container").appendChild(svg_element);
let panZoom = svgPanZoom(svg_element, {
zoomEnabled: true,
controlIconsEnabled: true,
fit: false,
center: true
});
//hacky work around for pan zoom so it scale properly
svg_element.setAttribute("height", `${panZoom.getSizes().height}px`)
})
.catch(error => {
// Create a new Viz instance (@see Caveats page for more info)
viz = new Viz();
// Possibly display the error
console.error(error);
});
}
function build_graph_string(vote_rules)
{
let graph = "digraph G \n";
graph += "\{";
let rules = vote_rules.rules;
for(const rule in rules)
{
const nomination = rules[rule];
for (const layer of nomination)
{
graph += `${rule} -> ${layer.map}\n`;
}
}
graph += "\}";
return graph;
}
function build_vote_rules()
{
let vote_rules = {
"modes": [],
"rules": {},
"addon_map_layer_strings": [],
"mode_repeat_blacklist": []
};
for (let mode of modes_list.children)
{
vote_rules.modes.push(mode.innerHTML.trim());
}
for (let blacklist_item of blacklist_list.children)
{
vote_rules.mode_repeat_blacklist.push(blacklist_item.innerHTML.trim());
}
for (let rule_element of rules_list.children)
{
let rule = {
"name": rule_element.rule_string_input.value,
"nominations": []
};
for (let nomination of rule_element.nomination_list.children)
{
rule.nominations.push({
"map": nomination.map_input.value.trim(),
"modes": nomination.mode_input.value.trim().split(',').map(e => e.trim()),
"versions": nomination.verison_input.value.trim().split(',').map(e => e.trim())
});
}
vote_rules.rules[rule.name] = rule.nominations;
}
return vote_rules;
}
add_blacklist_button.onclick = ()=>{
if (blacklist_input.value == "")
return;
add_blacklist_item(blacklist_input.value);
};
add_rule_button.onclick = ()=>{
let rule = build_rule_element();
rules_list.appendChild(rule);
};
add_mode_button.onclick = () => {
if (mode_input.value == "")
return;
add_mode(mode_input.value);
};
function add_mode(mode_string)
{
let mode = document.createElement("li");
mode.innerHTML = mode_string;
modes_list.appendChild(mode);
mode.onclick = () => {
if (selected_mode)
{
selected_mode.classList.remove("selected");
}
selected_mode = mode;
selected_mode.classList.add("selected");
};
}
function add_blacklist_item(blacklist_string)
{
let blacklist_item = document.createElement("li");
blacklist_item.innerHTML = blacklist_string;
blacklist_list.appendChild(blacklist_item);
blacklist_item.onclick = () => {
if (selected_blacklist_item)
{
selected_blacklist_item.classList.remove("selected");
}
selected_blacklist_item = blacklist_item;
selected_blacklist_item.classList.add("selected");
};
}
remove_blacklist_button.onclick = () => {
if (selected_blacklist_item)
{
selected_blacklist_item.parentNode.removeChild(selected_blacklist_item);
selected_blacklist_item = null;
}
};
remove_mode_button.onclick = () => {
if (selected_mode)
{
selected_mode.parentNode.removeChild(selected_mode);
selected_mode = null;
}
};
save_button.onclick = () => {
let vote_rules = build_vote_rules();
var a = document.createElement("a");
a.href = window.URL.createObjectURL(new Blob([JSON.stringify(vote_rules)], {type: "text/plain"}));
a.download = "vote_rules.json";
a.click();
};
load_button.onchange = () => {
let reader = new FileReader();
rules_list.innerHTML = "";
modes_list.innerHTML = "";
reader.addEventListener("load", (event) => {
let vote_rules = JSON.parse(event.target.result);
for (let mode of vote_rules.modes)
add_mode(mode);
for (let blacklist_item of vote_rules.mode_repeat_blacklist)
add_blacklist_item(blacklist_item);
let rules = Object.keys(vote_rules.rules).sort();
for (let rule of rules)
{
let rule_element = build_rule_element();
rule_element.rule_string_input.value = rule;
rules_list.appendChild(rule_element);
for(let nomination of vote_rules.rules[rule])
{
let nomination_element = build_nomination_element(nomination.map, nomination.modes, nomination.versions);
rule_element.nomination_list.appendChild(nomination_element);
}
}
});
reader.readAsText(load_button.files[0]);
};
function build_rule_element()
{
let rule = document.createElement("li");
rule.classList.add("rule");
let label = document.createElement("label");
label.innerHTML = "Rule string: ";
let input = document.createElement("input");
input.setAttribute("type", "text");
input.classList.add("rule_input");
label.appendChild(input);
rule.appendChild(label);
rule["rule_string_input"] = input;
let add_nomination_button = document.createElement("button");
add_nomination_button.innerHTML = "add nomination";
rule.appendChild(add_nomination_button);
let nominations_list = document.createElement("ul");
nominations_list.classList.add("nomination_list");
rule.appendChild(nominations_list);
rule["nomination_list"] = nominations_list;
let delete_button = document.createElement("button");
delete_button.innerHTML = "delete";
rule.appendChild(delete_button);
add_nomination_button.onclick = () => {
nominations_list.appendChild(build_nomination_element());
};
delete_button.onclick = () => {
rule.parentNode.removeChild(rule);
}
return rule;
}
function build_nomination_element(map_string = "", mode_string = "Any", version_string = "Any")
{
let nomination = document.createElement("li");
nomination.classList.add("nomination");
//map field
let map_label = document.createElement("label");
map_label.innerHTML = "Map:";
nomination.appendChild(map_label);
let map_input = document.createElement("input");
map_input.setAttribute("type", "text");
map_input.value = map_string;
map_label.appendChild(map_input);
nomination["map_input"] = map_input;
//mode field
let mode_label = document.createElement("label");
mode_label.innerHTML = "Mode(s):";
nomination.appendChild(mode_label);
let mode_input = document.createElement("input");
mode_input.setAttribute("type", "text");
mode_input.classList.add("short_input");
mode_input.value = mode_string;
mode_label.appendChild(mode_input);
nomination["mode_input"] = mode_input;
//version feild
let version = document.createElement("label");
version.innerHTML = "Version(s):";
nomination.appendChild(version);
let verison_input = document.createElement("input");
verison_input.setAttribute("type", "text");
verison_input.classList.add("short_input");
verison_input.value = version_string;
version.appendChild(verison_input);
nomination["verison_input"] = verison_input;
//delete button
let delete_button = document.createElement("button");
delete_button.innerHTML = "delete";
delete_button.onclick = () => {
nomination.parentNode.removeChild(nomination);
};
nomination.appendChild(delete_button);
return nomination;
}
function main()
{
console.log("hello world");
}
document.addEventListener("DOMContentLoaded", main);
</script>
</body>
</html>

View File

@ -5,35 +5,29 @@ import BasePlugin from "./base-plugin.js";
import fs from "fs"; import fs from "fs";
import { Layers } from "../layers/index.js" import { Layers } from "../layers/index.js"
function randomElement(array) function randomElement(array) {
{ return array[ Math.floor(Math.random() * array.length) ];
return array[Math.floor(Math.random() * array.length)];
} }
function formatChoice(choiceIndex, mapString, currentVotes) function formatChoice(choiceIndex, mapString, currentVotes, firstBroadcast) {
{ return `${choiceIndex + 1}${mapString} ` + (!firstBroadcast ? `(${currentVotes})` : "");
return `type !vote ${choiceIndex + 1} : ${mapString} (${currentVotes} votes)` // return `${choiceIndex + 1}❱ ${mapString} (${currentVotes} votes)`
} }
function toMils(min) function toMils(min) {
{ return min * 60 * 1000;
return min * 60 * 1000;
} }
export default class MapVote extends BasePlugin export default class MapVote extends BasePlugin {
{ static get description() {
static get description()
{
return "Map Voting plugin"; return "Map Voting plugin";
} }
static get defaultEnabled() static get defaultEnabled() {
{
return true; return true;
} }
static get optionsSpecification() static get optionsSpecification() {
{
return { return {
commandPrefix: commandPrefix:
{ {
@ -47,7 +41,7 @@ export default class MapVote extends BasePlugin
description: 'the path to the layersConfig file', description: 'the path to the layersConfig file',
default: '' default: ''
}, },
minPlayersForVote: minPlayersForVote:
{ {
required: false, required: false,
description: 'number of players needed on the server for a vote to start', description: 'number of players needed on the server for a vote to start',
@ -67,101 +61,96 @@ export default class MapVote extends BasePlugin
} }
}; };
} }
constructor(server, options, connectors) constructor(server, options, connectors) {
{
super(server, options, connectors); super(server, options, connectors);
this.voteRules = {}; //data object holding vote configs this.voteRules = {}; //data object holding vote configs
this.nominations = []; //layer strings for the current vote choices this.nominations = []; //layer strings for the current vote choices
this.trackedVotes = {}; //player votes, keyed by steam id this.trackedVotes = {}; //player votes, keyed by steam id
this.tallies = []; //votes per layer, parellel with nominations this.tallies = []; //votes per layer, parellel with nominations
this.votingEnabled = false; this.votingEnabled = false;
this.onConnectBound = false; this.onConnectBound = false;
this.broadcastIntervalTask = null; this.broadcastIntervalTask = null;
this.firstBroadcast = true;
this.onNewGame = this.onNewGame.bind(this); this.onNewGame = this.onNewGame.bind(this);
this.onPlayerDisconnected = this.onPlayerDisconnected.bind(this); this.onPlayerDisconnected = this.onPlayerDisconnected.bind(this);
this.onChatMessage = this.onChatMessage.bind(this); this.onChatMessage = this.onChatMessage.bind(this);
this.broadcastNominations = this.broadcastNominations.bind(this); this.broadcastNominations = this.broadcastNominations.bind(this);
this.beginVoting = this.beginVoting.bind(this); this.beginVoting = this.beginVoting.bind(this);
this.msgBroadcast = (msg) => {this.server.rcon.broadcast(msg);}; this.msgBroadcast = (msg) => { this.server.rcon.broadcast(msg); };
this.msgDirect = (steamid, msg) => {this.server.rcon.warn(steamid, msg);}; this.msgDirect = (steamid, msg) => { this.server.rcon.warn(steamid, msg); };
//load voteRules with options from source file //load voteRules with options from source file
this.loadLayersConfig(); this.loadLayersConfig();
} }
async mount() async mount() {
{ this.server.on('NEW_GAME', this.onNewGame);
this.server.on('NEW_GAME', this.onNewGame);
this.server.on('CHAT_MESSAGE', this.onChatMessage); this.server.on('CHAT_MESSAGE', this.onChatMessage);
this.server.on('PLAYER_DISCONNECTED', this.onPlayerDisconnected); this.server.on('PLAYER_DISCONNECTED', this.onPlayerDisconnected);
this.verbose(1, 'Map vote was mounted.'); this.verbose(1, 'Map vote was mounted.');
} }
async unmount() async unmount() {
{ this.server.removeEventListener('NEW_GAME', this.onNewGame);
this.server.removeEventListener('NEW_GAME', this.onNewGame);
this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage); this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage);
this.server.removeEventListener('PLAYER_DISCONNECTED', this.onPlayerDisconnected); this.server.removeEventListener('PLAYER_DISCONNECTED', this.onPlayerDisconnected);
clearInterval(this.broadcastIntervalTask); clearInterval(this.broadcastIntervalTask);
this.verbose(1, 'Map vote was un-mounted.'); this.verbose(1, 'Map vote was un-mounted.');
} }
//loads layer configs from disk into plugin memory //loads layer configs from disk into plugin memory
loadLayersConfig() loadLayersConfig() {
{
this.verbose(1, `Fetching Map Voting Lists...`); this.verbose(1, `Fetching Map Voting Lists...`);
let layersConfigString = ''; let layersConfigString = '';
try try {
{ if (!fs.existsSync(this.options.voteRulesPath))
if (!fs.existsSync(this.options.voteRulesPath))
throw new Error(`Could not find Map Vote List at ${this.options.voteRulesPath}`); throw new Error(`Could not find Map Vote List at ${this.options.voteRulesPath}`);
layersConfigString = fs.readFileSync(this.options.voteRulesPath, 'utf8'); layersConfigString = fs.readFileSync(this.options.voteRulesPath, 'utf8');
} }
catch (error) catch (error) {
{
this.verbose('SquadServer', 1, `Error fetching mapvoting list: ${options.voteRulesPath}`, error); this.verbose('SquadServer', 1, `Error fetching mapvoting list: ${options.voteRulesPath}`, error);
} }
this.voteRules = JSON.parse(layersConfigString); this.voteRules = JSON.parse(layersConfigString);
} }
async onNewGame() async onNewGame() {
{
//wait to start voting //wait to start voting
this.endVoting(); this.endVoting();
this.trackedVotes = {}; this.trackedVotes = {};
this.tallies = []; this.tallies = [];
this.nominations = []; this.nominations = [];
this.factionStrings = [];
setTimeout(this.beginVoting, toMils(this.options.voteWaitTimeFromMatchStart)); setTimeout(this.beginVoting, toMils(this.options.voteWaitTimeFromMatchStart));
} }
async onPlayerDisconnected() async onPlayerDisconnected() {
{
if (!this.votingEnabled) return; if (!this.votingEnabled) return;
await this.server.updatePlayerList(); await this.server.updatePlayerList();
this.clearVote(); this.clearVote();
this.updateNextMap(); this.updateNextMap();
} }
async onChatMessage(info) async onChatMessage(info) {
{ const { steamID, name: playerName } = info;
const {steamID, name: playerName} = info;
const message = info.message.toLowerCase(); const message = info.message.toLowerCase();
//check to see if this message has a command prefix //check to see if this message has a command prefix
if (!message.startsWith(this.options.commandPrefix)) if (!message.startsWith(this.options.commandPrefix) && isNaN(message))
return; return;
const subCommand = message.substring(this.options.commandPrefix.length).trim(); const commandSplit = (isNaN(message) ? message.substring(this.options.commandPrefix.length).trim().split(' ') : [ message ]);
if(!isNaN(subCommand)) // if this succeeds player is voting for a map let cmdLayers = commandSplit.slice(1);
for (let k in cmdLayers) cmdLayers[ k ] = cmdLayers[ k ].toLowerCase();
const subCommand = commandSplit[ 0 ];
if (!isNaN(subCommand)) // if this succeeds player is voting for a map
{ {
const mapNumber = parseInt(subCommand); //try to get a vote number const mapNumber = parseInt(subCommand); //try to get a vote number
if (!this.votingEnabled) if (!this.votingEnabled) {
{
await this.msgDirect(steamID, "There is no vote running right now"); await this.msgDirect(steamID, "There is no vote running right now");
return; return;
} }
@ -169,94 +158,93 @@ export default class MapVote extends BasePlugin
this.updateNextMap(); this.updateNextMap();
return; return;
} }
const isAdmin = info.chat === "ChatAdmin"; const isAdmin = info.chat === "ChatAdmin";
switch(subCommand) // select the sub command switch (subCommand) // select the sub command
{ {
case "choices": //sends choices to player in the from of a warning case "choices": //sends choices to player in the from of a warning
if (!this.votingEnabled) if (!this.votingEnabled) {
{
await this.msgDirect(steamID, "There is no vote running right now"); await this.msgDirect(steamID, "There is no vote running right now");
return; return;
} }
this.directMsgNominations(steamID); this.directMsgNominations(steamID);
return; return;
case "results": //sends player the results in a warning case "results": //sends player the results in a warning
if (!this.votingEnabled) if (!this.votingEnabled) {
{
await this.msgDirect(steamID, "There is no vote running right now"); await this.msgDirect(steamID, "There is no vote running right now");
return; return;
} }
this.directMsgNominations(steamID); this.directMsgNominations(steamID);
return; return;
case "restart": //starts the vote again if it was canceled case "start": //starts the vote again if it was canceled
if(!isAdmin) return; if (!isAdmin) return;
if(this.votingEnabled) if (this.votingEnabled) {
{
await this.msgDirect(steamID, "Voting is already enabled"); await this.msgDirect(steamID, "Voting is already enabled");
return; return;
} }
this.beginVoting(true); this.beginVoting(true, steamID, cmdLayers);
return;
case "restart": //starts the vote again if it was canceled
if (!isAdmin) return;
this.endVoting();
this.beginVoting(true, steamID, cmdLayers);
return; return;
case "cancel": //cancels the current vote and wont set next map to current winnner case "cancel": //cancels the current vote and wont set next map to current winnner
if(!isAdmin) return; if (!isAdmin) return;
if(!this.votingEnabled) if (!this.votingEnabled) {
{ await this.msgDirect(steamID, "There is no vote running right now");
await this.msgDirect(steamID, "Voting is already disabled, emotional damage!");
return; return;
} }
this.endVoting(); this.endVoting();
await this.msgDirect(steamID, "ending current vote"); await this.msgDirect(steamID, "Ending current vote");
return; return;
case "reload": //allows for config hot reloads case "broadcast":
if(!isAdmin) return; if (!this.votingEnabled) {
await this.msgDirect(steamID, "There is no vote running right now");
this.loadLayersConfig(); return;
await this.msgDirect(steamID, "Reloaded map vote layers configuration") }
this.broadcastNominations();
return; return;
case "help": //displays available commands case "help": //displays available commands
await this.msgDirect(steamID, `Map voting system built by JetDave for MAD`);
await this.msgDirect(steamID, `!vote <choices|number|results>`); await this.msgDirect(steamID, `!vote <choices|number|results>`);
if(!isAdmin) return; if (!isAdmin) return;
await this.msgDirect(steamID, `!vote <restart|cancel|reload> (admin only)`); await this.msgDirect(steamID, `!vote <start|restart|cancel|broadcast> (admin only)`);
return; return;
default: default:
//give them an error //give them an error
await this.msgDirect(steamID, `Unknown vote subcommand: ${subCommand}`); await this.msgDirect(steamID, `Unknown vote subcommand: ${subCommand}`);
return; return;
} }
} }
updateNextMap() //sets next map to current mapvote winner, if there is a tie will pick at random updateNextMap() //sets next map to current mapvote winner, if there is a tie will pick at random
{ {
const nextMap = randomElement(this.currentWinners); const nextMap = randomElement(this.currentWinners);
this.server.rcon.execute(`AdminSetNextLayer ${nextMap}`); this.server.rcon.execute(`AdminSetNextLayer ${nextMap}`);
} }
matchLayers(builtString) matchLayers(builtString) {
{
return Layers.layers.filter(element => element.layerid.includes(builtString)); return Layers.layers.filter(element => element.layerid.includes(builtString));
} }
getMode(nomination, currentMode) getMode(nomination, currentMode) {
{
const mapName = nomination.map; const mapName = nomination.map;
let modes = nomination.modes; let modes = nomination.modes;
let mode = modes[0]; let mode = modes[ 0 ];
if (mode === "Any") if (mode === "Any")
modes = this.voteRules.modes; modes = this.voteRules.modes;
if (this.voteRules.mode_repeat_blacklist.includes(currentMode)) if (this.voteRules.mode_repeat_blacklist.includes(currentMode)) {
{
modes = modes.filter(mode => !mode.includes(currentMode)); modes = modes.filter(mode => !mode.includes(currentMode));
} }
while (modes.length > 0) while (modes.length > 0) {
{
mode = randomElement(modes); mode = randomElement(modes);
modes = modes.filter(elem => elem !== mode); modes = modes.filter(elem => elem !== mode);
if (this.matchLayers(`${mapName}_${mode}`).length > 0) if (this.matchLayers(`${mapName}_${mode}`).length > 0)
@ -267,93 +255,80 @@ export default class MapVote extends BasePlugin
} }
//TODO: right now if version is set to "Any" no caf layers will be selected //TODO: right now if version is set to "Any" no caf layers will be selected
populateNominations() //gets nomination strings from layer options populateNominations(steamid = null, cmdLayers = null, bypassRaasFilter = false) //gets nomination strings from layer options
{ {
//helpers // this.nominations.push(builtLayerString);
const splitName = name => name.substring(0, name.lastIndexOf("_")); // this.tallies.push(0);
const removeCAF = name => name.replace("CAF_", "");
const translations = {
let layerString = ""; 'United States Army': "USA",
let currentMode = ""; 'United States Marine Corps': "USMC",
if (this.server.currentLayer) 'Russian Ground Forces': "RUS",
{ 'British Army': "GB",
layerString = this.server.currentLayer.layerid 'Canadian Army': "CAF",
currentMode = this.server.currentLayer.gamemode 'Australian Defence Force': "AUS",
'Irregular Militia Forces': "IRR",
'Middle Eastern Alliance': "MEA",
'Insurgent Forces': "INS",
} }
this.nominations = []; this.nominations = [];
const rulesList = this.voteRules.rules; this.tallies = [];
let nominationsList = rulesList.default; this.factionStrings = [];
let rnd_layers = [];
//chomp string until we find a match // let rnd_layers = [];
while(layerString.length > 0) if (!cmdLayers) {
{ const all_layers = Layers.layers.filter((l) => [ 'RAAS', 'AAS', 'INVASION' ].includes(l.gamemode.toUpperCase()));
if(layerString in rulesList) for (let i = 0; i < 6; i++) {
{ // rnd_layers.push(all_layers[Math.floor(Math.random()*all_layers.length)]);
nominationsList = rulesList[layerString]; let l = all_layers[ Math.floor(Math.random() * all_layers.length) ];
break; rnd_layers.push(l);
this.nominations.push(l.layerid)
this.tallies.push(0);
this.factionStrings.push(getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ]));
} }
layerString = removeCAF(layerString); if (!bypassRaasFilter && rnd_layers.filter((l) => l.gamemode === 'RAAS').length < 3) this.populateNominations();
layerString = splitName(layerString); } else {
if (cmdLayers.length <= 6)
for (let cl of cmdLayers) {
const cls = cl.split('_');
const fLayers = Layers.layers.filter((l) => (l.classname.toLowerCase().startsWith(cls[ 0 ]) && (l.gamemode.toLowerCase().startsWith(cls[ 1 ]) || (!cls[ 1 ] && [ 'RAAS', 'AAS', 'INVASION' ].includes(l.gamemode.toUpperCase()))) && (!cls[ 2 ] || l.version.toLowerCase().startsWith("v" + cls[ 2 ].replace(/v/gi, '')))));
let l = fLayers[ Math.floor(Math.random() * fLayers.length) ]; rnd_layers.push(l);
this.nominations.push(l.layerid)
this.tallies.push(0);
this.factionStrings.push(getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ]));
}
else if (steamid) this.msgDirect(steamid, "You cannot start a vote with more than 6 options"); return;
} }
for(const nomination of nominationsList) function getTranslation(t) {
{ if (translations[ t.faction ]) return translations[ t.faction ]
const mapName = nomination.map; else {
let mode = this.getMode(nomination, currentMode); const f = t.faction.split(' ');
let version = randomElement(nomination.versions); let fTag = "";
let cafPrefix = ""; f.forEach((e) => { fTag += e[ 0 ] });
return fTag.toUpperCase();
if (version.includes("CAF_"))
{
cafPrefix = "CAF_";
version = removeCAF(version);
} }
let builtLayerString = `${cafPrefix}${mapName}_${mode}_${version}`;
if (version === "Any")
{
let maps = this.matchLayers(`${mapName}_${mode}`);
if (maps.length == 0)
{
this.verbose(1, `error: could not find layer for ${builtLayerString} from vote rule \"${layerString}\"`);
continue;
}
maps = maps.map(l => l.layerid);
builtLayerString = randomElement(maps);
}
if (!Layers.getLayerByCondition((layer) => layer.layerid === builtLayerString))
{
this.verbose(1, `error: could not find layer for ${builtLayerString} from vote rule \"${layerString}\"`);
continue;
}
this.nominations.push(builtLayerString);
this.tallies.push(0);
} }
} }
//checks if there are enough players to start voting, if not binds itself to player connected //checks if there are enough players to start voting, if not binds itself to player connected
//when there are enough players it clears old votes, sets up new nominations, and starts broadcast //when there are enough players it clears old votes, sets up new nominations, and starts broadcast
beginVoting(force = false) beginVoting(force = false, steamid = null, cmdLayers = null) {
{
const playerCount = this.server.players.length; const playerCount = this.server.players.length;
const minPlayers = this.options.minPlayersForVote; const minPlayers = this.options.minPlayersForVote;
if (this.votingEnabled) //voting has already started if (this.votingEnabled) //voting has already started
return; return;
if (playerCount < minPlayers && !force) if (playerCount < minPlayers && !force) {
{ if (this.onConnectBound == false) {
if (this.onConnectBound == false)
{
this.server.on("PLAYER_CONNECTED", this.beginVoting) this.server.on("PLAYER_CONNECTED", this.beginVoting)
this.onConnectBound = true; this.onConnectBound = true;
} }
return; return;
} }
if (this.onConnectBound) if (this.onConnectBound) {
{
this.server.removeEventListener("PLAYER_CONNECTED", this.beginVoting); this.server.removeEventListener("PLAYER_CONNECTED", this.beginVoting);
this.onConnectBound = false; this.onConnectBound = false;
} }
@ -361,97 +336,93 @@ export default class MapVote extends BasePlugin
// these need to be reset after reenabling voting // these need to be reset after reenabling voting
this.trackedVotes = {}; this.trackedVotes = {};
this.tallies = []; this.tallies = [];
this.populateNominations(); this.populateNominations(steamid, cmdLayers);
this.votingEnabled = true; this.votingEnabled = true;
this.broadcastNominations(); this.firstBroadcast = true;
this.broadcastNominations();
this.broadcastIntervalTask = setInterval(this.broadcastNominations, toMils(this.options.voteBroadcastInterval)); this.broadcastIntervalTask = setInterval(this.broadcastNominations, toMils(this.options.voteBroadcastInterval));
} }
endVoting() endVoting() {
{
this.votingEnabled = false; this.votingEnabled = false;
clearInterval(this.broadcastIntervalTask); clearInterval(this.broadcastIntervalTask);
this.broadcastIntervalTask = null; this.broadcastIntervalTask = null;
} }
//sends a message about nominations through a broadcast //sends a message about nominations through a broadcast
//NOTE: max squad broadcast message length appears to be 485 characters //NOTE: max squad broadcast message length appears to be 485 characters
//Note: broadcast strings with multi lines are very strange //Note: broadcast strings with multi lines are very strange
async broadcastNominations() async broadcastNominations() {
{ if(this.nominations.length>0){
await this.msgBroadcast("Type !vote <map number> in chat to cast your vote, Candidates:\n"); await this.msgBroadcast("✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number!\n");
let nominationStrings = []; let nominationStrings = [];
for(let choice in this.nominations) for (let choice in this.nominations) {
{ choice = Number(choice);
choice = Number(choice); nominationStrings.push(formatChoice(choice, this.nominations[ choice ].replace(/\_/gi, ' ').replace(/\sv\d{1,2}/gi, '') + ' ' + this.factionStrings[ choice ], this.tallies[ choice ], this.firstBroadcast));
nominationStrings.push(formatChoice(choice, this.nominations[choice], this.tallies[choice])); }
} await this.msgBroadcast(nominationStrings.join("\n"));
await this.msgBroadcast(nominationStrings.join("\n"));
this.firstBroadcast = false;
}
//const winners = this.currentWinners; //const winners = this.currentWinners;
//await this.msgBroadcast(`Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`); //await this.msgBroadcast(`Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
} }
async directMsgNominations(steamID) async directMsgNominations(steamID) {
{ for (let choice in this.nominations) {
for(let choice in this.nominations) choice = Number(choice);
{ await this.msgDirect(steamID, formatChoice(choice, this.nominations[ choice ], this.tallies[ choice ]));
choice = Number(choice); }
await this.msgDirect(steamID, formatChoice(choice, this.nominations[choice], this.tallies[choice]));
}
const winners = this.currentWinners; const winners = this.currentWinners;
await this.msgDirect(steamID, `Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`); await this.msgDirect(steamID, `Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
} }
//counts a vote from a player and adds it to tallies //counts a vote from a player and adds it to tallies
async registerVote(steamID, nominationIndex, playerName) async registerVote(steamID, nominationIndex, playerName) {
{
nominationIndex -= 1; // shift indices from display range nominationIndex -= 1; // shift indices from display range
if(nominationIndex < 0 || nominationIndex > this.nominations.length) if (nominationIndex < 0 || nominationIndex > this.nominations.length) {
{
await this.msgDirect(steamID, `[Map Vote] ${playerName}: invalid map number, typ !vote results to see map numbers`); await this.msgDirect(steamID, `[Map Vote] ${playerName}: invalid map number, typ !vote results to see map numbers`);
return; return;
} }
const previousVote = this.trackedVotes[steamID]; const previousVote = this.trackedVotes[ steamID ];
this.trackedVotes[steamID] = nominationIndex; this.trackedVotes[ steamID ] = nominationIndex;
this.tallies[nominationIndex] += 1; this.tallies[ nominationIndex ] += 1;
if(previousVote !== undefined) if (previousVote !== undefined)
this.tallies[previousVote] -= 1; this.tallies[ previousVote ] -= 1;
await this.msgDirect(steamID, `you voted for ${this.nominations[nominationIndex]}`); await this.msgDirect(steamID, `Registered vote: ${this.nominations[ nominationIndex ].replace(/\_/gi, ' ').replace(/\sv\d{1,2}/gi, '')} ${this.factionStrings[ nominationIndex ]} (${this.tallies[ nominationIndex ]} votes)`);
// await this.msgDirect(steamID, `Registered vote`);// ${this.nominations[ nominationIndex ]} ${this.factionStrings[ nominationIndex ]} (${this.tallies[ nominationIndex ]} votes)`);
// await this.msgDirect(steamID, `${this.nominations[ nominationIndex ]} (${this.tallies[ nominationIndex ]} votes)`);
// await this.msgDirect(steamID, `${this.factionStrings[ nominationIndex ]}`);
// await this.msgDirect(steamID, `${this.tallies[ nominationIndex ]} votes`);
} }
//removes a players vote if they disconnect from the sever //removes a players vote if they disconnect from the sever
clearVote() clearVote() {
{
const currentPlayers = this.server.players.map((p) => p.steamID); const currentPlayers = this.server.players.map((p) => p.steamID);
for (const steamID in this.trackedVotes) for (const steamID in this.trackedVotes) {
{ if (!(currentPlayers.includes(steamID))) {
if (!(currentPlayers.includes(steamID))) const vote = this.trackedVotes[ steamID ];
{ this.tallies[ vote ] -= 1;
const vote = this.trackedVotes[steamID]; delete this.trackedVotes[ steamID ];
this.tallies[vote] -= 1;
delete this.trackedVotes[steamID];
} }
} }
} }
//calculates the current winner(s) of the vote and returns thier strings in an array //calculates the current winner(s) of the vote and returns thier strings in an array
get currentWinners() get currentWinners() {
{
const ties = []; const ties = [];
let highestScore = -Infinity; let highestScore = -Infinity;
for(let choice in this.tallies) for (let choice in this.tallies) {
{ const score = this.tallies[ choice ];
const score = this.tallies[choice]; if (score < highestScore)
if(score < highestScore)
continue; continue;
else if(score > highestScore) else if (score > highestScore) {
{
highestScore = score; highestScore = score;
ties.length = 0; ties.length = 0;
ties.push(choice); ties.push(choice);
@ -459,7 +430,7 @@ export default class MapVote extends BasePlugin
else // equal else // equal
ties.push(choice); ties.push(choice);
} }
return ties.map(i => this.nominations[i]); return ties.map(i => this.nominations[ i ]);
} }
} }