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", "game.js")
31 STORE("descrHdr", "Game file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "game.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "59d5d7b1eb0a3906bcd2b4d37143b257874b415e7d6979a1d74eac6ce02b9ba")
37 STORE("fileCheckS", "11bd0a47103c4a6ac0d506d595b6612881e83f6aab2f05e0f37986e3afc27b94")
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
/*const svg = document.getElementById("game");
const players = document.getElementById("players");
let keys = {};
let zoom = 1;
const WORLD_W = 5000;
const WORLD_H = 5000;
// pick a good size for minimap
const screenW = window.innerWidth;
const screenH = window.innerHeight;
let camCenter = {};
let camLeft = 0;
let camTop = 0;
let camRight = 0;
let camBottom = 0;
let sw = 0;
let sh = 0;
const STATE = {
MENU: 0,
PLAYING: 1,
PAUSED: 2,
WIN: 3
};
let game = {};
let currentLevel = 1;
game.enemies = [];
let spawned = 0;
let enemyId = 0;
let spawnTimer = 0;
let spawnInterval = 2000;
function resetStats(){
game.score = 0;
game.time = 0;
game.totalKills = 0;
game.deaths = 0;
}
resetStats();
initAudio()
const maxClimbAngle = Math.PI * .25;
const poses = {
idle: {
upperArmL: -0.8,
lowerArmL: -2,
upperArmR: -.6,
lowerArmR: -0.12,
upperLegR: 0,
lowerLegR: 0,
upperLegL: 0,
lowerLegL: 0,
head: 0,
torso: 0
},
jump: {
upperArmL: -0.8,
lowerArmL: 0.2,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0,
torso: 0
},
fall: {
upperArmL: -1.4,
lowerArmL: 0.2,
upperArmR: -1.4,
lowerArmR: -0.2,
upperLegR: 0,
lowerLegR: 0,
upperLegL: 0,
lowerLegL: 0,
head: 0,
torso: 0
},
crouch: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.5,
lowerArmR: -1.6,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL:-1.6,
lowerLegL: 2.4,
head: -0.1,
torso: 0.1
},
punch: {
upperArmL: -1.5,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
crouchPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -2.2,
lowerArmR: -.5,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL:-1.6,
lowerLegL: 2.4,
head: -0.1,
torso: 0.1
},
crouchKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL: -1.3,
lowerLegL: 0,
head: 0.1,
torso: -0.1
},
walkPunch: {
upperArmL: -1.5,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
walkHighPunch: {
upperArmL: .5,
lowerArmL: 0,
upperArmR: -2,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
highPunch: {
upperArmL: .5,
lowerArmL: 0,
upperArmR: -2,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
jumpPunch: {
upperArmL: -1.9,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
jumpHighPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -2.2,
lowerArmR: -.5,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
jumpLowPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -1,
lowerArmR: 0,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
kick: {
upperArmL: -.8,
lowerArmL: -1.6,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -2,
lowerLegR: -0,
upperLegL: -0.2,
lowerLegL: 0.5,
head: 0.1,
torso: -0.1
},
highKick: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.8,
lowerArmR: -1.6,
upperLegR: -0.2,
lowerLegR: 0.4,
upperLegL: -2.5,
lowerLegL: -0,
head: 0.1,
torso: -0.1
},
jumpKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.4,
lowerLegR: -0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
},
jumpHighKick: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.8,
lowerArmR: -1.6,
upperLegR: -2,
lowerLegR: -0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
},
jumpLowKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -.9,
lowerLegR: 0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
}
};
const attackFrames = {
punch: { start: 4, end: 8 },
crouchPunch: { start: 4, end: 8 },
walkPunch: { start: 4, end: 10 },
walkHighPunch: { start: 4, end: 10 },
highPunch: { start: 4, end: 10 },
jumpPunch: { start: 3, end: 12 },
jumpLowPunch: { start: 3, end: 12 },
jumpHighPunch: { start: 3, end: 12 },
kick: { start: 4, end: 12 },
crouchKick: { start: 4, end: 12 },
highKick: { start: 4, end: 12 },
jumpKick: { start: 3, end: 12 },
jumpLowKick: { start: 3, end: 12 },
jumpHighKick: { start: 3, end: 12 },
};
const hat = {}
hat.poly = [
{x:-9, y:-10},
{x:0, y:-12},
{x: 9, y:-10},
{x: 12, y:2},
{x: 15, y:4},
{x:-11, y:4},
]
function spawnEnemy(e) {
const id = `enemy-${enemyId++}`;
const enemy = {
id,
type: e.type,
x:e.x,
y:e.y,
vx: 0, vy: 0,
moveInput: 0,
verticalInput: 0,
speed: (e.type !== "boss"? 0.2:0.25),
height: 100,
width: 20,
inAir: true,
onGround: false,
facing: 1,
faceLockTimer:0,
skeleton: createSkeleton(),
skeletonOffsetY:0,
moveState:"idle",
actionState:"none",
anim: {
currentPose: poses.idle,
nextPose: null,
state: "idle",
frame: 0
},
radius:20,
headRecoil:0,
attackCooldown:0,
jumpCooldown:0,
stun:0,
hitPoints:(e.type !== "boss"? 100:500),
dead:false,
deathTimer:0,
//blocked:-1,
blockedLeft:false,
blockedRight:false,
onPlatform:-1,
prevHit:false,
prevAttack:false,
hasHat:true
};
addBone(enemy.skeleton.torso.children.head, "hat", {
pivot: [0, 0],
childOrigin: [0, 0],
rotation: 0,
rotationTarget: 0,
poly: hat.poly
});
game.enemies.push(enemy);
createSVGGroup(id);
createEntityGradient(id, e.type, "torso")
return enemy;
}
function createSVGGroup(id) {
// check if it already exists
let g = document.getElementById(id);
if (g) return g; // return existing one
// otherwise create a new one
g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.setAttribute("id", id);
return g;
}
function createEntityGradient(enemyId,enemyType, boneName) {
const grad = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
grad.setAttribute("gradientUnits", "userSpaceOnUse");
if(enemyType ==="grunt" || enemyType ==="defender"){
grad.id = `${enemyId}_${boneName}_grad`;
grad.innerHTML = `
<stop offset="45%" stop-color="#d2c59a" />
<stop offset="75%" stop-color="#d2c59a" />
<stop offset="77.5%" stop-color="#8a7a4f" />
<stop offset="80%" stop-color="#b8a878" />
<stop offset="100%" stop-color="#b8a878" />
`;
}else if(enemyType ==="player"){
grad.id = "player_torso_grad"
grad.innerHTML = `
<stop offset="45%" stop-color="#ccf" />
<stop offset="80%" stop-color="#ccf" />
<stop offset="82.5%" stop-color="brown" />
<stop offset="85%" stop-color="#54f" />
<stop offset="100%" stop-color="#54f" />
`
}
let defs = document.getElementById("defs");
defs.appendChild(grad);
return `url(#${grad.id})`;
}
function createTile(x, y, scalewidth, scalewidth, svgMarkup) {
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.innerHTML = svgMarkup;
g.setAttribute("transform", `translate(${x}, ${y}) scale(${scalewidth}, ${scalewidth})`);
return g;
}
function respawnPlayer() {
createSVGGroup("player");
let player = {
player: {
id:"player",
x: levelData[currentLevel].playerStart.x,
y: levelData[currentLevel].playerStart.y,
vx: 0,
vy: 0,
moveInput: 0,
verticalInput: 0,
speed: 0.25,
height: 100,
width: 20,
inAir: true,
onGround: false,
facing: levelData[currentLevel].playerStart.facing,
skeleton: createSkeleton(),
skeletonOffsetY:0,
moveState:"idle",
actionState:"none",
anim: {
currentPose: poses.idle,
nextPose: null,
state: "idle",
frame: 0
},
radius:20,
headRecoil:0,
attackCooldown:0,
jumpCooldown:0,
stun:0,
hitPoints:100,
maxPoints:100,
prevHit:false,
prevAction:"none",
hasHat:true
}
};
// mutate existing game object instead of replacing it
Object.assign(game, player);
// reapply custom settings
game.gravity = 0.0025;
game.jumpStrength = 0.8;
// reset camera
camCenter.x = game.player.x;
camCenter.y = game.player.y;
createSVGGroup("player");
addBone(game.player.skeleton.torso.children.head, "hat", {
pivot: [0, 0],
childOrigin: [0, 0],
rotation: 0,
rotationTarget: 0,
poly: hat.poly
});
}
function loadLevel(level) {
var g = document.getElementById("tiles");
g.innerHTML = "";
let i=1;
for (let t of levelData[level].tileMap) {
let tile = createTile(t.x, t.y, 1, 1, levelTiles[level][i++])
g.appendChild(tile);
}
game.platforms = levelPlatforms[level];
game.enemies = [];
enemyId = 0;
//reset enemies
spawned = 0;
game.objective = levelData[level].objective;
game.doors = JSON.parse(JSON.stringify(levelData[level].doors));
for (let door of game.doors) {
door.node = document.getElementById(door.id);
}
game.kills = 0;
players.innerHTML = "";
respawnPlayer(); // start game
game.enemyPlatforms = {};//for shared enemy states
addBackground(levelData[level].background);
showEphemeralMessage(levelData[level].objective.message);
let id=0;
let pid=0;
for (let plat of game.platforms) {
// Remove duplicate consecutive points
const clean = [];
for (let i = 0; i < plat.points.length; i++) {
const p = plat.points[i];
const prev = plat.points[i - 1];
if (!prev || p.x !== prev.x || p.y !== prev.y) {
clean.push(p);
}
}
plat.points = clean;
plat.segments = [];
const pts = plat.points;
// 1. Build segments
for (let i = 0; i < pts.length - 1; i++) {
const a = pts[i];
const b = pts[i+1];
const dx = b.x - a.x;
const dy = b.y - a.y;
plat.segments.push({
x1: a.x,
y1: a.y,
x2: b.x,
y2: b.y,
dx,
dy,
length: Math.hypot(dx, dy),
angle: Math.atan2(dy, dx),
sinAngle: Math.sin(Math.atan2(dy, dx)),
cosAngle: Math.cos(Math.atan2(dy, dx)),
isWall: Math.abs(dx) < 0.001,
oneWay: a.oneWay,
slopeGoesRight: dx > 0,
midX: (a.x + b.x) / 2,
minY: Math.min(a.y, b.y),
maxY: Math.max(a.y, b.y),
id: id++,
pid:pid
});
}
// 2. Compute platform-level bounds ONCE
plat.minX = Infinity;
plat.maxX = -Infinity;
plat.minY = Infinity;
plat.maxY = -Infinity;
for (const seg of plat.segments) {
plat.minX = Math.min(plat.minX, seg.x1, seg.x2);
plat.maxX = Math.max(plat.maxX, seg.x1, seg.x2);
plat.minY = Math.min(plat.minY, seg.minY);
plat.maxY = Math.max(plat.maxY, seg.maxY);
}
plat.enemies = [],
plat.id = pid++
}
let defs = document.getElementById("defs");
defs.innerHTML = "";
createEntityGradient("player","player", "torso")
}
loadLevel(currentLevel)
function updateSpawn(dt) {
spawnTimer -= dt;
if (spawnTimer <= 0) {
if (game.enemies.length < levelData[currentLevel].maxAlive && spawned < levelData[currentLevel].maxSpawn){
spawnEnemy(levelEnemies[currentLevel][spawned]);
spawned++;
}
// ramp difficulty
spawnInterval = Math.max(400, spawnInterval * 0.97);
spawnTimer = spawnInterval;
}
}
function setState(s) {
game.state = s;
}
setState(STATE.PLAYING)
function rotatePoly(poly, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return poly.map(p => ({
x: p.x * cos - p.y * sin,
y: p.x * sin + p.y * cos
}));
}
function addBone(parentBone, name, bone) {
if (!parentBone.children) {
parentBone.children = {};
}
parentBone.children[name] = bone;
}
function createSkeleton() {
let body = {
"torso": {
"pivot": [0, -40],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-10, y: 0}, //top-left
{x:0, y: -10}, //top-middle
{x: 10, y: 0}, //top-right
{x: 10, y: 40},//bottom-right
{x: -10, y: 40},//bottom-left
]
,
"children": {
"head": {
"pivot": [0, -20],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-10, y: 0}, //top-left
{x:-4, y: -3},
{x: 0, y: -5}, //top
{x: 4, y: -3},
{x: 10, y: 0}, //top-right
{x: 12, y: 10},
{x: 10, y: 20},//bottom-right
{x: -10, y: 20},//bottom-left
{x: -12, y: 10},
]
},
"upperArmL": {
"pivot": [5, 0],
"childOrigin": [0, 20],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
],
"children": {
"lowerArmL": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
]
}
}
}
,
"upperArmR": {
"pivot": [0, 0],
"childOrigin": [0, 20],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
],
"children": {
"lowerArmR": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
]
}
}
},
"upperLegL": {
"pivot": [3, 30],
"childOrigin": [0, 25],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-leftt
],
"children": {
"lowerLegL": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
]
}
}
},
"upperLegR": {
"pivot": [-3, 30],
"childOrigin": [0, 25],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
],
"children": {
"lowerLegR": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
]
}
}
}
}
}
}
return body
}
const zMap = {
hat: 7,
head: 6,
torso: 5,
upperArmL: 1,
lowerArmL: 2,
upperArmR: 10,
lowerArmR: 11,
upperLegL: 3,
lowerLegL: 4,
upperLegR: 8,
lowerLegR: 9,
};
game.hats=[]
*/ |
| 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", "game.js")
31 STORE("descrHdr", "Game file")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "game.js")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "59d5d7b1eb0a3906bcd2b4d37143b257874b415e7d6979a1d74eac6ce02b9ba")
37 STORE("fileCheckS", "11bd0a47103c4a6ac0d506d595b6612881e83f6aab2f05e0f37986e3afc27b94")
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
/*const svg = document.getElementById("game");
const players = document.getElementById("players");
let keys = {};
let zoom = 1;
const WORLD_W = 5000;
const WORLD_H = 5000;
// pick a good size for minimap
const screenW = window.innerWidth;
const screenH = window.innerHeight;
let camCenter = {};
let camLeft = 0;
let camTop = 0;
let camRight = 0;
let camBottom = 0;
let sw = 0;
let sh = 0;
const STATE = {
MENU: 0,
PLAYING: 1,
PAUSED: 2,
WIN: 3
};
let game = {};
let currentLevel = 1;
game.enemies = [];
let spawned = 0;
let enemyId = 0;
let spawnTimer = 0;
let spawnInterval = 2000;
function resetStats(){
game.score = 0;
game.time = 0;
game.totalKills = 0;
game.deaths = 0;
}
resetStats();
initAudio()
const maxClimbAngle = Math.PI * .25;
const poses = {
idle: {
upperArmL: -0.8,
lowerArmL: -2,
upperArmR: -.6,
lowerArmR: -0.12,
upperLegR: 0,
lowerLegR: 0,
upperLegL: 0,
lowerLegL: 0,
head: 0,
torso: 0
},
jump: {
upperArmL: -0.8,
lowerArmL: 0.2,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0,
torso: 0
},
fall: {
upperArmL: -1.4,
lowerArmL: 0.2,
upperArmR: -1.4,
lowerArmR: -0.2,
upperLegR: 0,
lowerLegR: 0,
upperLegL: 0,
lowerLegL: 0,
head: 0,
torso: 0
},
crouch: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.5,
lowerArmR: -1.6,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL:-1.6,
lowerLegL: 2.4,
head: -0.1,
torso: 0.1
},
punch: {
upperArmL: -1.5,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
crouchPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -2.2,
lowerArmR: -.5,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL:-1.6,
lowerLegL: 2.4,
head: -0.1,
torso: 0.1
},
crouchKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.4,
lowerLegR: 2.2,
upperLegL: -1.3,
lowerLegL: 0,
head: 0.1,
torso: -0.1
},
walkPunch: {
upperArmL: -1.5,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
walkHighPunch: {
upperArmL: .5,
lowerArmL: 0,
upperArmR: -2,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
highPunch: {
upperArmL: .5,
lowerArmL: 0,
upperArmR: -2,
lowerArmR: -1,
upperLegR: .1,
lowerLegR: 0,
upperLegL: -.1,
lowerLegL: 0,
head: 0.1,
torso: 0.1
},
jumpPunch: {
upperArmL: -1.9,
lowerArmL: 0,
upperArmR: .5,
lowerArmR: -1,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
jumpHighPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -2.2,
lowerArmR: -.5,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
jumpLowPunch: {
upperArmL: .5,
lowerArmL: -1,
upperArmR: -1,
lowerArmR: 0,
upperLegR: -1.2,
lowerLegR: 1.5,
upperLegL: -1.1,
lowerLegL: 1.9,
head: 0.3,
torso: 0.5
},
kick: {
upperArmL: -.8,
lowerArmL: -1.6,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -2,
lowerLegR: -0,
upperLegL: -0.2,
lowerLegL: 0.5,
head: 0.1,
torso: -0.1
},
highKick: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.8,
lowerArmR: -1.6,
upperLegR: -0.2,
lowerLegR: 0.4,
upperLegL: -2.5,
lowerLegL: -0,
head: 0.1,
torso: -0.1
},
jumpKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -1.4,
lowerLegR: -0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
},
jumpHighKick: {
upperArmL: -0.1,
lowerArmL: -0.2,
upperArmR: -.8,
lowerArmR: -1.6,
upperLegR: -2,
lowerLegR: -0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
},
jumpLowKick: {
upperArmL: -.8,
lowerArmL: -1.5,
upperArmR: -0.1,
lowerArmR: -0.2,
upperLegR: -.9,
lowerLegR: 0,
upperLegL: 2,
lowerLegL: -2,
head: 0.1,
torso: -.3
}
};
const attackFrames = {
punch: { start: 4, end: 8 },
crouchPunch: { start: 4, end: 8 },
walkPunch: { start: 4, end: 10 },
walkHighPunch: { start: 4, end: 10 },
highPunch: { start: 4, end: 10 },
jumpPunch: { start: 3, end: 12 },
jumpLowPunch: { start: 3, end: 12 },
jumpHighPunch: { start: 3, end: 12 },
kick: { start: 4, end: 12 },
crouchKick: { start: 4, end: 12 },
highKick: { start: 4, end: 12 },
jumpKick: { start: 3, end: 12 },
jumpLowKick: { start: 3, end: 12 },
jumpHighKick: { start: 3, end: 12 },
};
const hat = {}
hat.poly = [
{x:-9, y:-10},
{x:0, y:-12},
{x: 9, y:-10},
{x: 12, y:2},
{x: 15, y:4},
{x:-11, y:4},
]
function spawnEnemy(e) {
const id = `enemy-${enemyId++}`;
const enemy = {
id,
type: e.type,
x:e.x,
y:e.y,
vx: 0, vy: 0,
moveInput: 0,
verticalInput: 0,
speed: (e.type !== "boss"? 0.2:0.25),
height: 100,
width: 20,
inAir: true,
onGround: false,
facing: 1,
faceLockTimer:0,
skeleton: createSkeleton(),
skeletonOffsetY:0,
moveState:"idle",
actionState:"none",
anim: {
currentPose: poses.idle,
nextPose: null,
state: "idle",
frame: 0
},
radius:20,
headRecoil:0,
attackCooldown:0,
jumpCooldown:0,
stun:0,
hitPoints:(e.type !== "boss"? 100:500),
dead:false,
deathTimer:0,
//blocked:-1,
blockedLeft:false,
blockedRight:false,
onPlatform:-1,
prevHit:false,
prevAttack:false,
hasHat:true
};
addBone(enemy.skeleton.torso.children.head, "hat", {
pivot: [0, 0],
childOrigin: [0, 0],
rotation: 0,
rotationTarget: 0,
poly: hat.poly
});
game.enemies.push(enemy);
createSVGGroup(id);
createEntityGradient(id, e.type, "torso")
return enemy;
}
function createSVGGroup(id) {
// check if it already exists
let g = document.getElementById(id);
if (g) return g; // return existing one
// otherwise create a new one
g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.setAttribute("id", id);
return g;
}
function createEntityGradient(enemyId,enemyType, boneName) {
const grad = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
grad.setAttribute("gradientUnits", "userSpaceOnUse");
if(enemyType ==="grunt" || enemyType ==="defender"){
grad.id = `${enemyId}_${boneName}_grad`;
grad.innerHTML = `
<stop offset="45%" stop-color="#d2c59a" />
<stop offset="75%" stop-color="#d2c59a" />
<stop offset="77.5%" stop-color="#8a7a4f" />
<stop offset="80%" stop-color="#b8a878" />
<stop offset="100%" stop-color="#b8a878" />
`;
}else if(enemyType ==="player"){
grad.id = "player_torso_grad"
grad.innerHTML = `
<stop offset="45%" stop-color="#ccf" />
<stop offset="80%" stop-color="#ccf" />
<stop offset="82.5%" stop-color="brown" />
<stop offset="85%" stop-color="#54f" />
<stop offset="100%" stop-color="#54f" />
`
}
let defs = document.getElementById("defs");
defs.appendChild(grad);
return `url(#${grad.id})`;
}
function createTile(x, y, scalewidth, scalewidth, svgMarkup) {
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.innerHTML = svgMarkup;
g.setAttribute("transform", `translate(${x}, ${y}) scale(${scalewidth}, ${scalewidth})`);
return g;
}
function respawnPlayer() {
createSVGGroup("player");
let player = {
player: {
id:"player",
x: levelData[currentLevel].playerStart.x,
y: levelData[currentLevel].playerStart.y,
vx: 0,
vy: 0,
moveInput: 0,
verticalInput: 0,
speed: 0.25,
height: 100,
width: 20,
inAir: true,
onGround: false,
facing: levelData[currentLevel].playerStart.facing,
skeleton: createSkeleton(),
skeletonOffsetY:0,
moveState:"idle",
actionState:"none",
anim: {
currentPose: poses.idle,
nextPose: null,
state: "idle",
frame: 0
},
radius:20,
headRecoil:0,
attackCooldown:0,
jumpCooldown:0,
stun:0,
hitPoints:100,
maxPoints:100,
prevHit:false,
prevAction:"none",
hasHat:true
}
};
// mutate existing game object instead of replacing it
Object.assign(game, player);
// reapply custom settings
game.gravity = 0.0025;
game.jumpStrength = 0.8;
// reset camera
camCenter.x = game.player.x;
camCenter.y = game.player.y;
createSVGGroup("player");
addBone(game.player.skeleton.torso.children.head, "hat", {
pivot: [0, 0],
childOrigin: [0, 0],
rotation: 0,
rotationTarget: 0,
poly: hat.poly
});
}
function loadLevel(level) {
var g = document.getElementById("tiles");
g.innerHTML = "";
let i=1;
for (let t of levelData[level].tileMap) {
let tile = createTile(t.x, t.y, 1, 1, levelTiles[level][i++])
g.appendChild(tile);
}
game.platforms = levelPlatforms[level];
game.enemies = [];
enemyId = 0;
//reset enemies
spawned = 0;
game.objective = levelData[level].objective;
game.doors = JSON.parse(JSON.stringify(levelData[level].doors));
for (let door of game.doors) {
door.node = document.getElementById(door.id);
}
game.kills = 0;
players.innerHTML = "";
respawnPlayer(); // start game
game.enemyPlatforms = {};//for shared enemy states
addBackground(levelData[level].background);
showEphemeralMessage(levelData[level].objective.message);
let id=0;
let pid=0;
for (let plat of game.platforms) {
// Remove duplicate consecutive points
const clean = [];
for (let i = 0; i < plat.points.length; i++) {
const p = plat.points[i];
const prev = plat.points[i - 1];
if (!prev || p.x !== prev.x || p.y !== prev.y) {
clean.push(p);
}
}
plat.points = clean;
plat.segments = [];
const pts = plat.points;
// 1. Build segments
for (let i = 0; i < pts.length - 1; i++) {
const a = pts[i];
const b = pts[i+1];
const dx = b.x - a.x;
const dy = b.y - a.y;
plat.segments.push({
x1: a.x,
y1: a.y,
x2: b.x,
y2: b.y,
dx,
dy,
length: Math.hypot(dx, dy),
angle: Math.atan2(dy, dx),
sinAngle: Math.sin(Math.atan2(dy, dx)),
cosAngle: Math.cos(Math.atan2(dy, dx)),
isWall: Math.abs(dx) < 0.001,
oneWay: a.oneWay,
slopeGoesRight: dx > 0,
midX: (a.x + b.x) / 2,
minY: Math.min(a.y, b.y),
maxY: Math.max(a.y, b.y),
id: id++,
pid:pid
});
}
// 2. Compute platform-level bounds ONCE
plat.minX = Infinity;
plat.maxX = -Infinity;
plat.minY = Infinity;
plat.maxY = -Infinity;
for (const seg of plat.segments) {
plat.minX = Math.min(plat.minX, seg.x1, seg.x2);
plat.maxX = Math.max(plat.maxX, seg.x1, seg.x2);
plat.minY = Math.min(plat.minY, seg.minY);
plat.maxY = Math.max(plat.maxY, seg.maxY);
}
plat.enemies = [],
plat.id = pid++
}
let defs = document.getElementById("defs");
defs.innerHTML = "";
createEntityGradient("player","player", "torso")
}
loadLevel(currentLevel)
function updateSpawn(dt) {
spawnTimer -= dt;
if (spawnTimer <= 0) {
if (game.enemies.length < levelData[currentLevel].maxAlive && spawned < levelData[currentLevel].maxSpawn){
spawnEnemy(levelEnemies[currentLevel][spawned]);
spawned++;
}
// ramp difficulty
spawnInterval = Math.max(400, spawnInterval * 0.97);
spawnTimer = spawnInterval;
}
}
function setState(s) {
game.state = s;
}
setState(STATE.PLAYING)
function rotatePoly(poly, angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return poly.map(p => ({
x: p.x * cos - p.y * sin,
y: p.x * sin + p.y * cos
}));
}
function addBone(parentBone, name, bone) {
if (!parentBone.children) {
parentBone.children = {};
}
parentBone.children[name] = bone;
}
function createSkeleton() {
let body = {
"torso": {
"pivot": [0, -40],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-10, y: 0}, //top-left
{x:0, y: -10}, //top-middle
{x: 10, y: 0}, //top-right
{x: 10, y: 40},//bottom-right
{x: -10, y: 40},//bottom-left
]
,
"children": {
"head": {
"pivot": [0, -20],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-10, y: 0}, //top-left
{x:-4, y: -3},
{x: 0, y: -5}, //top
{x: 4, y: -3},
{x: 10, y: 0}, //top-right
{x: 12, y: 10},
{x: 10, y: 20},//bottom-right
{x: -10, y: 20},//bottom-left
{x: -12, y: 10},
]
},
"upperArmL": {
"pivot": [5, 0],
"childOrigin": [0, 20],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
],
"children": {
"lowerArmL": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
]
}
}
}
,
"upperArmR": {
"pivot": [0, 0],
"childOrigin": [0, 20],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
],
"children": {
"lowerArmR": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 20},//bottom-right
{x: -5, y: 20},//bottom-left
]
}
}
},
"upperLegL": {
"pivot": [3, 30],
"childOrigin": [0, 25],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-leftt
],
"children": {
"lowerLegL": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
]
}
}
},
"upperLegR": {
"pivot": [-3, 30],
"childOrigin": [0, 25],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
],
"children": {
"lowerLegR": {
"pivot": [0, 0],
"childOrigin": [0, 0],
"rotation":0,
"rotationTarget":0,
"poly": [
{x:-5, y: 0}, //top-left
{x: 5, y: 0}, //top-right
{x: 5, y: 25},//bottom-right
{x: -5, y: 25},//bottom-left
]
}
}
}
}
}
}
return body
}
const zMap = {
hat: 7,
head: 6,
torso: 5,
upperArmL: 1,
lowerArmL: 2,
upperArmR: 10,
lowerArmR: 11,
upperLegL: 3,
lowerLegL: 4,
upperLegR: 8,
lowerLegR: 9,
};
game.hats=[]
*/'] |