Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const directions = ["back", "br", "fr", "front", "right"];
- const anims = ["stand", "walk", "attack"];
- const frames = { "walk": 8, "attack": 15 };
- const TEXTURE_SIZE = 2048;
- const DIFFERENCES = ["front", "fs_right", "right", "bs_right", "back", "bs_left", "left", "fs_left"];
- const ANIM_FRAME_RATE = 150;
- const RESOLUTION = 'default';
- /**
- * Base class to represent an NPC or Character object.
- */
- class Actor {
- constructor() {
- }
- /**
- * Pops some text above this Actor's head.
- */
- say(message) {
- if (!this.text) {
- this.text = document.createElement('div');
- this.text.classList.add('outline');
- this.text.classList.add('message');
- document.body.appendChild(this.text);
- }
- this.text.style.display = 'block';
- this.text.innerHTML = message;
- this.lastMessage = message;
- setTimeout(() => {
- if (this.lastMessage == message) {
- this.text.style.display = 'none';
- }
- }, 3000)
- }
- /**
- * Pops a 'skill bubble' above the actor's head.
- */
- action(anim) {
- if (!this.doing) {
- this.doing = document.createElement('img');
- this.doing.classList.add('animation');
- document.body.appendChild(this.doing);
- }
- if (anim) {
- this.doing.style.display = 'block';
- let ii = itemImageURL(anim);
- if (ii) {
- this.doing.src = ii;
- } else {
- this.doing.src = getStaticEndpoint('items/' + anim + '.png');
- }
- } else {
- this.doing.style.display = 'none';
- }
- }
- /**
- * Shows an HP bar above the actor's head.
- */
- health(percent) {
- if (!this.hp_container) {
- this.hp_container = document.createElement('div');
- this.hp_container.classList.add('combat-hp-container');
- this.hp_bar = document.createElement('div');
- this.hp_bar.classList.add('combat-hp-bar');
- this.hp_container.appendChild(this.hp_bar);
- document.body.appendChild(this.hp_container);
- }
- this.hp_container.style.display = 'block';
- if (percent <= 1) {
- this.hp_bar.style.width = '0px';
- } else {
- this.hp_bar.style.width = percent + '%';
- }
- let time = new Date();
- this.hp_container.spawned = time;
- setTimeout(() => {
- if (this.hp_container.spawned == time) {
- this.hp_container.style.display = 'none';
- }
- }, 3500);
- }
- /**
- * Draws a damage splat on the player
- * {
- * type: "melee_hit",
- * damage: 3
- * }
- */
- damage(style, type, damage) {
- if (!this.damage_splat) {
- this.damage_splat = document.createElement('div');
- this.damage_splat.classList.add('combat-splat');
- this.damage_splat_text = document.createElement('div');
- this.damage_splat_text.classList.add('combat-splat-text');
- this.damage_splat.appendChild(this.damage_splat_text);
- document.body.appendChild(this.damage_splat);
- }
- let time = new Date();
- this.damage_splat.style.display = 'flex';
- let icon = style == 'ranged' ? 'arrow' : 'sword';
- if (type == 'dodge') {
- this.damage_splat.style.backgroundImage = "";
- } else if (damage > 0) {
- this.damage_splat.style.backgroundImage = "url(" + getStaticEndpoint('img/combat_' + icon + '_red.png') + ")";
- } else {
- this.damage_splat.style.backgroundImage = "url(" + getStaticEndpoint('img/combat_' + icon + '_blue.png') + ")";
- }
- this.damage_splat_text.innerText = type == 'dodge' ? 'evaded' : damage;
- this.damage_splat.spawned = time;
- if (type == 'dodge' || damage == 0) {
- AUDIO_SFX_PLAYER.play_location('combat-damage-miss', this.position());
- } else if (type != 'ranged') {
- AUDIO_SFX_PLAYER.play_location('combat-damage-hit-cloth', this.position());
- }
- setTimeout(() => {
- if (this.damage_splat.spawned == time) {
- this.damage_splat.style.display = 'none';
- }
- }, 500);
- }
- update(cam, dt) {
- if (this.text && this.text.style.display != 'none') {
- let p = this.position().clone();
- p.y += 0.75;
- this.projectWorldToCSS(this.text, p, cam);
- }
- if (this.doing && this.doing.style.display != 'none') {
- let p = this.position().clone();
- p.y += 1.05;
- this.projectWorldToCSS(this.doing, p, cam);
- }
- if (this.damage_splat && this.damage_splat.style.display != 'none') {
- let p = this.position().clone();
- this.projectWorldToCSS(this.damage_splat, p, cam);
- }
- if (this.hp_container && this.hp_container.style.display != 'none') {
- let p = this.position().clone();
- p.y += 0.8;
- this.projectWorldToCSS(this.hp_container, p, cam);
- }
- }
- projectWorldToCSS(dom, p, cam) {
- p.project(cam);
- let w = window.innerWidth, h = window.innerHeight;
- let w2 = w / 2.0, h2 = h / 2.0;
- let l = p.x * w2 + w2 - dom.offsetWidth / 2.0;
- let t = -(p.y * h2) + h2 - dom.offsetHeight / 2.0;
- dom.style.left = l + "px";
- dom.style.top = t + "px";
- }
- onDelete() {
- if (this.text) this.text.parentNode.removeChild(this.text);
- if (this.doing) this.doing.parentNode.removeChild(this.doing);
- if (this.damage_splat) this.damage_splat.parentNode.removeChild(this.damage_splat);
- if (this.hp_container) this.hp_container.parentNode.removeChild(this.hp_container);
- }
- }
- var spineBase = getStaticEndpoint("spine/");
- var spineAssetManager = new spine.threejs.AssetManager(spineBase);
- var meshFile = 'rig.json';
- var atlasFile = RESOLUTION + '.atlas';
- spineAssetManager.loadText(meshFile);
- spineAssetManager.loadText(atlasFile);
- let TIL = new THREE.ImageLoader();
- var spineTextureCache = {};
- function createAtlas(mods = {}, callback) {
- let text = spineAssetManager.get(atlasFile);
- let pending = 0;
- let atlas = new spine.TextureAtlas(text, (path) => {
- let actual = mods[path] || path;
- if (spineTextureCache[actual]) return spineTextureCache[actual];
- pending++;
- TIL.load(cacheURI(spineBase + actual), (image) => {
- pending--;
- spineTextureCache[actual] = new spine.threejs.ThreeJsTexture(image);
- if (pending == 0) {
- // recreate once all textures are ready.
- createAtlas(mods, callback);
- }
- });
- var fake = document.createElement("img");
- fake.width = 16;
- fake.height = 16;
- return new spine.FakeTexture(fake);
- });
- if (pending == 0) callback(new spine.AtlasAttachmentLoader(atlas));
- }
- function tintSkin(skin, r, g, b) {
- for (let i in skin.attachments) {
- for (let j in skin.attachments[i]) {
- skin.attachments[i][j].color.r = r;
- skin.attachments[i][j].color.g = g;
- skin.attachments[i][j].color.b = b;
- }
- }
- }
- const allSkins = JSON.parse('{"belt":true,"body":true,"cape":true,"face":true,"footwear":true,"fullbody":true,"gloves":true,"hair":true,"headgear":true,"necklace":true,"offhand":true,"pants":true,"scar":true,"shirt":true,"skirt":true,"weaponBack":true,"weaponFullSize":true,"weaponMainHand":true, "wings": true}');
- const slotToSkin = {
- chest: "shirt",
- legs: "pants",
- shield: "offhand",
- head: "headgear",
- }
- function rgbToGL(color) {
- color.r /= 255.0;
- color.g /= 255.0;
- color.b /= 255.0;
- return color;
- }
- function createMods(model) {
- let skinMods = Object.assign({}, model.model);
- // TODO: This is pretty hacky.
- for (let i in model.items) {
- let slot = slotToSkin[i] || i;
- if (model.items[i].gfxslot) slot = model.items[i].gfxslot;
- skinMods[slot] = {
- override: model.items[i].style + ".png",
- }
- if (model.items[i].color) skinMods[slot].tint = rgbToGL(model.items[i].color);
- }
- let textureMods = {};
- for (let i in allSkins) {
- if (skinMods[i] && skinMods[i].override) {
- textureMods[RESOLUTION + '/' + i + '/' + i + '.png'] = RESOLUTION + '/' + i + '/' + skinMods[i].override;
- }
- }
- if (model.items && model.items.suppress_hair) {
- delete skinMods['hair'];
- }
- return [skinMods, textureMods];
- }
- function createSpineMesh(def, callback) {
- let [model, mods] = createMods(def);
- let scale = 0.045;
- createAtlas(mods, (atlas) => {
- let skeletonJson = new spine.SkeletonJson(atlas);
- skeletonJson.scale = scale;
- let data = spineAssetManager.get(meshFile);
- let skeletonData = skeletonJson.readSkeletonData(data);
- let skeletonMesh = new spine.threejs.SkeletonMesh(skeletonData);
- skeletonMesh.translateY(-0.9);
- skeletonMesh.scale.set(scale, scale, scale);
- skeletonMesh.zOffset = 0.01;
- let skin = new spine.Skin('test');
- for (let i in allSkins) {
- if (!model[i]) continue;
- let s = skeletonData.findSkin(i);
- if (model[i].tint) {
- tintSkin(s, model[i].tint.r, model[i].tint.g, model[i].tint.b);
- }
- skin.addSkin(s);
- }
- skeletonMesh.state.setAnimation(0, 'stand/stand_front', true);
- skeletonMesh.skeleton.setSkin(skin);
- skeletonMesh.skeleton.setSlotsToSetupPose();
- callback(skeletonMesh);
- });
- }
- function createShadow(height) {
- let sGeo = new THREE.PlaneGeometry(1, 1, 1, 1);
- let sMat = new THREE.MeshBasicMaterial({
- map: TEXTUREMANAGER.get('shadow.png'),
- transparent: true,
- side: THREE.DoubleSide,
- depthTest: false
- });
- let shadow = new THREE.Mesh(sGeo, sMat);
- shadow.is_shadow_mesh = true;
- shadow.rotateX(THREE.Math.degToRad(-90));
- shadow.position.set(0, -height + 0.01, 0);
- shadow.renderOrder = -10;
- return shadow;
- }
- /**
- * Adds the playerkit-specific code to the world actor
- */
- class HumanCharacter extends Actor {
- constructor(position, model, height) {
- super();
- this.animation = 'stand';
- this.direction = 0;
- this.facing = 'front';
- let group = new THREE.Group();
- group.position.set(position.x, position.y, position.z);
- group.add(createShadow(model.shadow_height || height));
- if (typeof model.scale === 'number') {
- group.scale.set(model.scale, model.scale, model.scale);
- } else if (model.scale && model.scale.x) {
- group.scale.set(model.scale.x, model.scale.y, model.scale.z);
- }
- this.threeObject = group;
- this.updateAppearance(model);
- }
- updateAppearance(model) {
- // TODO: better default appearance implementation
- if (!model.model) {
- console.log("Error: No model for character: " + JSON.stringify(model));
- model.model = JSON.parse('{"body":{"override":"male.png","tint":{"r":0.55,"g":0.33,"b":0.14}},"face":{"override":"male.png"},"footwear":{"override":"black_shoes.png"},"hair":{"override":"male_cropped.png","tint":{"r":0.84,"g":0.77,"b":0.76}},"pants":{"override":"straight-grey.png"},"shirt":{"override":"male_belt-red.png","tint":{"r":0.91,"g":0.93,"b":0.1}}}');
- }
- this.model = model;
- createSpineMesh(model, (mesh) => {
- this.replaceSpineMesh(mesh);
- });
- }
- replaceSpineMesh(newMesh) {
- if (this.spineMesh) this.threeObject.remove(this.spineMesh);
- this.threeObject.add(newMesh);
- this.spineMesh = newMesh;
- this.needsUpdate = true;
- }
- getThreeObject() {
- return this.threeObject;
- }
- position() {
- if (this.combat) {
- let vec = new THREE.Vector3();
- this.threeObject.getWorldPosition(vec);
- return vec;
- } else {
- return this.threeObject.position;
- }
- }
- setAnimation(animation, offset) {
- if (this.animation == animation) { return; }
- this.animation = animation;
- /*if (this.animation == 'attack') {
- this.timeScale = 0.5;
- }*/
- // TODO: hack
- if (offset == 'half') {
- this.trackTime = 0.15;
- }
- this.needsUpdate = true;
- }
- setDirection(direction) {
- this.direction = direction;
- this.needsUpdate = true;
- }
- updateSpineAnimation() {
- if (!this.spineMesh) return;
- let prefix = this.animation + "/" + this.animation + "_";
- let direction = this.facing;
- if (this.animation == 'attack' && direction != 'right' && direction != 'left') {
- direction = 'right';
- }
- let te = this.spineMesh.state.setAnimation(0, prefix + direction, true);
- if (this.trackTime) {
- te.trackTime = this.trackTime;
- delete this.trackTime;
- }
- if (this.timeScale) {
- te.timeScale = this.timeScale;
- delete this.timeScale;
- }
- }
- intersect(ray) {
- if (!this.spineMesh) return;
- let i = ray.intersectObject(this.spineMesh, true);
- if (i.length == 0) return;
- return i[0];
- }
- update(cam, dt) {
- if (this.combat) {
- } else {
- let camDir = 180 * Math.atan2(cam.position.z - this.threeObject.position.z, cam.position.x - this.threeObject.position.x) / Math.PI;
- let playerDir = this.direction;
- let diff = camDir - playerDir;
- diff = Math.round(diff / 45) + 4;
- diff += 4;
- while (diff >= 8) diff -= 8;
- while (diff < 0) diff += 8;
- let cameraAdjustedDirection = DIFFERENCES[diff];
- // change sprite to the direction
- if (cameraAdjustedDirection && this.facing != cameraAdjustedDirection) {
- this.facing = cameraAdjustedDirection;
- this.needsUpdate = true;
- }
- var forward = new THREE.Vector3();
- cam.getWorldDirection(forward);
- this.threeObject.lookAt(this.threeObject.position.x - forward.x, this.threeObject.position.y, this.threeObject.position.z - forward.z);
- }
- if (this.needsUpdate) {
- delete this.needsUpdate;
- this.updateSpineAnimation();
- if (this.spineMesh && this.animation == 'stand') {
- this.spineMesh.update(0.0);
- }
- //this.threeObject.traverse( (e) => e.renderOrder = 25);
- }
- super.update(cam, dt);
- // Skeletal animation: It's slow.
- if (this.spineMesh && this.animation != 'stand') {
- this.spineMesh.update(dt / 2000.0);
- }
- }
- }
- const MONSTER_INDICES = {
- "back_stand.png": 0, "back_walk0.png": 1, "back_walk1.png": 2, "back_walk2.png": 3,
- "bs_right_stand.png": 4, "bs_right_walk0.png": 5, "bs_right_walk1.png": 6, "bs_right_walk2.png": 7,
- "fs_right_stand.png": 8, "fs_right_walk0.png": 9, "fs_right_walk1.png": 10, "fs_right_walk2.png": 11,
- "front_stand.png": 12, "front_walk0.png": 13, "front_walk1.png": 14, "front_walk2.png": 15,
- "right_stand.png": 16, "right_walk0.png": 17, "right_walk1.png": 18, "right_walk2.png": 19, "right_attack0.png": 20, "right_attack1.png": 21, "right_attack2.png": 22, "right_attack3.png": 23, "right_hurt.png": 24
- };
- const MONSTER_COLUMNS = 5;
- class MonsterCharacter extends Actor {
- constructor(def, position, height) {
- super();
- this.animation = 'stand';
- this.facing = 'front';
- this.model = def.model;
- this.group = new THREE.Group();
- this.group.position.set(position.x, position.y, position.z);
- this.layerObjects = {};
- this.direction = 0;
- let geometry = new THREE.BufferGeometry();
- geometry.addAttribute('position', DEFAULT_VERTEX_BUFFER_ATTRIBUTE);
- geometry.addAttribute('uv', UV_ATTRIBUTES);
- geometry.setIndex(INDEX_ATTRIBUTES);
- let material = new THREE.MeshBasicMaterial(
- {
- transparent: true,
- alphaTest: 0.1,
- });
- let o = new THREE.Mesh(geometry, material);
- o.matrixAutoUpdate = false;
- o.layerSize = 'default';
- o.visible = false;
- this.group.add(o);
- this.group.add(createShadow(def.shadow_height || height));
- this.layerObjects[0] = o;
- this.dt = 0.0;
- this.frameRate = 250;
- this.geometry = this.layerObjects[0].geometry;
- this.material = this.layerObjects[0].material;
- if (def.scale) {
- this.group.scale.set(def.scale, def.scale, def.scale);
- }
- if (def.exact_collision) {
- this.exact_collision = true;
- }
- this.layerObjects[0].visible = false;
- this.loaded = false;
- TEXTUREMANAGER.getTextureAndImage(getStaticEndpoint('monsters/' + def.model + ".png"), (result) => {
- this.setTextureAndImage(result[0], result[1]);
- });
- }
- setTextureAndImage(texture, image) {
- this.texture = texture;
- this.image = image;
- this.material.map = this.texture;
- this.material.needsUpdate = true;
- this.layerObjects[0].visible = true;
- this._set(this.facing || 'front', this.animation || 'stand', this.frame || 0);
- }
- getThreeObject() {
- return this.group;
- }
- position() {
- if (this.combat) {
- let vec = new THREE.Vector3();
- this.group.getWorldPosition(vec);
- return vec;
- } else {
- return this.group.position;
- }
- }
- setAnimation(animation, offset) {
- if (this.animation == animation) { return; }
- this.frame = (offset == 'half') ? 7 : 1;
- this.animation = animation;
- this.needsUpdate = true;
- }
- setDirection(direction) {
- this.direction = direction;
- this.needsUpdate = true;
- }
- // boolean
- intersect(ray) {
- let i = ray.intersectObjects(this.group.children);
- if (i.length == 0) return;
- if (this.exact_collision) {
- let uv = i[0].uv;
- let x = Math.floor(uv.x * this.image[0].width);
- let y = Math.floor((1.0 - uv.y) * this.image[0].height);
- if (this.image[1].getImageData(x, y, 1, 1).data[3] > 0) return i[0];
- return false;
- } else {
- return i[0];
- }
- }
- incrementFrame() {
- if (this.animation == 'walk') {
- this.frame++;
- if (this.frame > 2) this.frame = 0;
- } else if (this.animation == 'attack') {
- this.frame++;
- if (this.frame > 3) this.frame = 0;
- }
- this.needsUpdate = true;
- }
- _set(direction, anim, frame) {
- if (!direction || !anim) return;
- // attack only exists in side-view
- if (anim == 'attack' && direction != 'right' && direction != 'left') { direction = 'right'; }
- // mirroring
- let invertX = false;
- if (direction == 'left') {
- direction = 'right';
- invertX = true;
- } else if (direction == 'fs_left') {
- direction = 'fs_right';
- invertX = true;
- } else if (direction == 'bs_left') {
- direction = 'bs_right';
- invertX = true;
- }
- let uri = direction + "_" + anim + (frames[anim] ? frame : "") + ".png";
- let newIndex = MONSTER_INDICES[uri] || 0;
- let x = (newIndex % MONSTER_COLUMNS) * 1.0 / MONSTER_COLUMNS;
- let y = Math.floor(newIndex / MONSTER_COLUMNS) * 1.0 / MONSTER_COLUMNS;
- let w = 0.2, h = 0.2;
- let left = x;
- let right = x;
- if (invertX) { left += w; } else { right += w; }
- let top = 1.0 - y;
- let bot = 1.0 - (y + h);
- let quad_uvs = [
- left, top,
- right, top,
- right, bot,
- left, bot
- ];
- var uvs = new Float32Array(quad_uvs);
- this.geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2);
- this.geometry.uvsNeedUpdate = true;
- }
- update(cam, dt) {
- if (this.combat) {
- // Do nothing.
- } else {
- // If I were better with math there's probably a one-liner in trig here...
- // TODO: Move this to shader.
- let camDir = 180 * Math.atan2(cam.position.z - this.group.position.z, cam.position.x - this.group.position.x) / Math.PI;
- let playerDir = this.direction;
- let diff = camDir - playerDir;
- diff = Math.round(diff / 45) + 4;
- diff += 4;
- while (diff >= 8) diff -= 8;
- while (diff < 0) diff += 8;
- let cameraAdjustedDirection = DIFFERENCES[diff];
- // change sprite to the direction
- if (cameraAdjustedDirection && this.facing != cameraAdjustedDirection) {
- this.facing = cameraAdjustedDirection;
- this.needsUpdate = true;
- }
- var forward = new THREE.Vector3();
- cam.getWorldDirection(forward);
- this.group.lookAt(this.group.position.x - forward.x, this.group.position.y, this.group.position.z - forward.z);
- }
- super.update(cam, dt);
- this.dt += dt;
- while (this.dt > this.frameRate) {
- this.dt -= this.frameRate;
- this.incrementFrame();
- }
- if (this.needsUpdate) {
- this._set(this.facing, this.animation, this.frame);
- this.needsUpdate = false;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement