mirror of
https://github.com/AsgardEternal/squad-js-map-vote.git
synced 2025-01-23 17:13:54 -06:00
working new plugin
This commit is contained in:
parent
42c1552f0c
commit
0721661af3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
configTool/svg-pan-zoom.min.js
vendored
3
configTool/svg-pan-zoom.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||
|
||||
})));
|
@ -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":[]}
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
507
index.html
507
index.html
@ -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>
|
421
mapvote.js
421
mapvote.js
@ -5,35 +5,29 @@ import BasePlugin from "./base-plugin.js";
|
||||
import fs from "fs";
|
||||
import { Layers } from "../layers/index.js"
|
||||
|
||||
function randomElement(array)
|
||||
{
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
function randomElement(array) {
|
||||
return array[ Math.floor(Math.random() * array.length) ];
|
||||
}
|
||||
|
||||
function formatChoice(choiceIndex, mapString, currentVotes)
|
||||
{
|
||||
return `type !vote ${choiceIndex + 1} : ${mapString} (${currentVotes} votes)`
|
||||
function formatChoice(choiceIndex, mapString, currentVotes, firstBroadcast) {
|
||||
return `${choiceIndex + 1}➤ ${mapString} ` + (!firstBroadcast ? `(${currentVotes})` : "");
|
||||
// return `${choiceIndex + 1}❱ ${mapString} (${currentVotes} votes)`
|
||||
}
|
||||
|
||||
function toMils(min)
|
||||
{
|
||||
return min * 60 * 1000;
|
||||
function toMils(min) {
|
||||
return min * 60 * 1000;
|
||||
}
|
||||
|
||||
export default class MapVote extends BasePlugin
|
||||
{
|
||||
static get description()
|
||||
{
|
||||
export default class MapVote extends BasePlugin {
|
||||
static get description() {
|
||||
return "Map Voting plugin";
|
||||
}
|
||||
|
||||
static get defaultEnabled()
|
||||
{
|
||||
|
||||
static get defaultEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static get optionsSpecification()
|
||||
{
|
||||
|
||||
static get optionsSpecification() {
|
||||
return {
|
||||
commandPrefix:
|
||||
{
|
||||
@ -47,7 +41,7 @@ export default class MapVote extends BasePlugin
|
||||
description: 'the path to the layersConfig file',
|
||||
default: ''
|
||||
},
|
||||
minPlayersForVote:
|
||||
minPlayersForVote:
|
||||
{
|
||||
required: false,
|
||||
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);
|
||||
|
||||
|
||||
this.voteRules = {}; //data object holding vote configs
|
||||
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.votingEnabled = false;
|
||||
this.onConnectBound = false;
|
||||
this.broadcastIntervalTask = null;
|
||||
|
||||
this.firstBroadcast = true;
|
||||
|
||||
this.onNewGame = this.onNewGame.bind(this);
|
||||
this.onPlayerDisconnected = this.onPlayerDisconnected.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.msgBroadcast = (msg) => {this.server.rcon.broadcast(msg);};
|
||||
this.msgDirect = (steamid, msg) => {this.server.rcon.warn(steamid, msg);};
|
||||
this.msgBroadcast = (msg) => { this.server.rcon.broadcast(msg); };
|
||||
this.msgDirect = (steamid, msg) => { this.server.rcon.warn(steamid, msg); };
|
||||
|
||||
//load voteRules with options from source file
|
||||
this.loadLayersConfig();
|
||||
}
|
||||
|
||||
async mount()
|
||||
{
|
||||
this.server.on('NEW_GAME', this.onNewGame);
|
||||
async mount() {
|
||||
this.server.on('NEW_GAME', this.onNewGame);
|
||||
this.server.on('CHAT_MESSAGE', this.onChatMessage);
|
||||
this.server.on('PLAYER_DISCONNECTED', this.onPlayerDisconnected);
|
||||
this.verbose(1, 'Map vote was mounted.');
|
||||
this.verbose(1, 'Map vote was mounted.');
|
||||
}
|
||||
|
||||
async unmount()
|
||||
{
|
||||
this.server.removeEventListener('NEW_GAME', this.onNewGame);
|
||||
async unmount() {
|
||||
this.server.removeEventListener('NEW_GAME', this.onNewGame);
|
||||
this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage);
|
||||
this.server.removeEventListener('PLAYER_DISCONNECTED', this.onPlayerDisconnected);
|
||||
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
|
||||
loadLayersConfig()
|
||||
{
|
||||
loadLayersConfig() {
|
||||
this.verbose(1, `Fetching Map Voting Lists...`);
|
||||
|
||||
|
||||
let layersConfigString = '';
|
||||
try
|
||||
{
|
||||
if (!fs.existsSync(this.options.voteRulesPath))
|
||||
try {
|
||||
if (!fs.existsSync(this.options.voteRulesPath))
|
||||
throw new Error(`Could not find Map Vote List at ${this.options.voteRulesPath}`);
|
||||
layersConfigString = fs.readFileSync(this.options.voteRulesPath, 'utf8');
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
catch (error) {
|
||||
this.verbose('SquadServer', 1, `Error fetching mapvoting list: ${options.voteRulesPath}`, error);
|
||||
}
|
||||
|
||||
this.voteRules = JSON.parse(layersConfigString);
|
||||
}
|
||||
|
||||
async onNewGame()
|
||||
{
|
||||
|
||||
async onNewGame() {
|
||||
//wait to start voting
|
||||
this.endVoting();
|
||||
this.trackedVotes = {};
|
||||
this.tallies = [];
|
||||
this.nominations = [];
|
||||
this.factionStrings = [];
|
||||
setTimeout(this.beginVoting, toMils(this.options.voteWaitTimeFromMatchStart));
|
||||
}
|
||||
|
||||
async onPlayerDisconnected()
|
||||
{
|
||||
}
|
||||
|
||||
async onPlayerDisconnected() {
|
||||
if (!this.votingEnabled) return;
|
||||
await this.server.updatePlayerList();
|
||||
await this.server.updatePlayerList();
|
||||
this.clearVote();
|
||||
this.updateNextMap();
|
||||
}
|
||||
|
||||
async onChatMessage(info)
|
||||
{
|
||||
const {steamID, name: playerName} = info;
|
||||
async onChatMessage(info) {
|
||||
const { steamID, name: playerName } = info;
|
||||
const message = info.message.toLowerCase();
|
||||
//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;
|
||||
|
||||
const subCommand = message.substring(this.options.commandPrefix.length).trim();
|
||||
if(!isNaN(subCommand)) // if this succeeds player is voting for a map
|
||||
|
||||
const commandSplit = (isNaN(message) ? message.substring(this.options.commandPrefix.length).trim().split(' ') : [ message ]);
|
||||
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
|
||||
if (!this.votingEnabled)
|
||||
{
|
||||
const mapNumber = parseInt(subCommand); //try to get a vote number
|
||||
if (!this.votingEnabled) {
|
||||
await this.msgDirect(steamID, "There is no vote running right now");
|
||||
return;
|
||||
}
|
||||
@ -169,94 +158,93 @@ export default class MapVote extends BasePlugin
|
||||
this.updateNextMap();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
if (!this.votingEnabled)
|
||||
{
|
||||
if (!this.votingEnabled) {
|
||||
await this.msgDirect(steamID, "There is no vote running right now");
|
||||
return;
|
||||
}
|
||||
this.directMsgNominations(steamID);
|
||||
return;
|
||||
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");
|
||||
return;
|
||||
}
|
||||
this.directMsgNominations(steamID);
|
||||
return;
|
||||
case "restart": //starts the vote again if it was canceled
|
||||
if(!isAdmin) return;
|
||||
|
||||
if(this.votingEnabled)
|
||||
{
|
||||
case "start": //starts the vote again if it was canceled
|
||||
if (!isAdmin) return;
|
||||
|
||||
if (this.votingEnabled) {
|
||||
await this.msgDirect(steamID, "Voting is already enabled");
|
||||
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;
|
||||
case "cancel": //cancels the current vote and wont set next map to current winnner
|
||||
if(!isAdmin) return;
|
||||
|
||||
if(!this.votingEnabled)
|
||||
{
|
||||
await this.msgDirect(steamID, "Voting is already disabled, emotional damage!");
|
||||
if (!isAdmin) return;
|
||||
|
||||
if (!this.votingEnabled) {
|
||||
await this.msgDirect(steamID, "There is no vote running right now");
|
||||
return;
|
||||
}
|
||||
this.endVoting();
|
||||
await this.msgDirect(steamID, "ending current vote");
|
||||
await this.msgDirect(steamID, "Ending current vote");
|
||||
return;
|
||||
case "reload": //allows for config hot reloads
|
||||
if(!isAdmin) return;
|
||||
|
||||
this.loadLayersConfig();
|
||||
await this.msgDirect(steamID, "Reloaded map vote layers configuration")
|
||||
case "broadcast":
|
||||
if (!this.votingEnabled) {
|
||||
await this.msgDirect(steamID, "There is no vote running right now");
|
||||
return;
|
||||
}
|
||||
this.broadcastNominations();
|
||||
return;
|
||||
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>`);
|
||||
if(!isAdmin) return;
|
||||
|
||||
await this.msgDirect(steamID, `!vote <restart|cancel|reload> (admin only)`);
|
||||
if (!isAdmin) return;
|
||||
|
||||
await this.msgDirect(steamID, `!vote <start|restart|cancel|broadcast> (admin only)`);
|
||||
return;
|
||||
default:
|
||||
//give them an error
|
||||
await this.msgDirect(steamID, `Unknown vote subcommand: ${subCommand}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateNextMap() //sets next map to current mapvote winner, if there is a tie will pick at random
|
||||
{
|
||||
const nextMap = randomElement(this.currentWinners);
|
||||
this.server.rcon.execute(`AdminSetNextLayer ${nextMap}`);
|
||||
}
|
||||
|
||||
matchLayers(builtString)
|
||||
{
|
||||
|
||||
matchLayers(builtString) {
|
||||
return Layers.layers.filter(element => element.layerid.includes(builtString));
|
||||
}
|
||||
|
||||
getMode(nomination, currentMode)
|
||||
{
|
||||
getMode(nomination, currentMode) {
|
||||
const mapName = nomination.map;
|
||||
let modes = nomination.modes;
|
||||
let mode = modes[0];
|
||||
let mode = modes[ 0 ];
|
||||
|
||||
if (mode === "Any")
|
||||
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));
|
||||
}
|
||||
|
||||
while (modes.length > 0)
|
||||
{
|
||||
while (modes.length > 0) {
|
||||
mode = randomElement(modes);
|
||||
modes = modes.filter(elem => elem !== mode);
|
||||
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
|
||||
populateNominations() //gets nomination strings from layer options
|
||||
populateNominations(steamid = null, cmdLayers = null, bypassRaasFilter = false) //gets nomination strings from layer options
|
||||
{
|
||||
//helpers
|
||||
const splitName = name => name.substring(0, name.lastIndexOf("_"));
|
||||
const removeCAF = name => name.replace("CAF_", "");
|
||||
|
||||
let layerString = "";
|
||||
let currentMode = "";
|
||||
if (this.server.currentLayer)
|
||||
{
|
||||
layerString = this.server.currentLayer.layerid
|
||||
currentMode = this.server.currentLayer.gamemode
|
||||
// this.nominations.push(builtLayerString);
|
||||
// this.tallies.push(0);
|
||||
|
||||
const translations = {
|
||||
'United States Army': "USA",
|
||||
'United States Marine Corps': "USMC",
|
||||
'Russian Ground Forces': "RUS",
|
||||
'British Army': "GB",
|
||||
'Canadian Army': "CAF",
|
||||
'Australian Defence Force': "AUS",
|
||||
'Irregular Militia Forces': "IRR",
|
||||
'Middle Eastern Alliance': "MEA",
|
||||
'Insurgent Forces': "INS",
|
||||
}
|
||||
|
||||
this.nominations = [];
|
||||
const rulesList = this.voteRules.rules;
|
||||
let nominationsList = rulesList.default;
|
||||
|
||||
//chomp string until we find a match
|
||||
while(layerString.length > 0)
|
||||
{
|
||||
if(layerString in rulesList)
|
||||
{
|
||||
nominationsList = rulesList[layerString];
|
||||
break;
|
||||
this.tallies = [];
|
||||
this.factionStrings = [];
|
||||
let rnd_layers = [];
|
||||
// let rnd_layers = [];
|
||||
if (!cmdLayers) {
|
||||
const all_layers = Layers.layers.filter((l) => [ 'RAAS', 'AAS', 'INVASION' ].includes(l.gamemode.toUpperCase()));
|
||||
for (let i = 0; i < 6; i++) {
|
||||
// rnd_layers.push(all_layers[Math.floor(Math.random()*all_layers.length)]);
|
||||
let l = all_layers[ Math.floor(Math.random() * all_layers.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 ]));
|
||||
}
|
||||
layerString = removeCAF(layerString);
|
||||
layerString = splitName(layerString);
|
||||
if (!bypassRaasFilter && rnd_layers.filter((l) => l.gamemode === 'RAAS').length < 3) this.populateNominations();
|
||||
} 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)
|
||||
{
|
||||
const mapName = nomination.map;
|
||||
let mode = this.getMode(nomination, currentMode);
|
||||
let version = randomElement(nomination.versions);
|
||||
let cafPrefix = "";
|
||||
|
||||
if (version.includes("CAF_"))
|
||||
{
|
||||
cafPrefix = "CAF_";
|
||||
version = removeCAF(version);
|
||||
function getTranslation(t) {
|
||||
if (translations[ t.faction ]) return translations[ t.faction ]
|
||||
else {
|
||||
const f = t.faction.split(' ');
|
||||
let fTag = "";
|
||||
f.forEach((e) => { fTag += e[ 0 ] });
|
||||
return fTag.toUpperCase();
|
||||
}
|
||||
|
||||
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
|
||||
//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 minPlayers = this.options.minPlayersForVote;
|
||||
|
||||
if (this.votingEnabled) //voting has already started
|
||||
return;
|
||||
|
||||
if (playerCount < minPlayers && !force)
|
||||
{
|
||||
if (this.onConnectBound == false)
|
||||
{
|
||||
if (playerCount < minPlayers && !force) {
|
||||
if (this.onConnectBound == false) {
|
||||
this.server.on("PLAYER_CONNECTED", this.beginVoting)
|
||||
this.onConnectBound = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.onConnectBound)
|
||||
{
|
||||
if (this.onConnectBound) {
|
||||
this.server.removeEventListener("PLAYER_CONNECTED", this.beginVoting);
|
||||
this.onConnectBound = false;
|
||||
}
|
||||
@ -361,97 +336,93 @@ export default class MapVote extends BasePlugin
|
||||
// these need to be reset after reenabling voting
|
||||
this.trackedVotes = {};
|
||||
this.tallies = [];
|
||||
|
||||
this.populateNominations();
|
||||
|
||||
|
||||
this.populateNominations(steamid, cmdLayers);
|
||||
|
||||
this.votingEnabled = true;
|
||||
this.broadcastNominations();
|
||||
this.firstBroadcast = true;
|
||||
this.broadcastNominations();
|
||||
this.broadcastIntervalTask = setInterval(this.broadcastNominations, toMils(this.options.voteBroadcastInterval));
|
||||
}
|
||||
|
||||
endVoting()
|
||||
{
|
||||
|
||||
endVoting() {
|
||||
this.votingEnabled = false;
|
||||
clearInterval(this.broadcastIntervalTask);
|
||||
this.broadcastIntervalTask = null;
|
||||
}
|
||||
|
||||
//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
|
||||
async broadcastNominations()
|
||||
{
|
||||
await this.msgBroadcast("Type !vote <map number> in chat to cast your vote, Candidates:\n");
|
||||
let nominationStrings = [];
|
||||
for(let choice in this.nominations)
|
||||
{
|
||||
choice = Number(choice);
|
||||
nominationStrings.push(formatChoice(choice, this.nominations[choice], this.tallies[choice]));
|
||||
}
|
||||
await this.msgBroadcast(nominationStrings.join("\n"));
|
||||
async broadcastNominations() {
|
||||
if(this.nominations.length>0){
|
||||
await this.msgBroadcast("✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number!\n");
|
||||
let nominationStrings = [];
|
||||
for (let choice in this.nominations) {
|
||||
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));
|
||||
}
|
||||
await this.msgBroadcast(nominationStrings.join("\n"));
|
||||
|
||||
this.firstBroadcast = false;
|
||||
}
|
||||
//const winners = this.currentWinners;
|
||||
//await this.msgBroadcast(`Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
|
||||
}
|
||||
|
||||
async directMsgNominations(steamID)
|
||||
{
|
||||
for(let choice in this.nominations)
|
||||
{
|
||||
choice = Number(choice);
|
||||
await this.msgDirect(steamID, formatChoice(choice, this.nominations[choice], this.tallies[choice]));
|
||||
}
|
||||
|
||||
async directMsgNominations(steamID) {
|
||||
for (let choice in this.nominations) {
|
||||
choice = Number(choice);
|
||||
await this.msgDirect(steamID, formatChoice(choice, this.nominations[ choice ], this.tallies[ choice ]));
|
||||
}
|
||||
|
||||
const winners = this.currentWinners;
|
||||
await this.msgDirect(steamID, `Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
|
||||
}
|
||||
|
||||
//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
|
||||
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`);
|
||||
return;
|
||||
}
|
||||
|
||||
const previousVote = this.trackedVotes[steamID];
|
||||
this.trackedVotes[steamID] = nominationIndex;
|
||||
|
||||
this.tallies[nominationIndex] += 1;
|
||||
if(previousVote !== undefined)
|
||||
this.tallies[previousVote] -= 1;
|
||||
await this.msgDirect(steamID, `you voted for ${this.nominations[nominationIndex]}`);
|
||||
|
||||
const previousVote = this.trackedVotes[ steamID ];
|
||||
this.trackedVotes[ steamID ] = nominationIndex;
|
||||
|
||||
this.tallies[ nominationIndex ] += 1;
|
||||
if (previousVote !== undefined)
|
||||
this.tallies[ previousVote ] -= 1;
|
||||
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
|
||||
clearVote()
|
||||
{
|
||||
clearVote() {
|
||||
const currentPlayers = this.server.players.map((p) => p.steamID);
|
||||
for (const steamID in this.trackedVotes)
|
||||
{
|
||||
if (!(currentPlayers.includes(steamID)))
|
||||
{
|
||||
const vote = this.trackedVotes[steamID];
|
||||
this.tallies[vote] -= 1;
|
||||
delete this.trackedVotes[steamID];
|
||||
for (const steamID in this.trackedVotes) {
|
||||
if (!(currentPlayers.includes(steamID))) {
|
||||
const vote = 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
|
||||
get currentWinners()
|
||||
{
|
||||
get currentWinners() {
|
||||
const ties = [];
|
||||
|
||||
|
||||
let highestScore = -Infinity;
|
||||
for(let choice in this.tallies)
|
||||
{
|
||||
const score = this.tallies[choice];
|
||||
if(score < highestScore)
|
||||
for (let choice in this.tallies) {
|
||||
const score = this.tallies[ choice ];
|
||||
if (score < highestScore)
|
||||
continue;
|
||||
else if(score > highestScore)
|
||||
{
|
||||
else if (score > highestScore) {
|
||||
highestScore = score;
|
||||
ties.length = 0;
|
||||
ties.push(choice);
|
||||
@ -459,7 +430,7 @@ export default class MapVote extends BasePlugin
|
||||
else // equal
|
||||
ties.push(choice);
|
||||
}
|
||||
|
||||
return ties.map(i => this.nominations[i]);
|
||||
|
||||
return ties.map(i => this.nominations[ i ]);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user