diff --git a/common/damage/damaging.trait.js b/common/damage/damaging.trait.js new file mode 100644 index 0000000..6991adc --- /dev/null +++ b/common/damage/damaging.trait.js @@ -0,0 +1,65 @@ +import * as I from 'immutable'; + +import {Trait} from '@avocado/entity'; + +export class Damaging extends Trait { + + static defaultParams() { + return { + affinities: [], + lock: 0.1, + power: 0, + variance: 0.2, + }; + } + + initialize() { + this._affinities = this.params.get('affinities').toJS(); + this._collidingWith = []; + this._lock = this.params.get('lock'); + this._power = this.params.get('power'); + this._variance = this.params.get('variance'); + } + + get affinities() { + return this._affinities; + } + + get lock() { + return this._lock; + } + + get power() { + return this._power; + } + + get variance() { + return this._variance; + } + + listeners() { + return { + + collisionEnd: (other) => { + const index = this._collidingWith.indexOf(other); + this._collidingWith.splice(index, 1); + }, + + collisionStart: (other) => { + this._collidingWith.push(other); + }, + + }; + } + + tick(elapsed) { + for (let i = 0; i < this._collidingWith.length; ++i) { + const entity = this._collidingWith[i]; + if (entity.is('vulnerable')) { + entity.takeDamageFrom(this.entity); + } + } + } + +} + diff --git a/common/damage/emitter.js b/common/damage/emitter.js new file mode 100644 index 0000000..b4efbe3 --- /dev/null +++ b/common/damage/emitter.js @@ -0,0 +1,71 @@ +import {Proton, TextNode, TextNodeRenderer} from '@avocado/graphics'; + +export class DamageEmitter { + + constructor() { + const proton = new Proton(); + const emitter = new Proton.Emitter(); + proton.addEmitter(emitter); + this.emitter = emitter; + this.proton = proton; + } + + addRenderer(renderer) { + this.renderer = renderer; + this.proton.addRender(this.renderer); + } + + emit(position, amount) { + // BUGS + const pz = new Proton.PointZone(); + pz.x = position[0]; + pz.y = -position[1]; + const initializers = [ + new Proton.Body(new DamageTextNode(Math.abs(amount))), + new Proton.Position(pz), + new Proton.Mass(1), + new Proton.Life(2), + new Proton.Velocity( + new Proton.Span(80, 120), + new Proton.Vector3D(0, 5, 0), + 27.5 + ), + ]; + // Heh, bugs. + const rot = new Proton.Rotate(0, 0, 0); + rot.a = new Proton.Span(-.003, .003); + const behaviors = [ + new Proton.Alpha(1, .5), + new Proton.Color(amount < 0 ? 0x00ff00 : 0xff0000), + new Proton.Scale(.6, 1), + new Proton.Force(0, -1, 0), + rot, + ]; + this.emitter.createParticle(initializers, behaviors); + } + + tick(elapsed) { + this.proton.tick(elapsed); + } + +} + +export class DamageTextNode extends TextNode { + + constructor(damage) { + super(damage); + if (damage > 999) { + this.span.style.fontSize = '2em'; + } + else if (damage > 99) { + this.span.style.fontSize = '1.5em'; + } + else if (damage > 9) { + this.span.style.fontSize = '1.2em'; + } + else { + this.span.style.fontSize = '1em'; + } + } + +} diff --git a/common/damage/vulnerable.trait.js b/common/damage/vulnerable.trait.js new file mode 100644 index 0000000..567f0a2 --- /dev/null +++ b/common/damage/vulnerable.trait.js @@ -0,0 +1,108 @@ +import * as I from 'immutable'; + +import {Trait} from '@avocado/entity'; +import {hasGraphics, TextNodeRenderer} from '@avocado/graphics'; + +import {DamageEmitter} from './emitter'; + +export class Vulnerable extends Trait { + + static defaultState() { + return { + damageList: I.Map(), + }; + } + + initialize() { + this.damageList = {}; + this.locks = new Map(); + if (hasGraphics) { + this.emitter = new DamageEmitter(); + this.setRenderer(); + } + } + + patchStateStep(key, step) { + if ('state' !== key) { + return; + } + const stateKey = step.path.substr(1); + const value = this.transformPatchValue(stateKey, step.value); + const stateKeyParts = stateKey.split('/'); + switch (stateKeyParts[0]) { + case 'damageList': + for (let i = 0; i < value.length; ++i) { + const amount = value[i]; + this.emitter.emit(this.entity.position, amount); + } + break; + default: + super.patchStateStep(key, step); + break; + } + } + + setRenderer() { + if (this.entity.is('staged') && this.entity.stage) { + const renderer = new TextNodeRenderer('.damage', this.entity.stage); + this.emitter.addRenderer(renderer); + } + } + + listeners() { + return { + + stageChanged: () => { + this.setRenderer(); + }, + + }; + } + + methods() { + return { + + takeDamageFrom: (entity) => { + if (this.locks.has(entity)) { + return; + } + this.locks.set(entity, entity.lock); + const variance = Math.random() * entity.variance * 2 - entity.variance; + const difference = entity.power * variance; + const amount = (entity.power + difference) >> 0; + if (!this.damageList[entity.instanceUuid]) { + this.damageList[entity.instanceUuid] = []; + } + this.damageList[entity.instanceUuid].push(amount); + }, + + }; + } + + tick(elapsed) { + if (this.state.get('damageList').size > 0) { + this.state = this.state.set('damageList', I.Map()); + } + if (Object.keys(this.damageList).length > 0) { + this.state = this.state.set('damageList', I.Map(this.damageList)); + this.isDirty = true; + this.damageList = {}; + } + if (hasGraphics) { + this.emitter.tick(elapsed); + } + const keys = Array.from(this.locks.keys()); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const remaining = this.locks.get(key) - elapsed; + if (remaining <= 0) { + this.locks.delete(key); + } + else { + this.locks.set(key, remaining); + } + } + } + +} + diff --git a/resource/fire.png b/resource/fire.png new file mode 100644 index 0000000..fa12008 Binary files /dev/null and b/resource/fire.png differ diff --git a/server/create-entity-for-connection.js b/server/create-entity-for-connection.js index 1ad2bf4..d9878f7 100644 --- a/server/create-entity-for-connection.js +++ b/server/create-entity-for-connection.js @@ -47,6 +47,7 @@ export function createEntityForConnection(socket) { }, }, }, + vulnerable: {}, }, }); entity.addTrait('controllable'); diff --git a/server/create-server-room.js b/server/create-server-room.js index 062c8a0..8dc5b6b 100644 --- a/server/create-server-room.js +++ b/server/create-server-room.js @@ -1,5 +1,52 @@ import {World} from '@avocado/physics/matter/world'; import {Room} from '@avocado/topdown'; +// A fire. +function fireJSON(position) { + return { + traits: { + collider: { + params: { + isSensor: true, + }, + }, + damaging: { + params: { + power: 70, + affinities: ['fire'], + }, + }, + existent: {}, + visible: {}, + physical: {}, + pictured: { + params: { + images: { + initial: { + offset: [0, 0], + size: [16, 16], // Derive? + uri: '/fire.png', + }, + } + }, + }, + positioned: { + state: { + x: position[0], + y: position[1], + }, + }, + shaped: { + params: { + shape: { + type: 'rectangle', + position: [0, 0], + size: [16, 16], + }, + }, + }, + }, + }; +} // A flower barrel. function flowerBarrelJSON(position) { return { @@ -201,6 +248,7 @@ function kittyJSON(position) { }, }, }, + vulnerable: {}, }, }; } @@ -243,6 +291,7 @@ const roomJSON = { }, }, }; +roomJSON.layers.everything.entities.push(fireJSON([200, 200])); for (let i = 0; i < 4; ++i) { const x = Math.floor(Math.random() * 284) + 50; const y = Math.floor(Math.random() * 284) + 50; diff --git a/yarn.lock b/yarn.lock index d075198..b78496c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,8 +37,8 @@ lodash.without "4.4.0" "@avocado/graphics@1.x": - version "1.0.1" - resolved "https://npm.i12e.cha0s.io/@avocado%2fgraphics/-/graphics-1.0.1.tgz#ce799e134425d9c5d117aafafd35d5f52d2defda" + version "1.0.2" + resolved "https://npm.i12e.cha0s.io/@avocado%2fgraphics/-/graphics-1.0.2.tgz#a424f69b0f0c12476ff58229e92c5f6dabc15506" dependencies: "@avocado/core" "1.x" "@avocado/math" "1.x" @@ -47,6 +47,8 @@ debug "^3.1.0" immutable "4.0.0-rc.12" pixi.js "4.8.6" + three "0.103.0" + three.proton.js "0.1.5" "@avocado/input@1.x": version "1.0.1" @@ -3889,8 +3891,8 @@ resolve@^1.3.2: path-parse "^1.0.6" resource-loader@^2.2.3: - version "2.2.3" - resolved "https://npm.i12e.cha0s.io/resource-loader/-/resource-loader-2.2.3.tgz#2c8b075b7b9328a2e72fba6f51b4dff274870000" + version "2.2.4" + resolved "https://npm.i12e.cha0s.io/resource-loader/-/resource-loader-2.2.4.tgz#9bf43dba59475d56be29c796399211ce0e96fd2d" dependencies: mini-signals "^1.1.1" parse-uri "^1.0.0" @@ -4367,6 +4369,14 @@ terser@^3.16.1: source-map "~0.6.1" source-map-support "~0.5.10" +three.proton.js@0.1.5: + version "0.1.5" + resolved "https://npm.i12e.cha0s.io/three.proton.js/-/three.proton.js-0.1.5.tgz#3ae32fd9cf3b5698e3b546946e53badbda04948e" + +three@0.103.0: + version "0.103.0" + resolved "https://npm.i12e.cha0s.io/three/-/three-0.103.0.tgz#63b3dbccc861caad93269618061a73dadebae71b" + through2@^2.0.0: version "2.0.5" resolved "https://npm.i12e.cha0s.io/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"