matrix-engine-wgpu
Version:
Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas
1,044 lines (954 loc) • 38.4 kB
JavaScript
import {uploadGLBModel} from "../../../src/engine/loaders/webgpu-gltf.js";
import {MatrixStream} from "../../../src/engine/networking/net.js";
import {byId, isOdd, LS, SS, mb, typeText, FullscreenManager, isMobile} from "../../../src/engine/utils.js";
import MatrixEngineWGPU from "../../../src/world.js";
import {HERO_ARCHETYPES} from "./hero.js";
import {AnimatedCursor} from "../../../src/engine/plugin/animated-cursor/animated-cursor.js";
import {fetchAll, fetchInfo} from "../../../src/engine/networking/matrix-stream.js";
import {RCSAccount} from "./rocket-crafting-account.js";
import {en} from "../../../public/res/multilang/en-backup.js";
import {MatrixTTS} from "./tts.js";
import {Editor} from "../../../src/tools/editor/editor.js";
/**
* @name forestOfHollowBloodStartSceen
*
* @licence
* Creative Commons Attribution 4.0 International (CC BY 4.0)
* You are free to share and adapt this project, provided that you give appropriate credit.
* Attribution requirement:
* Include the following notice (with working link) in any distributed version or about page:
*
* "Forest Of Hollow Blood — an RPG example made with MatrixEngineWGPU (https://github.com/zlatnaspirala/matrix-engine-wgpu)"
* @Note
* “Character and animation assets from Mixamo,
* used under Adobe’s royalty‑free license.
* Redistribution of raw assets is not permitted.”
*
* @Note
* This is startup main instance for menu screen and for the game.
* All @zlatnaspirala software use networking based
* on openvidu/kurento media server(webRTC).
* Node.js used for middleware.
* Server Events API also used for helping in creation of
* matching/waiting list players or get status of public channel
* (game-play channel).
*
* @note
* Only last non selected hero player will get
* first free hero in selection action next/back.
* For now. Next better varian can be timer solution.
*
* @Backend Session account stuff.
* RocketCraftingServer platform used.
**/
LS.clear();
SS.clear();
let forestOfHollowBloodStartSceen = new MatrixEngineWGPU({
dontUsePhysics: true,
// useEditor: true,
useSingleRenderPass: true,
canvasSize: 'fullscreen', // {w: window.visualViewport.width, h: window.visualViewport.height }
mainCameraParams: {
type: 'WASD',
responseCoef: 1000
},
clearColor: {r: 0, b: 0.1, g: 0.1, a: 1}
}, (forestOfHollowBloodStartSceen) => {
if('serviceWorker' in navigator) {
if(location.hostname.indexOf('localhost') == -1) {
navigator.serviceWorker.register('cache.js');
} else {
// RCSAccount
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
}
} else {
console.warn('Matrix Engine WGPU : No support for web workers in this browser.');
}
forestOfHollowBloodStartSceen.tts = new MatrixTTS();
forestOfHollowBloodStartSceen.account = new RCSAccount("https://maximumroulette.com");
forestOfHollowBloodStartSceen.account.createDOM();
forestOfHollowBloodStartSceen.FS = new FullscreenManager();
forestOfHollowBloodStartSceen.gamePlayStatus = null;
// in future replace with server event solution
forestOfHollowBloodStartSceen.gamePlayStatusTimer = null;
forestOfHollowBloodStartSceen.heroByBody = [];
forestOfHollowBloodStartSceen.selectedHero = 0;
forestOfHollowBloodStartSceen.lock = false;
// Audios
forestOfHollowBloodStartSceen.matrixSounds.createAudio('music2', 'res/audios/rpg/music.mp3', 1);
forestOfHollowBloodStartSceen.matrixSounds.createAudio('music', 'res/audios/rpg/wizard-rider.mp3', 1);
forestOfHollowBloodStartSceen.matrixSounds.createAudio('click1', 'res/audios/click1.mp3', 1);
app.matrixSounds.audios.click1.volume = 0.2;
forestOfHollowBloodStartSceen.matrixSounds.createAudio('hover', 'res/audios/kenney/mp3/click3.mp3', 2);
forestOfHollowBloodStartSceen.matrixSounds.createAudio('feel', 'res/audios/rpg/feel.mp3', 2);
let heros = null;
function checkUsername() {
if(JSON.parse(SS.get('RocketAcount')) != null && typeof JSON.parse(SS.get('RocketAcount')).nickname !== 'undefined') {
return JSON.parse(SS.get('RocketAcount')).nickname;
} else {
if(app.net.session !== null) {
return app.net.session.connection.connectionId;
} else {
return 'nosession';
}
}
}
// Networking
forestOfHollowBloodStartSceen.net = new MatrixStream({
active: true,
domain: 'maximumroulette.com',
port: 2020,
sessionName: 'forestOfHollowBlood-free-for-all-start',
resolution: '160x240',
isDataOnly: true
});
function handleHeroImage(selectHeroIndex) {
// func exist in case of changinf hero names...
let name = 'no-name';
if(selectHeroIndex == 0) {
name = 'mariasword';
} else if(selectHeroIndex == 1) {
name = 'slayzer';
} else if(selectHeroIndex == 2) {
name = 'steelborn';
} else if(selectHeroIndex == 3) {
name = 'warrok';
} else if(selectHeroIndex == 4) {
name = 'skeletonz';
} else if(selectHeroIndex == 5) {
name = 'erika';
} else if(selectHeroIndex == 6) {
name = 'arissa';
}
return name;
}
function checkHeroStatus() {
const indices = [];
document.querySelectorAll('[data-hero-index]').forEach(elem => {
const index = parseInt(elem.getAttribute('data-hero-index'));
indices.push(index);
});
// check if any value appears more than once
const hasDuplicate = indices.some((val, i) => indices.indexOf(val) !== i);
return hasDuplicate;
}
function determinateTeam() {
console.log('check remote conn.app.net.session.remoteConnections.size..', app.net.session.remoteConnections.size);
if(app.net.session.remoteConnections.size == 0) {
// Rule - even -> south team odd -> north team
return "south";
} else {
if(isOdd(app.net.session.remoteConnections.size) == true) {
return "north";
} else {
return "south";
}
}
}
function determinateSelection() {
if(app.net.session.connection != null) {
// console.log("Test team data moment", byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team'))
let testDom = byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team');
if(typeof testDom != 'string') {
console.log('Potencial error not handled....');
}
app.net.sendOnlyData({
type: "selectHeroIndex",
selectHeroIndex: app.selectedHero,
team: testDom
});
}
// fix for local
if(byId(`waithero-img-${app.net.session.connection.connectionId}`)) {
let heroImage = byId(`waithero-img-${app.net.session.connection.connectionId}`);
heroImage.src =
`./res/textures/rpg/hero-image/${handleHeroImage(app.selectedHero)}.png`;
heroImage.setAttribute('data-hero-index', app.selectedHero);
} else {
let heroImage = document.createElement('img');
heroImage.setAttribute('data-hero-index', app.selectedHero);
heroImage.id = `waithero-img-${app.net.session.connection.connectionId}`;
heroImage.width = '64';
heroImage.height = '64';
heroImage.src = `./res/textures/rpg/hero-image/${handleHeroImage(app.selectedHero)}.png`
byId(`waiting-${app.net.session.connection.connectionId}`).appendChild(heroImage);
}
// Only last non selected hero player will get
// first free hero in selection action next/back.
// For now.
if(checkHeroStatus() == true) {
console.log("hero used keep graphics no send");
return;
}
if(isAllSelected() == true) {
forestOfHollowBloodStartSceen.gotoGamePlay();
}
}
forestOfHollowBloodStartSceen.determinateSelection = determinateSelection;
function isAllSelected() {
let sumParty = document.querySelectorAll('[id*="waiting-"]');
let testSelection = document.querySelectorAll('[id*="waithero-img-"]');
console.info(testSelection, ' testSelection vs Number of players:', sumParty);
if(sumParty.length == forestOfHollowBloodStartSceen.MINIMUM_PLAYERS) {
// good all are still here
if(testSelection.length == forestOfHollowBloodStartSceen.MINIMUM_PLAYERS) {
// good all selected hero !PLAY!
return true;
} else {
mb.error(`No selection hero for all players...`);
return false;
}
} else {
mb.error(`No enough players...`);
return false;
}
}
forestOfHollowBloodStartSceen.gotoGamePlay = (preventEmit) => {
setTimeout(() => {
// check again ! good all selected hero !PLAY!
// console.log('...', byId(`waiting-${app.net.session.connection.connectionId}`));
LS.set('player', {
mesh: heros[app.selectedHero].meshName,
hero: heros[app.selectedHero].name,
path: heros[app.selectedHero].path,
archetypes: [heros[app.selectedHero].type],
team: byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team'),
data: Date.now(),
numOfPlayers: forestOfHollowBloodStartSceen.MINIMUM_PLAYERS,
useCameraOrAudio: true
})
SS.set('player', {
mesh: heros[app.selectedHero].meshName,
hero: heros[app.selectedHero].name,
path: heros[app.selectedHero].path,
archetypes: [heros[app.selectedHero].type],
team: byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team'),
data: Date.now(),
numOfPlayers: forestOfHollowBloodStartSceen.MINIMUM_PLAYERS,
useCameraOrAudio: true
})
if(typeof preventEmit === 'undefined') forestOfHollowBloodStartSceen.net.sendOnlyData({
type: 'start'
})
location.assign('rpg-game.html');
}, 1000);
}
if('connection' in navigator && navigator.connection) {
navigator.connection.onchange = (e) => {
console.info('Network state changed...', e);
if(e.target.downlink < 0.4) {
byId('loader').style.display = 'block';
byId('loader').style.fontSize = '150%';
byId('loader').innerHTML = `NO INTERNET CONNECTIONS`;
setTimeout(() => {
location.href = 'https://maximumroulette.com';
}, 3000);
}
}
}
addEventListener('check-gameplay-channel', (e) => {
let info = e.detail;
if(info.status != 'false' && typeof info.status !== "undefined") {
// console.log('check-gameplay-channel status:', info.status)
byId("onlineUsers").innerHTML = `GamePlay:Free`;
forestOfHollowBloodStartSceen.gamePlayStatus = "free";
byId('startBtnText').innerHTML = app.label.get.play;
byId("startBtnText").style.color = 'rgba(0, 0, 0, 0)';
clearInterval(forestOfHollowBloodStartSceen.gamePlayStatusTimer);
forestOfHollowBloodStartSceen.gamePlayStatusTimer = null;
} else {
// console.log('check-gameplay-channel status:', info.status)
if(typeof info.status != "undefined" && info.status == "false") {
// no internet
byId('loader').style.display = 'block';
alert("This is modal window, No internet connection... Please try ");
} else {
info = JSON.parse(e.detail);
if(info.connections && info.connections.numberOfElements == 0) {
byId("onlineUsers").innerHTML = `GamePlay:Free`;
forestOfHollowBloodStartSceen.gamePlayStatus = "free";
byId('startBtnText').innerHTML = app.label.get.play;
byId("startBtnText").style.color = 'rgba(0, 0, 0, 0)';
clearInterval(forestOfHollowBloodStartSceen.gamePlayStatusTimer);
forestOfHollowBloodStartSceen.gamePlayStatusTimer = null;
return;
}
byId("onlineUsers").innerHTML = `${app.label.get.alreadyingame}:${info.connections.numberOfElements}`;
forestOfHollowBloodStartSceen.gamePlayStatus = "used";
byId('startBtnText').innerHTML = `${app.label.get.gameplaychannel}:${app.label.get.used}`;
byId("startBtnText").style.color = 'rgb(255 53 53)';
forestOfHollowBloodStartSceen.gamePlayStatusTimer = setTimeout(() => {
app.net.fetchInfo('forestOfHollowBlood-free-for-all');
}, 30000);
}
}
})
forestOfHollowBloodStartSceen.MINIMUM_PLAYERS = (location.hostname.indexOf('localhost') != -1 ? 2 : 4);
forestOfHollowBloodStartSceen.setWaitingList = () => {
// access net doms who comes with broadcaster2.html
const waitingForOthersDOM = document.createElement("div");
waitingForOthersDOM.id = "waitingForOthersDOM";
Object.assign(waitingForOthersDOM.style, {
flexFlow: 'wrap',
width: "100%",
height: "35%",
backgroundColor: "rgba(60, 60, 60, 1)",
display: "flex",
alignItems: "center",
justifyContent: "space-around",
color: "white",
fontFamily: "'Orbitron', sans-serif",
zIndex: "1",
fontSize: '20px',
padding: "10px",
boxSizing: "border-box"
});
byId('session-header').appendChild(waitingForOthersDOM);
const onlineUsers = document.createElement("div");
onlineUsers.id = "onlineUsers";
Object.assign(onlineUsers.style, {
flexFlow: 'wrap',
width: "100%",
height: "35%",
backgroundColor: "rgba(60, 60, 60, 1)",
display: "flex",
alignItems: "center",
justifyContent: "space-around",
color: "white",
fontFamily: "'Orbitron', sans-serif",
zIndex: "1",
fontSize: '20px',
padding: "10px",
boxSizing: "border-box"
});
byId('netHeader').appendChild(onlineUsers);
// app.net.fetchInfo('forestOfHollowBlood-free-for-all');
}
if(document.querySelector('.form-group')) document.querySelector('.form-group').style.display = 'none';
// keep simple all networking code on top level
// all job will be done with no account for now.
addEventListener('net-ready', () => {
byId('matrix-net').style.opacity = '0.75';
document.querySelector('.form-group').style.display = 'none';
byId("caller-title").innerHTML = `forestOfHollowBlood`;
byId("sessionName").disabled = true;
forestOfHollowBloodStartSceen.setWaitingList();
// check game-play channel
setTimeout(() => {
app.net.fetchInfo('forestOfHollowBlood-free-for-all');
app.sendmsg = (m) => {
if(typeof m != 'string') return;
if(m.length > 120) return;
let username = checkUsername();
if(username != 'nosession') app.net.sendOnlyData({type: "chat", msg: m, username: username});
};
}, 1500);
});
addEventListener('connectionDestroyed', (e) => {
byId(`waiting-${e.detail.connectionId}`).remove();
});
addEventListener("onConnectionCreated", (e) => {
console.log('newconn : created', e.detail);
let newPlayer = document.createElement('div');
if(app.net.session.connection.connectionId == e.detail.connection.connectionId) {
console.log('newconn : created [LOCAL] determinate team');
document.title = app.net.session.connection.connectionId;
let team = determinateTeam();
newPlayer.setAttribute('data-hero-team', team);
newPlayer.innerHTML = `<div id="${e.detail.connection.connectionId}-title" >Player:${e.detail.connection.connectionId} Team:${team}</div>`;
setTimeout(() => {
//---------- test
if(app.net.session.connection != null) {
console.log("Test team data moment", byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team'))
let testDom = byId(`waiting-${app.net.session.connection.connectionId}`).getAttribute('data-hero-team');
if(typeof testDom != 'string') {
console.low('Potencial error not handled....')
}
app.net.sendOnlyData({
type: "selectHeroIndex",
selectHeroIndex: app.selectedHero,
team: testDom
});
app.net.sendOnlyData({type: "team-notify", team: team})
}
}, 2000);
} else {
newPlayer.innerHTML = `<div id="${e.detail.connection.connectionId}-title" >Player:${e.detail.connection.connectionId}</div>`;
}
newPlayer.id = `waiting-${e.detail.connection.connectionId}`;
byId('waitingForOthersDOM').appendChild(newPlayer);
let testParty = document.querySelectorAll('[id*="waiting-"]');
console.info('Test number of players:', testParty);
if(testParty.length == forestOfHollowBloodStartSceen.MINIMUM_PLAYERS) {
// when all choose hero goto play
mb.success(`Consensus is reached. Party${forestOfHollowBloodStartSceen.MINIMUM_PLAYERS}
When all player select hero gameplay starts.
`);
} else if(testParty.length < forestOfHollowBloodStartSceen.MINIMUM_PLAYERS) {
mb.success(`Player ${e.detail.connection.connectionId} joined party.Select your hero and wait for other...`);
} else if(testParty.length > forestOfHollowBloodStartSceen.MINIMUM_PLAYERS) {
if(e.detail.connection.connectionId == app.net.session.connection.connectionId) {
mb.success(`Max players is reached.Please wait for next party...`);
}
}
})
addEventListener('only-data-receive', (e) => {
let t = JSON.parse(e.detail.data);
if(t) {
if(t.type == 'selectHeroIndex') {
console.log(`<data-receive From ${e.detail.from} data:${t.selectHeroIndex}`);
let name = handleHeroImage(t.selectHeroIndex);
let heroImage = byId(`waithero-img-${e.detail.from.connectionId}`);
if(heroImage) {
heroImage.src = `./res/textures/rpg/hero-image/${name.toLowerCase()}.png`;
heroImage.setAttribute('data-hero-index', t.selectHeroIndex);
} else {
let heroImage = document.createElement('img');
heroImage.id = `waithero-img-${e.detail.from.connectionId}`;
heroImage.width = '64'; heroImage.height = '64';
heroImage.src = `./res/textures/rpg/hero-image/${name.toLowerCase()}.png`;
heroImage.setAttribute('data-hero-index', t.selectHeroIndex);
byId(`waiting-${e.detail.from.connectionId}`).appendChild(heroImage);
// also add team for initial user problem case...
if(t.team) {
byId(`${e.detail.from.connectionId}-title`).innerHTML = `Player:${e.detail.from.connectionId} Team:${t.team}`;
}
}
} else if(t.type == 'team-notify') {
console.log(`<data-receive From ${e.detail.from.connectionId} team:${t.team} ${byId(`waiting-${e.detail.from.connectionId}`)}`);
byId(`${e.detail.from.connectionId}-title`).innerHTML = `Player:${e.detail.from.connectionId} Team:${t.team}`;
} else if(t.type == 'start') {
forestOfHollowBloodStartSceen.gotoGamePlay("no emit");
} else if(t.type == 'chat') {
// add chat
if(t.msg.length > 120) {
t.msg = '';
return;
}
mb.show(`Msg from ${t.username}: ${t.msg}`);
}
}
})
// addEventListener('AmmoReady', async () => {
// catch
if(typeof app.label == 'undefined' || typeof app.label.get == 'undefined' || typeof app.label.get.mariasword == 'undefined') {
if(typeof app.label == 'undefined') app.label = {get: {}};
app.label.get = en;
}
app.matrixSounds.play('music');
heros = [
{type: "Warrior", name: 'MariaSword', path: "res/meshes/glb/woman1.glb", desc: forestOfHollowBloodStartSceen.label.get.mariasword},
{type: "Ranger", name: 'Slayzer', path: "res/meshes/glb/monster.glb", desc: forestOfHollowBloodStartSceen.label.get.slayzer},
{type: "Tank", name: 'Steelborn', path: "res/meshes/glb/bot.glb", desc: forestOfHollowBloodStartSceen.label.get.steelborn},
{type: "Mage", name: 'Warrok', path: "res/meshes/glb/warrok.glb", desc: forestOfHollowBloodStartSceen.label.get.warrok},
{type: "Necromancer", name: 'Skeletonz', path: "res/meshes/glb/skeletonz.glb", desc: forestOfHollowBloodStartSceen.label.get.skeletonz},
{type: "Assassin", name: 'Erika', path: "res/meshes/glb/erika.glb", desc: forestOfHollowBloodStartSceen.label.get.erika},
{type: "Support", name: 'Arissa', path: "res/meshes/glb/arissa.glb", desc: forestOfHollowBloodStartSceen.label.get.arissa},
];
forestOfHollowBloodStartSceen.heros = heros;
// helper
async function loadHeros() {
for(var x = 0;x < heros.length;x++) {
var glbFile01 = await fetch(heros[x].path).then(
res => res.arrayBuffer().then(buf => uploadGLBModel(buf, app.device)));
forestOfHollowBloodStartSceen.addGlbObjInctance({
material: (x == 2 ? {type: 'power', useTextureFromGlb: true} : {type: 'standard', useTextureFromGlb: true}),
scale: [20, 20, 20],
position: {x: 0 + x * 50, y: 0, z: -10},
name: heros[x].name,
texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'],
raycast: {enabled: true, radius: 1},
pointerEffect: {
enabled: true,
pointer: true,
flameEffect: false,
flameEmitter: true,
circlePlane: true,
circlePlaneTex: true,
circlePlaneTexPath: './res/textures/star1.png',
}
}, null, glbFile01);
}
setTimeout(() => {
forestOfHollowBloodStartSceen.cameras.WASD.position = [0, 14, 52];
app.cameras.WASD.pitch = -0.13;
app.cameras.WASD.yaw = 0;
app.mainRenderBundle.forEach((sceneObj) => {
sceneObj.position.thrust = 1;
if(sceneObj.effects) if(sceneObj.effects.flameEmitter) sceneObj.effects.flameEmitter.recreateVertexDataRND(1);
});
for(var x = 0;x < heros.length;x++) {
let hero0 = app.mainRenderBundle.filter((obj) => obj.name.indexOf(heros[x].name) != -1)
app.heroByBody.push(hero0);
heros[x].meshName = hero0[0].name;
if(x == 0) {
hero0[0].effects.circlePlane.instanceTargets[0].color = [1, 0, 2, 1];
}
hero0[0].effects.flameEmitter.instanceTargets.forEach((p, i, array) => {
array[i].color = [0, 1, 0, 0.7];
})
if(x == 2) {
hero0.forEach((p, i, array) => {
array[i].globalAmbient = [11, 11, 1];
})
}
if(x == 3 || x == 5) {
hero0.forEach((p, i, array) => {
array[i].globalAmbient = [10, 10, 10];
array[i].effects.flameEmitter.smoothFlickeringScale = 0.005;
})
}
if(x == 6) {
hero0.forEach((p, i, array) => {
array[i].globalAmbient = [21, 11, 11];
})
}
}
app.lightContainer[0].position[2] = 10;
app.lightContainer[0].position[1] = 50;
app.lightContainer[0].intensity = 1.4;
}, 4000);
}
loadHeros();
createHUDMenu();
// })
forestOfHollowBloodStartSceen.addLight();
function createHUDMenu() {
forestOfHollowBloodStartSceen.animatedCursor = new AnimatedCursor({
path: "./res/icons/seq1/",
frameCount: 7,
speed: 80,
loop: true
})
forestOfHollowBloodStartSceen.animatedCursor.start();
// document.body.style.cursor = "url('./res/icons/default.png') 0 0, auto";
document.addEventListener("contextmenu", event => event.preventDefault());
byId('canvas1').style.pointerEvents = 'none';
const hud = document.createElement("div");
hud.id = "hud-menu";
Object.assign(hud.style, {
position: "fixed",
bottom: "0",
left: "0",
width: "100%",
height: "35%",
backgroundColor: "rgba(60, 60, 60, 1)",
display: "flex",
alignItems: "center",
justifyContent: "space-around",
color: "white",
fontFamily: "'Orbitron', sans-serif",
zIndex: "1",
fontSize: '20px',
padding: "10px",
boxSizing: "border-box"
});
const nextBtn = document.createElement("button");
Object.assign(nextBtn.style, {
// position: "absolute",
width: "80px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
height: "40px",
fontSize: '16px',
});
nextBtn.classList.add('buttonMatrix');
nextBtn.innerHTML = `
<div class="button-outer">
<div class="button-inner">
<span id='nextBtn'>${app.label.get.next}</span>
</div>
</div>
`;
nextBtn.addEventListener('click', () => {
if(app.selectedHero >= app.heroByBody.length - 1 || app.lock == true) {
console.log('NEXTBLOCKED ', app.selectedHero)
return;
}
app.lock = true;
app.selectedHero++;
console.log('app.selectedHero::: ', app.selectedHero)
// Fix on remote
if(app.net.session) {
determinateSelection();
} else {
app.tts.speakHero(handleHeroImage(app.selectedHero), 'hello');
}
app.heroByBody.forEach((sceneObj, indexRoot) => {
sceneObj.forEach((heroBodie) => {
heroBodie.position.translateByX(-50 * app.selectedHero + indexRoot * 50)
heroBodie.position.onTargetPositionReach = () => {
app.lock = false;
}
if(heroBodie.effects.circlePlane) {
if(indexRoot == app.selectedHero) {
heroBodie.effects.circlePlane.instanceTargets[0].color = [1, 0, 2, 1];
} else {
heroBodie.effects.circlePlane.instanceTargets[0].color = [0.6, 0.8, 1, 0.4];
}
}
})
})
updateDesc();
app.matrixSounds.play('click1');
});
const desc = document.createElement("div");
desc.id = 'desc';
Object.assign(desc.style, {
display: 'flex',
flexDirection: 'column',
width: "300px",
textAlign: "center",
color: 'c4deff',
fontWeight: "bold",
textShadow: "0 0 5px black",
});
desc.textContent = "HERO INFO";
const previusBtn = document.createElement("button");
Object.assign(previusBtn.style, {
width: "80px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
height: "40px",
fontSize: '16px'
});
previusBtn.classList.add('buttonMatrix');
previusBtn.innerHTML = `
<div class="button-outer">
<div class="button-inner">
<span id='previusBtnText'>${app.label.get.back}</span>
</div>
</div>
`;
previusBtn.addEventListener('click', () => {
console.log('TEST previusBtn forestOfHollowBloodStartSceen.selectedHero', app.selectedHero)
if(app.selectedHero < 1 || app.lock == true) {
// console.log('BLOCKED', app.selectedHero)
return;
}
app.lock = true;
app.selectedHero--;
if(app.net.session) {
determinateSelection();
} else {
app.tts.speakHero(handleHeroImage(app.selectedHero), 'hello');
}
app.heroByBody.forEach((sceneObj, indexRoot) => {
sceneObj.forEach((heroBodie) => {
heroBodie.position.translateByX(-app.selectedHero * 50 + indexRoot * 50)
heroBodie.position.onTargetPositionReach = () => {
app.lock = false;
};
if(heroBodie.effects.circlePlane) {
if(indexRoot == app.selectedHero) {
heroBodie.effects.circlePlane.instanceTargets[0].color = [1, 0, 2, 1];
} else {
heroBodie.effects.circlePlane.instanceTargets[0].color = [0.6, 0.8, 1, 0.4];
}
}
})
})
updateDesc();
app.matrixSounds.play('click1');
});
function updateDesc() {
byId('desc').innerHTML = `
<div style='height:130px;'> ${app.heros[(app.selectedHero)].desc}</div>
`;
let C = HERO_ARCHETYPES[app.heros[(app.selectedHero)].type];
for(let key in C) {
byId('desc').innerHTML += `
<div style='font-size: 15px;display: inline-flex;justify-content:space-between'>
<span style="color:#00e2ff"> ${key} </span> : <span style="color:#02e2ff">${C[key]} </span>
</div>
`;
}
}
const startBtn = document.createElement("button");
Object.assign(startBtn.style, {
position: "fixed",
bottom: '40px',
right: '120px',
width: "250px",
height: "54px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
color: '#ffffffff',
background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto'
});
startBtn.classList.add('buttonMatrix');
startBtn.innerHTML = `
<div class="button-outer">
<div class="button-inner">
<span id='startBtnText'>${app.label.get.play}</span>
</div>
</div>
`;
forestOfHollowBloodStartSceen.notifyHeroSelectionTimer = null;
startBtn.addEventListener('click', (e) => {
if(app.net.connection == null) {
if(forestOfHollowBloodStartSceen.gamePlayStatus != "free") {
mb.show(app.label.get.gameplayused);
return;
}
// console.log('app.net.connection is null let join gameplay sesion... Wait list.', app.selectedHero)
byId('join-btn').click();
byId("startBtnText").innerHTML = app.label.get.waiting_for_others;
e.target.disabled = true;
app.matrixSounds.play('feel');
// test - for late users notify again
app.notifyHeroSelectionTimer = setInterval(() => {
// not tested
console.log('determinateSelection called 10 sec !')
determinateSelection();
}, 10000);
return;
} else {
console.log('nothing...', app.selectedHero)
return;
}
});
//about
forestOfHollowBloodStartSceen.showAbout = () => {
byId('helpBox').style.display = 'block';
typeText('helpBox', app.label.get.aboutRPG, 10);
};
var helpBox = document.createElement('div')
helpBox.id = 'helpBox';
helpBox.style.position = 'fixed';
helpBox.style.right = '20%';
helpBox.style.display = 'none';
helpBox.style.zIndex = '2';
helpBox.style.top = '15%';
helpBox.style.width = '60%';
helpBox.style.height = '50%';
helpBox.style.fontSize = '100%';
helpBox.classList.add('btn');
helpBox.addEventListener('click', () => {
byId('helpBox').style.display = 'none';
});
document.body.appendChild(helpBox);
const aboutBtn = document.createElement("button");
Object.assign(aboutBtn.style, {
position: "fixed",
bottom: '40px',
left: '20px',
width: "150px",
height: "54px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
color: '#ffffffff',
background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto',
pointerEvents: 'auto'
});
aboutBtn.classList.add('buttonMatrix');
aboutBtn.innerHTML = `
<div class="button-outer">
<div class="button-inner">
<span data-label='aboutword'>${app.label.get.about_}</span>
</div>
</div>
`;
aboutBtn.addEventListener('click', (e) => app.showAbout());
hud.appendChild(aboutBtn);
const LBBtn = document.createElement("button");
Object.assign(LBBtn.style, {
position: "fixed",
bottom: '220px',
left: '20px',
width: "140px",
height: "28px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
color: '#ffffffff',
background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto',
pointerEvents: 'auto'
});
// LBBtn.classList.add('buttonMatrix');
// LBBtn.innerHTML = `
// <div class="button-outer">
// <div class="button-inner">
// <span data-label='leaderboard'>${app.label.get.leaderboard}</span>
// </div>
// </div>
// `;
LBBtn.innerHTML = `
<span data-label='leaderboard'>${app.label.get.leaderboard}</span>
`;
LBBtn.addEventListener('click', app.account.getLeaderboard);
hud.appendChild(LBBtn);
// chat box
const sendMsgInput = document.createElement("input");
sendMsgInput.id = 'msg-input';
sendMsgInput.type = "text";
sendMsgInput.addEventListener("input", (e) => {
if(e.target.value.length > 120) {
e.target.value = e.target.value.slice(0, 120);
}
});
Object.assign(sendMsgInput.style, {
position: "fixed",
bottom: '282px',
left: '20px',
width: "134px",
height: "17px",
textAlign: "center",
color: "white",
fontWeight: "bold",
// textShadow: "0 0 2px black",
color: 'black',
// background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto',
pointerEvents: 'auto'
});
hud.appendChild(sendMsgInput);
const sendMsgBtn = document.createElement("button");
Object.assign(sendMsgBtn.style, {
position: "fixed",
bottom: '253px',
left: '20px',
width: "140px",
height: "28px",
textAlign: "center",
color: "white",
fontWeight: "bold",
textShadow: "0 0 2px black",
color: '#ffffffff',
background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto',
pointerEvents: 'auto'
});
// sendMsgBtn.classList.add('buttonMatrix');
// sendMsgBtn.innerHTML = `
// <div class="button-outer">
// <div class="button-inner">
// <span data-label='leaderboard'>${app.label.get.leaderboard}</span>
// </div>
// </div>
// `;
sendMsgBtn.innerHTML = `<span data-label='leaderboard'>${app.label.get.sendmsg}</span> `;
sendMsgBtn.addEventListener('click', () => {
sendMsgBtn.disabled = true;
sendMsgBtn.style.color = 'gray';
app.sendmsg(sendMsgInput.value);
setTimeout(() => {
sendMsgBtn.disabled = false;
sendMsgBtn.style.color = 'white';
}, 5000);
sendMsgInput.value = "";
});
hud.appendChild(sendMsgBtn);
// end
const loader = document.createElement("div");
loader.id = 'loader';
Object.assign(loader.style, {
position: "fixed",
display: 'flex',
bottom: '0',
left: '0',
width: "100vw",
height: "100vh",
textAlign: "center",
color: "white",
zIndex: 10,
fontWeight: "bold",
textShadow: "0 0 2px black",
color: '#ffffffff',
background: '#000000ff',
fontSize: '16px',
cursor: 'url(./res/icons/default.png) 0 0, auto',
pointerEvents: 'auto',
filter: 'grayscale(1)'
});
loader.innerHTML = `
<div class="loader">
<div class="progress-container">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="counter" id="counter">0%</div>
</div>
`;
loader.addEventListener('click', (e) => {app.matrixSounds.play('music');});
hud.appendChild(loader);
let progress = 0;
let bar = null;
let counter = null;
function fakeProgress() {
if(progress < 100) {
progress += Math.random() * 3.5;
if(progress > 100) progress = 100;
bar.style.width = progress + '%';
counter.textContent = Math.floor(progress) + '%' + ' This is beta 1 version - no magic attack implementation...';
let grayEffect = 30 / progress;
byId('loader').style.filter = `grayscale(${grayEffect})`;
setTimeout(fakeProgress, 80 + Math.random() * 150);
} else {
counter.textContent = app.label.get.letthegame + " - This is beta 1 version - no magic attack...";
bar.style.boxShadow = "0 0 30px #00ff99";
setTimeout(() => {
loader.style.display = 'none';
// loader.remove();
}, 250)
}
}
setTimeout(() => {
bar = document.getElementById('progressBar');
counter = document.getElementById('counter');
fakeProgress()
}, 300);
hud.appendChild(previusBtn);
hud.appendChild(desc);
hud.appendChild(nextBtn);
hud.appendChild(startBtn);
document.body.appendChild(hud);
updateDesc();
document.querySelectorAll('.buttonMatrix').forEach(el => {
el.addEventListener('mouseenter', () => {
app.matrixSounds.play('hover');
});
});
/**
* @description
* Important moment for sys browser stuff
*/
function firstClick() {
// add here after - fs force
app.matrixSounds.audios.music.volume = 0.2;
app.matrixSounds.audios.music2.volume = 0.2;
app.matrixSounds.play('music');
app.matrixSounds.audios.music.onended = () => {
app.matrixSounds.play('music2');
};
app.matrixSounds.audios.music2.onended = () => {
app.matrixSounds.play('music');
};
removeEventListener('click', firstClick);
// for mobile no need to call - if called porttrain forced (current orientation on mobile device)
if(location.hostname.indexOf('localhost') == -1 && isMobile() == false) app.FS.request();
}
addEventListener('click', firstClick);
}
})
window.app = forestOfHollowBloodStartSceen;