flatland
JUNK tech: flatland - draw line of sight
credit to Cornbread for line of sight algorithm
a preview of future line of site content
try it out in console: tech.giveTech("flatland")
new images
bug fixes
BIN
img/aerogel.webp
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 70 KiB |
@@ -6751,7 +6751,7 @@ const b = {
|
||||
},
|
||||
{
|
||||
name: "wave", //3
|
||||
description: "emit a <strong>wave packet</strong> of oscillating particles<br>that propagates through <strong>solids</strong>",
|
||||
description: "emit <strong>wave packets</strong> that propagate through <strong>solids</strong><br>waves <strong class='color-s'>slow</strong> mobs", // of oscillating particles<br>
|
||||
ammo: 0,
|
||||
ammoPack: 115,
|
||||
defaultAmmoPack: 115,
|
||||
|
||||
3804
js/level.js
253
js/simulation.js
@@ -388,6 +388,213 @@ const simulation = {
|
||||
}
|
||||
requestAnimationFrame(loop)
|
||||
},
|
||||
|
||||
sight: { //credit to Cornbread for adding this algorithm to n-gon
|
||||
intersectMap: [], //this is precalculated in simulation.draw.setPaths() when the map changes
|
||||
getIntersection(v1, v1End, domain) {
|
||||
const intersections = simulation.sight.getIntersections(v1, v1End, domain);
|
||||
var best = { x: v1End.x, y: v1End.y, dist: Math.sqrt((v1End.x - v1.x) ** 2 + (v1End.y - v1.y) ** 2) }
|
||||
for (const intersection of intersections) {
|
||||
const dist = Math.sqrt((intersection.x - v1.x) ** 2 + (intersection.y - v1.y) ** 2);
|
||||
if (dist < best.dist) best = { x: intersection.x, y: intersection.y, dist: dist }
|
||||
}
|
||||
return best;
|
||||
},
|
||||
getIntersections(v1, v1End, domain) {
|
||||
const intersections = [];
|
||||
for (const obj of domain) {
|
||||
for (var i = 0; i < obj.vertices.length - 1; i++) {
|
||||
results = simulation.checkLineIntersection(v1, v1End, obj.vertices[i], obj.vertices[i + 1]);
|
||||
if (results.onLine1 && results.onLine2) intersections.push({ x: results.x, y: results.y });
|
||||
}
|
||||
results = simulation.checkLineIntersection(v1, v1End, obj.vertices[obj.vertices.length - 1], obj.vertices[0]);
|
||||
if (results.onLine1 && results.onLine2) intersections.push({ x: results.x, y: results.y });
|
||||
}
|
||||
return intersections;
|
||||
},
|
||||
circleLoS(pos, radius) {
|
||||
function allCircleLineCollisions(c, radius, domain) {
|
||||
var lines = [];
|
||||
for (const obj of domain) {
|
||||
for (var i = 0; i < obj.vertices.length - 1; i++) lines.push(circleLineCollisions(obj.vertices[i], obj.vertices[i + 1], c, radius));
|
||||
lines.push(circleLineCollisions(obj.vertices[obj.vertices.length - 1], obj.vertices[0], c, radius));
|
||||
}
|
||||
const collisionLines = [];
|
||||
for (const line of lines) {
|
||||
if (line.length == 2) {
|
||||
// const distance1 = Math.sqrt((line[0].x - c.x) ** 2 + (line[0].y - c.y) ** 2)
|
||||
// const angle1 = Math.atan2(line[0].y - c.y, line[0].x - c.x);
|
||||
// const queryPoint1 = {
|
||||
// x: Math.cos(angle1) * (distance1 - 1) + c.x,
|
||||
// y: Math.sin(angle1) * (distance1 - 1) + c.y
|
||||
// }
|
||||
// const distance2 = Math.sqrt((line[1].x - c.x) ** 2 + (line[1].y - c.y) ** 2)
|
||||
// const angle2 = Math.atan2(line[1].y - c.y, line[1].x - c.x);
|
||||
// const queryPoint2 = {
|
||||
// x: Math.cos(angle2) * (distance2 - 1) + c.x,
|
||||
// y: Math.sin(angle2) * (distance2 - 1) + c.y
|
||||
// }
|
||||
collisionLines.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
return collisionLines;
|
||||
}
|
||||
|
||||
function circleLineCollisions(a, b, c, radius) {
|
||||
// calculate distances
|
||||
const angleOffset = Math.atan2(b.y - a.y, b.x - a.x);
|
||||
const sideB = Math.sqrt((a.x - c.x) ** 2 + (a.y - c.y) ** 2);
|
||||
const sideC = Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2);
|
||||
const sideA = Math.sqrt((c.x - b.x) ** 2 + (c.y - b.y) ** 2);
|
||||
|
||||
// calculate the closest point on line AB to point C
|
||||
const angleA = Math.acos((sideB ** 2 + sideC ** 2 - sideA ** 2) / (2 * sideB * sideC)) * (a.x - c.x) / -Math.abs(a.x - c.x)
|
||||
const sideAD = Math.cos(angleA) * sideB;
|
||||
const d = { // closest point
|
||||
x: Math.cos(angleOffset) * sideAD + a.x,
|
||||
y: Math.sin(angleOffset) * sideAD + a.y
|
||||
}
|
||||
const distance = Math.sqrt((d.x - c.x) ** 2 + (d.y - c.y) ** 2);
|
||||
if (distance == radius) {
|
||||
// tangent
|
||||
return [d];
|
||||
} else if (distance < radius) {
|
||||
// secant
|
||||
const angleOffset = Math.atan2(d.y - c.y, d.x - c.x);
|
||||
const innerAngle = Math.acos(distance / radius);
|
||||
const intersection1 = {
|
||||
x: Math.cos(angleOffset + innerAngle) * radius + c.x,
|
||||
y: Math.sin(angleOffset + innerAngle) * radius + c.y
|
||||
}
|
||||
|
||||
const intersection2 = {
|
||||
x: Math.cos(angleOffset - innerAngle) * radius + c.x,
|
||||
y: Math.sin(angleOffset - innerAngle) * radius + c.y
|
||||
}
|
||||
|
||||
const distance1 = {
|
||||
a: Math.sqrt((intersection1.x - a.x) ** 2 + (intersection1.y - a.y) ** 2),
|
||||
b: Math.sqrt((intersection1.x - b.x) ** 2 + (intersection1.y - b.y) ** 2)
|
||||
}
|
||||
const distance2 = {
|
||||
a: Math.sqrt((intersection2.x - a.x) ** 2 + (intersection2.y - a.y) ** 2),
|
||||
b: Math.sqrt((intersection2.x - b.x) ** 2 + (intersection2.y - b.y) ** 2)
|
||||
}
|
||||
const result = [];
|
||||
if (Math.abs(sideC - (distance1.a + distance1.b)) < 0.01) {
|
||||
result.push(intersection1);
|
||||
} else {
|
||||
if (distance1.a < distance1.b) {
|
||||
if (sideB <= radius) result.push(a);
|
||||
} else {
|
||||
if (sideA <= radius) result.push(b)
|
||||
}
|
||||
}
|
||||
if (Math.abs(sideC - (distance2.a + distance2.b)) < 0.01) {
|
||||
result.push(intersection2);
|
||||
} else {
|
||||
if (distance2.a <= distance2.b) {
|
||||
if (sideB <= radius) result.push(a);
|
||||
} else {
|
||||
if (sideA <= radius) result.push(b)
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else {
|
||||
// no intersection
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
var vertices = [];
|
||||
for (const obj of simulation.sight.intersectMap) {
|
||||
for (var i = 0; i < obj.vertices.length; i++) {
|
||||
const vertex = obj.vertices[i];
|
||||
const angleToVertex = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
const distanceToVertex = Math.sqrt((vertex.x - pos.x) ** 2 + (vertex.y - pos.y) ** 2);
|
||||
const queryPoint = { x: Math.cos(angleToVertex) * (distanceToVertex - 1) + pos.x, y: Math.sin(angleToVertex) * (distanceToVertex - 1) + pos.y }
|
||||
|
||||
if (Matter.Query.ray(map, pos, queryPoint).length == 0) {
|
||||
var distance = Math.sqrt((vertex.x - pos.x) ** 2 + (vertex.y - pos.y) ** 2);
|
||||
var endPoint = { x: vertex.x, y: vertex.y }
|
||||
|
||||
if (distance > radius) {
|
||||
const angle = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
endPoint = { x: Math.cos(angle) * radius + pos.x, y: Math.sin(angle) * radius + pos.y }
|
||||
distance = radius
|
||||
}
|
||||
|
||||
var best = simulation.sight.getIntersection(pos, endPoint, map);
|
||||
if (best.dist >= distance) best = { x: endPoint.x, y: endPoint.y, dist: distance }
|
||||
vertices.push(best)
|
||||
|
||||
var angle = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
endPoint = { x: Math.cos(angle + 0.001) * radius + pos.x, y: Math.sin(angle + 0.001) * radius + pos.y }
|
||||
best = simulation.sight.getIntersection(pos, endPoint, map);
|
||||
|
||||
if (best.dist >= radius) best = { x: endPoint.x, y: endPoint.y, dist: radius }
|
||||
vertices.push(best)
|
||||
|
||||
angle = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
endPoint = { x: Math.cos(angle - 0.001) * radius + pos.x, y: Math.sin(angle - 0.001) * radius + pos.y }
|
||||
|
||||
best = simulation.sight.getIntersection(pos, endPoint, map);
|
||||
if (best.dist >= radius) best = { x: endPoint.x, y: endPoint.y, dist: radius }
|
||||
vertices.push(best)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const outerCollisions = allCircleLineCollisions(pos, radius, map);
|
||||
const circleCollisions = [];
|
||||
for (const line of outerCollisions) {
|
||||
for (const vertex of line) {
|
||||
const distance = Math.sqrt((vertex.x - pos.x) ** 2 + (vertex.y - pos.y) ** 2)
|
||||
const angle = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
const queryPoint = {
|
||||
x: Math.cos(angle) * (distance - 1) + pos.x,
|
||||
y: Math.sin(angle) * (distance - 1) + pos.y
|
||||
}
|
||||
if (Math.abs(distance - radius) < 1 && Matter.Query.ray(map, pos, queryPoint).length == 0) circleCollisions.push(vertex)
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < circleCollisions.length; i++) {
|
||||
const vertex = circleCollisions[i];
|
||||
var nextIndex = i + 1;
|
||||
if (nextIndex == circleCollisions.length) nextIndex = 0;
|
||||
const nextVertex = circleCollisions[nextIndex];
|
||||
const angle1 = Math.atan2(vertex.y - pos.y, vertex.x - pos.x);
|
||||
const angle2 = Math.atan2(nextVertex.y - pos.y, nextVertex.x - pos.x);
|
||||
var newAngle;
|
||||
if (Math.abs(angle1) > Math.PI / 2 && Math.abs(angle2) > Math.PI / 2 && angle1 / Math.abs(angle1) != angle2 / Math.abs(angle2)) {
|
||||
// if the arc between the to points crosses over the left side (+/- pi radians)
|
||||
const newAngle1 = (Math.PI - Math.abs(angle1)) * (angle1 / Math.abs(angle1));
|
||||
const newAngle2 = (Math.PI - Math.abs(angle2)) * (angle2 / Math.abs(angle2));
|
||||
newAngle = (newAngle1 + newAngle2) / 2;
|
||||
var multiplier;
|
||||
if (newAngle == 0) {
|
||||
multiplier = 1;
|
||||
} else {
|
||||
multiplier = newAngle / Math.abs(newAngle);
|
||||
}
|
||||
newAngle = Math.PI * multiplier - newAngle * multiplier;
|
||||
test = true;
|
||||
} else {
|
||||
newAngle = (angle1 + angle2) / 2;
|
||||
}
|
||||
|
||||
// shoot ray between them
|
||||
var endPoint = { x: Math.cos(newAngle) * radius + pos.x, y: Math.sin(newAngle) * radius + pos.y }
|
||||
var best = simulation.sight.getIntersection(pos, endPoint, map);
|
||||
vertices.push(vertex);
|
||||
if (best.dist <= radius) vertices.push({ x: best.x, y: best.y })
|
||||
}
|
||||
vertices.sort((a, b) => Math.atan2(a.y - pos.y, a.x - pos.x) - Math.atan2(b.y - pos.y, b.x - pos.x));
|
||||
return vertices;
|
||||
},
|
||||
},
|
||||
boldActiveGunHUD() {
|
||||
if (b.inventory.length > 0) {
|
||||
for (let i = 0, len = b.inventory.length; i < len; ++i) {
|
||||
@@ -398,23 +605,9 @@ const simulation = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (b.inventory.length > 0) {
|
||||
// for (let i = 0, len = b.inventory.length; i < len; ++i) document.getElementById(b.inventory[i]).style.opacity = "0.3";
|
||||
// // document.getElementById(b.activeGun).style.fontSize = "30px";
|
||||
// if (document.getElementById(b.activeGun)) document.getElementById(b.activeGun).style.opacity = "1";
|
||||
// }
|
||||
},
|
||||
updateGunHUD() {
|
||||
// for (let i = 0, len = b.inventory.length; i < len; ++i) {
|
||||
// if (flashIndex === i) {
|
||||
// document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo;
|
||||
// } else {
|
||||
// document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo;
|
||||
// }
|
||||
// }
|
||||
for (let i = 0, len = b.inventory.length; i < len; ++i) {
|
||||
// document.getElementById(b.inventory[i]).innerHTML = b.guns[b.inventory[i]].name + " - " + b.guns[b.inventory[i]].ammo;
|
||||
document.getElementById(b.inventory[i]).innerHTML = `${b.guns[b.inventory[i]].name} - ${b.guns[b.inventory[i]].ammo}`
|
||||
}
|
||||
},
|
||||
@@ -1395,6 +1588,38 @@ const simulation = {
|
||||
}
|
||||
simulation.draw.mapPath.lineTo(vertices[0].x, vertices[0].y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//store data for line of sight precalculation
|
||||
simulation.sight.intersectMap = [];
|
||||
for (var i = 0; i < map.length; i++) {
|
||||
const obj = map[i];
|
||||
const newVertices = [];
|
||||
const restOfMap = [...map].slice(0, i).concat([...map].slice(i + 1))
|
||||
for (var j = 0; j < obj.vertices.length - 1; j++) {
|
||||
var intersections = simulation.sight.getIntersections(obj.vertices[j], obj.vertices[j + 1], restOfMap);
|
||||
newVertices.push(obj.vertices[j]);
|
||||
for (const vertex of intersections) newVertices.push({ x: vertex.x, y: vertex.y });
|
||||
}
|
||||
intersections = simulation.sight.getIntersections(obj.vertices[obj.vertices.length - 1], obj.vertices[0], restOfMap);
|
||||
newVertices.push(obj.vertices[obj.vertices.length - 1]);
|
||||
for (const vertex of intersections) newVertices.push({ x: vertex.x, y: vertex.y });
|
||||
//draw the vertices as black circles for debugging
|
||||
// for (const vertex of newVertices) {
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(vertex.x, vertex.y);
|
||||
// ctx.arc(vertex.x, vertex.y, 10, 0, 2 * Math.PI);
|
||||
// ctx.fillStyle = '#000';
|
||||
// ctx.fill()
|
||||
// }
|
||||
simulation.sight.intersectMap.push({ vertices: newVertices });
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
drawMapPath() {
|
||||
ctx.fillStyle = color.map;
|
||||
|
||||
72
js/tech.js
@@ -523,7 +523,7 @@ const tech = {
|
||||
{
|
||||
name: "causality bombs",
|
||||
link: `<a target="_blank" href='https://en.wikipedia.org/wiki/Causality' class="link">causality bombs</a>`,
|
||||
description: "when you <strong class='color-rewind'>rewind</strong> drop several <strong>grenades</strong><br>become <strong>invulnerable</strong> until they <strong class='color-e'>explode</strong>",
|
||||
description: "when you <strong class='color-rewind'>rewind</strong> drop several <strong>grenades</strong>", //<br>become <strong>invulnerable</strong> until they <strong class='color-e'>explode</strong>
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
frequency: 2,
|
||||
@@ -7257,9 +7257,9 @@ const tech = {
|
||||
frequency: 2,
|
||||
frequencyDefault: 2,
|
||||
allowed() {
|
||||
return (m.fieldMode === 4 && tech.deflectEnergy === 0) || (m.fieldMode === 1 && tech.harmonics === 2)
|
||||
return m.fieldMode === 1 && tech.harmonics === 2
|
||||
},
|
||||
requires: "molecular assembler, standing wave, not electric generator",
|
||||
requires: "standing wave",
|
||||
effect() {
|
||||
tech.isLaserField = true
|
||||
},
|
||||
@@ -7836,9 +7836,9 @@ const tech = {
|
||||
frequency: 2,
|
||||
frequencyDefault: 2,
|
||||
allowed() {
|
||||
return m.fieldMode === 4 && !tech.isLaserField
|
||||
return m.fieldMode === 4
|
||||
},
|
||||
requires: "molecular assembler, not surface plasmon",
|
||||
requires: "molecular assembler",
|
||||
effect() {
|
||||
tech.deflectEnergy += 0.5;
|
||||
},
|
||||
@@ -8360,7 +8360,7 @@ const tech = {
|
||||
},
|
||||
{
|
||||
name: "vacuum fluctuation",
|
||||
description: `use ${powerUps.orb.research(3)}to exploit your <strong class='color-f'>field</strong> for a<br><strong>+11%</strong> chance to <strong class='color-dup'>duplicate</strong> spawned <strong>power ups</strong>`,
|
||||
description: `use ${powerUps.orb.research(3)}<br><strong>+11%</strong> chance to <strong class='color-dup'>duplicate</strong> spawned <strong>power ups</strong>`,
|
||||
isFieldTech: true,
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
@@ -10081,6 +10081,66 @@ const tech = {
|
||||
},
|
||||
remove() { }
|
||||
},
|
||||
{
|
||||
name: "flatland",
|
||||
description: "map blocks line of sight",
|
||||
maxCount: 1,
|
||||
count: 0,
|
||||
frequency: 0,
|
||||
isNonRefundable: true,
|
||||
isJunk: true,
|
||||
allowed() { return true },
|
||||
requires: "",
|
||||
effect() {
|
||||
simulation.ephemera.push({
|
||||
name: "LoS", count: 0, do() {
|
||||
const pos = m.pos
|
||||
const radius = 5000
|
||||
if (!simulation.isTimeSkipping) {
|
||||
const vertices = simulation.sight.circleLoS(pos, radius);
|
||||
if (vertices.length) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
for (var i = 1; i < vertices.length; i++) {
|
||||
var currentDistance = Math.sqrt((vertices[i - 1].x - pos.x) ** 2 + (vertices[i - 1].y - pos.y) ** 2);
|
||||
var newDistance = Math.sqrt((vertices[i].x - pos.x) ** 2 + (vertices[i].y - pos.y) ** 2);
|
||||
if (Math.abs(currentDistance - radius) < 1 && Math.abs(newDistance - radius) < 1) {
|
||||
const currentAngle = Math.atan2(vertices[i - 1].y - pos.y, vertices[i - 1].x - pos.x);
|
||||
const newAngle = Math.atan2(vertices[i].y - pos.y, vertices[i].x - pos.x);
|
||||
ctx.arc(pos.x, pos.y, radius, currentAngle, newAngle);
|
||||
} else {
|
||||
ctx.lineTo(vertices[i].x, vertices[i].y)
|
||||
}
|
||||
}
|
||||
newDistance = Math.sqrt((vertices[0].x - pos.x) ** 2 + (vertices[0].y - pos.y) ** 2);
|
||||
currentDistance = Math.sqrt((vertices[vertices.length - 1].x - pos.x) ** 2 + (vertices[vertices.length - 1].y - pos.y) ** 2);
|
||||
if (Math.abs(currentDistance - radius) < 1 && Math.abs(newDistance - radius) < 1) {
|
||||
const currentAngle = Math.atan2(vertices[vertices.length - 1].y - pos.y, vertices[vertices.length - 1].x - pos.x);
|
||||
const newAngle = Math.atan2(vertices[0].y - pos.y, vertices[0].x - pos.x);
|
||||
ctx.arc(pos.x, pos.y, radius, currentAngle, newAngle);
|
||||
} else {
|
||||
ctx.lineTo(vertices[0].x, vertices[0].y)
|
||||
}
|
||||
//stroke the map, so it looks different form the line of sight
|
||||
ctx.strokeStyle = "#234";
|
||||
ctx.lineWidth = 9;
|
||||
ctx.stroke(simulation.draw.mapPath);
|
||||
|
||||
ctx.globalCompositeOperation = "destination-in";
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fill();
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
// also see the map
|
||||
// ctx.fill(simulation.draw.mapPath);
|
||||
// ctx.fillStyle = "#000";
|
||||
ctx.clip();
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
remove() { }
|
||||
},
|
||||
{
|
||||
name: "umbra",
|
||||
description: "produce a blue glow around everything<br>and probably some simulation lag",
|
||||
|
||||
47
todo.txt
@@ -1,21 +1,48 @@
|
||||
******************************************************** NEXT PATCH **************************************************
|
||||
|
||||
factory: rewrote the end
|
||||
JUNK tech: flatland - draw line of sight
|
||||
credit to Cornbread for line of sight algorithm
|
||||
a preview of future line of site content
|
||||
try it out in console: tech.giveTech("flatland")
|
||||
|
||||
clock gating was removed because it's annoying
|
||||
liquid cooling -> refrigerant - freezes mobs after losing at least 5% health
|
||||
mass-energy gets more effect from defense (0.13 -> 0.19)
|
||||
ternary 84 -> 77% damage
|
||||
dark patterns 15 -> 17% damage and JUNK
|
||||
Maxwell's demon 3% -> 1% energy loss above max
|
||||
exciton 16 -> 14% chance to drop
|
||||
|
||||
10% increase in overall mob health
|
||||
new images
|
||||
bug fixes
|
||||
|
||||
*********************************************************** TODO *****************************************************
|
||||
|
||||
LoS
|
||||
LoS clipping
|
||||
performance
|
||||
calculate things that don't change when the map ctx is done
|
||||
like the intersections?
|
||||
rewrite code for infinite range
|
||||
how to use this?
|
||||
JUNK tech?
|
||||
custom level?
|
||||
give it a dark back ground for contrast?
|
||||
game setting?
|
||||
boss that you need to avoid, probably requires a custom level
|
||||
boss is a source of light
|
||||
level that is dark, and you can only see LoS
|
||||
LoS with limited radius
|
||||
explosion graphic
|
||||
mobs area of effect damage
|
||||
maybe make a shared mob AoE damage function
|
||||
standing wave graphic
|
||||
|
||||
Also another thing I made that could fit in-game: https://kgurchiek.github.io/universal-n-gon-loader/
|
||||
by default it just plays a random version of n-gon downloaded from past github commits
|
||||
maybe the "snapshots" could work like this rather than downloading 8 versions of the game?
|
||||
also you can play any version with https://kgurchiek.github.io/universal-n-gon-loader/?commitIndex=NUM
|
||||
where setting "NUM" to 0 is the very first commit
|
||||
here's the code if you want to check it out: https://github.com/kgurchiek/universal-n-gon-loader/blob/main/script.js
|
||||
|
||||
level - funicular
|
||||
The system is characterized by two counterbalanced carriages (also called cars or trains) permanently attached to opposite ends of a haulage cable, which is looped over a pulley at the upper end of the track.[2][3] The result of such a configuration is that the two carriages move synchronously: as one ascends, the other descends at an equal speed.
|
||||
|
||||
missile bot and plasma bot don't get converted by bot upgrade tech?
|
||||
is this more confusing because it contradicts text?
|
||||
|
||||
use ephemera to replace things
|
||||
JUNK?
|
||||
request animation stuff
|
||||
|
||||