feat: combat
This commit is contained in:
parent
22e5b3adee
commit
bd0367c8ae
|
@ -28,6 +28,7 @@
|
||||||
"@avocado/sound": "^1.0.0",
|
"@avocado/sound": "^1.0.0",
|
||||||
"@avocado/timing": "^2.0.0",
|
"@avocado/timing": "^2.0.0",
|
||||||
"@avocado/topdown": "^2.0.0",
|
"@avocado/topdown": "^2.0.0",
|
||||||
|
"@humus/combat": "^1.0.0",
|
||||||
"@humus/core": "^1.0.0",
|
"@humus/core": "^1.0.0",
|
||||||
"@humus/farm": "^1.0.0",
|
"@humus/farm": "^1.0.0",
|
||||||
"@humus/inventory": "^1.0.0",
|
"@humus/inventory": "^1.0.0",
|
||||||
|
|
|
@ -1088,6 +1088,19 @@
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.1"
|
scheduler "^0.20.1"
|
||||||
|
|
||||||
|
"@humus/combat@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "http://npm.cha0sdev/@humus%2fcombat/-/combat-1.0.0.tgz#e47dd94033f179c2d8f31f5fa8fa6661c3b28ca0"
|
||||||
|
integrity sha512-js0CNAyljtOdJbO6nZkDM626nSh+NP5ASa0WKqcEAYPRrpD/o/5Rh10acGG1N/4tGPBMdMST/dzKwUW90PW9KQ==
|
||||||
|
dependencies:
|
||||||
|
"@avocado/behavior" "^2.0.0"
|
||||||
|
"@avocado/math" "^2.0.0"
|
||||||
|
"@avocado/traits" "^2.0.0"
|
||||||
|
"@latus/core" "^2.0.0"
|
||||||
|
"@latus/socket" "^2.0.0"
|
||||||
|
debug "4.3.1"
|
||||||
|
lodash.flatten "^4.4.0"
|
||||||
|
|
||||||
"@humus/core@^1.0.0":
|
"@humus/core@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "http://npm.cha0sdev/@humus%2fcore/-/core-1.0.0.tgz#e7e2388c5510e5d54524d3805fb9858ca0aa27fa"
|
resolved "http://npm.cha0sdev/@humus%2fcore/-/core-1.0.0.tgz#e7e2388c5510e5d54524d3805fb9858ca0aa27fa"
|
||||||
|
|
1
packages/combat/.eslintrc.js
Normal file
1
packages/combat/.eslintrc.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('../../config/.eslintrc');
|
6
packages/combat/.gitignore
vendored
Normal file
6
packages/combat/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
**/*.js
|
||||||
|
**/*.map
|
||||||
|
!/.*
|
||||||
|
!/webpack.config.js
|
||||||
|
!src/**/*.js
|
||||||
|
!/test/**/*.js
|
1
packages/combat/.neutrinorc.js
Normal file
1
packages/combat/.neutrinorc.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('../../config/.neutrinorc');
|
47
packages/combat/package.json
Normal file
47
packages/combat/package.json
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "@humus/combat",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_PATH=./node_modules webpack --mode production",
|
||||||
|
"clean": "rm -rf yarn.lock node_modules && yarn",
|
||||||
|
"dev": "NODE_PATH=./node_modules webpack --mode development",
|
||||||
|
"forcepub": "npm unpublish --force $(node -e 'const {name, version} = require(`./package.json`); process.stdout.write(`${name}@${version}`)') && npm publish",
|
||||||
|
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
|
||||||
|
"test": "mocha --colors test.js",
|
||||||
|
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"index.js",
|
||||||
|
"index.js.map",
|
||||||
|
"test.js",
|
||||||
|
"test.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@avocado/behavior": "^2.0.0",
|
||||||
|
"@avocado/math": "^2.0.0",
|
||||||
|
"@avocado/traits": "^2.0.0",
|
||||||
|
"@latus/core": "^2.0.0",
|
||||||
|
"@latus/socket": "^2.0.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"lodash.flatten": "^4.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
"@neutrinojs/banner": "^9.4.0",
|
||||||
|
"@neutrinojs/copy": "^9.4.0",
|
||||||
|
"@neutrinojs/mocha": "^9.4.0",
|
||||||
|
"@neutrinojs/react": "^9.4.0",
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"eslint": "^7",
|
||||||
|
"eslint-import-resolver-webpack": "0.13.0",
|
||||||
|
"glob": "7.1.6",
|
||||||
|
"mocha": "^8",
|
||||||
|
"neutrino": "^9.4.0",
|
||||||
|
"source-map-support": "0.5.19",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3"
|
||||||
|
}
|
||||||
|
}
|
57
packages/combat/src/index.js
Normal file
57
packages/combat/src/index.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import {Class, gather, gatherWithLatus} from '@latus/core';
|
||||||
|
import flatten from 'lodash.flatten';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@avocado/traits': gatherWithLatus(
|
||||||
|
require.context('./traits', false, /\.js$/),
|
||||||
|
),
|
||||||
|
'@humus/combat/affinities': () => ({
|
||||||
|
Void: Class,
|
||||||
|
Bio: Class,
|
||||||
|
Stone: Class,
|
||||||
|
Wood: Class,
|
||||||
|
Metal: Class,
|
||||||
|
Air: Class,
|
||||||
|
Earth: Class,
|
||||||
|
Fire: Class,
|
||||||
|
Water: Class,
|
||||||
|
}),
|
||||||
|
'@humus/combat/interactions': () => {
|
||||||
|
const context = require.context('./interactions', false, /from-.*-to.*\.js$/);
|
||||||
|
return context.keys().map((path) => context(path).default);
|
||||||
|
},
|
||||||
|
'@latus/core/starting': (latus) => {
|
||||||
|
latus.set('%affinities', gather(
|
||||||
|
latus,
|
||||||
|
{
|
||||||
|
type: '@humus/combat/affinities',
|
||||||
|
},
|
||||||
|
));
|
||||||
|
const interactions = flatten(latus.invokeFlat('@humus/combat/interactions'))
|
||||||
|
.reduce(
|
||||||
|
(r, interaction) => {
|
||||||
|
const {harming, harmed} = interaction;
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
[harming]: {
|
||||||
|
...(r[harming] || {}),
|
||||||
|
[harmed]: [
|
||||||
|
...((r[harming] || {})[harmed] || []),
|
||||||
|
interaction,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
latus.set(
|
||||||
|
'%interactions',
|
||||||
|
(harmingAffinity, harmedAffinity) => interactions[harmingAffinity]?.[harmedAffinity] || [],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
'@latus/socket/packets': gatherWithLatus(
|
||||||
|
require.context('./packets', false, /\.js$/),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
57
packages/combat/src/interactions/blood.js
Normal file
57
packages/combat/src/interactions/blood.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
export default (color) => ({
|
||||||
|
rate: 0.025,
|
||||||
|
traits: {
|
||||||
|
Emitted: {
|
||||||
|
params: {
|
||||||
|
alpha: {
|
||||||
|
start: 1,
|
||||||
|
end: 0.4,
|
||||||
|
},
|
||||||
|
force: [0, 3],
|
||||||
|
velocity: {
|
||||||
|
angle: {
|
||||||
|
min: 338.5,
|
||||||
|
max: 382.5,
|
||||||
|
},
|
||||||
|
magnitude: {
|
||||||
|
min: 0.7,
|
||||||
|
max: 0.9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
start: 1,
|
||||||
|
end: 1.25,
|
||||||
|
},
|
||||||
|
transient: false,
|
||||||
|
ttl: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Existent: {},
|
||||||
|
Layered: {},
|
||||||
|
Listed: {},
|
||||||
|
Perishable: {
|
||||||
|
params: {
|
||||||
|
ttl: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Positioned: {},
|
||||||
|
Primitive: {
|
||||||
|
params: {
|
||||||
|
primitives: [
|
||||||
|
{
|
||||||
|
type: 'circle',
|
||||||
|
radius: 0.5,
|
||||||
|
line: {
|
||||||
|
rgba: color,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
rgba: color,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Roomed: {},
|
||||||
|
Visible: {},
|
||||||
|
},
|
||||||
|
});
|
25
packages/combat/src/interactions/from-stone-to-bio.js
Normal file
25
packages/combat/src/interactions/from-stone-to-bio.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
buildInvoke,
|
||||||
|
buildExpression,
|
||||||
|
} from '@avocado/behavior';
|
||||||
|
|
||||||
|
import blood from './blood';
|
||||||
|
import logParticle from './log-particle';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
harming: 'Stone',
|
||||||
|
harmed: 'Bio',
|
||||||
|
actions: {
|
||||||
|
client: {
|
||||||
|
type: 'expressions',
|
||||||
|
expressions: [
|
||||||
|
buildInvoke(['entity', 'emitParticleJson'], [
|
||||||
|
logParticle(blood([255, 0, 0]), 5),
|
||||||
|
]),
|
||||||
|
buildInvoke(['from', 'playSound'], [
|
||||||
|
buildExpression(['from', 'harmfulSound']),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
31
packages/combat/src/interactions/log-particle.js
Normal file
31
packages/combat/src/interactions/log-particle.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {
|
||||||
|
buildExpression,
|
||||||
|
buildInvoke,
|
||||||
|
} from '@avocado/behavior';
|
||||||
|
|
||||||
|
export default (json, count) => (
|
||||||
|
buildInvoke(
|
||||||
|
['Utility', 'merge'],
|
||||||
|
[
|
||||||
|
buildInvoke(
|
||||||
|
['Utility', 'makeObject'],
|
||||||
|
[
|
||||||
|
'count',
|
||||||
|
buildInvoke(
|
||||||
|
['Math', 'mul'],
|
||||||
|
[
|
||||||
|
buildInvoke(
|
||||||
|
['Math', 'log10'],
|
||||||
|
[
|
||||||
|
buildExpression(['harm', 'amount']),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
count,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
json,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
36
packages/combat/src/packets/harm.js
Normal file
36
packages/combat/src/packets/harm.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import {Packet} from '@latus/socket';
|
||||||
|
|
||||||
|
export default (latus) => class HarmPacket extends Packet {
|
||||||
|
|
||||||
|
static pack(harms) {
|
||||||
|
const fromType = latus.get('%affinities.fromType');
|
||||||
|
return harms.map((harm) => {
|
||||||
|
const {[harm.affinity]: Affinity} = fromType;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
harm.affinity = Affinity.id;
|
||||||
|
return harm;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
amount: 'varuint',
|
||||||
|
from: 'uint32',
|
||||||
|
isDamage: 'bool',
|
||||||
|
affinity: 'uint8',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static unpack(harms) {
|
||||||
|
const fromId = latus.get('%affinities.fromId');
|
||||||
|
return harms.map((harm) => {
|
||||||
|
const {[harm.affinity]: Affinity} = fromId;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
harm.affinity = Affinity.type;
|
||||||
|
return harm;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
191
packages/combat/src/traits/harmful.js
Normal file
191
packages/combat/src/traits/harmful.js
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import {Vector} from '@avocado/math';
|
||||||
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
|
import {compose} from '@latus/core';
|
||||||
|
|
||||||
|
const decorate = compose(
|
||||||
|
StateProperty('isHarmful'),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default () => class Harmful extends decorate(Trait) {
|
||||||
|
|
||||||
|
#harmSpecs = [];
|
||||||
|
|
||||||
|
static behaviorTypes() {
|
||||||
|
return {
|
||||||
|
harm: {
|
||||||
|
type: 'void',
|
||||||
|
label: 'Harm $1.',
|
||||||
|
args: [
|
||||||
|
['other', {
|
||||||
|
type: 'entity',
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultParams() {
|
||||||
|
return {
|
||||||
|
// TODO newtons
|
||||||
|
harmKnockback: 500,
|
||||||
|
harmLock: 0.1,
|
||||||
|
harmSpecs: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultState() {
|
||||||
|
return {
|
||||||
|
isHarmful: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeParams() {
|
||||||
|
return {
|
||||||
|
harmKnockback: {
|
||||||
|
type: 'number',
|
||||||
|
label: 'Knockback force',
|
||||||
|
},
|
||||||
|
harmLock: {
|
||||||
|
type: 'number',
|
||||||
|
label: 'Harm lockout in seconds',
|
||||||
|
},
|
||||||
|
harmSpecs: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Specs',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeState() {
|
||||||
|
return {
|
||||||
|
isHarmful: {
|
||||||
|
type: 'bool',
|
||||||
|
label: 'Is harmful',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
hooks() {
|
||||||
|
return {
|
||||||
|
particles: () => ({
|
||||||
|
harmful: {
|
||||||
|
traits: {
|
||||||
|
Emitted: {
|
||||||
|
params: {
|
||||||
|
alpha: {
|
||||||
|
start: 1,
|
||||||
|
end: 0.2,
|
||||||
|
},
|
||||||
|
rotation: {
|
||||||
|
start: 0,
|
||||||
|
add: {
|
||||||
|
min: -0.5,
|
||||||
|
max: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
start: 1,
|
||||||
|
end: 1.25,
|
||||||
|
},
|
||||||
|
ttl: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Existent: {},
|
||||||
|
Layered: {},
|
||||||
|
Listed: {},
|
||||||
|
Positioned: {},
|
||||||
|
Roomed: {},
|
||||||
|
Visible: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners() {
|
||||||
|
const listeners = {};
|
||||||
|
if ('client' !== process.env.SIDE) {
|
||||||
|
listeners.collisionStart = (other) => {
|
||||||
|
this.entity.harm(other);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(json) {
|
||||||
|
await super.load(json);
|
||||||
|
this.#harmSpecs = this.params.harmSpecs.map((harmSpec) => ({
|
||||||
|
power: 0,
|
||||||
|
affinity: 'Void',
|
||||||
|
variance: 0.2,
|
||||||
|
...harmSpec,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
methods() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
harm: (entity) => {
|
||||||
|
if (!this.entity.isHarmful) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!entity.is('Vulnerable')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!entity.isHarmedBy(this.entity)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!entity.ensureVulnerabilityLock(this.entity, this.params.harmLock)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.#harmSpecs.length; ++i) {
|
||||||
|
const {power, affinity, variance} = this.#harmSpecs[i];
|
||||||
|
let amount = Math.round(power + power * Math.random() * variance * 2 - variance);
|
||||||
|
if (power < 0) {
|
||||||
|
if (amount > 0) {
|
||||||
|
amount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (amount < 0) {
|
||||||
|
amount = 0;
|
||||||
|
}
|
||||||
|
const harm = {
|
||||||
|
amount: Math.abs(amount),
|
||||||
|
isDamage: power >= 0,
|
||||||
|
from: this.entity.instanceUuid,
|
||||||
|
affinity,
|
||||||
|
};
|
||||||
|
entity.emit('tookHarm', harm);
|
||||||
|
}
|
||||||
|
if (this.params.harmKnockback) {
|
||||||
|
if (
|
||||||
|
entity.is('Mobile')
|
||||||
|
&& entity.is('Positioned')
|
||||||
|
&& this.entity.is('Positioned')
|
||||||
|
) {
|
||||||
|
const unit = Vector.normalize(Vector.sub(
|
||||||
|
entity.position,
|
||||||
|
this.entity.position,
|
||||||
|
));
|
||||||
|
const knockback = Vector.scale(unit, this.params.harmKnockback);
|
||||||
|
entity.applyMovement(knockback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
if ('client' !== process.env.SIDE) {
|
||||||
|
if (this.entity.is('collider')) {
|
||||||
|
const {isCollidingWith} = this.entity;
|
||||||
|
for (let i = 0; i < isCollidingWith.length; i++) {
|
||||||
|
this.entity.harm(isCollidingWith[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
334
packages/combat/src/traits/vulnerable.js
Normal file
334
packages/combat/src/traits/vulnerable.js
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
import {
|
||||||
|
Actions,
|
||||||
|
compile,
|
||||||
|
Context,
|
||||||
|
} from '@avocado/behavior';
|
||||||
|
import {Trait} from '@avocado/traits';
|
||||||
|
import flatten from 'lodash.flatten';
|
||||||
|
|
||||||
|
export default (latus) => class Vulnerable extends Trait {
|
||||||
|
|
||||||
|
#harms = [];
|
||||||
|
|
||||||
|
#locks = new Map();
|
||||||
|
|
||||||
|
#isInvulnerable = false;
|
||||||
|
|
||||||
|
#isNotHarmedBy = [];
|
||||||
|
|
||||||
|
acceptHarm(harm) {
|
||||||
|
const {from, affinity} = harm;
|
||||||
|
const context = new Context(
|
||||||
|
{
|
||||||
|
entity: [this.entity, 'entity'],
|
||||||
|
from: [from, 'entity'],
|
||||||
|
harm: [harm, 'harm'],
|
||||||
|
},
|
||||||
|
latus,
|
||||||
|
);
|
||||||
|
this.constructor.interactions(this.entity, affinity, context, 'client');
|
||||||
|
this.entity.emit('acceptedHarm', harm);
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptPacket(packet) {
|
||||||
|
if ('Harm' === packet.constructor.type) {
|
||||||
|
for (let i = 0; i < packet.data.length; ++i) {
|
||||||
|
const harm = packet.data[i];
|
||||||
|
if (this.entity.is('Listed') && this.entity.list) {
|
||||||
|
harm.from = this.entity.list.findEntity(harm.from);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
harm.from = undefined;
|
||||||
|
}
|
||||||
|
this.acceptHarm(harm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanPackets() {
|
||||||
|
this.#harms = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultParams() {
|
||||||
|
return {
|
||||||
|
modifiers: undefined,
|
||||||
|
affinities: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeParams() {
|
||||||
|
return {
|
||||||
|
modifiers: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Modifiers',
|
||||||
|
},
|
||||||
|
affinities: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Types',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.#locks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static harmTextSize(amount) {
|
||||||
|
const biggest = 16;
|
||||||
|
const smallest = biggest / 2;
|
||||||
|
const step = biggest / 6;
|
||||||
|
if (amount > 999) {
|
||||||
|
return biggest;
|
||||||
|
}
|
||||||
|
if (amount > 99) {
|
||||||
|
return smallest + (step * 2);
|
||||||
|
}
|
||||||
|
if (amount > 9) {
|
||||||
|
return smallest + step;
|
||||||
|
}
|
||||||
|
return smallest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
hooks() {
|
||||||
|
return {
|
||||||
|
particles: () => ({
|
||||||
|
harm: {
|
||||||
|
traits: {
|
||||||
|
Darkened: {
|
||||||
|
params: {
|
||||||
|
isDarkened: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Emitted: {
|
||||||
|
params: {
|
||||||
|
alpha: {
|
||||||
|
start: 1,
|
||||||
|
end: 0,
|
||||||
|
},
|
||||||
|
force: [0, 1],
|
||||||
|
velocity: {
|
||||||
|
angle: {
|
||||||
|
min: 337.5,
|
||||||
|
max: 382.5,
|
||||||
|
},
|
||||||
|
magnitude: {
|
||||||
|
min: -1.25,
|
||||||
|
max: -0.75,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rotation: {
|
||||||
|
start: 0,
|
||||||
|
add: {
|
||||||
|
min: -0.5,
|
||||||
|
max: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
start: 1,
|
||||||
|
end: 1.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Existent: {},
|
||||||
|
Layered: {},
|
||||||
|
Listed: {},
|
||||||
|
Positioned: {},
|
||||||
|
Roomed: {},
|
||||||
|
Visible: {
|
||||||
|
state: {
|
||||||
|
zIndex: 65535,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
blood: {
|
||||||
|
traits: {
|
||||||
|
Emitted: {
|
||||||
|
params: {
|
||||||
|
alpha: {
|
||||||
|
start: 1,
|
||||||
|
end: 0.4,
|
||||||
|
},
|
||||||
|
force: [0, 3],
|
||||||
|
velocity: {
|
||||||
|
angle: {
|
||||||
|
min: 338.5,
|
||||||
|
max: 382.5,
|
||||||
|
},
|
||||||
|
magnitude: {
|
||||||
|
min: 0.7,
|
||||||
|
max: 0.9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
start: 1,
|
||||||
|
end: 1.25,
|
||||||
|
},
|
||||||
|
transient: false,
|
||||||
|
ttl: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Existent: {},
|
||||||
|
Layered: {},
|
||||||
|
Listed: {},
|
||||||
|
Perishable: {
|
||||||
|
params: {
|
||||||
|
ttl: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Positioned: {},
|
||||||
|
Primitive: {
|
||||||
|
params: {
|
||||||
|
primitives: [
|
||||||
|
{
|
||||||
|
type: 'circle',
|
||||||
|
radius: 0.5,
|
||||||
|
line: {
|
||||||
|
rgba: [255, 0, 0],
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
rgba: [255, 0, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Roomed: {},
|
||||||
|
Visible: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static interactions(harmed, harmingAffinity, context, side) {
|
||||||
|
const interactions = latus.get('%interactions');
|
||||||
|
flatten(
|
||||||
|
harmed.affinities()
|
||||||
|
.map((harmedAffinity) => interactions(harmingAffinity, harmedAffinity)),
|
||||||
|
).forEach((interaction) => {
|
||||||
|
if (interaction.actions[side]) {
|
||||||
|
const actions = new Actions(compile(interaction.actions[side], latus));
|
||||||
|
harmed.addTickingPromise(actions.tickingPromise(context));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get isInvulnerable() {
|
||||||
|
return this.#isInvulnerable;
|
||||||
|
}
|
||||||
|
|
||||||
|
set isInvulnerable(isInvulnerable) {
|
||||||
|
this.#isInvulnerable = isInvulnerable;
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
acceptedHarm: (harm) => {
|
||||||
|
const {amount, isDamage} = harm;
|
||||||
|
const fill = isDamage ? '#FF0000' : '#00FF77';
|
||||||
|
this.entity.emitParticle('harm', {
|
||||||
|
traits: {
|
||||||
|
Textual: {
|
||||||
|
state: {
|
||||||
|
text: amount,
|
||||||
|
textStyle: {
|
||||||
|
fill,
|
||||||
|
fontFamily: 'joystix',
|
||||||
|
fontSize: this.constructor.harmTextSize(amount),
|
||||||
|
strokeThickness: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
isDyingChanged: (_, isDying) => {
|
||||||
|
this.#isInvulnerable = isDying;
|
||||||
|
},
|
||||||
|
|
||||||
|
tookHarm: (harm) => {
|
||||||
|
if ('client' !== process.env.SIDE) {
|
||||||
|
this.#harms.push(harm);
|
||||||
|
const {from, affinity} = harm;
|
||||||
|
const context = new Context(
|
||||||
|
{
|
||||||
|
entity: [this.entity, 'entity'],
|
||||||
|
from: [this.entity.list.findEntity(from), 'entity'],
|
||||||
|
harm: [harm, 'harm'],
|
||||||
|
},
|
||||||
|
latus,
|
||||||
|
);
|
||||||
|
this.constructor.interactions(this.entity, affinity, context, 'server');
|
||||||
|
this.markAsDirty();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
methods() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
affinities: () => this.params.affinities,
|
||||||
|
|
||||||
|
ensureVulnerabilityLock: (entity, duration) => {
|
||||||
|
if (duration <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.#locks.has(entity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.#locks.set(entity, duration);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
isHarmedBy: (entity) => {
|
||||||
|
if (this.#isInvulnerable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return -1 === this.#isNotHarmedBy.indexOf(entity);
|
||||||
|
},
|
||||||
|
|
||||||
|
setHarmedBy: (entity) => {
|
||||||
|
const index = this.#isNotHarmedBy.indexOf(entity);
|
||||||
|
if (-1 !== index) {
|
||||||
|
this.#isNotHarmedBy.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setNotHarmedBy: (entity) => {
|
||||||
|
if (-1 === this.#isNotHarmedBy.indexOf(entity)) {
|
||||||
|
this.#isNotHarmedBy.push(entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
packets() {
|
||||||
|
return this.#harms.length > 0
|
||||||
|
? [['Harm', this.#harms]]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
tick(elapsed) {
|
||||||
|
if ('client' !== process.env.SIDE) {
|
||||||
|
const it = this.#locks.keys();
|
||||||
|
for (let current = it.next(); current.done !== true; current = it.next()) {
|
||||||
|
const {value: key} = current;
|
||||||
|
const remaining = this.#locks.get(key) - elapsed;
|
||||||
|
if (remaining <= 0) {
|
||||||
|
this.#locks.delete(key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.#locks.set(key, remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
9
packages/combat/test/exists.js
Normal file
9
packages/combat/test/exists.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
const {name} = require('../package.json');
|
||||||
|
|
||||||
|
describe(name, () => {
|
||||||
|
it('exists', () => {
|
||||||
|
expect(true).to.be.true;
|
||||||
|
})
|
||||||
|
});
|
3
packages/combat/webpack.config.js
Normal file
3
packages/combat/webpack.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();
|
8399
packages/combat/yarn.lock
Normal file
8399
packages/combat/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
97
packages/core/src/traits/lootable.js
Normal file
97
packages/core/src/traits/lootable.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
|
import {compose} from '@latus/core';
|
||||||
|
|
||||||
|
const decorate = compose(
|
||||||
|
StateProperty('lootable', {
|
||||||
|
track: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default () => class Lootable extends decorate(Trait) {
|
||||||
|
|
||||||
|
calculateLoot() {
|
||||||
|
const jsons = [];
|
||||||
|
const values = Object.values(this.params.table);
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
const roll = Math.random() * 100;
|
||||||
|
const {perc, json} = values[i];
|
||||||
|
if (perc > roll) {
|
||||||
|
jsons.push(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsons;
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultParams() {
|
||||||
|
return {
|
||||||
|
table: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultState() {
|
||||||
|
return {
|
||||||
|
lootable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeParams() {
|
||||||
|
return {
|
||||||
|
table: {
|
||||||
|
type: 'object',
|
||||||
|
label: 'Loot table',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeState() {
|
||||||
|
return {
|
||||||
|
lootable: {
|
||||||
|
type: 'bool',
|
||||||
|
label: 'Lootable',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hooks() {
|
||||||
|
const hooks = {};
|
||||||
|
if ('client' !== process.env.SIDE) {
|
||||||
|
hooks.died = async () => {
|
||||||
|
const jsons = this.calculateLoot();
|
||||||
|
const {position} = this.entity;
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 0; i < jsons.length; i++) {
|
||||||
|
const json = jsons[i];
|
||||||
|
if (!json.traits) {
|
||||||
|
json.traits = {};
|
||||||
|
}
|
||||||
|
json.traits.Emitted = {
|
||||||
|
params: {
|
||||||
|
force: [0, 8],
|
||||||
|
velocity: {
|
||||||
|
angle: {
|
||||||
|
min: 316,
|
||||||
|
max: 405,
|
||||||
|
},
|
||||||
|
magnitude: {
|
||||||
|
min: 0.7,
|
||||||
|
max: 0.9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
position,
|
||||||
|
transient: false,
|
||||||
|
ttl: 0.25,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const stream = this.entity.emitParticleJson(json);
|
||||||
|
promises.push(new Promise((resolve) => {
|
||||||
|
stream.onValue((particle) => this.entity.list.addEntity(particle));
|
||||||
|
stream.onEnd(() => resolve());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return hooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
84
packages/core/src/traits/magnetic.js
Normal file
84
packages/core/src/traits/magnetic.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import {StateProperty, Trait} from '@avocado/traits';
|
||||||
|
import {Rectangle, Vector} from '@avocado/math';
|
||||||
|
import {compose} from '@latus/core';
|
||||||
|
|
||||||
|
const decorate = compose(
|
||||||
|
StateProperty('attraction', {
|
||||||
|
track: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default () => class Magnetic extends decorate(Trait) {
|
||||||
|
|
||||||
|
static defaultParams() {
|
||||||
|
return {
|
||||||
|
isAttractor: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultState() {
|
||||||
|
return {
|
||||||
|
attraction: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeParams() {
|
||||||
|
return {
|
||||||
|
isAttractor: {
|
||||||
|
type: 'bool',
|
||||||
|
label: 'Is an attractor',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static describeState() {
|
||||||
|
return {
|
||||||
|
attraction: {
|
||||||
|
type: 'number',
|
||||||
|
label: 'Attraction',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAttracted() {
|
||||||
|
return !this.params.isAttractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAttractor() {
|
||||||
|
return !!this.params.isAttractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
if (!this.params.isAttractor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {attraction, layer, position} = this.entity;
|
||||||
|
if (0 === attraction || !layer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const query = Rectangle.compose(
|
||||||
|
Vector.sub(position, [32, 32]),
|
||||||
|
[64, 64],
|
||||||
|
);
|
||||||
|
const entities = layer.visibleEntities(query);
|
||||||
|
for (let i = 0; i < entities.length; ++i) {
|
||||||
|
const entity = entities[i];
|
||||||
|
if (
|
||||||
|
entity.is('Magnetic')
|
||||||
|
&& entity.isAttracted
|
||||||
|
&& entity.is('Positioned')
|
||||||
|
&& entity.is('Mobile')
|
||||||
|
) {
|
||||||
|
const distance = Vector.distance(position, entity.position);
|
||||||
|
if (distance <= attraction) {
|
||||||
|
const difference = Vector.sub(position, entity.position);
|
||||||
|
const unit = Vector.normalize(difference);
|
||||||
|
const rdiff = Math.max(0.4, 1 - (distance / attraction));
|
||||||
|
const magnitude = 100 * rdiff;
|
||||||
|
entity.applyMovement(Vector.scale(unit, magnitude));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user