@@ -11,7 +11,7 @@ | |||
<link rel="stylesheet" href="css/main.css"> | |||
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script> | |||
<script src="js/system/addons.js"></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.1.2/browser/pixi.min.js"></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/6.5.9/browser/pixi.min.js"></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js" data-main="js/app"></script> | |||
<script>if (window.module) module = window.module;</script> | |||
<link rel="icon" href="images/favicon.ico"> | |||
@@ -144,6 +144,9 @@ define([ | |||
}); | |||
} | |||
if (template.filters && obj.sprite) | |||
renderer.addFilter(obj.sprite, template.filters[0]); | |||
//We need to set visibility before components kick in as they sometimes need access to isVisible | |||
obj.updateVisibility(); | |||
@@ -241,6 +244,9 @@ define([ | |||
if ((!obj.sprite) && (template.sheetName)) | |||
obj.sprite = renderer.buildObject(obj); | |||
if (template.filters && !obj.sprite?.filters?.length) | |||
renderer.addFilter(obj.sprite, template.filters[0]); | |||
if (template.name) { | |||
if (obj.nameSprite) | |||
renderer.destroyObject({ sprite: obj.nameSprite }); | |||
@@ -17,9 +17,11 @@ define([ | |||
let tx = config.toX * scale; | |||
let ty = config.toY * scale; | |||
const linkSize = config.linkSize ?? 3; | |||
let angle = Math.atan2(ty - fy, tx - fx); | |||
let distance = Math.sqrt(Math.pow(tx - fx, 2) + Math.pow(ty - fy, 2)); | |||
let divDistance = Math.min(20, distance); | |||
let divDistance = config.divDistance ?? Math.min(20, distance); | |||
let divisions = config.divisions || Math.max(1, distance / divDistance); | |||
let x = fx; | |||
@@ -75,8 +77,8 @@ define([ | |||
lightPatch.tint = '0xffffff'; | |||
lightPatch.x = ~~((xx - scaleMult) / scaleMult) * scaleMult; | |||
lightPatch.y = ~~((yy - scaleMult) / scaleMult) * scaleMult; | |||
lightPatch.width = scaleMult * 3; | |||
lightPatch.height = scaleMult * 3; | |||
lightPatch.width = scaleMult * linkSize; | |||
lightPatch.height = scaleMult * linkSize; | |||
lightPatch.blendMode = PIXI.BLEND_MODES.ADD; | |||
@@ -9,8 +9,28 @@ define([ | |||
renderer, | |||
config | |||
) { | |||
//Defaults | |||
const MOVE_SPEED = 0.5; | |||
const TTL = 35; | |||
const FONT_SIZE = 18; | |||
const FONT_SIZE_CRIT = 22; | |||
const LAYER_NAME = 'effects'; | |||
const PADDING = scaleMult; | |||
const POSITION = { | |||
BOTTOM_CENTER: 0, | |||
LEFT_BOTTOM: 1, | |||
RIGHT_BOTTOM: 2, | |||
TOP_CENTER: 3 | |||
}; | |||
//Internals | |||
const list = []; | |||
//Create an object of the form: { elementName: elementIntegerColor, ... } from corresponding variable values. | |||
// These variables are defiend in main.less and take the form: var(--color-element-elementName) | |||
// These variables are defined in main.less and take the form: var(--color-element-elementName) | |||
const elementColors = Object.fromEntries( | |||
['default', 'arcane', 'frost', 'fire', 'holy', 'poison'].map(e => { | |||
const variableName = `--color-element-${e}`; | |||
@@ -22,97 +42,188 @@ define([ | |||
}) | |||
); | |||
return { | |||
list: [], | |||
//Helpers | |||
const getColor = ({ color, element }) => { | |||
if (color) | |||
return color; | |||
return elementColors[element]; | |||
}; | |||
const getText = ({ amount, text, heal }) => { | |||
if (amount === undefined) | |||
return text; | |||
const div = ((~~(amount * 10) / 10) > 0) ? 10 : 100; | |||
let result = ~~(amount * div) / div; | |||
if (heal) | |||
result = `+${result}`; | |||
return result; | |||
}; | |||
const getPosition = ({ position, event: isEvent, heal }) => { | |||
if (position) | |||
return position; | |||
//Events render under the target, centered | |||
if (isEvent) | |||
return POSITION.BOTTOM_CENTER; | |||
else if (heal) | |||
return POSITION.LEFT_BOTTOM; | |||
return POSITION.RIGHT_BOTTOM; | |||
}; | |||
const getXY = (msg, position, sprite) => { | |||
let x = 0; | |||
let y = 0; | |||
if (position === POSITION.TOP_CENTER) { | |||
x = (scale / 2) - (sprite.width / 2); | |||
y = -PADDING - sprite.height; | |||
} else if (position === POSITION.BOTTOM_CENTER) { | |||
x = (scale / 2) - (sprite.width / 2); | |||
y = scale + PADDING; | |||
} else if (position === POSITION.RIGHT_BOTTOM) { | |||
x = scale; | |||
y = scale - sprite.height + (scaleMult * 2); | |||
} else if (position === POSITION.LEFT_BOTTOM) { | |||
x = -sprite.width - PADDING; | |||
y = scale - sprite.height + (scaleMult * 2); | |||
} | |||
return { x, y }; | |||
}; | |||
init: function () { | |||
events.on('onGetDamage', this.onGetDamage.bind(this)); | |||
}, | |||
const getMovementDelta = ({ movementDelta }, position) => { | |||
if (movementDelta) | |||
return movementDelta; | |||
onGetDamage: function (msg) { | |||
if (config.damageNumbers === 'off') | |||
if (position === POSITION.BOTTOM_CENTER) | |||
return [0, 1]; | |||
return [0, -1]; | |||
}; | |||
const getFontSize = ({ fontSize, crit }) => { | |||
if (fontSize) | |||
return fontSize; | |||
else if (crit) | |||
return FONT_SIZE_CRIT; | |||
return FONT_SIZE; | |||
}; | |||
//Events | |||
const onGetDamage = msg => { | |||
const { ttl = TTL } = msg; | |||
if (config.damageNumbers === 'off') | |||
return; | |||
const target = objects.objects.find(o => o.id === msg.id); | |||
if (!target || !target.isVisible) | |||
return; | |||
const sprite = renderer.buildText({ | |||
fontSize: getFontSize(msg), | |||
layerName: LAYER_NAME, | |||
text: getText(msg), | |||
color: getColor(msg), | |||
visible: false | |||
}); | |||
const position = getPosition(msg); | |||
const movementDelta = getMovementDelta(msg, position); | |||
const { x, y } = getXY(msg, position, sprite); | |||
sprite.x = (target.x * scale) + x; | |||
sprite.y = (target.y * scale) + y; | |||
sprite.visible = true; | |||
const numberObj = { | |||
obj: target, | |||
x, | |||
y, | |||
ttl, | |||
ttlMax: ttl, | |||
movementDelta, | |||
sprite | |||
}; | |||
list.push(numberObj); | |||
}; | |||
//Call this method from inside update to generate test numbers | |||
// around the player | |||
/* eslint-disable-next-line no-unused-vars */ | |||
const test = () => { | |||
objects.objects.forEach(o => { | |||
if (!o.player) | |||
return; | |||
let target = objects.objects.find(function (o) { | |||
return (o.id === msg.id); | |||
const amount = Math.random() < 0.5 ? ~~(Math.random() * 100) : undefined; | |||
const isEvent = amount ? false : Math.random() < 0.5; | |||
const text = amount ? undefined : 'text'; | |||
const heal = Math.random() < 0.5; | |||
let position; | |||
if (!amount) | |||
position = Math.random() < 0.5 ? POSITION.TOP_CENTER : POSITION.BOTTOM_CENTER; | |||
const element = ['default', 'arcane', 'frost', 'fire', 'holy', 'poison'][~~(Math.random() * 6)]; | |||
const crit = amount > 50; | |||
onGetDamage({ | |||
id: o.id, | |||
event: isEvent, | |||
text, | |||
amount, | |||
element, | |||
heal, | |||
position, | |||
crit | |||
}); | |||
if (!target || !target.isVisible) | |||
return; | |||
}); | |||
}; | |||
let ttl = 35; | |||
let numberObj = { | |||
obj: target, | |||
amount: msg.amount, | |||
x: (target.x * scale), | |||
y: (target.y * scale) + scale - (scale / 4), | |||
ttl: ttl, | |||
ttlMax: ttl, | |||
event: msg.event, | |||
text: msg.text, | |||
crit: msg.crit, | |||
heal: msg.heal, | |||
element: msg.element | |||
}; | |||
if (numberObj.event) | |||
numberObj.y += (scale / 2); | |||
else if (numberObj.heal) | |||
numberObj.x -= scale; | |||
else | |||
numberObj.x += scale; | |||
let text = numberObj.text; | |||
if (!numberObj.event) { | |||
let amount = numberObj.amount; | |||
let div = ((~~(amount * 10) / 10) > 0) ? 10 : 100; | |||
text = (numberObj.heal ? '+' : '') + (~~(amount * div) / div); | |||
} | |||
const update = () => { | |||
let lLen = list.length; | |||
for (let i = 0; i < lLen; i++) { | |||
const l = list[i]; | |||
const colorVariableName = config.damageNumbers === 'element' ? numberObj.element : 'default'; | |||
numberObj.sprite = renderer.buildText({ | |||
fontSize: numberObj.crit ? 22 : 18, | |||
layerName: 'effects', | |||
x: numberObj.x, | |||
y: numberObj.y, | |||
text: text, | |||
color: elementColors[colorVariableName] | |||
}); | |||
l.ttl--; | |||
if (l.ttl === 0) { | |||
list.splice(i, 1); | |||
lLen--; | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: l.sprite | |||
}); | |||
this.list.push(numberObj); | |||
}, | |||
update: function () { | |||
let list = this.list; | |||
let lLen = list.length; | |||
for (let i = 0; i < lLen; i++) { | |||
let l = list[i]; | |||
l.ttl--; | |||
if (l.ttl === 0) { | |||
renderer.destroyObject({ | |||
layerName: 'effects', | |||
sprite: l.sprite | |||
}); | |||
list.splice(i, 1); | |||
i--; | |||
lLen--; | |||
continue; | |||
} | |||
if (l.event) | |||
l.y += 1; | |||
else | |||
l.y -= 1; | |||
let alpha = l.ttl / l.ttlMax; | |||
l.sprite.x = ~~(l.x / scaleMult) * scaleMult; | |||
l.sprite.y = ~~(l.y / scaleMult) * scaleMult; | |||
l.sprite.alpha = alpha; | |||
continue; | |||
} | |||
l.x += l.movementDelta[0] * MOVE_SPEED; | |||
l.y += l.movementDelta[1] * MOVE_SPEED; | |||
l.sprite.x = (l.obj.x * scale) + l.x; | |||
l.sprite.y = (l.obj.y * scale) + l.y; | |||
l.sprite.alpha = l.ttl / l.ttlMax; | |||
} | |||
}; | |||
const init = () => { | |||
events.on('onGetDamage', onGetDamage); | |||
}; | |||
//Exports | |||
return { | |||
init, | |||
update, | |||
onGetDamage, | |||
POSITION | |||
}; | |||
}); |
@@ -326,6 +326,12 @@ define([ | |||
c.zoneId = this.zoneId; | |||
events.emit('onGetObject', c); | |||
}); | |||
//Normally, the mounts mod queues this event when unmounting. | |||
// If we rezone, our effects are destroyed, so the event is queued, | |||
// but flushForTarget clears the event right after and the event is never received. | |||
// We emit it again here to make sure the speed is reset after entering the new zone. | |||
events.emit('onMoveSpeedChange', 0); | |||
}, | |||
setPosition: function (pos, instant) { | |||
@@ -734,14 +740,13 @@ define([ | |||
return sprite; | |||
}, | |||
addFilter: function (sprite) { | |||
let thickness = (sprite.width > scale) ? 8 : 16; | |||
addFilter: function (sprite, config) { | |||
const filter = new shaderOutline(config); | |||
let filter = new shaderOutline(this.renderer.width, this.renderer.height, thickness, '0xffffff'); | |||
if (!sprite.filters) | |||
sprite.filters = [filter]; | |||
else | |||
sprite.filters.push(); | |||
sprite.filters.push(filter); | |||
return filter; | |||
}, | |||
@@ -752,19 +757,24 @@ define([ | |||
}, | |||
buildText: function (obj) { | |||
let textSprite = new PIXI.Text(obj.text, { | |||
const { text, visible, x, y, parent: spriteParent, layerName } = obj; | |||
const { fontSize = 14, color = 0xF2F5F5 } = obj; | |||
const textSprite = new PIXI.Text(text, { | |||
fontFamily: 'bitty', | |||
fontSize: (obj.fontSize || 14), | |||
fill: obj.color || 0xF2F5F5, | |||
fontSize: fontSize, | |||
fill: color, | |||
stroke: 0x2d2136, | |||
strokeThickness: 4, | |||
align: 'center' | |||
strokeThickness: 4 | |||
}); | |||
textSprite.x = obj.x - (textSprite.width / 2); | |||
textSprite.y = obj.y; | |||
if (visible === false) | |||
textSprite.visible = false; | |||
textSprite.x = x - (textSprite.width / 2); | |||
textSprite.y = y; | |||
let parentSprite = obj.parent || this.layers[obj.layerName]; | |||
const parentSprite = spriteParent ?? this.layers[layerName]; | |||
parentSprite.addChild(textSprite); | |||
return textSprite; | |||
@@ -2,62 +2,65 @@ define([ | |||
'js/rendering/shaders/outline/vert', | |||
'js/rendering/shaders/outline/frag' | |||
], function ( | |||
vert, | |||
frag | |||
vertex, | |||
fragment | |||
) { | |||
let OutlineFilter = function (viewWidth, viewHeight, thickness, color) { | |||
thickness = thickness || 1; | |||
PIXI.Filter.call(this, | |||
vert, | |||
frag.replace(/%THICKNESS%/gi, (1.0 / thickness).toFixed(7)) | |||
); | |||
this.uniforms.pixelWidth = 0.002; | |||
this.uniforms.pixelHeight = 1.0 / (viewHeight || 1); | |||
this.uniforms.thickness = thickness; | |||
this.uniforms.outlineColor = new Float32Array([0, 0, 0, 1]); | |||
this.alpha = 0; | |||
if (color) | |||
this.color = color; | |||
}; | |||
OutlineFilter.prototype = Object.create(PIXI.Filter.prototype); | |||
OutlineFilter.prototype.constructor = OutlineFilter; | |||
Object.defineProperties(OutlineFilter.prototype, { | |||
color: { | |||
get: function () { | |||
return PIXI.utils.rgb2hex(this.uniforms.outlineColor); | |||
}, | |||
set: function (value) { | |||
PIXI.utils.hex2rgb(value, this.uniforms.outlineColor); | |||
} | |||
}, | |||
alpha: { | |||
set: function (value) { | |||
this.uniforms.alpha = value; | |||
} | |||
}, | |||
viewWidth: { | |||
get: function () { | |||
return 1 / this.uniforms.pixelWidth; | |||
}, | |||
set: function (value) { | |||
this.uniforms.pixelWidth = 1 / value; | |||
} | |||
}, | |||
viewHeight: { | |||
get: function () { | |||
return 1 / this.uniforms.pixelHeight; | |||
}, | |||
set: function (value) { | |||
this.uniforms.pixelHeight = 1 / value; | |||
} | |||
} | |||
}); | |||
class OutlineFilter extends PIXI.Filter { | |||
constructor ({ thickness = 5, color = 0xFFFFFF, quality = 0.1, alpha = 1.0, knockout = false }) { | |||
const angleStep = Math.PI / 2; | |||
super(vertex, fragment.replace('$angleStep$', angleStep)); | |||
this.uniforms.uThickness = new Float32Array([thickness, thickness]); | |||
this.uniforms.uColor = new Float32Array([1, 1, 1, 1]); | |||
this.uniforms.uAlpha = alpha; | |||
this.uniforms.uKnockout = knockout; | |||
const rgbColor = PIXI.utils.hex2rgb(color); | |||
this.uniforms.uColor = PIXI.utils.hex2rgb(rgbColor, this.uniforms.uColor); | |||
Object.assign(this, { thickness, color, quality, alpha, knockout }); | |||
} | |||
apply (filterManager, input, output, clear) { | |||
this.uniforms.uThickness[0] = this.thickness / input._frame.width; | |||
this.uniforms.uThickness[1] = this.thickness / input._frame.height; | |||
this.uniforms.uAlpha = this.alpha; | |||
this.uniforms.uKnockout = this.knockout; | |||
this.uniforms.uColor = PIXI.utils.hex2rgb(this.color, this.uniforms.uColor); | |||
filterManager.applyFilter(this, input, output, clear); | |||
} | |||
get alpha () { | |||
return this._alpha; | |||
} | |||
set alpha (value) { | |||
this._alpha = value; | |||
} | |||
get color () { | |||
return PIXI.utils.rgb2hex(this.uniforms.uColor); | |||
} | |||
set color (value) { | |||
PIXI.utils.hex2rgb(value, this.uniforms.uColor); | |||
} | |||
get knockout () { | |||
return this._knockout; | |||
} | |||
set knockout (value) { | |||
this._knockout = value; | |||
} | |||
get thickness () { | |||
return this._thickness; | |||
} | |||
set thickness (value) { | |||
this._thickness = value; | |||
this.padding = value; | |||
} | |||
} | |||
return OutlineFilter; | |||
}); |
@@ -6,28 +6,41 @@ define([ | |||
return ` | |||
varying vec2 vTextureCoord; | |||
uniform sampler2D uSampler; | |||
uniform vec4 filterClamp; | |||
uniform float thickness; | |||
uniform vec4 outlineColor; | |||
uniform float pixelWidth; | |||
uniform float alpha; | |||
vec2 px = vec2(pixelWidth, pixelWidth); | |||
uniform float uAlpha; | |||
uniform vec2 uThickness; | |||
uniform vec4 uColor; | |||
uniform bool uKnockout; | |||
void main(void) { | |||
const float PI = 3.14159265358979323846264; | |||
vec4 ownColor = texture2D(uSampler, vTextureCoord); | |||
vec4 curColor; | |||
const float DOUBLE_PI = 2. * 3.14159265358979323846264; | |||
const float ANGLE_STEP = $angleStep$; | |||
float outlineMaxAlphaAtPos(vec2 pos) { | |||
if (uThickness.x == 0. || uThickness.y == 0.) { | |||
return 0.; | |||
} | |||
vec4 displacedColor; | |||
vec2 displacedPos; | |||
float maxAlpha = 0.; | |||
for (float angle = 0.; angle < PI * 2.; angle += %THICKNESS% ) { | |||
curColor = texture2D(uSampler, vec2(vTextureCoord.x + thickness * px.x * cos(angle), vTextureCoord.y + thickness * px.y * sin(angle))); | |||
maxAlpha = max(maxAlpha, curColor.a); | |||
for (float angle = 0.; angle <= DOUBLE_PI; angle += ANGLE_STEP) { | |||
displacedPos.x = vTextureCoord.x + uThickness.x * cos(angle); | |||
displacedPos.y = vTextureCoord.y + uThickness.y * sin(angle); | |||
displacedColor = texture2D(uSampler, clamp(displacedPos, filterClamp.xy, filterClamp.zw)); | |||
maxAlpha = max(maxAlpha, displacedColor.a); | |||
} | |||
if (maxAlpha > 0.1) | |||
maxAlpha = alpha; | |||
else | |||
maxAlpha = 0.0; | |||
float resultAlpha = max(maxAlpha, ownColor.a); | |||
gl_FragColor = vec4((ownColor.rgb + outlineColor.rgb * (1. - ownColor.a)) * resultAlpha, resultAlpha); | |||
return maxAlpha; | |||
} | |||
void main(void) { | |||
vec4 sourceColor = texture2D(uSampler, vTextureCoord); | |||
vec4 contentColor = sourceColor * float(!uKnockout); | |||
float outlineAlpha = uAlpha * outlineMaxAlphaAtPos(vTextureCoord.xy) * (1.-sourceColor.a); | |||
vec4 outlineColor = vec4(vec3(uColor) * outlineAlpha, outlineAlpha); | |||
gl_FragColor = contentColor + outlineColor; | |||
} | |||
`; | |||
}); |
@@ -5,14 +5,30 @@ define([ | |||
) { | |||
return ` | |||
attribute vec2 aVertexPosition; | |||
attribute vec2 aTextureCoord; | |||
uniform mat3 projectionMatrix; | |||
varying vec2 vTextureCoord; | |||
void main(void){ | |||
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); | |||
vTextureCoord = aTextureCoord; | |||
uniform vec4 inputSize; | |||
uniform vec4 outputFrame; | |||
vec4 filterVertexPosition( void ) | |||
{ | |||
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; | |||
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); | |||
} | |||
vec2 filterTextureCoord( void ) | |||
{ | |||
return aVertexPosition * (outputFrame.zw * inputSize.zw); | |||
} | |||
void main(void) | |||
{ | |||
gl_Position = filterVertexPosition(); | |||
vTextureCoord = filterTextureCoord(); | |||
} | |||
`; | |||
}); |
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "isleward_client", | |||
"version": "0.11.0", | |||
"version": "0.12.0", | |||
"description": "isleward", | |||
"dependencies": { | |||
}, | |||
@@ -90,8 +90,13 @@ define([ | |||
let path = null; | |||
if (options && options.path) | |||
path = options.path + `\\${type}.js`; | |||
else | |||
path = this.root + 'ui/templates/' + type + '/' + type; | |||
else { | |||
const entryInClientConfig = globals.clientConfig.uiList.find(u => u.type === type); | |||
if (entryInClientConfig) | |||
path = entryInClientConfig.path; | |||
else | |||
path = this.root + 'ui/templates/' + type + '/' + type; | |||
} | |||
require([path], this.onGetTemplate.bind(this, options, type)); | |||
}, | |||
@@ -112,7 +112,6 @@ | |||
width: 80px; | |||
height: 80px; | |||
background-color: fade(#3a3b4a, 90%); | |||
margin-bottom: 8px; | |||
cursor: pointer; | |||
padding: 8px; | |||
position: relative; | |||
@@ -11,11 +11,11 @@ | |||
</div> | |||
<div class="message"></div> | |||
</div> | |||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.11.0-Release-Notes">[ Latest Release Notes ]</div> | |||
<div class="news" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.12.0-Release-Notes">[ Latest Release Notes ]</div> | |||
<div class="extra"> | |||
<div class="el btn btnPatreon monetization" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | |||
<div class="el btn btnPaypal monetization" location="https://www.paypal.com/donate?hosted_button_id=NEQAV3NG9PWXA">Donate on Paypal</div> | |||
<div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | |||
</div> | |||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.11.0-Release-Notes">v0.11.0</div> | |||
<div class="version" location="https://gitlab.com/Isleward/play.isleward.com/-/wikis/v0.12.0-Release-Notes">v0.12.0</div> | |||
</div> |
@@ -242,10 +242,12 @@ define([ | |||
} | |||
if (m.type) { | |||
let isChannel = (['info', 'chat', 'loot', 'rep'].indexOf(m.type) === -1); | |||
const isChannel = m?.subType === 'custom'; | |||
if (isChannel) { | |||
if (this.find('.filter[filter="' + m.type + '"]').hasClass('active')) | |||
if (this.find('.filter[filter="' + m.channel + '"]').hasClass('active')) | |||
el.show(); | |||
else | |||
el.hide(); | |||
} | |||
if (isMobile && ['loot', 'info'].indexOf(m.type) !== -1) { | |||
@@ -7,8 +7,10 @@ define([ | |||
renderKeyboard: function () { | |||
this.find('.keyboard').remove(); | |||
let mainContainer = this.find('.main'); | |||
let container = $('<div class="keyboard"></div>') | |||
.appendTo(this.el); | |||
.appendTo(mainContainer); | |||
let keyboard = { | |||
0: 'qwertyuiop|asdfghjkl|zxcvbnm', | |||
@@ -318,13 +318,20 @@ | |||
&.typing { | |||
left: 0px; | |||
top: 0px; | |||
width: 100%; | |||
height: 100%; | |||
width: 100vw; | |||
height: 100vh; | |||
background-color: fade(@blackC, 90%); | |||
display: flex; | |||
flex-direction: column; | |||
z-index: 999999998; | |||
.main { | |||
display: flex; | |||
flex-direction: column; | |||
flex-grow: 1; | |||
min-height: 0px; | |||
} | |||
.channelPicker { | |||
display: none; | |||
} | |||
@@ -54,28 +54,30 @@ define([ | |||
msg.forEach(m => { | |||
const { id: mId, zoneId: mZone } = m; | |||
if (party.indexOf(m.id) === -1) | |||
if (!party.includes(m.id)) | |||
return; | |||
if (mId === playerId) { | |||
party.forEach(p => { | |||
const mObj = globals.onlineList.find(o => o.id === p); | |||
let el = this.find('.member[memberId="' + p + '"]'); | |||
el.removeClass('differentZone'); | |||
if (mObj.mZone !== mZone) | |||
el.addClass('differentZone'); | |||
}); | |||
} else { | |||
let el = this.find('.member[memberId="' + m.id + '"]'); | |||
if (mId !== playerId) { | |||
const el = this.find('.member[memberId="' + m.id + '"]'); | |||
el.removeClass('differentZone'); | |||
if (m.mZone !== playerZone) | |||
if (m.zoneId !== playerZone) | |||
el.addClass('differentZone'); | |||
el.find('.txtLevel').html('level: ' + m.level); | |||
return; | |||
} | |||
party.forEach(p => { | |||
const mObj = globals.onlineList.find(o => o.id === p); | |||
const el = this.find('.member[memberId="' + p + '"]'); | |||
el.removeClass('differentZone'); | |||
if (mObj.zoneId !== mZone) | |||
el.addClass('differentZone'); | |||
}); | |||
}); | |||
}, | |||
@@ -214,7 +214,15 @@ define([ | |||
html += '<br />'; | |||
}); | |||
return html; | |||
const result = ( | |||
lineBuilders.div('space', ' ') + | |||
(!!item.spell?.values ? lineBuilders.div('line', ' ') + lineBuilders.div('space', ' ') : '') + | |||
html + | |||
lineBuilders.div('space', ' ') + | |||
lineBuilders.div('line', ' ') | |||
); | |||
return result; | |||
}, | |||
material: () => { | |||
@@ -314,7 +322,7 @@ define([ | |||
item.factions.forEach((f, i) => { | |||
let htmlF = f.name + ': ' + f.tierName; | |||
if (f.noEquip) | |||
if (f.tier > window.player.reputation.getTier(f.id)) | |||
htmlF = '<font class="color-red">' + htmlF + '</font>'; | |||
htmlFactions += htmlF; | |||
@@ -5,11 +5,11 @@ define([ | |||
) { | |||
const percentageStats = [ | |||
'addCritChance', | |||
'addCritemultiplier', | |||
'addCritMultiplier', | |||
'addAttackCritChance', | |||
'addAttackCritemultiplier', | |||
'addAttackCritMultiplier', | |||
'addSpellCritChance', | |||
'addSpellCritemultiplier', | |||
'addSpellCritMultiplier', | |||
'sprintChance', | |||
'xpIncrease', | |||
'blockAttackChance', | |||
@@ -99,7 +99,7 @@ | |||
} | |||
.effects { | |||
color: @white; | |||
color: @blueB; | |||
} | |||
.faction { | |||
@@ -93,7 +93,7 @@ define([ | |||
noAfford = (~~(item.worth * this.itemList.markup) > window.player.trade.gold); | |||
if (!noAfford && item.factions) | |||
noAfford = item.factions.some(f => f.noEquip); | |||
noAfford = item.factions.some(f => f.tier > window.player.reputation.getTier(f.id)); | |||
if (noAfford) | |||
$('<div class="no-afford"></div>').appendTo(itemEl); | |||
@@ -1,183 +1,314 @@ | |||
{ | |||
"root": true, | |||
"parser": "babel-eslint", | |||
"env": { | |||
"es6": true, | |||
"node": true | |||
}, | |||
"plugins": [ | |||
"prettier" | |||
], | |||
// Map from global var to bool specifying if it can be redefined | |||
"globals": { | |||
"__DEV__": true, | |||
"__dirname": false, | |||
"__fbBatchedBridgeConfig": false, | |||
"alert": false, | |||
"cancelAnimationFrame": false, | |||
"cancelIdleCallback": false, | |||
"clearImmediate": true, | |||
"clearInterval": false, | |||
"clearTimeout": false, | |||
"console": false, | |||
"document": false, | |||
"escape": false, | |||
"Event": false, | |||
"EventTarget": false, | |||
"exports": false, | |||
"fetch": false, | |||
"FormData": false, | |||
"global": false, | |||
"jest": false, | |||
"Map": true, | |||
"module": false, | |||
"navigator": false, | |||
"process": false, | |||
"Promise": true, | |||
"requestAnimationFrame": true, | |||
"requestIdleCallback": true, | |||
"require": false, | |||
"Set": true, | |||
"setImmediate": true, | |||
"setInterval": false, | |||
"setTimeout": false, | |||
"window": false, | |||
"XMLHttpRequest": false, | |||
"pit": false, | |||
"extend": false, | |||
"_": false, | |||
"io": false, | |||
"cons": false, | |||
"atlas": false, | |||
"instancer": false, | |||
"leaderboard": false, | |||
"clientConfig": false, | |||
"random": false, | |||
"consts": false, | |||
"rezoneManager": false, | |||
"eventManager": false | |||
}, | |||
"rules": { | |||
"comma-dangle": [2,"never"], | |||
"no-cond-assign": [2,"always"], | |||
"no-console": 1, | |||
"no-constant-condition": 2, | |||
"no-control-regex": 2, | |||
"no-debugger": 1, | |||
"no-dupe-args": 2, | |||
"no-dupe-keys": 2, | |||
"no-duplicate-case": 2, | |||
"no-empty-character-class": 2, | |||
"no-empty": [2, { "allowEmptyCatch": true }], | |||
"no-ex-assign": 2, | |||
"no-extra-semi": 2, | |||
"no-func-assign": 2, | |||
"no-inner-declarations": [2,"functions"], | |||
"no-invalid-regexp": 2, | |||
"no-irregular-whitespace": 2, | |||
"no-negated-in-lhs": 2, | |||
"no-obj-calls": 2, | |||
"no-regex-spaces": 2, | |||
"no-sparse-arrays": 2, | |||
"no-unreachable": 1, | |||
"use-isnan": 2, | |||
"valid-typeof": 2, | |||
"block-scoped-var": 2, | |||
"curly": [2,"multi-or-nest"], | |||
"dot-notation": 2, | |||
"dot-location": [2,"property"], | |||
"eqeqeq": [2,"always", {"null": "ignore"}], | |||
"no-alert": 2, | |||
"no-caller": 2, | |||
"no-else-return": 2, | |||
"no-eq-null": 1, | |||
"no-eval": 2, | |||
"no-extend-native": 1, | |||
"no-fallthrough": 2, | |||
"no-floating-decimal": 2, | |||
"no-implied-eval": 2, | |||
"no-iterator": 2, | |||
"no-labels": 2, | |||
"no-lone-blocks": 2, | |||
"no-multi-spaces": 2, | |||
"no-native-reassign": 2, | |||
"no-new-func": 2, | |||
"no-new-wrappers": 2, | |||
"no-new": 2, | |||
"no-octal-escape": 2, | |||
"no-octal": 2, | |||
"no-process-env": 1, | |||
"no-proto": 2, | |||
"no-redeclare": 2, | |||
"no-return-assign": [2,"always"], | |||
"no-script-url": 2, | |||
"no-self-compare": 2, | |||
"no-sequences": 2, | |||
"no-throw-literal": 2, | |||
"no-unused-expressions": 2, | |||
"no-useless-call": 2, | |||
"no-void": 2, | |||
"no-with": 2, | |||
"wrap-iife": [2,"inside"], | |||
"yoda": [2,"never",{}], | |||
"strict": [2,"global"], | |||
"no-catch-shadow": 2, | |||
"no-delete-var": 2, | |||
"no-label-var": 2, | |||
"no-shadow-restricted-names": 2, | |||
"no-shadow": [2,{"builtinGlobals":true,"hoist":"all"}], | |||
"no-undef-init": 2, | |||
"no-undef": 1, | |||
"no-unused-vars": [1, {"args": "none"}], | |||
"no-use-before-define": 2, | |||
"no-mixed-requires": [2,false], | |||
"no-new-require": 2, | |||
"no-path-concat": 2, | |||
"brace-style": [2,"1tbs",{}], | |||
"camelcase": [1,{"properties":"never"}], | |||
"comma-spacing": [2,{"before": false, "after":true}], | |||
"comma-style": 2, | |||
"eol-last": 2, | |||
"func-style": [1,"expression"], | |||
"indent": [2, "tab"], | |||
"key-spacing": [2,{"afterColon":true}], | |||
"max-lines-per-function": [ | |||
2, | |||
{ | |||
"max": 120, | |||
"skipBlankLines": false, | |||
"skipComments": false | |||
} | |||
], | |||
"new-parens": 2, | |||
"no-inline-comments": 2, | |||
"no-lonely-if": 2, | |||
"no-mixed-spaces-and-tabs": 2, | |||
"no-multiple-empty-lines": [2,{"max":1}], | |||
"no-nested-ternary": 1, | |||
"no-new-object": 2, | |||
"no-spaced-func": 2, | |||
"no-underscore-dangle": 1, | |||
"no-unneeded-ternary": 2, | |||
"object-curly-spacing": [2,"always",{"arraysInObjects":true,"objectsInObjects":true}], | |||
"padded-blocks": [2,"never"], | |||
"quote-props": [2,"as-needed"], | |||
"quotes": [2,"single","avoid-escape"], | |||
"semi-spacing": [2,{"after":true}], | |||
"semi": [2,"always"], | |||
"space-before-blocks": [2,"always"], | |||
"space-before-function-paren": [2,"always"], | |||
"space-infix-ops": 2, | |||
"keyword-spacing": [2,{"before":true,"after":true}], | |||
"arrow-parens": [2,"as-needed"], | |||
"arrow-spacing": [2,{"before":true,"after":true}], | |||
"no-const-assign": 2, | |||
"no-var": 2 | |||
} | |||
} | |||
{ | |||
"root": true, | |||
"parser": "babel-eslint", | |||
"env": { | |||
"es6": true, | |||
"node": true | |||
}, | |||
"plugins": [ | |||
"prettier" | |||
], | |||
"globals": { | |||
"__DEV__": true, | |||
"__dirname": false, | |||
"__fbBatchedBridgeConfig": false, | |||
"alert": false, | |||
"cancelAnimationFrame": false, | |||
"cancelIdleCallback": false, | |||
"clearImmediate": true, | |||
"clearInterval": false, | |||
"clearTimeout": false, | |||
"console": false, | |||
"document": false, | |||
"escape": false, | |||
"Event": false, | |||
"EventTarget": false, | |||
"exports": false, | |||
"fetch": false, | |||
"FormData": false, | |||
"global": false, | |||
"jest": false, | |||
"Map": true, | |||
"module": false, | |||
"navigator": false, | |||
"process": false, | |||
"Promise": true, | |||
"requestAnimationFrame": true, | |||
"requestIdleCallback": true, | |||
"require": false, | |||
"Set": true, | |||
"setImmediate": true, | |||
"setInterval": false, | |||
"setTimeout": false, | |||
"window": false, | |||
"XMLHttpRequest": false, | |||
"pit": false, | |||
"extend": false, | |||
"_": false, | |||
"io": false, | |||
"cons": false, | |||
"atlas": false, | |||
"instancer": false, | |||
"leaderboard": false, | |||
"clientConfig": false, | |||
"random": false, | |||
"consts": false, | |||
"rezoneManager": false, | |||
"eventManager": false | |||
}, | |||
"rules": { | |||
"comma-dangle": [ | |||
2, | |||
"never" | |||
], | |||
"no-cond-assign": [ | |||
2, | |||
"always" | |||
], | |||
"no-console": 1, | |||
"no-constant-condition": 2, | |||
"no-control-regex": 2, | |||
"no-debugger": 1, | |||
"no-dupe-args": 2, | |||
"no-dupe-keys": 2, | |||
"no-duplicate-case": 2, | |||
"no-empty-character-class": 2, | |||
"no-empty": [ | |||
2, | |||
{ | |||
"allowEmptyCatch": true | |||
} | |||
], | |||
"no-ex-assign": 2, | |||
"no-extra-semi": 2, | |||
"no-func-assign": 2, | |||
"no-inner-declarations": [ | |||
2, | |||
"functions" | |||
], | |||
"no-invalid-regexp": 2, | |||
"no-irregular-whitespace": 2, | |||
"no-negated-in-lhs": 2, | |||
"no-obj-calls": 2, | |||
"no-regex-spaces": 2, | |||
"no-sparse-arrays": 2, | |||
"no-unreachable": 1, | |||
"use-isnan": 2, | |||
"valid-typeof": 2, | |||
"block-scoped-var": 2, | |||
"curly": [ | |||
2, | |||
"multi-or-nest" | |||
], | |||
"dot-notation": 2, | |||
"dot-location": [ | |||
2, | |||
"property" | |||
], | |||
"eqeqeq": [ | |||
2, | |||
"always", | |||
{ | |||
"null": "ignore" | |||
} | |||
], | |||
"no-alert": 2, | |||
"no-caller": 2, | |||
"no-else-return": 2, | |||
"no-eq-null": 1, | |||
"no-eval": 2, | |||
"no-extend-native": 1, | |||
"no-fallthrough": 2, | |||
"no-floating-decimal": 2, | |||
"no-implied-eval": 2, | |||
"no-iterator": 2, | |||
"no-labels": 2, | |||
"no-lone-blocks": 2, | |||
"no-multi-spaces": 2, | |||
"no-native-reassign": 2, | |||
"no-new-func": 2, | |||
"no-new-wrappers": 2, | |||
"no-new": 2, | |||
"no-octal-escape": 2, | |||
"no-octal": 2, | |||
"no-process-env": 1, | |||
"no-proto": 2, | |||
"no-redeclare": 2, | |||
"no-return-assign": [ | |||
2, | |||
"always" | |||
], | |||
"no-script-url": 2, | |||
"no-self-compare": 2, | |||
"no-sequences": 2, | |||
"no-throw-literal": 2, | |||
"no-unused-expressions": 2, | |||
"no-useless-call": 2, | |||
"no-void": 2, | |||
"no-with": 2, | |||
"wrap-iife": [ | |||
2, | |||
"inside" | |||
], | |||
"yoda": [ | |||
2, | |||
"never", | |||
{} | |||
], | |||
"strict": [ | |||
2, | |||
"global" | |||
], | |||
"no-catch-shadow": 2, | |||
"no-delete-var": 2, | |||
"no-label-var": 2, | |||
"no-shadow-restricted-names": 2, | |||
"no-shadow": [ | |||
2, | |||
{ | |||
"builtinGlobals": true, | |||
"hoist": "all" | |||
} | |||
], | |||
"no-undef-init": 2, | |||
"no-undef": 1, | |||
"no-unused-vars": [ | |||
1, | |||
{ | |||
"args": "none" | |||
} | |||
], | |||
"no-use-before-define": 2, | |||
"no-mixed-requires": [ | |||
2, | |||
false | |||
], | |||
"no-new-require": 2, | |||
"no-path-concat": 2, | |||
"brace-style": [ | |||
2, | |||
"1tbs", | |||
{} | |||
], | |||
"camelcase": [ | |||
1, | |||
{ | |||
"properties": "never" | |||
} | |||
], | |||
"comma-spacing": [ | |||
2, | |||
{ | |||
"before": false, | |||
"after": true | |||
} | |||
], | |||
"comma-style": 2, | |||
"eol-last": 2, | |||
"func-style": [ | |||
1, | |||
"expression" | |||
], | |||
"indent": [ | |||
2, | |||
"tab" | |||
], | |||
"key-spacing": [ | |||
2, | |||
{ | |||
"afterColon": true | |||
} | |||
], | |||
"max-lines-per-function": [ | |||
2, | |||
{ | |||
"max": 120, | |||
"skipBlankLines": false, | |||
"skipComments": false | |||
} | |||
], | |||
"new-parens": 2, | |||
"no-inline-comments": 2, | |||
"no-lonely-if": 2, | |||
"no-mixed-spaces-and-tabs": 2, | |||
"no-multiple-empty-lines": [ | |||
2, | |||
{ | |||
"max": 1 | |||
} | |||
], | |||
"no-nested-ternary": 1, | |||
"no-new-object": 2, | |||
"no-spaced-func": 2, | |||
"no-underscore-dangle": 1, | |||
"no-unneeded-ternary": 2, | |||
"object-curly-spacing": [ | |||
2, | |||
"always", | |||
{ | |||
"arraysInObjects": true, | |||
"objectsInObjects": true | |||
} | |||
], | |||
"padded-blocks": [ | |||
2, | |||
"never" | |||
], | |||
"quote-props": [ | |||
2, | |||
"as-needed" | |||
], | |||
"quotes": [ | |||
2, | |||
"single", | |||
"avoid-escape" | |||
], | |||
"semi-spacing": [ | |||
2, | |||
{ | |||
"after": true | |||
} | |||
], | |||
"semi": [ | |||
2, | |||
"always" | |||
], | |||
"space-before-blocks": [ | |||
2, | |||
"always" | |||
], | |||
"space-before-function-paren": [ | |||
2, | |||
"always" | |||
], | |||
"space-infix-ops": 2, | |||
"keyword-spacing": [ | |||
2, | |||
{ | |||
"before": true, | |||
"after": true | |||
} | |||
], | |||
"arrow-parens": [ | |||
2, | |||
"as-needed" | |||
], | |||
"arrow-spacing": [ | |||
2, | |||
{ | |||
"before": true, | |||
"after": true | |||
} | |||
], | |||
"no-const-assign": 2, | |||
"no-var": 2 | |||
}, | |||
"overrides": [ | |||
{ | |||
"files": [ | |||
"**/clientComponents/**/*.js" | |||
], | |||
"extends": "../client/.eslintrc" | |||
} | |||
] | |||
} |
@@ -1,3 +0,0 @@ | |||
{ | |||
"extends": "../../client/.eslintrc" | |||
} |
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
define([ | |||
'js/system/events', | |||
'js/rendering/numbers' | |||
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
define([ | |||
'js/rendering/renderer', | |||
'js/system/events' | |||
@@ -59,19 +59,18 @@ define([ | |||
}, | |||
equipItemErrors: function (item) { | |||
let errors = []; | |||
let stats = this.obj.stats.values; | |||
const { obj: { reputation, stats: { values: statValues } } } = this; | |||
if (item.level > stats.level) | |||
const errors = []; | |||
if (item.level > statValues.level) | |||
errors.push('level'); | |||
if (item.requires && item.requires[0] && stats[item.requires[0].stat] < item.requires[0].value) | |||
if (item.requires && item.requires[0] && statValues[item.requires[0].stat] < item.requires[0].value) | |||
errors.push('stats'); | |||
if (item.factions) { | |||
if (item.factions.some(f => f.noEquip)) | |||
errors.push('faction'); | |||
} | |||
if (item.factions?.some(f => reputation.getTier(f.id) < f.tier)) | |||
errors.push('faction'); | |||
return errors; | |||
}, | |||
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
define([ | |||
'js/rendering/lightningBuilder', | |||
'js/rendering/effects' | |||
@@ -23,13 +25,13 @@ define([ | |||
init: function () { | |||
effects.register(this); | |||
let xOffset = (this.toX >= this.obj.x) ? 1 : 0; | |||
let { toX = this.target.x, toY = this.target.y } = this; | |||
let fromX = this.obj.x + xOffset; | |||
let fromY = this.obj.y + 0.5; | |||
const fromX = this.obj.x + ((toX >= this.obj.x) ? 1 : 0); | |||
const fromY = this.obj.y + 0.5; | |||
let toX = this.lineGrow ? fromX : this.toX + 0.5; | |||
let toY = this.lineGrow ? fromY : this.toY + 0.5; | |||
toX = this.lineGrow ? fromX : toX + 0.5; | |||
toY = this.lineGrow ? fromY : toY + 0.5; | |||
this.effect = lightningBuilder.build({ | |||
fromX: fromX, | |||
@@ -38,7 +40,9 @@ define([ | |||
toY: toY, | |||
divisions: this.divisions, | |||
colors: this.colors, | |||
maxDeviate: this.maxDeviate | |||
maxDeviate: this.maxDeviate, | |||
divDistance: this.divDistance, | |||
linkSize: this.linkSize | |||
}); | |||
}, | |||
@@ -56,8 +60,10 @@ define([ | |||
this.cd = cdMax; | |||
lightningBuilder.destroy(this.effect); | |||
this.effect = null; | |||
if (this.effect) { | |||
lightningBuilder.destroy(this.effect); | |||
this.effect = null; | |||
} | |||
if (!this.shrinking) { | |||
this.ttl--; | |||
@@ -67,14 +73,15 @@ define([ | |||
} | |||
} | |||
let xOffset = (this.toX >= this.obj.x) ? 1 : 0; | |||
let { toX = this.target.x, toY = this.target.y } = this; | |||
toX += 0.5; | |||
toY += 0.5; | |||
let xOffset = (toX >= this.obj.x) ? 1 : 0; | |||
let fromX = this.obj.x + xOffset; | |||
let fromY = this.obj.y + 0.5; | |||
let toX = this.toX + 0.5; | |||
let toY = this.toY + 0.5; | |||
let changeTo = ( | |||
( | |||
(this.lineGrow) && | |||
@@ -108,7 +115,9 @@ define([ | |||
toY: toY, | |||
divisions: this.divisions, | |||
colors: this.colors, | |||
maxDeviate: this.maxDeviate | |||
maxDeviate: this.maxDeviate, | |||
divDistance: this.divDistance, | |||
linkSize: this.linkSize | |||
}); | |||
if ((this.shrinking) && (linePercentage < 0.1)) | |||
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
define([ | |||
'js/rendering/renderer', | |||
'js/system/events', | |||
@@ -29,6 +29,10 @@ define([ | |||
events.emit('onGetReputations', this.list); | |||
} | |||
}, | |||
getTier: function (factionId) { | |||
return this.factions.find(f => f.id === factionId)?.tier ?? 3; | |||
} | |||
}; | |||
}); |
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
define([ | |||
'js/system/client', | |||
'js/rendering/renderer', | |||
@@ -4,9 +4,9 @@ const max = Math.max.bind(Math); | |||
//Helpers | |||
const scaleStatType = (config, result) => { | |||
const { statType, statMult = 1, srcValues } = config; | |||
const { statType, statMult = 1, srcValues, scaleConfig } = config; | |||
if (!statType) | |||
if (!statType || scaleConfig?.statMult === false) | |||
return; | |||
let statValue = 0; | |||
@@ -24,7 +24,10 @@ const scaleStatType = (config, result) => { | |||
result.amount *= statValue * statMult; | |||
}; | |||
const scalePercentMultipliers = ({ isAttack, elementName, srcValues }, result) => { | |||
const scalePercentMultipliers = ({ isAttack, elementName, srcValues, scaleConfig }, result) => { | |||
if (scaleConfig?.percentMult === false) | |||
return; | |||
const { dmgPercent = 0, physicalPercent = 0, spellPercent = 0 } = srcValues; | |||
let totalPercent = 100 + dmgPercent; | |||
@@ -40,8 +43,8 @@ const scalePercentMultipliers = ({ isAttack, elementName, srcValues }, result) = | |||
result.amount *= (totalPercent / 100); | |||
}; | |||
const scaleCrit = ({ noCrit, isAttack, crit: forceCrit, srcValues }, result) => { | |||
if (noCrit) | |||
const scaleCrit = ({ noCrit, isAttack, crit: forceCrit, srcValues, scaleConfig }, result) => { | |||
if (noCrit || scaleConfig?.critMult === false) | |||
return; | |||
const { critChance, attackCritChance, spellCritChance } = srcValues; | |||
@@ -1,7 +1,6 @@ | |||
const configThreatCeiling = { | |||
regular: 1, | |||
rare: 0.5, | |||
champion: 0.2 | |||
rare: 0.5 | |||
}; | |||
module.exports = { | |||
@@ -179,7 +178,7 @@ module.exports = { | |||
this.ignoreList.spliceWhere(o => o === obj); | |||
}, | |||
tryEngage: function (source, amount, threatMult) { | |||
tryEngage: function (source, amount, threatMult = 1) { | |||
let obj = this.obj; | |||
//Don't aggro yourself, stupid | |||
@@ -205,7 +204,7 @@ module.exports = { | |||
let list = this.list; | |||
amount = (amount || 0); | |||
let threat = (amount / obj.stats.values.hpMax) * (threatMult || 1); | |||
let threat = (amount / obj.stats.values.hpMax) * threatMult; | |||
let exists = list.find(l => l.obj.id === oId); | |||
if (!exists) { | |||
@@ -238,7 +237,7 @@ module.exports = { | |||
return null; | |||
}, | |||
die: function () { | |||
reset: function () { | |||
let list = this.list; | |||
let lLen = list.length; | |||
@@ -260,6 +259,10 @@ module.exports = { | |||
this.list = []; | |||
}, | |||
die: function () { | |||
this.reset(); | |||
}, | |||
unAggro: function (obj, amount) { | |||
let list = this.list; | |||
let lLen = list.length; | |||
@@ -407,5 +410,11 @@ module.exports = { | |||
isInCombat: function () { | |||
return this.list.length > 0; | |||
}, | |||
setAllAmounts: function (amount) { | |||
this.list.forEach(l => { | |||
l.amount = amount; | |||
}); | |||
} | |||
}; |
@@ -213,7 +213,7 @@ module.exports = { | |||
if (effect.silent) | |||
return; | |||
this.obj.syncer.setArray(true, 'effects', 'extendEffects', { | |||
this.obj.syncer.setArray(false, 'effects', 'extendEffects', { | |||
id, | |||
data | |||
}); | |||
@@ -4,7 +4,6 @@ module.exports = { | |||
type: 'equipment', | |||
eq: {}, | |||
doAutoEq: true, | |||
quickSlots: {}, | |||
@@ -32,24 +31,6 @@ module.exports = { | |||
return !this.eq.has(slot); | |||
}, | |||
autoEquip: function (itemId) { | |||
if (!this.doAutoEq) | |||
return; | |||
let item = this.obj.inventory.findItem(itemId); | |||
if (!item) | |||
return; | |||
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (!this.obj.inventory.canEquipItem(item))) { | |||
item.eq = false; | |||
return; | |||
} | |||
if (!this.eq.has(item.slot)) { | |||
this.equip({ itemId }); | |||
return true; | |||
} | |||
}, | |||
equip: function (itemId) { | |||
let slot = null; | |||
if (typeof (itemId) === 'object') { | |||
@@ -122,7 +103,8 @@ module.exports = { | |||
this.eq[slot] = itemId; | |||
item.equipSlot = slot; | |||
obj.spellbook.calcDps(); | |||
if (obj.spellbook) | |||
obj.spellbook.calcDps(); | |||
if ((!obj.mob) || (item.ability)) { | |||
if (item.spell) | |||
@@ -465,8 +465,10 @@ module.exports = { | |||
die: function () { | |||
this.obj.stats.takeDamage({ | |||
amount: 20000000 | |||
}, 1, this.obj); | |||
damage: { amount: 20000000 }, | |||
source: this.obj, | |||
target: this.obj | |||
}); | |||
}, | |||
setPassword: async function (config) { | |||
@@ -169,5 +169,15 @@ module.exports = { | |||
type: 'follower', | |||
master: this.master.id | |||
}; | |||
}, | |||
events: { | |||
afterDeath: function (deathEvent) { | |||
this.master.fireEvent('afterFollowerDeath', { | |||
deathEvent, | |||
follower: this.obj, | |||
master: this.master | |||
}); | |||
} | |||
} | |||
}; |
@@ -311,7 +311,7 @@ module.exports = { | |||
this.events.beforeMove.call(this); | |||
}, | |||
beforeTakeDamage: function () { | |||
beforeTakeDamage: function (damageEvent) { | |||
this.events.beforeMove.call(this); | |||
}, | |||
@@ -1,16 +1,21 @@ | |||
//System | |||
const events = require('../misc/events'); | |||
//External Helpers | |||
let generator = require('../items/generator'); | |||
let salvager = require('../items/salvager'); | |||
let classes = require('../config/spirits'); | |||
let factions = require('../config/factions'); | |||
let itemEffects = require('../items/itemEffects'); | |||
const events = require('../misc/events'); | |||
const { isItemStackable } = require('./inventory/helpers'); | |||
//Helpers | |||
const simplifyItem = require('./inventory/simplifyItem'); | |||
const getItem = require('./inventory/getItem'); | |||
const dropBag = require('./inventory/dropBag'); | |||
const useItem = require('./inventory/useItem'); | |||
const { isItemStackable } = require('./inventory/helpers'); | |||
//Component | |||
module.exports = { | |||
type: 'inventory', | |||
@@ -91,44 +96,7 @@ module.exports = { | |||
}, | |||
simplifyItem: function (item) { | |||
let result = extend({}, item); | |||
if (result.effects) { | |||
result.effects = result.effects.map(e => ({ | |||
factionId: e.factionId || null, | |||
text: e.text || null, | |||
properties: e.properties || null, | |||
type: e.type || null, | |||
rolls: e.rolls || null | |||
})); | |||
} | |||
let reputation = this.obj.reputation; | |||
if (result.factions) { | |||
result.factions = result.factions.map(function (f) { | |||
let res = { | |||
id: f.id, | |||
tier: f.tier, | |||
tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier] | |||
}; | |||
if (reputation) { | |||
let faction = reputation.getBlueprint(f.id); | |||
let factionTier = reputation.getTier(f.id); | |||
let noEquip = null; | |||
if (factionTier < f.tier) | |||
noEquip = true; | |||
res.name = faction.name; | |||
res.noEquip = noEquip; | |||
} | |||
return res; | |||
}, this); | |||
} | |||
return result; | |||
return simplifyItem(this, item); | |||
}, | |||
update: function () { | |||
@@ -403,13 +371,53 @@ module.exports = { | |||
try { | |||
let effectModule = require('../' + effectUrl); | |||
e.events = effectModule.events; | |||
if (effectModule.events.onGetText) | |||
const { rolls } = e; | |||
if (rolls.textTemplate) { | |||
let text = rolls.textTemplate; | |||
while (text.includes('((')) { | |||
Object.entries(rolls).forEach(([k, v]) => { | |||
text = text.replaceAll(`((${k}))`, v); | |||
}); | |||
if (rolls.applyEffect) { | |||
Object.entries(rolls.applyEffect).forEach(([k, v]) => { | |||
text = text.replaceAll(`((applyEffect.${k}))`, v); | |||
}); | |||
} | |||
if (rolls.castSpell) { | |||
Object.entries(rolls.castSpell).forEach(([k, v]) => { | |||
text = text.replaceAll(`((castSpell.${k}))`, v); | |||
}); | |||
} | |||
if (rolls.applyEffect?.scaleDamage) { | |||
Object.entries(rolls.applyEffect.scaleDamage).forEach(([k, v]) => { | |||
text = text.replaceAll(`((applyEffect.scaleDamage.${k}))`, v); | |||
}); | |||
} | |||
if (rolls.castSpell?.scaleDamage) { | |||
Object.entries(rolls.castSpell.scaleDamage).forEach(([k, v]) => { | |||
text = text.replaceAll(`((castSpell.scaleDamage.${k}))`, v); | |||
}); | |||
} | |||
} | |||
e.text = text; | |||
} else if (effectModule.events.onGetText) | |||
e.text = effectModule.events.onGetText(item, e); | |||
} catch (error) { | |||
_.log(`Effect not found: ${e.type}`); | |||
_.log(error); | |||
} | |||
} | |||
}); | |||
item.effects.spliceWhere(e => !e.events); | |||
} | |||
if (!item.has('pos') && !item.eq) { | |||
@@ -682,25 +690,21 @@ module.exports = { | |||
}, | |||
equipItemErrors: function (item) { | |||
let errors = []; | |||
const { obj: { player, stats: { values: statValues }, reputation } } = this; | |||
if (!this.obj.player) | |||
return []; | |||
const errors = []; | |||
let stats = this.obj.stats.values; | |||
if (!player) | |||
return errors; | |||
if (item.level > stats.level) | |||
if (item.level > statValues.level) | |||
errors.push('level'); | |||
if ((item.requires) && (stats[item.requires[0].stat] < item.requires[0].value)) | |||
if (item.requires && statValues[item.requires[0].stat] < item.requires[0].value) | |||
errors.push(item.requires[0].stat); | |||
if (item.factions) { | |||
if (item.factions.some(function (f) { | |||
return f.noEquip; | |||
})) | |||
errors.push('faction'); | |||
} | |||
if (item.factions?.some(f => reputation.getTier(f.id) < f.tier)) | |||
errors.push('faction'); | |||
return errors; | |||
}, | |||
@@ -1,13 +1,15 @@ | |||
let generator = require('../../items/generator'); | |||
module.exports = (cpnInv, ownerName, killSource) => { | |||
if (!cpnInv.blueprint) | |||
const { obj, blueprint } = cpnInv; | |||
if (!blueprint) | |||
return; | |||
const obj = cpnInv.obj; | |||
const { stats, instance: { objects, eventEmitter } } = obj; | |||
//Only drop loot if this player is in the zone | |||
let playerObject = obj.instance.objects.find(o => o.player && o.name === ownerName); | |||
let playerObject = objects.find(o => o.player && o.name === ownerName); | |||
if (!playerObject) | |||
return; | |||
@@ -18,7 +20,6 @@ module.exports = (cpnInv, ownerName, killSource) => { | |||
delete items[i].pos; | |||
} | |||
let blueprint = cpnInv.blueprint; | |||
let magicFind = (blueprint.magicFind || 0); | |||
let savedItems = extend([], cpnInv.items); | |||
@@ -44,7 +45,7 @@ module.exports = (cpnInv, ownerName, killSource) => { | |||
continue; | |||
let itemBlueprint = { | |||
level: obj.stats.values.level, | |||
level: stats.values.level, | |||
magicFind: magicFind, | |||
bonusMagicFind: bonusMagicFind, | |||
noCurrency: i > 0 | |||
@@ -64,7 +65,7 @@ module.exports = (cpnInv, ownerName, killSource) => { | |||
else if ((drop.chance) && (~~(Math.random() * 100) >= drop.chance * dropEvent.chanceMultiplier)) | |||
continue; | |||
drop.level = drop.level || obj.stats.values.level; | |||
drop.level = drop.level || stats.values.level; | |||
drop.magicFind = magicFind; | |||
let item = drop; | |||
@@ -80,8 +81,17 @@ module.exports = (cpnInv, ownerName, killSource) => { | |||
playerObject.fireEvent('beforeTargetDeath', obj, cpnInv.items); | |||
obj.instance.eventEmitter.emit('onBeforeDropBag', obj, cpnInv.items, killSource); | |||
//Deprecated | |||
eventEmitter.emit('onBeforeDropBag', obj, cpnInv.items, killSource); | |||
obj.fireEvent('onBeforeDropBag', cpnInv.items, killSource); | |||
//New | |||
const eventMsg = { | |||
objDropper: obj, | |||
objLooter: playerObject, | |||
objKillSource: killSource, | |||
items: cpnInv.items | |||
}; | |||
eventEmitter.emit('beforeDropBag', eventMsg); | |||
if (cpnInv.items.length > 0) | |||
cpnInv.createBag(obj.x, obj.y, cpnInv.items, ownerName); | |||
@@ -0,0 +1,37 @@ | |||
//Helpers | |||
const { getFactionBlueprint } = require('../../config/factions/helpers'); | |||
//Internals | |||
const tierNames = ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted']; | |||
//Method | |||
const simplifyItem = (cpnInventory, item) => { | |||
const result = extend({}, item); | |||
if (result.effects) { | |||
result.effects = result.effects.map(e => ({ | |||
factionId: e.factionId ?? null, | |||
text: e.text ?? null, | |||
properties: e.properties ?? null, | |||
type: e.type ?? null, | |||
rolls: e.rolls ?? null | |||
})); | |||
} | |||
if (result.factions) { | |||
result.factions = result.factions.map(f => { | |||
const res = { | |||
id: f.id, | |||
tier: f.tier, | |||
tierName: tierNames[f.tier], | |||
name: getFactionBlueprint(f.id).name | |||
}; | |||
return res; | |||
}); | |||
} | |||
return result; | |||
}; | |||
module.exports = simplifyItem; |
@@ -56,10 +56,19 @@ module.exports = async (cpnInv, itemId) => { | |||
success: true, | |||
cdMax: item.cdMax | |||
}; | |||
//Deprecated | |||
obj.instance.eventEmitter.emit('onBeforeUseItem', obj, item, result); | |||
obj.fireEvent('onBeforeUseItem', item, result); | |||
//New | |||
const eventMsg = { | |||
obj, | |||
item, | |||
cdMax: item.cdMax, | |||
success: true | |||
}; | |||
obj.instance.eventEmitter.emit('beforeUseItem', eventMsg); | |||
if (!result.success) | |||
if (!result.success || !eventMsg.success) | |||
return; | |||
placeItemOnCooldown(obj, cpnInv, item, result); | |||
@@ -126,19 +126,14 @@ module.exports = { | |||
let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | |||
if (!distanceFromHome) { | |||
this.goHome = false; | |||
if (!obj.spellbook) { | |||
/* eslint-disable-next-line no-console */ | |||
console.log('MOB HAS NO SPELLBOOK BUT WANTS TO RESET ROTATION'); | |||
/* eslint-disable-next-line no-console */ | |||
console.log(obj.name, obj.zone, obj.zoneName, obj.x, obj.y, obj.components.map(c => c.type).join(',')); | |||
} | |||
obj.spellbook.resetRotation(); | |||
if (obj.spellbook) | |||
obj.spellbook.resetRotation(); | |||
} | |||
} | |||
if (!this.goHome) { | |||
//Are we too far from home? | |||
//Are we chasing a target too far from home? | |||
if (!obj.follower && target) { | |||
if (!this.canChase(target)) { | |||
obj.clearQueue(); | |||
@@ -147,7 +142,11 @@ module.exports = { | |||
} | |||
} | |||
if ((target) && (target !== obj) && ((!obj.follower) || (obj.follower.master !== target))) { | |||
//Are we too far from home? | |||
let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y)); | |||
if (distanceFromHome > this.maxChaseDistance || (distanceFromHome > this.walkDistance && !target && !this.patrol)) | |||
this.goHome = true; | |||
else if (target && target !== obj && (!obj.follower || obj.follower.master !== target)) { | |||
//If we just started attacking, patrols need to know where home is | |||
if (!this.target && this.patrol) { | |||
this.originX = obj.x; | |||
@@ -361,9 +360,9 @@ module.exports = { | |||
}, | |||
events: { | |||
beforeTakeDamage: function (msg) { | |||
beforeTakeDamage: function ({ damage }) { | |||
if (this.goHome) | |||
msg.failed = true; | |||
damage.failed = true; | |||
} | |||
} | |||
}; |
@@ -1,6 +1,7 @@ | |||
let factionBase = require('../config/factionBase'); | |||
let factions = require('../config/factions'); | |||
//Helpers | |||
const { getFactionBlueprint } = require('../config/factions/helpers'); | |||
//Component | |||
module.exports = { | |||
type: 'reputation', | |||
@@ -13,7 +14,7 @@ module.exports = { | |||
delete blueprint.list; | |||
list.forEach(function (l) { | |||
let bpt = this.getBlueprint(l.id); | |||
let bpt = getFactionBlueprint(l.id); | |||
if (!bpt) | |||
return; | |||
@@ -27,25 +28,6 @@ module.exports = { | |||
}, this); | |||
}, | |||
getBlueprint: function (factionId) { | |||
if (this.factions[factionId]) | |||
return this.factions[factionId]; | |||
let factionBlueprint = null; | |||
try { | |||
factionBlueprint = factions.getFaction(factionId); | |||
} catch (e) {} | |||
if (!factionBlueprint) | |||
return; | |||
factionBlueprint = extend({}, factionBase, factionBlueprint); | |||
this.factions[factionBlueprint.id] = factionBlueprint; | |||
return factionBlueprint; | |||
}, | |||
getTier: function (factionId) { | |||
let faction = this.list.find(l => l.id === factionId); | |||
if (!faction) { | |||
@@ -53,9 +35,7 @@ module.exports = { | |||
faction = this.list.find(l => l.id === factionId); | |||
} | |||
return (faction || { | |||
tier: 3 | |||
}).tier; | |||
return faction?.tier ?? 3; | |||
}, | |||
canEquipItem: function (item) { | |||
@@ -71,7 +51,7 @@ module.exports = { | |||
}, | |||
calculateTier: function (factionId) { | |||
let blueprint = this.getBlueprint(factionId); | |||
let blueprint = getFactionBlueprint(factionId); | |||
let faction = this.list.find(l => l.id === factionId); | |||
let rep = faction.rep; | |||
@@ -99,7 +79,7 @@ module.exports = { | |||
getReputation: function (factionId, gain) { | |||
let fullSync = false; | |||
let blueprint = this.getBlueprint(factionId); | |||
let blueprint = getFactionBlueprint(factionId); | |||
let faction = this.list.find(l => l.id === factionId); | |||
if (!faction) { | |||
@@ -147,7 +127,7 @@ module.exports = { | |||
if (this.list.some(l => l.id === factionId)) | |||
return; | |||
let blueprint = this.getBlueprint(factionId); | |||
let blueprint = getFactionBlueprint(factionId); | |||
if (!blueprint) | |||
return; | |||
@@ -185,7 +165,7 @@ module.exports = { | |||
let sendList = this.list | |||
.map(function (l) { | |||
let result = {}; | |||
let blueprint = this.getBlueprint(l.id); | |||
let blueprint = getFactionBlueprint(l.id); | |||
extend(result, l, blueprint); | |||
return result; | |||
@@ -206,7 +186,7 @@ module.exports = { | |||
}; | |||
if (full) { | |||
let blueprint = this.getBlueprint(factionId); | |||
let blueprint = getFactionBlueprint(factionId); | |||
extend(faction, l, blueprint); | |||
} | |||
@@ -276,7 +276,13 @@ module.exports = { | |||
//Sends a notification to yourself | |||
// arg1 = { message, className, type } | |||
notifySelf: function ({ message, className = 'color-redA', type = 'info', subType }) { | |||
const { obj: { id, serverId, instance: { syncer } } } = this; | |||
const { obj: { id, serverId, instance } } = this; | |||
//Maybe we are in the main thread | |||
if (!instance) | |||
return; | |||
const { syncer } = instance; | |||
syncer.queue('onGetMessages', { | |||
id, | |||
@@ -1,5 +1,9 @@ | |||
//Imports | |||
const { messageAllThreads } = require('../../world/threadManager'); | |||
//Exports | |||
module.exports = async (cpnSocial, eventName) => { | |||
atlas.messageAllThreads({ | |||
messageAllThreads({ | |||
threadModule: 'eventManager', | |||
method: 'startEventByCode', | |||
data: eventName | |||
@@ -1,5 +1,9 @@ | |||
//Imports | |||
const { messageAllThreads } = require('../../world/threadManager'); | |||
//Exports | |||
module.exports = async (cpnSocial, eventName) => { | |||
atlas.messageAllThreads({ | |||
messageAllThreads({ | |||
threadModule: 'eventManager', | |||
method: 'stopEventByCode', | |||
data: eventName | |||
@@ -1,13 +1,14 @@ | |||
let spellTemplate = require('../config/spells/spellTemplate'); | |||
let animations = require('../config/animations'); | |||
let playerSpells = require('../config/spells'); | |||
let playerSpellsConfig = require('../config/spellsConfig'); | |||
//Imports | |||
const spellTemplate = require('../config/spells/spellTemplate'); | |||
const animations = require('../config/animations'); | |||
const playerSpells = require('../config/spells'); | |||
const playerSpellsConfig = require('../config/spellsConfig'); | |||
//Helpers | |||
const rotationManager = require('./spellbook/rotationManager'); | |||
const cast = require('./spellbook/cast'); | |||
//Component | |||
module.exports = { | |||
type: 'spellbook', | |||
@@ -27,8 +28,6 @@ module.exports = { | |||
this.objects = this.obj.instance.objects; | |||
this.physics = this.obj.instance.physics; | |||
this.dmgMult = blueprint.dmgMult; | |||
(blueprint.spells || []).forEach(s => this.addSpell(s, -1)); | |||
if (blueprint.rotation) { | |||
@@ -340,119 +339,7 @@ module.exports = { | |||
}, | |||
cast: function (action, isAuto) { | |||
if (!action.has('spell')) { | |||
const isCasting = this.isCasting(); | |||
this.stopCasting(); | |||
const consumeTick = isCasting; | |||
return consumeTick; | |||
} | |||
let spell = this.spells.find(s => (s.id === action.spell)); | |||
if (!spell) | |||
return false; | |||
action.target = this.getTarget(spell, action); | |||
//If a target has become nonSelectable, we need to stop attacks that are queued/auto | |||
if (!action.target || action.target.nonSelectable) | |||
return false; | |||
action.auto = spell.auto; | |||
let success = true; | |||
if (spell.cd > 0) { | |||
if (!isAuto) { | |||
let type = (spell.auto) ? 'Weapon' : 'Spell'; | |||
this.sendAnnouncement(`${type} is on cooldown`); | |||
} | |||
success = false; | |||
} else if (spell.manaCost > this.obj.stats.values.mana) { | |||
if (!isAuto) | |||
this.sendAnnouncement('Insufficient mana to cast spell'); | |||
success = false; | |||
} else if (spell.has('range')) { | |||
let distance = Math.max(Math.abs(action.target.x - this.obj.x), Math.abs(action.target.y - this.obj.y)); | |||
let range = spell.range; | |||
if ((spell.useWeaponRange) && (this.obj.player)) { | |||
let weapon = this.obj.inventory.findItem(this.obj.equipment.eq.oneHanded) || this.obj.inventory.findItem(this.obj.equipment.eq.twoHanded); | |||
if (weapon) | |||
range = weapon.range || 1; | |||
} | |||
if (distance > range) { | |||
if (!isAuto) | |||
this.sendAnnouncement('Target out of range'); | |||
success = false; | |||
} | |||
} | |||
//LoS check | |||
//Null means we don't have LoS and as such, we should move | |||
if (spell.needLos && success) { | |||
if (!this.physics.hasLos(~~this.obj.x, ~~this.obj.y, ~~action.target.x, ~~action.target.y)) { | |||
if (!isAuto) | |||
this.sendAnnouncement('Target not in line of sight'); | |||
action.auto = false; | |||
success = null; | |||
} | |||
} | |||
if (!success) { | |||
this.queueAuto(action, spell); | |||
return success; | |||
} else if (!this.queueAuto(action, spell)) | |||
return false; | |||
let castSuccess = { | |||
success: true | |||
}; | |||
this.obj.fireEvent('beforeCastSpell', castSuccess); | |||
if (!castSuccess.success) | |||
return false; | |||
if (spell.manaReserve) { | |||
let reserve = spell.manaReserve; | |||
if (reserve.percentage) { | |||
let reserveEvent = { | |||
spell: spell.name, | |||
reservePercent: reserve.percentage | |||
}; | |||
this.obj.fireEvent('onBeforeReserveMana', reserveEvent); | |||
if (!spell.active) { | |||
if (1 - this.obj.stats.values.manaReservePercent < reserve.percentage) { | |||
this.sendAnnouncement('Insufficient mana to cast spell'); | |||
return; | |||
} this.obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent); | |||
} else | |||
this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent); | |||
} | |||
} | |||
if (spell.targetFurthest) | |||
spell.target = this.obj.aggro.getFurthest(); | |||
else if (spell.targetRandom) | |||
spell.target = this.obj.aggro.getRandom(); | |||
success = spell.castBase(action); | |||
this.stopCasting(spell, true); | |||
if (success) { | |||
spell.consumeMana(); | |||
spell.setCd(); | |||
} | |||
this.obj.fireEvent('afterCastSpell', { | |||
castSuccess: success, | |||
spell, | |||
action | |||
}); | |||
//Null means we didn't fail but are initiating casting | |||
return (success === null || success === true); | |||
return cast(this, action, isAuto); | |||
}, | |||
getClosestRange: function (spellNum) { | |||
@@ -0,0 +1,131 @@ | |||
/* eslint-disable-next-line max-lines-per-function */ | |||
const cast = (cpnSpellbook, action, isAuto) => { | |||
const { obj, physics, spells } = cpnSpellbook; | |||
//Stop casting | |||
if (!action.has('spell')) { | |||
const wasCasting = cpnSpellbook.isCasting(); | |||
cpnSpellbook.stopCasting(); | |||
//Consume a tick if we were casting | |||
return wasCasting; | |||
} | |||
const spell = spells.find(s => (s.id === action.spell)); | |||
if (!spell) | |||
return false; | |||
action.target = cpnSpellbook.getTarget(spell, action); | |||
action.auto = spell.auto; | |||
//If a target has become nonSelectable, we need to stop attacks that are queued/auto | |||
if (!action.target || action.target.nonSelectable) | |||
return false; | |||
let success = true; | |||
if (spell.cd > 0) { | |||
if (!isAuto) { | |||
const type = (spell.auto) ? 'Weapon' : 'Spell'; | |||
cpnSpellbook.sendAnnouncement(`${type} is on cooldown`); | |||
} | |||
success = false; | |||
} else if (spell.manaCost > obj.stats.values.mana) { | |||
if (!isAuto) | |||
cpnSpellbook.sendAnnouncement('Insufficient mana to cast spell'); | |||
success = false; | |||
} else if (spell.has('range')) { | |||
const distance = Math.max(Math.abs(action.target.x - obj.x), Math.abs(action.target.y - obj.y)); | |||
let range = spell.range; | |||
if ((spell.useWeaponRange) && (obj.player)) { | |||
const weapon = obj.inventory.findItem(obj.equipment.eq.oneHanded) || obj.inventory.findItem(obj.equipment.eq.twoHanded); | |||
if (weapon) | |||
range = weapon.range || 1; | |||
} | |||
if (distance > range) { | |||
if (!isAuto) | |||
cpnSpellbook.sendAnnouncement('Target out of range'); | |||
success = false; | |||
} | |||
} | |||
//LoS check | |||
//Null means we don't have LoS and as such, we should move | |||
if (spell.needLos && success) { | |||
if (!physics.hasLos(~~obj.x, ~~obj.y, ~~action.target.x, ~~action.target.y)) { | |||
if (!isAuto) | |||
cpnSpellbook.sendAnnouncement('Target not in line of sight'); | |||
action.auto = false; | |||
success = null; | |||
} | |||
} | |||
if (!success) { | |||
cpnSpellbook.queueAuto(action, spell); | |||
return success; | |||
} else if (!cpnSpellbook.queueAuto(action, spell)) | |||
return false; | |||
const eventBeforeCastSpell = { | |||
success: true, | |||
action | |||
}; | |||
obj.fireEvent('beforeCastSpell', eventBeforeCastSpell); | |||
if (!eventBeforeCastSpell.success) | |||
return false; | |||
if (spell.manaReserve) { | |||
const reserve = spell.manaReserve; | |||
if (reserve.percentage) { | |||
const reserveEvent = { | |||
spell: spell.name, | |||
reservePercent: reserve.percentage | |||
}; | |||
obj.fireEvent('onBeforeReserveMana', reserveEvent); | |||
if (!spell.active) { | |||
if (1 - obj.stats.values.manaReservePercent < reserve.percentage) { | |||
cpnSpellbook.sendAnnouncement('Insufficient mana to cast spell'); | |||
return; | |||
} obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent); | |||
} else | |||
obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent); | |||
} | |||
} | |||
if (spell.targetFurthest) | |||
spell.target = obj.aggro.getFurthest(); | |||
else if (spell.targetRandom) | |||
spell.target = obj.aggro.getRandom(); | |||
if (!!eventBeforeCastSpell.action.target?.effects) { | |||
const eventBeforeIsSpellTarget = { | |||
source: obj, | |||
spell, | |||
target: eventBeforeCastSpell.action.target | |||
}; | |||
eventBeforeIsSpellTarget.target.fireEvent('beforeIsSpellTarget', eventBeforeIsSpellTarget); | |||
eventBeforeCastSpell.action.target = eventBeforeIsSpellTarget.target; | |||
} | |||
success = spell.castBase(eventBeforeCastSpell.action); | |||
cpnSpellbook.stopCasting(spell, true); | |||
if (success) { | |||
spell.consumeMana(); | |||
spell.setCd(); | |||
} | |||
obj.fireEvent('afterCastSpell', { | |||
castSuccess: success, | |||
spell, | |||
action: eventBeforeCastSpell.action | |||
}); | |||
//Null means we didn't fail but are initiating casting | |||
return (success === null || success === true); | |||
}; | |||
module.exports = cast; | |||
@@ -1,10 +1,13 @@ | |||
//Imports | |||
const animations = require('../config/animations'); | |||
const spirits = require('../config/spirits'); | |||
const scheduler = require('../misc/scheduler'); | |||
//Methods | |||
const die = require('./stats/die'); | |||
const takeDamage = require('./stats/takeDamage'); | |||
let animations = require('../config/animations'); | |||
let spirits = require('../config/spirits'); | |||
let scheduler = require('../misc/scheduler'); | |||
//Internals | |||
let baseStats = { | |||
mana: 20, | |||
manaMax: 20, | |||
@@ -82,6 +85,7 @@ let baseStats = { | |||
fishItems: 0 | |||
}; | |||
//Exports | |||
module.exports = { | |||
type: 'stats', | |||
@@ -348,8 +352,6 @@ module.exports = { | |||
let mobDiffMult = 1; | |||
if (target.isRare) | |||
mobDiffMult = 2; | |||
else if (target.isChampion) | |||
mobDiffMult = 5; | |||
//Who should get xp? | |||
let aggroList = target.aggro.list; | |||
@@ -520,85 +522,23 @@ module.exports = { | |||
this.addStat(s, gainStats[s] * count); | |||
}, | |||
takeDamage: function (damage, threatMult, source) { | |||
if (this.values.hp <= 0) | |||
return; | |||
let obj = this.obj; | |||
if (!damage.noEvents) { | |||
source.fireEvent('beforeDealDamage', damage, obj); | |||
obj.fireEvent('beforeTakeDamage', damage, source); | |||
} | |||
if (damage.failed || obj.destroyed) | |||
return; | |||
let amount = Math.min(this.values.hp, damage.amount); | |||
damage.dealt = amount; | |||
let msg = { | |||
id: obj.id, | |||
source: source.id, | |||
crit: damage.crit, | |||
amount: amount, | |||
element: damage.element | |||
}; | |||
this.values.hp -= amount; | |||
let recipients = []; | |||
if (obj.serverId) | |||
recipients.push(obj.serverId); | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (source.follower && source.follower.master.serverId) { | |||
recipients.push(source.follower.master.serverId); | |||
msg.masterSource = source.follower.master.id; | |||
} | |||
if (obj.follower && obj.follower.master.serverId) { | |||
recipients.push(obj.follower.master.serverId); | |||
msg.masterId = obj.follower.master.id; | |||
} | |||
if (recipients.length) { | |||
if (!damage.blocked && !damage.dodged) | |||
this.syncer.queue('onGetDamage', msg, recipients); | |||
else { | |||
this.syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
source: source.id, | |||
event: true, | |||
text: damage.blocked ? 'blocked' : 'dodged' | |||
}, recipients); | |||
} | |||
} | |||
obj.aggro.tryEngage(source, amount, threatMult); | |||
let died = (this.values.hp <= 0); | |||
if (died) { | |||
let death = { | |||
success: true | |||
}; | |||
obj.instance.eventEmitter.emit('onBeforeActorDies', death, obj, source); | |||
obj.fireEvent('beforeDeath', death); | |||
if (death.success) | |||
this.preDeath(source); | |||
} else { | |||
source.aggro.tryEngage(obj, 0); | |||
obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp); | |||
} | |||
if (!damage.noEvents) | |||
source.fireEvent('afterDealDamage', damage, obj); | |||
takeDamage: function (eventDamage) { | |||
takeDamage(this, eventDamage); | |||
}, | |||
getHp: function (heal, source) { | |||
/* | |||
Gives hp to heal.target | |||
heal: Damage object returned by combat.getDamage | |||
source: Source object | |||
event: Optional config object. We want to eventually phase out the first 2 args. | |||
heal: Same as 1st parameter | |||
source: Same as 2nd parameter | |||
target: Target object (heal.target) | |||
spell: Optional spell object that caused this event | |||
*/ | |||
getHp: function (event) { | |||
const { heal, source } = event; | |||
let amount = heal.amount; | |||
if (amount === 0) | |||
return; | |||
@@ -610,44 +550,46 @@ module.exports = { | |||
let values = this.values; | |||
let hpMax = values.hpMax; | |||
if (values.hp >= hpMax) | |||
return; | |||
if (hpMax - values.hp < amount) | |||
amount = hpMax - values.hp; | |||
if (values.hp < hpMax) { | |||
if (hpMax - values.hp < amount) | |||
amount = hpMax - values.hp; | |||
values.hp += amount; | |||
if (values.hp > hpMax) | |||
values.hp = hpMax; | |||
values.hp += amount; | |||
if (values.hp > hpMax) | |||
values.hp = hpMax; | |||
let recipients = []; | |||
if (this.obj.serverId) | |||
recipients.push(this.obj.serverId); | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (recipients.length > 0) { | |||
this.syncer.queue('onGetDamage', { | |||
id: this.obj.id, | |||
source: source.id, | |||
heal: true, | |||
amount: amount, | |||
crit: heal.crit, | |||
element: heal.element | |||
}, recipients); | |||
} | |||
let recipients = []; | |||
if (this.obj.serverId) | |||
recipients.push(this.obj.serverId); | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (recipients.length > 0) { | |||
this.syncer.queue('onGetDamage', { | |||
id: this.obj.id, | |||
source: source.id, | |||
heal: true, | |||
amount: amount, | |||
crit: heal.crit, | |||
element: heal.element | |||
}, recipients); | |||
} | |||
//Add aggro to all our attackers | |||
let threat = amount * 0.4 * threatMult; | |||
if (threat !== 0) { | |||
let aggroList = this.obj.aggro.list; | |||
let aLen = aggroList.length; | |||
for (let i = 0; i < aLen; i++) { | |||
let a = aggroList[i].obj; | |||
a.aggro.tryEngage(source, threat); | |||
//Add aggro to all our attackers | |||
let threat = amount * 0.4 * threatMult; | |||
if (threat !== 0) { | |||
let aggroList = this.obj.aggro.list; | |||
let aLen = aggroList.length; | |||
for (let i = 0; i < aLen; i++) { | |||
let a = aggroList[i].obj; | |||
a.aggro.tryEngage(source, threat); | |||
} | |||
} | |||
this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp); | |||
} | |||
this.obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp); | |||
if (!heal.noEvents) | |||
source.fireEvent('afterGiveHp', event); | |||
}, | |||
save: function () { | |||
@@ -778,8 +720,8 @@ module.exports = { | |||
} | |||
}, | |||
afterDealDamage: function (damageEvent, target) { | |||
if (damageEvent.element) | |||
afterDealDamage: function ({ damage, target }) { | |||
if (damage.element) | |||
return; | |||
const { obj, values: { lifeOnHit } } = this; | |||
@@ -787,7 +729,11 @@ module.exports = { | |||
if (target === obj || !lifeOnHit) | |||
return; | |||
this.getHp({ amount: lifeOnHit }, obj); | |||
this.getHp({ | |||
heal: { amount: lifeOnHit }, | |||
source: obj, | |||
target: obj | |||
}); | |||
} | |||
} | |||
}; |
@@ -0,0 +1,84 @@ | |||
const takeDamage = (cpnStats, eventDamage) => { | |||
const { obj, values, syncer } = cpnStats; | |||
if (values.hp <= 0) | |||
return; | |||
if (!eventDamage.noEvents) { | |||
eventDamage.source.fireEvent('beforeDealDamage', eventDamage); | |||
obj.fireEvent('beforeTakeDamage', eventDamage); | |||
} | |||
const { source, damage, threatMult = 1 } = eventDamage; | |||
const { noEvents, failed, blocked, dodged, crit, element } = damage; | |||
if (failed || obj.destroyed) | |||
return; | |||
const amount = Math.min(values.hp, damage.amount); | |||
damage.dealt = amount; | |||
const msg = { | |||
id: obj.id, | |||
source: source.id, | |||
crit, | |||
amount, | |||
element | |||
}; | |||
values.hp -= amount; | |||
const recipients = []; | |||
if (obj.serverId) | |||
recipients.push(obj.serverId); | |||
if (source.serverId) | |||
recipients.push(source.serverId); | |||
if (source.follower && source.follower.master.serverId) { | |||
recipients.push(source.follower.master.serverId); | |||
msg.masterSource = source.follower.master.id; | |||
} | |||
if (obj.follower && obj.follower.master.serverId) { | |||
recipients.push(obj.follower.master.serverId); | |||
msg.masterId = obj.follower.master.id; | |||
} | |||
if (recipients.length) { | |||
if (!blocked && !dodged) | |||
syncer.queue('onGetDamage', msg, recipients); | |||
else { | |||
syncer.queue('onGetDamage', { | |||
id: obj.id, | |||
source: source.id, | |||
event: true, | |||
text: blocked ? 'blocked' : 'dodged' | |||
}, recipients); | |||
} | |||
} | |||
obj.aggro.tryEngage(source, amount, threatMult); | |||
let died = (values.hp <= 0); | |||
if (died) { | |||
let death = { | |||
success: true | |||
}; | |||
obj.instance.eventEmitter.emit('onBeforeActorDies', death, obj, source); | |||
obj.fireEvent('beforeDeath', death); | |||
if (death.success) | |||
cpnStats.preDeath(source); | |||
} else { | |||
source.aggro.tryEngage(obj, 0); | |||
obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp); | |||
} | |||
if (!noEvents) { | |||
source.fireEvent('afterDealDamage', eventDamage); | |||
obj.fireEvent('afterTakeDamage', eventDamage); | |||
} | |||
}; | |||
module.exports = takeDamage; |
@@ -5,6 +5,12 @@ module.exports = { | |||
//The maximum level a player can reach | |||
maxLevel: 20, | |||
//Rune damage is multiplied by nth entry from this array where n = level - 1 | |||
dmgMults: [0.25, 0.4, 0.575, 0.8, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2.1, 2.2, 2.3, 2.4, 2.5], | |||
//Mob HP is multiplied by nth entry from this array where n = level - 1 | |||
hpMults: [0.1, 0.2, 0.4, 0.7, 0.78, 0.91, 1.16, 1.19, 1.65, 2.36, 3.07, 3.55, 4.1, 4.85, 5.6, 5.9, 6.5, 7.1, 7.9, 12], | |||
//How far a player can see objects horizontally | |||
viewDistanceX: 25, | |||
@@ -1,3 +0,0 @@ | |||
module.exports = { | |||
type: 'champion' | |||
}; |
@@ -71,7 +71,7 @@ module.exports = { | |||
targetPos.y = obj.y; | |||
}, | |||
beforeDealDamage: function (damage) { | |||
beforeDealDamage: function ({ damage }) { | |||
if (damage) | |||
damage.failed = true; | |||
}, | |||
@@ -4,9 +4,13 @@ module.exports = { | |||
persist: true, | |||
events: { | |||
afterDealDamage: function (damage, target) { | |||
afterDealDamage: function ({ damage, target }) { | |||
damage.dealt *= 0.5; | |||
this.obj.stats.getHp(damage, this.obj); | |||
this.obj.stats.getHp({ | |||
heal: damage, | |||
source: this.obj, | |||
target: this.obj | |||
}); | |||
} | |||
} | |||
}; |
@@ -2,12 +2,12 @@ module.exports = { | |||
type: 'invulnerability', | |||
events: { | |||
beforeDealDamage: function (damage) { | |||
beforeDealDamage: function ({ damage }) { | |||
if (damage) | |||
damage.amount = 0; | |||
}, | |||
beforeTakeDamage: function (damage, source) { | |||
beforeTakeDamage: function ({ damage }) { | |||
damage.amount = 0; | |||
} | |||
} | |||
@@ -3,23 +3,39 @@ let combat = require('../../combat/combat'); | |||
module.exports = { | |||
type: 'lifeDrain', | |||
amount: 0, | |||
scaleDamage: { | |||
isAttack: false, | |||
damage: 1, | |||
element: undefined, | |||
noScale: false, | |||
noMitigate: false, | |||
noCrit: false | |||
}, | |||
events: { | |||
afterTick: function () { | |||
let newDamage = combat.getDamage({ | |||
source: { | |||
stats: { | |||
values: {} | |||
} | |||
}, | |||
isAttack: false, | |||
const { isAttack, damage, element, noScale, noMitigate, noCrit } = this.scaleDamage; | |||
const damageEvent = combat.getDamage({ | |||
source: this.caster, | |||
target: this.obj, | |||
damage: this.amount, | |||
element: this.element, | |||
noScale: this.noScale, | |||
noCrit: true | |||
isAttack, | |||
damage, | |||
element, | |||
noScale, | |||
noMitigate, | |||
noCrit | |||
}); | |||
this.obj.stats.takeDamage(newDamage, 1, this.caster); | |||
this.obj.stats.takeDamage({ | |||
damage: damageEvent, | |||
threatMult: 1, | |||
source: this.caster, | |||
target: this.obj, | |||
effectName: 'lifeDrain' | |||
}); | |||
} | |||
} | |||
}; |
@@ -2,9 +2,16 @@ module.exports = { | |||
type: 'reflectDamage', | |||
events: { | |||
beforeTakeDamage: function (damage, source) { | |||
beforeTakeDamage: function ({ damage, source }) { | |||
damage.amount *= 0.5; | |||
source.stats.takeDamage(damage, this.threatMult, this.obj); | |||
source.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: this.obj, | |||
target: source, | |||
effectName: 'reflectDamage' | |||
}); | |||
damage.failed = true; | |||
@@ -11,7 +11,7 @@ module.exports = { | |||
targetPos.success = false; | |||
}, | |||
beforeDealDamage: function (damage) { | |||
beforeDealDamage: function ({ damage }) { | |||
if (damage) | |||
damage.failed = true; | |||
}, | |||
@@ -47,7 +47,7 @@ module.exports = { | |||
}, | |||
events: { | |||
beforeDealDamage: function (item, damage, target) { | |||
beforeDealDamage: function (item, { damage, target }) { | |||
if (!damage.crit) | |||
return; | |||
@@ -69,7 +69,13 @@ module.exports = { | |||
noCrit: true | |||
}); | |||
boundTarget.stats.takeDamage(damageConfig, 1, this); | |||
boundTarget.stats.takeDamage({ | |||
damage: damageConfig, | |||
threatMult: 1, | |||
source: this, | |||
target: boundTarget, | |||
effectName: 'akareiZap' | |||
}); | |||
}; | |||
this.instance.syncer.queue('onGetObject', { | |||
@@ -0,0 +1,30 @@ | |||
//Imports | |||
let factionBase = require('../factionBase'); | |||
let factions = require('../factions'); | |||
//Internals | |||
const cache = {}; | |||
//Method | |||
const getFactionBlueprint = factionId => { | |||
if (cache[factionId]) | |||
return cache[factionId]; | |||
let res = null; | |||
try { | |||
res = factions.getFaction(factionId); | |||
} catch (e) {} | |||
if (!res) | |||
return; | |||
res = extend({}, factionBase, res); | |||
cache[factionId] = res; | |||
return res; | |||
}; | |||
module.exports = { | |||
getFactionBlueprint | |||
}; |
@@ -1,37 +1,118 @@ | |||
//Imports | |||
const spellBaseTemplate = require('../spells/spellTemplate'); | |||
//Helpers | |||
const getItemEffect = item => { | |||
return item.effects.find(e => (e.type === 'castSpellOnHit')); | |||
}; | |||
const doesEventMatch = (firedEvent, eventCondition) => { | |||
if ( | |||
!firedEvent || | |||
( | |||
eventCondition.targetNotSelf === false && | |||
firedEvent.target === firedEvent.source | |||
) | |||
) | |||
return false; | |||
const foundNonMatch = Object.entries(eventCondition).some(([k, v]) => { | |||
if (v !== null && typeof(v) === 'object') { | |||
if (!doesEventMatch(firedEvent[k], v)) | |||
return true; | |||
return false; | |||
} | |||
return firedEvent[k] !== v; | |||
}); | |||
return !foundNonMatch; | |||
}; | |||
const shouldApplyEffect = (itemEffect, firedEvent, firedEventName) => { | |||
const { rolls: { chance, combatEvent: { [firedEventName]: eventCondition } } } = itemEffect; | |||
if (!eventCondition || !doesEventMatch(firedEvent, eventCondition)) | |||
return false; | |||
if (chance !== undefined && Math.random() * 100 >= chance) | |||
return false; | |||
return true; | |||
}; | |||
const handler = (obj, item, event, firedEventName) => { | |||
const itemEffect = getItemEffect(item); | |||
if (!shouldApplyEffect(itemEffect, event, firedEventName)) | |||
return; | |||
const { rolls: { castSpell, castTarget } } = itemEffect; | |||
const spellConfig = extend({}, castSpell); | |||
spellConfig.noEvents = true; | |||
const scaleDamage = spellConfig.scaleDamage; | |||
delete spellConfig.scaleDamage; | |||
if (scaleDamage) { | |||
if (scaleDamage.useOriginal) { | |||
scaleDamage.useOriginal.forEach(s => { | |||
spellConfig[s] = event.spell[s]; | |||
}); | |||
} | |||
if (scaleDamage.percentage) | |||
spellConfig.damage *= (scaleDamage.percentage / 100); | |||
} | |||
const typeTemplate = { | |||
type: spellConfig.type[0].toUpperCase() + spellConfig.type.substr(1), | |||
template: null | |||
}; | |||
obj.instance.eventEmitter.emit('onBeforeGetSpellTemplate', typeTemplate); | |||
if (!typeTemplate.template) | |||
typeTemplate.template = require('../spells/spell' + typeTemplate.type); | |||
const builtSpell = extend({ obj }, spellBaseTemplate, typeTemplate.template, spellConfig); | |||
let target = event.target; | |||
if (castTarget === 'self') | |||
target = obj; | |||
else if (castTarget === 'none') | |||
target = undefined; | |||
//Need to write a generic way to apply these | |||
else if (castTarget === '{{event.oldPos}}') | |||
target = extend({}, event.oldPos); | |||
else if (JSON.stringify(castTarget) === '{"x":"{{event.follower.x}}","y":"{{event.follower.y}}"}') { | |||
target = { | |||
x: event.follower.x, | |||
y: event.follower.y | |||
}; | |||
} | |||
builtSpell.cast({ target }); | |||
}; | |||
//Effect | |||
module.exports = { | |||
events: { | |||
onGetText: function (item) { | |||
const { rolls: { chance, spell, damage = 1 } } = item.effects.find(e => (e.type === 'castSpellOnHit')); | |||
afterGiveHp: function (item, event) { | |||
handler(this, item, event, 'afterGiveHp'); | |||
}, | |||
return `${chance}% chance to cast a ${damage} damage ${spell} on hit`; | |||
afterDealDamage: function (item, event) { | |||
handler(this, item, event, 'afterDealDamage'); | |||
}, | |||
afterDealDamage: function (item, damage, target) { | |||
//Should only proc for attacks...this is kind of a hack | |||
const { element } = damage; | |||
if (element) | |||
return; | |||
const { rolls: { chance, spell, statType = 'dex', damage: spellDamage = 1 } } = item.effects.find(e => (e.type === 'castSpellOnHit')); | |||
const chanceRoll = Math.random() * 100; | |||
if (chanceRoll >= chance) | |||
return; | |||
const spellName = 'spell' + spell.replace(/./, spell.toUpperCase()[0]); | |||
const spellTemplate = require(`../spells/${spellName}`); | |||
const builtSpell = extend({ obj: this }, spellBaseTemplate, spellTemplate, { | |||
name: spellName, | |||
noEvents: true, | |||
statType, | |||
damage: spellDamage, | |||
duration: 5, | |||
radius: 1 | |||
}); | |||
afterPositionChange: function (item, event) { | |||
handler(this, item, event, 'afterPositionChange'); | |||
}, | |||
builtSpell.cast(); | |||
afterFollowerDeath: function (item, event) { | |||
handler(this, item, event, 'afterFollowerDeath'); | |||
} | |||
} | |||
}; |
@@ -10,7 +10,7 @@ module.exports = { | |||
return `you take ${rolls.percentage}% of the damage you deal`; | |||
}, | |||
afterDealDamage: function (item, damage, target) { | |||
afterDealDamage: function (item, { damage, target }) { | |||
let effect = item.effects.find(e => (e.type === 'damageSelf')); | |||
let rolls = effect.rolls; | |||
@@ -29,9 +29,14 @@ module.exports = { | |||
noCrit: true | |||
}); | |||
newDamage.noEvents = true; | |||
this.stats.takeDamage(newDamage, 1, this); | |||
this.stats.takeDamage({ | |||
damage: newDamage, | |||
threatMult: 1, | |||
source: this, | |||
target: this, | |||
effectName: 'damageSelf', | |||
noEvents: true | |||
}); | |||
} | |||
} | |||
}; |
@@ -6,7 +6,7 @@ module.exports = { | |||
return `${rolls.chance}% chance on hit to freeze target for ${rolls.duration} ticks`; | |||
}, | |||
afterDealDamage: function (item, damage, target) { | |||
afterDealDamage: function (item, { damage, target }) { | |||
let rolls = item.effects.find(e => (e.type === 'freezeOnHit')).rolls; | |||
let chanceRoll = Math.random() * 100; | |||
@@ -18,9 +18,13 @@ module.exports = { | |||
amount = (cpnStats.values.hpMax / 100) * ~~amount.replace('%', ''); | |||
cpnStats.getHp({ | |||
amount: amount, | |||
threatMult: 0 | |||
}, item); | |||
heal: { | |||
amount, | |||
threatMult: 0 | |||
}, | |||
source: cpnStats.obj, | |||
target: cpnStats.obj | |||
}); | |||
} else | |||
cpnStats.addStat(stat, amount); | |||
}, | |||
@@ -21,7 +21,7 @@ module.exports = { | |||
return text; | |||
}, | |||
afterDealDamage: function (item, damage, target) { | |||
afterDealDamage: function (item, { damage, target }) { | |||
if (!damage.crit) | |||
return; | |||
@@ -34,8 +34,12 @@ module.exports = { | |||
let amount = rolls.amount || ((damage.dealt / 100) * rolls.percentage); | |||
this.stats.getHp({ | |||
amount: amount | |||
}, this); | |||
heal: { | |||
amount | |||
}, | |||
source: this, | |||
target: this | |||
}); | |||
} | |||
} | |||
}; |
@@ -1,19 +0,0 @@ | |||
let events = require('../../misc/events'); | |||
let config = [ | |||
{ | |||
name: 'cave', | |||
path: 'config/maps' | |||
}, | |||
{ | |||
name: 'fjolarok', | |||
path: 'config/maps' | |||
} | |||
]; | |||
module.exports = { | |||
init: function () { | |||
events.emit('onBeforeGetMapList', config); | |||
this.mapList = config; | |||
} | |||
}; |
@@ -10,8 +10,8 @@ module.exports = { | |||
}, | |||
events: { | |||
beforeTakeDamage: function (dmg, source) { | |||
dmg.amount *= 4; | |||
beforeTakeDamage: function ({ damage }) { | |||
damage.amount *= 4; | |||
} | |||
} | |||
}; |
@@ -1,7 +1,9 @@ | |||
let questTemplate = require('./templates/questTemplate'); | |||
let globalQuests = require('../questsBase'); | |||
let mapList = require('../maps/mapList'); | |||
//Imports | |||
const questTemplate = require('./templates/questTemplate'); | |||
const globalQuests = require('../questsBase'); | |||
const { mapList } = require('../../world/mapManager'); | |||
//Exports | |||
module.exports = { | |||
instance: null, | |||
@@ -11,7 +13,7 @@ module.exports = { | |||
obtain: function (obj, template) { | |||
let zoneName = template?.zoneName ?? obj.zoneName; | |||
let zone = mapList.mapList.find(m => m.name === zoneName); | |||
let zone = mapList.find(m => m.name === zoneName); | |||
//Zone doesn't exist any more. Probably been renamed | |||
if (!zone) | |||
@@ -1,7 +1,7 @@ | |||
/* eslint-disable no-process-env */ | |||
module.exports = { | |||
version: '0.11.0', | |||
version: '0.12.0', | |||
port: 4000, | |||
startupMessage: 'Server: ready', | |||
@@ -183,7 +183,14 @@ module.exports = { | |||
this.obj.aggro.move(); | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'ambush', | |||
noEvents: this.noEvents | |||
}); | |||
}, | |||
isTileValid: function (physics, fromX, fromY, toX, toY) { | |||
@@ -38,8 +38,12 @@ let cpnArcanePatch = { | |||
for (let i = 0; i < cLen; i++) { | |||
let c = contents[i]; | |||
let amount = this.spell.getDamage(c, true); | |||
c.stats.getHp(amount, this.caster); | |||
let heal = this.spell.getDamage(c, true); | |||
c.stats.getHp({ | |||
heal, | |||
source: this.caster, | |||
target: c | |||
}); | |||
} | |||
} | |||
}; | |||
@@ -31,6 +31,13 @@ module.exports = { | |||
return; | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'chainLightning', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
}; |
@@ -95,6 +95,18 @@ module.exports = { | |||
let obj = this.obj; | |||
const moveEvent = { | |||
oldPos: { | |||
x: obj.x, | |||
y: obj.y | |||
}, | |||
newPos: targetPos, | |||
source: this, | |||
target: this, | |||
spellName: 'charge', | |||
spell: this | |||
}; | |||
obj.instance.physics.removeObject(obj, obj.x, obj.y); | |||
obj.x = targetPos.x; | |||
@@ -114,12 +126,15 @@ module.exports = { | |||
targetEffect.ttl = this.stunDuration; | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'charge', | |||
noEvents: this.noEvents | |||
}); | |||
const moveEvent = { | |||
newPos: targetPos, | |||
source: this | |||
}; | |||
this.obj.fireEvent('afterPositionChange', moveEvent); | |||
if (this.castOnEnd) | |||
@@ -48,9 +48,15 @@ module.exports = { | |||
damage: 1, | |||
cast: function (action) { | |||
let obj = this.obj; | |||
const { obj, targetPlayerPos } = this; | |||
let { x, y, instance: { physics, syncer } } = obj; | |||
if (!targetPlayerPos) { | |||
x = action.target.x; | |||
y = action.target.y; | |||
} | |||
let radius = this.radius; | |||
const particleEvent = { | |||
@@ -99,7 +105,14 @@ module.exports = { | |||
m.clearQueue(); | |||
let damage = this.getDamage(m); | |||
m.stats.takeDamage(damage, 1, obj); | |||
m.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: this.obj, | |||
target: m, | |||
spellName: 'fireblast', | |||
noEvents: this.noEvents | |||
}); | |||
if (m.destroyed) | |||
continue; | |||
@@ -173,11 +186,18 @@ module.exports = { | |||
syncer.o.y = yFinal; | |||
const moveEvent = { | |||
oldPos: { | |||
x: xOld, | |||
y: yOld | |||
}, | |||
newPos: { | |||
x: xFinal, | |||
y: yFinal | |||
}, | |||
source: this | |||
source: this.obj, | |||
target, | |||
spellName: 'fireblast', | |||
spell: this | |||
}; | |||
target.fireEvent('afterPositionChange', moveEvent); | |||
} | |||
@@ -8,8 +8,12 @@ let cpnHealPatch = { | |||
this[p] = blueprint[p]; | |||
}, | |||
applyHeal: function (o, amount) { | |||
o.stats.getHp(amount, this.caster); | |||
applyHeal: function (target, heal) { | |||
target.stats.getHp({ | |||
heal, | |||
source: this.caster, | |||
target | |||
}); | |||
}, | |||
collisionEnter: function (o) { | |||
@@ -60,6 +60,13 @@ module.exports = { | |||
}); | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'iceSpear', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
}; |
@@ -43,6 +43,13 @@ module.exports = { | |||
return; | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'melee', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
}; |
@@ -138,6 +138,13 @@ module.exports = { | |||
if (this.applyEffect) | |||
target.effects.addEffect(this.applyEffect, this.obj); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'projectile', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
}; |
@@ -17,8 +17,18 @@ module.exports = { | |||
const target = action.target; | |||
const { x, y } = target; | |||
const amount = this.getDamage(target, true); | |||
target.stats.getHp(amount, this.obj); | |||
const heal = this.getDamage(target, true); | |||
const event = { | |||
heal, | |||
source: this.obj, | |||
target, | |||
spellName: 'singleTargetHeal', | |||
spell: this, | |||
noEvents: this.noEvents | |||
}; | |||
target.stats.getHp(event); | |||
const effect = { | |||
x, | |||
@@ -43,6 +43,13 @@ module.exports = { | |||
return; | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'slash', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
}; |
@@ -89,7 +89,14 @@ module.exports = { | |||
continue; | |||
let damage = this.getDamage(m); | |||
m.stats.takeDamage(damage, 1, obj); | |||
m.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: obj, | |||
target: m, | |||
spellName: 'slowBlast', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
} | |||
} | |||
@@ -42,7 +42,14 @@ module.exports = { | |||
return; | |||
let damage = this.getDamage(target); | |||
target.stats.takeDamage(damage, this.threatMult, this.obj); | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: this.threatMult, | |||
source: this.obj, | |||
target, | |||
spellName: 'smite', | |||
noEvents: this.noEvents | |||
}); | |||
target.effects.addEffect({ | |||
type: 'stunned', | |||
@@ -4,8 +4,15 @@ let cpnSmokePatch = { | |||
contents: [], | |||
ttl: 0, | |||
applyDamage: function (o, amount) { | |||
o.stats.takeDamage(amount, 1, this.caster); | |||
applyDamage: function (target, damage) { | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: this.caster, | |||
target: target, | |||
spellName: 'smokeBomb', | |||
noEvents: this.noEvents | |||
}); | |||
}, | |||
collisionEnter: function (o) { | |||
@@ -171,7 +178,8 @@ module.exports = { | |||
caster: obj, | |||
statType: this.statType, | |||
getDamage: this.getDamage.bind(this), | |||
ttl: this.duration | |||
ttl: this.duration, | |||
noEvents: this.noEvents | |||
} | |||
} | |||
}]); | |||
@@ -96,9 +96,13 @@ module.exports = { | |||
mLen--; | |||
} else if ((Math.abs(x - m.x) <= 1) && (Math.abs(y - m.y) <= 1)) { | |||
m.destroyed = true; | |||
this.obj.stats.getHp({ | |||
amount: obj.stats.values.hpMax / 10 | |||
}, obj); | |||
obj.stats.getHp({ | |||
heal: { | |||
amount: obj.stats.values.hpMax / 10 | |||
}, | |||
source: obj, | |||
target: obj | |||
}); | |||
obj.instance.syncer.queue('onGetObject', { | |||
x: m.x, | |||
@@ -292,7 +292,8 @@ module.exports = { | |||
isAttack: this.isAttack, | |||
noScale: this.noScale, | |||
noMitigate: noMitigate, | |||
spell: this | |||
spell: this, | |||
scaleConfig: this.scaleConfig | |||
}; | |||
if (this.obj.mob) | |||
@@ -3,8 +3,15 @@ let cpnSpikePatch = { | |||
contents: [], | |||
applyDamage: function (o, amount) { | |||
o.stats.takeDamage(amount, 1, this.caster); | |||
applyDamage: function (target, damage) { | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: this.caster, | |||
target: target, | |||
spellName: 'smokeBomb', | |||
noEvents: this.noEvents | |||
}); | |||
}, | |||
collisionEnter: function (o) { | |||
@@ -116,6 +123,7 @@ module.exports = { | |||
}]); | |||
spike.spikePatch.caster = this.obj; | |||
spike.spikePatch.damage = this.damage; | |||
spike.spikePatch.noEvents = this.noEvents; | |||
this.queueCallback(null, this.spikeDuration * consts.tickTime, this.endEffect.bind(this, spike), null, true); | |||
@@ -124,7 +124,14 @@ module.exports = { | |||
continue; | |||
let damage = this.getDamage(m); | |||
m.stats.takeDamage(damage, 1, obj); | |||
m.stats.takeDamage({ | |||
damage, | |||
threatMult: 1, | |||
source: obj, | |||
target: m, | |||
spellName: 'warnBlast', | |||
noEvents: this.noEvents | |||
}); | |||
} | |||
} | |||
}; |
@@ -3,8 +3,14 @@ const coordinateDeltas = [ | |||
[[0, -2], [0, -1], [1, -2], [2, -2], [1, -1], [2, -1], [2, 0], [1, 0], [2, 1], [2, 2], [1, 1], [1, 2], [0, 2], [0, 1], [-1, 2], [-2, 2], [-2, 1], [-1, 1], [-2, 0], [-1, 0], [-2, -1], [-2, -2], [-1, -1], [-1, -2]] | |||
]; | |||
const applyDamage = (target, damage, threat, source) => { | |||
target.stats.takeDamage(damage, threat, source); | |||
const applyDamage = (target, damage, threatMult, source) => { | |||
target.stats.takeDamage({ | |||
damage, | |||
threatMult, | |||
source, | |||
target, | |||
spellName: 'whirlwind' | |||
}); | |||
}; | |||
const dealDamage = (spell, obj, coords) => { | |||
@@ -36,17 +36,6 @@ module.exports = { | |||
rolls: 2, | |||
magicFind: 2000 | |||
} | |||
}, | |||
champion: { | |||
hpMult: 5, | |||
dmgMult: 2, | |||
drops: { | |||
chance: 100, | |||
rolls: 2, | |||
magicFind: [2000, 175] | |||
} | |||
} | |||
} | |||
} | |||
@@ -77,11 +77,23 @@ module.exports = { | |||
return res; | |||
}, | |||
getFilterAsync: async function ({ table, noDefault, filter }) { | |||
const res = await r | |||
getFilterAsync: async function ( | |||
{ table, noDefault, filter, limit, offset, orderAsc, orderDesc } | |||
) { | |||
let res = r | |||
.table(table) | |||
.filter(filter) | |||
.run(); | |||
.filter(filter); | |||
if (orderAsc) | |||
res = res.orderBy(orderAsc); | |||
if (orderDesc) | |||
res = res.orderBy(r.desc(orderDesc)); | |||
if (offset) | |||
res = res.skip(offset); | |||
if (limit) | |||
res = res.limit(limit); | |||
await res.run(); | |||
if (res) | |||
return res; | |||
@@ -129,6 +141,20 @@ module.exports = { | |||
} | |||
}, | |||
setFlat: async function ({ | |||
table, | |||
value, | |||
conflict = 'update' | |||
}) { | |||
try { | |||
await r.table(table) | |||
.insert(value, { conflict }) | |||
.run(); | |||
} catch (e) { | |||
this.logError(e, table, JSON.stringify(value)); | |||
} | |||
}, | |||
deleteAsync: async function ({ | |||
key, | |||
table | |||
@@ -1,15 +1,14 @@ | |||
const tableNames = [ | |||
'accountInfo', | |||
'character', | |||
'characterList', | |||
'stash', | |||
'login', | |||
'leaderboard', | |||
'customMap', | |||
'customChannels', | |||
'error', | |||
'leaderboard', | |||
'login', | |||
'modLog', | |||
'accountInfo', | |||
'recipes' | |||
'recipes', | |||
'stash' | |||
]; | |||
module.exports = tableNames; |
@@ -1,7 +1,11 @@ | |||
let phaseTemplate = require('./phases/phaseTemplate'); | |||
let fs = require('fs'); | |||
let mapList = require('../config/maps/mapList'); | |||
//System Imports | |||
const fs = require('fs'); | |||
//Imports | |||
const phaseTemplate = require('./phases/phaseTemplate'); | |||
const { mapList } = require('../world/mapManager'); | |||
//Helpers | |||
const applyVariablesToDescription = (desc, variables) => { | |||
if (!variables) | |||
return desc; | |||
@@ -14,6 +18,7 @@ const applyVariablesToDescription = (desc, variables) => { | |||
return desc; | |||
}; | |||
//Exports | |||
module.exports = { | |||
configs: [], | |||
nextId: 0, | |||
@@ -22,7 +27,7 @@ module.exports = { | |||
this.instance = instance; | |||
const zoneName = this.instance.map.name; | |||
const zonePath = mapList.mapList.find(z => z.name === zoneName).path; | |||
const zonePath = mapList.find(z => z.name === zoneName).path; | |||
const zoneEventPath = zonePath + '/' + zoneName + '/events'; | |||
const paths = ['config/globalEvents', zoneEventPath]; | |||
@@ -4,7 +4,7 @@ let cpnDeathStopper = { | |||
end: false, | |||
events: { | |||
beforeTakeDamage: function (damage, source) { | |||
beforeTakeDamage: function ({ damage }) { | |||
let statValues = this.obj.stats.values; | |||
let minHp = statValues.hpMax * this.percentage; | |||
if (statValues.hp - damage.amount < minHp) { | |||
@@ -1,3 +1,5 @@ | |||
/* eslint-disable max-lines-per-function */ | |||
module.exports = { | |||
fixDb: async function () { | |||
await io.deleteAsync({ | |||
@@ -117,7 +119,65 @@ module.exports = { | |||
}); | |||
items | |||
.filter(f => ((f.effects) && (f.effects[0].factionId === 'akarei') && (!f.effects[0].properties))) | |||
.filter(i => i.name === 'Gourdhowl') | |||
.forEach(i => { | |||
const effect = i.effects[0]; | |||
if (!effect.rolls.castSpell) { | |||
effect.rolls = { | |||
castSpell: { | |||
type: 'whirlwind', | |||
damage: effect.rolls.damage, | |||
range: 1, | |||
statType: 'str', | |||
statMult: 1, | |||
isAttack: true | |||
}, | |||
castTarget: 'none', | |||
chance: effect.rolls.chance, | |||
textTemplate: 'Grants you a ((chance))% chance to cast a ((castSpell.damage)) damage whirlwind on hit', | |||
combatEvent: { | |||
name: 'afterDealDamage', | |||
afterDealDamage: { | |||
spellName: 'melee' | |||
} | |||
} | |||
}; | |||
} | |||
}); | |||
items | |||
.filter(i => i.name === 'Putrid Shank') | |||
.forEach(i => { | |||
const effect = i.effects[0]; | |||
if (!effect.rolls.castSpell) { | |||
effect.rolls = { | |||
chance: effect.rolls.chance, | |||
textTemplate: 'Grants you a ((chance))% chance to cast a ((castSpell.damage)) damage smokebomb on hit', | |||
combatEvent: { | |||
name: 'afterDealDamage', | |||
afterDealDamage: { | |||
spellName: 'melee' | |||
} | |||
}, | |||
castTarget: 'none', | |||
castSpell: { | |||
type: 'smokebomb', | |||
damage: 1, | |||
range: 1, | |||
element: 'poison', | |||
statType: 'dex', | |||
statMult: 1, | |||
duration: 5, | |||
isAttack: true | |||
} | |||
}; | |||
} | |||
}); | |||
items | |||
.filter(f => f.effects?.[0]?.factionId === 'akarei' && !f.effects[0].properties) | |||
.forEach(function (i) { | |||
let effect = i.effects[0]; | |||
let chance = parseFloat(effect.text.split(' ')[0].replace('%', '')); | |||
@@ -1,20 +1,22 @@ | |||
require('./globals'); | |||
let server = require('./server/index'); | |||
let components = require('./components/components'); | |||
let mods = require('./misc/mods'); | |||
let animations = require('./config/animations'); | |||
let skins = require('./config/skins'); | |||
let factions = require('./config/factions'); | |||
let classes = require('./config/spirits'); | |||
let spellsConfig = require('./config/spellsConfig'); | |||
let spells = require('./config/spells'); | |||
let itemTypes = require('./items/config/types'); | |||
let recipes = require('./config/recipes/recipes'); | |||
let mapList = require('./config/maps/mapList'); | |||
let fixes = require('./fixes/fixes'); | |||
let profanities = require('./misc/profanities'); | |||
const server = require('./server/index'); | |||
const components = require('./components/components'); | |||
const mods = require('./misc/mods'); | |||
const animations = require('./config/animations'); | |||
const skins = require('./config/skins'); | |||
const factions = require('./config/factions'); | |||
const classes = require('./config/spirits'); | |||
const spellsConfig = require('./config/spellsConfig'); | |||
const spells = require('./config/spells'); | |||
const itemTypes = require('./items/config/types'); | |||
const salvager = require('./items/salvager'); | |||
const recipes = require('./config/recipes/recipes'); | |||
const mapManager = require('./world/mapManager'); | |||
const fixes = require('./fixes/fixes'); | |||
const profanities = require('./misc/profanities'); | |||
const routerConfig = require('./security/routerConfig'); | |||
const { spawnMapThreads } = require('./world/threadManager'); | |||
let startup = { | |||
init: function () { | |||
@@ -40,8 +42,9 @@ let startup = { | |||
spells.init(); | |||
recipes.init(); | |||
itemTypes.init(); | |||
salvager.init(); | |||
profanities.init(); | |||
mapList.init(); | |||
mapManager.init(); | |||
components.init(this.onComponentsReady.bind(this)); | |||
}, | |||
@@ -55,7 +58,7 @@ let startup = { | |||
await leaderboard.init(); | |||
atlas.init(); | |||
await spawnMapThreads(); | |||
}, | |||
onError: async function (e) { | |||
@@ -75,6 +75,14 @@ module.exports = { | |||
if (blueprint.isSpell) | |||
isSpell = true; | |||
const beforeGenerateItemEvent = { | |||
blueprint, | |||
item: null | |||
}; | |||
global.instancer.instances[0].eventEmitter.emit('beforeGenerateItem', beforeGenerateItemEvent); | |||
if (beforeGenerateItemEvent.item) | |||
return beforeGenerateItemEvent.item; | |||
if (isSpell) | |||
spellGenerators.forEach(g => g.generate(item, blueprint)); | |||
else if (isCurrency) | |||
@@ -1,3 +1,40 @@ | |||
const rollValues = (rollsDefinition, result) => { | |||
for (let p in rollsDefinition) { | |||
const entry = rollsDefinition[p]; | |||
if (typeof(entry) === 'object' && entry !== null && !Array.isArray(entry) ) { | |||
const newResult = {}; | |||
result[p] = newResult; | |||
rollValues(entry, newResult); | |||
continue; | |||
} | |||
const range = entry; | |||
const isInt = (p.indexOf('i_') === 0); | |||
const fieldName = p.replace('i_', ''); | |||
//Keys that start with s_ indicate that they shouldn't be rolled | |||
// We use this to allow arrays inside rolls to be hardcoded | |||
if (!Array.isArray(entry) || p.indexOf('s_') === 0) { | |||
if (p.indexOf('s_') === 0) | |||
result[p.substr(2)] = range; | |||
else | |||
result[fieldName] = range; | |||
continue; | |||
} | |||
let value = range[0] + (Math.random() * (range[1] - range[0])); | |||
if (isInt) | |||
value = ~~value; | |||
result[fieldName] = value; | |||
} | |||
}; | |||
module.exports = { | |||
generate: function (item, blueprint) { | |||
if (!blueprint.effects) | |||
@@ -6,22 +43,8 @@ module.exports = { | |||
item.effects = blueprint.effects.map(function (e) { | |||
let rolls = e.rolls; | |||
let newRolls = {}; | |||
for (let p in rolls) { | |||
let isInt = (p.indexOf('i_') === 0); | |||
let fieldName = p.replace('i_', ''); | |||
let range = rolls[p]; | |||
if (!range.push) { | |||
newRolls[fieldName] = range; | |||
continue; | |||
} | |||
let value = range[0] + (Math.random() * (range[1] - range[0])); | |||
if (isInt) | |||
value = ~~value; | |||
newRolls[fieldName] = value; | |||
} | |||
rollValues(rolls, newRolls); | |||
return { | |||
type: e.type, | |||
@@ -47,7 +47,7 @@ module.exports = { | |||
generate: function (item, blueprint) { | |||
blueprint = blueprint || {}; | |||
let spellQuality = blueprint ? blueprint.spellQuality : ''; | |||
let spellName = blueprint.spellName; | |||
let spellName = blueprint.spellName?.replaceAll('_', ' '); | |||
if (!spellName) { | |||
let spellList = Object.keys(spellsConfig.spells).filter(s => !spellsConfig.spells[s].auto && !spellsConfig.spells[s].noDrop); | |||
@@ -8,7 +8,10 @@ module.exports = { | |||
if (!type || !configTypes.types[item.slot][type]) { | |||
//Pick a material type first | |||
const types = configTypes.types[item.slot]; | |||
const typeArray = Object.entries(types); | |||
const typeArray = Object | |||
.entries(types) | |||
.filter(t => t.noDrop !== true); | |||
const materials = Object.values(types) | |||
.map(t => { | |||
return t.material; | |||
@@ -1,4 +1,7 @@ | |||
let mappings = { | |||
//Imports | |||
const events = require('../misc/events'); | |||
const mappings = { | |||
rune: [{ | |||
materials: [{ | |||
name: 'Essence', | |||
@@ -68,7 +71,7 @@ let mappings = { | |||
}] | |||
}; | |||
let materialItems = { | |||
const materialItems = { | |||
'Iron Bar': { | |||
sprite: [0, 0] | |||
}, | |||
@@ -99,6 +102,11 @@ let materialItems = { | |||
}; | |||
module.exports = { | |||
init: function () { | |||
events.emit('onBeforeGetSalvagerMappings', { | |||
mappings | |||
}); | |||
}, | |||
salvage: function (item, maxRoll) { | |||
let result = []; | |||
@@ -35,12 +35,21 @@ module.exports = { | |||
return; | |||
let amount = (obj.stats.values.hpMax / 100) * this.drainPercentage; | |||
const damage = { amount }; | |||
obj.stats.takeDamage(damage, 0, obj); | |||
obj.stats.takeDamage({ | |||
damage: { amount }, | |||
threatMult: 0, | |||
source: obj, | |||
target: obj, | |||
spellName: 'bloodBarrier' | |||
}); | |||
amount = amount * this.shieldMultiplier; | |||
const heal = { amount }; | |||
target.stats.getHp(heal, obj); | |||
target.stats.getHp({ | |||
heal, | |||
source: obj, | |||
target | |||
}); | |||
//Only reset the first spell's cooldown if it's an auto attack and not a spell | |||
const firstSpell = target.spellbook.spells[0]; | |||