WIP: add multiple counters to troopers and cells

This commit is contained in:
Catalin Constantin Mititiuc 2025-06-16 22:41:33 -07:00
parent afd0ff2f6a
commit edb5af99c7
10 changed files with 276 additions and 70 deletions

View File

@ -516,30 +516,39 @@ use[class^="counter-"] {
--translateX: -5px; --translateX: -5px;
--translateY: -5px; --translateY: -5px;
transform: scale(var(--scale)) translate(var(--translateX), var(--translateY)); transform: scale(var(--scale)) translate(var(--translateX), var(--translateY));
/*transform: translate(var(--translateX), var(--translateY)) scale(var(--scale));*/
} }
use[class^="counter-"] { use[class^="counter-"] {
transition: x 0.25s, y 0.25s; transition: x 0.25s, y 0.25s;
--scale: 0.5; --scale: 0.5;
/*--translateY: 0px;*/
}
.counter use[href^="#counter"] {
/*transform: scale(0.5);*/
} }
g.counter use[class^="counter-"] { g.counter use[class^="counter-"] {
x: calc(var(--x) * 1.25); x: calc(var(--x) * 1.25);
y: calc(var(--y) * 1.25); y: calc(var(--y) * 1.25);
/*y: 10px;*/
/*x: var(--x);*/
/*y: var(--y);*/
} }
[data-q][data-r][data-s][data-t]:hover use[class^="counter-"], [data-q][data-r][data-s][data-t] > use[class^="counter-"] {
x: calc(var(--x) * 1.5); x: calc(var(--x) / 6);
y: calc(var(--y) * 1.5); y: calc(var(--y) / 6);
/*--scale: 1;*/ }
/* Counters carried by a trooper */
[data-q][data-r][data-s][data-t]:hover g.counter use[class^="counter-"] {
/*x: calc(var(--x) * 1.5);*/
/*y: calc(var(--y) * 1.5);*/
/*--translateY: -5px;*/
}
/* Counters placed on a hex */
[data-q][data-r][data-s][data-t]:hover > g.counter ~ use[class^="counter-"] {
x: calc(var(--x) * 2);
y: calc(var(--y) * 2);
--translateY: -5px;
}
[data-q][data-r][data-s][data-t]:hover > use[class^="counter-"] {
x: calc(var(--x) * 1.5);
y: calc(var(--y) * 1.5);
--translateY: -5px; --translateY: -5px;
} }

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="71"
height="71"
viewBox="0 0 71 71"
version="1.1"
id="svg1"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" /><g
id="layer1"><circle
style="fill:#000000;stroke:none;stroke-width:0.32991;stroke-dasharray:none;stroke-dashoffset:0"
id="path2"
cx="29.117487"
cy="32.638958"
r="3.7584312" /><path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 31.271484,34.935547 -6.664062,4.232422 -5.97461,-3.947266 -2.089843,4.261719 8.039062,4.958984 7.128906,-4.455078 7.513672,2.103516 1.279297,-4.570313 z"
id="path7" /><path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 37.242187,37.804687 0.521485,7.400391 1.986328,1.75 h 13.289062 v -4 H 42.800781 l -0.314453,-1.150391 h 8.075195 8.075196 v -4 H 47.939453 Z"
id="path3" /><path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="M 12.447266,34.826172 V 37.96875 H 29.117187 V 34.826172 Z"
id="path5" /><path
style="color:#000000;fill:#000000;-inkscape-stroke:none"
d="m 31.765625,36.871094 -2.080078,3.416015 8.078125,4.917969 2.082031,-3.416016 z"
id="path8" /></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -28,11 +28,11 @@
<g id="counter-grenade"> <g id="counter-grenade">
<rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/> <rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/>
<image href="counter_grenade.png" width="10"/> <image href="counter_grenade2.svg" width="10"/>
</g> </g>
<g id="counter-prone"> <g id="counter-prone">
<rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/> <rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/>
<image href="counter_prone.png" width="10"/> <image href="counter_prone2.svg" width="10"/>
</g> </g>
<g id="counter-basement"> <g id="counter-basement">
<rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/> <rect x="0" y="0" width="10" height="10" fill="beige" stroke="black" stroke-width="0.5"/>
@ -63,6 +63,6 @@
</g> </g>
<g class="grid"/> <g class="grid"/>
<g class="pieces"/> <g class="grid-top"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -234,12 +234,6 @@
<button type="button" class="set-mech-template">Mech</button> <button type="button" class="set-mech-template">Mech</button>
</span> </span>
<div class="counters-list"> <div class="counters-list">
<!--<img src="assets/images/counter_grenade.png" />-->
<!--<img src="assets/images/counter_prone.png" />-->
<!--<img src="assets/images/counter_basement.png" />-->
<!--<img src="assets/images/counter_1st_floor.png" />-->
<!--<img src="assets/images/counter_2nd_floor.png" />-->
<!--<img src="assets/images/counter_3rd_floor.png" />-->
<button type="button" class="grenade"> <button type="button" class="grenade">
<img src="assets/images/icon_grenade.png" height="12" /> <img src="assets/images/icon_grenade.png" height="12" />
</button> </button>

View File

@ -84,6 +84,11 @@ async function buildScenario(req) {
// recordSheet.start(svg.querySelector('.start-locations'), gameboard.getUnits()); // recordSheet.start(svg.querySelector('.start-locations'), gameboard.getUnits());
recordSheet.start(null, scenarioUnits); recordSheet.start(null, scenarioUnits);
const [trooper] = gameboard.getUnits();
Observable.notify('select', trooper);
gameboard.setCounter('prone');
gameboard.setCounter('1st-floor');
} }
function updateTurnCounter() { function updateTurnCounter() {

View File

@ -34,7 +34,7 @@ function updatePlacement(cell, selected, clone) {
const { q, r, s, t } = clone.parentElement.dataset; const { q, r, s, t } = clone.parentElement.dataset;
selected.dataset.previous = [q, r, s, t]; selected.dataset.previous = [q, r, s, t];
cell.appendChild(selected); placeIn(cell, selected);
Array.from(selected.children).forEach(n => { Array.from(selected.children).forEach(n => {
if (n.classList.contains('removed')) { if (n.classList.contains('removed')) {
@ -56,6 +56,10 @@ function createTrace(previous, current, selected) {
return trace; return trace;
} }
function placeIn(location, target) {
location.querySelector('use[href="#hex"]').after(target);
}
export function createCounter(selected, weapon = 'rifle') { export function createCounter(selected, weapon = 'rifle') {
const g = document.createElementNS(svgns, 'g'); const g = document.createElementNS(svgns, 'g');
const weaponCounter = document.createElementNS(svgns, 'use'); const weaponCounter = document.createElementNS(svgns, 'use');
@ -126,7 +130,7 @@ export function place(svg, selected, cell) {
handleTrace(svg, selected, clone, getCellPosition(cell)); handleTrace(svg, selected, clone, getCellPosition(cell));
} else { } else {
selected.removeAttribute('data-x'); selected.removeAttribute('data-x');
cell.appendChild(selected); placeIn(cell, selected);
} }
} }

View File

@ -34,8 +34,9 @@ function getActiveSightLine(svg) {
return svg.querySelector('line.sight-line.active'); return svg.querySelector('line.sight-line.active');
} }
function isGrenade(el) { function isCounter(el) {
return el && el.getAttribute('href') === '#counter-grenade'; const regex = new RegExp('^#counter-')
return el && regex.test(el.getAttribute('href'));
} }
function isMechTemplate(el) { function isMechTemplate(el) {
@ -196,10 +197,10 @@ function endMove() {
} }
} }
function returnPieces(collection) { function returnPieces(top) {
[...svg.querySelector('.pieces').children].forEach(piece => { [...top.container.children].forEach(piece => {
collection.get(piece).parent.append(piece); top.collection.get(piece).parent.append(piece);
collection.delete(piece); top.collection.delete(piece);
}); });
} }
@ -209,19 +210,19 @@ export function start(el) {
const startingLocations = svg.querySelector('.start-locations'); const startingLocations = svg.querySelector('.start-locations');
startingLocations && getUnits(startingLocations).forEach(unit => unit.addEventListener('click', selectOffBoard)); startingLocations && getUnits(startingLocations).forEach(unit => unit.addEventListener('click', selectOffBoard));
const top = {
const pieces = svg.querySelector('.pieces'); container: svg.querySelector('.grid-top'),
const inFront = new Map(); collection: new Map()
let inFrontParent; };
//addEventListener('pointerout', e => { returnPieces(inFront) });
getCells(svg).forEach(cell => { getCells(svg).forEach(cell => {
cell.addEventListener('click', e => { cell.addEventListener('click', e => {
const occupant = getCellOccupant(cell); const occupant = getCellOccupant(cell);
let toPlace = placing.pop(); let toPlace = placing.pop();
if (isGrenade(toPlace) || isMechTemplate(toPlace)) { if (isCounter(toPlace) || isMechTemplate(toPlace)) {
getHex(cell).after(toPlace); getHex(cell).after(toPlace);
if (isCounter(toPlace)) arrangeCounters(cell);
removeEventListener("keydown", handleMechTemplateRotation); removeEventListener("keydown", handleMechTemplateRotation);
} else if (toPlace && !occupant) { } else if (toPlace && !occupant) {
soldier.place(svg, toPlace, cell); soldier.place(svg, toPlace, cell);
@ -282,12 +283,12 @@ export function start(el) {
}); });
cell.addEventListener('pointerover', () => { cell.addEventListener('pointerover', () => {
if (!pieces.contains(cell)) { if (!top.container.contains(cell)) {
returnPieces(inFront); returnPieces(top);
inFront.set(cell, { parent: cell.parentElement }); top.collection.set(cell, { parent: cell.parentElement });
pieces.append(cell); top.container.append(cell);
} }
console.log(top.container.children[0].children);
const selected = getSelected(); const selected = getSelected();
if (placing[0]?.getAttributeNS(null, 'class') == 'mech-template') { if (placing[0]?.getAttributeNS(null, 'class') == 'mech-template') {
@ -307,22 +308,6 @@ export function start(el) {
}); });
cell.addEventListener('pointerout', () => { cell.addEventListener('pointerout', () => {
//if (inFront && inFrontParent) {
// inFrontParent.append(inFront);
// inFront = null;
// inFrontParent = null;
//}
//for (const [el, parent] of inFront) {
// parent.append(el);
// inFront.delete(el);
//}
//[...pieces.children].forEach(piece => {
// inFront.get(piece).parent.append(piece);
// inFront.delete(piece);
//});
//returnPieces(inFront);
getActiveSightLine(svg) && clearSightLine(); getActiveSightLine(svg) && clearSightLine();
const occupant = getCellOccupant(cell); const occupant = getCellOccupant(cell);
@ -334,27 +319,27 @@ export function start(el) {
}); });
// debug // // debug //
// Add a trooper counter
const attacker = { dataset: { allegiance: 'attacker', number: 1, squad: 1 }}; const attacker = { dataset: { allegiance: 'attacker', number: 1, squad: 1 }};
// const defender = { dataset: { allegiance: 'defender', number: 1, squad: 2 }}; // const defender = { dataset: { allegiance: 'defender', number: 1, squad: 2 }};
const cell = getCell(0, 0, 0, 0);
const cell = getCell(2, 0, -2, 0);
const trooper = soldier.createCounter(attacker, 'blazer'); const trooper = soldier.createCounter(attacker, 'blazer');
soldier.place(svg, trooper, cell); soldier.place(svg, trooper, cell);
// Add some counters in an unoccupied cell
const countersCell = getCell(-1, 1, 0, 0);
setCounter('grenade');
setCounter('prone');
setCounter('1st-floor');
const e = new PointerEvent('click');
countersCell.dispatchEvent(e);
countersCell.dispatchEvent(e);
countersCell.dispatchEvent(e);
/////////// ///////////
Observable.subscribe('select', select); Observable.subscribe('select', select);
Observable.subscribe('endmove', endMove); Observable.subscribe('endmove', endMove);
Observable.notify('select', trooper);
//Array(1).fill(null).forEach(() => {
// const counter = document.createElementNS(svgns, 'use');
// counter.setAttributeNS(null, 'href', '#counter-grenade');
// counter.classList.add('counter-grenade');
// trooper.appendChild(counter);
//});
//
//setGrenade();
console.log('gameboard.js loaded'); console.log('gameboard.js loaded');
} }
@ -427,6 +412,30 @@ export function setCounter(name) {
placing.push(counter); placing.push(counter);
} }
function arrangeCounters(cell) {
const counters = cell.querySelectorAll('[class^="counter-"]');
const length = 12;
const gravity = 1;
const lateralForce = gravity;
const rads = Math.atan(lateralForce / gravity);
const bestFitCount = 8;
const deflection = counters.length > bestFitCount ? 2 * Math.PI / counters.length : Math.atan(lateralForce / gravity);
counters.forEach((counter, index, arr) => {
const mult = index - arr.length / 2 + 0.5;
const theta = deflection * mult;
const x = length * Math.sin(theta);
const y = length * Math.cos(theta);
//counter.setAttributeNS(null, 'x', 0);
//counter.setAttributeNS(null, 'y', 10);
//counter.setAttributeNS(null, 'transform', `rotate(${theta * 180 / Math.PI})`);
//counter.setAttributeNS(null, 'style', `y: 10px; transform-origin: 5px 0; transform: translateX(-5px) rotate(${theta * 180 / Math.PI}deg)`);
counter.setAttributeNS(null, 'style', `--x: ${-x}px; --y: ${y}px`);
//counter.setAttributeNS(null, 'style', `x: 0; y: 10px;`);
});
console.log(counters);
}
function handleMechTemplateRotation(event) { function handleMechTemplateRotation(event) {
const counter = placing[0]; const counter = placing[0];
const upper = placing[0].querySelector('use[href="#mech-template-upper"]'); const upper = placing[0].querySelector('use[href="#mech-template-upper"]');