Переглянути джерело

Merge branch 'master' into 'release'

Master

See merge request Isleward/isleward!468
tags/v0.6
Big Bad Waffle 4 роки тому
джерело
коміт
2b36286a41
91 змінених файлів з 2760 додано та 2731 видалено
  1. +84
    -1
      src/client/css/main.less
  2. BIN
     
  3. BIN
     
  4. BIN
     
  5. BIN
     
  6. BIN
     
  7. +26
    -6
      src/client/js/components/animation.js
  8. +1
    -1
      src/client/js/components/chest.js
  9. +9
    -0
      src/client/js/components/keyboardMover.js
  10. +16
    -7
      src/client/js/main.js
  11. +1
    -1
      src/client/js/rendering/numbers.js
  12. +33
    -18
      src/client/js/rendering/renderer.js
  13. +16
    -55
      src/client/js/resources.js
  14. +1
    -1
      src/client/package.json
  15. +0
    -2
      src/client/ui/factory.js
  16. +1
    -1
      src/client/ui/shared/renderItem.js
  17. +18
    -10
      src/client/ui/templates/announcements/styles.less
  18. +0
    -1
      src/client/ui/templates/createCharacter/styles.less
  19. +7
    -16
      src/client/ui/templates/equipment/equipment.js
  20. +5
    -18
      src/client/ui/templates/inventory/inventory.js
  21. +2
    -2
      src/client/ui/templates/login/template.html
  22. +0
    -1
      src/client/ui/templates/menu/menu.js
  23. +12
    -15
      src/client/ui/templates/menu/styles.less
  24. +0
    -3
      src/client/ui/templates/menu/template.html
  25. +0
    -11
      src/client/ui/templates/middleHud/middleHud.js
  26. +0
    -343
      src/client/ui/templates/smithing/smithing.js
  27. +0
    -186
      src/client/ui/templates/smithing/styles.less
  28. +0
    -34
      src/client/ui/templates/smithing/template.html
  29. +0
    -4
      src/client/ui/templates/smithing/templateItem.html
  30. +66
    -12
      src/client/ui/templates/workbench/styles.less
  31. +13
    -1
      src/client/ui/templates/workbench/template.html
  32. +203
    -18
      src/client/ui/templates/workbench/workbench.js
  33. +5
    -7
      src/server/components/components.js
  34. +4
    -10
      src/server/components/door.js
  35. +13
    -32
      src/server/components/equipment.js
  36. +2
    -8
      src/server/components/extensions/factionVendor.js
  37. +33
    -31
      src/server/components/gatherer.js
  38. +13
    -68
      src/server/components/inventory.js
  39. +2
    -8
      src/server/components/inventory/useItem.js
  40. +3
    -0
      src/server/components/mob.js
  41. +15
    -24
      src/server/components/reputation.js
  42. +4
    -0
      src/server/components/spellbook.js
  43. +7
    -24
      src/server/components/stash.js
  44. +6
    -1
      src/server/components/wardrobe.js
  45. +11
    -78
      src/server/components/workbench.js
  46. +49
    -0
      src/server/components/workbench/buildMaterials.js
  47. +29
    -0
      src/server/components/workbench/buildNeedItems.js
  48. +19
    -0
      src/server/components/workbench/buildPickedItems.js
  49. +30
    -0
      src/server/components/workbench/buildRecipe.js
  50. +97
    -0
      src/server/components/workbench/craft.js
  51. +22
    -0
      src/server/config/clientConfig.js
  52. +1
    -59
      src/server/config/maps/fjolarok/dialogues.js
  53. +958
    -925
      src/server/config/maps/fjolarok/map.json
  54. +175
    -16
      src/server/config/maps/fjolarok/zone.js
  55. +28
    -8
      src/server/config/maps/sewer/map.json
  56. +20
    -28
      src/server/config/quests/templates/questTemplate.js
  57. +94
    -0
      src/server/config/recipes/enchanting.js
  58. +17
    -0
      src/server/config/recipes/enchanting/calculateAugmentMaterials.js
  59. +17
    -0
      src/server/config/recipes/enchanting/craftActions/augment.js
  60. +20
    -0
      src/server/config/recipes/enchanting/craftActions/reforge.js
  61. +20
    -0
      src/server/config/recipes/enchanting/craftActions/relevel.js
  62. +58
    -0
      src/server/config/recipes/enchanting/craftActions/reroll.js
  63. +36
    -0
      src/server/config/recipes/enchanting/craftActions/reslot.js
  64. +31
    -0
      src/server/config/recipes/enchanting/craftActions/scour.js
  65. +10
    -10
      src/server/config/recipes/recipes.js
  66. +3
    -9
      src/server/config/roles.js
  67. +1
    -1
      src/server/config/serverConfig.js
  68. +1
    -0
      src/server/config/spells/spellProjectile.js
  69. +3
    -0
      src/server/config/spells/spellTemplate.js
  70. +0
    -245
      src/server/items/enchanter.js
  71. +2
    -1
      src/server/items/generator.js
  72. +70
    -31
      src/server/items/generators/spellbook.js
  73. +8
    -16
      src/server/mail/mailRethinkDb.js
  74. +4
    -8
      src/server/mail/mailSqlite.js
  75. +8
    -7
      src/server/mods/class-necromancer/index.js
  76. +6
    -159
      src/server/mods/feature-cards/cards.js
  77. +17
    -0
      src/server/mods/feature-cards/config.js
  78. BIN
     
  79. BIN
     
  80. +44
    -25
      src/server/mods/feature-cards/index.js
  81. +17
    -0
      src/server/mods/feature-cards/recipes/craftActions/armor.js
  82. +18
    -0
      src/server/mods/feature-cards/recipes/craftActions/idol.js
  83. +14
    -0
      src/server/mods/feature-cards/recipes/craftActions/rune.js
  84. +17
    -0
      src/server/mods/feature-cards/recipes/craftActions/weapon.js
  85. +155
    -0
      src/server/mods/feature-cards/recipes/recipes.js
  86. +0
    -52
      src/server/mods/mounts/effects/effectMounted.js
  87. +0
    -66
      src/server/mods/mounts/index.js
  88. +1
    -1
      src/server/package.json
  89. +1
    -1
      src/server/security/routerConfig.js
  90. +5
    -4
      src/server/world/map.js
  91. +2
    -0
      src/server/world/worker.js

+ 84
- 1
src/client/css/main.less Переглянути файл

@@ -245,4 +245,87 @@ body {
}

.color-tealB {
color: @tealB !important;}
color: @tealB !important;
}

[class^="ui"] .renderItem {
width: 72px;
height: 72px;
float: left;
position: relative;
cursor: pointer;
box-sizing: border-box;
margin: 4px;
background-color: @blackD;

&.hover {
background-color: fade(@blueA, 10%);
}

&.dragging {
position: absolute;
opacity: 0.5;
pointer-events: none;
background-color: transparent;

.icon {
filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
-moz-filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
}

}

.quantity {
left: 6px;
bottom: 3px;
position: absolute;
color: @white;
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

.icon {
width: 64px;
height: 64px;
position: absolute;
left: 4px;
top: 4px;
}

&.eq {
.quantity {
color: @yellow;
}

}

&.new {
.quantity {
color: @green;
}

}

&:hover {
.icon {
filter: brightness(160%);
-moz-filter: brightness(160%);
}

}

}






+ 26
- 6
src/client/js/components/animation.js Переглянути файл

@@ -21,21 +21,27 @@ define([

frameDelayCd: 0,

oldSheetName: null,
oldCell: null,
oldTexture: null,

init: function (blueprint) {
if (!this.obj.sprite)
const { template, frameDelay, obj: { sheetName, cell, sprite } } = this;

if (!sprite)
return true;
this.oldTexture = this.obj.sprite.texture;
this.oldSheetName = sheetName;
this.oldCell = cell;
this.oldTexture = sprite.texture;

this.frame = 0;
this.frameDelayCd = 0;

for (let p in this.template)
this[p] = this.template[p];
for (let p in template)
this[p] = template[p];

this.frameDelayCd = this.frameDelay;
this.frameDelayCd = frameDelay;

this.setSprite();
},
@@ -68,7 +74,21 @@ define([
},

destroy: function () {
this.obj.sprite.texture = this.oldTexture;
const { oldSheetName, oldCell, oldTexture, obj: { sheetName, cell, sprite } } = this;

//Make sure something didn't happen while we were in animation form
// that made us change sprite
if (oldSheetName === sheetName && oldCell === cell) {
sprite.texture = oldTexture;

return;
}

renderer.setSprite({
sprite,
cell,
sheetName
});
}
};
});

+ 1
- 1
src/client/js/components/chest.js Переглянути файл

@@ -38,7 +38,7 @@ define([
return;
}

let index = indices[this.obj.cell];
let index = indices[this.obj.cell] || 0;

this.obj.addComponent('particles', {
chance: chances[index],


+ 9
- 0
src/client/js/components/keyboardMover.js Переглянути файл

@@ -17,6 +17,7 @@ define([

init: function () {
this.hookEvent('onCanvasKeyDown', this.onCanvasKeyDown.bind(this));
this.hookEvent('onMoveSpeedChange', this.onMoveSpeedChange.bind(this));
},

update: function () {
@@ -31,6 +32,14 @@ define([
this.keyMove();
},

//Changes the moveCdMax variable
// moveSpeed is affected when mounting and unmounting
// moveSpeed: 0 | moveCdMax: 8
// moveSpeed: 200 | moveCdMax: 4
onMoveSpeedChange: function (moveSpeed) {
this.moveCdMax = Math.ceil(4 + (((200 - moveSpeed) / 200) * 4));
},

onCanvasKeyDown: function (keyEvent) {
if (keyEvent.key === 'esc') {
client.request({


+ 16
- 7
src/client/js/main.js Переглянути файл

@@ -25,8 +25,14 @@ define([
sound,
globals
) {
let fnQueueTick = null;
const getQueueTick = updateMethod => {
return () => requestAnimationFrame(updateMethod);
};

return {
hasFocus: true,

lastRender: 0,
msPerFrame: ~~(1000 / 60),

@@ -45,11 +51,13 @@ define([
});
},

onGetClientConfig: function (config) {
onGetClientConfig: async function (config) {
globals.clientConfig = config;
resources.init(config.resourceList);

events.on('onResourcesLoaded', this.start.bind(this));
await resources.init();
events.emit('onResourcesLoaded');

this.start();
},

start: function () {
@@ -69,7 +77,8 @@ define([
uiFactory.init(null, globals.clientConfig.uiList);
uiFactory.build('login', 'body');

this.update();
fnQueueTick = getQueueTick(this.update.bind(this));
fnQueueTick();

$('.loader-container').remove();
},
@@ -93,7 +102,7 @@ define([
update: function () {
const time = +new Date();
if (time - this.lastRender < this.msPerFrame - 1) {
requestAnimationFrame(this.update.bind(this));
fnQueueTick();

return;
}
@@ -101,13 +110,13 @@ define([
objects.update();
renderer.update();
uiFactory.update();
numbers.update();

numbers.render();
renderer.render();

this.lastRender = time;

requestAnimationFrame(this.update.bind(this));
fnQueueTick();
}
};
});

+ 1
- 1
src/client/js/rendering/numbers.js Переглянути файл

@@ -61,7 +61,7 @@ define([
this.list.push(numberObj);
},

render: function () {
update: function () {
let list = this.list;
let lLen = list.length;



+ 33
- 18
src/client/js/rendering/renderer.js Переглянути файл

@@ -6,7 +6,8 @@ define([
'js/rendering/tileOpacity',
'js/rendering/particles',
'js/rendering/shaders/outline',
'js/rendering/spritePool'
'js/rendering/spritePool',
'js/system/globals'
], function (
resources,
events,
@@ -15,7 +16,8 @@ define([
tileOpacity,
particles,
shaderOutline,
spritePool
spritePool,
globals
) {
let pixi = PIXI;
let mRandom = Math.random.bind(Math);
@@ -75,6 +77,7 @@ define([

events.on('onGetMap', this.onGetMap.bind(this));
events.on('onToggleFullscreen', this.toggleScreen.bind(this));
events.on('onMoveSpeedChange', this.adaptCameraMoveSpeed.bind(this));

let zoom = isMobile ? 1 : window.devicePixelRatio;
this.width = $('body').width() * zoom;
@@ -91,29 +94,25 @@ define([

window.addEventListener('resize', this.onResize.bind(this));

$(this.renderer.view)
.appendTo('.canvas-container');
$(this.renderer.view).appendTo('.canvas-container');

this.stage = new pixi.Container();

let layers = this.layers;
Object.keys(layers).forEach(function (l) {
Object.keys(layers).forEach(l => {
layers[l] = new pixi.Container();
layers[l].layer = (l === 'tileSprites') ? 'tiles' : l;

this.stage.addChild(layers[l]);
}, this);

let spriteNames = ['tiles', 'mobs', 'bosses', 'animBigObjects', 'bigObjects', 'objects', 'characters', 'attacks', 'auras', 'walls', 'ui', 'animChar', 'animMob', 'animBoss', 'white', 'ray'];
resources.spriteNames.forEach(function (s) {
if (s.indexOf('.png') > -1)
spriteNames.push(s);
});

spriteNames.forEach(function (t) {
this.textures[t] = new pixi.BaseTexture(resources.sprites[t].image);
const textureList = globals.clientConfig.textureList;
const sprites = resources.sprites;

textureList.forEach(t => {
this.textures[t] = new pixi.BaseTexture(sprites[t]);
this.textures[t].scaleMode = pixi.SCALE_MODES.NEAREST;
}, this);
});

particles.init({
r: this,
@@ -643,17 +642,19 @@ define([
let deltaY = this.moveTo.y - this.pos.y;

if (deltaX !== 0 || deltaY !== 0) {
let moveSpeed = this.moveSpeed;
let distance = Math.max(Math.abs(deltaX), Math.abs(deltaY));

let moveSpeedMax = this.moveSpeedMax;
if (distance > 100)
moveSpeedMax *= 1.75;
if (this.moveSpeed < moveSpeedMax)
this.moveSpeed += this.moveSpeedInc;

let moveSpeed = this.moveSpeed;

if (moveSpeedMax < 1.6)
moveSpeed *= 1 + (distance / 200);

let elapsed = time - this.lastTick;
moveSpeed *= (elapsed / 16.67);
moveSpeed *= (elapsed / 15);

if (moveSpeed > distance)
moveSpeed = distance;
@@ -823,6 +824,20 @@ define([
obj.sprite.parent.removeChild(obj.sprite);
},

//Changes the moveSpeedMax and moveSpeedInc variables
// moveSpeed changes when mounting and unmounting
// moveSpeed: 0 | moveSpeedMax: 1.5 | moveSpeedInc: 0.5
// moveSpeed: 200 | moveSpeedMax: 5.5 | moveSpeedInc: 0.2
// Between these values we should follow an exponential curve for moveSpeedInc since
// a higher chance will proc more often, meaning the buildup in distance becomes greater
adaptCameraMoveSpeed: function (moveSpeed) {
const factor = Math.sqrt(moveSpeed);
const maxValue = Math.sqrt(200);

this.moveSpeedMax = 1.5 + ((moveSpeed / 200) * 3.5);
this.moveSpeedInc = 0.2 + (((maxValue - factor) / maxValue) * 0.3);
},

render: function () {
if (!this.stage)
return;


+ 16
- 55
src/client/js/resources.js Переглянути файл

@@ -1,66 +1,27 @@
define([
'js/system/events'
'js/system/globals'
], function (
events
globals
) {
let resources = {
spriteNames: [
'tiles',
'walls',
'mobs',
'bosses',
'animBigObjects',
'bigObjects',
'objects',
'characters',
'attacks',
'ui',
'auras',
'animChar',
'animMob',
'animBoss',
'white',
'ray',
'images/skins/0001.png',
'images/skins/0010.png',
'images/skins/0012.png'
],
return {
sprites: {},
ready: false,
init: function (list) {
list.forEach(function (l) {
this.spriteNames.push(l);
}, this);

this.spriteNames.forEach(function (s) {
let sprite = {
image: (new Image()),
ready: false
};
sprite.image.src = s.indexOf('png') > -1 ? s : 'images/' + s + '.png';
sprite.image.onload = this.onSprite.bind(this, sprite);
init: async function () {
const { sprites } = this;
const { clientConfig: { resourceList, textureList } } = globals;

this.sprites[s] = sprite;
}, this);
},
onSprite: function (sprite) {
sprite.ready = true;
const fullList = [].concat(resourceList, textureList);

let readyCount = 0;
for (let s in this.sprites) {
if (this.sprites[s].ready)
readyCount++;
}
return Promise.all(fullList.map(s => {
return new Promise(res => {
const spriteSource = s.includes('.png') ? s : `images/${s}.png`;

if (readyCount === this.spriteNames.length)
this.onReady();
},
onReady: function () {
this.ready = true;

events.emit('onResourcesLoaded');
const sprite = new Image();
sprites[s] = sprite;
sprite.onload = res;
sprite.src = spriteSource;
});
}));
}
};

return resources;
});

+ 1
- 1
src/client/package.json Переглянути файл

@@ -1,6 +1,6 @@
{
"name": "isleward_client",
"version": "0.5.1",
"version": "0.6.0",
"description": "isleward",
"dependencies": {},
"devDependencies": {


+ 0
- 2
src/client/ui/factory.js Переглянути файл

@@ -32,7 +32,6 @@ define([
'events',
'progressBar',
'stash',
'smithing',
'talk',
'trade',
'overlay',
@@ -151,7 +150,6 @@ define([
'passives',
'quests',
'reputation',
'smithing',
'stash'
].map(m => 'ui/templates/' + m + '/' + m), this.afterPreload.bind(this));
},


+ 1
- 1
src/client/ui/shared/renderItem.js Переглянути файл

@@ -1,5 +1,5 @@
const tplItem = `
<div class="item">
<div class="renderItem item">
<div class="icon"></div>
<div class="quantity"></div>
</div>


+ 18
- 10
src/client/ui/templates/announcements/styles.less Переглянути файл

@@ -20,13 +20,9 @@
text-align: center;
color: @white;
font-size: 18px;
background-color: fade(@blackD, 10%);
background-color: fade(@blackD, 15%);
padding: 8px;
text-shadow: 2px 2px 0 @blackD,
-2px -2px 0 @blackD,
2px -2px 0 @blackD,
-2px 2px 0 @blackD,
2px 2px 0 @blackD;
text-shadow: 2px 2px 0 @blackD, -2px -2px 0 @blackD, 2px -2px 0 @blackD, -2px 2px 0 @blackD, 2px 2px 0 @blackD;
animation: 0.5s ease-in-out infinite bounce;

&.success {
@@ -36,12 +32,24 @@
&.failure {
color: @red;
}

}

}

}

@keyframes bounce {
0% { top: 0px; }
50% { top: 4px; }
100% { top: 0px ;}
}
0% {
top: 0px;
}

50% {
top: 4px;
}

100% {
top: 0px;
}

}

+ 0
- 1
src/client/ui/templates/createCharacter/styles.less Переглянути файл

@@ -114,7 +114,6 @@
image-rendering: optimizeSpeed;
image-rendering: pixelated;
image-rendering: crisp-edges;
background: url('../../../images/charas.png') -64px 0px;
}

}


+ 7
- 16
src/client/ui/templates/equipment/equipment.js Переглянути файл

@@ -155,31 +155,18 @@ define([
});
},

openAugmentUi: function (item) {
events.emit('onSetSmithItem', {
item: item
});
},

showContext: function (item, e) {
let menuItems = {
unequip: {
text: 'unequip',
callback: this.unequipItem.bind(this, item)
},
augment: {
text: 'craft',
callback: this.openAugmentUi.bind(this, item)
}
}
};

let config = [];

config.push(menuItems.unequip);

if (item.slot)
config.push(menuItems.augment);

events.emit('onContextMenu', config, e);

e.preventDefault();
@@ -187,13 +174,17 @@ define([
},

unequipItem: function (item) {
const isQuickslot = item.has('quickSlot');
const method = isQuickslot ? 'setQuickSlot' : 'unequip';
const data = isQuickslot ? { slot: item.quickSlot } : item.id;

client.request({
cpn: 'player',
method: 'performAction',
data: {
cpn: 'equipment',
method: 'unequip',
data: item.id
method,
data
}
});
},


+ 5
- 18
src/client/ui/templates/inventory/inventory.js Переглянути файл

@@ -293,10 +293,6 @@ define([
text: 'equip',
callback: this.performItemAction.bind(this, item, 'equip')
},
augment: {
text: 'craft',
callback: this.openAugmentUi.bind(this, item)
},
mail: {
text: 'mail',
callback: this.openMailUi.bind(this, item)
@@ -336,11 +332,6 @@ define([
ctxConfig.push(menuItems.equip);
if (!item.eq)
ctxConfig.push(menuItems.divider);

if (!item.eq) {
ctxConfig.push(menuItems.augment);
ctxConfig.push(menuItems.divider);
}
}

if ((!item.eq) && (!item.active)) {
@@ -354,9 +345,6 @@ define([
if ((!item.material) && (!item.noSalvage))
ctxConfig.push(menuItems.salvage);
}

if (!item.noDestroy)
ctxConfig.push(menuItems.destroy);
}

if (item.quantity > 1 && !item.quest)
@@ -367,6 +355,11 @@ define([

ctxConfig.push(menuItems.link);

if (!item.eq && !item.active && !item.noDestroy) {
ctxConfig.push(menuItems.divider);
ctxConfig.push(menuItems.destroy);
}

if (isMobile)
this.hideTooltip(null, this.hoverItem);

@@ -568,12 +561,6 @@ define([
});
},

openAugmentUi: function (item) {
events.emit('onSetSmithItem', {
item: item
});
},

openMailUi: function (item) {
events.emit('onSetMailItem', {
item: item


+ 2
- 2
src/client/ui/templates/login/template.html Переглянути файл

@@ -11,11 +11,11 @@
</div>
<div class="message"></div>
</div>
<div class="news" location="https://gitlab.com/Isleward/isleward/tags/v0.5.1">[ Latest Release Notes ]</div>
<div class="news" location="https://gitlab.com/Isleward/isleward/tags/v0.6">[ Latest Release Notes ]</div>
<div class="extra">
<div class="el btn btnPatreon" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div>
<div class="el btn btnPaypal" location="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BR2CC82WUAVEA">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/isleward/tags/v0.5.1">v0.5.1</div>
<div class="version" location="https://gitlab.com/Isleward/isleward/tags/v0.6">v0.6</div>
</div>

+ 0
- 1
src/client/ui/templates/menu/menu.js Переглянути файл

@@ -15,7 +15,6 @@ define([
this.find('.btnCollapse').on('click', this.toggleButtons.bind(this));
}

this.find('.btnSmithing').on('click', this.handler.bind(this, 'onShowSmithing'));
this.find('.btnHelp').on('click', this.handler.bind(this, 'onShowHelp'));
this.find('.btnInventory').on('click', this.handler.bind(this, 'onShowInventory'));
this.find('.btnEquipment').on('click', this.handler.bind(this, 'onShowEquipment'));


+ 12
- 15
src/client/ui/templates/menu/styles.less Переглянути файл

@@ -1,5 +1,4 @@
@import "../../../css/colors.less";

@pad: 8px;
@btnSize: 64px;

@@ -7,8 +6,7 @@
position: absolute;
right: 10px;
bottom: 10px;

width: ((@btnSize * 5) + (@pad * 12));
width: ((@btnSize * 4) + (@pad * 10));
height: ((@btnSize * 2) + (@pad * 2));
padding: @pad;
background-color: fade(@darkGray, 90%);
@@ -17,9 +15,8 @@
width: @btnSize;
height: @btnSize;
float: left;
margin: 0px 8px;
margin: 0px @pad;
float: left;

cursor: pointer;
position: relative;

@@ -40,10 +37,6 @@
display: none;
}

&.btnInventory {
margin: 0px 8px 0px 45px;
}

&.btnInventory .icon {
background: url('../../../images/uiIcons.png') 0px 0px;
}
@@ -57,7 +50,6 @@
}

&.btnPassives {
width: 65px;
position: relative;

.points {
@@ -67,12 +59,12 @@
bottom: 23px;
position: absolute;
color: @white;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

}

&.btnPassives .icon {
@@ -98,7 +90,9 @@
&.btnMainMenu .icon {
background: url('../../../images/uiIcons.png') 0px -64px;
}

}

}

.mobile .uiMenu {
@@ -157,6 +151,9 @@
height: 100%;
background: url('../../../images/uiIcons.png') -448px 0px;
}

}

}

}

+ 0
- 3
src/client/ui/templates/menu/template.html Переглянути файл

@@ -5,9 +5,6 @@
<div class="btnEquipment">
<div class="icon"></div>
</div>
<div class="btnSmithing">
<div class="icon"></div>
</div>
<div class="btnPassives">
<div class="icon"></div>
<div class="points"></div>


+ 0
- 11
src/client/ui/templates/middleHud/middleHud.js Переглянути файл

@@ -14,11 +14,6 @@ define([
this.onEvent('onGetSelfCasting', this.onGetCasting.bind(this));

if (isMobile) {
this.onEvent('onEnterGatherNode', this.toggleGatherButton.bind(this, true));
this.onEvent('onExitGatherNode', this.toggleGatherButton.bind(this, false));
this.onEvent('onRespawn', this.toggleGatherButton.bind(this, false));
this.onEvent('onShowProgress', this.toggleGatherButton.bind(this, false));

this.onEvent('onGetServerActions', this.onGetServerActions.bind(this));

this.find('.btnGather').on('click', this.gather.bind(this));
@@ -38,12 +33,6 @@ define([
}
},

toggleGatherButton: function (show) {
let btn = this.find('.btnGather').hide();
if (show)
btn.show();
},

gather: function () {
let btn = this.find('.btnGather');
let action = btn.data('action');


+ 0
- 343
src/client/ui/templates/smithing/smithing.js Переглянути файл

@@ -1,343 +0,0 @@
define([
'js/system/events',
'js/system/client',
'html!ui/templates/smithing/template',
'css!ui/templates/smithing/styles',
'html!/ui/templates/smithing/templateItem',
'js/misc/statTranslations'
], function (
events,
client,
template,
styles,
templateItem,
statTranslations
) {
return {
tpl: template,

centered: true,

modal: true,
hasClose: true,

eventCloseInv: null,
eventClickInv: null,

hoverItem: null,
item: null,

waiting: false,

action: 'augment',

postRender: function () {
this.onEvent('onShowSmithing', this.toggle.bind(this));
this.onEvent('onKeyDown', this.onKey.bind(this, true));
this.onEvent('onKeyUp', this.onKey.bind(this, false));

this.find('.item-picker').on('click', this.openInventory.bind(this));
this.find('.actionButton').on('click', this.smith.bind(this));

//If we don't listen to these events, they'll be queued
this.onEvent('onHideInventory', () => {});
this.onEvent('beforeInventoryClickItem', () => {});

this.onEvent('onGetItems', this.onGetItems.bind(this));
this.onEvent('onSetSmithItem', this.onHideInventory.bind(this));

this.find('.col-btn').on('click', this.clickAction.bind(this));
},

clickAction: function (e) {
let el = $(e.target);
this.find('.col-btn').removeClass('selected');

let action = el.attr('action');
let changed = (action !== this.action);
this.action = action;

el.addClass('selected');

if (this.item && changed)
this.getMaterials(this.item);
},

smith: function () {
this.setDisabled(true);

client.request({
cpn: 'player',
method: 'performAction',
data: {
cpn: 'inventory',
method: 'enchantItem',
data: {
itemId: this.item.id,
action: this.action
}
},
callback: this.onSmith.bind(this, this.item)
});
},

onSmith: function (item, result) {
this.setDisabled(false);

let msg = {
msg: 'Item Enhancement Succeeded',
type: 'success',
zIndex: 9999999,
top: 100
};
if (this.action === 'reroll')
msg.msg = 'Item Reroll Succeeded';
else if (this.action === 'relevel')
msg.msg = 'Item Relevel Succeeded';
else if (this.action === 'reslot')
msg.msg = 'Item Reslot Succeeded';

result.addStatMsgs.forEach(a => {
let statName = statTranslations.translate(a.stat);
msg.msg += `<br />${(a.value > 0) ? '+' : ''}${a.value} ${statName}`;
});

events.emit('onGetAnnouncement', msg);

if (result.item)
this.item = result.item;

this.getMaterials(this.item);

let augment = this.find('[action="augment"]').addClass('disabled');
if ((result.item.power || 0) < 3)
augment.removeClass('disabled');
else if (this.action === 'augment')
this.find('[action="reroll"]').click();
},

openInventory: function () {
this.waiting = true;

this.eventCloseInv = this.onEvent('onHideInventory', this.onHideInventory.bind(this));
this.eventClickInv = this.onEvent('beforeInventoryClickItem', this.onHideInventory.bind(this));

events.emit('onShowInventory');
},

onHideInventory: function (msg) {
if (msg)
msg.success = false;

this.waiting = false;

if (!msg || !msg.item) {
this.offEvent(this.eventCloseInv);
this.offEvent(this.eventClickInv);

return;
} else if (!msg.item.slot || msg.item.noAugment) {
let resultMsg = {
msg: 'Incorrect Item Type',
type: 'failure',
zIndex: 9999999,
top: 180
};
events.emit('onGetAnnouncement', resultMsg);

return;
}

this.find('.selected').removeClass('selected');
this.find('[action="augment"]').addClass('selected');
this.action = 'augment';

let augment = this.find('[action="augment"]').addClass('disabled');
if ((msg.item.power || 0) < 3)
augment.removeClass('disabled');

let reforge = this.find('[action="reforge"]').addClass('disabled');
if (msg.item.spell)
reforge.removeClass('disabled');

let reslot = this.find('[action="reslot"]').addClass('disabled');
if (!msg.item.effects && msg.item.slot !== 'tool')
reslot.removeClass('disabled');

let relevel = this.find('[action="relevel"]').addClass('disabled');
if (msg.item.slot !== 'tool')
relevel.removeClass('disabled');

this.offEvent(this.eventClickInv);

$('.uiInventory').data('ui').hide();
this.show();

this.el.show();
this.shown = true;

msg.success = false;

if (!msg || !msg.item || !msg.item.slot)
return;

this.item = msg.item;

this.getMaterials(msg.item);
},

getMaterials: function (item) {
this.setDisabled(true);

client.request({
cpn: 'player',
method: 'performAction',
data: {
cpn: 'inventory',
method: 'getEnchantMaterials',
data: {
itemId: item.id,
action: this.action
}
},
callback: this.onGetMaterials.bind(this, item)
});
},

onGetMaterials: function (item, result) {
this.find('.item').remove();
this.drawItem(this.find('.item-picker'), item);

this.find('.actionButton').removeClass('disabled').addClass('disabled');

if (result.materials) {
let material = result.materials[0];
if (material) {
let hasMaterials = window.player.inventory.items.find(i => i.name === material.name);
if (hasMaterials) {
material.quantityText = hasMaterials.quantity + '/' + material.quantity;
hasMaterials = hasMaterials.quantity >= material.quantity;
} else {
if (!material.quantityText)
material.quantityText = '';
material.quantityText += '0/' + material.quantity;
}

if (hasMaterials)
this.find('.actionButton').removeClass('disabled');

this.drawItem(this.find('.material'), material, !hasMaterials);
}
}

this.setDisabled(false);
},

onGetItems: function (items) {
let elMaterial = this.find('.material .item');
if (!elMaterial.length)
return;

let itemMaterial = elMaterial.data('item');
let elQuantity = elMaterial.find('.quantity');
let invMaterial = items.find(i => i.name === itemMaterial.name) || { quantity: 0 };
let currentText = elQuantity.html().split('/');
let newText = invMaterial.quantity + '/' + currentText[1];
elQuantity.html(newText);

let elButton = this.find('.actionButton').removeClass('disabled');
if (invMaterial.quantity < currentText[1]) {
elButton.addClass('disabled');
elQuantity.addClass('red');
} else {
elButton.removeClass('disabled');
elQuantity.removeClass('red');
}
},

drawItem: function (container, item, redQuantity) {
container.find('.icon').hide();

let imgX = -item.sprite[0] * 64;
let imgY = -item.sprite[1] * 64;

let spritesheet = item.spritesheet || '../../../images/items.png';
if (item.material)
spritesheet = '../../../images/materials.png';
else if (item.quest)
spritesheet = '../../../images/questItems.png';
else if (item.type === 'consumable')
spritesheet = '../../../images/consumables.png';

let el = $(templateItem)
.appendTo(container);

el
.data('item', item)
.on('mousemove', this.onHover.bind(this, el, item))
.on('mouseleave', this.hideTooltip.bind(this, el, item))
.find('.icon')
.css('background', 'url(' + spritesheet + ') ' + imgX + 'px ' + imgY + 'px');

if (item.quantity) {
let quantityText = item.quantityText;
el.find('.quantity').html(quantityText);
if (redQuantity)
el.find('.quantity').addClass('red');
}
},

onHover: function (el, item, e) {
if (item)
this.hoverItem = item;
else
item = this.hoverItem;

let ttPos = null;

if (el) {
ttPos = {
x: ~~(e.clientX + 32),
y: ~~(e.clientY)
};
}

events.emit('onShowItemTooltip', item, ttPos, true);
},

hideTooltip: function (el, item, e) {
events.emit('onHideItemTooltip', this.hoverItem);
this.hoverItem = null;
},

beforeHide: function () {
if (this.waiting)
return;

this.item = null;
this.offEvent(this.eventCloseInv);
this.offEvent(this.eventClickInv);
},

toggle: function () {
this.shown = !this.el.is(':visible');

if (this.shown) {
this.find('.item').remove();
this.find('.icon').show();
this.find('.actionButton').removeClass('disabled').addClass('disabled');
this.show();
} else
this.hide();
},

onKey: function (isDown, key) {
if (isDown && key === 'm')
this.toggle();
else if (key === 'shift' && this.hoverItem)
this.onHover();
}
};
});

+ 0
- 186
src/client/ui/templates/smithing/styles.less Переглянути файл

@@ -1,186 +0,0 @@
@import "../../../css/colors.less";

.uiSmithing {
display: none;
background-color: @blackB;
border: 5px solid @blackB;
text-align: center;

> .heading {
color: @blueA;
width: 100%;
height: 36px;
background-color: @blackB;

.heading-text {
padding-top: 8px;
margin: auto;
}

}

.bottom {
height: 170px;
background-color: @blackC;
padding: 10px;
}

.col {
float: left;
margin-right: 10px;

&:last-child {
margin-right: 0px;
}

.heading {
width: 80px;
height: 16px;
color: @white;
text-align: center;
margin-bottom: 10px;
}

.content {
width: 80px;
height: 80px;
background-color: @blackD;

&.chance {
padding-top: 32px;
color: @white;
text-align: center;
}

&.item-picker,
&.actionButton {
cursor: pointer;

&:hover {
background-color: @blackC;
}

}

&.item-picker {
> .icon {
margin: 8px;
display: inline-block;
width: 64px;
height: 64px;
background: url('../../../images/uiIcons.png') -256px -64px;
}

}

&.actionButton {
padding: 8px;

.icon {
width: 64px;
height: 64px;
background: url('../../../images/uiIcons.png') -192px -64px;
}

}

.item {
width: 100%;
height: 100%;
float: left;
position: relative;
cursor: pointer;
box-sizing: border-box;

.quantity {
left: 6px;
bottom: 3px;
position: absolute;
color: @white;
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

&.red {
color: @red;
}

}

.icon {
width: 64px;
height: 64px;
position: absolute;
left: 8px;
top: 8px;
filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
-moz-filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
}

}

}

&:first-child {
.heading {
width: 190px;
}

.content {
width: 190px;
background-color: transparent;

.col-btn {
height: calc((100% - 10px) / 2);
width: 100%;
color: #f2f5f5;
text-align: center;
padding-top: 10px;
background-color: @blackB;
margin-bottom: 10px;
cursor: pointer;

&.selected {
color: @orangeA;
}

&:hover {
background-color: @blackA;
}

&.col-half {
width: calc((100% - 10px) / 2);
float: left;

&:nth-child(2n + 1) {
margin-right: 10px;
}

}

&:not(.col-half) {
clear: both;
}

}

}

}

}

}

+ 0
- 34
src/client/ui/templates/smithing/template.html Переглянути файл

@@ -1,34 +0,0 @@
<div class="uiSmithing">
<div class="heading">
<div class="heading-text">craft item</div>
</div>
<div class="bottom">
<div class="col">
<div class="heading">action</div>
<div class="content">
<div class="col-btn col-half selected" action="augment">augment</div>
<div class="col-btn col-half" action="scour">scour</div>
<div class="col-btn col-half" action="reroll">reroll</div>
<div class="col-btn col-half" action="relevel">relevel</div>
<div class="col-btn col-half" action="reslot">reslot</div>
<div class="col-btn col-half" action="reforge">reforge</div>
</div>
</div>
<div class="col">
<div class="heading">item</div>
<div class="content item-picker">
<div class="icon"></div>
</div>
</div>
<div class="col">
<div class="heading">material</div>
<div class="content material"></div>
</div>
<div class="col">
<div class="heading"></div>
<div class="content actionButton disabled">
<div class="icon"></div>
</div>
</div>
</div>
</div>

+ 0
- 4
src/client/ui/templates/smithing/templateItem.html Переглянути файл

@@ -1,4 +0,0 @@
<div class="item">
<div class="icon"></div>
<div class="quantity"></div>
</div>

+ 66
- 12
src/client/ui/templates/workbench/styles.less Переглянути файл

@@ -2,17 +2,20 @@

.uiWorkbench {
display: none;
width: 720px;
height: 385px;
border: 5px solid fade(@blackB, 90%);
text-align: center;
width: 827px;
height: 447px;
border: 5px solid @blackB;
color: @white;
position: relative;
z-index: 2;

> .heading {
color: @blueA;
> .heading,
> .itemPicker > .heading {
color: @orangeA;
width: 100%;
height: 36px;
background-color: fade(@blackB, 90%);
background-color: @blackB;
text-align: center;

.heading-text {
padding-top: 8px;
@@ -21,14 +24,15 @@

}

.bottom {
background-color: fade(@blackC, 90%);
> .bottom {
background-color: @blackC;
height: calc(100% - 36px);
width: 100%;

.heading {
color: @blueB;
margin-bottom: 10px;
text-align: center;
}

.left,
@@ -44,14 +48,18 @@
.list {
height: calc(100% - 25px);
overflow-y: auto;
display: flex;
flex-direction: column;

.item {
width: 100%;
padding: 5px 0px;
padding: 5px 10px;
cursor: pointer;
color: @grayB;

&.selected {
background-color: @blackB;
color: @white;
}

&:hover {
@@ -66,6 +74,9 @@

.right {
width: calc(100% - 300px);
display: flex;
flex-direction: column;
justify-content: space-between;

> * {
width: 100%;
@@ -73,17 +84,24 @@

.info {
height: calc(100% - 100px - 35px);
flex: 1;

.title {
color: @orangeA;
color: @blueB;
padding-bottom: 10px;
text-align: center;
}

.description {
color: @grayB;
text-align: justify;
}

}

.materialList {
height: 100px;
visibility: hidden;
margin-bottom: 20px;

.material {
&.need {
@@ -94,6 +112,24 @@

}

.needItems {
display: none;
margin-bottom: 20px;
flex-direction: column;

.title {
color: @blueB;
padding-bottom: 10px;
text-align: center;
}

.list {
display: flex;
justify-content: space-around;
}

}

.buttons {
height: 40px;

@@ -123,6 +159,24 @@

}

> .itemPicker {
display: none;
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-color: @blackC;
flex-direction: column;

.list {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
}

}

}

.mobile .uiWorkbench {


+ 13
- 1
src/client/ui/templates/workbench/template.html Переглянути файл

@@ -1,6 +1,6 @@
<div class="uiWorkbench">
<div class="heading">
<div class="heading-text">Workbench</div>
<div class="mainHeading heading-text">Workbench</div>
</div>
<div class="bottom">
<div class="left">
@@ -15,6 +15,10 @@
<div class="title"></div>
<div class="description"></div>
</div>
<div class="needItems">
<div class="title">Pick Items</div>
<div class="list"></div>
</div>
<div class="materialList">
<div class="heading">Requires:</div>
</div>
@@ -24,4 +28,12 @@
</div>
</div>
</div>
<div class="itemPicker">
<div class="heading">
<div class="heading-text"></div>
</div>
<div class="list">
</div>
</div>
</div>

+ 203
- 18
src/client/ui/templates/workbench/workbench.js Переглянути файл

@@ -2,12 +2,16 @@ define([
'js/system/events',
'js/system/client',
'html!ui/templates/workbench/template',
'css!ui/templates/workbench/styles'
'css!ui/templates/workbench/styles',
'ui/shared/renderItem',
'js/misc/statTranslations'
], function (
events,
client,
template,
styles
styles,
renderItem,
statTranslations
) {
return {
tpl: template,
@@ -22,9 +26,18 @@ define([
recipes: null,
currentRecipe: null,

selectedNeedItems: null,

hoverItem: null,
hoverEl: null,

postRender: function () {
this.onEvent('onOpenWorkbench', this.onOpenWorkbench.bind(this));
this.onEvent('onCloseWorkbench', this.hide.bind(this));
this.onEvent('onGetItems', this.onGetItems.bind(this));

this.onEvent('onKeyDown', this.onKeyDown.bind(this));
this.onEvent('onKeyUp', this.onKeyUp.bind(this));

this.on('.btnCraft', 'click', this.craft.bind(this));
this.on('.btnCancel', 'click', this.hide.bind(this));
@@ -32,17 +45,29 @@ define([

onOpenWorkbench: function (msg) {
this.workbenchId = msg.workbenchId;
this.find('.heading-text').html(msg.name);
this.find('.mainHeading').html(msg.name);
this.find('.itemPicker').hide();
this.find('.needItems').hide();

this.renderRecipes(msg.recipes);

this.show();
},

//Redraw items if they change
onGetItems: function (items) {
if (!this.currentRecipe)
return;

const { currentRecipe: { needItems } } = this;
this.buildNeedItemBoxes(needItems, true);
},

renderRecipes: function (recipes) {
this.recipes = recipes;

let container = this.find('.list').empty();
let container = this.find('.left .list').empty();

recipes.forEach(function (r) {
let el = $('<div class="item">' + r + '</div>')
@@ -64,18 +89,20 @@ define([
cpn: 'workbench',
method: 'getRecipe',
data: {
name: recipeName
name: recipeName
}
},
callback: this.onGetRecipe.bind(this)
callback: this.onGetRecipe.bind(this, false)
});
},

onGetRecipe: function (recipe) {
onGetRecipe: function (persistNeedItems, recipe) {
const { name: recipeName, description, materials, needItems } = recipe;

this.currentRecipe = recipe;

this.find('.title').html(recipe.name);
this.find('.description').html(recipe.description);
this.find('.info .title').html(recipeName);
this.find('.description').html(description);

this.find('.materialList .material').remove();

@@ -84,13 +111,16 @@ define([
visibility: 'visible'
});

let canCraft = true;
let canCraft = !!materials.length;

materials.forEach(m => {
const { needQuantity, nameLike, name: materialName, haveQuantity, noHaveEnough } = m;

recipe.materials.forEach(function (m) {
let el = $('<div class="material">' + m.quantity + 'x ' + (m.nameLike || m.name) + '</div>')
const materialText = `${needQuantity}x ${(nameLike || materialName)} (${haveQuantity})`;
let el = $(`<div class="material">${materialText}</div>`)
.appendTo(container);

if (m.need) {
if (noHaveEnough) {
canCraft = false;
el.addClass('need');
}
@@ -103,10 +133,138 @@ define([
this.find('.btnCraft')
.addClass('disabled');
}

//If there are no materials, the selected items aren't valid
this.find('.materialList').show();
if (!materials.length) {
this.find('.materialList').hide();
persistNeedItems = false;
}

this.buildNeedItemBoxes(needItems, persistNeedItems);
},

buildNeedItemBoxes: function (needItems = [], persistNeedItems) {
if (!persistNeedItems) {
this.selectedNeedItems = new Array(needItems.length);
this.selectedNeedItems.fill(null);
}

const container = this.find('.needItems').hide();
const list = container.find('.list').empty();
if (!needItems.length)
return;

container.css({ display: 'flex' });

needItems.forEach((n, i) => this.buildNeedItemBox(list, n, i));
},

buildNeedItemBox: function (container, needItem, needItemIndex) {
const item = this.selectedNeedItems[needItemIndex];
const el = renderItem(container, item);

el
.on('mousemove', this.toggleTooltip.bind(this, true, el, needItem, item))
.on('mouseleave', this.toggleTooltip.bind(this, false, el, needItem, item))
.on('click', this.toggleItemPicker.bind(this, true, needItem, needItemIndex));
},

toggleItemPicker: function (show, needItem, needItemIndex) {
const container = this.find('.itemPicker').hide();
if (!show)
return;

const { allowedItemIds } = needItem;

container
.css({ display: 'flex' })
.find('.heading-text').html(needItem.info);

const list = container.find('.list').empty();

const items = window.player.inventory.items
.filter(item => {
const isValidItem = allowedItemIds.find(f => f === item.id);

return isValidItem;
});

items.forEach(item => {
const el = renderItem(list, item);

el
.on('click', this.onSelectItem.bind(this, item, needItemIndex))
.on('mousemove', this.toggleTooltip.bind(this, true, el, null, item))
.on('mouseleave', this.toggleTooltip.bind(this, false, el, null, item));
});
},

onSelectItem: function (item, needItemIndex) {
this.selectedNeedItems[needItemIndex] = item;

const { currentRecipe: { needItems } } = this;
this.buildNeedItemBoxes(needItems, true);

const allItemsSelected = this.selectedNeedItems.every(i => !!i);
if (allItemsSelected && this.currentRecipe.dynamicMaterials) {
const pickedItemIds = this.selectedNeedItems.map(i => i.id);

client.request({
cpn: 'player',
method: 'performAction',
data: {
targetId: this.workbenchId,
cpn: 'workbench',
method: 'getRecipe',
data: {
name: this.currentRecipe.name,
pickedItemIds
}
},
callback: this.onGetRecipe.bind(this, true)
});
}

this.find('.itemPicker').hide();
},

toggleTooltip: function (show, el, needItem, item, e) {
if (item) {
this.hoverItem = show ? item : null;
this.hoverEl = show ? el : null;
}

let pos = null;
if (e) {
const { clientX, clientY } = e;
pos = {
x: clientX + 25,
y: clientY
};
}

if (item) {
if (show)
events.emit('onShowItemTooltip', item, pos, true);
else
events.emit('onHideItemTooltip', item);

return;
}

if (show)
events.emit('onShowTooltip', needItem.info, el[0], pos);
else
events.emit('onHideTooltip', el[0]);
},

craft: function () {
let selectedRecipe = this.find('.list .item.selected').html();
const selectedRecipe = this.find('.left .list .item.selected').html();

const pickedItemIds = this.selectedNeedItems
.map(item => item.id);

client.request({
cpn: 'player',
@@ -116,15 +274,32 @@ define([
cpn: 'workbench',
method: 'craft',
data: {
name: selectedRecipe
name: selectedRecipe,
pickedItemIds
}
},
callback: this.onCraft.bind(this)
});
},

onCraft: function (recipe) {
this.onGetRecipe(recipe);
onCraft: function ({ recipe, resultMsg }) {
this.onGetRecipe(true, recipe);
if (resultMsg) {
const { msg: baseMsg, addStatMsgs = [] } = resultMsg;

let msg = baseMsg;

addStatMsgs.forEach(a => {
const statName = statTranslations.translate(a.stat);
msg += `<br />${(a.value > 0) ? '+' : ''}${a.value} ${statName}`;
});

events.emit('onGetAnnouncement', {
msg,
top: 150
});
}
},

onAfterShow: function () {
@@ -133,7 +308,7 @@ define([

clear: function () {
this.find('.left .list .selected').removeClass('selected');
this.find('.title').html('');
this.find('.info .title').html('');
this.find('.description').html('');
this.find('.materialList .material').remove();
this.find('.materialList')
@@ -141,6 +316,16 @@ define([
visibility: 'hidden'
});
this.find('.btnCraft').addClass('disabled');
},

onKeyDown: function (key) {
if (key === 'shift' && this.hoverItem)
this.toggleTooltip(true, this.hoverEl, null, this.hoverItem);
},
onKeyUp: function (key) {
if (key === 'shift' && this.hoverItem)
this.toggleTooltip(true, this.hoverEl, null, this.hoverItem);
}
};
});

+ 5
- 7
src/server/components/components.js Переглянути файл

@@ -14,13 +14,11 @@ module.exports = {
},

getComponentFolder: function () {
let files = fileLister.getFolder('./components/');
files = files.filter(w => (
(w.indexOf('components') === -1) &&
(w.indexOf('cpnBase') === -1) &&
(w.indexOf('projectile') === -1)
));
let fLen = files.length;
const ignoreFiles = ['components.js', 'componentBase.js'];
const files = fileLister.getFolder('./components/')
.filter(f => !ignoreFiles.includes(f));

const fLen = files.length;
for (let i = 0; i < fLen; i++)
this.getComponentFile(`./${files[i]}`);



+ 4
- 10
src/server/components/door.js Переглянути файл

@@ -119,21 +119,15 @@ module.exports = {
if (this.autoClose)
this.autoCloseCd = this.autoClose;

let key = obj.inventory.items.find(i => ((i.keyId === this.key) || (i.keyId === 'world')));
let key = obj.inventory.items.find(i => (i.keyId === this.key || i.keyId === 'world'));
if (!key)
return;

if (((key.singleUse) || (this.destroyKey)) && (key.keyId !== 'world')) {
if ((key.singleUse || this.destroyKey) && key.keyId !== 'world') {
obj.inventory.destroyItem(key.id, 1);

obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'color-redA',
message: 'The ' + key.name + ' disintegrates on use',
type: 'info'
}]
}, [obj.serverId]);
const message = `The ${key.name} disintegrates on use`;
obj.social.notifySelf({ message });
}
}



+ 13
- 32
src/server/components/equipment.js Переглянути файл

@@ -89,14 +89,8 @@ module.exports = {
};
obj.fireEvent('beforeEquipItem', equipMsg);
if (!equipMsg.success) {
obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'color-redA',
message: equipMsg.msg || 'you cannot equip that item',
type: 'info'
}]
}, [obj.serverId]);
const message = equipMsg.msg || 'you cannot equip that item';
obj.social.notifySelf({ message });

return;
}
@@ -154,14 +148,8 @@ module.exports = {
if (!item)
return;
else if (!ignoreSpaceCheck && !inventory.hasSpace()) {
obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'color-redA',
message: 'You do not have room in your inventory to unequip that item',
type: 'info'
}]
}, [obj.serverId]);
const message = 'You do not have room in your inventory to unequip that item';
obj.social.notifySelf({ message });

return;
}
@@ -277,14 +265,10 @@ module.exports = {
level: `Your level is too low to equip your ${item.name}`
})[errors[0]];

this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: message,
type: 'rep'
}]
}, [this.obj.serverId]);
this.obj.social.notifySelf({
message,
type: 'rep'
});
}
}, this);
},
@@ -308,14 +292,11 @@ module.exports = {
if (findFaction.tier > tier) {
this.unequip(itemId);

this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: 'You unequip your ' + item.name + ' as it zaps you.',
type: 'rep'
}]
}, [this.obj.serverId]);
const message = `You unequip your ${item.name} as it zaps you.`;
this.obj.social.notifySelf({
message,
type: 'rep'
});
}
}, this);
},


+ 2
- 8
src/server/components/extensions/factionVendor.js Переглянути файл

@@ -161,14 +161,8 @@ module.exports = {
result = requestedBy.reputation.canEquipItem(item);

if (!result) {
requestedBy.instance.syncer.queue('onGetMessages', {
id: requestedBy.id,
messages: [{
class: 'color-redA',
message: 'your reputation is too low to buy that item',
type: 'info'
}]
}, [requestedBy.serverId]);
const message = 'your reputation is too low to buy that item';
requestedBy.social.notifySelf({ message });
}

return result;


+ 33
- 31
src/server/components/gatherer.js Переглянути файл

@@ -187,6 +187,7 @@ module.exports = {
this.sendAnnouncement('The school has been depleted');

this.nodes.spliceWhere(n => (n === gathering));
this.updateServerActions(false);
}

this.gathering = null;
@@ -201,36 +202,33 @@ module.exports = {
},

enter: function (node) {
const { obj } = this;

let gatherResult = extend({
nodeName: node.name
});
this.obj.instance.eventEmitter.emitNoSticky('beforeEnterPool', gatherResult, this.obj);
obj.instance.eventEmitter.emitNoSticky('beforeEnterPool', gatherResult, obj);

let nodeType = node.resourceNode.nodeType;
let msg = `Press G to $ (${gatherResult.nodeName})`;
msg = msg.replace('$', {
herb: 'gather',
fish: 'fish for'
}[nodeType]);

if (nodeType === 'fish') {
if (!this.obj.equipment.eq.has('tool'))
msg = 'You need a fishing rod to fish';
if (!obj.equipment.eq.has('tool')) {
this.sendAnnouncement('You need a fishing rod to fish');

return;
}
}

this.sendAnnouncement(msg);
this.updateServerActions(true);

process.send({
method: 'events',
data: {
onEnterGatherNode: [{
obj: {
id: node.id
},
to: [this.obj.serverId]
}]
}
});
let action = null;
if (nodeType === 'fish')
action = 'fish for';
else if (nodeType === 'herb')
action = 'gather the';
const actionString = `${action} ${gatherResult.nodeName}`;

this.sendAnnouncement(`Press U to ${actionString}`);

this.nodes.spliceWhere(n => (n === node));
this.nodes.push(node);
@@ -240,17 +238,7 @@ module.exports = {
if (!this.nodes.includes(node))
return;

process.send({
method: 'events',
data: {
onExitGatherNode: [{
obj: {
id: node.id
},
to: [this.obj.serverId]
}]
}
});
this.updateServerActions(false);

this.nodes.spliceWhere(n => (n === node));
},
@@ -269,6 +257,20 @@ module.exports = {
});
},

updateServerActions: function (isAdd) {
const { obj } = this;

const action = isAdd ? 'addActions' : 'removeActions';
obj.syncer.setArray(true, 'serverActions', action, {
key: 'u',
action: {
targetId: obj.id,
cpn: 'gatherer',
method: 'gather'
}
});
},

events: {
beforeRezone: function () {
this.events.beforeMove.call(this);


+ 13
- 68
src/server/components/inventory.js Переглянути файл

@@ -1,6 +1,5 @@
let generator = require('../items/generator');
let salvager = require('../items/salvager');
let enchanter = require('../items/enchanter');
let classes = require('../config/spirits');
let mtx = require('../mtx/mtx');
let factions = require('../config/factions');
@@ -10,8 +9,6 @@ const events = require('../misc/events');
const { isItemStackable } = require('./inventory/helpers');
const transactions = require('../security/transactions');

const { applyItemStats } = require('./equipment/helpers');

const getItem = require('./inventory/getItem');
const dropBag = require('./inventory/dropBag');
const useItem = require('./inventory/useItem');
@@ -151,45 +148,6 @@ module.exports = {
}
},

enchantItem: function (msg) {
const { itemId, action } = msg;
const item = this.findItem(itemId);
if (!item)
return;

const { eq, slot, power, noAugment } = item;

if (!slot || noAugment || (action === 'scour' && !power)) {
this.resolveCallback(msg);
return;
}

const obj = this.obj;

if (eq) {
applyItemStats(obj, item, false);
enchanter.enchant(obj, item, msg);
applyItemStats(obj, item, true);

if (item.slot !== slot)
obj.equipment.unequip(itemId);
else
obj.spellbook.calcDps();
} else
enchanter.enchant(obj, item, msg);

obj.equipment.unequipAttrRqrGear();
},

getEnchantMaterials: function (msg) {
let result = [];
let item = this.findItem(msg.itemId);
if ((item) && (item.slot))
result = enchanter.getEnchantMaterials(item, msg.action);

this.resolveCallback(msg, result);
},

learnAbility: function (itemId, runeSlot) {
if (itemId.has('itemId')) {
let msg = itemId;
@@ -215,14 +173,8 @@ module.exports = {
};
this.obj.fireEvent('beforeLearnAbility', learnMsg);
if (!learnMsg.success) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: learnMsg.msg || 'you cannot learn that ability',
type: 'info'
}]
}, [this.obj.serverId]);
const message = learnMsg.msg || 'you cannot learn that ability';
this.obj.social.notifySelf({ message });

return;
}
@@ -365,15 +317,12 @@ module.exports = {
this.getItem(material, true, false, false, true);
messages.push({
class: 'q' + material.quality,
className: 'q' + material.quality,
message: 'salvage (' + material.name + ' x' + material.quantity + ')'
});
}
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: messages
}, [this.obj.serverId]);

this.obj.social.notifySelfArray(messages);
},

destroyItem: function (id, amount, force) {
@@ -515,8 +464,11 @@ module.exports = {
try {
let effectModule = require('../' + effectUrl);
e.events = effectModule.events;
e.text = effectModule.events.onGetText(item, e);
} catch (error) {}
if (effectModule.events.onGetText)
e.text = effectModule.events.onGetText(item, e);
} catch (error) {
_.log(`Effect not found: ${e.type}`);
}
}
});
}
@@ -623,7 +575,7 @@ module.exports = {
let item = generator.generate({
type: classes.weapons[this.obj.class],
quality: 0,
spellQuality: 'basic'
spellQuality: 0
});
item.worth = 0;
item.eq = true;
@@ -642,7 +594,7 @@ module.exports = {
if (!hasSpell) {
let item = generator.generate({
spell: true,
spellQuality: 'basic',
spellQuality: 0,
spellName: spellName
});
item.worth = 0;
@@ -805,13 +757,6 @@ module.exports = {
},

notifyNoBagSpace: function (message = 'Your bags are too full to loot any more items') {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message,
type: 'info'
}]
}, [this.obj.serverId]);
this.obj.social.notifySelf({ message });
}
};

+ 2
- 8
src/server/components/inventory/useItem.js Переглянути файл

@@ -40,6 +40,7 @@ module.exports = async (cpnInv, itemId) => {

let result = {};
obj.instance.eventEmitter.emit('onBeforeUseItem', obj, item, result);
obj.fireEvent('onBeforeUseItem', item, result);

if (item.recipe) {
const didLearn = await learnRecipe(obj, item);
@@ -68,14 +69,7 @@ module.exports = async (cpnInv, itemId) => {
effectEvent.call(obj, effectResult, item, effect);

if (!effectResult.success) {
obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'color-redA',
message: effectResult.errorMessage,
type: 'info'
}]
}, [obj.serverId]);
obj.social.notifySelf({ message: effectResult.errorMessage });

return;
}


+ 3
- 0
src/server/components/mob.js Переглянути файл

@@ -41,6 +41,9 @@ module.exports = {

if (blueprint.patrol)
this.patrol = blueprint.patrol;

if (blueprint.maxChaseDistance)
this.maxChaseDistance = blueprint.maxChaseDistance;
},

update: function () {


+ 15
- 24
src/server/components/reputation.js Переглянути файл

@@ -121,14 +121,11 @@ module.exports = {
if (gain < 0)
action = 'lost';

this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: (action === 'gained') ? 'color-greenB' : 'color-redA',
message: 'you ' + action + ' ' + Math.abs(gain) + ' reputation with ' + blueprint.name,
type: 'rep'
}]
}, [this.obj.serverId]);
this.obj.social.notifySelf({
className: (action === 'gained') ? 'color-greenB' : 'color-redA',
message: 'you ' + action + ' ' + Math.abs(gain) + ' reputation with ' + blueprint.name,
type: 'rep'
});

if (faction.tier !== oldTier) {
this.sendMessage(blueprint.tiers[faction.tier].name, blueprint.name, (faction.tier > oldTier));
@@ -139,14 +136,11 @@ module.exports = {
},

sendMessage: function (tierName, factionName, didIncrease) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: didIncrease ? 'color-greenB' : 'color-redA',
message: 'you are now ' + tierName + ' with ' + factionName,
type: 'rep'
}]
}, [this.obj.serverId]);
this.obj.social.notifySelf({
className: didIncrease ? 'color-greenB' : 'color-redA',
message: 'you are now ' + tierName + ' with ' + factionName,
type: 'rep'
});
},

discoverFaction (factionId) {
@@ -167,14 +161,11 @@ module.exports = {
let tier = blueprint.tiers[this.calculateTier(factionId)].name.toLowerCase();

if (!blueprint.noGainRep) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q4',
message: 'you are now ' + tier + ' with ' + blueprint.name,
type: 'rep'
}]
}, [this.obj.serverId]);
this.obj.social.notifySelf({
className: 'q4',
message: 'you are now ' + tier + ' with ' + blueprint.name,
type: 'rep'
});
}

this.syncFaction(factionId, true);


+ 4
- 0
src/server/components/spellbook.js Переглянути файл

@@ -613,6 +613,10 @@ module.exports = {
this.stopCasting(null, true);
},

onBeforeUseItem: function () {
this.stopCasting(null, true);
},

clearQueue: function () {
this.stopCasting(null, true);
},


+ 7
- 24
src/server/components/stash.js Переглянути файл

@@ -69,14 +69,9 @@ module.exports = {
else if (this.items.length >= this.maxItems) {
let isStackable = this.items.some(stashedItem => item.name === stashedItem.name && (isItemStackable(stashedItem)));
if (!isStackable) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: 'You do not have room in your stash to deposit that item',
type: 'info'
}]
}, [this.obj.serverId]);
const message = 'You do not have room in your stash to deposit that item';
this.obj.social.notifySelf({ message });

return;
}
}
@@ -109,14 +104,8 @@ module.exports = {
if (!item)
return;
else if (!this.obj.inventory.hasSpace(item)) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: 'You do not have room in your inventory to withdraw that item',
type: 'info'
}]
}, [this.obj.serverId]);
const message = 'You do not have room in your inventory to withdraw that item';
this.obj.social.notifySelf({ message });
return;
}
@@ -146,14 +135,8 @@ module.exports = {
});

if (this.active && this.items.length > this.maxItems) {
obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-redA',
message: `You have more than ${this.maxItems} items in your stash. In the next version (v0.3.1) you will lose all items that put you over the limit`,
type: 'info'
}]
}, [obj.serverId]);
const message = `You have more than ${this.maxItems} items in your stash. In the future, these items will be lost.`;
obj.social.notifySelf({ message });
}
},



+ 6
- 1
src/server/components/wardrobe.js Переглянути файл

@@ -103,10 +103,15 @@ module.exports = {
return;

obj.skinId = msg.skinId;

obj.cell = skins.getCell(obj.skinId);
obj.sheetName = skins.getSpritesheet(obj.skinId);

obj.fireEvent('onBeforeSkinChange', {
newSkinId: obj.skinId,
newCell: obj.cell,
newSheetName: obj.sheetName
});

let syncer = obj.syncer;
syncer.set(false, null, 'cell', obj.cell);
syncer.set(false, null, 'sheetName', obj.sheetName);


+ 11
- 78
src/server/components/workbench.js Переглянути файл

@@ -1,13 +1,18 @@
const recipes = require('../config/recipes/recipes');
const generator = require('../items/generator');

const buildRecipe = require('./workbench/buildRecipe');
const craft = require('./workbench/craft');

module.exports = {
type: 'workbench',

craftType: null,

noticeMessage: null,

init: function (blueprint) {
this.craftType = blueprint.type;
this.noticeMessage = blueprint.noticeMessage;

this.obj.instance.objects.buildObjects([{
properties: {
@@ -55,7 +60,7 @@ module.exports = {
if (!obj.player)
return;

let msg = `Press U to access the ${this.obj.name}`;
let msg = `Press U to ${this.noticeMessage || `access the ${this.obj.name}`}`;

obj.syncer.setArray(true, 'serverActions', 'addActions', {
key: 'u',
@@ -98,93 +103,21 @@ module.exports = {
}, [obj.serverId]);
},

buildRecipe: function (crafter, recipeName) {
let recipe = recipes.getRecipe(this.craftType, recipeName);
if (!recipe)
return;

const items = crafter.inventory.items;

let sendRecipe = extend({}, recipe);
(sendRecipe.materials || []).forEach(function (m) {
m.need = !items.some(i => (
(
i.name === m.name ||
i.name.indexOf(m.nameLike) > -1
) &&
(
m.quantity === 1 ||
i.quantity >= m.quantity
)
));
});

return sendRecipe;
},

getRecipe: function (msg) {
let obj = this.obj.instance.objects.objects.find(o => o.serverId === msg.sourceId);
if ((!obj) || (!obj.player))
return;

const sendRecipe = this.buildRecipe(obj, msg.name);
const sendRecipe = buildRecipe(this.craftType, obj, msg);

this.resolveCallback(msg, sendRecipe);
},

craft: function (msg) {
let obj = this.obj.instance.objects.objects.find(o => o.serverId === msg.sourceId);
if ((!obj) || (!obj.player))
return;

let recipe = recipes.getRecipe(this.craftType, msg.name);
if (!recipe)
return;

const items = obj.inventory.items;
let canCraft = recipe.materials.every(m => (items.some(i => (
(
i.name === m.name ||
i.name.indexOf(m.nameLike) > -1
) &&
(
m.quantity === 1 ||
i.quantity >= m.quantity
)
))));

if (!canCraft)
return;

recipe.materials.forEach(m => {
let findItem = obj.inventory.items.find(f => (
f.name === m.name ||
f.name.indexOf(m.nameLike) > -1
));
obj.inventory.destroyItem(findItem.id, m.quantity);
});

let outputItems = recipe.item ? [ recipe.item ] : recipe.items;
outputItems.forEach(itemBpt => {
let item = null;
if (itemBpt.generate)
item = generator.generate(itemBpt);
else
item = extend({}, itemBpt);
if (item.description)
item.description += `<br /><br />(Crafted by ${obj.name})`;
else
item.description = `<br /><br />(Crafted by ${obj.name})`;
const quantity = item.quantity;
if (quantity && quantity.push)
item.quantity = quantity[0] + ~~(Math.random() * (quantity[1] - quantity[0]));

obj.inventory.getItem(item);
});
const result = craft(this, msg);

this.resolveCallback(msg, this.buildRecipe(obj, msg.name));
if (result)
this.resolveCallback(msg, result);
},

resolveCallback: function (msg, result) {


+ 49
- 0
src/server/components/workbench/buildMaterials.js Переглянути файл

@@ -0,0 +1,49 @@
const buildPickedItems = require('./buildPickedItems');

module.exports = (crafter, recipe, msg) => {
const { inventory: { items } } = crafter;
const { materialGenerator, materials, needItems = [] } = recipe;
const { pickedItemIds = [] } = msg;

const pickedItems = buildPickedItems(crafter, recipe, msg);
const allPickedItemsSet = (
pickedItemIds.length === needItems.length &&
!pickedItems.some(i => !i)
);

if (!allPickedItemsSet)
return [];

let useMaterials = materials;

if (materialGenerator)
useMaterials = materialGenerator(crafter, pickedItems);

const result = useMaterials.map(m => {
const { name, nameLike, quantity } = m;

const haveMaterial = items.find(i => (
i.name === name ||
i.name.includes(nameLike)
));

const id = haveMaterial ? haveMaterial.id : null;
const haveQuantity = haveMaterial ? (haveMaterial.quantity || 1) : 0;
const needQuantity = quantity;

const noHaveEnough = haveQuantity < needQuantity;

const material = {
id,
name,
nameLike,
haveQuantity,
needQuantity,
noHaveEnough
};

return material;
});

return result;
};

+ 29
- 0
src/server/components/workbench/buildNeedItems.js Переглянути файл

@@ -0,0 +1,29 @@
module.exports = ({ inventory: { items } }, { needItems }) => {
if (!needItems)
return null;

const result = needItems.map(n => {
const { info, withProps = [], withoutProps = [], checks = [] } = n;

const allowedItemIds = items
.filter(item => {
const isValidItem = (
withProps.every(p => item.has(p)) &&
withoutProps.every(p => !item.has(p)) &&
checks.every(c => c(item))
);

return isValidItem;
})
.map(item => item.id);

const needItem = {
info,
allowedItemIds
};

return needItem;
});

return result;
};

+ 19
- 0
src/server/components/workbench/buildPickedItems.js Переглянути файл

@@ -0,0 +1,19 @@
const buildNeedItems = require('./buildNeedItems');

module.exports = (crafter, recipe, { pickedItemIds = [] }) => {
const needItems = buildNeedItems(crafter, recipe);

const { inventory: { items } } = crafter;

const result = pickedItemIds.map((pickedId, i) => {
const item = items.find(f => f.id === pickedId);
const isItemValid = needItems[i].allowedItemIds.includes(item.id);

if (!isItemValid)
return null;
return item;
});

return result;
};

+ 30
- 0
src/server/components/workbench/buildRecipe.js Переглянути файл

@@ -0,0 +1,30 @@
const recipes = require('../../config/recipes/recipes');

const buildMaterials = require('./buildMaterials');
const buildNeedItems = require('./buildNeedItems');

const buildBase = (crafter, { name, description }) => {
return {
name,
description
};
};

module.exports = (craftType, crafter, msg) => {
const recipe = recipes.getRecipe(craftType, msg.name);
if (!recipe)
return;

const result = buildBase(crafter, recipe);

const needItems = buildNeedItems(crafter, recipe);
if (needItems)
result.needItems = needItems;

if (recipe.materialGenerator || recipe.needItems)
result.dynamicMaterials = true;

result.materials = buildMaterials(crafter, recipe, msg);

return result;
};

+ 97
- 0
src/server/components/workbench/craft.js Переглянути файл

@@ -0,0 +1,97 @@
const recipes = require('../../config/recipes/recipes');
const generator = require('../../items/generator');

const { applyItemStats } = require('../equipment/helpers');

const buildRecipe = require('../workbench/buildRecipe');
const buildMaterials = require('../workbench/buildMaterials');
const buildPickedItems = require('../workbench/buildPickedItems');

module.exports = (cpnWorkbench, msg) => {
const { craftType, obj: { instance: { objects: { objects } } } } = cpnWorkbench;
const { name: recipeName, sourceId } = msg;

const crafter = objects.find(o => o.serverId === sourceId);
if (!crafter || !crafter.player)
return null;

const recipe = recipes.getRecipe(craftType, recipeName);
if (!recipe)
return null;

const { needItems = [] } = recipe;
const { syncer, inventory, equipment, spellbook } = crafter;

const materials = buildMaterials(crafter, recipe, msg);
const pickedItems = buildPickedItems(crafter, recipe, msg);
const canCraft = (
!materials.some(m => m.noHaveEnough) &&
pickedItems.length === needItems.length &&
!pickedItems.some(i => !i)
);
if (!canCraft)
return null;

materials.forEach(m => inventory.destroyItem(m.id, m.needQuantity));

let resultMsg = null;

if (recipe.craftAction) {
pickedItems.forEach(p => {
if (p.eq)
applyItemStats(crafter, p, false);
});

const oldSlots = pickedItems.map(p => p.slot);
resultMsg = recipe.craftAction(crafter, pickedItems);

pickedItems.forEach((p, i) => {
if (!p.eq) {
pickedItems.forEach(item => syncer.setArray(true, 'inventory', 'getItems', inventory.simplifyItem(item)));
return;
}

applyItemStats(crafter, p, true);

if (p.slot !== oldSlots[i])
equipment.unequip(p.id);

spellbook.calcDps();

pickedItems.forEach(item => syncer.setArray(true, 'inventory', 'getItems', inventory.simplifyItem(item)));
});

equipment.unequipAttrRqrGear();
}

if (recipe.item || recipe.items) {
const outputItems = recipe.item ? [ recipe.item ] : recipe.items;
outputItems.forEach(itemBpt => {
let item = null;
if (itemBpt.generate)
item = generator.generate(itemBpt);
else
item = extend({}, itemBpt);
if (item.description)
item.description += `<br /><br />(Crafted by ${crafter.name})`;
else
item.description = `<br /><br />(Crafted by ${crafter.name})`;
const quantity = item.quantity;
if (quantity && quantity.push)
item.quantity = quantity[0] + ~~(Math.random() * (quantity[1] - quantity[0]));

crafter.inventory.getItem(item);
});
}

const result = {
resultMsg,
recipe: buildRecipe(craftType, crafter, msg)
};

return result;
};

+ 22
- 0
src/server/config/clientConfig.js Переглянути файл

@@ -4,6 +4,27 @@ const tos = require('./tos');
const config = {
logoPath: null,
resourceList: [],
textureList: [
'tiles',
'walls',
'mobs',
'bosses',
'animBigObjects',
'bigObjects',
'objects',
'characters',
'attacks',
'ui',
'auras',
'animChar',
'animMob',
'animBoss',
'white',
'ray',
'images/skins/0001.png',
'images/skins/0010.png',
'images/skins/0012.png'
],
uiList: [],
contextMenuActions: {
player: [],
@@ -21,6 +42,7 @@ module.exports = {
events.emit('onBeforeGetUiList', config.uiList);
events.emit('onBeforeGetContextMenuActions', config.contextMenuActions);
events.emit('onBeforeGetTermsOfService', config.tos);
events.emit('onBeforeGetTextureList', config.textureList);
},

getClientConfig: function (msg) {


+ 1
- 59
src/server/config/maps/fjolarok/dialogues.js Переглянути файл

@@ -179,66 +179,8 @@ module.exports = {
vikar: {
1: {
msg: [{
msg: 'Is there anything I can help you with today?',
options: [1.1]
}],
options: {
1.1: {
msg: 'I want to hand in some cards.',
prereq: function (obj) {
let fullSet = obj.inventory.items.find(i => ((i.setSize) && (i.setSize <= i.quantity)));
return !!fullSet;
},
goto: 'tradeCards'
}
}
},
tradeCards: {
msg: [{
msg: '',
msg: 'How may I help you today?',
options: []
}],
method: function (obj) {
let inventory = obj.inventory;
let items = inventory.items;

let sets = items.filter(function (i) {
return (
(i.type === 'Reward Card') &&
(i.quantity >= i.setSize)
);
});

if (sets.length === 0)
return 'Sorry, you don\'t have any completed sets.';

sets.forEach(function (s) {
obj.instance.eventEmitter.emit('onGetCardSetReward', s.name, obj);
inventory.destroyItem(s.id, s.setSize);
});

return 'Thank you.';
}
},
tradeBuy: {
cpn: 'trade',
method: 'startBuy',
args: [{
targetName: 'vikar'
}]
},
tradeSell: {
cpn: 'trade',
method: 'startSell',
args: [{
targetName: 'vikar'
}]
},
tradeBuyback: {
cpn: 'trade',
method: 'startBuyback',
args: [{
targetName: 'vikar'
}]
}
},


+ 958
- 925
src/server/config/maps/fjolarok/map.json
Різницю між файлами не показано, бо вона завелика
Переглянути файл


+ 175
- 16
src/server/config/maps/fjolarok/zone.js Переглянути файл

@@ -18,14 +18,6 @@ module.exports = {
type: 'fish',
quantity: [6, 12]
},
vikardoor: {
properties: {
cpnDoor: {
locked: true,
key: 'vikar'
}
}
},
shopestrid: {
properties: {
cpnNotice: {
@@ -523,6 +515,61 @@ module.exports = {
type: 'cooking'
}
}
},
'enchanting shrine': {
components: {
cpnParticles: {
simplify: function () {
return {
type: 'particles',
blueprint: {
color: {
start: ['48edff', 'fc66f7'],
end: ['393268', '42548d']
},
scale: {
start: {
min: 2,
max: 10
},
end: {
min: 0,
max: 2
}
},
speed: {
start: {
min: 4,
max: 16
},
end: {
min: 2,
max: 8
}
},
lifetime: {
min: 1,
max: 4
},
randomScale: true,
randomSpeed: true,
chance: 0.2,
randomColor: true,
spawnType: 'rect',
spawnRect: {
x: -15,
y: -28,
w: 30,
h: 8
}
}
};
}
},
cpnWorkbench: {
type: 'enchanting'
}
}
}
},
mobs: {
@@ -592,6 +639,8 @@ module.exports = {
},
thumper: {
level: 5,
cron: '0 * * * *',

regular: {
hpMult: 3,
dmgMult: 3,
@@ -805,56 +854,56 @@ module.exports = {
extra: [{
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'magic missile',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'ice spear',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'smite',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'consecrate',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'slash',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'charge',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'flurry',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'smokebomb',
worth: 3
@@ -957,6 +1006,116 @@ module.exports = {
}
}
}
},

sundfehr: {
level: 9,
walkDistance: 0,

cron: '0 */2 * * *',

regular: {
hpMult: 10,
dmgMult: 1,

drops: {
chance: 100,
rolls: 3,
magicFind: [2000]
}
},

rare: {
chance: 0
},

spells: [{
type: 'warnBlast',
range: 8,
delay: 9,
damage: 0.8,
statMult: 1,
cdMax: 7,
targetRandom: true,
particles: {
color: {
start: ['c0c3cf', '929398'],
end: ['929398', 'c0c3cf']
},
spawnType: 'circle',
spawnCircle: {
x: 0,
y: 0,
r: 12
},
randomColor: true,
chance: 0.03
}
}, {
type: 'projectile',
damage: 0.4,
statMult: 1,
cdMax: 5,
targetRandom: true,
row: 2,
col: 4
}],

components: {
cpnParticles: {
simplify: function () {
return {
type: 'particles',
blueprint: {
color: {
start: ['fc66f7', '802343'],
end: ['393268', 'de43ae']
},
scale: {
start: {
min: 10,
max: 18
},
end: {
min: 4,
max: 8
}
},
speed: {
start: {
min: 6,
max: 12
},
end: {
min: 2,
max: 4
}
},
lifetime: {
min: 5,
max: 12
},
alpha: {
start: 0.25,
end: 0
},
randomScale: true,
randomSpeed: true,
chance: 0.06,
randomColor: true,
spawnType: 'rect',
blendMode: 'add',
spawnRect: {
x: -24,
y: -24,
w: 48,
h: 48
}
}
};
}
}
}
}
}
};

+ 28
- 8
src/server/config/maps/sewer/map.json Переглянути файл

@@ -1,4 +1,12 @@
{ "backgroundcolor":"#32222e",
"compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":120,
"infinite":false,
"layers":[
@@ -2043,7 +2051,7 @@
"type":"",
"visible":true,
"width":8,
"x":568,
"x":528,
"y":736
},
{
@@ -2175,8 +2183,8 @@
"type":"",
"visible":true,
"width":8,
"x":480,
"y":744
"x":464,
"y":752
},
{
"gid":273,
@@ -2307,7 +2315,7 @@
"type":"",
"visible":true,
"width":8,
"x":536,
"x":520,
"y":808
},
{
@@ -2319,8 +2327,20 @@
"type":"",
"visible":true,
"width":8,
"x":600,
"y":776
"x":592,
"y":720
},
{
"gid":273,
"height":8,
"id":1291,
"name":"Rat",
"rotation":0,
"type":"",
"visible":true,
"width":8,
"x":560,
"y":824
}],
"opacity":1,
"type":"objectgroup",
@@ -2329,7 +2349,7 @@
"y":0
}],
"nextlayerid":60,
"nextobjectid":1291,
"nextobjectid":1292,
"orientation":"orthogonal",
"properties":[
{
@@ -2338,7 +2358,7 @@
"value":"[{\"x\":97,\"y\":87}]"
}],
"renderorder":"right-down",
"tiledversion":"1.2.5",
"tiledversion":"1.3.3",
"tileheight":8,
"tilesets":[
{


+ 20
- 28
src/server/config/quests/templates/questTemplate.js Переглянути файл

@@ -15,13 +15,11 @@ module.exports = {
this.obj.syncer.setArray(true, 'quests', 'obtainQuests', this.simplify(true));

if (!hideMessage) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-yellowB',
message: 'quest obtained (' + this.name + ')'
}]
}, [this.obj.serverId]);
const message = `Quest obtained (${this.name})`;
this.obj.social.notifySelf({
message,
className: 'color-yellowB'
});
}

return true;
@@ -33,13 +31,11 @@ module.exports = {
if (this.oReady)
this.oReady();

this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'color-yellowB',
message: 'quest ready for turn-in (' + this.name + ')'
}]
}, [this.obj.serverId]);
const message = `Quest ready for turn-in (${this.name})`;
this.obj.social.notifySelf({
message,
className: 'color-yellowB'
});

this.obj.syncer.setArray(true, 'quests', 'updateQuests', this.simplify(true));
},
@@ -48,27 +44,23 @@ module.exports = {
if (this.oComplete)
this.oComplete();

let obj = this.obj;
const obj = this.obj;

this.obj.instance.eventEmitter.emitNoSticky('beforeCompleteAutoquest', this, obj);
obj.instance.eventEmitter.emitNoSticky('beforeCompleteAutoquest', this, obj);

obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'color-yellowB',
message: 'quest completed (' + this.name + ')'
}]
}, [obj.serverId]);
const message = `Quest completed (${this.name})`;
obj.social.notifySelf({
message,
className: 'color-yellowB'
});

obj.syncer.setArray(true, 'quests', 'completeQuests', this.id);

this.obj.instance.eventEmitter.emit('onCompleteQuest', this);
obj.instance.eventEmitter.emit('onCompleteQuest', this);

this.rewards.forEach(function (r) {
this.obj.inventory.getItem(r);
}, this);
this.rewards.forEach(reward => obj.inventory.getItem(reward));

this.obj.stats.getXp(this.xp || 10, this.obj, this);
obj.stats.getXp(this.xp || 10, obj, this);
},

simplify: function (self) {


+ 94
- 0
src/server/config/recipes/enchanting.js Переглянути файл

@@ -0,0 +1,94 @@
const calculateAugmentMaterials = require('./enchanting/calculateAugmentMaterials');

const reroll = require('./enchanting/craftActions/reroll');
const relevel = require('./enchanting/craftActions/relevel');
const augment = require('./enchanting/craftActions/augment');
const reslot = require('./enchanting/craftActions/reslot');
const reforge = require('./enchanting/craftActions/reforge');
const scour = require('./enchanting/craftActions/scour');

module.exports = [{
name: 'Augment',
description: 'Adds a random stat to an item. Items can hold a maximum of three augments.',
materialGenerator: calculateAugmentMaterials,
craftAction: augment,
needItems: [{
info: 'Pick an item to augment',
withProps: ['slot'],
withoutProps: ['noAugment'],
checks: [
item => !item.power || item.power < 3
]
}]
}, {
name: 'Reroll',
description: 'Rerolls an item\'s implicit and explicit stats. Augmentations are not affected.',
materials: [{
name: 'Unstable Idol',
quantity: 1
}],
needItems: [{
info: 'Pick an item to reroll',
withProps: ['slot'],
withoutProps: ['noAugment']
}],
craftAction: reroll
}, {
name: 'Increase Level',
description: 'Adds [1 - 3] to an item\'s required level. Items with higher levels yield better stats when rerolled.',
materials: [{
name: 'Ascendant Idol',
quantity: 1
}],
needItems: [{
info: 'Pick the item you wish to ascend',
withProps: ['slot'],
withoutProps: ['noAugment'],
checks: [
item => item.level && item.level < consts.maxLevel
]
}],
craftAction: relevel
}, {
name: 'Reslot',
description: 'Reforms the item into a random new item that retains the source item\'s quality and stat types.',
materials: [{
name: 'Dragon-Glass Idol',
quantity: 1
}],
needItems: [{
info: 'Pick an item to reslot',
withProps: ['slot'],
withoutProps: ['noAugment', 'effects']
}],
craftAction: reslot
}, {
name: 'Reforge Weapon',
description: 'Rerolls a weapon\'s damage range.',
materials: [{
name: 'Bone Idol',
quantity: 1
}],
needItems: [{
info: 'Pick an item to reforge',
withProps: ['slot', 'spell'],
withoutProps: ['noAugment']
}],
craftAction: reforge
}, {
name: 'Scour',
description: 'Wipe all augments from an item.',
materials: [{
name: 'Smoldering Idol',
quantity: 1
}],
needItems: [{
info: 'Pick an item to scour',
withProps: ['slot', 'power'],
withoutProps: ['noAugment']
}],
craftAction: scour,
checks: [
item => item.power && item.power >= 1
]
}];

+ 17
- 0
src/server/config/recipes/enchanting/calculateAugmentMaterials.js Переглянути файл

@@ -0,0 +1,17 @@
const salvager = require('../../../items/salvager');

module.exports = (obj, [item]) => {
let powerLevel = item.power || 0;
let mult = null;
if (powerLevel < 3)
mult = [5, 10, 20][powerLevel];
else
return;

const result = salvager.salvage(item, true);
result.forEach(r => {
r.quantity = Math.max(1, ~~(r.quantity * mult));
});

return result;
};

+ 17
- 0
src/server/config/recipes/enchanting/craftActions/augment.js Переглянути файл

@@ -0,0 +1,17 @@
let generatorStats = require('../../../../items/generators/stats');

module.exports = (obj, [item]) => {
let newPower = (item.power || 0) + 1;
if (newPower > 3)
return;

item.power = newPower;

const result = { msg: 'Augment successful', addStatMsgs: [] };

generatorStats.generate(item, {
statCount: 1
}, result);

return result;
};

+ 20
- 0
src/server/config/recipes/enchanting/craftActions/reforge.js Переглянути файл

@@ -0,0 +1,20 @@
let generatorSpells = require('../../../../items/generators/spellbook');

module.exports = (obj, [item]) => {
if (!item.spell)
return;

let spellName = item.spell.name.toLowerCase();
let oldSpell = item.spell;
delete item.spell;

generatorSpells.generate(item, {
spellName: spellName
});
item.spell = extend(oldSpell, item.spell);

const damage = item.spell.values.damage;
const msg = `Reforged weapon to damage: ${damage}`;

return { msg };
};

+ 20
- 0
src/server/config/recipes/enchanting/craftActions/relevel.js Переглянути файл

@@ -0,0 +1,20 @@
module.exports = (obj, [item]) => {
if (item.slot === 'tool')
return;

let offset = 1 + ~~(Math.random() * 2);

const maxLevel = consts.maxLevel;

if (!item.originalLevel)
item.level = Math.min(maxLevel, item.level + offset);
else {
offset = Math.min(maxLevel - item.originalLevel, offset);
item.originalLevel = Math.min(maxLevel, item.originalLevel + offset);
item.level = Math.min(maxLevel, item.level + offset);
}

const msg = `Relevelled item to level ${item.level}`;

return { msg };
};

+ 58
- 0
src/server/config/recipes/enchanting/craftActions/reroll.js Переглянути файл

@@ -0,0 +1,58 @@
let generatorStats = require('../../../../items/generators/stats');
let generatorSlots = require('../../../../items/generators/slots');
let generatorTypes = require('../../../../items/generators/types');

module.exports = (obj, [item]) => {
const enchantedStats = item.enchantedStats;
const implicitStats = item.implicitStats;

delete item.enchantedStats;
delete item.implicitStats;

if ((item.stats) && (item.stats.lvlRequire)) {
item.level = Math.min(consts.maxLevel, item.level + item.stats.lvlRequire);
delete item.originalLevel;
}

item.stats = {};
let bpt = {
slot: item.slot,
type: item.type,
sprite: item.sprite,
spritesheet: item.spritesheet
};
generatorSlots.generate(item, bpt);
generatorTypes.generate(item, bpt);
generatorStats.generate(item, bpt);

for (let p in enchantedStats) {
if (!item.stats[p])
item.stats[p] = 0;

item.stats[p] += enchantedStats[p];

if (p === 'lvlRequire') {
if (!item.originalLevel)
item.originalLevel = item.level;

item.level -= enchantedStats[p];
if (item.level < 1)
item.level = 1;
}
}
item.enchantedStats = enchantedStats || null;

//Some items have special implicits (different stats than their types imply)
// We add the old one back in if this is the case. Ideally we'd like to reroll
// these but that'd be a pretty big hack. We'll solve this one day
if (
item.implicitStats &&
implicitStats &&
item.implicitStats[0] &&
implicitStats[0] &&
item.implicitStats[0].stat !== implicitStats[0].stat
)
item.implicitStats = implicitStats;

return { msg: 'Reroll successful' };
};

+ 36
- 0
src/server/config/recipes/enchanting/craftActions/reslot.js Переглянути файл

@@ -0,0 +1,36 @@
let configSlots = require('../../../../items/config/slots');
let generator = require('../../../../items/generator');

module.exports = (obj, [item]) => {
if (item.effects || item.slot === 'tool')
return;

if (item.originalLevel)
item.level = item.originalLevel;

delete item.enchantedStats;

let possibleStats = Object.keys(item.stats || {});

let newItem = generator.generate({
slot: configSlots.getRandomSlot(item.slot),
level: item.level,
quality: item.quality,
stats: possibleStats,
limitSlotStats: true
});

delete item.spritesheet;
delete item.stats;
delete item.spell;
delete item.implicitStats;
delete item.power;
delete item.range;
delete item.requires;

extend(item, newItem);

const msg = `Reslotted item to slot: ${item.slot}`;

return { msg };
};

+ 31
- 0
src/server/config/recipes/enchanting/craftActions/scour.js Переглянути файл

@@ -0,0 +1,31 @@
module.exports = (obj, [item]) => {
if (!item.power)
return;

const result = { msg: 'Scour successful', addStatMsgs: [] };

for (let p in item.enchantedStats) {
let value = item.enchantedStats[p];

if (item.stats[p]) {
result.addStatMsgs.push({
stat: p,
value: -value
});

item.stats[p] -= value;
if (item.stats[p] <= 0)
delete item.stats[p];

if (p === 'lvlRequire') {
item.level = Math.min(consts.maxLevel, item.level + value);
delete item.originalLevel;
}
}
}

delete item.enchantedStats;
delete item.power;

return result;
};

+ 10
- 10
src/server/config/recipes/recipes.js Переглянути файл

@@ -3,17 +3,13 @@ let events = require('../../misc/events');
const recipesAlchemy = require('./alchemy');
const recipesCooking = require('./cooking');
const recipesEtching = require('./etching');
const recipesEnchanting = require('./enchanting');

let recipes = {
alchemy: [
...recipesAlchemy
],
cooking: [
...recipesCooking
],
etching: [
...recipesEtching
]
alchemy: [ ...recipesAlchemy ],
cooking: [ ...recipesCooking ],
etching: [ ...recipesEtching ],
enchanting: [ ...recipesEnchanting ]
};

module.exports = {
@@ -22,7 +18,11 @@ module.exports = {
},

getList: function (type, unlocked) {
return (recipes[type] || [])
const useRecipes = recipes[type];
if (!useRecipes)
return [];

return useRecipes
.filter(r => {
let hasUnlocked = (r.default !== false);
if (!hasUnlocked)


+ 3
- 9
src/server/config/roles.js Переглянути файл

@@ -82,14 +82,8 @@ module.exports = {
},

sendMessage: function (player, msg) {
msg = 'Only certain roles can ' + msg + ' at the moment';

player.instance.syncer.queue('onGetMessages', {
id: player.id,
messages: {
class: 'color-redA',
message: msg
}
}, [player.serverId]);
const message = `Only certain roles can ${msg} at the moment`;

player.social.notifySelf({ message });
}
};

+ 1
- 1
src/server/config/serverConfig.js Переглянути файл

@@ -1,5 +1,5 @@
module.exports = {
version: '0.5.1',
version: '0.6',
port: 4000,
startupMessage: 'Server: ready',
defaultZone: 'fjolarok',


+ 1
- 0
src/server/config/spells/spellProjectile.js Переглянути файл

@@ -9,6 +9,7 @@ module.exports = {
range: 9,

speed: 150,
statMult: 1,
damage: 1,

row: 3,


+ 3
- 0
src/server/config/spells/spellTemplate.js Переглянути файл

@@ -280,6 +280,9 @@ module.exports = {
noMitigate: noMitigate
};

if (this.obj.mob)
config.noCrit = true;

this.obj.fireEvent('onBeforeCalculateDamage', config);

if (this.percentDamage)


+ 0
- 245
src/server/items/enchanter.js Переглянути файл

@@ -1,245 +0,0 @@
let generatorStats = require('./generators/stats');
let generatorSlots = require('./generators/slots');
let generatorTypes = require('./generators/types');
let generatorSpells = require('./generators/spellbook');
let salvager = require('./salvager');
let configCurrencies = require('./config/currencies');
let configSlots = require('./config/slots');
let generator = require('./generator');

const reroll = (item, msg) => {
const enchantedStats = item.enchantedStats;
const implicitStats = item.implicitStats;

delete item.enchantedStats;
delete item.implicitStats;
delete msg.addStatMsgs;

if ((item.stats) && (item.stats.lvlRequire)) {
item.level = Math.min(consts.maxLevel, item.level + item.stats.lvlRequire);
delete item.originalLevel;
}

item.stats = {};
let bpt = {
slot: item.slot,
type: item.type,
sprite: item.sprite,
spritesheet: item.spritesheet
};
generatorSlots.generate(item, bpt);
generatorTypes.generate(item, bpt);
generatorStats.generate(item, bpt);

for (let p in enchantedStats) {
if (!item.stats[p])
item.stats[p] = 0;

item.stats[p] += enchantedStats[p];

if (p === 'lvlRequire') {
if (!item.originalLevel)
item.originalLevel = item.level;

item.level -= enchantedStats[p];
if (item.level < 1)
item.level = 1;
}
}
item.enchantedStats = enchantedStats || null;

//Some items have special implicits (different stats than their types imply)
// We add the old one back in if this is the case. Ideally we'd like to reroll
// these but that'd be a pretty big hack. We'll solve this one day
if (
item.implicitStats &&
implicitStats &&
item.implicitStats[0] &&
implicitStats[0] &&
item.implicitStats[0].stat !== implicitStats[0].stat
)
item.implicitStats = implicitStats;
};

const relevel = item => {
if (item.slot === 'tool')
return;

let offset = 1 + ~~(Math.random() * 2);

const maxLevel = consts.maxLevel;

if (!item.originalLevel)
item.level = Math.min(maxLevel, item.level + offset);
else {
offset = Math.min(maxLevel - item.originalLevel, offset);
item.originalLevel = Math.min(maxLevel, item.originalLevel + offset);
item.level = Math.min(maxLevel, item.level + offset);
}
};

const reslot = (item, msg) => {
if (item.effects || item.slot === 'tool')
return;

if (item.originalLevel)
item.level = item.originalLevel;

delete item.enchantedStats;
delete msg.addStatMsgs;

let possibleStats = Object.keys(item.stats || {});

let newItem = generator.generate({
slot: configSlots.getRandomSlot(item.slot),
level: item.level,
quality: item.quality,
stats: possibleStats,
limitSlotStats: true
});

delete item.spritesheet;
delete item.stats;
delete item.spell;
delete item.implicitStats;
delete item.power;
delete item.range;
delete item.requires;

extend(item, newItem);
};

const reforge = item => {
if (!item.spell)
return;

let spellName = item.spell.name.toLowerCase();
let oldSpell = item.spell;
delete item.spell;

generatorSpells.generate(item, {
spellName: spellName
});
item.spell = extend(oldSpell, item.spell);
};

const scour = (item, result) => {
if (!item.power)
return;

for (let p in item.enchantedStats) {
let value = item.enchantedStats[p];

if (item.stats[p]) {
result.addStatMsgs.push({
stat: p,
value: -value
});
item.stats[p] -= value;
if (item.stats[p] <= 0)
delete item.stats[p];

if (p === 'lvlRequire') {
item.level = Math.min(consts.maxLevel, item.level + value);
delete item.originalLevel;
}
}
}

delete item.enchantedStats;
delete item.power;
};

const augment = (item, inventory, result, msg) => {
let newPower = (item.power || 0) + 1;
if (newPower > 3) {
inventory.resolveCallback(msg);
return;
}

item.power = newPower;

generatorStats.generate(item, {
statCount: 1
}, result);
};

module.exports = {
enchant: function (obj, item, msg) {
let inventory = obj.inventory;
let config = this.getEnchantMaterials(item, msg.action);

let success = true;
config.materials.forEach(function (m) {
let hasMaterial = inventory.items.find(i => i.name === m.name);
if (hasMaterial)
hasMaterial = hasMaterial.quantity >= m.quantity;
if (!hasMaterial)
success = false;
});

if (!success) {
inventory.resolveCallback(msg);
return;
}

let result = {
item: item,
addStatMsgs: []
};

config.materials.forEach(function (m) {
let invMaterial = inventory.items.find(i => i.name === m.name);
inventory.destroyItem(invMaterial.id, m.quantity);
});

if (msg.action === 'reroll')
reroll(item, msg);
else if (msg.action === 'relevel')
relevel(item);
else if (msg.action === 'reslot')
reslot(item, msg);
else if (msg.action === 'reforge')
reforge(item);
else if (msg.action === 'scour')
scour(item, result);
else
augment(item, inventory, result, msg);

obj.syncer.setArray(true, 'inventory', 'getItems', inventory.simplifyItem(item));

inventory.resolveCallback(msg, result);
},

getEnchantMaterials: function (item, action) {
let result = null;

if (action === 'reroll')
result = [configCurrencies.getCurrencyFromAction('reroll')];
else if (action === 'relevel')
result = [configCurrencies.getCurrencyFromAction('relevel')];
else if (action === 'reslot')
result = [configCurrencies.getCurrencyFromAction('reslot')];
else if (action === 'reforge')
result = [configCurrencies.getCurrencyFromAction('reforge')];
else if (action === 'scour')
result = [configCurrencies.getCurrencyFromAction('scour')];
else {
let powerLevel = item.power || 0;
let mult = null;
if (powerLevel < 3)
mult = [5, 10, 20][powerLevel];
else
return;

result = salvager.salvage(item, true);
result.forEach(r => {
r.quantity = Math.max(1, ~~(r.quantity * mult));
});
}

return {
materials: result
};
}
};

+ 2
- 1
src/server/items/generator.js Переглянути файл

@@ -14,7 +14,7 @@ let g13 = require('./generators/recipeBook');

let generators = [g1, g2, g3, g4, g5, g6, g11, g12, g7];
let materialGenerators = [g6, g8];
let spellGenerators = [g1, g9, g7];
let spellGenerators = [g1, g2, g9, g7];
let currencyGenerators = [g10, g8];
let recipeGenerators = [g6, g13];

@@ -76,6 +76,7 @@ module.exports = {
item.noDrop = blueprint.noDrop || null;
item.noSalvage = blueprint.noSalvage || null;
item.noDestroy = blueprint.noDestroy || null;
item.quality = blueprint.quality || 0;
materialGenerators.forEach(g => g.generate(item, blueprint));
} else if (blueprint.type === 'mtx') {
item = extend({}, blueprint);


+ 70
- 31
src/server/items/generators/spellbook.js Переглянути файл

@@ -2,6 +2,46 @@ let spells = require('../../config/spells');
let spellsConfig = require('../../config/spellsConfig');
let configTypes = require('../config/types');

const qualityGenerator = require('./quality');
const qualityCount = qualityGenerator.qualities.length;

const buildRolls = (item, blueprint, { random: spellProperties, negativeStats = [] }, quality) => {
//We randomise the order so a random property gets to 'pick first'
// otherwise it's easier for earlier properties to use more of the valuePool
const propKeys = Object
.keys(spellProperties)
.sort((a, b) => Math.random() - Math.random());

const propCount = propKeys.length;

const maxRoll = (quality + 1) / qualityCount;
const minSum = (quality / qualityCount) * propCount;

let runningTotal = 0;

const result = {};

for (let i = 0; i < propCount; i++) {
const minRoll = Math.max(0, minSum - runningTotal - ((propCount - (i + 1)) * maxRoll));

let roll = minRoll + (Math.random() * (maxRoll - minRoll));

runningTotal += roll;

const prop = propKeys[i];
const isNegative = negativeStats.includes(prop);

if (isNegative)
roll = 1 - roll;

const scaledRoll = roll * (item.level / consts.maxLevel);

result[prop] = scaledRoll;
}

return result;
};

module.exports = {
generate: function (item, blueprint) {
blueprint = blueprint || {};
@@ -55,40 +95,46 @@ module.exports = {
extend(spell, typeConfig.spellConfig);
}

let propertyPerfection = [];
//If the item has a slot, we need to generate a new quality for the rune
let quality = item.quality;
if (item.slot) {
const tempItem = {};

let randomProperties = spell.random || {};
let negativeStats = spell.negativeStats || [];
for (let r in randomProperties) {
let negativeStat = (negativeStats.indexOf(r) > -1);
let range = randomProperties[r];
const tempBlueprint = extend(blueprint);
delete tempBlueprint.quality;
tempBlueprint.quality = blueprint.spellQuality;

let max = Math.min(consts.maxLevel, item.level) / consts.maxLevel;
qualityGenerator.generate(tempItem, tempBlueprint);

let roll = random.expNorm(0, max);
if (spellQuality === 'basic')
roll = 0;
else if (spellQuality === 'mid')
roll = 0.5;
quality = tempItem.quality;
item.spell.quality = quality;
}

const rolls = buildRolls(item, blueprint, spell, quality);
Object.entries(spell.random || {}).forEach(entry => {
const [ property, range ] = entry;
const roll = rolls[property];

item.spell.rolls[property] = roll;

item.spell.rolls[r] = roll;
const isInt = property.indexOf('i_') === 0;
let useProperty = property;
const minRange = range[0];
const maxRange = range[1];

let int = r.indexOf('i_') === 0;
let val = range[0] + ((range[1] - range[0]) * roll);
let val = minRange + ((maxRange - minRange) * roll);

if (int) {
val = ~~val;
r = r.replace('i_', '');
if (isInt) {
useProperty = property.substr(2);
val = Math.round(val);
} else
val = ~~(val * 100) / 100;

item.spell.values[r] = val;
val = Math.max(range[0], Math.min(range[1], val));

if (negativeStat)
propertyPerfection.push(1 - roll);
else
propertyPerfection.push(roll);
}
item.spell.values[useProperty] = val;
});

if (blueprint.spellProperties) {
item.spell.properties = {};
@@ -100,12 +146,5 @@ module.exports = {
item.spell.properties = item.spell.properties || {};
item.spell.properties.range = item.range;
}

let per = propertyPerfection.reduce((p, n) => p + n, 0);
let perfection = ~~((per / propertyPerfection.length) * 4);
if (!item.slot)
item.quality = perfection;
else
item.spell.quality = perfection;
}
};

+ 8
- 16
src/server/mail/mailRethinkDb.js Переглянути файл

@@ -54,14 +54,10 @@ module.exports = {
}
} else {
if ((r.msg) && (!sentMessages.some(s => (s === r.msg)))) {
player.instance.syncer.queue('onGetMessages', {
id: player.id,
messages: [{
class: 'color-greenB',
message: r.msg,
type: 'info'
}]
}, [player.serverId]);
player.social.notifySelf({
message: r.msg,
className: 'color-greenB'
});

sentMessages.push(r.msg);
delete r.msg;
@@ -125,14 +121,10 @@ module.exports = {
}
} else {
if ((r.msg) && (!sentMessages.some(s => (s === r.msg)))) {
player.instance.syncer.queue('onGetMessages', {
id: player.id,
messages: [{
class: 'color-greenB',
message: r.msg,
type: 'info'
}]
}, [player.serverId]);
player.social.notifySelf({
message: r.msg,
className: 'color-greenB'
});

sentMessages.push(r.msg);
delete r.msg;


+ 4
- 8
src/server/mail/mailSqlite.js Переглянути файл

@@ -77,14 +77,10 @@ module.exports = {
}
} else {
if ((r.msg) && (!sentMessages.some(s => (s === r.msg)))) {
player.instance.syncer.queue('onGetMessages', {
id: player.id,
messages: [{
class: 'color-greenB',
message: r.msg,
type: 'info'
}]
}, [player.serverId]);
player.social.notifySelf({
message: r.msg,
className: 'color-greenB'
});

sentMessages.push(r.msg);
delete r.msg;


+ 8
- 7
src/server/mods/class-necromancer/index.js Переглянути файл

@@ -13,7 +13,7 @@ module.exports = {
this.events.on('onBeforeGetSpellsInfo', this.beforeGetSpellsInfo.bind(this));
this.events.on('onBeforeGetSpellsConfig', this.beforeGetSpellsConfig.bind(this));
this.events.on('onBeforeGetSpellTemplate', this.beforeGetSpellTemplate.bind(this));
this.events.on('onBeforeGetResourceList', this.beforeGetResourceList.bind(this));
this.events.on('onBeforeGetClientConfig', this.onBeforeGetClientConfig.bind(this));
this.events.on('onBeforeGetAnimations', this.beforeGetAnimations.bind(this));
this.events.on('onAfterGetZone', this.onAfterGetZone.bind(this));
},
@@ -25,14 +25,14 @@ module.exports = {
let newRunes = [{
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'harvest life',
worth: 3
}, {
generate: true,
spell: true,
spellQuality: 'basic',
spellQuality: 0,
infinite: true,
spellName: 'summon skeleton',
worth: 3
@@ -97,10 +97,11 @@ module.exports = {
};
},

beforeGetResourceList: function (list) {
list.push(`${this.folderName}/images/inGameSprite.png`);
list.push(`${this.folderName}/images/abilityIcons.png`);
list.push(`${this.folderName}/images/mobs.png`);
onBeforeGetClientConfig: function ({ resourceList, textureList }) {
resourceList.push(`${this.folderName}/images/abilityIcons.png`);

textureList.push(`${this.folderName}/images/inGameSprite.png`);
textureList.push(`${this.folderName}/images/mobs.png`);
},

beforeGetSpellTemplate: function (spell) {


+ 6
- 159
src/server/mods/feature-cards/cards.js Переглянути файл

@@ -1,57 +1,37 @@
let itemGenerator = require('../../items/generator');

let config = {
'Runecrafter\'s Toil': {
chance: 0.025,
reward: 'Level 10 Rune',
setSize: 3,
mobLevel: [3, 100]
mobLevel: [8, 12]
},
'Godly Promise': {
chance: 0.01,
reward: 'Level 15 Legendary Weapon',
setSize: 6,
zone: 'sewer'
},
'The Other Heirloom': {
chance: 0.02,
reward: 'Perfect Level 10 Ring',
setSize: 3,
mobName: 'flamingo'
},
'Benthic Incantation': {
chance: 0.015,
reward: 'Princess Morgawsa\'s Trident',
setSize: 12,
zone: 'estuary'
},
'Fangs of Fury': {
chance: 0.05,
reward: 'Steelclaw\'s Bite',
setSize: 20,
mobName: 'stinktooth'
},
'Tradesman\'s Pride': {
chance: 0.007,
reward: 'Five Random Idols',
setSize: 10
reward: 'Five Random Idols'
}
};

module.exports = {
init: function () {

},

fixCard: function (card) {
let template = config[card.name];
if (!template)
return;

card.setSize = template.setSize;
},

getCard: function (looter, mob) {
getCard: function (modFolderPath, looter, mob) {
let pool = [];

let mobLevel = mob.stats.values.level;
@@ -96,147 +76,14 @@ module.exports = {

let builtCard = {
name: pickName,
spritesheet: pick.spritesheet || `${this.folderName}/images/items.png`,
type: 'Reward Card',
description: 'Reward: ' + pick.reward,
spritesheet: pick.spritesheet || `${modFolderPath}/images/items.png`,
type: 'Gambler\'s Card',
noSalvage: true,
sprite: pick.sprite || [0, 0],
quantity: 1,
quality: pick.quality || 1,
setSize: pick.setSize
quality: pick.quality || 1
};

return builtCard;
},

getReward: function (looter, set) {
let configs = extend({}, config);
looter.instance.eventEmitter.emit('onBeforeGetCardsConfig', configs);

let reward = configs[set].reward;
let msg = {
reward: reward,
handler: this.rewards[reward]
};

looter.instance.eventEmitter.emit('onBeforeGetCardReward', msg);

return msg.handler(looter);
},

rewards: {
'Level 10 Rune': function () {
return itemGenerator.generate({
level: 10,
spell: true
});
},

'Level 15 Legendary Weapon': function () {
let slot = ['oneHanded', 'twoHanded'][~~(Math.random() * 2)];

return itemGenerator.generate({
level: 15,
quality: 4,
noSpell: true,
slot: slot
});
},

'Perfect Level 10 Ring': function () {
return itemGenerator.generate({
level: 10,
noSpell: true,
quality: 1,
perfection: 1,
slot: 'finger'
});
},

"Princess Morgawsa's Trident": function () {
return itemGenerator.generate({
name: 'Princess Morgawsa\'s Trident',
level: [18, 20],
attrRequire: 'int',
quality: 4,
noSpell: true,
slot: 'twoHanded',
sprite: [0, 0],
spritesheet: '../../../images/legendaryItems.png',
type: 'Trident',
description: 'Summoned from the ancient depths of the ocean by the Benthic Incantation.',
stats: ['elementFrostPercent', 'elementFrostPercent', 'elementFrostPercent'],
effects: [{
type: 'freezeOnHit',
rolls: {
i_chance: [2, 5],
i_duration: [2, 4]
}
}],
spellName: 'projectile',
spellConfig: {
statType: 'int',
statMult: 1,
element: 'arcane',
auto: true,
cdMax: 7,
castTimeMax: 0,
manaCost: 0,
range: 9,
random: {
damage: [1.65, 10.81]
}
}
});
},

'Five Random Idols': function () {
let result = [];
for (let i = 0; i < 5; i++) {
result.push(itemGenerator.generate({
currency: true
}));
}
return result;
},

"Steelclaw's Bite": function () {
return itemGenerator.generate({
name: 'Steelclaw\'s Bite',
level: [18, 20],
attrRequire: 'dex',
quality: 4,
noSpell: true,
slot: 'oneHanded',
sprite: [1, 0],
spritesheet: '../../../images/legendaryItems.png',
type: 'Curved Dagger',
description: 'The blade seems to be made of some kind of bone and steel alloy.',
stats: ['dex', 'dex', 'addCritMultiplier', 'addCritMultiplier'],
effects: [{
type: 'damageSelf',
properties: {
element: 'poison'
},
rolls: {
i_percentage: [8, 22]
}
}, {
type: 'alwaysCrit',
rolls: {}
}],
spellName: 'melee',
spellConfig: {
statType: 'dex',
statMult: 1,
cdMax: 3,
castTimeMax: 0,
useWeaponRange: true,
random: {
damage: [0.88, 5.79]
}
}
});
}
}
};

+ 17
- 0
src/server/mods/feature-cards/config.js Переглянути файл

@@ -0,0 +1,17 @@
module.exports = {
dealer: {
zoneName: 'sewer',
pos: {
x: 74,
y: 99
},
zoneConfig: {
name: 'Shady Dealer',
cell: 0,
sheetName: '$MODFOLDER$/images/mobs.png',
components: {
cpnWorkbench: { type: 'gambling' }
}
}
}
};



+ 44
- 25
src/server/mods/feature-cards/index.js Переглянути файл

@@ -1,18 +1,52 @@
let cards = require('./cards');
const cardRecipes = require('./recipes/recipes');
const cards = require('./cards');
const { dealer } = require('./config');

module.exports = {
name: 'Feature: Cards',

extraScripts: [
'cards'
],

init: function () {
cards.init();

this.events.on('onBeforeGetClientConfig', this.onBeforeGetClientConfig.bind(this));
this.events.on('onBeforeDropBag', this.onBeforeDropBag.bind(this));
this.events.on('onGetCardSetReward', this.onGetCardSetReward.bind(this));
this.events.on('onBeforeGetItem', this.onBeforeGetItem.bind(this));
this.events.on('onBeforeGetRecipes', this.onBeforeGetRecipes.bind(this));
this.events.on('onAfterGetZone', this.onAfterGetZone.bind(this));
this.events.on('onAfterGetLayerObjects', this.onAfterGetLayerObjects.bind(this));
},

onBeforeGetClientConfig: function (config) {
config.textureList.push(`${this.folderName}/images/mobs.png`);
},

onAfterGetZone: function (zoneName, config) {
const { zoneName: dealerZoneName, zoneConfig } = dealer;
const dealerName = zoneConfig.name.toLowerCase();

if (zoneName !== dealerZoneName)
return;

zoneConfig.sheetName = zoneConfig.sheetName.replace('$MODFOLDER$', this.folderName);

config.objects[dealerName] = zoneConfig;
},

onAfterGetLayerObjects: function ({ map, layer, objects, mapScale }) {
const { zoneName: dealerZoneName, pos: { x, y }, zoneConfig: { name } } = dealer;

if (map !== dealerZoneName || layer !== 'objects')
return;

objects.push({
name,
x: x * mapScale,
y: y * mapScale,
height: 8,
width: 8,
visible: true
});
},

onBeforeGetRecipes: function (recipes) {
recipes.gambling = cardRecipes;
},

onBeforeDropBag: function (dropper, items, looter) {
@@ -27,25 +61,10 @@ module.exports = {
if (Math.random() >= dropEvent.chanceMultiplier)
return;

let res = cards.getCard(looter, dropper);
let res = cards.getCard(this.folderName, looter, dropper);
if (!res)
return;

items.push(res);
},

onBeforeGetItem: function (item, obj) {
if ((!obj.player) && (item.type !== 'Reward Card'))
return;

cards.fixCard(item);
},

onGetCardSetReward: function (set, obj) {
let reward = cards.getReward(obj, set);
if (!reward.push)
reward = [reward];

reward.forEach(r => obj.inventory.getItem(r, false, false, false, true));
}
};

+ 17
- 0
src/server/mods/feature-cards/recipes/craftActions/armor.js Переглянути файл

@@ -0,0 +1,17 @@
let itemGenerator = require('../../../../items/generator');

module.exports = ({ level, quality, slot, perfection }, crafter) => {
const result = itemGenerator.generate({
level,
noSpell: true,
quality,
perfection,
slot
});

crafter.inventory.getItem(result, false, false, false, true);

const msg = `You received: ${result.name}`;

return { msg };
};

+ 18
- 0
src/server/mods/feature-cards/recipes/craftActions/idol.js Переглянути файл

@@ -0,0 +1,18 @@
let itemGenerator = require('../../../../items/generator');

module.exports = ({ rolls, quantity = 1 }, crafter) => {
let quantityReceived = rolls * quantity;

for (let i = 0; i < rolls; i++) {
const idol = itemGenerator.generate({
currency: true,
quantity
});

crafter.inventory.getItem(idol, false, false, false, true);
}

const msg = `You received ${quantityReceived} idols`;

return { msg };
};

+ 14
- 0
src/server/mods/feature-cards/recipes/craftActions/rune.js Переглянути файл

@@ -0,0 +1,14 @@
let itemGenerator = require('../../../../items/generator');

module.exports = (config, crafter) => {
const result = itemGenerator.generate({
...config,
spell: true
});

crafter.inventory.getItem(result, false, false, false, true);

const msg = `You received: ${result.name}`;

return { msg };
};

+ 17
- 0
src/server/mods/feature-cards/recipes/craftActions/weapon.js Переглянути файл

@@ -0,0 +1,17 @@
let itemGenerator = require('../../../../items/generator');

module.exports = (config, crafter) => {
const slot = config.slot || ['oneHanded', 'twoHanded'][~~(Math.random() * 2)];

const result = itemGenerator.generate({
noSpell: true,
slot,
...config
});

crafter.inventory.getItem(result, false, false, false, true);

const msg = `You received: ${result.name}`;

return { msg };
};

+ 155
- 0
src/server/mods/feature-cards/recipes/recipes.js Переглянути файл

@@ -0,0 +1,155 @@
const rune = require('./craftActions/rune');
const weapon = require('./craftActions/weapon');
const armor = require('./craftActions/armor');
const idol = require('./craftActions/idol');

module.exports = [{
name: 'Level 10 Rune',
description: '',
materials: [{
name: 'Runecrafter\'s Toil',
quantity: 3
}],
craftAction: rune.bind(null, {
level: 10,
magicFind: 900
})
}, {
name: 'Level 15 Rune',
description: '',
materials: [{
name: 'Runecrafter\'s Toil',
quantity: 10
}],
craftAction: rune.bind(null, {
level: 15,
magicFind: 1400
})
}, {
name: 'Level 20 Rune',
description: '',
materials: [{
name: 'Runecrafter\'s Toil',
quantity: 30
}],
craftAction: rune.bind(null, {
level: 20,
magicFind: 1900
})
}, {
name: 'Legendary Level 15 Weapon',
description: '',
materials: [{
name: 'Godly Promise',
quantity: 6
}],
craftAction: weapon.bind(null, {
level: 15,
quality: 4
})
}, {
name: 'Perfect Level 10 Ring',
description: '',
materials: [{
name: 'The Other Heirloom',
quantity: 3
}],
craftAction: armor.bind(null, {
level: 10,
slot: 'finger',
perfection: 1,
quality: 1
})
}, {
name: '5 Random Idols',
description: '',
materials: [{
name: 'Tradesman\'s Pride',
quantity: 10
}],
craftAction: idol.bind(null, {
rolls: 5
})
}, {
name: 'Princess Morgawsa\'s Trident',
description: '',
materials: [{
name: 'Benthic Incantation',
quantity: 12
}],
craftAction: weapon.bind(null, {
name: 'Princess Morgawsa\'s Trident',
level: [18, 20],
attrRequire: 'int',
quality: 4,
slot: 'twoHanded',
sprite: [0, 0],
spritesheet: '../../../images/legendaryItems.png',
type: 'Trident',
description: 'Summoned from the ancient depths of the ocean by the Benthic Incantation.',
stats: ['elementFrostPercent', 'elementFrostPercent', 'elementFrostPercent'],
effects: [{
type: 'freezeOnHit',
rolls: {
i_chance: [2, 5],
i_duration: [2, 4]
}
}],
spellName: 'projectile',
spellConfig: {
statType: 'int',
statMult: 1,
element: 'arcane',
auto: true,
cdMax: 7,
castTimeMax: 0,
manaCost: 0,
range: 9,
random: {
damage: [1.65, 10.81]
}
}
})
}, {
name: 'Steelclaw\'s Bite',
description: '',
materials: [{
name: 'Fangs of Fury',
quantity: 20
}],
craftAction: weapon.bind(null, {
name: 'Steelclaw\'s Bite',
level: [18, 20],
attrRequire: 'dex',
quality: 4,
slot: 'oneHanded',
sprite: [1, 0],
spritesheet: '../../../images/legendaryItems.png',
type: 'Curved Dagger',
description: 'The blade seems to be made of some kind of bone and steel alloy.',
stats: ['dex', 'dex', 'addCritMultiplier', 'addCritMultiplier'],
effects: [{
type: 'damageSelf',
properties: {
element: 'poison'
},
rolls: {
i_percentage: [8, 22]
}
}, {
type: 'alwaysCrit',
rolls: {}
}],
spellName: 'melee',
spellConfig: {
statType: 'dex',
statMult: 1,
cdMax: 3,
castTimeMax: 0,
useWeaponRange: true,
random: {
damage: [0.88, 5.79]
}
}
})
}];

+ 0
- 52
src/server/mods/mounts/effects/effectMounted.js Переглянути файл

@@ -1,52 +0,0 @@
module.exports = {
type: 'mounted',

oldCell: null,
oldSheetName: null,

init: function () {
let obj = this.obj;

this.oldCell = obj.cell;
this.oldSheetName = obj.sheetName;

obj.cell = this.cell;
obj.sheetName = this.sheetName;

let syncer = obj.syncer;
syncer.set(false, null, 'cell', obj.cell);
syncer.set(false, null, 'sheetName', obj.sheetName);
},

simplify: function () {
return {
type: 'mounted',
ttl: this.ttl
};
},

destroy: function () {
let obj = this.obj;

obj.cell = this.oldCell;
obj.sheetName = this.oldSheetName;

let syncer = obj.syncer;
syncer.set(false, null, 'cell', obj.cell);
syncer.set(false, null, 'sheetName', obj.sheetName);
},

events: {
onBeforeTryMove: function (moveEvent) {
moveEvent.sprintChance = 200;
},

beforeCastSpell: function (castEvent) {
this.destroyed = true;
},

beforeTakeDamage: function (dmgEvent) {
this.destroyed = true;
}
}
};

+ 0
- 66
src/server/mods/mounts/index.js Переглянути файл

@@ -1,66 +0,0 @@
/*
Example of a mount:
{
name: 'Brown Horse\'s Reins',
type: 'mount',
quality: 2,
noDrop: true,
noSalvage: true,
cdMax: 10,
sprite: [0, 9],
spritesheet: 'images/questItems.png',
useText: 'mount',
description: 'Stout, dependable and at least faster than you',
effects: [{
type: 'mounted',
rolls: {
speed: 150,
cell: 0,
sheetName: 'mobs'
}
}]
}
*/

module.exports = {
name: 'Feature: Mounts',

init: function () {
this.events.on('onBeforeUseItem', this.onBeforeUseItem.bind(this));
this.events.on('onBeforeGetEffect', this.onBeforeGetEffect.bind(this));
},

onBeforeUseItem: function (obj, item, result) {
if (item.type !== 'mount')
return;

let syncer = obj.syncer;

let currentEffect = obj.effects.removeEffectByName('mounted', true);
if (currentEffect) {
let currentItem = currentEffect.source;
currentItem.useText = 'mount';
currentItem.cdMax = 0;

syncer.setArray(true, 'inventory', 'getItems', currentItem);

if (currentItem === item)
return;
}

let effectOptions = extend({ type: 'mounted',
ttl: -1
}, item.effects[0].rolls);

let builtEffect = obj.effects.addEffect(effectOptions);
builtEffect.source = item;

item.useText = 'unmount';
syncer.setArray(true, 'inventory', 'getItems', item);
},

onBeforeGetEffect: function (result) {
if (result.type.toLowerCase() === 'mounted')
result.url = `${this.relativeFolderName}/effects/effectMounted.js`;
}
};

+ 1
- 1
src/server/package.json Переглянути файл

@@ -1,6 +1,6 @@
{
"name": "isleward_server",
"version": "0.5.1",
"version": "0.6.0",
"description": "isleward",
"dependencies": {
"bcrypt-nodejs": "0.0.3",


+ 1
- 1
src/server/security/routerConfig.js Переглянути файл

@@ -10,7 +10,7 @@ const routerConfig = {
dialogue: ['talk'],
gatherer: ['gather'],
quests: ['complete'],
inventory: ['combineStacks', 'splitStack', 'activateMtx', 'useItem', 'moveItem', 'enchantItem', 'getEnchantMaterials', 'learnAbility', 'unlearnAbility', 'dropItem', 'destroyItem', 'salvageItem', 'stashItem', 'mailItem', 'sortInventory'],
inventory: ['combineStacks', 'splitStack', 'activateMtx', 'useItem', 'moveItem', 'learnAbility', 'unlearnAbility', 'dropItem', 'destroyItem', 'salvageItem', 'stashItem', 'mailItem', 'sortInventory'],
equipment: ['equip', 'unequip', 'setQuickSlot', 'useQuickSlot', 'inspect'],
stash: ['withdraw', 'open'],
trade: ['buySell'],


+ 5
- 4
src/server/world/map.js Переглянути файл

@@ -261,7 +261,8 @@ module.exports = {
let info = {
map: this.name,
layer: layerName,
objects: data
objects: data,
mapScale
};
events.emit('onAfterGetLayerObjects', info);
}
@@ -352,7 +353,7 @@ module.exports = {

if (layerName.indexOf('walls') > -1)
this.collisionMap[x][y] = 1;
else if (sheetName.toLowerCase().indexOf('tiles') > -1) {
else if (layerName === 'tiles' && sheetName === 'tiles') {
//Check for water and water-like tiles
if ([6, 7, 54, 55, 62, 63, 154, 189, 190, 192, 193, 194, 195, 196, 197].indexOf(cell) > -1)
this.collisionMap[x][y] = 1;
@@ -376,8 +377,8 @@ module.exports = {

let blueprint = {
clientObj: clientObj,
sheetName: cellInfo.sheetName,
cell: cellInfo.cell - 1,
sheetName: cell.has('sheetName') ? cell.sheetName : cellInfo.sheetName,
cell: cell.has('cell') ? cell.cell : cellInfo.cell - 1,
x: cell.x / mapScale,
y: (cell.y / mapScale) - 1,
name: name,


+ 2
- 0
src/server/world/worker.js Переглянути файл

@@ -18,6 +18,7 @@ let recipes = require('../config/recipes/recipes');
let itemTypes = require('../items/config/types');
let mapList = require('../config/maps/mapList');
let sheets = require('../security/sheets');
let itemEffects = require('../items/itemEffects');

let onCpnsReady = function () {
factions.init();
@@ -31,6 +32,7 @@ let onCpnsReady = function () {
mapList.init();
recipes.init();
sheets.init();
itemEffects.init();

process.send({
method: 'onReady'


Завантаження…
Відмінити
Зберегти