Title of State:
Improving Object Selection
Goal of Change:
PATCH 1 — State: replace single selectedEntityId with multi-select + drag state
Code to Remove
589 -
selectedEntityId: null, // Id of selected entity
Code to Add
589 -
selectedEntityIds: [], // array of selected entity Ids
selecting: false,
selectStartX: 0, selectStartY: 0,
selectEndX: 0, selectEndY: 0,
PATCH 2 — Mouse input: implement drag-select (mousedown/mousemove/mouseup)
Code to Remove
635 -
canvas.addEventListener("mousemove", (e)=>{
const r = canvas.getBoundingClientRect();
S.mouseX = e.clientX - r.left;
S.mouseY = e.clientY - r.top;
});
canvas.addEventListener("mousedown", (e)=>{
if(e.button!==0) return;
// left click
onClick(S.mouseX, S.mouseY);
});
Code to Add
635 -
canvas.addEventListener("mousemove", (e)=>{
const r = canvas.getBoundingClientRect();
S.mouseX = e.clientX - r.left;
S.mouseY = e.clientY - r.top;
if(S.selecting){
S.selectEndX = S.mouseX;
S.selectEndY = S.mouseY;
}
});
canvas.addEventListener("mousedown", (e)=>{
if(e.button!==0) return;
// begin drag-select
S.selecting = true;
S.selectStartX = S.mouseX;
S.selectStartY = S.mouseY;
S.selectEndX = S.mouseX;
S.selectEndY = S.mouseY;
});
Code to Remove
683 -
window.addEventListener("keyup", (e)=>{
S.keys[e.code] = false;
});
Code to Add
683 -
window.addEventListener("keyup", (e)=>{
S.keys[e.code] = false;
});
// finalize selection on mouseup
window.addEventListener("mouseup", (e)=>{
if(e.button !== 0) return;
if(!S.selecting) return;
S.selecting = false;
const dx = S.selectEndX - S.selectStartX;
const dy = S.selectEndY - S.selectStartY;
const dist2 = dx*dx + dy*dy;
const ctrl = !!(S.keys["ControlLeft"] || S.keys["ControlRight"]);
if(dist2 < 25){
onClick(S.selectEndX, S.selectEndY, ctrl);
} else {
boxSelect(S.selectStartX, S.selectStartY, S.selectEndX, S.selectEndY, ctrl);
}
});
PATCH 3 — Click logic: ctrl toggle + entity-first + resource fallback
Code to Remove
718 -
function onClick(x,y){
if(S.view !== "GAME") return;
// screen -> world
const wx = x + S.camX;
const wy = y + S.camY;
// 1 entity selection first
const eid = findEntityAtWorld(wx, wy);
S.selectedEntityId = eid;
// 2 if no entity hit, fall back to resource selection
if(!eid){
const idx = findResourceAtWorld(wx, wy);
S.selectedResourceIndex = idx;
} else {
S.selectedResourceIndex = -1;
}
}
Code to Add
718-
function onClick(x,y, ctrl){
if(S.view !== "GAME") return;
const wx = x + S.camX;
const wy = y + S.camY;
const eid = findEntityAtWorld(wx, wy);
if(eid){
// entity click
if(ctrl){
const i = S.selectedEntityIds.indexOf(eid);
if(i >= 0) S.selectedEntityIds.splice(i,1);
else S.selectedEntityIds.push(eid);
} else {
S.selectedEntityIds = [eid];
}
S.selectedResourceIndex = -1;
return;
}
// no entity => resource click
if(!ctrl) S.selectedEntityIds = [];
S.selectedResourceIndex = findResourceAtWorld(wx, wy);
}
PATCH 4 — Add boxSelect() helper (vehicles + infantry only)
Code to Remove
1009 -
2 new lines
Code to Add
1010 -
function boxSelect(x1,y1,x2,y2, ctrl){
if(S.view !== "GAME") return;
const minX = Math.min(x1,x2), maxX = Math.max(x1,x2);
const minY = Math.min(y1,y2), maxY = Math.max(y1,y2);
const wMinX = minX + S.camX, wMaxX = maxX + S.camX;
const wMinY = minY + S.camY, wMaxY = maxY + S.camY;
const hits = [];
for(const e of (S.entities || [])){
// vehicles + infantry only
if(e.Kind === "BUILDING") continue;
if(e.X >= wMinX && e.X <= wMaxX && e.Y >= wMinY && e.Y <= wMaxY){
hits.push(e.Id);
}
}
if(ctrl){
for(const id of hits){
if(!S.selectedEntityIds.includes(id)) S.selectedEntityIds.push(id);
}
} else {
S.selectedEntityIds = hits;
}
if(hits.length) S.selectedResourceIndex = -1;
}
PATCH 5 — Right-click: move only selected vehicles
Code to Remove
1118 -
function onCommandMove(screenX, screenY){
// command selected harvester; if none selected, command all harvesters
const wx = screenX + S.camX;
const wy = screenY + S.camY;
const selected = S.selectedEntityId
? S.entities.find(e => e.Id === S.selectedEntityId)
: null;
if(selected && selected.Kind === "VEHICLE"){
selected.TargetX = wx;
selected.TargetY = wy;
return;
}
for(const e of S.entities){
if(e.Kind === "VEHICLE"){
e.TargetX = wx;
e.TargetY = wy;
}
}
}
Code to Add
1118 -
function onCommandMove(screenX, screenY){
const wx = screenX + S.camX;
const wy = screenY + S.camY;
// move ONLY selected vehicles
if(!S.selectedEntityIds || S.selectedEntityIds.length === 0) return;
for(const id of S.selectedEntityIds){
const e = S.entities.find(x => x.Id === id);
if(!e) continue;
if(e.Kind !== "VEHICLE") continue;
e.TargetX = wx;
e.TargetY = wy;
}
}
PATCH 6 — Draw: green selection glow for all selected entities + box rectangle
Code to Remove
1399 -
// selection ring
if(e.Id === S.selectedEntityId){
ctx.globalAlpha = 0.9;
ctx.strokeStyle = "rgba(255,255,255,.85)";
ctx.lineWidth = 2;
const r = Math.max(img.width, img.height)/2 + 8;
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI*2);
ctx.stroke();
}
Code to Add
1399 -
// selection glow (green)
if(S.selectedEntityIds && S.selectedEntityIds.includes(e.Id)){
const rr = Math.max(img.width, img.height)/2 + 10;
ctx.globalAlpha = 0.22;
ctx.strokeStyle = "rgba(0,255,120,1)";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.arc(0, 0, rr, 0, Math.PI*2);
ctx.stroke();
ctx.globalAlpha = 0.85;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, rr, 0, Math.PI*2);
ctx.stroke();
}
Code to Remove
1565 -
// mouse dot (proves input works)
ctx.fillStyle="rgba(255,255,255,.65)";
ctx.beginPath();
ctx.arc(S.mouseX, S.mouseY, 3, 0, Math.PI*2);
ctx.fill();
Code to Add
1565 -
// selection box overlay
if(S.selecting){
const x1 = S.selectStartX, y1 = S.selectStartY;
const x2 = S.selectEndX, y2 = S.selectEndY;
const rx = Math.min(x1,x2), ry = Math.min(y1,y2);
const rw = Math.abs(x2-x1), rh = Math.abs(y2-y1);
ctx.save();
ctx.globalAlpha = 0.9;
ctx.strokeStyle = "rgba(0,255,120,1)";
ctx.lineWidth = 2;
ctx.strokeRect(rx, ry, rw, rh);
ctx.globalAlpha = 0.10;
ctx.fillStyle = "rgba(0,255,120,1)";
ctx.fillRect(rx, ry, rw, rh);
ctx.restore();
}
// mouse dot
ctx.fillStyle="rgba(255,255,255,.65)";
ctx.beginPath();
ctx.arc(S.mouseX, S.mouseY, 3, 0, Math.PI*2);
ctx.fill();
Fix setupEntities() to reset the right selection variable
Code to Remove
1062 -
S.selectedEntityId = null;
Code to Add
1062 -
S.selectedEntityIds = [];
Replace the ENTITY part of updateSelectionHUD() with grouped selection
Code to Remove
1428 -
// 1 entity selection
if(S.selectedEntityId){
const e = S.entities ? S.entities.find(x => x.Id === S.selectedEntityId) : null;
if(e){
const tx = (e.TargetX == null) ? "-" : Math.floor(e.TargetX);
const ty = (e.TargetY == null) ? "-" : Math.floor(e.TargetY);
selhud.textContent =
"ENTITY\\n" +
"Id: " + e.Id + "\\n" +
"Kind: " + e.Kind + "\\n" +
"Sprite: " + e.SpriteKey + "\\n" +
"Owner: " + (e.OwnerColor || "-") + "\\n" +
"Pos: " + Math.floor(e.X) + "," + Math.floor(e.Y) + "\\n" +
"Heading: " + (e.Heading ? e.Heading.toFixed(2) : "0.00") + "\\n" +
"Speed: " + (e.Speed || 0) + "\\n" +
"Target: " + tx + "," + ty;
return;
}
}
Code to Add
1428 -
// 1 entity selection (grouped)
if(S.selectedEntityIds && S.selectedEntityIds.length){
const selected = S.entities ? S.entities.filter(e => S.selectedEntityIds.includes(e.Id)) : [];
const total = selected.length;
const byType = {};
for(const e of selected){
const key = e.SpriteKey || e.Kind || "UNKNOWN";
byType[key] = (byType[key] || 0) + 1;
}
const lines = [];
lines.push("SELECTION");
lines.push("Total: " + total);
lines.push("");
lines.push("By Type:");
for(const k of Object.keys(byType).sort()){
lines.push(" - " + k + ": " + byType[k]);
}
selhud.textContent = lines.join("\\n");
return;
}
Code to Remove
1399 -
// selection glow (green)
if(S.selectedEntityIds && S.selectedEntityIds.includes(e.Id)){
const rr = Math.max(img.width, img.height)/2 + 10;
ctx.globalAlpha = 0.22;
ctx.strokeStyle = "rgba(0,255,120,1)";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.arc(0, 0, rr, 0, Math.PI*2);
ctx.stroke();
ctx.globalAlpha = 0.85;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, rr, 0, Math.PI*2);
ctx.stroke();
}
Code to Add
1399 -
// selection glow (green) — RECTANGULAR around sprite edges
if(S.selectedEntityIds && S.selectedEntityIds.includes(e.Id)){
const pad = 4;
const w = img.width + pad*2;
const h = img.height + pad*2;
const x = -w/2;
const y = -h/2;
// outer glow
ctx.globalAlpha = 0.18;
ctx.strokeStyle = "rgba(0,255,120,1)";
ctx.lineWidth = 10;
ctx.strokeRect(x, y, w, h);
// crisp outline
ctx.globalAlpha = 0.95;
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
}
Result