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
JavaScript
(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]