UNPKG

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,230 lines (1,207 loc) 1.22 MB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Character = void 0; var _wgpuMatrix = require("wgpu-matrix"); var _webgpuGltf = require("../../../src/engine/loaders/webgpu-gltf"); var _utils = require("../../../src/engine/utils"); var _hero = require("./hero"); var _creepCharacter = require("./creep-character"); var _navMesh = require("./nav-mesh"); var _static = require("./static"); var _friendlyCharacter = require("./friendly-character"); class Character extends _hero.Hero { friendlyLocal = { heroes: [], creeps: [] }; creepThrust = 0.85; heroAnimationArrange = { dead: null, walk: null, salute: null, attack: null, idle: null }; friendlyCreepAnimationArrange = { dead: null, walk: null, salute: null, attack: null, idle: null }; heroFocusAttackOn = null; mouseTarget = null; // gold = 100; constructor(forestOfHollowBlood, path, name = 'MariaSword', archetypes = ["Warrior", "Mage"]) { super(name, archetypes); console.info(`%cplayer.data : ${forestOfHollowBlood.player.data}`, _utils.LOG_MATRIX); this.name = name; this.core = forestOfHollowBlood; this.heroe_bodies = []; this.loadLocalHero(path); this.loadfriendlyCreeps(); setTimeout(() => this.setupHUDForHero(name), 1100); } setupHUDForHero(name) { // console.info(`%cLOADING hero name : ${name}`, LOG_MATRIX) for (var x = 1; x < 5; x++) { (0, _utils.byId)(`magic-slot-${x - 1}`).style.background = `url("./res/textures/rpg/magics/${name.toLowerCase()}-${x}.png")`; (0, _utils.byId)(`magic-slot-${x - 1}`).style.backgroundRepeat = "round"; } (0, _utils.byId)('hudLeftBox').style.background = `url('./res/textures/rpg/hero-image/${name.toLowerCase()}.png') center center / cover no-repeat`; (0, _utils.byId)('hudDesriptionText').innerHTML = app.label.get[name.toLowerCase()]; } async loadfriendlyCreeps() { this.friendlyLocal.creeps.push(new _creepCharacter.Creep({ core: this.core, name: 'friendly_creeps0', archetypes: ["creep"], path: 'res/meshes/glb/bot.glb', position: { x: 0, y: -23, z: 0 } }, ['creep'], 'friendly', app.player.data.team)); this.friendlyLocal.creeps.push(new _creepCharacter.Creep({ core: this.core, name: 'friendly_creeps1', archetypes: ["creep"], path: 'res/meshes/glb/bot.glb', position: { x: 150, y: -23, z: 0 } }, ['creep'], 'friendly', app.player.data.team)); this.friendlyLocal.creeps.push(new _creepCharacter.Creep({ core: this.core, name: 'friendly_creeps2', archetypes: ["creep"], path: 'res/meshes/glb/bot.glb', position: { x: 100, y: -23, z: 0 } }, ['creep'], 'friendly', app.player.data.team)); setTimeout(() => { app.localHero.setAllCreepsAtStartPos().then(() => {}).catch(() => { setTimeout(() => { app.localHero.setAllCreepsAtStartPos().then(() => { // console.log('passed in 2') }).catch(() => { setTimeout(() => { app.localHero.setAllCreepsAtStartPos().then(() => { // console.log('passed in 3') }).catch(() => { console.log('FAILD setAllCreepsAtStartPos'); }); }, 7000); }); }, 7000); }); }, 10000); } async loadLocalHero(p) { try { var glbFile01 = await fetch(p).then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, this.core.device))); this.core.addGlbObjInctance({ material: { type: 'standard', useTextureFromGlb: true }, scale: [20, 20, 20], position: { x: _static.startUpPositions[this.core.player.data.team][0], y: _static.startUpPositions[this.core.player.data.team][1], z: _static.startUpPositions[this.core.player.data.team][2] }, name: this.name, texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'], raycast: { enabled: true, radius: 1.5 }, pointerEffect: { enabled: true, pointer: true, energyBar: true, flameEffect: false, flameEmitter: true, circlePlane: false, circlePlaneTex: true, circlePlaneTexPath: './res/textures/star1.png' } }, null, glbFile01); // Poenter mouse click var glbFile02 = await fetch('./res/meshes/glb/ring1.glb').then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, this.core.device))); this.core.addGlbObjInctance({ material: { type: 'standard', useTextureFromGlb: false }, scale: [20, 20, 20], position: { x: 0, y: -24, z: -220 }, name: 'mouseTarget', texturesPaths: ['./res/textures/default.png'], raycast: { enabled: false, radius: 1.5 }, pointerEffect: { enabled: true, // circlePlane: true, circlePlaneTex: true, circlePlaneTexPath: './res/textures/star1.png' } }, null, glbFile02); // make small async - cooking glbs files mouseTarget_Circle this.setupHero().then(() => { // }).catch(() => { this.setupHero().then(() => {}).catch(() => {}); }); } catch (err) { throw err; } } setupHero() { return new Promise((resolve, reject) => { setTimeout(() => { // console.info(`%cAnimation setup...`, LOG_MATRIX) this.mouseTarget = app.getSceneObjectByName('mouseTarget_Circle'); this.mouseTarget.animationSpeed = 20000; if (typeof app.localHero.mouseTarget.instanceTargets === 'undefined') { reject(); return; } app.localHero.mouseTarget.instanceTargets[1].position[1] = 1; app.localHero.mouseTarget.instanceTargets[1].scale = [0.4, 0.4, 0.4]; this.heroe_bodies = app.mainRenderBundle.filter(obj => obj.name && obj.name.includes(this.name)); this.core.RPG.heroe_bodies = this.heroe_bodies; this.core.RPG.heroe_bodies.forEach((subMesh, id) => { subMesh.position.thrust = this.moveSpeed; subMesh.glb.animationIndex = 0; // adapt manual if blender is not setup subMesh.glb.glbJsonData.animations.forEach((a, index) => { // console.info(`%c ANimation: ${a.name} index ${index}`, LOG_MATRIX) if (a.name == 'dead') this.heroAnimationArrange.dead = index; if (a.name == 'walk') this.heroAnimationArrange.walk = index; if (a.name == 'salute') this.heroAnimationArrange.salute = index; if (a.name == 'attack') this.heroAnimationArrange.attack = index; if (a.name == 'idle') this.heroAnimationArrange.idle = index; }); if (id == 0) subMesh.sharedState.emitAnimationEvent = true; this.core.collisionSystem.register(`local${id}`, subMesh.position, 15.0, 'local_hero'); }); if (app.localHero.heroe_bodies[0].effects) { app.localHero.heroe_bodies[0].effects.flameEmitter.recreateVertexDataRND(1); } else { console.log(`warn: ${app.localHero.heroe_bodies[0]} `); } // adapt app.localHero.heroe_bodies[0].globalAmbient = [1, 1, 1, 1]; if (app.localHero.name == 'Slayzer') { app.localHero.heroe_bodies[0].globalAmbient = [2, 2, 3, 1]; } else if (app.localHero.name == 'Steelborn') { app.localHero.heroe_bodies[0].globalAmbient = [12, 12, 12, 1]; } else { app.localHero.heroe_bodies[0].globalAmbient = [2, 2, 3, 1]; } app.localHero.heroe_bodies[0].effects.circlePlaneTex.rotateEffectSpeed = 0.1; this.attachEvents(); // important! for (var x = 0; x < app.localHero.heroe_bodies.length; x++) { if (x > 0) { app.localHero.heroe_bodies[x].position = app.localHero.heroe_bodies[0].position; app.localHero.heroe_bodies[x].rotation = app.localHero.heroe_bodies[0].rotation; } } // activete net pos emit - becouse uniq name of hero body set net id by scene obj name simple // app.localHero.heroe_bodies[0].position.netObject = app.net.session.connection.connectionId; // not top solution - for now . High cost - precision good. app.localHero.heroe_bodies[0].position.netObject = app.localHero.heroe_bodies[0].name; // for now net view for rot is axis separated - cost is ok for orientaion remote pass app.localHero.heroe_bodies[0].rotation.emitY = app.localHero.heroe_bodies[0].name; dispatchEvent(new CustomEvent('local-hero-bodies-ready', { detail: `This is not sync - 99% works` })); }, 5000); // return to 2 -3 - testing on 3-4 on same computer }); } async loadFriendlyHero(p) { try { this.friendlyLocal.heroes.push(new _friendlyCharacter.FriendlyHero({ core: this.core, name: p.hero, archetypes: p.archetypes, path: p.path, position: { x: 0, y: -23, z: 0 } })); } catch (err) { console.error(err); } } setAllCreepsAtStartPos = () => { return new Promise((resolve, reject) => { try { this.friendlyLocal.creeps.forEach(subMesh_ => { if (typeof subMesh_.heroe_bodies === 'undefined') { reject(); return; } }); // console.info(`%c promise pass setAllCreepsAtStartPos...`, LOG_MATRIX) this.friendlyLocal.creeps.forEach((subMesh_, id) => { let subMesh = subMesh_.heroe_bodies[0]; subMesh.position.thrust = subMesh_.moveSpeed; subMesh.glb.animationIndex = 0; // adapt manual if blender is not setup subMesh.glb.glbJsonData.animations.forEach((a, index) => { // console.info(`%c ANimation: ${a.name} index ${index}`, LOG_MATRIX) if (a.name == 'dead') this.friendlyCreepAnimationArrange.dead = index; if (a.name == 'walk') this.friendlyCreepAnimationArrange.walk = index; if (a.name == 'salute') this.friendlyCreepAnimationArrange.salute = index; if (a.name == 'attack') this.friendlyCreepAnimationArrange.attack = index; if (a.name == 'idle') this.friendlyCreepAnimationArrange.idle = index; }); // if(id == 0) subMesh.sharedState.emitAnimationEvent = true; // this.core.collisionSystem.register(`local${id}`, subMesh.position, 15.0, 'local_hero'); }); app.localHero.friendlyLocal.creeps.forEach((creep, index) => { creep.heroe_bodies[0].position.setPosition(_static.startUpPositions[this.core.player.data.team][0] + (index + 1) * 50, _static.startUpPositions[this.core.player.data.team][1], _static.startUpPositions[this.core.player.data.team][2] + (index + 1) * 50); }); resolve(); } catch (err) { console.info('err in: ', err); } }); }; navigateCreeps() { app.localHero.friendlyLocal.creeps.forEach((creep, index) => { this.navigateCreep(creep, index); }); } distance3DArrayInput(a, b) { const dx = a[0] - b[0]; const dy = a[1] - b[1]; const dz = a[2] - b[2]; return Math.sqrt(dx * dx + dy * dy + dz * dz); } navigateCreep(creep, index) { if (creep.creepFocusAttackOn != null) { // console.log('test attacher nuuu return '); return; } creep.firstPoint = _static.creepPoints[this.core.player.data.team].firstPoint; creep.finalPoint = _static.creepPoints[this.core.player.data.team].finalPoint; const start = [creep.heroe_bodies[0].position.x, creep.heroe_bodies[0].position.y, creep.heroe_bodies[0].position.z]; let test = this.distance3DArrayInput(creep.firstPoint, start); if (test < 20) { creep.gotoFinal = true; } const end = [creep.firstPoint[0], creep.firstPoint[1], creep.firstPoint[2]]; const endFinal = [creep.finalPoint[0], creep.finalPoint[1], creep.finalPoint[2]]; let path; if (creep.gotoFinal) { if (creep.gotoFinal == true) { path = this.core.RPG.nav.findPath(start, endFinal); } else { path = this.core.RPG.nav.findPath(start, end); } } else { path = this.core.RPG.nav.findPath(start, end); } if (!path || path.length === 0) { console.warn('No valid path found.'); return; } this.setWalkCreep(index); (0, _navMesh.followPath)(creep.heroe_bodies[0], path, this.core); } setWalk() { this.core.RPG.heroe_bodies.forEach((subMesh, index) => { subMesh.glb.animationIndex = this.heroAnimationArrange.walk; // console.info(`%chero walk`, LOG_MATRIX) if (index == 0) app.net.send({ sceneName: subMesh.name, animationIndex: subMesh.glb.animationIndex }); }); } setSalute() { this.core.RPG.heroe_bodies.forEach((subMesh, index) => { subMesh.glb.animationIndex = this.heroAnimationArrange.salute; // console.info(`%chero salute`, LOG_MATRIX) if (index == 0) app.net.send({ sceneName: subMesh.name, animationIndex: subMesh.glb.animationIndex }); }); } setDead() { this.core.RPG.heroe_bodies.forEach((subMesh, index) => { subMesh.glb.animationIndex = this.heroAnimationArrange.dead; if (index == 0) app.net.send({ sceneName: subMesh.name, animationIndex: subMesh.glb.animationIndex }); console.info(`%cHero dead${subMesh.name}.`, _utils.LOG_MATRIX); }); } setIdle() { this.core.RPG.heroe_bodies.forEach((subMesh, index) => { subMesh.glb.animationIndex = this.heroAnimationArrange.idle; // console.info(`%chero idle`, LOG_MATRIX) if (index == 0) app.net.send({ sceneName: subMesh.name, animationIndex: subMesh.glb.animationIndex }); }); } setAttack(on) { this.heroFocusAttackOn = on; this.core.RPG.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.attack; // console.info(`%c ${subMesh.name} BEFORE SEND attack index ${subMesh.glb.animationIndex}`, LOG_MATRIX) app.net.send({ sceneName: subMesh.name, animationIndex: subMesh.glb.animationIndex }); }); app.tts.speakHero(app.player.data.hero.toLowerCase(), 'attack'); } setWalkCreep(creepIndex) { console.info(`%cfriendly setWalkCreep!`, _utils.LOG_MATRIX); // if(this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex != this.friendlyCreepAnimationArrange.walk) { // } this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex = this.friendlyCreepAnimationArrange.walk; let pos = this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].position; if (this.core.net.virtualEmiter == null) { return; } if (pos.teams.length > 0) if (pos.teams[0].length > 0) app.net.send({ toRemote: pos.teams[0], // default null remote conns sceneName: pos.netObject, // origin scene name to receive animationIndex: this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex }); if (pos.teams.length > 0) if (pos.teams[1].length > 0) app.net.send({ toRemote: pos.teams[1], // default null remote conns remoteName: pos.remoteName, // to enemy players sceneName: pos.netObject, // now not important animationIndex: this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex }); // } } setAttackCreep(creepIndex) { // console.info(`%cfriendly creep attack enemy!`, LOG_MATRIX) // if(this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex != this.friendlyCreepAnimationArrange.attack) { this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex = this.friendlyCreepAnimationArrange.attack; // } let pos = this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].position; if (this.core.net.virtualEmiter == null) { return; } if (pos.teams.length > 0) if (pos.teams[0].length > 0) app.net.send({ toRemote: pos.teams[0], // default null remote conns sceneName: pos.netObject, // origin scene name to receive animationIndex: this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex }); if (pos.teams.length > 0) if (pos.teams[1].length > 0) app.net.send({ toRemote: pos.teams[1], // default null remote conns remoteName: pos.remoteName, // to enemy players sceneName: pos.netObject, // now not important animationIndex: this.friendlyLocal.creeps[creepIndex].heroe_bodies[0].glb.animationIndex }); } attachEvents() { addEventListener('attack-magic0', e => { this.setSalute(); console.log(e.detail); this.core.RPG.heroe_bodies.forEach(subMesh => { // level0 have only one instance - more level more instance in visuals context console.info(`%cLOADING hero ghostPos`, _utils.LOG_MATRIX); const distance = 100.0; // how far in front of hero const lift = 0.5; // --- rotation.y in degrees → radians const yawRad = (subMesh.rotation.y || 0) * Math.PI / 180; // --- local forward vector (relative to hero) const forward = _wgpuMatrix.vec3.normalize([Math.sin(yawRad), // x 0, // y Math.cos(yawRad) // z (rig faces -Z) ]); // --- compute ghost local offset const ghostOffset = _wgpuMatrix.vec3.mulScalar(forward, distance); ghostOffset[1] += lift; // --- apply to local instance position subMesh.instanceTargets[1].position = ghostOffset; setTimeout(() => { subMesh.instanceTargets[1].position[0] = 0; subMesh.instanceTargets[1].position[2] = 0; console.log("maybe idle? ", this.setWalk); this.setWalk(); }, 1300); }); }); // Events HERO pos addEventListener('set-walk', () => { this.setWalk(); app.tts.speakHero(app.player.data.hero.toLowerCase(), 'walk'); }); addEventListener('set-idle', () => { this.setIdle(); }); addEventListener('set-attack', () => { this.setAttack(); }); addEventListener('set-dead', () => { this.setDead(); }); addEventListener('set-salute', () => { this.setSalute(); }); addEventListener('close-distance', e => { if (e.detail.A.id.indexOf('friendly') != -1 && e.detail.B.id.indexOf('friendly') != -1 || e.detail.A.group == "local_hero" && e.detail.B.id.indexOf('friendly') != -1 || e.detail.A.group == "friendly" && e.detail.B.group == "local_hero") { // console.info('close distance BOTH friendly :', e.detail.A) return; } // core.net.virtualEmiter != null no emiter only for local hero corespondes if (e.detail.A.group == "enemy" && this.core.net.virtualEmiter != null) { if (e.detail.B.group == "friendly" && e.detail.B.id.indexOf('friendlytron') == -1) { //------------------ BLOCK let lc = app.localHero.friendlyLocal.creeps.filter(localCreep => localCreep.name == e.detail.B.id)[0]; console.info('A = enemy vs B = friendly <close-distance> is there friendly creeps here ', lc); if (lc === undefined) { return; } lc.creepFocusAttackOn = app.enemies.enemies.filter(enemy => enemy.name == e.detail.A.id)[0]; if (lc.creepFocusAttackOn === undefined) { lc.creepFocusAttackOn = app.enemies.creeps.filter(creep => creep.name == e.detail.A.id)[0]; // console.info('A = enemy vs B = friendly <close-distance> is there enemy HERO here ', lc.creepFocusAttackOn); } if (lc.creepFocusAttackOn === undefined && e.detail.A.id.indexOf('enemytron') != -1) { lc.creepFocusAttackOn = app.enemytron; console.info('<generate game event here> creeps attack enemy home.', lc.creepFocusAttackOn); } if (lc.creepFocusAttackOn === undefined) { return; } app.localHero.setAttackCreep(e.detail.B.id[e.detail.B.id.length - 1]); } } else if (e.detail.A.group == "friendly" && e.detail.A.id.indexOf('friendlytron') == -1) { if (e.detail.B.group == "enemy" && this.core.net.virtualEmiter != null) { let lc = app.localHero.friendlyLocal.creeps.filter(localCreep => localCreep.name == e.detail.A.id)[0]; if (lc === undefined) { return; } lc.creepFocusAttackOn = app.enemies.enemies.filter(enemy => enemy.name == e.detail.B.id)[0]; if (lc.creepFocusAttackOn == undefined) { lc.creepFocusAttackOn = app.enemies.creeps.filter(creep => creep.name == e.detail.B.id)[0]; } if (lc.creepFocusAttackOn === undefined && e.detail.B.id.indexOf('enemytron') != -1) { lc.creepFocusAttackOn = app.enemytron; // console.info('<generate game event here> creeps attack enemy home.', lc.creepFocusAttackOn); } if (lc.creepFocusAttackOn === undefined) { return; } app.localHero.setAttackCreep(e.detail.A.id[e.detail.A.id.length - 1]); } } // LOCAL if (e.detail.A.group == 'local_hero') { this.heroFocusAttackOn = app.enemies.enemies.filter(enemy => enemy.name == e.detail.B.id)[0]; if (this.heroFocusAttackOn == undefined) { this.heroFocusAttackOn = app.enemies.creeps.filter(creep => creep.name == e.detail.B.id)[0]; if (this.heroFocusAttackOn == undefined) { if (e.detail.B.id.indexOf('enemytron') != -1) { this.heroFocusAttackOn = app.enemytron; // console.info('<generate game event> LOCALHERO attack enemy home.', this.heroFocusAttackOn); } } } this.setAttack(this.heroFocusAttackOn); } else if (e.detail.B.group == 'local_hero') { this.heroFocusAttackOn = app.enemies.enemies.filter(enemy => enemy.name == e.detail.A.id)[0]; if (this.heroFocusAttackOn == undefined) { this.heroFocusAttackOn = app.enemies.creeps.filter(creep => creep.name == e.detail.A.id)[0]; if (this.heroFocusAttackOn == undefined) { if (e.detail.A.id.indexOf('enemytron') != -1) { this.heroFocusAttackOn = app.enemytron; // console.info('<generate game event here2> creeps attack enemy home.', this.heroFocusAttackOn); } } } this.setAttack(this.heroFocusAttackOn); } }); addEventListener(`animationEnd-${this.heroe_bodies[0].name}`, e => { if (e.detail.animationName != 'attack' || typeof this.core.enemies === 'undefined') { return; } let isEnemiesClose = false; let isEnemiesCreepClose = false; if (this.heroFocusAttackOn == null) { // console.info('animationEnd [heroFocusAttackOn == null ]', e.detail.animationName) this.core.enemies.enemies.forEach(enemy => { if (typeof enemy.heroe_bodies === 'undefined') return; if (enemy.heroe_bodies) { let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, enemy.heroe_bodies[0].position); if (tt < this.core.RPG.distanceForAction) { console.log(`%cATTACK DAMAGE ${enemy.heroe_bodies[0].name}`, _utils.LOG_MATRIX); isEnemiesClose = true; this.calcDamage(this, enemy); } } }); this.core.enemies.creeps.forEach(creep => { if (typeof creep.heroe_bodies === 'undefined') return; if (creep.heroe_bodies) { let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, creep.heroe_bodies[0].position); if (tt < this.core.RPG.distanceForAction) { console.log(`%cATTACK DAMAGE ${creep.heroe_bodies[0].name}`, _utils.LOG_MATRIX); isEnemiesCreepClose = true; this.calcDamage(this, creep); } } }); if (isEnemiesCreepClose == false) this.setIdle(); return; } else { if (this.core.enemies.enemies.length > 0) this.core.enemies.enemies.forEach(enemy => { if (this.heroFocusAttackOn.name.indexOf(enemy.name) != -1) { let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, this.heroFocusAttackOn.position); if (tt < this.core.RPG.distanceForAction) { isEnemiesClose = true; console.log(`%cATTACK DAMAGE [lhero on enemy hero] ${enemy.heroe_bodies[0].name}`, _utils.LOG_MATRIX); this.calcDamage(this, enemy); return; } } }); if (this.core.enemies.creeps.length > 0) this.core.enemies.creeps.forEach(creep => { if (this.heroFocusAttackOn.name.indexOf(creep.name) != -1) { let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, this.heroFocusAttackOn.position); if (tt < this.core.RPG.distanceForAction) { isEnemiesCreepClose = true; console.log(`%cATTACK DAMAGE [lhero on creep] ${creep.heroe_bodies[0].name}`, _utils.LOG_MATRIX); this.calcDamage(this, creep); return; } } }); let enemytron = app.RPG.distance3D(this.heroe_bodies[0].position, app.enemytron.position); if (enemytron < app.RPG.distanceForAction) { console.log(`%c HERRO ATTACK ENEMY TRON`, _utils.LOG_MATRIX); isEnemiesClose = true; this.calcDamage(this, app.enemytron); return; } } }); // This is common for all kineamtic bodies addEventListener('onTargetPositionReach', e => { if (e.detail.name.indexOf('friendly-creep') != -1) { let getName = e.detail.name.split('_')[0]; let t = app.localHero.friendlyLocal.creeps.filter(obj => obj.name == getName); if (t[0].creepFocusAttackOn != null) { // console.log(`%[character base]`) return; } let testz = e.detail.body.position.z - t[0].firstPoint[2]; let testx = e.detail.body.position.x - t[0].firstPoint[0]; if (testz > 15 && testx > 15) { // got to first point t[0] for now only one sub mesh per creep... const start = [t[0].heroe_bodies[0].position.x, t[0].heroe_bodies[0].position.y, t[0].heroe_bodies[0].position.z]; const path = this.core.RPG.nav.findPath(start, t[0].firstPoint); if (!path || path.length === 0) { console.warn('No valid path found.'); return; } // getName[getName.length-1] becouse for now creekps have sum < 10 console.log('followPath creep to the FIRST POINT....'); setTimeout(() => { this.setWalkCreep(getName[getName.length - 1]); (0, _navMesh.followPath)(t[0].heroe_bodies[0], path, app); }, 1000); } else { // goto final // console.log('SEND TO last POINT POINT to the enemy home....', t[0].finalPoint) const start = [t[0].heroe_bodies[0].position.x, t[0].heroe_bodies[0].position.y, t[0].heroe_bodies[0].position.z]; const path = this.core.RPG.nav.findPath(start, t[0].finalPoint); if (!path || path.length === 0) { console.warn('No valid path found.'); return; } // getName[getName.length-1] becouse for now creekps have sum < 10 // at the end finalPoint will be point of enemy base! setTimeout(() => { (0, _navMesh.followPath)(t[0].heroe_bodies[0], path, app); this.setWalkCreep(getName[getName.length - 1]); }, 1000); } return; } // for now only local hero if (this.heroFocusAttackOn == null) { let isEnemiesClose = false; this.core.enemies.enemies.forEach(enemy => { if (typeof enemy.heroe_bodies === 'undefined') return; let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, enemy.heroe_bodies[0].position); if (tt < this.core.RPG.distanceForAction) { console.log(`%c ATTACK DAMAGE ${enemy.heroe_bodies[0].name}`, _utils.LOG_MATRIX); isEnemiesClose = true; this.calcDamage(this, enemy); } }); if (isEnemiesClose == false) this.setIdle(); } }); addEventListener('onMouseTarget', e => { if (this.core.RPG.selected.includes(this.heroe_bodies[0])) { // console.log("onMouseTarget POS:", e.detail.type); this.mouseTarget.position.setPosition(e.detail.x, this.mouseTarget.position.y, e.detail.z); if (e.detail.type == "attach") { this.mouseTarget.effects.circlePlaneTex.instanceTargets[0].color = [1, 0, 0, 0.9]; } else { this.mouseTarget.effects.circlePlaneTex.instanceTargets[0].color = [0.6, 0.8, 1, 0.4]; } } }); addEventListener('navigate-friendly_creeps', e => { if (app.net.virtualEmiter != null) { if (e.detail.localCreepNav) { console.log(`%c navigate creep ${e.detail.localCreepNav} index : ${e.detail.index}`, _utils.LOG_MATRIX); this.navigateCreep(e.detail.localCreepNav, e.detail.index); } else { this.navigateCreeps(); } } }); addEventListener('updateLocalHeroGold', e => { this.gold += e.detail.gold; }); } } exports.Character = Character; },{"../../../src/engine/loaders/webgpu-gltf":54,"../../../src/engine/utils":61,"./creep-character":3,"./friendly-character":7,"./hero":8,"./nav-mesh":13,"./static":15,"wgpu-matrix":32}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Controller = void 0; var _raycast = require("../../../src/engine/raycast.js"); var _wgpuMatrix = require("wgpu-matrix"); var _utils = require("../../../src/engine/utils.js"); var _navMesh = require("./nav-mesh.js"); class Controller { ignoreList = ['ground', 'mouseTarget_Circle']; selected = []; nav = null; // ONLY LOCAL heroe_bodies = null; distanceForAction = 36; constructor(core) { this.core = core; this.canvas = this.core.canvas; this.dragStart = null; this.dragEnd = null; this.selecting = false; this.canvas.addEventListener('mousedown', e => { if (e.button === 2) { // right m this.selecting = true; this.dragStart = { x: e.clientX, y: e.clientY }; this.dragEnd = { x: e.clientX, y: e.clientY }; } // else if(e.button === 0) { } }); this.canvas.addEventListener('mousemove', e => { if (this.selecting) { this.dragEnd = { x: e.clientX, y: e.clientY }; } }); this.canvas.addEventListener('mouseup', e => { if (this.selecting) { this.selecting = false; this.selectCharactersInRect(this.dragStart, this.dragEnd); this.dragStart = this.dragEnd = null; setTimeout(() => { if (this.ctx) this.ctx.clearRect(0, 0, this.overlay.width, this.overlay.height); }, 100); } }); (0, _raycast.addRaycastsListener)(undefined, 'click'); this.canvas.addEventListener("ray.hit.event", e => { // console.log('ray.hit.event detected', e); const { hitObject, hitPoint, button, eventName } = e.detail; if (e.detail.hitObject.name == 'ground') { dispatchEvent(new CustomEvent(`onMouseTarget`, { detail: { type: 'normal', x: hitPoint[0], y: hitPoint[1], z: hitPoint[2] } })); this.core.localHero.heroFocusAttackOn = null; // return; } else if (this.core.enemies && this.core.enemies.isEnemy(e.detail.hitObject.name)) { dispatchEvent(new CustomEvent(`onMouseTarget`, { detail: { type: 'attach', x: e.detail.hitObject.position.x, // hitPoint[0], blocked by colision observer y: e.detail.hitObject.position.y, z: e.detail.hitObject.position.z } })); } else { // for now if (app.net.virtualEmiter != null) { console.log("only emiter - navigate friendly_creeps creep from controller :", e.detail.hitObject.name); dispatchEvent(new CustomEvent('navigate-friendly_creeps', { detail: 'test' })); } // must be friendly objs return; } if (button == 0 && e.detail.hitObject.name != 'ground' && e.detail.hitObject.name !== this.heroe_bodies[0].name) { if (this.heroe_bodies.length == 2) { if (e.detail.hitObject.name == this.heroe_bodies[1].name) { console.log("Hit object SELF SLICKED :", e.detail.hitObject.name); return; } } const LH = this.core.localHero.heroe_bodies[0]; console.log("Hit object VS LH DISTANCE : ", this.distance3D(LH.position, e.detail.hitObject.position)); // after all check is it eneimy this.core.localHero.heroFocusAttackOn = e.detail.hitObject; let testDistance = this.distance3D(LH.position, e.detail.hitObject.position); // 37 LIMIT FOR ATTACH if (testDistance < this.distanceForAction) { console.log("this.core.localHero.setAttack [e.detail.hitObject]"); this.core.localHero.setAttack(e.detail.hitObject); return; } } // Only react to LEFT CLICK if (button !== 0 || this.heroe_bodies === null || !this.selected.includes(this.heroe_bodies[0])) { console.log(" no local here "); // not hero but maybe other creaps . based on selected.... return; } // Define start (hero position) and end (clicked point) const hero = this.heroe_bodies[0]; dispatchEvent(new CustomEvent('set-walk')); const start = [hero.position.x, hero.position.y, hero.position.z]; const end = [hitPoint[0], hitPoint[1], hitPoint[2]]; // app.net.send({ // heroName: app.localHero.name, // sceneName: hero.name, // followPath: {start: start, end: end}, // }) const path = this.nav.findPath(start, end); if (!path || path.length === 0) { console.warn('No valid path found.'); return; } // no need if position = position of root ??? test last bug track for (var x = 0; x < this.heroe_bodies.length; x++) { (0, _navMesh.followPath)(this.heroe_bodies[x], path, this.core); } // followPath(this.heroe_bodies[0], path, this.core); }); document.body.addEventListener("contextmenu", e => { e.preventDefault(); }); this.canvas.addEventListener("contextmenu", e => { e.preventDefault(); }); this.activateVisualRect(); let hiddenAt = null; if (location.hostname.indexOf('localhost') == -1) { console.log('Security stuff activated'); console.log = function () {}; // Security stuff if (window.innerHeight < window.outerHeight) { let test = window.outerHeight - window.innerHeight; // 87 person comp case -> addressbar ~~~ if (test > 100) { console.log('BAN', test); location.assign('https://maximumroulette.com'); } } if (window.innerWidth < window.outerWidth) { let testW = window.outerWidth - window.innerWidth; if (testW > 100) { console.log('BAN', testW); location.assign('https://maximumroulette.com'); } } window.addEventListener('keydown', e => { if (e.code == "F12") { e.preventDefault(); _utils.mb.error(` You are interest in Forest Of Hollow Blood. See <a href='https://github.com/zlatnapirala'>Github Source</a> You can download for free project and test it into localhost. `); console.log(`%c[keydown opened] ${e}`, _utils.LOG_MATRIX); return false; } }); let onVisibilityChange = () => { if (document.visibilityState === "visible") { if (hiddenAt !== null) { const now = Date.now(); const hiddenDuration = (now - hiddenAt) / 1000; if (parseFloat(hiddenDuration.toFixed(2)) > 1) { console.log(`🟢⚠️ Tab was hidden for ${hiddenDuration.toFixed(2)} sec.`); document.title = document.title.replace('🟢', '🟡'); } hiddenAt = null; // reset } else { console.log("🟢 Tab is visible — first activation."); } } else { hiddenAt = Date.now(); } }; document.addEventListener("visibilitychange", onVisibilityChange); } } projectToScreen(worldPos, viewMatrix, projectionMatrix, canvas) { // Convert world position to clip space const world = [worldPos[0], worldPos[1], worldPos[2], 1.0]; // Multiply in correct order: clip = projection * view * world const viewProj = _wgpuMatrix.mat4.multiply(projectionMatrix, viewMatrix); const clip = _wgpuMatrix.vec4.transformMat4(world, viewProj); // Perform perspective divide const ndcX = clip[0] / clip[3]; const ndcY = clip[1] / clip[3]; // Convert NDC (-1..1) to screen pixels const screenX = (ndcX * 0.5 + 0.5) * canvas.width; const screenY = (1 - (ndcY * 0.5 + 0.5)) * canvas.height; return { x: screenX, y: screenY }; } selectCharactersInRect(start, end) { const xMin = Math.min(start.x, end.x); const xMax = Math.max(start.x, end.x); const yMin = Math.min(start.y, end.y); const yMax = Math.max(start.y, end.y); // const camera = app.cameras.WASD; const camera = app.cameras.RPG; for (const object of app.mainRenderBundle) { if (!object.position) continue; const screen = this.projectToScreen([object.position.x, object.position.y, object.position.z, 1.0], camera.view, camera.projectionMatrix, this.canvas); if (screen.x >= xMin && screen.x <= xMax && screen.y >= yMin && screen.y <= yMax) { if (this.ignoreList.some(str => object.name.includes(str))) continue; if (this.selected.includes(object)) continue; object.setSelectedEffect(true); this.selected.push(object); (0, _utils.byId)('hud-menu').dispatchEvent(new CustomEvent("onSelectCharacter", { detail: object.name })); } else { if (this.selected.indexOf(object) !== -1) { this.selected.splice(this.selected.indexOf(object), 1); // byId('hud-menu').dispatchEvent(new CustomEvent("onSelectCharacter", {detail: object.name} )) } object.setSelectedEffect(false); } } console.log("Selected:", this.selected.map(o => o.name)); } activateVisualRect() { const overlay = document.createElement("canvas"); overlay.width = this.canvas.width; overlay.height = this.canvas.height; overlay.style.position = "absolute"; overlay.style.left = this.canvas.offsetLeft + "px"; overlay.style.top = this.canvas.offsetTop + "px"; this.canvas.parentNode.appendChild(overlay); this.ctx = overlay.getContext("2d"); overlay.style.pointerEvents = "none"; this.overlay = overlay; this.canvas.addEventListener("mousemove", e => { if (this.selecting) { this.dragEnd = { x: e.clientX, y: e.clientY }; this.ctx.clearRect(0, 0, overlay.width, overlay.height); this.ctx.strokeStyle = "rgba(0,255,0,0.8)"; this.ctx.lineWidth = 2.5; this.ctx.strokeRect(this.dragStart.x, this.dragStart.y, this.dragEnd.x - this.dragStart.x, this.dragEnd.y - this.dragStart.y); } }); } distance3D(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; const dz = a.z - b.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } } exports.Controller = Controller; },{"../../../src/engine/raycast.js":60,"../../../src/engine/utils.js":61,"./nav-mesh.js":13,"wgpu-matrix":32}],3:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Creep = void 0; var _webgpuGltf = require("../../../src/engine/loaders/webgpu-gltf"); var _utils = require("../../../src/engine/utils"); var _hero = require("./hero"); var _static = require("./static"); class Creep extends _hero.Hero { heroAnimationArrange = { dead: null, walk: null, salute: null, attack: null, idle: null }; creepFocusAttackOn = null; constructor(o, archetypes = ["creep"], group = "enemy", team) { super(o.name, archetypes); this.name = o.name; this.core = o.core; this.group = group; this.team = team; this.loadCreep(o); return this; } loadCreep = async o => { this.o = o; try { var glbFile01 = await fetch(o.path).then(res => res.arrayBuffer().then(buf => (0, _webgpuGltf.uploadGLBModel)(buf, this.core.device))); this.core.addGlbObjInctance({ material: { type: 'standard', useTextureFromGlb: true }, scale: [20, 20, 20], position: o.position, name: o.name, texturesPaths: ['./res/meshes/glb/textures/mutant_origin.png'], raycast: { enabled: true, radius: 1.1 }, pointerEffect: { enabled: true, energyBar: true } }, null, glbFile01); // make small async - cooking glbs files this.asyncHelper(this.o).then(() => { console.log('good'); }).catch(() => { console.log('catch'); setTimeout(() => { this.asyncHelper(this.o); }, 3000); }); } catch (err) { throw err; } }; asyncHelper = async o => { return new Promise((resolve, reject) => { setTimeout(() => { this.heroe_bodies = app.mainRenderBundle.filter(obj => obj.name && obj.name.includes(o.name)); if (this.heroe_bodies.length == 0 || this.core.net.session == null) { reject(); return; } this.heroe_bodies.forEach((subMesh, idx) => { subMesh.position.thrust = this.moveSpeed; subMesh.glb.animationIndex = 0; // adapt manual if blender is not setup subMesh.glb.glbJsonData.animations.forEach((a, index) => { // console.info(`%c Animation loading for creeps: ${a.name} index ${index}`, LOG_MATRIX) if (a.name == 'dead') this.heroAnimationArrange.dead = index; if (a.name == 'walk') this.heroAnimationArrange.walk = index; if (a.name == 'salute') this.heroAnimationArrange.salute = index; if (a.name == 'attack') this.heroAnimationArrange.attack = index; if (a.name == 'idle') this.heroAnimationArrange.idle = index; }); // adapt subMesh.globalAmbient = [1, 1, 1, 1]; if (this.name.indexOf('friendly_creeps') != -1) { subMesh.globalAmbient = [12, 12, 12, 1]; } else if (this.name.indexOf('enemy_creep') != -1) { subMesh.globalAmbient = [12, 1, 1, 1]; } if (this.group == 'friendly' && this.name.indexOf('friendly_creeps') != -1) { if (idx == 0) { if (this.core.net.virtualEmiter == this.core.net.session.connection.connectionId) { subMesh.position.teams[0] = app.player.remoteByTeam[app.player.data.team]; subMesh.position.teams[1] = app.player.remoteByTeam[app.player.data.enemyTeam]; subMesh.position.netObject = subMesh.name; let t = subMesh.name.replace('friendly_creeps', 'enemy_creep'); subMesh.position.remoteName = t; subMesh.rotation.teams[0] = app.player.remoteByTeam[app.player.data.team]; subMesh.rotation.teams[1] = app.player.remoteByTeam[app.player.data.enemyTeam]; subMesh.rotation.emitY = subMesh.name; subMesh.rotation.remoteName = t; subMesh.sharedState.emitAnimationEvent = true; } } } if (idx == 0) this.core.collisionSystem.register(o.name, subMesh.position, 15.0, this.group); }); this.setStartUpPosition(); this.attachEvents(); resolve(); setTimeout(() => { if (this.core.net.virtualEmiter != null) { console.info(`%c virtualEmiter navigateCreeps : `, _utils.LOG_MATRIX); app.localHero.navigateCreeps(); } }, 3000); }, 9000); }); }; setWalk() { this.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.walk; console.info(`%chero walk`, _utils.LOG_MATRIX); }); } setSalute() { this.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.salute; console.info(`%chero salute`, _utils.LOG_MATRIX); }); } setDead() { this.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.dead; console.info(`%chero dead`, _utils.LOG_MATRIX); }); } setIdle() { this.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.idle; console.info(`%chero idle`, _utils.LOG_MATRIX); }); } setAttack() { this.heroe_bodies.forEach(subMesh => { subMesh.glb.animationIndex = this.heroAnimationArrange.attack; console.info(`%chero attack`, _utils.LOG_MATRIX); }); } setStartUpPosition() { if (this.group == 'enemy') { this.heroe_bodies.forEach((subMesh, idx) => { subMesh.position.setPosition(_static.startUpPositions[this.core.player.data.enemyTeam][0], _static.startUpPositions[this.core.player.data.enemyTeam][1], _static.startUpPositions[this.core.player.data.enemyTeam][2]); }); } else { this.heroe_bodies.forEach((subMesh, idx) => { subMesh.position.setPosition(_static.startUpPositions[this.core.player.data.team][0], _static.startUpPositions[this.core.player.data.team][1], _static.startUpPositions[this.core.player.data.team][2]); }); } } setStartUpPosCreep() { if (this.group == 'enemy') { this.heroe_bodies.forEach((subMesh, idx) => { subMesh.position.setPosition(_static.startUpPositions[this.core.player.data.enemyTeam][0], _static.startUpPositions[this.core.player.data.enemyTeam][1], _static.startUpPositions[this.core.player.data.enemyTeam][2]); }); } else { this.heroe_bodies.forEach((subMesh, idx) => { subMesh.position.setPosition(_static.startUpPositions[this.core.player.data.team][0], _static.startUpPositions[this.core.player.data.team][1], _static.startUpPositions[this.core.player.data.team][2]); }); } } attachEvents() { addEventListener(`onDamage-${this.name}`, e => { if (this.group == 'enemy') { console.info(`%c onDamage-${this.name} group: ${this.group} creep damage!`, _utils.LOG_FUNNY); } else { console.log('friendly creep damage must come from net. [never]'); return; } this.heroe_bodies[0].effects.energyBar.setProgress(e.detail.progress); this.core.net.sendOnlyData({ type: "damage-creep", defenderName: e.detail.defender, defenderTeam: this.team, hp: e.detail.hp, progress: e.detail.progress }); if (e.detail.progress == 0) { this.setDead(); console.info(`%c Creep dead [${this.name}], attacker[${e.detail.attacker}]`, _utils.LOG_MATRIX); setTimeout(() => { this.setStartUpPosCreep(this.name[this.name.length - 1]); this.setWalk(); this.creepFocusAttackOn = null; this.gotoFinal = false; this.setStartUpPosCreep(this.name[this.name.length - 1]); this.hp = 300; this.heroe_bodies[0].effects.energyBar.setProgress(1); app.localHero.setWalkCreep(this.name[this.name.length - 1]); dispatchEvent(new CustomEvent('navigate-friendly_creeps', { detail: 'test' })); }, 700); } }); if (this.group != 'enemy') { addEventListener(`animationEnd-${this.heroe_bodies[0].name}`, e => { // CHECK DISTANCE if (e.detail.animationName != 'attack') { // && this.creepFocusAttackOn == null) { return; } if (this.group == "friendly") { if (this.creepFocusAttackOn == null) { // console.info('setIdle:', e.detail.animationName) let isEnemiesClose = false; this.core.enemies.enemies.forEach(enemy => { if (typeof enemy.heroe_bodies === 'undefined') return; let tt = this.core.RPG.distance3D(this.heroe_bodies[0].position, enemy.heroe_bodies[0]