'use strict'; function editReliefIcon() { if (customization) return; closeDialogs('.stable'); if (!layerIsOn('toggleRelief')) toggleRelief(); terrain.selectAll('use').call(d3.drag().on('drag', dragReliefIcon)).classed('draggable', true); elSelected = d3.select(d3.event.target); restoreEditMode(); updateReliefIconSelected(); updateReliefSizeInput(); $('#reliefEditor').dialog({ title: 'Edit Relief Icons', resizable: false, width: '27em', position: {my: 'left top', at: 'left+10 top+10', of: '#map'}, close: closeReliefEditor }); if (modules.editReliefIcon) return; modules.editReliefIcon = true; // add listeners document.getElementById('reliefIndividual').addEventListener('click', enterIndividualMode); document.getElementById('reliefBulkAdd').addEventListener('click', enterBulkAddMode); document.getElementById('reliefBulkRemove').addEventListener('click', enterBulkRemoveMode); document.getElementById('reliefSize').addEventListener('input', changeIconSize); document.getElementById('reliefSizeNumber').addEventListener('input', changeIconSize); document.getElementById('reliefEditorSet').addEventListener('change', changeIconsSet); reliefIconsDiv.querySelectorAll('svg').forEach((el) => el.addEventListener('click', changeIcon)); document.getElementById('reliefEditStyle').addEventListener('click', () => editStyle('terrain')); document.getElementById('reliefCopy').addEventListener('click', copyIcon); document.getElementById('reliefMoveFront').addEventListener('click', () => elSelected.raise()); document.getElementById('reliefMoveBack').addEventListener('click', () => elSelected.lower()); document.getElementById('reliefRemove').addEventListener('click', removeIcon); function dragReliefIcon() { const dx = +this.getAttribute('x') - d3.event.x; const dy = +this.getAttribute('y') - d3.event.y; d3.event.on('drag', function () { const x = d3.event.x, y = d3.event.y; this.setAttribute('x', dx + x); this.setAttribute('y', dy + y); }); } function restoreEditMode() { if (!reliefTools.querySelector('button.pressed')) enterIndividualMode(); else if (reliefBulkAdd.classList.contains('pressed')) enterBulkAddMode(); else if (reliefBulkRemove.classList.contains('pressed')) enterBulkRemoveMode(); } function updateReliefIconSelected() { const type = elSelected.attr('href') || elSelected.attr('data-type'); const button = reliefIconsDiv.querySelector("svg[data-type='" + type + "']"); reliefIconsDiv.querySelectorAll('svg.pressed').forEach((b) => b.classList.remove('pressed')); button.classList.add('pressed'); reliefIconsDiv.querySelectorAll('div').forEach((b) => (b.style.display = 'none')); button.parentNode.style.display = 'block'; reliefEditorSet.value = button.parentNode.dataset.type; } function updateReliefSizeInput() { const size = +elSelected.attr('width'); reliefSize.value = reliefSizeNumber.value = rn(size); } function enterIndividualMode() { reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed')); reliefIndividual.classList.add('pressed'); reliefSizeDiv.style.display = 'block'; reliefRadiusDiv.style.display = 'none'; reliefSpacingDiv.style.display = 'none'; reliefIconsSeletionAny.style.display = 'none'; removeCircle(); updateReliefSizeInput(); restoreDefaultEvents(); clearMainTip(); } function enterBulkAddMode() { reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed')); reliefBulkAdd.classList.add('pressed'); reliefSizeDiv.style.display = 'block'; reliefRadiusDiv.style.display = 'block'; reliefSpacingDiv.style.display = 'block'; reliefIconsSeletionAny.style.display = 'none'; const pressedType = reliefIconsDiv.querySelector('svg.pressed'); if (pressedType.id === 'reliefIconsSeletionAny') { // in "any" is pressed, select first type reliefIconsSeletionAny.classList.remove('pressed'); reliefIconsDiv.querySelector('svg').classList.add('pressed'); } viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragToAdd)).on('touchmove mousemove', moveBrush); tip('Drag to place relief icons within radius', true); } function moveBrush() { showMainTip(); const point = d3.mouse(this); const radius = +reliefRadiusNumber.value; moveCircle(point[0], point[1], radius); } function dragToAdd() { const pressed = reliefIconsDiv.querySelector('svg.pressed'); if (!pressed) return tip('Please select an icon', false, error); const type = pressed.dataset.type; const r = +reliefRadiusNumber.value; const spacing = +reliefSpacingNumber.value; const size = +reliefSizeNumber.value; // build a quadtree const tree = d3.quadtree(); const positions = []; terrain.selectAll('use').each(function () { const x = +this.getAttribute('x') + this.getAttribute('width') / 2; const y = +this.getAttribute('y') + this.getAttribute('height') / 2; tree.add([x, y, x]); const box = this.getBBox(); positions.push(box.y + box.height); }); d3.event.on('drag', function () { const p = d3.mouse(this); moveCircle(p[0], p[1], r); d3.range(Math.ceil(r / 10)).forEach(function () { const a = Math.PI * 2 * Math.random(); const rad = r * Math.random(); const cx = p[0] + rad * Math.cos(a); const cy = p[1] + rad * Math.sin(a); if (tree.find(cx, cy, spacing)) return; // too close to existing icon if (pack.cells.h[findCell(cx, cy)] < 20) return; // on water cell const h = rn((size / 2) * (Math.random() * 0.4 + 0.8), 2); const x = rn(cx - h, 2); const y = rn(cy - h, 2); const z = y + h * 2; const s = rn(h * 2, 2); let nth = 1; while (positions[nth] && z > positions[nth]) { nth++; } tree.add([cx, cy]); positions.push(z); terrain .insert('use', ':nth-child(' + nth + ')') .attr('href', type) .attr('x', x) .attr('y', y) .attr('width', s) .attr('height', s); }); }); } function enterBulkRemoveMode() { reliefTools.querySelectorAll('button.pressed').forEach((b) => b.classList.remove('pressed')); reliefBulkRemove.classList.add('pressed'); reliefSizeDiv.style.display = 'none'; reliefRadiusDiv.style.display = 'block'; reliefSpacingDiv.style.display = 'none'; reliefIconsSeletionAny.style.display = 'inline-block'; viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragToRemove)).on('touchmove mousemove', moveBrush); tip('Drag to remove relief icons in radius', true); } function dragToRemove() { const pressed = reliefIconsDiv.querySelector('svg.pressed'); if (!pressed) return tip('Please select an icon', false, error); const r = +reliefRadiusNumber.value; const type = pressed.dataset.type; const icons = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll('use'); const tree = d3.quadtree(); icons.each(function () { const x = +this.getAttribute('x') + this.getAttribute('width') / 2; const y = +this.getAttribute('y') + this.getAttribute('height') / 2; tree.add([x, y, this]); }); d3.event.on('drag', function () { const p = d3.mouse(this); moveCircle(p[0], p[1], r); tree.findAll(p[0], p[1], r).forEach((f) => f[2].remove()); }); } function changeIconSize() { const size = +reliefSizeNumber.value; if (!reliefIndividual.classList.contains('pressed')) return; const shift = (size - +elSelected.attr('width')) / 2; elSelected.attr('width', size).attr('height', size); const x = +elSelected.attr('x'), y = +elSelected.attr('y'); elSelected.attr('x', x - shift).attr('y', y - shift); } function changeIconsSet() { const set = reliefEditorSet.value; reliefIconsDiv.querySelectorAll('div').forEach((b) => (b.style.display = 'none')); reliefIconsDiv.querySelector("div[data-type='" + set + "']").style.display = 'block'; } function changeIcon() { if (this.classList.contains('pressed')) return; reliefIconsDiv.querySelectorAll('svg.pressed').forEach((b) => b.classList.remove('pressed')); this.classList.add('pressed'); if (reliefIndividual.classList.contains('pressed')) { const type = this.dataset.type; elSelected.attr('href', type); } } function copyIcon() { const parent = elSelected.node().parentNode; const copy = elSelected.node().cloneNode(true); let x = +elSelected.attr('x') - 3, y = +elSelected.attr('y') - 3; while (parent.querySelector("[x='" + x + "']", "[x='" + y + "']")) { x -= 3; y -= 3; } copy.setAttribute('x', x); copy.setAttribute('y', y); parent.insertBefore(copy, null); } function removeIcon() { let selection = null; const pressed = reliefTools.querySelector('button.pressed'); if (pressed.id === 'reliefIndividual') { alertMessage.innerHTML = `Are you sure you want to remove the icon?`; selection = elSelected; } else { const type = reliefIconsDiv.querySelector('svg.pressed')?.dataset.type; selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll('use'); const size = selection.size(); alertMessage.innerHTML = type ? `Are you sure you want to remove all ${type} icons (${size})?` : `Are you sure you want to remove all icons (${size})?`; } $('#alert').dialog({ resizable: false, title: 'Remove relief icons', buttons: { Remove: function () { if (selection) selection.remove(); $(this).dialog('close'); $('#reliefEditor').dialog('close'); }, Cancel: function () { $(this).dialog('close'); } } }); } function closeReliefEditor() { terrain.selectAll('use').call(d3.drag().on('drag', null)).classed('draggable', false); removeCircle(); unselect(); clearMainTip(); } }