SC CODE: // Copyright 2024. Civilware. All rights reserved.
// TELA Decentralized Web Document (TELA-DOC-1)
Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "aihelpers.js")
31 STORE("descrHdr", "AI helpers file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "aihelpers.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "26e58246f6ca09d92181a73379220599e9a5c46bd5a4bde5ea0b835ffb0b31d4")
37 STORE("fileCheckS", "26527e430eed734885608b370e1b91c7fee2ca564419b352ce8c27091d68eefa")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
function isUnderOrFacingSegment(enemy, target) {
const left = Math.min(target.x1, target.x2);
const right = Math.max(target.x1, target.x2);
if(enemy.x < left && enemy.facing ===1){
return true;
}else if(enemy.x > right && enemy.facing ===-1){
return true;
}else if(enemy.x >= left && enemy.x <= right){
return true;
}
return false;
}
function isSegmentBelowSegment(segA, segB, threshold = 0) {
// 1. Horizontal overlap check
const leftA = Math.min(segA.x1, segA.x2);
const rightA = Math.max(segA.x1, segA.x2);
const leftB = Math.min(segB.x1, segB.x2);
const rightB = Math.max(segB.x1, segB.x2);
const overlap = !(rightA < leftB - 1 || leftA > rightB + 1);
if (!overlap) return false;
// 2. Compute average heights (handles slopes)
const avgA = (segA.y1 + segA.y2) * 0.5;
const avgB = (segB.y1 + segB.y2) * 0.5;
// 3. A is below B if its average y is greater (screen coords)
return avgA > avgB + threshold;
}
function getPlatformById(id) {
for (const plat of game.platforms) {
if(plat.id === id)return plat;
}
}
function resetAttacks(enemy){
enemy.wantKick = false;
enemy.wantPunch = false;
}
function playerIsThreatening(player) {
return player.wantKick ||
player.wantPunch ||
player.anim.state === "kick" ||
player.anim.state === "highKick" ||
player.anim.state === "punch";
}
function countEnemiesBehind(enemy) {
let count = 0;
for (const e of game.enemies) {
if (e === enemy) continue;
if (e.dead) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Must be behind relative to facing
if (enemy.facing === 1 && e.x < enemy.x) count++;
if (enemy.facing === -1 && e.x > enemy.x) count++;
}
return count;
}
function countEnemiesAhead(enemy) {
let count = 0;
for (const e of game.enemies) {
if (e === enemy) continue;
if (e.dead) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Must be behind relative to facing
if (enemy.facing === 1 && e.x > enemy.x) count++;
if (enemy.facing === -1 && e.x < enemy.x) count++;
}
return count;
}
function keepEnemySpacing(enemy) {
const spacing = 50; // how much space they want
for (const e of game.enemies) {
if (e === enemy || e.dead || e.inAir) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Check if e is in front of enemy
const dx = e.x - enemy.x;
if (enemy.facing === 1 && dx > 0 && dx < spacing) {
return true;
}
if (enemy.facing === -1 && dx < 0 && -dx < spacing) {
return true;
}
}
return false;
}
function avoidWalls(enemy){
if(directionBlocked(enemy) && !enemy.inAir){
enemy.facing = -enemy.facing;
enemy.moveInput = enemy.facing
if(directionBlocked(enemy)){
enemy.moveInput = 0;
}
}
}
function avoidWallsOnSamePlatform(enemy, wallSeg){
if(directionBlocked(enemy) && !enemy.inAir && enemy.onPlatform === enemy.blockedP && !aiCanJumpOver(enemy, wallSeg)){
enemy.facing = -enemy.facing;
enemy.moveInput = enemy.facing
if(directionBlocked(enemy)){
enemy.moveInput = 0;
}
}
}
function directionBlocked(enemy){
if(enemy.facing === 1 && enemy.blockedRight) return true;
if(enemy.facing === -1 && enemy.blockedLeft) return true;
return false
}
function detectWallAhead(enemy) {
for (let plat of game.platforms) {
for (let seg of plat.segments) {
const direction = aiCheckWallAhead(enemy, seg)
if (direction) {
if(direction === "right"){
enemy.blockedP = plat.id;
enemy.blockedRight = true;
enemy.blockedLeft = false;
return seg
}else{
enemy.blockedP = plat.id;
enemy.blockedLeft = true;
enemy.blockedRight = false;
return seg
}
}
}
}
return null;
}
function aiCanJumpOver(ai, seg) {
if(!seg)return true;
const wallHeight = seg.maxY - seg.minY;
return wallHeight <= 100;
}
function aiCheckWallAhead(ai, seg, lookAheadMax = 40) {
if (!seg.isWall) return false;
const top = ai.y + ai.height * .25;
const bottom = ai.y + ai.height * 0.5;
// vertical overlap
if (bottom -1 <= seg.minY || top +1 >= seg.maxY) return false;
const dist = seg.x1 - ai.x
if (Math.abs(dist) <= lookAheadMax && dist > 0) {
return "right" ;
}else if (Math.abs(dist) <= lookAheadMax && dist < 0){
return "left" ;
}
return false;
}
function isNearPlatformEdge(enemy, plat, threshold = 20) {
const left = plat.minX;
const right = plat.maxX;
const nearLeft = enemy.x < left + threshold;
const nearRight = enemy.x > right - threshold;
// Only count as an edge if there's no connected segment
if (nearLeft) return true;
if (nearRight) return true;
return false;
}
function isNearEdgeFacing(enemy, platform, threshold = 20) {
const left = platform.minX;
const right = platform.maxX;
if (enemy.facing < 0) {
// Facing left
return enemy.x <= left + threshold;
} else {
// Facing right
return enemy.x >= right - threshold;
}
}
function isFacingSegment(enemy, segment){
if (enemy.facing < 0) {
// Facing left
return enemy.x > segment.midX;
} else {
// Facing right
return enemy.x < segment.midX;
}
}
function isPlayerBelowPlatform(plat, threshold = 20) {
const px = game.player.x;
const py = game.player.y;
for (const seg of plat.segments) {
// Optional: only treat floor-type segments as platform surface
if (seg.isWall)
continue;
const x1 = seg.x1;
const x2 = seg.x2;
const y1 = seg.y1;
const y2 = seg.y2;
// Skip vertical / near-vertical segments (walls)
const dx = x2 - x1;
if (Math.abs(dx) < 0.0001)
continue;
const left = Math.min(x1, x2);
const right = Math.max(x1, x2);
// Player must be horizontally under this segment (with small margin)
if (px < left - threshold || px > right + threshold)
continue;
// Interpolate the platform height at px
let t = (px - x1) / dx;
// Clamp t so we don't extend beyond the segment
if (t < 0 || t > 1)
continue;
const y_at_px = y1 + t * (y2 - y1);
// Player must be below the surface
if (py > y_at_px + 10)
return true;
}
return false;
}
function isSafeToDrop(enemy) {
const feetX = enemy.x;
const feetY = enemy.y + enemy.height / 2;
const segBelow = getPlatformBelowPoint(feetX, feetY);
return segBelow !== null;
}
function getPlatformBelowPoint(x, y) {
let best = null;
let bestY = Infinity;
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if(seg.isWall)continue;
// Skip segments above or at the same height
const segTop = Math.min(seg.y1, seg.y2);
if (segTop <= y) continue;
// Check if x is within segment horizontal span
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (x < left || x > right) continue;
// Interpolate the Y at this X
const t = (x - seg.x1) / (seg.x2 - seg.x1);
const iy = seg.y1 + t * (seg.y2 - seg.y1);
// Must be below the point
if (iy <= y) continue;
// Keep the closest one
if (iy < bestY) {
bestY = iy;
best = seg;
}
}
}
return best;
}
function canJumpToSegment(enemy, from, to) {
const gravity = game.gravity;
const jump = game.jumpStrength;
const maxJumpHeight = (jump * jump) / (2 * gravity);
const fromTop = Math.min(from.y1, from.y2);
const toTop = Math.min(to.y1, to.y2);
const verticalDiff = toTop - fromTop; // negative = above
// too high
if (verticalDiff < -maxJumpHeight) return false;
// too far down
if (verticalDiff > 150) return false;
// landing zone on the target segment
const left = Math.min(to.x1, to.x2);
const right = Math.max(to.x1, to.x2);
// clamp landingX to the segment
const landingX = Math.max(left, Math.min(enemy.x, right));
const horizontalGap = Math.abs(landingX - enemy.x);
// airtime
const timeUp = jump / gravity;
const timeDown = Math.sqrt((2 * (maxJumpHeight + verticalDiff)) / gravity);
const totalAirTime = timeUp + timeDown;
const maxHorizontalReach = enemy.speed * totalAirTime;
return horizontalGap <= maxHorizontalReach;
}
function canSegmentReachSegment(fromSeg, toSeg, jump, gravity, speed) {
const fromTop = Math.min(fromSeg.y1, fromSeg.y2);
const toTop = Math.min(toSeg.y1, toSeg.y2);
const verticalDiff = toTop - fromTop; // negative = above
const maxJumpHeight = (jump * jump) / (2 * gravity);
// Too high
if (verticalDiff < -maxJumpHeight)
return false;
// Too far down (optional)
if (verticalDiff > 150)
return false;
// Horizontal ranges
const fromLeft = Math.min(fromSeg.x1, fromSeg.x2);
const fromRight = Math.max(fromSeg.x1, fromSeg.x2);
const toLeft = Math.min(toSeg.x1, toSeg.x2);
const toRight = Math.max(toSeg.x1, toSeg.x2);
// Compute max horizontal reach
const timeUp = jump / gravity;
const timeDown = Math.sqrt((2 * (maxJumpHeight + verticalDiff)) / gravity);
const totalAirTime = timeUp + timeDown;
const maxReach = speed * totalAirTime;
// Check if ANY point on fromSeg can reach ANY point on toSeg
// This is interval overlap expanded by maxReach
const expandedFromLeft = fromLeft - maxReach;
const expandedFromRight = fromRight + maxReach;
const overlap =
expandedFromRight >= toLeft &&
expandedFromLeft <= toRight;
return overlap;
}
function findBestJumpSegment(enemy, enemyPlat, playerPlat) {
const jump = game.jumpStrength;
const gravity = game.gravity;
const speed = enemy.speed;
for (const pseg of playerPlat.segments) {
if ( pseg.isWall) continue;
for (const eseg of enemyPlat.segments) {
if ( eseg.isWall) continue;
if (canSegmentReachSegment(eseg, pseg, jump, gravity, speed)) {
return pseg;
}
}
}
return null;
}
function findClosestSegmentsBetween(platformA, platformB) {
let bestA = null;
let bestB = null;
let bestDist = Infinity;
for (const segA of platformA.segments) {
if(segA.isWall)continue;
for (const segB of platformB.segments) {
if(segB.isWall)continue;
// Compare endpoints of A to endpoints of B
const d1 = dist(segA.x1, segA.y1, segB.x1, segB.y1);
const d2 = dist(segA.x1, segA.y1, segB.x2, segB.y2);
const d3 = dist(segA.x2, segA.y2, segB.x1, segB.y1);
const d4 = dist(segA.x2, segA.y2, segB.x2, segB.y2);
const localMin = Math.min(d1, d2, d3, d4);
if (localMin < bestDist) {
bestDist = localMin;
bestA = segA;
bestB = segB;
}
}
}
return { segA: bestA, segB: bestB, dist: bestDist };
}
function findClosestSegmentsBetweenMidPoints(platformA, platformB) {
let bestA = null;
let bestB = null;
let bestScore = Infinity;
for (const segA of platformA.segments) {
if(segA.isWall)continue;
for (const segB of platformB.segments) {
if(segB.isWall)continue;
// Midpoint distance
const mxA = (segA.x1 + segA.x2) * 0.5;
const myA = (segA.y1 + segA.y2) * 0.5;
const mxB = (segB.x1 + segB.x2) * 0.5;
const myB = (segB.y1 + segB.y2) * 0.5;
const midDist = dist(mxA, myA, mxB, myB);
// Combined score (midpoint weighted slightly more)
const score = midDist;
if (score < bestScore) {
bestScore = score;
bestA = segA;
bestB = segB;
}
}
}
return { segA: bestA, segB: bestB, dist: bestScore };
}
function dist(x1, y1, x2, y2) {
const dx = x1 - x2;
const dy = y1 - y2;
return Math.sqrt(dx*dx + dy*dy);
}
function getPlatformUnder(entity) {
const feetY = entity.y + entity.height / 2;
let bestSeg = null;
let bestPlat = null;
let bestDelta = Infinity;
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if(seg.isWall)continue;
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (entity.x < left || entity.x > right) continue;
const t = (entity.x - seg.x1) / (seg.x2 - seg.x1);
const platY = seg.y1 + (seg.y2 - seg.y1) * t;
const delta = platY - feetY;
if (delta < -4) continue;
if (delta > 40) continue;
if (delta < bestDelta) {
bestDelta = delta;
bestSeg = seg;
bestPlat = plat;
}
}
}
return { segment: bestSeg, platform: bestPlat };
}
function getSegmentOverCurrent(currentSeg) {
const curLeft = Math.min(currentSeg.x1, currentSeg.x2);
const curRight = Math.max(currentSeg.x1, currentSeg.x2);
const curTop = Math.min(currentSeg.y1, currentSeg.y2);
let candidates = [];
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if (seg === currentSeg || seg.isWall ) continue;
const segTop = Math.min(seg.y1, seg.y2);
// must be above
if (segTop >= curTop) continue;
// must overlap horizontally
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (right < curLeft || left > curRight) continue;
candidates.push(seg);
}
}
if (candidates.length === 0) return null;
// pick lowest platform above
candidates.sort((a, b) => {
return Math.min(b.y1, b.y2) - Math.min(a.y1, a.y2);
});
return candidates[0];
}
function getSegmentOverPlatform(enemyPlatform) {
const platLeft = enemyPlatform.minX;
const platRight = enemyPlatform.maxX;
const platTop = enemyPlatform.minY;
let candidates = [];
for (const plat of game.platforms) {
for (const seg of plat.segments) {
// Skip segments belonging to the same platform
if (plat === enemyPlatform || seg.isWall ) continue;
const segTop = Math.min(seg.y1, seg.y2);
// Must be above the platform
if (segTop >= platTop) continue;
// Horizontal overlap with the ENTIRE platform
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (right < platLeft || left > platRight) continue;
candidates.push(seg);
}
}
if (candidates.length === 0) return null;
// Pick the LOWEST segment above the platform
candidates.sort((a, b) => {
const aTop = Math.min(a.y1, a.y2);
const bTop = Math.min(b.y1, b.y2);
return bTop - aTop; // reversed: lowest ABOVE
});
return candidates[0];
}
//for death animation
function getSegmentAngle(seg) {
return Math.atan2(seg.y2 - seg.y1, seg.x2 - seg.x1);
}
*/ |
| SC Arguments: [Name:SC_ACTION Type:uint64 Value:'1' Name:SC_CODE Type:string Value:'// Copyright 2024. Civilware. All rights reserved.
// TELA Decentralized Web Document (TELA-DOC-1)
Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "aihelpers.js")
31 STORE("descrHdr", "AI helpers file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "aihelpers.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "26e58246f6ca09d92181a73379220599e9a5c46bd5a4bde5ea0b835ffb0b31d4")
37 STORE("fileCheckS", "26527e430eed734885608b370e1b91c7fee2ca564419b352ce8c27091d68eefa")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
function isUnderOrFacingSegment(enemy, target) {
const left = Math.min(target.x1, target.x2);
const right = Math.max(target.x1, target.x2);
if(enemy.x < left && enemy.facing ===1){
return true;
}else if(enemy.x > right && enemy.facing ===-1){
return true;
}else if(enemy.x >= left && enemy.x <= right){
return true;
}
return false;
}
function isSegmentBelowSegment(segA, segB, threshold = 0) {
// 1. Horizontal overlap check
const leftA = Math.min(segA.x1, segA.x2);
const rightA = Math.max(segA.x1, segA.x2);
const leftB = Math.min(segB.x1, segB.x2);
const rightB = Math.max(segB.x1, segB.x2);
const overlap = !(rightA < leftB - 1 || leftA > rightB + 1);
if (!overlap) return false;
// 2. Compute average heights (handles slopes)
const avgA = (segA.y1 + segA.y2) * 0.5;
const avgB = (segB.y1 + segB.y2) * 0.5;
// 3. A is below B if its average y is greater (screen coords)
return avgA > avgB + threshold;
}
function getPlatformById(id) {
for (const plat of game.platforms) {
if(plat.id === id)return plat;
}
}
function resetAttacks(enemy){
enemy.wantKick = false;
enemy.wantPunch = false;
}
function playerIsThreatening(player) {
return player.wantKick ||
player.wantPunch ||
player.anim.state === "kick" ||
player.anim.state === "highKick" ||
player.anim.state === "punch";
}
function countEnemiesBehind(enemy) {
let count = 0;
for (const e of game.enemies) {
if (e === enemy) continue;
if (e.dead) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Must be behind relative to facing
if (enemy.facing === 1 && e.x < enemy.x) count++;
if (enemy.facing === -1 && e.x > enemy.x) count++;
}
return count;
}
function countEnemiesAhead(enemy) {
let count = 0;
for (const e of game.enemies) {
if (e === enemy) continue;
if (e.dead) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Must be behind relative to facing
if (enemy.facing === 1 && e.x > enemy.x) count++;
if (enemy.facing === -1 && e.x < enemy.x) count++;
}
return count;
}
function keepEnemySpacing(enemy) {
const spacing = 50; // how much space they want
for (const e of game.enemies) {
if (e === enemy || e.dead || e.inAir) continue;
// Same platform only
if (e.onPlatform !== enemy.onPlatform) continue;
// Check if e is in front of enemy
const dx = e.x - enemy.x;
if (enemy.facing === 1 && dx > 0 && dx < spacing) {
return true;
}
if (enemy.facing === -1 && dx < 0 && -dx < spacing) {
return true;
}
}
return false;
}
function avoidWalls(enemy){
if(directionBlocked(enemy) && !enemy.inAir){
enemy.facing = -enemy.facing;
enemy.moveInput = enemy.facing
if(directionBlocked(enemy)){
enemy.moveInput = 0;
}
}
}
function avoidWallsOnSamePlatform(enemy, wallSeg){
if(directionBlocked(enemy) && !enemy.inAir && enemy.onPlatform === enemy.blockedP && !aiCanJumpOver(enemy, wallSeg)){
enemy.facing = -enemy.facing;
enemy.moveInput = enemy.facing
if(directionBlocked(enemy)){
enemy.moveInput = 0;
}
}
}
function directionBlocked(enemy){
if(enemy.facing === 1 && enemy.blockedRight) return true;
if(enemy.facing === -1 && enemy.blockedLeft) return true;
return false
}
function detectWallAhead(enemy) {
for (let plat of game.platforms) {
for (let seg of plat.segments) {
const direction = aiCheckWallAhead(enemy, seg)
if (direction) {
if(direction === "right"){
enemy.blockedP = plat.id;
enemy.blockedRight = true;
enemy.blockedLeft = false;
return seg
}else{
enemy.blockedP = plat.id;
enemy.blockedLeft = true;
enemy.blockedRight = false;
return seg
}
}
}
}
return null;
}
function aiCanJumpOver(ai, seg) {
if(!seg)return true;
const wallHeight = seg.maxY - seg.minY;
return wallHeight <= 100;
}
function aiCheckWallAhead(ai, seg, lookAheadMax = 40) {
if (!seg.isWall) return false;
const top = ai.y + ai.height * .25;
const bottom = ai.y + ai.height * 0.5;
// vertical overlap
if (bottom -1 <= seg.minY || top +1 >= seg.maxY) return false;
const dist = seg.x1 - ai.x
if (Math.abs(dist) <= lookAheadMax && dist > 0) {
return "right" ;
}else if (Math.abs(dist) <= lookAheadMax && dist < 0){
return "left" ;
}
return false;
}
function isNearPlatformEdge(enemy, plat, threshold = 20) {
const left = plat.minX;
const right = plat.maxX;
const nearLeft = enemy.x < left + threshold;
const nearRight = enemy.x > right - threshold;
// Only count as an edge if there's no connected segment
if (nearLeft) return true;
if (nearRight) return true;
return false;
}
function isNearEdgeFacing(enemy, platform, threshold = 20) {
const left = platform.minX;
const right = platform.maxX;
if (enemy.facing < 0) {
// Facing left
return enemy.x <= left + threshold;
} else {
// Facing right
return enemy.x >= right - threshold;
}
}
function isFacingSegment(enemy, segment){
if (enemy.facing < 0) {
// Facing left
return enemy.x > segment.midX;
} else {
// Facing right
return enemy.x < segment.midX;
}
}
function isPlayerBelowPlatform(plat, threshold = 20) {
const px = game.player.x;
const py = game.player.y;
for (const seg of plat.segments) {
// Optional: only treat floor-type segments as platform surface
if (seg.isWall)
continue;
const x1 = seg.x1;
const x2 = seg.x2;
const y1 = seg.y1;
const y2 = seg.y2;
// Skip vertical / near-vertical segments (walls)
const dx = x2 - x1;
if (Math.abs(dx) < 0.0001)
continue;
const left = Math.min(x1, x2);
const right = Math.max(x1, x2);
// Player must be horizontally under this segment (with small margin)
if (px < left - threshold || px > right + threshold)
continue;
// Interpolate the platform height at px
let t = (px - x1) / dx;
// Clamp t so we don't extend beyond the segment
if (t < 0 || t > 1)
continue;
const y_at_px = y1 + t * (y2 - y1);
// Player must be below the surface
if (py > y_at_px + 10)
return true;
}
return false;
}
function isSafeToDrop(enemy) {
const feetX = enemy.x;
const feetY = enemy.y + enemy.height / 2;
const segBelow = getPlatformBelowPoint(feetX, feetY);
return segBelow !== null;
}
function getPlatformBelowPoint(x, y) {
let best = null;
let bestY = Infinity;
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if(seg.isWall)continue;
// Skip segments above or at the same height
const segTop = Math.min(seg.y1, seg.y2);
if (segTop <= y) continue;
// Check if x is within segment horizontal span
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (x < left || x > right) continue;
// Interpolate the Y at this X
const t = (x - seg.x1) / (seg.x2 - seg.x1);
const iy = seg.y1 + t * (seg.y2 - seg.y1);
// Must be below the point
if (iy <= y) continue;
// Keep the closest one
if (iy < bestY) {
bestY = iy;
best = seg;
}
}
}
return best;
}
function canJumpToSegment(enemy, from, to) {
const gravity = game.gravity;
const jump = game.jumpStrength;
const maxJumpHeight = (jump * jump) / (2 * gravity);
const fromTop = Math.min(from.y1, from.y2);
const toTop = Math.min(to.y1, to.y2);
const verticalDiff = toTop - fromTop; // negative = above
// too high
if (verticalDiff < -maxJumpHeight) return false;
// too far down
if (verticalDiff > 150) return false;
// landing zone on the target segment
const left = Math.min(to.x1, to.x2);
const right = Math.max(to.x1, to.x2);
// clamp landingX to the segment
const landingX = Math.max(left, Math.min(enemy.x, right));
const horizontalGap = Math.abs(landingX - enemy.x);
// airtime
const timeUp = jump / gravity;
const timeDown = Math.sqrt((2 * (maxJumpHeight + verticalDiff)) / gravity);
const totalAirTime = timeUp + timeDown;
const maxHorizontalReach = enemy.speed * totalAirTime;
return horizontalGap <= maxHorizontalReach;
}
function canSegmentReachSegment(fromSeg, toSeg, jump, gravity, speed) {
const fromTop = Math.min(fromSeg.y1, fromSeg.y2);
const toTop = Math.min(toSeg.y1, toSeg.y2);
const verticalDiff = toTop - fromTop; // negative = above
const maxJumpHeight = (jump * jump) / (2 * gravity);
// Too high
if (verticalDiff < -maxJumpHeight)
return false;
// Too far down (optional)
if (verticalDiff > 150)
return false;
// Horizontal ranges
const fromLeft = Math.min(fromSeg.x1, fromSeg.x2);
const fromRight = Math.max(fromSeg.x1, fromSeg.x2);
const toLeft = Math.min(toSeg.x1, toSeg.x2);
const toRight = Math.max(toSeg.x1, toSeg.x2);
// Compute max horizontal reach
const timeUp = jump / gravity;
const timeDown = Math.sqrt((2 * (maxJumpHeight + verticalDiff)) / gravity);
const totalAirTime = timeUp + timeDown;
const maxReach = speed * totalAirTime;
// Check if ANY point on fromSeg can reach ANY point on toSeg
// This is interval overlap expanded by maxReach
const expandedFromLeft = fromLeft - maxReach;
const expandedFromRight = fromRight + maxReach;
const overlap =
expandedFromRight >= toLeft &&
expandedFromLeft <= toRight;
return overlap;
}
function findBestJumpSegment(enemy, enemyPlat, playerPlat) {
const jump = game.jumpStrength;
const gravity = game.gravity;
const speed = enemy.speed;
for (const pseg of playerPlat.segments) {
if ( pseg.isWall) continue;
for (const eseg of enemyPlat.segments) {
if ( eseg.isWall) continue;
if (canSegmentReachSegment(eseg, pseg, jump, gravity, speed)) {
return pseg;
}
}
}
return null;
}
function findClosestSegmentsBetween(platformA, platformB) {
let bestA = null;
let bestB = null;
let bestDist = Infinity;
for (const segA of platformA.segments) {
if(segA.isWall)continue;
for (const segB of platformB.segments) {
if(segB.isWall)continue;
// Compare endpoints of A to endpoints of B
const d1 = dist(segA.x1, segA.y1, segB.x1, segB.y1);
const d2 = dist(segA.x1, segA.y1, segB.x2, segB.y2);
const d3 = dist(segA.x2, segA.y2, segB.x1, segB.y1);
const d4 = dist(segA.x2, segA.y2, segB.x2, segB.y2);
const localMin = Math.min(d1, d2, d3, d4);
if (localMin < bestDist) {
bestDist = localMin;
bestA = segA;
bestB = segB;
}
}
}
return { segA: bestA, segB: bestB, dist: bestDist };
}
function findClosestSegmentsBetweenMidPoints(platformA, platformB) {
let bestA = null;
let bestB = null;
let bestScore = Infinity;
for (const segA of platformA.segments) {
if(segA.isWall)continue;
for (const segB of platformB.segments) {
if(segB.isWall)continue;
// Midpoint distance
const mxA = (segA.x1 + segA.x2) * 0.5;
const myA = (segA.y1 + segA.y2) * 0.5;
const mxB = (segB.x1 + segB.x2) * 0.5;
const myB = (segB.y1 + segB.y2) * 0.5;
const midDist = dist(mxA, myA, mxB, myB);
// Combined score (midpoint weighted slightly more)
const score = midDist;
if (score < bestScore) {
bestScore = score;
bestA = segA;
bestB = segB;
}
}
}
return { segA: bestA, segB: bestB, dist: bestScore };
}
function dist(x1, y1, x2, y2) {
const dx = x1 - x2;
const dy = y1 - y2;
return Math.sqrt(dx*dx + dy*dy);
}
function getPlatformUnder(entity) {
const feetY = entity.y + entity.height / 2;
let bestSeg = null;
let bestPlat = null;
let bestDelta = Infinity;
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if(seg.isWall)continue;
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (entity.x < left || entity.x > right) continue;
const t = (entity.x - seg.x1) / (seg.x2 - seg.x1);
const platY = seg.y1 + (seg.y2 - seg.y1) * t;
const delta = platY - feetY;
if (delta < -4) continue;
if (delta > 40) continue;
if (delta < bestDelta) {
bestDelta = delta;
bestSeg = seg;
bestPlat = plat;
}
}
}
return { segment: bestSeg, platform: bestPlat };
}
function getSegmentOverCurrent(currentSeg) {
const curLeft = Math.min(currentSeg.x1, currentSeg.x2);
const curRight = Math.max(currentSeg.x1, currentSeg.x2);
const curTop = Math.min(currentSeg.y1, currentSeg.y2);
let candidates = [];
for (const plat of game.platforms) {
for (const seg of plat.segments) {
if (seg === currentSeg || seg.isWall ) continue;
const segTop = Math.min(seg.y1, seg.y2);
// must be above
if (segTop >= curTop) continue;
// must overlap horizontally
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (right < curLeft || left > curRight) continue;
candidates.push(seg);
}
}
if (candidates.length === 0) return null;
// pick lowest platform above
candidates.sort((a, b) => {
return Math.min(b.y1, b.y2) - Math.min(a.y1, a.y2);
});
return candidates[0];
}
function getSegmentOverPlatform(enemyPlatform) {
const platLeft = enemyPlatform.minX;
const platRight = enemyPlatform.maxX;
const platTop = enemyPlatform.minY;
let candidates = [];
for (const plat of game.platforms) {
for (const seg of plat.segments) {
// Skip segments belonging to the same platform
if (plat === enemyPlatform || seg.isWall ) continue;
const segTop = Math.min(seg.y1, seg.y2);
// Must be above the platform
if (segTop >= platTop) continue;
// Horizontal overlap with the ENTIRE platform
const left = Math.min(seg.x1, seg.x2);
const right = Math.max(seg.x1, seg.x2);
if (right < platLeft || left > platRight) continue;
candidates.push(seg);
}
}
if (candidates.length === 0) return null;
// Pick the LOWEST segment above the platform
candidates.sort((a, b) => {
const aTop = Math.min(a.y1, a.y2);
const bTop = Math.min(b.y1, b.y2);
return bTop - aTop; // reversed: lowest ABOVE
});
return candidates[0];
}
//for death animation
function getSegmentAngle(seg) {
return Math.atan2(seg.y2 - seg.y1, seg.x2 - seg.x1);
}
*/'] |