Browse Source

initial commit for #1460

1460-move-crafting-to-mod
Shaun 4 years ago
parent
commit
8edcba992e
31 changed files with 506 additions and 1785 deletions
  1. +1
    -0
      src/client/js/components/inventory.js
  2. +7
    -21
      src/client/ui/templates/inventory/inventory.js
  3. +6
    -2
      src/server/components/auth/checkLoginRewards.js
  4. +10
    -18
      src/server/components/extensions/socialCommands.js
  5. +0
    -334
      src/server/components/gatherer.js
  6. +0
    -24
      src/server/components/inventory.js
  7. +0
    -30
      src/server/components/resourceNode.js
  8. +8
    -16
      src/server/components/spellbook.js
  9. +27
    -13
      src/server/components/trade.js
  10. +3
    -0
      src/server/components/workbench/buildPickedItems.js
  11. +4
    -7
      src/server/components/workbench/craft.js
  12. +362
    -537
      src/server/config/maps/fjolarok/map.json
  13. +5
    -5
      src/server/config/maps/fjolarok/zone.js
  14. +0
    -94
      src/server/config/recipes/enchanting.js
  15. +0
    -17
      src/server/config/recipes/enchanting/calculateAugmentMaterials.js
  16. +0
    -17
      src/server/config/recipes/enchanting/craftActions/augment.js
  17. +0
    -20
      src/server/config/recipes/enchanting/craftActions/reforge.js
  18. +0
    -20
      src/server/config/recipes/enchanting/craftActions/relevel.js
  19. +0
    -58
      src/server/config/recipes/enchanting/craftActions/reroll.js
  20. +0
    -36
      src/server/config/recipes/enchanting/craftActions/reslot.js
  21. +0
    -31
      src/server/config/recipes/enchanting/craftActions/scour.js
  22. +1
    -3
      src/server/config/recipes/recipes.js
  23. +2
    -2
      src/server/config/resourceNodes.js
  24. +0
    -38
      src/server/items/config/materials.js
  25. +42
    -24
      src/server/items/generators/spellbook.js
  26. +0
    -159
      src/server/items/salvager.js
  27. +7
    -46
      src/server/misc/rewardGenerator.js
  28. +1
    -1
      src/server/security/routerConfig.js
  29. +6
    -5
      src/server/world/instancer.js
  30. +14
    -8
      src/server/world/map.js
  31. +0
    -199
      src/server/world/resourceSpawner.js

+ 1
- 0
src/client/js/components/inventory.js View File

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

if (blueprint.destroyItems) {
rerender = true;
this.items.spliceWhere(i => blueprint.destroyItems.includes(i.id));
events.emit('onDestroyItems', blueprint.destroyItems, this.items);
}



+ 7
- 21
src/client/ui/templates/inventory/inventory.js View File

@@ -22,8 +22,6 @@ define([

centered: true,

items: [],

dragItem: null,
dragEl: null,
hoverCell: null,
@@ -65,7 +63,7 @@ define([
let container = this.el.find('.grid')
.empty();

let items = this.items
let items = window.player.inventory.items
.filter(function (item) {
return !item.eq;
});
@@ -184,7 +182,7 @@ define([
pos: this.dragItem.index()
}];

this.items.find(function (i) {
window.player.inventory.items.find(function (i) {
return (i.id === this.dragItem.data('item').id);
}, this).pos = this.dragItem.index();

@@ -196,7 +194,7 @@ define([
pos: this.hoverCell.index()
});

this.items.find(function (i) {
window.player.inventory.items.find(function (i) {
return (i.id === hoverCellItem.id);
}, this).pos = this.hoverCell.index();
} else {
@@ -264,11 +262,6 @@ define([
text: 'destroy',
callback: this.performItemAction.bind(this, item, 'destroyItem')
},
salvage: {
text: 'salvage',
callback: this.performItemAction.bind(this, item, 'salvageItem'),
hotkey: 'f'
},
stash: {
text: 'stash',
callback: this.performItemAction.bind(this, item, 'stashItem')
@@ -341,9 +334,6 @@ define([

if (!item.noDrop)
ctxConfig.push(menuItems.drop);

if ((!item.material) && (!item.noSalvage))
ctxConfig.push(menuItems.salvage);
}
}

@@ -477,19 +467,15 @@ define([
},

onGetItems: function (items, rerender) {
this.items = items;

if ((this.shown) && (rerender))
if (this.shown && rerender)
this.build();
},
onDestroyItems: function (itemIds) {
itemIds.forEach(function (id) {
let item = this.items.find(i => i.id === id);
itemIds.forEach(id => {
const item = window.player.inventory.items.find(i => i.id === id);
if (item === this.hoverItem)
this.hideTooltip();

this.items.spliceWhere(i => i.id === id);
}, this);
});

if (this.shown)
this.build();


+ 6
- 2
src/server/components/auth/checkLoginRewards.js View File

@@ -1,6 +1,7 @@
const scheduler = require('../../misc/scheduler');
const rewardGenerator = require('../../misc/rewardGenerator');
const mail = require('../../mail/mail');
const events = require('../../misc/events');

const calculateDaysSkipped = (oldTime, newTime) => {
let daysSkipped = 1;
@@ -63,8 +64,11 @@ module.exports = async (cpnAuth, data, character, cbDone) => {
accountInfo.loginStreak = loginStreak;

const itemCount = 1 + ~~(loginStreak / 2);
const rewards = rewardGenerator(itemCount);
if (!rewards) {
const rewardConfig = [];
events.emit('onBeforeGenerateLoginRewards', rewardConfig);
const rewards = rewardGenerator(itemCount, rewardConfig);
if (!rewards.length) {
cbDone();
return;
}


+ 10
- 18
src/server/components/extensions/socialCommands.js View File

@@ -1,9 +1,9 @@
let roles = require('../../config/roles');
let generator = require('../../items/generator');
let configSlots = require('../../items/config/slots');
let configMaterials = require('../../items/config/materials');
let factions = require('../../config/factions');
let connections = require('../../security/connections');
const events = require('../../misc/events');

const ban = require('../social/ban');
const rezone = require('../social/rezone');
@@ -45,7 +45,6 @@ let commandRoles = {
getXp: 10,
setPassword: 10,
giveSkin: 10,
getMaterials: 10,
rezone: 10,
startEvent: 10,
stopEvent: 10,
@@ -86,6 +85,8 @@ const contextActions = [
}
];

const commandActions = {};

module.exports = {
customChannels: [],
roleLevel: null,
@@ -96,6 +97,13 @@ module.exports = {
.filter((c, i) => (this.customChannels.indexOf(c) === i));
}

events.emit('onBeforeGetCommandRoles', commandRoles, commandActions);
Object.entries(commandActions).forEach(a => {
const [ actionName, actionHandler ] = a;

this[actionName] = actionHandler.bind(this);
});

this.roleLevel = roles.getRoleLevel(this.obj);
this.calculateActions();
},
@@ -694,22 +702,6 @@ module.exports = {
});
},

getMaterials: function (config) {
if (typeof(config) === 'object')
config = 100;
let inventory = this.obj.inventory;

Object.entries(configMaterials).forEach(([material, blueprint]) => {
inventory.getItem({
name: material,
quantity: config,
material: true,
...blueprint
});
});
},

broadcast: function (config, msg) {
if (typeof(msg) === 'object')
msg = Object.keys(msg).join(' ');


+ 0
- 334
src/server/components/gatherer.js View File

@@ -1,334 +0,0 @@
let qualityGenerator = require('../items/generators/quality');

module.exports = {
type: 'gatherer',

nodes: [],
gathering: null,
gatheringTtl: 0,
gatheringTtlMax: 7,
defaultTtlMax: 7,

simplify: function () {
return {
type: 'gatherer'
};
},

gather: function () {
if (this.gathering)
return;

let nodes = this.nodes;
if (nodes.length === 0)
return;

const { obj: { equipment, stats } } = this;

let firstNode = nodes[0];

if (!this.hasSpace(firstNode)) {
this.sendAnnouncement('Your bags are too full to gather any more resources.');
return;
}

this.gathering = firstNode;

let ttlMax = firstNode.resourceNode.ttl || this.defaultTtlMax;

if (firstNode.resourceNode.nodeType === 'fish') {
if (equipment.isSlotEmpty('tool')) {
this.sendAnnouncement('You need a fishing rod to fish');
this.gathering = null;

return;
}

let statCatchSpeed = Math.min(150, stats.values.catchSpeed);
ttlMax *= (1 - (statCatchSpeed / 200));
}

this.gatheringTtlMax = ttlMax;
this.gatheringTtl = this.gatheringTtlMax;
},

update: function () {
let gathering = this.gathering;

if (!gathering)
return;

let isFish = (gathering.resourceNode.nodeType === 'fish');
let hasSpace = this.hasSpace(this.gathering);

if (gathering.destroyed || !hasSpace) {
this.gathering = null;
this.gatheringTtl = 0;
this.obj.syncer.set(false, 'gatherer', 'progress', 100);
this.obj.syncer.set(true, 'gatherer', 'progress', 100);
if (isFish)
this.obj.syncer.set(true, 'gatherer', 'action', 'Fishing');
if (!hasSpace)
this.sendAnnouncement('Your bags are too full to gather any more resources.');
return;
}

if (this.gatheringTtl > 0) {
if ((this.gatheringTtl === this.gatheringTtlMax) && (gathering.width)) {
['x', 'y', 'width', 'height'].forEach(function (p) {
this.obj.syncer.set(false, 'gatherer', p, gathering[p]);
}, this);
}

this.gatheringTtl--;

let progress = 100 - ~~((this.gatheringTtl / this.gatheringTtlMax) * 100);
this.obj.syncer.set(true, 'gatherer', 'progress', progress);
if (isFish)
this.obj.syncer.set(true, 'gatherer', 'action', 'Fishing');

return;
}

this.completeGathering(gathering, isFish);
},

completeGathering: function (gathering, isFish) {
let resourceNode = gathering.resourceNode;
let gatherResult = extend({
obj: gathering
}, {
nodeType: resourceNode.nodeType,
blueprint: resourceNode.blueprint,
xp: resourceNode.xp,
items: gathering.inventory.items
});
this.obj.instance.eventEmitter.emitNoSticky('beforeGatherResource', gatherResult, this.obj);
this.obj.fireEvent('beforeGatherResource', gatherResult, this.obj);

this.obj.syncer.set(false, 'gatherer', 'progress', 100);

if (isFish) {
let catchChance = 40 + this.obj.stats.values.catchChance;
if (~~(Math.random() * 100) >= catchChance) {
this.sendAnnouncement('The fish got away');
this.gathering = null;

return;
}

gatherResult.items.forEach(function (g) {
if (g.slot)
return;
delete g.quantity;

qualityGenerator.generate(g, {
//100 x 2.86 = 2000 (chance for a common)
bonusMagicFind: this.obj.stats.values.fishRarity * 2.82
});

g.name = {
0: '',
1: 'Big ',
2: 'Giant ',
3: 'Trophy ',
4: 'Fabled '
}[g.quality] + g.name;

let statFishWeight = 1 + (this.obj.stats.values.fishWeight / 100);
let weight = ~~((gatherResult.blueprint.baseWeight + g.quality + (Math.random() * statFishWeight)) * 100) / 100;
g.stats = {
weight: weight
};

g.worth = ~~(weight * 10);
}, this);
}

if (isFish) {
let itemChance = 1 + this.obj.stats.values.fishItems;
if (~~(Math.random() * 500) < itemChance) {
gatherResult.items = [{
name: 'Cerulean Pearl',
material: true,
quantity: 1,
quality: 3,
sprite: [11, 9]
}];
}
}

let blueprint = gatherResult.blueprint;

gatherResult.items.forEach((item, i) => {
delete item.pos;

if (i === 0) {
if (blueprint.itemName)
item.name = blueprint.itemName;
if (blueprint.itemAmount)
item.quantity = ~~(Math.random() * blueprint.itemAmount[1]) + blueprint.itemAmount[0];
}

this.obj.inventory.getItem(item, false, false, true);

if (item.material)
this.obj.fireEvent('afterGatherResource', gatherResult);
});

if (!gatherResult.noChangeAmount)
resourceNode.gather();

this.obj.stats.getXp(gatherResult.xp, this.obj, gatherResult.obj);

if (gathering.destroyed) {
if (isFish)
this.sendAnnouncement('The school has been depleted');

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

this.gathering = null;
},

hasSpace: function (node) {
// By default, the player is allowed to gather "nothing"
if (!node.inventory || !node.inventory.items)
return true;
return this.obj.inventory.hasSpaceList(node.inventory.items);
},

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

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

let nodeType = node.resourceNode.nodeType;

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

return;
}
}

this.updateServerActions(true);

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

exit: function (node) {
if (!this.nodes.includes(node))
return;

this.updateServerActions(false);

this.nodes.spliceWhere(n => (n === node));
},

sendAnnouncement: function (msg) {
process.send({
method: 'events',
data: {
onGetAnnouncement: [{
obj: {
msg: msg
},
to: [this.obj.serverId]
}]
}
});
},

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

beforeMove: function () {
if (!this.gathering)
return;

['x', 'y', 'width', 'height'].forEach(p => {
this.obj.syncer.delete(false, 'gatherer', p);
});

this.obj.syncer.set(true, 'gatherer', 'progress', 100);
this.obj.syncer.set(false, 'gatherer', 'progress', 100);

if (this.gathering.resourceNode.nodeType === 'fish')
this.obj.syncer.set(true, 'gatherer', 'action', 'Fishing');

this.gathering = null;
},

beforeCastSpell: function () {
this.events.beforeMove.call(this);
},

beforeTakeDamage: function () {
this.events.beforeMove.call(this);
},

afterEquipItem: function (item) {
let nodes = this.nodes;
let nLen = nodes.length;

for (let i = 0; i < nLen; i++) {
let node = nodes[i];
if (item.slot !== 'tool')
continue;

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

if (this.gathering === node) {
if (this.gathering.resourceNode.nodeType === 'fish')
this.obj.syncer.set(true, 'gatherer', 'action', 'Fishing');

this.gathering = null;
this.obj.syncer.set(true, 'gatherer', 'progress', 100);
this.obj.syncer.set(false, 'gatherer', 'progress', 100);
}
}
}
}
},

afterUnequipItem: function (item) {
this.events.afterEquipItem.call(this, item);
}
}
};

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

@@ -1,5 +1,4 @@
let generator = require('../items/generator');
let salvager = require('../items/salvager');
let classes = require('../config/spirits');
let mtx = require('../mtx/mtx');
let factions = require('../config/factions');
@@ -302,29 +301,6 @@ module.exports = {
this.destroyItem(id, null, true);
},

salvageItem: function (id) {
let item = this.findItem(id);
if ((!item) || (item.material) || (item.quest) || (item.noSalvage) || (item.eq))
return;
let messages = [];
let items = salvager.salvage(item);
this.destroyItem(id);
for (const material of items) {
this.getItem(material, true, false, false, true);
messages.push({
className: 'q' + material.quality,
message: 'salvage (' + material.name + ' x' + material.quantity + ')'
});
}

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

destroyItem: function (id, amount, force) {
let item = this.findItem(id);
if (!item || (item.noDestroy && !force))


+ 0
- 30
src/server/components/resourceNode.js View File

@@ -1,30 +0,0 @@
module.exports = {
type: 'resourceNode',

collisionEnter: function (obj) {
if (!obj.player)
return;

obj.gatherer.enter(this.obj);
},

collisionExit: function (obj) {
if (!obj.player)
return;

obj.gatherer.exit(this.obj);
},

gather: function () {
this.quantity--;
if (this.quantity <= 0)
this.obj.destroyed = true;
},

simplify: function () {
return {
type: 'resourceNode',
nodeType: this.nodeType
};
}
};

+ 8
- 16
src/server/components/spellbook.js View File

@@ -2,6 +2,7 @@ let spellTemplate = require('../config/spells/spellTemplate');
let animations = require('../config/animations');
let playerSpells = require('../config/spells');
let playerSpellsConfig = require('../config/spellsConfig');
const { buildValues: buildRuneValues } = require('../items/generators/spellbook');

module.exports = {
type: 'spellbook',
@@ -170,24 +171,15 @@ module.exports = {
values: {}
}, playerSpell, playerSpellConfig, runeSpell);

for (let r in builtSpell.random) {
let range = builtSpell.random[r];
let roll = runeSpell.rolls[r] || 0;
runeSpell.rolls[r] = roll;
const runeValues = buildRuneValues(builtSpell);

let int = r.indexOf('i_') === 0;
Object.entries(runeValues).forEach(e => {
const [ property, value ] = e;

let val = range[0] + ((range[1] - range[0]) * roll);
if (int) {
val = ~~val;
r = r.replace('i_', '');
} else
val = ~~(val * 100) / 100;

builtSpell[r] = val;
builtSpell.values[r] = val;
runeSpell.values[r] = val;
}
builtSpell[property] = value;
builtSpell.values[property] = value;
runeSpell.values[property] = value;
});

if (runeSpell.properties) {
for (let p in runeSpell.properties)


+ 27
- 13
src/server/components/trade.js View File

@@ -1,6 +1,7 @@
let generator = require('../items/generator');
let statGenerator = require('../items/generators/stats');
let skins = require('../config/skins');
const events = require('../misc/events');

const sendMessage = ({ instance, id, serverId }, color, message) => {
instance.syncer.queue('onGetMessages', {
@@ -263,24 +264,35 @@ module.exports = {
if (!target)
return;

const { obj: { inventory, syncer } } = this;

let targetTrade = target.trade;

const item = this.obj.inventory.findItem(msg.itemId);
const item = inventory.findItem(msg.itemId);
if (!item)
return;

const oldQuantity = item.quantity;
this.obj.inventory.destroyItem(msg.itemId);
inventory.destroyItem(msg.itemId);

if (oldQuantity)
item.quantity = oldQuantity;

let worth = ~~(item.worth * targetTrade.markup.buy);
const sellEventMsg = {
item,
worth: ~~(item.worth * targetTrade.markup.buy)
};
events.emit('onBeforeSellItem', sellEventMsg);
const { worth } = sellEventMsg;

this.gold += worth;
if (typeof(worth) !== 'object') {
this.gold += worth;
syncer.set(true, 'trade', 'gold', this.gold);
} else
inventory.getItem(worth, false, false, false, true);

this.obj.syncer.set(true, 'trade', 'gold', this.gold);
this.obj.syncer.setArray(true, 'trade', 'removeItems', item.id);
syncer.setArray(true, 'trade', 'removeItems', item.id);

let buybackList = targetTrade.buybackList;
let name = this.obj.name;
@@ -311,14 +323,16 @@ module.exports = {

this.target = target;

let itemList = this.obj.inventory.items
.filter(i => ((i.worth > 0) && (!i.eq)));
itemList = extend([], itemList);
const itemList = extend([], this.obj.inventory.items.filter(i => i.worth && !i.eq));

this.obj.syncer.set(true, 'trade', 'sellList', {
markup: target.trade.markup.buy,
items: itemList.map(i => this.obj.inventory.simplifyItem(i))
});
const sellEventMsg = {
items: itemList,
markup: target.trade.markup.buy
};

events.emit('onBeforeGetSellList', sellEventMsg);

this.obj.syncer.set(true, 'trade', 'sellList', sellEventMsg);
},

startBuyback: function (msg) {


+ 3
- 0
src/server/components/workbench/buildPickedItems.js View File

@@ -7,6 +7,9 @@ module.exports = (crafter, recipe, { pickedItemIds = [] }) => {

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

if (!isItemValid)


+ 4
- 7
src/server/components/workbench/craft.js View File

@@ -20,7 +20,7 @@ module.exports = (cpnWorkbench, msg) => {
return null;

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

const materials = buildMaterials(crafter, recipe, msg);
const pickedItems = buildPickedItems(crafter, recipe, msg);
@@ -47,11 +47,8 @@ module.exports = (cpnWorkbench, msg) => {
resultMsg = recipe.craftAction(crafter, pickedItems);

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

applyItemStats(crafter, p, true);

@@ -59,8 +56,6 @@ module.exports = (cpnWorkbench, msg) => {
equipment.unequip(p.id);

spellbook.calcDps();

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

equipment.unequipAttrRqrGear();
@@ -84,6 +79,8 @@ module.exports = (cpnWorkbench, msg) => {
if (quantity && quantity.push)
item.quantity = quantity[0] + ~~(Math.random() * (quantity[1] - quantity[0]));

console.log(item);

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


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


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

@@ -10,14 +10,14 @@ module.exports = {
type: 'herb',
max: 1,
cdMax: 1710
},
'Sun Carp School': {
max: 900,
type: 'fish',
quantity: [6, 12]
}
},
objects: {
'sun carp school': {
max: 9,
type: 'fish',
quantity: [6, 12]
},
shopestrid: {
properties: {
cpnNotice: {


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

@@ -1,94 +0,0 @@
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
]
}];

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

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

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

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

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

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

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

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

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

@@ -1,58 +0,0 @@
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' };
};

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

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

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

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

+ 1
- 3
src/server/config/recipes/recipes.js View File

@@ -3,13 +3,11 @@ 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 ],
enchanting: [ ...recipesEnchanting ]
etching: [ ...recipesEtching ]
};

module.exports = {


src/server/config/herbs.js → src/server/config/resourceNodes.js View File

@@ -2,7 +2,7 @@ let events = require('../misc/events');

module.exports = {
init: function () {
events.emit('onBeforeGetHerbConfig', this);
events.emit('onBeforeGetResourceNodeConfig', this);
},

Moonbell: {
@@ -20,7 +20,7 @@ module.exports = {
cell: 51,
itemSprite: [1, 0]
},
'Sun Carp': {
'Sun Carp School': {
sheetName: 'objects',
itemSprite: [11, 2],
baseWeight: 3,

+ 0
- 38
src/server/items/config/materials.js View File

@@ -1,38 +0,0 @@
module.exports = {
'Iron Bar': {
sprite: [0, 0],
quality: 0
},
'Cloth Scrap': {
sprite: [0, 1],
quality: 0
},
'Leather Scrap': {
sprite: [0, 7],
quality: 0
},
'Common Essence': {
sprite: [0, 2],
quality: 0
},
'Magic Essence': {
sprite: [0, 3],
quality: 1
},
'Rare Essence': {
sprite: [0, 4],
quality: 2
},
'Epic Essence': {
sprite: [0, 5],
quality: 3
},
'Legendary Essence': {
sprite: [0, 6],
quality: 4
},
'Cerulean Pearl': {
sprite: [11, 9],
quality: 3
}
};

+ 42
- 24
src/server/items/generators/spellbook.js View File

@@ -42,6 +42,42 @@ const buildRolls = (item, blueprint, { random: spellProperties, negativeStats =
return result;
};

const buildValues = item => {
//Weapons have item.spell, runes just have item
const spell = item.spell ? item.spell : item;
const { name, type, rolls } = spell;

const useName = (name || type).toLowerCase();

const randomSpec = spellsConfig.spells[useName].random;

const result = {};

Object.entries(rolls).forEach(entry => {
const [ property, roll ] = entry;
const range = randomSpec[property];

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

let val = minRange + ((maxRange - minRange) * roll);

if (isInt) {
useProperty = property.substr(2);
val = Math.round(val);
} else
val = ~~(val * 100) / 100;

val = Math.max(range[0], Math.min(range[1], val));

result[useProperty] = val;
});

return result;
};

module.exports = {
generate: function (item, blueprint) {
blueprint = blueprint || {};
@@ -111,30 +147,10 @@ module.exports = {
}

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;

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

let val = minRange + ((maxRange - minRange) * roll);

if (isInt) {
useProperty = property.substr(2);
val = Math.round(val);
} else
val = ~~(val * 100) / 100;

val = Math.max(range[0], Math.min(range[1], val));

item.spell.values[useProperty] = val;
});
const values = buildValues(item);
item.spell.values = values;

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

buildValues
};

+ 0
- 159
src/server/items/salvager.js View File

@@ -1,159 +0,0 @@
let mappings = {
rune: [{
materials: [{
name: 'Essence',
qualityName: ['Common Essence', 'Magic Essence', 'Rare Essence', 'Epic Essence', 'Legendary Essence'],
chance: 100,
quantity: 1
}]
}],
slots: [{
list: ['neck', 'finger', 'twoHanded', 'oneHanded', 'offHand'],
materials: [{
name: 'Iron Bar',
chance: 100,
quantity: 3,
qualityMult: 1
}]
}, {
list: ['trinket'],
materials: [{
name: 'Essence',
qualityName: ['Common Essence', 'Magic Essence', 'Rare Essence', 'Epic Essence', 'Legendary Essence'],
chance: 100,
quantity: 1
}]
}, {
list: ['tool'],
materials: [{
name: 'Cerulean Pearl',
chance: 100,
quantity: 1,
quality: 3,
qualityMult: 1
}]
}],
types: [{
list: ['Helmet', 'Belt', 'Legplates', 'Gauntlets', 'Steel Boots', 'Breastplate'],
materials: [{
name: 'Iron Bar',
chance: 100,
quantity: 3,
qualityMult: 1
}]
}, {
list: ['Cowl', 'Robe', 'Gloves', 'Sash', 'Pants', 'Boots'],
materials: [{
name: 'Cloth Scrap',
chance: 100,
quantity: 3,
qualityMult: 1
}]
}, {
list: ['Leather Cap', 'Leather Armor', 'Leather Gloves', 'Leather Belt', 'Leather Pants', 'Leather Boots', 'Facemask', 'Scalemail', 'Scale Gloves', 'Scaled Binding', 'Scale Leggings', 'Scale Boots'],
materials: [{
name: 'Leather Scrap',
chance: 100,
quantity: 3,
qualityMult: 1
}]
}, {
list: ['Fishing Rod'],
materials: [{
name: 'Cerulean Pearl',
chance: 100,
quantity: 1,
qualityMult: 1
}]
}]
};

let materialItems = {
'Iron Bar': {
sprite: [0, 0]
},
'Cloth Scrap': {
sprite: [0, 1]
},
'Leather Scrap': {
sprite: [0, 7]
},
'Common Essence': {
sprite: [0, 2]
},
'Magic Essence': {
sprite: [0, 3]
},
'Rare Essence': {
sprite: [0, 4]
},
'Epic Essence': {
sprite: [0, 5]
},
'Legendary Essence': {
sprite: [0, 6]
},
'Cerulean Pearl': {
sprite: [11, 9]
}
};
module.exports = {
salvage: function (item, maxRoll) {
let result = [];

let materials = [];

let temp = mappings.slots.filter(m => m.list.indexOf(item.slot) > -1);
temp = temp.concat(mappings.types.filter(m => m.list.indexOf(item.type) > -1));

if (item.ability)
temp = temp.concat(mappings.rune);

temp.forEach(function (t) {
let mats = t.materials;
mats.forEach(function (m) {
let exists = materials.find(mf => (mf.name === m.name));
if (exists) {
exists.chance = Math.max(exists.chance, m.chance);
exists.quantity = Math.max(exists.quantity, m.quantity);
exists.qualityMult = Math.max(exists.qualityMult, m.qualityMult);
} else
materials.push(extend({}, m));
});
});

materials.forEach(function (m) {
if ((!maxRoll) && (Math.random() * 100 > m.chance))
return;

let max = m.quantity;
if (m.qualityMult)
max *= (m.qualityMult * (item.quality + 1));

let quantity = Math.ceil(random.norm(0, 1) * max) || 1;
if (maxRoll)
quantity = Math.ceil(max);

let newItem = {
name: m.name,
quantity: quantity,
quality: 0,
material: true,
sprite: null
};

if (m.qualityName) {
newItem.quality = item.quality;
newItem.name = m.qualityName[item.quality];
} else if (m.has('quality'))
newItem.quality = m.quality;

newItem.sprite = materialItems[newItem.name].sprite;

result.push(newItem);
});

return result;
}
};

+ 7
- 46
src/server/misc/rewardGenerator.js View File

@@ -1,40 +1,3 @@
const defaultConfig = [{
name: 'Iron Bar',
sprite: [0, 0],
quality: 0,
chance: 15
}, {
name: 'Cloth Scrap',
sprite: [0, 1],
quality: 0,
chance: 15
}, {
name: 'Leather Scrap',
sprite: [0, 7],
quality: 0,
chance: 15
}, {
name: 'Skyblossom',
sprite: [1, 2],
quality: 0,
chance: 8
}, {
name: 'Common Essence',
sprite: [0, 2],
quality: 0,
chance: 5
}, {
name: 'Magic Essence',
sprite: [0, 3],
quality: 1,
chance: 2
}, {
name: 'Rare Essence',
sprite: [0, 4],
quality: 2,
chance: 1
}];

const buildPool = config => {
const pool = [];

@@ -46,11 +9,9 @@ const buildPool = config => {
return pool;
};

const defaultPool = buildPool(defaultConfig);

module.exports = (itemCount, useConfig) => {
const config = useConfig || defaultConfig;
const pool = useConfig ? buildPool(useConfig) : defaultPool;
module.exports = (itemCount, useConfig = []) => {
const config = useConfig;
const pool = buildPool(useConfig);

const items = [];
@@ -58,13 +19,13 @@ module.exports = (itemCount, useConfig) => {
let pickName = pool[~~(Math.random() * pool.length)];
const pick = config.find(f => f.name === pickName);

if (!pick)
break;

let item = items.find(f => f.name === pickName);
if (!item) {
items.push({
name: pick.name,
material: true,
quality: pick.quality,
sprite: pick.sprite,
...pick,
quantity: pick.quantity || 1
});
} else


+ 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', 'learnAbility', 'unlearnAbility', 'dropItem', 'destroyItem', 'salvageItem', 'stashItem', 'mailItem', 'sortInventory'],
inventory: ['combineStacks', 'splitStack', 'activateMtx', 'useItem', 'moveItem', 'learnAbility', 'unlearnAbility', 'dropItem', 'destroyItem', 'stashItem', 'mailItem', 'sortInventory'],
equipment: ['equip', 'unequip', 'setQuickSlot', 'useQuickSlot', 'inspect'],
stash: ['withdraw', 'open'],
trade: ['buySell'],


+ 6
- 5
src/server/world/instancer.js View File

@@ -3,14 +3,13 @@ let syncer = require('./syncer');
let objects = require('../objects/objects');
let spawners = require('./spawners');
let physics = require('./physics');
let resourceSpawner = require('./resourceSpawner');
let spellCallbacks = require('../config/spells/spellCallbacks');
let questBuilder = require('../config/quests/questBuilder');
let randomMap = require('./randomMap');
let events = require('../events/events');
let scheduler = require('../misc/scheduler');
let mail = require('../mail/mail');
let herbs = require('../config/herbs');
let resourceNodes = require('../config/resourceNodes');
let eventEmitter = require('../misc/events');
const transactions = require('../security/transactions');

@@ -25,7 +24,7 @@ module.exports = {
this.zoneId = args.zoneId;

spellCallbacks.init();
herbs.init();
resourceNodes.init();
map.init(args);

const fakeInstance = {
@@ -64,15 +63,17 @@ module.exports = {

map.clientMap.zoneId = this.zoneId;

[resourceSpawner, syncer, objects, questBuilder, events, mail].forEach(i => i.init(fakeInstance));
[syncer, objects, questBuilder, events, mail].forEach(i => i.init(fakeInstance));
eventEmitter.emitNoSticky('onInitModules', fakeInstance);

this.tick();
},

tick: function () {
eventEmitter.emitNoSticky('onBeforeZoneUpdate');

events.update();
objects.update();
resourceSpawner.update();
spawners.update();
syncer.update();
scheduler.update();


+ 14
- 8
src/server/world/map.js View File

@@ -1,7 +1,6 @@
let objects = require('../objects/objects');
let physics = require('./physics');
let spawners = require('./spawners');
let resourceSpawner = require('./resourceSpawner');
let globalZone = require('../config/zoneBase');
let randomMap = require('./randomMap');
let events = require('../misc/events');
@@ -97,10 +96,6 @@ module.exports = {

this.zone = extend({}, globalZone, this.zone);

let resources = this.zone.resources || {};
for (let r in resources)
resourceSpawner.register(r, resources[r]);

mapFile = require('../' + this.path + '/' + this.name + '/map');
this.mapFile = mapFile;
//Fix for newer versions of Tiled
@@ -208,6 +203,8 @@ module.exports = {
},

build: function () {
events.emit('onBeforeBuildMap', this.name, this.zone);

const mapSize = {
w: mapFile.width,
h: mapFile.height
@@ -360,6 +357,17 @@ module.exports = {
}
},
object: function (layerName, cell) {
const buildObjectMsg = {
layerName,
mapScale,
obj: cell,
zoneConfig: this.zone,
ignore: false
};
events.emit('onBeforeBuildMapObject', buildObjectMsg);
if (buildObjectMsg.built)
return;

//Fixes for newer versions of tiled
cell.properties = objectifyProperties(cell.properties);
cell.polyline = cell.polyline || cell.polygon;
@@ -421,9 +429,7 @@ module.exports = {
});

room.exits.push(blueprint);
} else if (blueprint.properties.resource)
resourceSpawner.register(blueprint.properties.resource, blueprint);
else {
} else {
blueprint.exits = [];
blueprint.objects = [];
this.rooms.push(blueprint);


+ 0
- 199
src/server/world/resourceSpawner.js View File

@@ -1,199 +0,0 @@
let herbs = require('../config/herbs');

module.exports = {
nodes: [],

objects: null,
syncer: null,
zone: null,
physics: null,
map: null,

cdMax: 171,

init: function (instance) {
Object.assign(this, {
objects: instance.objects,
syncer: instance.syncer,
physics: instance.physics,
map: instance.map,
zone: instance.zone
});
},

register: function (name, blueprint) {
let exists = this.nodes.find(n => (n.blueprint.name === name));
if (exists) {
if (!exists.blueprint.positions) {
exists.blueprint.positions = [{
x: exists.blueprint.x,
y: exists.blueprint.y,
width: exists.blueprint.width,
height: exists.blueprint.height
}];
}

exists.blueprint.positions.push({
x: blueprint.x,
y: blueprint.y,
width: blueprint.width,
height: blueprint.height
});

return;
}

blueprint = extend({}, blueprint, herbs[name], {
name: name
});

let max = blueprint.max;
delete blueprint.max;

let chance = blueprint.chance;
delete blueprint.chance;

let cdMax = blueprint.cdMax;
delete blueprint.cdMax;

this.nodes.push({
cd: 0,
max: max,
chance: chance,
cdMax: cdMax,
blueprint: blueprint,
spawns: []
});
},

getRandomSpawnPosition: function (node, blueprint) {
//Get an accessible position
let w = this.physics.width;
let h = this.physics.height;

let x = blueprint.x;
let y = blueprint.y;

let position = null;

if (blueprint.type === 'herb') {
x = ~~(Math.random() * w);
y = ~~(Math.random() * h);

if (this.physics.isTileBlocking(x, y))
return false;

let spawn = this.map.spawn[0];

let path = this.physics.getPath(spawn, {
x: x,
y: y
});

let endTile = path[path.length - 1];
if (!endTile)
return false;
else if ((endTile.x !== x) || (endTile.y !== y))
return false;
//Don't spawn in rooms or on objects/other resources
let cell = this.physics.getCell(x, y);
if (cell.length > 0)
return false;
position = { x, y };
} else if (blueprint.positions) {
//Find all possible positions in which a node hasn't spawned yet
position = blueprint.positions.filter(f => !node.spawns.some(s => ((s.x === f.x) && (s.y === f.y))));
if (position.length === 0)
return false;

position = position[~~(Math.random() * position.length)];
}

return position;
},

spawn: function (node) {
let blueprint = node.blueprint;
let position = this.getRandomSpawnPosition(node, blueprint);
if (!position)
return false;

let quantity = 1;
if (blueprint.quantity)
quantity = blueprint.quantity[0] + ~~(Math.random() * (blueprint.quantity[1] - blueprint.quantity[0]));

let zoneLevel = this.zone.level;
zoneLevel = ~~(zoneLevel[0] + ((zoneLevel[1] - zoneLevel[0]) / 2));

let objBlueprint = extend({}, blueprint, position);
objBlueprint.properties = {
cpnResourceNode: {
nodeType: blueprint.type,
ttl: blueprint.ttl,
xp: zoneLevel * zoneLevel,
blueprint: extend({}, blueprint),
quantity: quantity
}
};

let obj = this.objects.buildObjects([objBlueprint]);
delete obj.ttl;

if (blueprint.type === 'herb') {
this.syncer.queue('onGetObject', {
x: obj.x,
y: obj.y,
components: [{
type: 'attackAnimation',
row: 0,
col: 4
}]
}, -1);
}

let inventory = obj.addComponent('inventory');
obj.layerName = 'objects';

node.spawns.push(obj);

let item = {
material: true,
type: node.type || null,
sprite: node.blueprint.itemSprite,
name: node.blueprint.name,
quantity: (blueprint.type !== 'fish') ? 1 : null,
quality: 0
};

if (blueprint.itemSheet)
item.spritesheet = blueprint.itemSheet;

if (blueprint.type === 'fish')
item.noStack = true;

inventory.getItem(item);

return true;
},

update: function () {
let nodes = this.nodes;
let nLen = nodes.length;

for (let i = 0; i < nLen; i++) {
let node = nodes[i];

let spawns = node.spawns;
spawns.spliceWhere(f => f.destroyed);

if (spawns.length < node.max) {
if (node.cd > 0)
node.cd--;
else if ((!node.chance || Math.random() < node.chance) && this.spawn(node))
node.cd = node.cdMax || this.cdMax;
}
}
}
};

Loading…
Cancel
Save