feat: combat
This commit is contained in:
parent
22e5b3adee
commit
bd0367c8ae
|
@ -28,6 +28,7 @@
|
|||
"@avocado/sound": "^1.0.0",
|
||||
"@avocado/timing": "^2.0.0",
|
||||
"@avocado/topdown": "^2.0.0",
|
||||
"@humus/combat": "^1.0.0",
|
||||
"@humus/core": "^1.0.0",
|
||||
"@humus/farm": "^1.0.0",
|
||||
"@humus/inventory": "^1.0.0",
|
||||
|
|
|
@ -1088,6 +1088,19 @@
|
|||
object-assign "^4.1.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":
|
||||
version "1.0.0"
|
||||
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