Browse Source

closes #1440

tags/v0.6^2
Shaun 4 years ago
parent
commit
e634983ac4
31 changed files with 1559 additions and 1630 deletions
  1. +84
    -1
      src/client/css/main.less
  2. +0
    -2
      src/client/ui/factory.js
  3. +1
    -1
      src/client/ui/shared/renderItem.js
  4. +1
    -14
      src/client/ui/templates/equipment/equipment.js
  5. +5
    -18
      src/client/ui/templates/inventory/inventory.js
  6. +0
    -343
      src/client/ui/templates/smithing/smithing.js
  7. +0
    -186
      src/client/ui/templates/smithing/styles.less
  8. +0
    -34
      src/client/ui/templates/smithing/template.html
  9. +0
    -4
      src/client/ui/templates/smithing/templateItem.html
  10. +66
    -12
      src/client/ui/templates/workbench/styles.less
  11. +13
    -1
      src/client/ui/templates/workbench/template.html
  12. +203
    -18
      src/client/ui/templates/workbench/workbench.js
  13. +0
    -42
      src/server/components/inventory.js
  14. +79
    -68
      src/server/components/workbench.js
  15. +57
    -0
      src/server/components/workbench/buildMaterials.js
  16. +29
    -0
      src/server/components/workbench/buildNeedItems.js
  17. +19
    -0
      src/server/components/workbench/buildPickedItems.js
  18. +30
    -0
      src/server/components/workbench/buildRecipe.js
  19. +624
    -622
      src/server/config/maps/fjolarok/map.json
  20. +55
    -8
      src/server/config/maps/fjolarok/zone.js
  21. +90
    -0
      src/server/config/recipes/enchanting.js
  22. +17
    -0
      src/server/config/recipes/enchanting/calculateAugmentMaterials.js
  23. +17
    -0
      src/server/config/recipes/enchanting/craftActions/augment.js
  24. +17
    -0
      src/server/config/recipes/enchanting/craftActions/reforge.js
  25. +18
    -0
      src/server/config/recipes/enchanting/craftActions/relevel.js
  26. +58
    -0
      src/server/config/recipes/enchanting/craftActions/reroll.js
  27. +34
    -0
      src/server/config/recipes/enchanting/craftActions/reslot.js
  28. +31
    -0
      src/server/config/recipes/enchanting/craftActions/scour.js
  29. +10
    -10
      src/server/config/recipes/recipes.js
  30. +0
    -245
      src/server/items/enchanter.js
  31. +1
    -1
      src/server/security/routerConfig.js

+ 84
- 1
src/client/css/main.less View File

@@ -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%);
}

}

}

+ 0
- 2
src/client/ui/factory.js View File

@@ -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 View File

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


+ 1
- 14
src/client/ui/templates/equipment/equipment.js View File

@@ -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();


+ 5
- 18
src/client/ui/templates/inventory/inventory.js View File

@@ -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


+ 0
- 343
src/client/ui/templates/smithing/smithing.js View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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);
}
};
});

+ 0
- 42
src/server/components/inventory.js View File

@@ -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;


+ 79
- 68
src/server/components/workbench.js View File

@@ -1,6 +1,12 @@
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 = {
type: 'workbench',

@@ -98,93 +104,98 @@ 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))
const { craftType, obj: { instance: { objects: { objects } } } } = this;
const { name: recipeName, sourceId } = msg;

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

let recipe = recipes.getRecipe(this.craftType, msg.name);
const recipe = recipes.getRecipe(craftType, recipeName);
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
)
))));

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;

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);
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)
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);
});
}

this.resolveCallback(msg, {
resultMsg,
recipe: buildRecipe(craftType, crafter, msg)
});

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

resolveCallback: function (msg, result) {


+ 57
- 0
src/server/components/workbench/buildMaterials.js View File

@@ -0,0 +1,57 @@
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 noHaveEnough = (
haveMaterial &&
(
haveMaterial.quantity === 1 ||
haveMaterial.quantity < quantity
)
);

const id = haveMaterial ? haveMaterial.id : null;

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

const needQuantity = quantity;

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

return material;
});

return result;
};

+ 29
- 0
src/server/components/workbench/buildNeedItems.js View File

@@ -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 View File

@@ -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 View File

@@ -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;
};

+ 624
- 622
src/server/config/maps/fjolarok/map.json
File diff suppressed because it is too large
View File


+ 55
- 8
src/server/config/maps/fjolarok/zone.js View File

@@ -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: {


+ 90
- 0
src/server/config/recipes/enchanting.js View File

@@ -0,0 +1,90 @@
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 => {
return !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 an item to reroll',
withProps: ['slot'],
withoutProps: ['noAugment']
}],
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 reroll',
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 reroll',
withProps: ['slot', 'spell'],
withoutProps: ['noAugment']
}],
craftAction: reforge
}, {
name: 'Scour',
description: 'Rerolls a weapon\'s damage range.',
materials: [{
name: 'Bone Idol',
quantity: 1
}],
needItems: [{
info: 'Pick an item to reroll',
withProps: ['slot', 'power'],
withoutProps: ['noAugment']
}],
craftAction: scour
}];

+ 17
- 0
src/server/config/recipes/enchanting/calculateAugmentMaterials.js View File

@@ -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 View File

@@ -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;
};

+ 17
- 0
src/server/config/recipes/enchanting/craftActions/reforge.js View File

@@ -0,0 +1,17 @@
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);

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

+ 18
- 0
src/server/config/recipes/enchanting/craftActions/relevel.js View File

@@ -0,0 +1,18 @@
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);
}

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

+ 58
- 0
src/server/config/recipes/enchanting/craftActions/reroll.js View File

@@ -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' };
};

+ 34
- 0
src/server/config/recipes/enchanting/craftActions/reslot.js View File

@@ -0,0 +1,34 @@
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);

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

+ 31
- 0
src/server/config/recipes/enchanting/craftActions/scour.js View File

@@ -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 View File

@@ -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)


+ 0
- 245
src/server/items/enchanter.js View File

@@ -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
};
}
};

+ 1
- 1
src/server/security/routerConfig.js View File

@@ -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'],


Loading…
Cancel
Save