@@ -4,7 +4,7 @@ | |||
Built with NodeJS, JS, HTML and CSS. | |||
[Play Now](http://play.isleward.com/) | [Discord](https://discord.gg/gnsn7ZP) | [Subreddit](https://www.reddit.com/r/isleward) | [Blog](http://blog.isleward.com/) | [Wiki](http://wiki.isleward.com/Main_Page) | [Twitter](https://twitter.com/bigbadwofl) | [Patreon](http://patreon.com/bigbadwaffle) | |||
[Play Now](http://play.isleward.com/) | [Discord](https://discord.gg/gnsn7ZP) | [Subreddit](https://www.reddit.com/r/isleward) | [Blog](http://blog.isleward.com/) | [Wiki](http://wiki.isleward.com/Main_Page) | [Twitter](https://twitter.com/playisleward) | [Facebook](https://www.facebook.com/isleward/) | [Patreon](http://patreon.com/bigbadwaffle) | |||
[How to contribute](CONTRIBUTING.md) | |||
@@ -14,4 +14,4 @@ Built with NodeJS, JS, HTML and CSS. | |||
* [Linux](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(linux)) | |||
* [MacOS](https://gitlab.com/Isleward/isleward/wikis/installation-and-usage-(macos)) | |||
![Ingame Screenshots](http://i.imgur.com/p4ktJ5O.png) | |||
![Ingame Screenshots](http://i.imgur.com/p4ktJ5O.png) |
@@ -1,8 +1,4 @@ | |||
var extend = require('extend'); | |||
var requirejs = require('requirejs'); | |||
global.extend = require('../../../src/server/misc/clone'); | |||
global.extend = extend; | |||
requirejs(['sim'], function (sim) { | |||
sim.init(); | |||
}); | |||
const sim = require('./sim'); | |||
sim(); |
@@ -1,182 +1,181 @@ | |||
define([ | |||
'../../src/server/config/spellsConfig', | |||
'../../src/server/combat/combat' | |||
], function ( | |||
config, | |||
combat | |||
) { | |||
var spells = config.spells; | |||
var max = false; | |||
var maxTarget = false; | |||
spells['harvest life'] = { | |||
statType: ['str', 'int'], | |||
statMult: 0.76, | |||
auto: true, | |||
cdMax: 6, | |||
manaCost: 0, | |||
range: 1, | |||
random: { | |||
damage: [1.5, 5.7], | |||
healPercent: [5, 15] | |||
} | |||
}; | |||
var bloodBarrierMult = 1.25; | |||
spells['skeleton melee'] = { | |||
statType: ['str', 'int'], | |||
statMult: 0.27 * bloodBarrierMult, | |||
auto: true, | |||
cdMax: 5, | |||
manaCost: 0, | |||
range: 1, | |||
random: { | |||
damage: [1, 3.8] | |||
} | |||
}; | |||
var level = 20; | |||
var hp = [ | |||
32.70, | |||
65.40, | |||
98.10, | |||
130.80, | |||
163.50, | |||
196.20, | |||
228.90, | |||
261.60, | |||
294.30, | |||
327.00, | |||
359.70, | |||
392.40, | |||
425.10, | |||
457.80, | |||
490.50, | |||
523.20, | |||
555.90, | |||
588.60, | |||
621.30, | |||
654.00 | |||
]; | |||
var hpMax = [ | |||
160.48, | |||
324.53, | |||
489.90, | |||
660.79, | |||
841.44, | |||
1036.21, | |||
1249.50, | |||
1485.85, | |||
1749.87, | |||
2046.32, | |||
2380.05, | |||
2756.08, | |||
3179.54, | |||
3655.72, | |||
4190.09, | |||
4788.27, | |||
5456.08, | |||
6199.50, | |||
7024.73, | |||
7938.17 | |||
]; | |||
return { | |||
init: function () { | |||
var res = []; | |||
for (var s in spells) { | |||
var c = spells[s]; | |||
var d = c.random.damage || c.random.healing; | |||
if (!d) | |||
continue; | |||
var damage = d[0]; | |||
if (max) | |||
damage = d[1]; | |||
var config = { | |||
statType: c.statType, | |||
statMult: c.statMult, | |||
element: c.element, | |||
cd: c.cdMax, | |||
damage: damage, | |||
noCrit: true, | |||
noMitigate: !!c.random.healing, | |||
source: { | |||
stats: { | |||
values: { | |||
level: level, | |||
elementArcanePercent: 0, | |||
elementFrostPercent: 0, | |||
elementPoisonPercent: 0, | |||
elementHolyPercent: 0, | |||
elementFirePercent: 0 | |||
} | |||
}, | |||
}, | |||
target: { | |||
stats: { | |||
values: { | |||
armor: maxTarget ? (level * 50) : (level * 20), | |||
elementAllResist: maxTarget ? 100 : 0, | |||
elementArcaneResist: 0, | |||
elementFrostResist: 0, | |||
elementPoisonResist: 0, | |||
elementHolyResist: 0, | |||
elementFireResist: 0 | |||
} | |||
} | |||
const spellsConfig = require('../../../src/server/config/spellsConfig'); | |||
const combat = require('../../../src/server/combat/combat'); | |||
let spells = spellsConfig.spells; | |||
let max = true; | |||
let maxTarget = false; | |||
spells['harvest life'] = { | |||
statType: ['str', 'int'], | |||
statMult: 1, | |||
cdMax: 10, | |||
castTimeMax: 3, | |||
manaCost: 5, | |||
isAttack: true, | |||
range: 1, | |||
random: { | |||
damage: [4, 14], | |||
healPercent: [10, 30] | |||
} | |||
}; | |||
/*let bloodBarrierMult = 1.25; | |||
spells['skeleton melee'] = { | |||
statType: ['str', 'int'], | |||
statMult: 1 * bloodBarrierMult, | |||
auto: true, | |||
cdMax: 5, | |||
manaCost: 0, | |||
range: 1, | |||
random: { | |||
damage: [1, 3.8] | |||
} | |||
};*/ | |||
let level = 20; | |||
let hp = [ | |||
32.70, | |||
65.40, | |||
98.10, | |||
130.80, | |||
163.50, | |||
196.20, | |||
228.90, | |||
261.60, | |||
294.30, | |||
327.00, | |||
359.70, | |||
392.40, | |||
425.10, | |||
457.80, | |||
490.50, | |||
523.20, | |||
555.90, | |||
588.60, | |||
621.30, | |||
654.00 | |||
]; | |||
let hpMax = [ | |||
160.48, | |||
324.53, | |||
489.90, | |||
660.79, | |||
841.44, | |||
1036.21, | |||
1249.50, | |||
1485.85, | |||
1749.87, | |||
2046.32, | |||
2380.05, | |||
2756.08, | |||
3179.54, | |||
3655.72, | |||
4190.09, | |||
4788.27, | |||
5456.08, | |||
6199.50, | |||
7024.73, | |||
7938.17 | |||
]; | |||
module.exports = function () { | |||
let res = []; | |||
for (let s in spells) { | |||
let c = spells[s]; | |||
c.statType = c.statType || 'int'; | |||
let d = c.random.damage || c.random.healing; | |||
if (!d) | |||
continue; | |||
let damage = d[0]; | |||
if (max) | |||
damage = d[1]; | |||
var config = { | |||
statType: c.statType, | |||
statMult: c.statMult, | |||
element: c.element, | |||
cd: c.cdMax, | |||
damage: damage, | |||
noCrit: true, | |||
noMitigate: true, | |||
source: { | |||
stats: { | |||
values: { | |||
level: level, | |||
elementArcanePercent: 0, | |||
elementFrostPercent: 0, | |||
elementPoisonPercent: 0, | |||
elementHolyPercent: 0, | |||
elementFirePercent: 0 | |||
} | |||
}; | |||
var stat = c.statType; | |||
if (!stat.push) | |||
stat = [stat]; | |||
var minStat = level; | |||
var maxStat = level * 5; | |||
var mult = (stat.length == 1) ? 1 : 1; | |||
stat.forEach(s => config.source.stats.values[s] = (max ? maxStat : minStat) * mult); | |||
var amount = combat.getDamage(config).amount; | |||
var duration = c.random.i_duration; | |||
if (duration) | |||
amount *= max ? duration[1] : duration[0]; | |||
amount /= c.cdMax; | |||
var critChance = max ? 0.5 : 0.05; | |||
var critMult = max ? 3 : 1.5; | |||
amount = (amount * (1 - critChance)) + (amount * critChance * critMult); | |||
res.push({ | |||
name: s, | |||
dpt: (~~(amount * 10) / 10), | |||
cd: c.cdMax, | |||
mana: c.manaCost || '', | |||
tpk: ~~((maxTarget ? hpMax : hp)[level - 1] / amount), | |||
amount: amount | |||
}); | |||
} | |||
}, | |||
target: { | |||
stats: { | |||
values: { | |||
armor: maxTarget ? (level * 50) : (level * 20), | |||
elementAllResist: maxTarget ? 100 : 0, | |||
elementArcaneResist: 0, | |||
elementFrostResist: 0, | |||
elementPoisonResist: 0, | |||
elementHolyResist: 0, | |||
elementFireResist: 0 | |||
} | |||
} | |||
} | |||
res = res.sort((a, b) => (b.dpt - a.dpt)); | |||
console.log(); | |||
console.log('ability dpt'); | |||
console.log(); | |||
res.forEach(function (r) { | |||
var gap = new Array(20 - r.name.length); | |||
console.log(r.name + ': ' + gap.join(' ') + r.dpt + ' ' + r.tpk + ' ticks ' + (~~((r.tpk / 2.85) * 10) / 10) + ' seconds'); | |||
}); | |||
console.log(); | |||
} | |||
}; | |||
}); | |||
}; | |||
let stat = c.statType; | |||
if (!stat.push) | |||
stat = [stat]; | |||
const minStat = level; | |||
const maxStat = level * 10; | |||
stat.forEach(ss => { | |||
config.source.stats.values[ss] = (max ? maxStat : minStat); | |||
}); | |||
let amount = combat.getDamage(config).amount; | |||
let critChance = max ? 0.5 : 0.05; | |||
let critMult = max ? 3 : 1.5; | |||
let castTimeMax = c.castTimeMax; | |||
amount = (((amount / 100) * (100 - critChance)) + (((amount / 100) * critChance) * (critMult / 100))); | |||
let duration = c.random.i_duration; | |||
if (duration) | |||
amount *= max ? duration[1] : duration[0]; | |||
const div = (c.cdMax + castTimeMax) || 1; | |||
amount /= div; | |||
res.push({ | |||
name: s, | |||
dpt: ~~(~~(amount * 10) / 10), | |||
cd: c.cdMax, | |||
mana: c.manaCost || '', | |||
tpk: ~~((maxTarget ? hpMax : hp)[level - 1] / amount), | |||
amount: amount | |||
}); | |||
} | |||
res = res.sort((a, b) => (b.dpt - a.dpt)); | |||
console.log(); | |||
console.log('ability dpt'); | |||
console.log(); | |||
res.forEach(function (r) { | |||
let gap = new Array(20 - r.name.length); | |||
console.log(r.name + ': ' + gap.join(' ') + r.dpt + ' ' + r.tpk + ' ticks ' + (~~((r.tpk / 2.85) * 10) / 10) + ' seconds'); | |||
}); | |||
console.log(); | |||
}; | |||
@@ -52,6 +52,26 @@ r.db('live').table('character') | |||
}); | |||
}); | |||
r.db('live').table('character') | |||
.concatMap(row => { | |||
return row('value')('components') | |||
.filter(cpn => { | |||
return cpn('type').eq('inventory'); | |||
}) | |||
.concatMap(c => { | |||
return [{ | |||
name: row('value')('name'), | |||
account: row('value')('account'), | |||
cpn: c('items').filter(item => { | |||
return item('quantity').ge(30000); | |||
}) | |||
}]; | |||
}) | |||
.filter(c => { | |||
return c('cpn').count().ge(1); | |||
}); | |||
}); | |||
//Play time per account from low to high | |||
r.db('live').table('character') | |||
.concatMap(row => { | |||
@@ -26,7 +26,7 @@ body { | |||
> .right { | |||
position: absolute; | |||
right: 10px; | |||
top: 92px; | |||
top: 94px; | |||
} | |||
&.mobile { | |||
@@ -194,28 +194,11 @@ define([ | |||
alpha = Math.min(Math.max(0.15, alpha), 0.65); | |||
//Hack for xmas | |||
tile = 3; | |||
let min = Math.min( | |||
(i + j), | |||
(w - i + j), | |||
(i + h - j), | |||
(w - i + h - j) | |||
); | |||
let tree = false; | |||
let val = min + (Math.random() * 10); | |||
if (val < 23) { | |||
if (val < 18) | |||
tree = true; | |||
tile = 184; | |||
} | |||
if (mRandom() < 0.35) { | |||
tile = { | |||
5: 6, | |||
3: 0, | |||
4: 1, | |||
184: 185, | |||
53: 54 | |||
}[tile]; | |||
} | |||
@@ -234,25 +217,6 @@ define([ | |||
} | |||
container.addChild(sprite); | |||
if (tree) { | |||
let s = [216, 216, 217, 217, 217, 217, 217, 218, 219, 219, 219][~~(Math.random() * 11)]; | |||
s += 224; | |||
tile = new pixi.Sprite(this.getTexture('sprites', s)); | |||
tile.alpha = 0.7 + (Math.random() * 0.3); | |||
tile.position.x = i * scale; | |||
tile.position.y = j * scale; | |||
tile.width = scale; | |||
tile.height = scale; | |||
if (Math.random() < 0.5) { | |||
tile.position.x += scale; | |||
tile.scale.x = -scaleMult; | |||
} | |||
container.addChild(tile); | |||
} | |||
} | |||
} | |||
}, | |||
@@ -103,7 +103,7 @@ | |||
} | |||
.btn { | |||
background-color: @redC; | |||
background-color: @blueC; | |||
width: calc((100% - (16px * 2)) / 3); | |||
float: left; | |||
margin-right: 16px; | |||
@@ -114,12 +114,12 @@ | |||
} | |||
&:hover { | |||
background-color: @redB; | |||
background-color: @blueB; | |||
} | |||
&.deleting { | |||
background-color: @yellow; | |||
color: @black; | |||
background-color: @red; | |||
color: @white; | |||
} | |||
} | |||
@@ -1,5 +1,5 @@ | |||
<div class="uiCharacters"> | |||
<img class="logo" src="server/mods/iwd-merrywinter/images/logo.png" alt=""> | |||
<img class="logo" src="images/logo_0.png" alt=""> | |||
<div class="left"> | |||
<div class="character">loading characters...</div> | |||
</div> | |||
@@ -13,32 +13,41 @@ define([ | |||
tpl: template, | |||
modal: true, | |||
config: null, | |||
postRender: function () { | |||
this.onEvent('onContextMenu', this.onContextMenu.bind(this)); | |||
this.onEvent('onHideContextMenu', this.onMouseDown.bind(this)); | |||
this.onEvent('mouseDown', this.onMouseDown.bind(this)); | |||
this.onEvent('onUiKeyDown', this.onUiKeyDown.bind(this)); | |||
$('.ui-container').on('mouseup', this.onMouseDown.bind(this)); | |||
}, | |||
onContextMenu: function (config, e) { | |||
this.config = config; | |||
let container = this.el.find('.list') | |||
.empty(); | |||
config.forEach(function (c, i) { | |||
let text = c.text ? c.text : c; | |||
config.forEach((c, i) => { | |||
const text = (c.text || c); | |||
const hotkey = c.hotkey; | |||
let html = templateItem | |||
const html = templateItem | |||
.replace('$TEXT$', text); | |||
let row = $(html) | |||
const row = $(html) | |||
.appendTo(container); | |||
if (hotkey) | |||
row.find('.hotkey').html(`(${hotkey})`); | |||
if (c.callback) | |||
row.on('click', this.onClick.bind(this, i, c.callback)); | |||
else | |||
row.addClass('no-hover'); | |||
}, this); | |||
}); | |||
this.el | |||
.css({ | |||
@@ -57,6 +66,20 @@ define([ | |||
if (!this.el.is(':visible') || (e && (e.cancel || e.button === 2))) | |||
return; | |||
this.config = null; | |||
this.el.hide(); | |||
}, | |||
onUiKeyDown: function (keyEvent) { | |||
if (!this.config || !this.el.is(':visible')) | |||
return; | |||
const configEntry = this.config.find(({ hotkey }) => hotkey === keyEvent.key); | |||
if (!configEntry) | |||
return; | |||
configEntry.callback(); | |||
keyEvent.consumed = true; | |||
this.el.hide(); | |||
} | |||
}; | |||
@@ -22,6 +22,8 @@ | |||
padding: 4px; | |||
color: @white; | |||
cursor: pointer; | |||
display: flex; | |||
justify-content: center; | |||
&:hover:not(.no-hover) { | |||
background-color: @lightGray; | |||
@@ -31,6 +33,14 @@ | |||
line-height: 2px; | |||
color: @blueB; | |||
} | |||
.hotkey { | |||
color: @blueA; | |||
&:not(:empty) { | |||
margin-left: 8px; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -1 +1 @@ | |||
<div class="option">$TEXT$</div> | |||
<div class="option">$TEXT$<div class='hotkey'></div></div> |
@@ -130,7 +130,7 @@ | |||
} | |||
.btn { | |||
background-color: @redC; | |||
background-color: @blueC; | |||
width: calc((100% - @boxPadding) / 2); | |||
float: left; | |||
margin-right: @boxPadding; | |||
@@ -141,7 +141,7 @@ | |||
} | |||
&:hover { | |||
background-color: @redB; | |||
background-color: @blueB; | |||
} | |||
} | |||
@@ -1,5 +1,5 @@ | |||
<div class="uiCreateCharacter"> | |||
<img class="logo" src="server/mods/iwd-merrywinter/images/logo.png" alt=""> | |||
<img class="logo" src="images/logo_0.png" alt=""> | |||
<div class="box-left"> | |||
<div class="left"> | |||
<div class="label">name</div> | |||
@@ -27,13 +27,9 @@ | |||
<div class="heading">prophecies</div> | |||
<div class="list"> | |||
<div prophecy="austere" tooltip="you live a simple life and will only use common and magic items" class="prophecy">austere</div> | |||
<div prophecy="butcher" tooltip="you can engage other butchers in combat" class="prophecy">butcher</div> | |||
<div prophecy="crushable" tooltip="take quadruple damage from all sources" class="prophecy">crushable</div> | |||
<div prophecy="hardcore" tooltip="death is permanent" class="prophecy">hardcore</div> | |||
<div prophecy="titangrip" tooltip="gain double stats from weapons" class="prophecy">titangrip</div> | |||
<div class="prophecy disabled">waterproof</div> | |||
<div class="prophecy disabled">wolfborn</div> | |||
<div class="prophecy disabled">demonic</div> | |||
<div prophecy="butcher" tooltip="you will engage other butchers in combat" class="prophecy">butcher</div> | |||
<div prophecy="crushable" tooltip="you will take quadruple damage from all sources" class="prophecy">crushable</div> | |||
<div prophecy="hardcore" tooltip="your death will be permanent" class="prophecy">hardcore</div> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -1,11 +1,10 @@ | |||
@import "../../../css/colors.less"; | |||
@btnSize: 64px; | |||
.uiEvents { | |||
margin-top: 10px; | |||
width: 320px; | |||
width: 326px; | |||
.heading { | |||
color: @yellow; | |||
padding: 8px; | |||
@@ -35,6 +34,7 @@ | |||
.name { | |||
color: @blue; | |||
} | |||
} | |||
&.ready { | |||
@@ -45,13 +45,17 @@ | |||
.description { | |||
display: none; | |||
} | |||
} | |||
} | |||
} | |||
.btnClose { | |||
display: none; | |||
} | |||
} | |||
.mobile .uiEvents { | |||
@@ -78,6 +82,7 @@ | |||
> * { | |||
display: none; | |||
} | |||
} | |||
&.active { | |||
@@ -101,6 +106,9 @@ | |||
height: 100%; | |||
background: url('../../../images/uiIcons.png') -448px 0px; | |||
} | |||
} | |||
} | |||
} |
@@ -15,12 +15,18 @@ define([ | |||
stats: null, | |||
items: null, | |||
quickItem: null, | |||
postRender: function () { | |||
this.onEvent('onGetStats', this.events.onGetStats.bind(this)); | |||
this.onEvent('onGetPortrait', this.events.onGetPortrait.bind(this)); | |||
this.onEvent('onGetItems', this.events.onGetItems.bind(this)); | |||
this.onEvent('onDestroyItems', this.events.onDestroyItems.bind(this)); | |||
this.onEvent('onKeyDown', this.events.onKeyDown.bind(this)); | |||
this.find('.quickItem') | |||
.on('mousemove', this.showQuickItemTooltip.bind(this, true)) | |||
.on('mouseleave', this.showQuickItemTooltip.bind(this, false)); | |||
}, | |||
build: function () { | |||
@@ -61,7 +67,9 @@ define([ | |||
}); | |||
}, | |||
showQuickItemTooltip: function (show, item, e) { | |||
showQuickItemTooltip: function (show, e) { | |||
const item = this.quickItem; | |||
if (show) { | |||
let ttPos = null; | |||
if (e) { | |||
@@ -112,6 +120,7 @@ define([ | |||
this.items = items; | |||
const quickItem = items.find(f => f.has('quickSlot')); | |||
this.quickItem = quickItem; | |||
if (!quickItem) { | |||
const oldQuickItem = this.find('.quickItem').data('item'); | |||
if (oldQuickItem) | |||
@@ -132,13 +141,14 @@ define([ | |||
let imgX = -quickItem.sprite[0] * 64; | |||
let imgY = -quickItem.sprite[1] * 64; | |||
let el = this.find('.quickItem').show(); | |||
const el = this.find('.quickItem').show(); | |||
if (el.data('item') && el.data('item').id === quickItem.id) | |||
return; | |||
el | |||
.data('item', quickItem) | |||
.find('.icon') | |||
.css('background', 'url("' + spritesheet + '") ' + imgX + 'px ' + imgY + 'px') | |||
.on('mousemove', this.showQuickItemTooltip.bind(this, true, quickItem)) | |||
.on('mouseleave', this.showQuickItemTooltip.bind(this, false, quickItem)); | |||
.css('background', 'url("' + spritesheet + '") ' + imgX + 'px ' + imgY + 'px'); | |||
}, | |||
onKeyDown: function (key) { | |||
@@ -274,7 +274,8 @@ define([ | |||
}, | |||
salvage: { | |||
text: 'salvage', | |||
callback: this.performItemAction.bind(this, item, 'salvageItem') | |||
callback: this.performItemAction.bind(this, item, 'salvageItem'), | |||
hotkey: 'f' | |||
}, | |||
stash: { | |||
text: 'stash', | |||
@@ -362,7 +363,7 @@ define([ | |||
config.push(menuItems.destroy); | |||
} | |||
if (item.quantity > 1) | |||
if (item.quantity > 1 && !item.quest) | |||
config.push(menuItems.split); | |||
//if ((!item.noDrop) && (!item.quest)) | |||
@@ -11,15 +11,25 @@ | |||
.uiLogin { | |||
display: none; | |||
width: @totalWidth; | |||
height: @totalHeight; | |||
width: 562px; | |||
height: 475px; | |||
margin-top: -80px; | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
> * { | |||
flex-shrink: 0; | |||
} | |||
.logo { | |||
width: 562px; | |||
height: @logoHeight; | |||
margin-left: ((@totalWidth / 2) - (@logoWidth / 2)); | |||
margin-bottom: (@boxPadding * 3); | |||
filter: drop-shadow(0px -4px 0px @blackD) | |||
drop-shadow(0px 4px 0px @blackD) | |||
drop-shadow(4px 0px 0px @blackD) | |||
drop-shadow(-4px 0px 0px @blackD); | |||
} | |||
.right { | |||
@@ -86,7 +96,7 @@ | |||
height: 35px; | |||
.btn { | |||
background-color: @redC; | |||
background-color: @blueC; | |||
width: calc((100% - @boxPadding) / 2); | |||
float: left; | |||
margin-right: @boxPadding; | |||
@@ -97,7 +107,7 @@ | |||
} | |||
&:hover { | |||
background-color: @redB; | |||
background-color: @blueB; | |||
} | |||
} | |||
@@ -107,10 +117,8 @@ | |||
} | |||
.news { | |||
margin-left: -100px; | |||
margin-top: 40px; | |||
color: @white; | |||
width: 600px; | |||
text-align: center; | |||
cursor: pointer; | |||
filter: drop-shadow(0px -4px 0px @blackD) | |||
@@ -128,23 +136,25 @@ | |||
.uiLoginExtra { | |||
.extra { | |||
position: absolute; | |||
left: 10px; | |||
left: 50%; | |||
bottom: 10px; | |||
transform: translateX(-50%); | |||
display: flex; | |||
.btn { | |||
width: 180px; | |||
padding-left: 10px; | |||
padding-right: 10px; | |||
width: 100%; | |||
margin-bottom: 10px; | |||
background-color: @redD; | |||
margin-left: 10px; | |||
background-color: @blueD; | |||
color: @white; | |||
&:last-child { | |||
margin-bottom: 0px; | |||
&:first-child { | |||
margin-left: 0px; | |||
} | |||
&:hover { | |||
background-color: @redC; | |||
background-color: @blueC; | |||
} | |||
} | |||
@@ -171,6 +181,18 @@ | |||
} | |||
.mobile .uiLogin { | |||
margin-top: 0px; | |||
.mobile { | |||
.uiLogin { | |||
margin-top: 0px; | |||
.logo { | |||
margin-bottom: 30px; | |||
} | |||
.news { | |||
margin-top: 20px; | |||
} | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
<div class="uiLogin"> | |||
<img class="logo" src="server/mods/iwd-merrywinter/images/logo.png" alt=""> | |||
<img class="logo" src="images/logo_0.png" alt=""> | |||
<div class="right"> | |||
<div class="label">username</div> | |||
<input type="text" class="el textbox txtUsername" placeholder="username"> | |||
@@ -11,11 +11,11 @@ | |||
</div> | |||
<div class="message"></div> | |||
</div> | |||
<div class="news" location="https://gitlab.com/Isleward/isleward/tags/v0.4.0">[ Latest Release Notes ]</div> | |||
<div class="news" location="https://gitlab.com/Isleward/isleward/tags/v0.4.2">[ Latest Release Notes ]</div> | |||
<div class="extra"> | |||
<div class="el btn btnPatreon" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div> | |||
<div class="el btn btnPaypal" location="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BR2CC82WUAVEA">Donate on Paypal</div> | |||
<div class="el btn btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div> | |||
</div> | |||
<div class="version" location="https://gitlab.com/Isleward/isleward/tags/v0.4.0">v0.4.1</div> | |||
<div class="version" location="https://gitlab.com/Isleward/isleward/tags/v0.4.2">v0.4.2</div> | |||
</div> |
@@ -368,13 +368,23 @@ define([ | |||
} | |||
let textbox = this.find('input'); | |||
let val = textbox.val() | |||
let config = { | |||
success: true, | |||
message: textbox.val() | |||
}; | |||
events.emit('onBeforeChat', config); | |||
let val = config.message | |||
.split('<') | |||
.join('<') | |||
.split('>') | |||
.join('>'); | |||
textbox.blur(); | |||
if (!config.success) | |||
return; | |||
if (val.trim() === '') | |||
return; | |||
@@ -1,9 +1,8 @@ | |||
@import "../../../css/colors.less"; | |||
@btnSize: 64px; | |||
.uiQuests { | |||
width: 320px; | |||
width: 326px; | |||
.heading { | |||
color: @yellow; | |||
@@ -40,6 +39,7 @@ | |||
.description { | |||
display: none; | |||
} | |||
} | |||
.reward { | |||
@@ -55,6 +55,7 @@ | |||
> .reward { | |||
display: block; | |||
} | |||
} | |||
.ready-text { | |||
@@ -65,6 +66,7 @@ | |||
.name { | |||
color: @blue; | |||
} | |||
} | |||
&.ready { | |||
@@ -84,13 +86,17 @@ | |||
display: block; | |||
color: @white; | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
display: none; | |||
} | |||
} | |||
.mobile .uiQuests { | |||
@@ -117,6 +123,7 @@ | |||
> * { | |||
display: none; | |||
} | |||
} | |||
&.active { | |||
@@ -140,6 +147,9 @@ | |||
height: 100%; | |||
background: url('../../../images/uiIcons.png') -448px 0px; | |||
} | |||
} | |||
} | |||
} |
@@ -16,4 +16,4 @@ | |||
<div class="tier"></div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> |
@@ -1,5 +1,4 @@ | |||
@import "../../../css/colors.less"; | |||
@pad: 8px; | |||
@btnSize: 64px; | |||
@@ -7,7 +6,6 @@ | |||
position: absolute; | |||
right: 10px; | |||
top: 10px; | |||
height: (@btnSize + @pad); | |||
.spell { | |||
@@ -29,7 +27,6 @@ | |||
&:hover { | |||
filter: brightness(160%); | |||
-moz-filter: brightness(160%); | |||
} | |||
@@ -61,17 +58,16 @@ | |||
color: @white; | |||
font-size: 18px; | |||
z-index: 2; | |||
text-shadow: 2px 2px 0 #2d2136, | |||
-2px -2px 0 #2d2136, | |||
2px -2px 0 #2d2136, | |||
-2px 2px 0 #2d2136, | |||
2px 2px 0 #2d2136; | |||
text-shadow: 2px 2px 0 #2d2136, -2px -2px 0 #2d2136, 2px -2px 0 #2d2136, -2px 2px 0 #2d2136, 2px 2px 0 #2d2136; | |||
&.no-mana { | |||
color: @red; | |||
} | |||
} | |||
} | |||
} | |||
.mobile .uiSpells { | |||
@@ -83,7 +79,6 @@ | |||
display: flex; | |||
flex-direction: column-reverse; | |||
.spell { | |||
position: relative; | |||
margin: 8px 0px 0px 0px; | |||
@@ -95,12 +90,14 @@ | |||
&.active:before { | |||
content: ''; | |||
width: 4px; | |||
height: 100%; | |||
left: -4px; | |||
top: 0%; | |||
background-color: @greenB; | |||
position: absolute; | |||
width: 4px; | |||
height: 100%; | |||
left: -4px; | |||
top: 0%; | |||
background-color: @greenB; | |||
position: absolute; | |||
} | |||
} | |||
} |
@@ -1,5 +1,5 @@ | |||
const scheduler = require('../../misc/scheduler'); | |||
const loginRewards = require('../../config/loginRewards'); | |||
const rewardGenerator = require('../../misc/rewardGenerator'); | |||
const mail = require('../../mail/mail'); | |||
const calculateDaysSkipped = (oldTime, newTime) => { | |||
@@ -37,7 +37,8 @@ module.exports = async (cpnAuth, data, character, cbDone) => { | |||
const accountInfo = cpnAuth.accountInfo; | |||
const time = scheduler.getTime(); | |||
const lastLogin = accountInfo.lastLogin; | |||
let { lastLogin, loginStreak } = accountInfo; | |||
accountInfo.lastLogin = time; | |||
if ( | |||
@@ -53,13 +54,22 @@ module.exports = async (cpnAuth, data, character, cbDone) => { | |||
} | |||
const daysSkipped = calculateDaysSkipped(lastLogin, time); | |||
if (daysSkipped === 1) | |||
accountInfo.loginStreak++; | |||
else | |||
accountInfo.loginStreak -= (daysSkipped - 1); | |||
if (daysSkipped === 1) | |||
loginStreak++; | |||
else | |||
loginStreak = 1; | |||
loginStreak = Math.max(1, Math.min(21, loginStreak)); | |||
accountInfo.loginStreak = loginStreak; | |||
const itemCount = 1 + ~~(loginStreak / 2); | |||
const rewards = rewardGenerator(itemCount); | |||
if (!rewards) { | |||
cbDone(); | |||
return; | |||
} | |||
accountInfo.loginStreak = Math.min(1, Math.max(21, accountInfo.loginStreak)); | |||
rewards[0].msg = `Daily login reward for ${loginStreak} day${(loginStreak > 1) ? 's' : ''}:`; | |||
const rewards = loginRewards.generate(accountInfo.loginStreak); | |||
mail.sendMail(character.name, rewards, cbDone); | |||
}; |
@@ -270,7 +270,7 @@ module.exports = { | |||
splitStack: function (msg) { | |||
let item = this.findItem(msg.itemId); | |||
if (!item || !item.quantity || item.quantity <= msg.stackSize || msg.stackSize < 1) | |||
if (!item || !item.quantity || item.quantity <= msg.stackSize || msg.stackSize < 1 || item.quest) | |||
return; | |||
const hasSpace = this.hasSpace(item, true); | |||
@@ -449,8 +449,7 @@ module.exports = { | |||
}, | |||
mailItem: async function (msg) { | |||
return; | |||
let item = this.findItem(msg.itemId); | |||
/*let item = this.findItem(msg.itemId); | |||
if ((!item) || (item.noDrop) || (item.quest)) { | |||
this.resolveCallback(msg); | |||
return; | |||
@@ -482,7 +481,7 @@ module.exports = { | |||
} | |||
this.destroyItem(item.id); | |||
this.resolveCallback(msg); | |||
this.resolveCallback(msg);*/ | |||
}, | |||
hookItemEvents: function (items) { | |||
@@ -6,6 +6,9 @@ module.exports = (cpnSocial, msg) => { | |||
if (!msg.data.message) | |||
return; | |||
const { obj } = cpnSocial; | |||
const sendMessage = cpnSocial.sendMessage.bind(cpnSocial); | |||
msg.data.message = msg.data.message | |||
.split('<') | |||
.join('<') | |||
@@ -19,7 +22,7 @@ module.exports = (cpnSocial, msg) => { | |||
return; | |||
if (cpnSocial.muted) { | |||
cpnSocial.sendMessage('You have been muted from talking', 'color-redA'); | |||
sendMessage('You have been muted from talking', 'color-redA'); | |||
return; | |||
} | |||
@@ -34,10 +37,10 @@ module.exports = (cpnSocial, msg) => { | |||
if (history.length > 0) { | |||
if (history[history.length - 1].msg === messageString) { | |||
cpnSocial.sendMessage('You have already sent that message', 'color-redA'); | |||
sendMessage('You have already sent that message', 'color-redA'); | |||
return; | |||
} else if (history.length >= 3) { | |||
cpnSocial.sendMessage('You are sending too many messages', 'color-redA'); | |||
sendMessage('You are sending too many messages', 'color-redA'); | |||
return; | |||
} | |||
} | |||
@@ -47,17 +50,17 @@ module.exports = (cpnSocial, msg) => { | |||
return; | |||
if (!msg.data.item && !profanities.isClean(messageString)) { | |||
cpnSocial.sendMessage('Profanities detected in message. Blocked.', 'color-redA'); | |||
sendMessage('Profanities detected in message. Blocked.', 'color-redA'); | |||
return; | |||
} | |||
let playerLevel = cpnSocial.obj.level; | |||
let playedTime = cpnSocial.obj.stats.stats.played * 1000; | |||
let sessionStart = cpnSocial.obj.player.sessionStart; | |||
let playerLevel = obj.level; | |||
let playedTime = obj.stats.stats.played * 1000; | |||
let sessionStart = obj.player.sessionStart; | |||
let sessionDelta = time - sessionStart; | |||
if (playerLevel < 3 || playedTime + sessionDelta < 180000) { | |||
cpnSocial.sendMessage('Your character needs to be played for at least 3 minutes or be at least level 3 to be able to send messages in chat.', 'color-redA'); | |||
if (playerLevel < 3 && playedTime + sessionDelta < 180000) { | |||
sendMessage('Your character needs to be played for at least 3 minutes or be at least level 3 to be able to send messages in chat.', 'color-redA'); | |||
return; | |||
} | |||
@@ -66,9 +69,9 @@ module.exports = (cpnSocial, msg) => { | |||
time: time | |||
}); | |||
let charname = cpnSocial.obj.auth.charname; | |||
let charname = obj.auth.charname; | |||
let msgStyle = roles.getRoleMessageStyle(cpnSocial.obj) || ('color-grayB'); | |||
let msgStyle = roles.getRoleMessageStyle(obj) || ('color-grayB'); | |||
let msgEvent = { | |||
source: charname, | |||
@@ -83,7 +86,7 @@ module.exports = (cpnSocial, msg) => { | |||
else if (messageString[0] === '%') | |||
cpnSocial.sendPartyMessage(msg); | |||
else { | |||
let prefix = roles.getRoleMessagePrefix(cpnSocial.obj) || ''; | |||
let prefix = roles.getRoleMessagePrefix(obj) || ''; | |||
cons.emit('event', { | |||
event: 'onGetMessages', | |||
@@ -93,7 +96,7 @@ module.exports = (cpnSocial, msg) => { | |||
message: prefix + charname + ': ' + msg.data.message, | |||
item: msg.data.item, | |||
type: 'chat', | |||
source: cpnSocial.obj.name | |||
source: obj.name | |||
}] | |||
} | |||
}); | |||
@@ -391,16 +391,17 @@ module.exports = { | |||
preDeath: function (source) { | |||
const obj = this.obj; | |||
let deathEvent = {}; | |||
let killSource = source; | |||
if (source.follower) | |||
killSource = source.follower.master; | |||
if (killSource.player) | |||
killSource.stats.kill(obj); | |||
const deathEvent = { | |||
source: killSource | |||
}; | |||
obj.fireEvent('afterDeath', deathEvent); | |||
if (obj.player) { | |||
@@ -0,0 +1,28 @@ | |||
module.exports = { | |||
id: null, | |||
init: function () { | |||
const { instance, id } = this; | |||
const mob = instance.objects.find(o => o.id === id); | |||
if (!mob) { | |||
this.end = true; | |||
return; | |||
} | |||
this.instance.syncer.queue('onGetObject', { | |||
x: mob.x, | |||
y: mob.y, | |||
components: [{ | |||
type: 'attackAnimation', | |||
row: 0, | |||
col: 4 | |||
}] | |||
}, -1); | |||
mob.destroyed = true; | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,9 @@ | |||
module.exports = { | |||
init: function () { | |||
const event = this.event; | |||
event.nextPhase = event.phases.length; | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,24 @@ | |||
module.exports = { | |||
init: function (event) { | |||
const { config, rewards, eventManager } = event; | |||
event.participators.forEach(p => { | |||
const rList = [{ | |||
nameLike: 'Ancient Carp', | |||
removeAll: true | |||
}]; | |||
const pRewards = rewards[p.name]; | |||
rList.push(...pRewards); | |||
if (rList.length > 1) | |||
rList[1].msg = `${config.name} reward:`; | |||
eventManager.instance.mail.sendMail(p.name, rList); | |||
}); | |||
if (config.events && config.events.afterGiveRewards) | |||
config.events.afterGiveRewards(event); | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,27 @@ | |||
module.exports = { | |||
gotoPhaseIndex: null, | |||
repeats: 0, | |||
init: function () { | |||
if (!this.repeats) { | |||
this.end = true; | |||
return; | |||
} | |||
const event = this.event; | |||
const currentPhaseIndex = event.phases.findIndex(p => p === this); | |||
for (let i = this.gotoPhaseIndex; i < currentPhaseIndex; i++) { | |||
const phase = event.phases[i]; | |||
delete phase.end; | |||
delete phase.destroyed; | |||
} | |||
event.nextPhase = this.gotoPhaseIndex; | |||
this.repeats--; | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,15 @@ | |||
module.exports = { | |||
update: function () { | |||
const anyMobsAlive = this.event.objects.some(o => { | |||
if (!o.mob) | |||
return false; | |||
else if (!o.destroyed) | |||
return true; | |||
return false; | |||
}); | |||
if (!anyMobsAlive) | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,28 @@ | |||
module.exports = { | |||
id: null, | |||
mob: null, | |||
pos: { | |||
x: 0, | |||
y: 0 | |||
}, | |||
init: function () { | |||
const { instance, id, pos: { x, y } } = this; | |||
this.mob = instance.objects.find(o => o.id === id); | |||
this.mob.mob.originX = x; | |||
this.mob.mob.originY = y; | |||
this.mob.mob.goHome = true; | |||
}, | |||
update: function () { | |||
const { pos: { x: targetX, y: targetY } } = this; | |||
const { mob: { x, y } } = this; | |||
const distance = Math.max(Math.abs(x - targetX), Math.abs(y - targetY)); | |||
if (distance > 0) | |||
return; | |||
this.end = true; | |||
} | |||
}; |
@@ -0,0 +1,9 @@ | |||
module.exports = { | |||
desc: null, | |||
init: function (event) { | |||
event.eventManager.setEventDescription(event.config.name, this.desc); | |||
this.end = true; | |||
} | |||
}; |
@@ -1,12 +1,97 @@ | |||
let mobBuilder = require('../../world/mobBuilder'); | |||
const buildMob = (objects, mobConfig, x, y, mobIndex) => { | |||
const { id, sheetName, cell, name, properties, originX, originY, maxChaseDistance, dialogue, trade, chats, events } = mobConfig; | |||
let mob = objects.buildObjects([{ | |||
x: x, | |||
y: y, | |||
sheetName: sheetName || 'mobs', | |||
cell: cell, | |||
name: name, | |||
properties: properties | |||
}]); | |||
mobBuilder.build(mob, mobConfig); | |||
if (id) | |||
mob.id = id.split('$').join(mobIndex); | |||
if (originX) { | |||
mob.mob.originX = originX; | |||
mob.mob.originY = originY; | |||
mob.mob.goHome = true; | |||
mob.mob.maxChaseDistance = maxChaseDistance; | |||
//This is a hack to make mobs that run somewhere able to take damage | |||
delete mob.mob.events.beforeTakeDamage; | |||
} | |||
if (dialogue) { | |||
mob.addComponent('dialogue', { | |||
config: dialogue.config | |||
}); | |||
if (dialogue.auto) { | |||
mob.dialogue.trigger = objects.buildObjects([{ | |||
properties: { | |||
x: mob.x - 1, | |||
y: mob.y - 1, | |||
width: 3, | |||
height: 3, | |||
cpnNotice: { | |||
actions: { | |||
enter: { | |||
cpn: 'dialogue', | |||
method: 'talk', | |||
args: [{ | |||
targetName: 'angler nayla' | |||
}] | |||
}, | |||
exit: { | |||
cpn: 'dialogue', | |||
method: 'stopTalk' | |||
} | |||
} | |||
} | |||
} | |||
}]); | |||
} | |||
} | |||
if (trade) | |||
mob.addComponent('trade', trade); | |||
if (chats) | |||
mob.addComponent('chatter', chats); | |||
if (events) { | |||
mob.addBuiltComponent({ | |||
type: 'eventComponent', | |||
events: events | |||
}); | |||
} | |||
return mob; | |||
}; | |||
const spawnAnimation = (syncer, { x, y }) => { | |||
syncer.queue('onGetObject', { | |||
x: x, | |||
y: y, | |||
components: [{ | |||
type: 'attackAnimation', | |||
row: 0, | |||
col: 4 | |||
}] | |||
}, -1); | |||
}; | |||
module.exports = { | |||
spawnRect: null, | |||
mobs: null, | |||
init: function () { | |||
let objects = this.instance.objects; | |||
let spawnRect = this.spawnRect; | |||
const { spawnRect, instance: { objects, syncer } } = this; | |||
if (!this.mobs.push) | |||
this.mobs = [this.mobs]; | |||
@@ -60,79 +145,15 @@ module.exports = { | |||
this.spawnAnimation(mob); | |||
this.event.objects.push(mob); | |||
} else { | |||
let mob = objects.buildObjects([{ | |||
x: x, | |||
y: y, | |||
sheetName: l.sheetName || 'mobs', | |||
cell: l.cell, | |||
name: l.name, | |||
properties: l.properties | |||
}]); | |||
mobBuilder.build(mob, l); | |||
this.spawnAnimation(mob); | |||
if (l.id) { | |||
let id = l.id.split('$').join(i); | |||
mob.id = id; | |||
} | |||
const mob = buildMob(objects, l, x, y, i); | |||
this.event.objects.push(mob); | |||
if (l.dialogue) { | |||
mob.addComponent('dialogue', { | |||
config: l.dialogue.config | |||
}); | |||
if (l.dialogue.auto) { | |||
mob.dialogue.trigger = objects.buildObjects([{ | |||
properties: { | |||
x: mob.x - 1, | |||
y: mob.y - 1, | |||
width: 3, | |||
height: 3, | |||
cpnNotice: { | |||
actions: { | |||
enter: { | |||
cpn: 'dialogue', | |||
method: 'talk', | |||
args: [{ | |||
targetName: 'angler nayla' | |||
}] | |||
}, | |||
exit: { | |||
cpn: 'dialogue', | |||
method: 'stopTalk' | |||
} | |||
} | |||
} | |||
} | |||
}]); | |||
} | |||
} | |||
if (l.trade) | |||
mob.addComponent('trade', l.trade); | |||
if (l.chats) | |||
mob.addComponent('chatter', l.chats); | |||
mob.event = this.event; | |||
spawnAnimation(syncer, mob); | |||
} | |||
} | |||
}, this); | |||
if (!this.endMark) | |||
this.end = true; | |||
}, | |||
spawnAnimation: function (mob) { | |||
this.instance.syncer.queue('onGetObject', { | |||
x: mob.x, | |||
y: mob.y, | |||
components: [{ | |||
type: 'attackAnimation', | |||
row: 0, | |||
col: 4 | |||
}] | |||
}, -1); | |||
} | |||
}; |
@@ -1,3 +1,10 @@ | |||
module.exports = { | |||
oldTtl: null, | |||
init: function () { | |||
if (!this.oldTtl) | |||
this.oldTtl = this.ttl; | |||
this.ttl = this.oldTtl; | |||
} | |||
}; |
@@ -1,72 +0,0 @@ | |||
const config = [{ | |||
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 | |||
}]; | |||
let pool = []; | |||
config.forEach(function (c) { | |||
for (let i = 0; i < c.chance; i++) | |||
pool.push(c.name); | |||
}); | |||
module.exports = { | |||
generate: function (streak) { | |||
let items = []; | |||
const itemCount = 1 + ~~(streak / 2); | |||
for (let i = 0; i < itemCount; i++) { | |||
let pickName = pool[~~(Math.random() * pool.length)]; | |||
const pick = config.find(f => f.name === pickName); | |||
let item = items.find(f => f.name === pickName); | |||
if (!item) { | |||
items.push({ | |||
name: pick.name, | |||
material: true, | |||
quality: pick.quality, | |||
sprite: pick.sprite, | |||
quantity: 1 | |||
}); | |||
} else | |||
item.quantity++; | |||
} | |||
if (items.length > 0) | |||
items[0].msg = `Daily login reward for ${streak} day${(streak > 1) ? 's' : ''}:`; | |||
return items; | |||
} | |||
}; |
@@ -0,0 +1,290 @@ | |||
const { mobs: { rat: { level, faction, grantRep, regular: { drops } } } } = require('../zone'); | |||
const rewardGenerator = require('../../../../misc/rewardGenerator'); | |||
const rewardConfig = { | |||
regularKill: [{ | |||
name: 'Iron Bar', | |||
sprite: [0, 0], | |||
quality: 0, | |||
quantity: 5, | |||
chance: 10 | |||
}, { | |||
name: 'Cloth Scrap', | |||
sprite: [0, 1], | |||
quality: 0, | |||
quantity: 5, | |||
chance: 10 | |||
}, { | |||
name: 'Leather Scrap', | |||
sprite: [0, 7], | |||
quality: 0, | |||
quantity: 5, | |||
chance: 10 | |||
}, { | |||
name: 'Muddy Runestone', | |||
material: true, | |||
quality: 0, | |||
sprite: [6, 0], | |||
spritesheet: 'images/materials.png', | |||
chance: 1 | |||
}, { | |||
name: 'Epic Essence', | |||
material: true, | |||
sprite: [0, 5], | |||
quality: 3, | |||
chance: 1 | |||
}, { | |||
name: 'Rat Claw', | |||
material: true, | |||
sprite: [3, 0], | |||
spritesheet: 'images/materials.png', | |||
chance: 3 | |||
}] | |||
}; | |||
const descriptionStrings = { | |||
leadup: 'A bandit alchemist has been spotted in the sewer tunnels.', | |||
active: 'Rats are swarming towards the city.', | |||
success: 'Success: The rat invasion has been averted.', | |||
failure: 'Failure: The rats have made it to the city.', | |||
escapeCounter: 'Escapees: $ratEscapees$' | |||
}; | |||
const config = { | |||
cron: '0/3 * * * *', | |||
//cron: '* * * * *', | |||
idFirstSpawnPhase: 6, | |||
idFailPhase: 19, | |||
maxEscapees: 5, | |||
repeats: [5, 5, 3], | |||
//repeats: [1, 1, 1], | |||
rewardItemsPerKill: 3 | |||
}; | |||
const ratTargetPos = { | |||
x: 97, | |||
y: 87 | |||
}; | |||
const rat = { | |||
name: 'Bloodthirsty Rat', | |||
cell: 24, | |||
level, | |||
faction, | |||
grantRep, | |||
drops, | |||
hpMult: 3, | |||
pos: { | |||
x: 61, | |||
y: 62 | |||
}, | |||
originX: ratTargetPos.x, | |||
originY: ratTargetPos.y, | |||
maxChaseDistance: 1000, | |||
spells: [{ | |||
type: 'smokeBomb', | |||
radius: 1, | |||
duration: 20, | |||
range: 2, | |||
selfCast: 1, | |||
statMult: 1, | |||
damage: 0.125, | |||
element: 'poison', | |||
cdMax: 5, | |||
particles: { | |||
scale: { | |||
start: { | |||
min: 10, | |||
max: 25 | |||
}, | |||
end: { | |||
min: 10, | |||
max: 0 | |||
} | |||
}, | |||
opacity: { | |||
start: 0.3, | |||
end: 0 | |||
}, | |||
lifetime: { | |||
min: 1, | |||
max: 2 | |||
}, | |||
speed: { | |||
start: 3, | |||
end: 0 | |||
}, | |||
color: { | |||
start: ['db5538', '4ac441'], | |||
end: ['953f36', '386646'] | |||
}, | |||
chance: 0.125, | |||
randomColor: true, | |||
randomScale: true, | |||
blendMode: 'screen', | |||
spawnType: 'rect', | |||
spawnRect: { | |||
x: -10, | |||
y: -10, | |||
w: 20, | |||
h: 20 | |||
} | |||
} | |||
}], | |||
events: { | |||
afterMove: function () { | |||
const { obj: { x, y } } = this; | |||
if (x !== ratTargetPos.x || y !== ratTargetPos.y) | |||
return; | |||
const eventManager = this.obj.instance.events; | |||
const eventName = this.obj.event.config.name; | |||
eventManager.incrementEventVariable(eventName, 'ratEscapees', 1); | |||
const { active, escapeCounter } = descriptionStrings; | |||
const newDesc = `${active}<br /><br />${escapeCounter}`; | |||
eventManager.setEventDescription(eventName, newDesc); | |||
this.obj.destroyed = true; | |||
const totalEscapees = this.obj.event.variables.ratEscapees; | |||
if (totalEscapees >= config.maxEscapees) { | |||
const event = this.obj.event; | |||
event.currentPhase.end = true; | |||
event.nextPhase = config.idFailPhase; | |||
} | |||
}, | |||
afterDeath: function ({ source: { name } }) { | |||
const eventManager = this.obj.instance.events; | |||
const eventName = this.obj.event.config.name; | |||
const itemCount = config.rewardItemsPerKill; | |||
const rewards = rewardGenerator(itemCount, rewardConfig.regularKill); | |||
eventManager.addParticipantRewards(eventName, name, rewards); | |||
} | |||
} | |||
}; | |||
module.exports = { | |||
name: 'Plague of Rats', | |||
description: descriptionStrings.leadup, | |||
distance: -1, | |||
cron: config.cron, | |||
phases: [{ | |||
type: 'spawnMob', | |||
auto: true, | |||
mobs: [{ | |||
id: 'banditAlchemist', | |||
name: 'Bandit Alchemist', | |||
attackable: false, | |||
hpMult: 1, | |||
cell: 79, | |||
level: 15, | |||
pos: { | |||
x: 117, | |||
y: 62 | |||
} | |||
}] | |||
}, { | |||
type: 'moveMob', | |||
id: 'banditAlchemist', | |||
pos: { | |||
x: 64, | |||
y: 63 | |||
} | |||
}, { | |||
type: 'eventChain', | |||
config: [{ | |||
type: 'mobTalk', | |||
id: 'banditAlchemist', | |||
text: 'I think I cracked it! The serum should finally be complete.', | |||
delay: 15 | |||
}, { | |||
type: 'mobTalk', | |||
id: 'banditAlchemist', | |||
text: 'One taste of this and the rats\' hunger for blood will be instatiable.', | |||
delay: 15 | |||
}, { | |||
type: 'mobTalk', | |||
id: 'banditAlchemist', | |||
text: '*pours a bubbling liquid into a rat nest*', | |||
delay: 15 | |||
}, { | |||
type: 'mobTalk', | |||
id: 'banditAlchemist', | |||
text: 'Let\'s see how Fjolgard handles a rat infestation of the bloodthirsty variety.', | |||
delay: 15 | |||
}, { | |||
type: 'mobTalk', | |||
id: 'banditAlchemist', | |||
text: '*laughs*', | |||
delay: 15 | |||
}] | |||
}, { | |||
type: 'moveMob', | |||
id: 'banditAlchemist', | |||
pos: { | |||
x: 117, | |||
y: 63 | |||
}, | |||
auto: true | |||
}, { | |||
type: 'despawnMob', | |||
id: 'banditAlchemist' | |||
}, { | |||
type: 'setDescription', | |||
desc: descriptionStrings.active | |||
}, { | |||
type: 'spawnMob', | |||
mobs: [rat], | |||
auto: true | |||
}, { | |||
type: 'wait', | |||
ttl: 15 | |||
}, { | |||
type: 'goto', | |||
gotoPhaseIndex: config.idFirstSpawnPhase, | |||
repeats: config.repeats[0] | |||
}, { | |||
type: 'spawnMob', | |||
mobs: [rat], | |||
auto: true | |||
}, { | |||
type: 'wait', | |||
ttl: 7 | |||
}, { | |||
type: 'goto', | |||
gotoPhaseIndex: config.idFirstSpawnPhase + 3, | |||
repeats: config.repeats[1] | |||
}, { | |||
type: 'spawnMob', | |||
mobs: [rat], | |||
auto: true | |||
}, { | |||
type: 'wait', | |||
ttl: 3 | |||
}, { | |||
type: 'goto', | |||
gotoPhaseIndex: config.idFirstSpawnPhase + 6, | |||
repeats: config.repeats[2] | |||
}, { | |||
type: 'killAllMobs' | |||
}, { | |||
type: 'setDescription', | |||
desc: descriptionStrings.success, | |||
ttl: 50 | |||
}, { | |||
type: 'giveRewards' | |||
}, { | |||
type: 'end' | |||
}, { | |||
type: 'setDescription', | |||
desc: descriptionStrings.failure, | |||
ttl: 50 | |||
}] | |||
}; |
@@ -111,6 +111,7 @@ module.exports = { | |||
drops: { | |||
rolls: 3, | |||
chance: 100, | |||
noRandom: true, | |||
alsoRandom: true, | |||
magicFind: [2000, 125], | |||
@@ -231,6 +232,7 @@ module.exports = { | |||
level: 13, | |||
walkDistance: 0, | |||
faction: 'hostile', | |||
noQuest: true, | |||
grantRep: { | |||
fjolgard: 22 | |||
}, | |||
@@ -1,36 +0,0 @@ | |||
module.exports = { | |||
type: 'titangrip', | |||
init: function () { | |||
}, | |||
simplify: function () { | |||
return this.type; | |||
}, | |||
events: { | |||
afterEquipItem: function (item) { | |||
if (['oneHanded', 'twoHanded'].indexOf(item.slot) === -1) | |||
return; | |||
let stats = item.stats; | |||
for (let s in stats) { | |||
let val = stats[s]; | |||
this.obj.stats.addStat(s, val); | |||
} | |||
}, | |||
afterUnequipItem: function (item) { | |||
if (['oneHanded', 'twoHanded'].indexOf(item.slot) === -1) | |||
return; | |||
let stats = item.stats; | |||
for (let s in stats) { | |||
let val = stats[s]; | |||
this.obj.stats.addStat(s, -val); | |||
} | |||
} | |||
} | |||
}; |
@@ -14,7 +14,7 @@ module.exports = { | |||
if (this.mobName) { | |||
let mobType = mobTypes[this.mobName.toLowerCase()]; | |||
//Maybe the zoneFile changed in the meantime. If so, regenerate | |||
if ((!mobType) || (mobType.attackable === false)) | |||
if (!mobType || mobType.attackable === false || mobType.noQuest) | |||
this.mobName = null; | |||
} | |||
@@ -24,13 +24,14 @@ module.exports = { | |||
let mobBlueprint = mobTypes[m]; | |||
return ( | |||
(m !== 'default') && | |||
m !== 'default' && | |||
!mobBlueprint.noQuest && | |||
( | |||
(mobBlueprint.attackable) || | |||
(!mobBlueprint.has('attackable')) | |||
mobBlueprint.attackable || | |||
!mobBlueprint.has('attackable') | |||
) && | |||
(mobBlueprint.level <= ~~(this.obj.stats.values.level * 1.35)) && | |||
(mobCounts[m] > 1) | |||
mobBlueprint.level <= ~~(this.obj.stats.values.level * 1.35) && | |||
mobCounts[m] > 1 | |||
); | |||
}, this); | |||
@@ -1,5 +1,5 @@ | |||
module.exports = { | |||
version: '0.4.0', | |||
version: '0.4.2', | |||
port: 4000, | |||
startupMessage: 'Server: ready', | |||
defaultZone: 'fjolarok', | |||
@@ -8,7 +8,7 @@ module.exports = { | |||
// sqlite | |||
// rethink | |||
//eslint-disable-next-line no-process-env | |||
db: process.env.IWD_DB || 'rethink', | |||
db: process.env.IWD_DB || 'sqlite', | |||
//eslint-disable-next-line no-process-env | |||
dbHost: process.env.IWD_DB_HOST || 'localhost', | |||
//eslint-disable-next-line no-process-env | |||
@@ -1,3 +1,41 @@ | |||
const getTargetPos = (physics, obj, m) => { | |||
let targetPos = { | |||
x: m.x, | |||
y: m.y | |||
}; | |||
let dx = m.x - obj.x; | |||
let dy = m.y - obj.y; | |||
while ((dx === 0) && (dy === 0)) { | |||
dx = ~~(Math.random() * 2) - 1; | |||
dy = ~~(Math.random() * 2) - 1; | |||
} | |||
dx = ~~(dx / Math.abs(dx)); | |||
dy = ~~(dy / Math.abs(dy)); | |||
for (let l = 0; l < this.pushback; l++) { | |||
if (physics.isTileBlocking(targetPos.x + dx, targetPos.y + dy)) { | |||
if (physics.isTileBlocking(targetPos.x + dx, targetPos.y)) { | |||
if (physics.isTileBlocking(targetPos.x, targetPos.y + dy)) | |||
break; | |||
else { | |||
dx = 0; | |||
targetPos.y += dy; | |||
} | |||
} else { | |||
dy = 0; | |||
targetPos.x += dx; | |||
} | |||
} else { | |||
targetPos.x += dx; | |||
targetPos.y += dy; | |||
} | |||
} | |||
return targetPos; | |||
}; | |||
module.exports = { | |||
type: 'fireblast', | |||
@@ -17,7 +55,7 @@ module.exports = { | |||
const particleConfig = extend({}, this.particles); | |||
this.obj.fireEvent('beforeSpawnParticles', this, particleConfig); | |||
obj.fireEvent('beforeSpawnParticles', this, particleConfig); | |||
for (let i = x - radius; i <= x + radius; i++) { | |||
for (let j = y - radius; j <= y + radius; j++) { | |||
@@ -46,60 +84,33 @@ module.exports = { | |||
if (!m) { | |||
mLen--; | |||
continue; | |||
} | |||
if ((!m.aggro) || (!m.effects)) | |||
} else if (!m.aggro || !m.effects) | |||
continue; | |||
if (!this.obj.aggro.canAttack(m)) | |||
else if (!obj.aggro.canAttack(m)) | |||
continue; | |||
let targetEffect = m.effects.addEffect({ | |||
type: 'stunned', | |||
noMsg: true, | |||
new: true | |||
}); | |||
let targetPos = { | |||
x: m.x, | |||
y: m.y | |||
}; | |||
//Find out where the mob should end up | |||
let dx = m.x - obj.x; | |||
let dy = m.y - obj.y; | |||
while ((dx === 0) && (dy === 0)) { | |||
dx = ~~(Math.random() * 2) - 1; | |||
dy = ~~(Math.random() * 2) - 1; | |||
} | |||
dx = ~~(dx / Math.abs(dx)); | |||
dy = ~~(dy / Math.abs(dy)); | |||
for (let l = 0; l < this.pushback; l++) { | |||
if (physics.isTileBlocking(targetPos.x + dx, targetPos.y + dy)) { | |||
if (physics.isTileBlocking(targetPos.x + dx, targetPos.y)) { | |||
if (physics.isTileBlocking(targetPos.x, targetPos.y + dy)) | |||
break; | |||
else { | |||
dx = 0; | |||
targetPos.y += dy; | |||
} | |||
} else { | |||
dy = 0; | |||
targetPos.x += dx; | |||
} | |||
} else { | |||
targetPos.x += dx; | |||
targetPos.y += dy; | |||
} | |||
} | |||
const targetPos = getTargetPos(physics, obj, m); | |||
let distance = Math.max(Math.abs(m.x - targetPos.x), Math.abs(m.y - targetPos.y)); | |||
let ttl = distance * 125; | |||
m.clearQueue(); | |||
let damage = this.getDamage(m); | |||
m.stats.takeDamage(damage, 1, obj); | |||
if (m.destroyed) | |||
continue; | |||
const eventMsg = { | |||
success: true, | |||
targetPos | |||
}; | |||
m.fireEvent('beforePositionChange', eventMsg); | |||
if (!eventMsg.success) | |||
continue; | |||
this.sendAnimation({ | |||
id: m.id, | |||
components: [{ | |||
@@ -110,13 +121,14 @@ module.exports = { | |||
}] | |||
}); | |||
let damage = this.getDamage(m); | |||
m.stats.takeDamage(damage, 1, obj); | |||
let targetEffect = m.effects.addEffect({ | |||
type: 'stunned', | |||
noMsg: true, | |||
new: true | |||
}); | |||
if (!m.destroyed) { | |||
physics.removeObject(m, m.x, m.y); | |||
this.queueCallback(this.endEffect.bind(this, m, targetPos, targetEffect), ttl, null, m); | |||
} | |||
physics.removeObject(m, m.x, m.y); | |||
this.queueCallback(this.endEffect.bind(this, m, targetPos, targetEffect), ttl, null, m); | |||
} | |||
} | |||
} | |||
@@ -140,5 +152,6 @@ module.exports = { | |||
syncer.o.y = targetPos.y; | |||
target.instance.physics.addObject(target, target.x, target.y); | |||
target.fireEvent('afterPositionChange', targetPos); | |||
} | |||
}; |
@@ -17,7 +17,7 @@ let spells = { | |||
manaCost: 0, | |||
range: 9, | |||
random: { | |||
damage: [2, 7.6] | |||
damage: [2, 7.2] | |||
} | |||
}, | |||
@@ -25,12 +25,12 @@ let spells = { | |||
statType: 'int', | |||
statMult: 1, | |||
element: 'arcane', | |||
cdMax: 6, | |||
castTimeMax: 7, | |||
manaCost: 4, | |||
cdMax: 7, | |||
castTimeMax: 6, | |||
manaCost: 5, | |||
range: 9, | |||
random: { | |||
damage: [4, 15] | |||
damage: [4, 32] | |||
} | |||
}, | |||
'ice spear': { | |||
@@ -42,7 +42,7 @@ let spells = { | |||
manaCost: 4, | |||
range: 9, | |||
random: { | |||
damage: [2, 8], | |||
damage: [2, 15], | |||
i_freezeDuration: [6, 10] | |||
} | |||
}, | |||
@@ -54,7 +54,7 @@ let spells = { | |||
castTimeMax: 2, | |||
manaCost: 5, | |||
random: { | |||
damage: [2, 5], | |||
damage: [2, 10], | |||
i_radius: [1, 2.2], | |||
i_pushback: [2, 5] | |||
} | |||
@@ -63,8 +63,8 @@ let spells = { | |||
statType: 'int', | |||
statMult: 1, | |||
element: 'holy', | |||
cdMax: 4, | |||
castTimeMax: 6, | |||
cdMax: 6, | |||
castTimeMax: 3, | |||
range: 9, | |||
manaCost: 7, | |||
random: { | |||
@@ -77,7 +77,7 @@ let spells = { | |||
statMult: 1, | |||
element: 'holy', | |||
cdMax: 5, | |||
castTimeMax: 5, | |||
castTimeMax: 4, | |||
manaCost: 8, | |||
range: 9, | |||
radius: 3, | |||
@@ -108,7 +108,7 @@ let spells = { | |||
range: 10, | |||
manaCost: 3, | |||
random: { | |||
damage: [2, 8], | |||
damage: [2, 11], | |||
i_stunDuration: [6, 10] | |||
} | |||
}, | |||
@@ -117,10 +117,10 @@ let spells = { | |||
statMult: 1, | |||
cdMax: 20, | |||
castTimeMax: 0, | |||
manaCost: 12, | |||
manaCost: 10, | |||
random: { | |||
i_duration: [10, 20], | |||
i_chance: [20, 50] | |||
i_chance: [30, 60] | |||
} | |||
}, | |||
whirlwind: { | |||
@@ -133,18 +133,18 @@ let spells = { | |||
noDrop: true, | |||
random: { | |||
i_range: [1, 2.5], | |||
damage: [4, 15] | |||
damage: [4, 18] | |||
} | |||
}, | |||
smokebomb: { | |||
statType: 'dex', | |||
statMult: 1, | |||
element: 'poison', | |||
cdMax: 5, | |||
cdMax: 7, | |||
castTimeMax: 0, | |||
manaCost: 6, | |||
random: { | |||
damage: [0.25, 1.45], | |||
damage: [0.25, 1.2], | |||
i_radius: [1, 3], | |||
i_duration: [7, 13] | |||
} | |||
@@ -152,26 +152,26 @@ let spells = { | |||
ambush: { | |||
statType: 'dex', | |||
statMult: 1, | |||
cdMax: 16, | |||
castTimeMax: 7, | |||
cdMax: 15, | |||
castTimeMax: 3, | |||
range: 10, | |||
manaCost: 7, | |||
noDrop: true, | |||
random: { | |||
damage: [8, 28], | |||
i_stunDuration: [4, 6] | |||
damage: [8, 35], | |||
i_stunDuration: [4, 7] | |||
} | |||
}, | |||
'crystal spikes': { | |||
statType: ['dex', 'int'], | |||
statMult: 1, | |||
manaCost: 22, | |||
manaCost: 14, | |||
needLos: true, | |||
cdMax: 20, | |||
castTimeMax: 0, | |||
range: 9, | |||
random: { | |||
damage: [3, 14], | |||
damage: [3, 16], | |||
i_delay: [1, 4] | |||
}, | |||
negativeStats: [ | |||
@@ -2,6 +2,18 @@ let phaseTemplate = require('../config/eventPhases/phaseTemplate'); | |||
let fs = require('fs'); | |||
let mapList = require('../config/maps/mapList'); | |||
const applyVariablesToDescription = (desc, variables) => { | |||
if (!variables) | |||
return desc; | |||
Object.entries(variables).forEach(e => { | |||
const [key, value] = e; | |||
desc = desc.split(`$${key}$`).join(value); | |||
}); | |||
return desc; | |||
}; | |||
module.exports = { | |||
configs: [], | |||
nextId: 0, | |||
@@ -30,6 +42,7 @@ module.exports = { | |||
getEvent: function (name) { | |||
return this.configs.find(c => (c.name === name)).event.config; | |||
}, | |||
setEventDescription: function (name, desc) { | |||
let config = this.getEvent(name); | |||
let event = config.event; | |||
@@ -42,11 +55,15 @@ module.exports = { | |||
if ((config.events) && (config.events.beforeSetDescription)) | |||
config.events.beforeSetDescription(this); | |||
if (desc) | |||
if (desc) { | |||
desc = applyVariablesToDescription(desc, event.variables); | |||
config.description = desc; | |||
} | |||
event.participators.forEach(p => p.events.syncList()); | |||
}, | |||
setEventRewards: function (name, rewards) { | |||
let config = this.getEvent(name); | |||
let event = config.event; | |||
@@ -56,6 +73,36 @@ module.exports = { | |||
event.rewards = rewards; | |||
event.age = event.config.duration - 2; | |||
}, | |||
setParticipantRewards: function (eventName, participantName, newRewards) { | |||
const { event: { rewards } } = this.getEvent(eventName); | |||
rewards[participantName] = newRewards; | |||
}, | |||
addParticipantRewards: function (eventName, participantName, addRewards) { | |||
const { event: { rewards } } = this.getEvent(eventName); | |||
let pRewards = rewards[participantName]; | |||
if (!pRewards) { | |||
pRewards = []; | |||
rewards[participantName] = pRewards; | |||
} | |||
if (!addRewards.push) | |||
addRewards = [ addRewards ]; | |||
addRewards.forEach(r => { | |||
const { name, quantity = 1 } = r; | |||
const exists = pRewards.find(f => f.name === name); | |||
if (exists) | |||
exists.quantity = (exists.quantity || 1) + quantity; | |||
else | |||
pRewards.push(r); | |||
}); | |||
}, | |||
setWinText: function (name, text) { | |||
let config = this.getEvent(name); | |||
let event = config.event; | |||
@@ -65,6 +112,25 @@ module.exports = { | |||
event.winText = text; | |||
}, | |||
setEventVariable: function (eventName, variableName, value) { | |||
let config = this.getEvent(eventName); | |||
let event = config.event; | |||
if (!event) | |||
return; | |||
event.variables[variableName] = value; | |||
}, | |||
incrementEventVariable: function (eventName, variableName, delta) { | |||
let config = this.getEvent(eventName); | |||
let event = config.event; | |||
if (!event) | |||
return; | |||
const currentValue = event.variables[variableName] || 0; | |||
event.variables[variableName] = currentValue + delta; | |||
}, | |||
update: function () { | |||
let configs = this.configs; | |||
if (!configs) | |||
@@ -103,6 +169,9 @@ module.exports = { | |||
let event = { | |||
id: this.nextId++, | |||
config: extend({}, config), | |||
eventManager: this, | |||
variables: {}, | |||
rewards: {}, | |||
phases: [], | |||
participators: [], | |||
objects: [], | |||
@@ -127,7 +196,7 @@ module.exports = { | |||
if ((rewards) && (rewards[p.name])) { | |||
rewards[p.name].forEach(r => rList.push(r)); | |||
if (rList.length > 1) | |||
rList[1].msg = 'Fishing tournament reward:'; | |||
rList[1].msg = `${event.config.name} reward:`; | |||
} | |||
this.instance.mail.sendMail(p.name, rList); | |||
@@ -243,10 +312,18 @@ module.exports = { | |||
phase.destroyed = true; | |||
continue; | |||
} else { | |||
if (!phase.auto) | |||
if (phase.has('ttl')) { | |||
if (phase.ttl === 0) { | |||
phase.end = true; | |||
continue; | |||
} | |||
phase.ttl--; | |||
stillBusy = true; | |||
} else if (!phase.auto) | |||
stillBusy = true; | |||
phase.update(); | |||
phase.update(event); | |||
} | |||
} | |||
} | |||
@@ -275,18 +352,21 @@ module.exports = { | |||
for (let i = event.nextPhase; i < pLen; i++) { | |||
let p = phases[i]; | |||
let phaseFile = 'phase' + p.type[0].toUpperCase() + p.type.substr(1); | |||
let typeTemplate = require('../config/eventPhases/' + phaseFile); | |||
let phase = extend({ | |||
instance: this.instance, | |||
event: event | |||
}, phaseTemplate, typeTemplate, p); | |||
event.phases.push(phase); | |||
phase.init(); | |||
let phase = event.phases[i]; | |||
if (!phase) { | |||
let phaseFile = 'phase' + p.type[0].toUpperCase() + p.type.substr(1); | |||
let typeTemplate = require('../config/eventPhases/' + phaseFile); | |||
phase = extend({ | |||
instance: this.instance, | |||
event: event | |||
}, phaseTemplate, typeTemplate, p); | |||
event.phases.push(phase); | |||
event.currentPhase = phase; | |||
} | |||
event.nextPhase = i + 1; | |||
phase.init(event); | |||
if (!p.auto) { | |||
stillBusy = true; | |||
@@ -660,9 +660,10 @@ module.exports = { | |||
stat: i.stat | |||
}; | |||
if (i.value) | |||
stat.value = i.value[0] + ~~(Math.random() * (i.value[1] - i.value[0] + 1)); | |||
else if (i.valueMult) { | |||
if (i.value) { | |||
const [min, max] = i.value; | |||
stat.value = Math.ceil(random.expNorm(min, max)); | |||
} else if (i.valueMult) { | |||
let statBlueprint = this.stats[i.stat]; | |||
if (statBlueprint.generator) { | |||
@@ -27,14 +27,9 @@ extend(random, { | |||
}, | |||
expNorm: function(low, high) { | |||
var roll = random.norm(0, 100); | |||
if (roll > 50) | |||
roll = 100 - roll; | |||
roll = (50 - roll) / 50; | |||
roll = low + ((high - low) * roll); | |||
var roll = Math.abs(random.norm(0, 100) - 50) / 50; | |||
const result = low + ((high - low) * roll); | |||
return roll; | |||
return result; | |||
} | |||
}); |
@@ -0,0 +1,76 @@ | |||
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 = []; | |||
config.forEach(c => { | |||
for (let i = 0; i < c.chance; i++) | |||
pool.push(c.name); | |||
}); | |||
return pool; | |||
}; | |||
const defaultPool = buildPool(defaultConfig); | |||
module.exports = (itemCount, useConfig) => { | |||
const config = useConfig || defaultConfig; | |||
const pool = useConfig ? buildPool(useConfig) : defaultPool; | |||
const items = []; | |||
for (let i = 0; i < itemCount; i++) { | |||
let pickName = pool[~~(Math.random() * pool.length)]; | |||
const pick = config.find(f => f.name === pickName); | |||
let item = items.find(f => f.name === pickName); | |||
if (!item) { | |||
items.push({ | |||
name: pick.name, | |||
material: true, | |||
quality: pick.quality, | |||
sprite: pick.sprite, | |||
quantity: pick.quantity || 1 | |||
}); | |||
} else | |||
item.quantity += (pick.quantity || 1); | |||
} | |||
return items; | |||
}; | |||
@@ -32,6 +32,14 @@ module.exports = { | |||
return cpn; | |||
}, | |||
addBuiltComponent: function (cpn) { | |||
this[cpn.type] = cpn; | |||
cpn.obj = this; | |||
this.components.push(cpn); | |||
return cpn; | |||
}, | |||
removeComponent: function (type) { | |||
let cpn = this[type]; | |||
if (!cpn) | |||
@@ -28,7 +28,6 @@ module.exports = { | |||
let lessMiddleware = require('less-middleware'); | |||
app.use(lessMiddleware('../', { | |||
force: true, | |||
render: { | |||
strictMath: true | |||
} | |||
@@ -7,6 +7,7 @@ let randomMap = require('./randomMap'); | |||
let events = require('../misc/events'); | |||
const mapObjects = require('./map/mapObjects'); | |||
const canPathFromPos = require('./map/canPathFromPos'); | |||
let mapFile = null; | |||
let mapScale = null; | |||
@@ -69,7 +70,6 @@ module.exports = { | |||
try { | |||
this.zone = require('../' + this.path + '/' + this.name + '/zone'); | |||
} catch (e) { | |||
console.log(e); | |||
this.zone = globalZone; | |||
} | |||
events.emit('onAfterGetZone', this.name, this.zone); | |||
@@ -442,31 +442,6 @@ module.exports = { | |||
//Find if any spawns can path to a position. This is important for when maps change and players | |||
// log in on tiles that aren't blocking but not able to reach anywhere useful | |||
canPathFromPos: function (pos) { | |||
const fnCanPath = positions => { | |||
return positions.some(p => { | |||
const path = physics.getPath(pos, p); | |||
//Are we on the position? | |||
if (!path.length) | |||
return true; | |||
const { x, y } = path[path.length - 1]; | |||
//Can we reach the position? | |||
const isFullPath = (p.x === x && p.y === y); | |||
if (isFullPath) | |||
return true; | |||
return false; | |||
}); | |||
}; | |||
const canPathToSpawn = fnCanPath(this.spawn); | |||
if (canPathToSpawn) | |||
return true; | |||
const portals = objects.objects.filter(o => o.portal); | |||
const canPathToPortal = fnCanPath(portals); | |||
return canPathToPortal; | |||
return canPathFromPos(this, pos); | |||
} | |||
}; |
@@ -0,0 +1,37 @@ | |||
let objects = require('../../objects/objects'); | |||
let physics = require('../physics'); | |||
const canPath = (pos, positions, maxDistance = 0) => { | |||
return positions.some(p => { | |||
const path = physics.getPath(pos, p); | |||
//Are we on the position? | |||
if (!path.length) | |||
return true; | |||
const { x, y } = path[path.length - 1]; | |||
//Can we get close enough to the position? | |||
const isCloseEnough = Math.max(Math.abs(p.x - x), Math.abs(p.y - y)) <= maxDistance; | |||
if (isCloseEnough) | |||
return true; | |||
return false; | |||
}); | |||
}; | |||
module.exports = (map, pos) => { | |||
const canPathToSpawn = canPath(pos, map.spawn); | |||
if (canPathToSpawn) | |||
return true; | |||
const portals = objects.objects.filter(o => o.portal); | |||
const canPathToPortal = canPath(pos, portals); | |||
if (canPathToPortal) | |||
return true; | |||
const doors = objects.objects.filter(o => o.door); | |||
const canPathToDoor = canPath(pos, doors, 1); | |||
return canPathToDoor; | |||
}; |
@@ -106,7 +106,7 @@ module.exports = { | |||
mob.equipment.unequipAll(); | |||
mob.inventory.clear(); | |||
let hp = level * 32.7; | |||
let hp = level * 40; | |||
statValues.hpMax = hp; | |||
statValues.level = level; | |||