Browse Source

merged v0.4.2 from master

tags/v0.4.2
Shaun Kichenbrand 4 years ago
parent
commit
96e988416c
53 changed files with 1287 additions and 647 deletions
  1. +2
    -2
      README.md
  2. +3
    -7
      helpers/sims/dpt/index.js
  3. +179
    -180
      helpers/sims/dpt/sim.js
  4. +20
    -0
      helpers/sqlite-to-rethink/cheatsheet.js
  5. +1
    -1
      src/client/css/main.less
  6. +0
    -36
      src/client/js/rendering/renderer.js
  7. +4
    -4
      src/client/ui/templates/characters/styles.less
  8. +1
    -1
      src/client/ui/templates/characters/template.html
  9. +28
    -5
      src/client/ui/templates/context/context.js
  10. +11
    -1
      src/client/ui/templates/context/styles.less
  11. +1
    -1
      src/client/ui/templates/context/templateItem.html
  12. +2
    -2
      src/client/ui/templates/createCharacter/styles.less
  13. +4
    -8
      src/client/ui/templates/createCharacter/template.html
  14. +11
    -3
      src/client/ui/templates/events/styles.less
  15. +15
    -5
      src/client/ui/templates/hud/hud.js
  16. +3
    -2
      src/client/ui/templates/inventory/inventory.js
  17. +38
    -16
      src/client/ui/templates/login/styles.less
  18. +3
    -3
      src/client/ui/templates/login/template.html
  19. +11
    -1
      src/client/ui/templates/messages/messages.js
  20. +12
    -2
      src/client/ui/templates/quests/styles.less
  21. +1
    -1
      src/client/ui/templates/reputation/template.html
  22. +12
    -15
      src/client/ui/templates/spells/styles.less
  23. +18
    -8
      src/server/components/auth/checkLoginRewards.js
  24. +3
    -4
      src/server/components/inventory.js
  25. +16
    -13
      src/server/components/social/chat.js
  26. +4
    -3
      src/server/components/stats.js
  27. +28
    -0
      src/server/config/eventPhases/phaseDespawnMob.js
  28. +9
    -0
      src/server/config/eventPhases/phaseEnd.js
  29. +24
    -0
      src/server/config/eventPhases/phaseGiveRewards.js
  30. +27
    -0
      src/server/config/eventPhases/phaseGoto.js
  31. +15
    -0
      src/server/config/eventPhases/phaseKillAllMobs.js
  32. +28
    -0
      src/server/config/eventPhases/phaseMoveMob.js
  33. +9
    -0
      src/server/config/eventPhases/phaseSetDescription.js
  34. +90
    -69
      src/server/config/eventPhases/phaseSpawnMob.js
  35. +8
    -1
      src/server/config/eventPhases/phaseWait.js
  36. +0
    -72
      src/server/config/loginRewards.js
  37. +290
    -0
      src/server/config/maps/sewer/events/plagueOfRats.js
  38. +33
    -9
      src/server/config/maps/sewer/map.json
  39. +2
    -0
      src/server/config/maps/sewer/zone.js
  40. +0
    -36
      src/server/config/prophecies/titangrip.js
  41. +7
    -6
      src/server/config/quests/templates/questKillX.js
  42. +2
    -2
      src/server/config/serverConfig.js
  43. +65
    -52
      src/server/config/spells/spellFireblast.js
  44. +22
    -22
      src/server/config/spellsConfig.js
  45. +94
    -14
      src/server/events/events.js
  46. +4
    -3
      src/server/items/generators/stats.js
  47. +3
    -8
      src/server/misc/random.js
  48. +76
    -0
      src/server/misc/rewardGenerator.js
  49. +8
    -0
      src/server/objects/objBase.js
  50. +0
    -1
      src/server/server.js
  51. +2
    -27
      src/server/world/map.js
  52. +37
    -0
      src/server/world/map/canPathFromPos.js
  53. +1
    -1
      src/server/world/mobBuilder.js

+ 2
- 2
README.md View File

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

+ 3
- 7
helpers/sims/dpt/index.js View File

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

+ 179
- 180
helpers/sims/dpt/sim.js View File

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

+ 20
- 0
helpers/sqlite-to-rethink/cheatsheet.js View File

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


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

@@ -26,7 +26,7 @@ body {
> .right {
position: absolute;
right: 10px;
top: 92px;
top: 94px;
}

&.mobile {


+ 0
- 36
src/client/js/rendering/renderer.js View File

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


+ 4
- 4
src/client/ui/templates/characters/styles.less View File

@@ -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
- 1
src/client/ui/templates/characters/template.html View File

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


+ 28
- 5
src/client/ui/templates/context/context.js View File

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


+ 11
- 1
src/client/ui/templates/context/styles.less View File

@@ -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
src/client/ui/templates/context/templateItem.html View File

@@ -1 +1 @@
<div class="option">$TEXT$</div>
<div class="option">$TEXT$<div class='hotkey'></div></div>

+ 2
- 2
src/client/ui/templates/createCharacter/styles.less View File

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

}


+ 4
- 8
src/client/ui/templates/createCharacter/template.html View File

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


+ 11
- 3
src/client/ui/templates/events/styles.less View File

@@ -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
- 5
src/client/ui/templates/hud/hud.js View File

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


+ 3
- 2
src/client/ui/templates/inventory/inventory.js View File

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


+ 38
- 16
src/client/ui/templates/login/styles.less View File

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

}

}

+ 3
- 3
src/client/ui/templates/login/template.html View File

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

+ 11
- 1
src/client/ui/templates/messages/messages.js View File

@@ -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('&lt;')
.split('>')
.join('&gt;');

textbox.blur();
if (!config.success)
return;

if (val.trim() === '')
return;


+ 12
- 2
src/client/ui/templates/quests/styles.less View File

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

}

}

}

+ 1
- 1
src/client/ui/templates/reputation/template.html View File

@@ -16,4 +16,4 @@
<div class="tier"></div>
</div>
</div>
</div>
</div>

+ 12
- 15
src/client/ui/templates/spells/styles.less View File

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

}

}

+ 18
- 8
src/server/components/auth/checkLoginRewards.js View File

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

+ 3
- 4
src/server/components/inventory.js View File

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


+ 16
- 13
src/server/components/social/chat.js View File

@@ -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('&lt;')
@@ -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
}]
}
});


+ 4
- 3
src/server/components/stats.js View File

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


+ 28
- 0
src/server/config/eventPhases/phaseDespawnMob.js View File

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

+ 9
- 0
src/server/config/eventPhases/phaseEnd.js View File

@@ -0,0 +1,9 @@
module.exports = {
init: function () {
const event = this.event;

event.nextPhase = event.phases.length;

this.end = true;
}
};

+ 24
- 0
src/server/config/eventPhases/phaseGiveRewards.js View File

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

+ 27
- 0
src/server/config/eventPhases/phaseGoto.js View File

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

+ 15
- 0
src/server/config/eventPhases/phaseKillAllMobs.js View File

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

+ 28
- 0
src/server/config/eventPhases/phaseMoveMob.js View File

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

+ 9
- 0
src/server/config/eventPhases/phaseSetDescription.js View File

@@ -0,0 +1,9 @@
module.exports = {
desc: null,

init: function (event) {
event.eventManager.setEventDescription(event.config.name, this.desc);

this.end = true;
}
};

+ 90
- 69
src/server/config/eventPhases/phaseSpawnMob.js View File

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

+ 8
- 1
src/server/config/eventPhases/phaseWait.js View File

@@ -1,3 +1,10 @@
module.exports = {
oldTtl: null,

init: function () {
if (!this.oldTtl)
this.oldTtl = this.ttl;

this.ttl = this.oldTtl;
}
};

+ 0
- 72
src/server/config/loginRewards.js View File

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

+ 290
- 0
src/server/config/maps/sewer/events/plagueOfRats.js View File

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

+ 33
- 9
src/server/config/maps/sewer/map.json
File diff suppressed because it is too large
View File


+ 2
- 0
src/server/config/maps/sewer/zone.js View File

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


+ 0
- 36
src/server/config/prophecies/titangrip.js View File

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

+ 7
- 6
src/server/config/quests/templates/questKillX.js View File

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



+ 2
- 2
src/server/config/serverConfig.js View File

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


+ 65
- 52
src/server/config/spells/spellFireblast.js View File

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

+ 22
- 22
src/server/config/spellsConfig.js View File

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


+ 94
- 14
src/server/events/events.js View File

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


+ 4
- 3
src/server/items/generators/stats.js View File

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


+ 3
- 8
src/server/misc/random.js View File

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

+ 76
- 0
src/server/misc/rewardGenerator.js View File

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


+ 8
- 0
src/server/objects/objBase.js View File

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


+ 0
- 1
src/server/server.js View File

@@ -28,7 +28,6 @@ module.exports = {

let lessMiddleware = require('less-middleware');
app.use(lessMiddleware('../', {
force: true,
render: {
strictMath: true
}


+ 2
- 27
src/server/world/map.js View File

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

+ 37
- 0
src/server/world/map/canPathFromPos.js View File

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

+ 1
- 1
src/server/world/mobBuilder.js View File

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


Loading…
Cancel
Save