Преглед на файлове

Merge branch 'master' into qol-chat

tags/v0.4.3^2
Vildravn преди 4 години
родител
ревизия
6a355017fd
променени са 100 файла, в които са добавени 2604 реда и са изтрити 1161 реда
  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. +6
    -16
      src/client/css/colors.less
  6. +12
    -10
      src/client/css/main.less
  7. +57
    -2
      src/client/js/config.js
  8. +5
    -1
      src/client/js/main.js
  9. +16
    -33
      src/client/js/objects/objects.js
  10. +0
    -36
      src/client/js/rendering/renderer.js
  11. +36
    -6
      src/client/js/sound/sound.js
  12. +14
    -3
      src/client/ui/factory.js
  13. +11
    -2
      src/client/ui/templates/characters/characters.js
  14. +18
    -15
      src/client/ui/templates/characters/styles.less
  15. +1
    -1
      src/client/ui/templates/characters/template.html
  16. +28
    -5
      src/client/ui/templates/context/context.js
  17. +11
    -1
      src/client/ui/templates/context/styles.less
  18. +1
    -1
      src/client/ui/templates/context/templateItem.html
  19. +2
    -2
      src/client/ui/templates/createCharacter/createCharacter.js
  20. +25
    -13
      src/client/ui/templates/createCharacter/styles.less
  21. +4
    -8
      src/client/ui/templates/createCharacter/template.html
  22. +16
    -11
      src/client/ui/templates/death/styles.less
  23. +1
    -0
      src/client/ui/templates/equipment/styles.less
  24. +16
    -2
      src/client/ui/templates/events/events.js
  25. +15
    -9
      src/client/ui/templates/events/styles.less
  26. +15
    -5
      src/client/ui/templates/hud/hud.js
  27. +54
    -22
      src/client/ui/templates/inventory/inventory.js
  28. +173
    -34
      src/client/ui/templates/inventory/styles.less
  29. +1
    -1
      src/client/ui/templates/login/login.js
  30. +78
    -61
      src/client/ui/templates/login/styles.less
  31. +3
    -3
      src/client/ui/templates/login/template.html
  32. +112
    -0
      src/client/ui/templates/mainMenu/mainMenu.js
  33. +49
    -0
      src/client/ui/templates/mainMenu/styles.less
  34. +8
    -0
      src/client/ui/templates/mainMenu/template.html
  35. +1
    -1
      src/client/ui/templates/menu/menu.js
  36. +1
    -1
      src/client/ui/templates/menu/styles.less
  37. +1
    -1
      src/client/ui/templates/menu/template.html
  38. +65
    -30
      src/client/ui/templates/messages/messages.js
  39. +66
    -41
      src/client/ui/templates/messages/styles.less
  40. +4
    -1
      src/client/ui/templates/middleHud/styles.less
  41. +7
    -4
      src/client/ui/templates/online/styles.less
  42. +118
    -44
      src/client/ui/templates/options/options.js
  43. +56
    -31
      src/client/ui/templates/options/styles.less
  44. +35
    -7
      src/client/ui/templates/options/template.html
  45. +7
    -5
      src/client/ui/templates/passives/input.js
  46. +11
    -13
      src/client/ui/templates/passives/passives.js
  47. +20
    -2
      src/client/ui/templates/quests/quests.js
  48. +23
    -7
      src/client/ui/templates/quests/styles.less
  49. +5
    -7
      src/client/ui/templates/reputation/reputation.js
  50. +30
    -12
      src/client/ui/templates/reputation/styles.less
  51. +1
    -1
      src/client/ui/templates/reputation/template.html
  52. +59
    -50
      src/client/ui/templates/smithing/styles.less
  53. +14
    -16
      src/client/ui/templates/spells/styles.less
  54. +33
    -18
      src/client/ui/templates/workbench/styles.less
  55. +18
    -8
      src/server/components/auth/checkLoginRewards.js
  56. +4
    -0
      src/server/components/equipment.js
  57. +15
    -3
      src/server/components/extensions/socialCommands.js
  58. +4
    -5
      src/server/components/gatherer.js
  59. +11
    -8
      src/server/components/inventory.js
  60. +1
    -0
      src/server/components/passives.js
  61. +3
    -1
      src/server/components/quests.js
  62. +13
    -0
      src/server/components/social/canChat.js
  63. +14
    -15
      src/server/components/social/chat.js
  64. +18
    -0
      src/server/components/social/rezone.js
  65. +21
    -19
      src/server/components/spellbook.js
  66. +4
    -3
      src/server/components/stats.js
  67. +7
    -1
      src/server/components/trade.js
  68. +4
    -0
      src/server/config/effects/effectStunned.js
  69. +28
    -0
      src/server/config/eventPhases/phaseDespawnMob.js
  70. +9
    -0
      src/server/config/eventPhases/phaseEnd.js
  71. +26
    -0
      src/server/config/eventPhases/phaseGiveRewards.js
  72. +27
    -0
      src/server/config/eventPhases/phaseGoto.js
  73. +15
    -0
      src/server/config/eventPhases/phaseKillAllMobs.js
  74. +28
    -0
      src/server/config/eventPhases/phaseMoveMob.js
  75. +7
    -0
      src/server/config/eventPhases/phaseSetDescription.js
  76. +90
    -69
      src/server/config/eventPhases/phaseSpawnMob.js
  77. +8
    -1
      src/server/config/eventPhases/phaseWait.js
  78. +0
    -72
      src/server/config/loginRewards.js
  79. +46
    -4
      src/server/config/maps/cave/map.json
  80. +291
    -0
      src/server/config/maps/sewer/events/plagueOfRats.js
  81. +33
    -9
      src/server/config/maps/sewer/map.json
  82. +2
    -0
      src/server/config/maps/sewer/zone.js
  83. +0
    -36
      src/server/config/prophecies/titangrip.js
  84. +5
    -0
      src/server/config/quests/questBuilder.js
  85. +7
    -6
      src/server/config/quests/templates/questKillX.js
  86. +2
    -2
      src/server/config/recipes/etching.js
  87. +2
    -2
      src/server/config/serverConfig.js
  88. +15
    -10
      src/server/config/spells/spellAmbush.js
  89. +2
    -1
      src/server/config/spells/spellAura.js
  90. +65
    -52
      src/server/config/spells/spellFireblast.js
  91. +22
    -22
      src/server/config/spellsConfig.js
  92. +94
    -14
      src/server/events/events.js
  93. +1
    -0
      src/server/items/enchanter.js
  94. +4
    -3
      src/server/items/generators/stats.js
  95. +3
    -8
      src/server/misc/random.js
  96. +76
    -0
      src/server/misc/rewardGenerator.js
  97. +8
    -0
      src/server/objects/objBase.js
  98. +1
    -1
      src/server/security/rest.js
  99. +0
    -1
      src/server/server.js
  100. +1
    -1
      src/server/world/instancer.js

+ 2
- 2
README.md Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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


+ 6
- 16
src/client/css/colors.less Целия файл

@@ -1,59 +1,49 @@
@white: rgb(242, 245, 245);
@black: #2d2136;

@brown: rgb(107, 79, 76);

@darkGray: rgb(55, 48, 65);
@gray: rgb(60, 63, 76);
@lightGray: rgb(146, 147, 152);

@yellow: #ffeb38;
@purple: #a24eff;
@orange: #ff6942;

@red: #d43346;
@blue: #3fa7dd;
@green: #80f643;
@greenA: #80f643;
@greenB: #4ac441;
@greenC: #386646;

@blackA: #505360;
@blackB: #3c3f4c;
@blackC: #373041;
@blackD: #312136;

@brownC: #b15a30;
@brownD: #763b3b;

@redA: #ff4252;
@redB: #d43346;
@redC: #a82841;
@redD: #802343;
@orangeA: #ff6942;
@orangeB: #db5538;
@orangeC: #b34b3a;
@orangeD: #953f36;

@yellowB: #faac45;
@yellowC: #d07840;

@blueA: #48edff;
@blueB: #3fa7dd;
@blueC: #3a71ba;
@blueD: #42548d;

@tealB: #51fc9a;
@tealC: #44cb95;

@purpleA: #a24eff;
@purpleB: #7a3ad3;
@purpleC: #533399;
@purpleD: #393268;

@pinkA: #fc66f7;
@pinkB: #de43ae;

@grayB: #c0c3cf;
@grayC: #929398;
@grayD: #69696e;
@qualityCommon: @white;
@qualityMagic: @greenB;
@qualityRare: @blueB;
@qualityEpic: @purpleA;
@qualityLegendary: @orangeA;

+ 12
- 10
src/client/css/main.less Целия файл

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

&.mobile {
@@ -38,10 +38,12 @@ body {

}

.mobile .right {
top: 0px;
right: 0px;
.mobile.ui-container > .right {
top: 10px;
right: 158px;
z-index: 2;
display: flex;
flex-direction: row-reverse;
}

* {
@@ -122,7 +124,7 @@ body {
.btnClose {
position: absolute;
background-color: @blackA;
color: @orange;
color: @orangeA;
cursor: pointer;
height: 31px;
width: 31px;
@@ -159,23 +161,23 @@ body {
}

.q0 {
color: @white;
color: @qualityCommon;
}

.q1 {
color: @greenB;
color: @qualityMagic;
}

.q2 {
color: @blueB;
color: @qualityRare;
}

.q3 {
color: @purpleA;
color: @qualityEpic;
}

.q4 {
color: @orange;
color: @qualityLegendary;
}

.color-red {


+ 57
- 2
src/client/js/config.js Целия файл

@@ -3,7 +3,62 @@ define([
], function (

) {
return {
showNames: false
const config = {
showNames: true,
showQuests: 'on',
showEvents: true,
playAudio: true,
qualityIndicators: 'off',
unusableIndicators: 'off'
};

const valueChains = {
showQuests: ['on', 'minimal', 'off'],
qualityIndicators: ['border', 'bottom', 'background', 'off'],
unusableIndicators: ['off', 'border', 'top', 'background']
};

const getNextValue = key => {
const currentValue = config[key];
const chain = valueChains[key];
const currentIndex = chain.indexOf(currentValue);

const nextValue = chain[(currentIndex + 1) % chain.length];

return nextValue;
};

const getKeyName = key => {
return `iwd_opt_${key.toLowerCase()}`;
};

config.set = (key, value) => {
config[key] = value;

window.localStorage.setItem(getKeyName(key), config[key]);
};

config.toggle = key => {
if (valueChains[key])
config[key] = getNextValue(key);
else
config[key] = !config[key];

window.localStorage.setItem(getKeyName(key), config[key]);
};

const loadValue = key => {
const keyName = getKeyName(key);
const { [keyName]: currentValue = '{unset}' } = localStorage;

if (currentValue === '{unset}')
return;

const morphedValue = valueChains[key] ? currentValue : (currentValue === 'true');
config[key] = morphedValue;
};

Object.keys(config).forEach(key => loadValue(key) );

return config;
});

+ 5
- 1
src/client/js/main.js Целия файл

@@ -8,6 +8,7 @@ define([
'js/input',
'js/system/events',
'js/resources',
'js/sound/sound',
'ui/templates/online/online',
'ui/templates/tooltips/tooltips'
], function (
@@ -19,7 +20,8 @@ define([
numbers,
input,
events,
resources
resources,
sound
) {
return {
hasFocus: true,
@@ -51,6 +53,8 @@ define([

$(window).on('contextmenu', this.onContextMenu.bind(this));

sound.init();

objects.init();
renderer.init();
input.init();


+ 16
- 33
src/client/js/objects/objects.js Целия файл

@@ -12,21 +12,14 @@ define([
config
) {
return {
showNames: true,

objects: [],

init: function () {
events.on('onKeyDown', this.onKeyDown.bind(this));
events.on('onGetObject', this.onGetObject.bind(this));
events.on('onRezone', this.onRezone.bind(this));
events.on('onChangeHoverTile', this.getLocation.bind(this));
events.on('onTilesVisible', this.onTilesVisible.bind(this));

//Get saved value for showNames, or use the value set above
let showNames = window.localStorage.getItem('iwd_opt_shownames');
this.showNames = showNames ? (showNames === 'true') : this.showNames;
config.showNames = this.showNames;
events.on('onToggleNameplates', this.onToggleNameplates.bind(this));
},

getLocation: function (x, y) {
@@ -282,7 +275,7 @@ define([
if (template.hidden !== null) {
sprite.visible = !template.hidden;
if (obj.nameSprite)
obj.nameSprite.visible = this.showNames;
obj.nameSprite.visible = config.showNames;
if ((obj.stats) && (obj.stats.hpSprite)) {
obj.stats.hpSprite.visible = !template.hidden;
obj.stats.hpSpriteInner.visible = !template.hidden;
@@ -305,7 +298,7 @@ define([
x: (obj.x * scale) + (scale / 2),
y: (obj.y * scale) + scale
});
obj.nameSprite.visible = this.showNames;
obj.nameSprite.visible = config.showNames;
}

if (obj.sprite) {
@@ -346,29 +339,6 @@ define([
}
},

onKeyDown: function (key) {
if (key === 'v') {
this.showNames = !this.showNames;

//Set new value in localStorage for showNames
window.localStorage.setItem('iwd_opt_shownames', this.showNames);
config.showNames = this.showNames;

let showNames = this.showNames;

let objects = this.objects;
let oLen = objects.length;
for (let i = 0; i < oLen; i++) {
let obj = objects[i];
let ns = obj.nameSprite;
if ((!ns) || (obj.dead) || ((obj.sprite) && (!obj.sprite.visible)))
continue;

ns.visible = showNames;
}
}
},

onTilesVisible: function (tiles, visible) {
let objects = this.objects;
let oLen = objects.length;
@@ -383,6 +353,19 @@ define([

o.setVisible(visible);
}
},

onToggleNameplates: function (show) {
let objects = this.objects;
let oLen = objects.length;
for (let i = 0; i < oLen; i++) {
let obj = objects[i];
let ns = obj.nameSprite;
if ((!ns) || (obj.dead) || ((obj.sprite) && (!obj.sprite.visible)))
continue;

ns.visible = show;
}
}
};
});

+ 0
- 36
src/client/js/rendering/renderer.js Целия файл

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


+ 36
- 6
src/client/js/sound/sound.js Целия файл

@@ -1,15 +1,23 @@
define([
'howler',
'js/misc/physics'
'js/misc/physics',
'js/system/events',
'js/config'
], function (
howler,
physics
physics,
events,
config
) {
return {
sounds: [],

init: function (zone) {
muted: false,

init: function () {
events.on('onToggleAudio', this.onToggleAudio.bind(this));

this.onToggleAudio(config.playAudio);
},

unload: function (zoneId) {
@@ -24,7 +32,7 @@ define([
},

update: function (x, y) {
this.sounds.forEach(function (s) {
this.sounds.forEach(s => {
let volume;

if (!s.w) {
@@ -67,9 +75,13 @@ define([
loop: true,
volume: 0
});

if (this.muted)
s.sound.mute(true);
}

s.sound.volume(volume * s.volume);
if (!this.muted)
s.sound.volume(volume * s.volume);
});
},

@@ -96,6 +108,24 @@ define([
};

this.sounds.push(sound);
},

onToggleAudio: function (isAudioOn) {
this.muted = !isAudioOn;

this.sounds.forEach(s => {
if (!s.sound)
return;

s.sound.mute(this.muted);
});

if (!window.player)
return;
const { player: { x, y } } = window;

this.update(x, y);
}
};
});

+ 14
- 3
src/client/ui/factory.js Целия файл

@@ -30,7 +30,7 @@ define([
'spells',
'messages',
'online',
'options',
'mainMenu',
'context',
'party',
'help',
@@ -55,7 +55,8 @@ define([
'wardrobe',
'passives',
'workbench',
'middleHud'
'middleHud',
'options'
].forEach(function (u) {
this.build(u);
}, this);
@@ -100,15 +101,25 @@ define([

onUiKeyDown: function (keyEvent) {
if (keyEvent.key === 'esc') {
this.uis.forEach(function (u) {
const closedUis = [];

this.uis.forEach(u => {
if (!u.modal || !u.shown)
return;

keyEvent.consumed = true;
u.hide();

closedUis.push(u);
});
$('.uiOverlay').hide();
events.emit('onHideContextMenu');

closedUis.forEach(c => {
if (c.afterHide)
c.afterHide();
});
} else if (['o', 'j', 'h', 'i'].indexOf(keyEvent.key) > -1)
$('.uiOverlay').hide();
},


+ 11
- 2
src/client/ui/templates/characters/characters.js Целия файл

@@ -28,6 +28,15 @@ define([
.on('mouseleave', this.onDeleteReset.bind(this));

this.getCharacters();

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

onKeyDown: function (key) {
if (key !== 'enter' || this.el.hasClass('disabled'))
return;

this.onPlayClick();
},

onPlayClick: function () {
@@ -48,13 +57,13 @@ define([
},
onPlay: function () {
this.el.removeClass('disabled');
this.el.remove();
this.destroy();
events.emit('onEnterGame');
},

onNewClick: function () {
uiFactory.build('createCharacter', {});
this.el.remove();
this.destroy();
},

getCharacters: function () {


+ 18
- 15
src/client/ui/templates/characters/styles.less Целия файл

@@ -1,16 +1,11 @@
@import "../../../css/colors.less";

@leftWidth: 400px;
@rightWidth: 400px;
@boxPadding: 35px;

@logoWidth: 559px;
@logoHeight: 200px;

@boxHeight: 300px;

@messageHeight: @boxPadding;

@totalWidth: (@leftWidth + @rightWidth + @boxPadding);
@totalHeight: (@logoHeight + @boxHeight + (@boxPadding * 3) + @messageHeight);

@@ -25,7 +20,8 @@
margin-bottom: (@boxPadding * 2);
}

.left, .right {
.left,
.right {
height: @boxHeight;
float: left;
background-color: #3a3b4a;
@@ -50,7 +46,9 @@
&.selected {
background-color: fade(@white, 25%);
}

}

}

.right {
@@ -74,6 +72,7 @@
image-rendering: crisp-edges;
display: none;
}

}

.info {
@@ -100,10 +99,11 @@
text-align: right;
color: darken(@white, 25%);
}

}

.btn {
background-color: @redC;
background-color: @blueC;
width: calc((100% - (16px * 2)) / 3);
float: left;
margin-right: 16px;
@@ -114,18 +114,20 @@
}

&:hover {
background-color: @redB;
background-color: @blueB;
}

&.deleting {
background-color: @yellow;
color: @black;
background-color: @red;
color: @white;
}

}

.spacer-h {
height: 91px;
}

}

.message {
@@ -135,12 +137,12 @@
float: left;
text-align: center;
color: @white;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

}

.mobile .uiCharacters {
@@ -149,4 +151,5 @@
.logo {
display: none;
}

}

+ 1
- 1
src/client/ui/templates/characters/template.html Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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

+ 2
- 2
src/client/ui/templates/createCharacter/createCharacter.js Целия файл

@@ -111,7 +111,7 @@ define([
back: function () {
this.clear();

this.el.remove();
this.destroy();

uiFactory.build('characters', {});
},
@@ -137,7 +137,7 @@ define([

if (!result) {
this.clear();
this.el.remove();
this.destroy();
events.emit('onEnterGame');
} else
this.el.find('.message').html(result);


+ 25
- 13
src/client/ui/templates/createCharacter/styles.less Целия файл

@@ -1,13 +1,9 @@
@import "../../../css/colors.less";

@boxHeight: 276px;
@boxPadding: 27px;

@logoWidth: 559px;
@logoHeight: 200px;

@messageHeight: @boxPadding;

@totalWidth: (@logoWidth + (@boxPadding * 2) + 427px);
@totalHeight: (@logoHeight + @boxHeight + (@boxPadding * 3) + @messageHeight);

@@ -28,7 +24,8 @@
width: 613px;
background-color: #3a3b4a;

.left, .right {
.left,
.right {
float: left;
height: 100%;
}
@@ -37,17 +34,18 @@
width: calc(100% - (@boxPadding * 2) - 160px);
padding: @boxPadding 0px @boxPadding @boxPadding;

.txtClass, .txtCostume {
.txtClass,
.txtCostume {
cursor: pointer;
-webkit-user-select: none;

&:active {
background-color: darken(@gray, 20%);
}

}

.txtClass {

}

.label {
@@ -73,16 +71,17 @@
background-color: @grayD;

&:first-child {
margin-right: 15px;
margin-right: 10px;
}

&:last-child {
margin-left: 15px;
margin-left: 10px;
}

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

}

.txtCostume {
@@ -92,7 +91,9 @@
align-items: center;
padding: 0px;
}
}

}

}

.right {
@@ -117,6 +118,7 @@
image-rendering: crisp-edges;
background: url('../../../images/charas.png') -64px 0px;
}

}

.textbox {
@@ -128,7 +130,7 @@
}

.btn {
background-color: @redC;
background-color: @blueC;
width: calc((100% - @boxPadding) / 2);
float: left;
margin-right: @boxPadding;
@@ -139,8 +141,9 @@
}

&:hover {
background-color: @redB;
background-color: @blueB;
}

}

.message {
@@ -151,6 +154,7 @@
text-align: center;
color: @redA;
}

}

.box-right {
@@ -195,6 +199,7 @@
&:after {
content: ' ✔';
}

}

&.disabled {
@@ -204,10 +209,15 @@
&:hover {
color: @white;
}

}

}

}

}

}

.mobile .uiCreateCharacter {
@@ -226,11 +236,13 @@
width: 45%;

.skinBox {
height: 70px;
height: 35px;
}

}

.box-right {
width: 45%;
}

}

+ 4
- 8
src/client/ui/templates/createCharacter/template.html Целия файл

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


+ 16
- 11
src/client/ui/templates/death/styles.less Целия файл

@@ -2,7 +2,6 @@

.uiDeath {
display: none;

width: 400px;
background-color: @gray;
border: 4px solid @lightGray;
@@ -14,8 +13,9 @@

.inner {
display: inline-block;
color: @orange;
color: @orangeA;
}

}

.penalty {
@@ -25,17 +25,18 @@

.btn {
color: @white;
width: 100%;
height: 32px;
background-color: @blue;
margin: 16px 0px;
padding-top: 8px;
cursor: pointer;
&:hover {
width: 100%;
height: 32px;
background-color: @blue;
margin: 16px 0px;
padding-top: 8px;
cursor: pointer;
&:hover {
background-color: lighten(@blue, 15%);
color: @black;
}
}

}

&.permadeath {
@@ -47,12 +48,16 @@
.btn-logout {
display: block;
}

}

}

.buttons {
.btn-logout {
display: none;
}

}

}

+ 1
- 0
src/client/ui/templates/equipment/styles.less Целия файл

@@ -279,6 +279,7 @@
background-color: @blackC;
padding: 0px;
z-index: 2;
overflow-y: auto;

.slot {
float: left;


+ 16
- 2
src/client/ui/templates/events/events.js Целия файл

@@ -3,13 +3,15 @@ define([
'js/system/events',
'html!ui/templates/events/template',
'html!ui/templates/events/templateEvent',
'css!ui/templates/events/styles'
'css!ui/templates/events/styles',
'js/config'
], function (
client,
events,
tpl,
templateEvent,
styles
styles,
config
) {
return {
tpl: tpl,
@@ -30,6 +32,9 @@ define([
this.onEvent('onRemoveEvent', this.onRemoveEvent.bind(this));
this.onEvent('onUpdateEvent', this.onUpdateEvent.bind(this));
this.onEvent('onCompleteEvent', this.onCompleteEvent.bind(this));

this.onEvent('onToggleEventsVisibility', this.onToggleEventsVisibility.bind(this));
this.onToggleEventsVisibility(config.showEvents);
},

onRezone: function () {
@@ -112,6 +117,15 @@ define([
toggleButtons: function (e) {
this.el.toggleClass('active');
e.stopPropagation();
},

onToggleEventsVisibility: function (active) {
this.shown = active;

if (this.shown)
this.show();
else
this.hide();
}
};
});

+ 15
- 9
src/client/ui/templates/events/styles.less Целия файл

@@ -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,22 +45,24 @@
.description {
display: none;
}

}

}

}

.btnClose {
.btnCollapse {
display: none;
}

}

.mobile .uiEvents {
background-color: fade(@darkGray, 90%);
margin-top: 0px;

&:not(.active) {
position: absolute;
top: 0px;
right: 232px;
width: @btnSize;
height: @btnSize;

@@ -78,6 +80,7 @@
> * {
display: none;
}

}

&.active {
@@ -90,17 +93,20 @@
display: block;
position: absolute;
right: calc(100% + 10px);
top: 50%;
transform: translateY(-50%);
top: 0px;
background-color: fade(@darkGray, 90%);
width: @btnSize;
height: @btnSize;
padding-top: 0px;

.icon {
width: 100%;
height: 100%;
background: url('../../../images/uiIcons.png') -448px 0px;
}

}

}

}

+ 15
- 5
src/client/ui/templates/hud/hud.js Целия файл

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


+ 54
- 22
src/client/ui/templates/inventory/inventory.js Целия файл

@@ -4,14 +4,16 @@ define([
'html!ui/templates/inventory/template',
'css!ui/templates/inventory/styles',
'html!ui/templates/inventory/templateItem',
'js/input'
'js/input',
'js/config'
], function (
events,
client,
template,
styles,
tplItem,
input
input,
config
) {
return {
tpl: template,
@@ -33,6 +35,11 @@ define([
this.onEvent('onGetItems', this.onGetItems.bind(this));
this.onEvent('onDestroyItems', this.onDestroyItems.bind(this));
this.onEvent('onShowInventory', this.toggle.bind(this));
this.onEvent('onToggleQualityIndicators', this.onToggleQualityIndicators.bind(this));
this.onToggleQualityIndicators(config.qualityIndicators);

this.onEvent('onToggleUnusableIndicators', this.onToggleUnusableIndicators.bind(this));
this.onToggleUnusableIndicators(config.unusableIndicators);

this.onEvent('onKeyDown', this.onKeyDown.bind(this));
this.onEvent('onKeyUp', this.onKeyUp.bind(this));
@@ -78,6 +85,7 @@ define([
.on('mouseup', this.onMouseDown.bind(this, null, null, false))
.on('mousemove', this.onHover.bind(this, itemEl, item))
.on('mouseleave', this.hideTooltip.bind(this, itemEl, item))
.addClass('empty')
.children()
.remove();

@@ -135,9 +143,32 @@ define([
itemEl.addClass('new');
itemEl.find('.quantity').html('NEW');
}

if (item.slot) {
const equipErrors = window.player.inventory.equipItemErrors(item);
if (equipErrors.length)
itemEl.addClass('no-equip');
}

if (item.has('quality'))
itemEl.addClass(`quality-${item.quality}`);
}
},

onToggleQualityIndicators: function (state) {
this.el.removeClass('quality-off quality-bottom quality-border quality-background');

const className = `quality-${state.toLowerCase()}`;
this.el.addClass(className);
},

onToggleUnusableIndicators: function (state) {
this.el.removeClass('unusable-off unusable-border unusable-top unusable-background');

const className = `unusable-${state.toLowerCase()}`;
this.el.addClass(className);
},

onClick: function (item) {
let msg = {
item: item,
@@ -274,7 +305,8 @@ define([
},
salvage: {
text: 'salvage',
callback: this.performItemAction.bind(this, item, 'salvageItem')
callback: this.performItemAction.bind(this, item, 'salvageItem'),
hotkey: 'f'
},
stash: {
text: 'stash',
@@ -323,56 +355,56 @@ define([
if (item.active)
menuItems.activate.text = 'deactivate';

let config = [];
let ctxConfig = [];

if (item.ability)
config.push(menuItems.learn);
ctxConfig.push(menuItems.learn);
else if (item.type === 'mtx')
config.push(menuItems.activate);
ctxConfig.push(menuItems.activate);
else if (item.type === 'toy' || item.type === 'consumable' || item.useText || item.type === 'recipe') {
if (item.useText)
menuItems.use.text = item.useText;
config.push(menuItems.use);
ctxConfig.push(menuItems.use);
if (!item.has('quickSlot'))
config.push(menuItems.quickSlot);
ctxConfig.push(menuItems.quickSlot);
} else if (item.slot) {
config.push(menuItems.equip);
ctxConfig.push(menuItems.equip);
if (!item.eq)
config.push(menuItems.divider);
ctxConfig.push(menuItems.divider);

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

if ((!item.eq) && (!item.active)) {
if (!item.quest) {
if ((window.player.stash.active) && (!item.noStash))
config.push(menuItems.stash);
ctxConfig.push(menuItems.stash);

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

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

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

if (item.quantity > 1)
config.push(menuItems.split);
if (item.quantity > 1 && !item.quest)
ctxConfig.push(menuItems.split);

//if ((!item.noDrop) && (!item.quest))
// config.push(menuItems.mail);
if ((!item.noDrop) && (!item.quest))
ctxConfig.push(menuItems.mail);

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

if (config.length > 0)
events.emit('onContextMenu', config, e);
if (ctxConfig.length > 0)
events.emit('onContextMenu', ctxConfig, e);

e.preventDefault();
return false;


+ 173
- 34
src/client/ui/templates/inventory/styles.less Целия файл

@@ -1,6 +1,7 @@
@import "../../../css/colors.less";

.uiInventory, .uiStash {
.uiInventory,
.uiStash {
display: none;
z-index: 2;
width: 818px;
@@ -18,8 +19,9 @@
padding-top: 8px;
margin: auto;
}

}
position: relative;

.grid {
@@ -52,20 +54,18 @@
background-color: transparent;

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

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

}

.quantity {
@@ -73,18 +73,14 @@
bottom: 3px;
position: absolute;
color: @white;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

-moz-filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

.icon {
@@ -99,30 +95,162 @@
.quantity {
color: @yellow;
}

}

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

}

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

-moz-filter: brightness(160%);
}

}

}

}

&.quality-border .grid .item:not(.empty),
&.quality-bottom .grid .item:not(.empty) {
&:before {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
box-sizing: border-box;
border-width: 5px;
border-style: solid;
}

&.quality-0:before {
border: none;
}

&.quality-1:before {
border-color: @greenC;
}

&.quality-2:before {
border-color: @blueD;
}

&.quality-3:before {
border-color: @purpleC;
}

&.quality-4:before {
border-color: @orangeD;
}

}

&.quality-bottom .grid .item:not(.empty) {
&:before {
border-width: 0px 0px 5px 0px;
}

}

&.quality-background .grid .item:not(.empty),&.unusable-background .grid .item.no-equip {
&:before {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}

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

&:hover {
filter: brightness(160%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
}

}

&.quality-1:before {
background-color: @greenC;
}

&.quality-2:before {
background-color: @blueD;
}

&.quality-3:before {
background-color: @purpleC;
}

&.quality-4:before {
background-color: @orangeD;
}

&.no-equip:before {
background-color: @redD;
}

}

&.unusable-border .grid .item.no-equip,
&.unusable-top .grid .item.no-equip {
&:after {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
box-sizing: border-box;
border-width: 5px;
border-style: solid;
border-color: @redD;
pointer-events: none;
}

}

&.unusable-top .grid .item.no-equip {
&:after {
border-width: 5px 0px 0px 0px;
}

}

&.quality-border.unusable-border .grid .item.no-equip:not(.quality-0),
&.quality-border.unusable-top .grid .item.no-equip:not(.quality-0) {
&:after {
top: 5px;
left: 5px;
width: calc(100% - 10px);
height: calc(100% - 10px);
}

}

.menu-bar {
background-color: @blackB;
width: 100%;
height: 41px;
padding: 5px 0px;
clear: both;
width: 100%;
height: 41px;
padding: 5px 0px;
clear: both;

.btn {
background-color: @blueC;
@@ -136,7 +264,9 @@
&.btnSortInv {
float: left;
}

}

}

.split-box {
@@ -168,6 +298,7 @@
padding-top: 8px;
margin: auto;
}

}

.bottom {
@@ -185,7 +316,9 @@
border: none;
}

input, .textbox, input:-webkit-autofill {
input,
.textbox,
input:-webkit-autofill {
color: @white;
-webkit-text-fill-color: @white;
background-color: @blackC;
@@ -210,14 +343,20 @@
&:hover {
background-color: @blackA;
}

}

&.btnSplit {
margin-top: 45px;
clear: both;
}

}

}
}

}

}

}

+ 1
- 1
src/client/ui/templates/login/login.js Целия файл

@@ -73,7 +73,7 @@ define([
uiFactory.preload();

$('.uiLoginExtra').remove();
this.el.remove();
this.destroy();
} else
this.el.find('.message').html(res);
},


+ 78
- 61
src/client/ui/templates/login/styles.less Целия файл

@@ -1,40 +1,45 @@
@import "../../../css/colors.less";

@leftWidth: 400px;
@rightWidth: 400px;
@boxPadding: 16px;

@logoWidth: 559px;
@logoHeight: 200px;

@boxHeight: 169px;

@messageHeight: @boxPadding;

@totalWidth: @rightWidth;
@totalHeight: (@logoHeight + @boxHeight + (@boxPadding * 3) + @messageHeight);

.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 {
height: @boxHeight;
background-color: #3a3b4a;

width: @rightWidth;
padding: @boxPadding;

.label, input {
.label,
input {
float: left;
}

@@ -48,16 +53,19 @@
width: 70%;
}

input, .textbox, input:-webkit-autofill {
box-shadow: 0 0 0px 1000px darken(@gray, 15%) inset;
input,
.textbox,
input:-webkit-autofill {
box-shadow: 0 0 0px 1000px darken(@gray, 15%) inset;
color: @white;
-webkit-text-fill-color: @white;
margin-bottom: @boxPadding;
}

/* We duplicate this for firefox which doesn't like the webkit selector */
input, .textbox {
box-shadow: 0 0 0px 1000px darken(@gray, 15%) inset;
input,
.textbox {
box-shadow: 0 0 0px 1000px darken(@gray, 15%) inset;
color: @white;
-webkit-text-fill-color: @white;
margin-bottom: @boxPadding;
@@ -70,21 +78,17 @@
margin-top: 36px;
float: left;
text-align: center;
color: @orange;

filter:
brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);

-moz-filter:
brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
color: @orangeA;
filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
-moz-filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
}

.top-buttons {
@@ -92,7 +96,7 @@
height: 35px;

.btn {
background-color: @redC;
background-color: @blueC;
width: calc((100% - @boxPadding) / 2);
float: left;
margin-right: @boxPadding;
@@ -103,54 +107,58 @@
}

&:hover {
background-color: @redB;
background-color: @blueB;
}

}

}

}

.news {
margin-left: -100px;
margin-top: 40px;
color: @white;
width: 600px;
text-align: center;
cursor: pointer;

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

.spacer-h {
height: 61px;
}

}

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

}

}

.version {
@@ -159,23 +167,32 @@
bottom: 10px;
color: @yellow;
cursor: pointer;
filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
-moz-filter: brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);
}

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

.mobile {
.uiLogin {
margin-top: 0px;

.logo {
margin-bottom: 30px;
}

.news {
margin-top: 20px;
}

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

.mobile .uiLogin {
margin-top: 0px;
}

+ 3
- 3
src/client/ui/templates/login/template.html Целия файл

@@ -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.3">[ 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.3">v0.4.3</div>
</div>

+ 112
- 0
src/client/ui/templates/mainMenu/mainMenu.js Целия файл

@@ -0,0 +1,112 @@
define([
'js/system/events',
'html!ui/templates/mainMenu/template',
'css!ui/templates/mainMenu/styles',
'js/rendering/renderer',
'ui/factory',
'js/objects/objects',
'js/system/client',
'js/sound/sound'
], function (
events,
template,
styles,
renderer,
factory,
objects,
client,
sound
) {
return {
tpl: template,
centered: true,

modal: true,

postRender: function () {
this.onEvent('onToggleMainMenu', this.toggle.bind(this));

this.el.find('.btnOptions').on('click', this.handler.bind(this, 'onToggleOptions'));
this.el.find('.btnCharSelect').on('click', this.charSelect.bind(this));
this.el.find('.btnLogOut').on('click', this.logOut.bind(this));
this.el.find('.btnContinue').on('click', this.toggle.bind(this));
this.el.find('.btnPatreon').on('click', this.patreon.bind(this));
this.el.find('.btnIssue').on('click', this.reportIssue.bind(this));

this.onEvent('onResize', this.onResize.bind(this));
},

handler: function (e) {
if (isMobile)
this.el.removeClass('active');

events.emit(e);

return false;
},
reportIssue: function () {
window.open('https://gitlab.com/Isleward/isleward/issues/new', '_blank');
},

patreon: function () {
window.open('https://patreon.com/bigbadwaffle', '_blank');
},

charSelect: function () {
this.el.addClass('disabled');

client.request({
module: 'cons',
method: 'unzone',
callback: this.onCharSelect.bind(this)
});
},

onCharSelect: function () {
renderer.clean();
objects.onRezone();
renderer.buildTitleScreen();
sound.unload();

events.emit('onShowCharacterSelect');
$('[class^="ui"]:not(.ui-container)').toArray().forEach(el => {
let ui = $(el).data('ui');
if (ui && ui.destroy)
ui.destroy();
});
factory.build('characters', {});
},

onResize: function () {
let isFullscreen = (window.innerHeight === screen.height);
if (isFullscreen)
this.el.find('.btnScreen').html('Windowed');
else
this.el.find('.btnScreen').html('Fullscreen');
},

toggle: function () {
this.onResize();

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

if (this.shown) {
this.show();
events.emit('onShowOverlay', this.el);
} else {
this.hide();
events.emit('onHideOverlay', this.el);
}
},

logOut: function () {
window.location = window.location;
},

onKeyDown: function (key) {
if (key === 'esc')
this.toggle();
}
};
});

+ 49
- 0
src/client/ui/templates/mainMenu/styles.less Целия файл

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

.uiMainMenu {
display: none;

width: 400px;
background-color: @gray;
border: 4px solid @lightGray;
text-align: center;
padding: 0px 16px;

.btn {
color: @white;
width: 100%;
height: 32px;
background-color: @blue;
margin: 16px 0px;
padding-top: 8px;
cursor: pointer;

&.btnNames {
display: none;
}

&:hover {
background-color: lighten(@blue, 15%);
color: @black;
}
}

.btnIssue {
background-color: @red;

&:hover {
background-color: lighten(@red, 15%);
color: @black;
}
}
}

.mobile .uiMainMenu {
.btnScreen {
display: none;
}

.btnNames {
display: block;
}
}

+ 8
- 0
src/client/ui/templates/mainMenu/template.html Целия файл

@@ -0,0 +1,8 @@
<div class="uiMainMenu">
<div class="btn btnContinue">Continue</div>
<div class="btn btnOptions">Options</div>
<div class="btn btnCharSelect">Character Select</div>
<div class="btn btnLogOut">Log Out</div>
<div class="btn btnPatreon">Pledge on Patreon</div>
<div class="btn btnIssue">Report an Issue</div>
</div>

+ 1
- 1
src/client/ui/templates/menu/menu.js Целия файл

@@ -22,7 +22,7 @@ define([
this.find('.btnOnline').on('click', this.handler.bind(this, 'onShowOnline'));
this.find('.btnLeaderboard').on('click', this.handler.bind(this, 'onShowLeaderboard'));
this.find('.btnReputation').on('click', this.handler.bind(this, 'onShowReputation'));
this.find('.btnOptions').on('click', this.handler.bind(this, 'onToggleOptions'));
this.find('.btnMainMenu').on('click', this.handler.bind(this, 'onToggleMainMenu'));
this.find('.btnPassives').on('click', this.handler.bind(this, 'onShowPassives'));

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


+ 1
- 1
src/client/ui/templates/menu/styles.less Целия файл

@@ -95,7 +95,7 @@
background: url('../../../images/uiIcons.png') -320px 0px;
}

&.btnOptions .icon {
&.btnMainMenu .icon {
background: url('../../../images/uiIcons.png') 0px -64px;
}
}


+ 1
- 1
src/client/ui/templates/menu/template.html Целия файл

@@ -24,7 +24,7 @@
<div class="btnHelp">
<div class="icon"></div>
</div>
<div class="btnOptions">
<div class="btnMainMenu">
<div class="icon"></div>
</div>
<div class="btn btnCollapse">


+ 65
- 30
src/client/ui/templates/messages/messages.js Целия файл

@@ -93,37 +93,57 @@ define([
let container = $('<div class="keyboard"></div>')
.appendTo(this.el);

let controls = ['|', 'caps', 'space', 'backspace', 'enter'];

let keyboard = {
0: '1234567890|qwertyuiop|asdfghjkl|zxcvbnm',
1: '!@#$%^&*()|QWERTYUIOP|ASDFGHJKL|ZXCVBNM',
2: '!@#$%^&*()| {}[]\\|`-=_+;\':"|~,./<>?'
}[this.kbUpper].split('').concat(controls);

keyboard
.forEach(k => {
if (k === '|') {
$('<div class="newline"></div>')
.appendTo(container);

return;
}
0: 'qwertyuiop|asdfghjkl|zxcvbnm',
1: 'QWERTYUIOP|ASDFGHJKL|ZXCVBNM',
2: '1234567890|@#&*-+=()|_$"\';/'
}[this.kbUpper].split('');

//Hacky: Insert control characters in correct positions
//Backspace goes after 'm'
if (this.kbUpper === 0) {
keyboard.splice(keyboard.indexOf('z'), 0, 'caps');
keyboard.splice(keyboard.indexOf('m') + 1, 0, '<<');
} else if (this.kbUpper === 1) {
keyboard.splice(keyboard.indexOf('Z'), 0, 'caps');
keyboard.splice(keyboard.indexOf('M') + 1, 0, '<<');
} else if (this.kbUpper === 2)
keyboard.splice(keyboard.indexOf('/') + 1, 0, '<<');

keyboard.push(...['|', '123', ',', 'space', '.', 'send']);

let row = 0;
keyboard.forEach(k => {
if (k === '|') {
row++;

const postGapCount = row === 4 ? 0 : row - 1;
for (let i = 0; i < postGapCount; i++)
$('<div class="gap" />').appendTo(container);
$('<div class="newline" />').appendTo(container);
const preGapCount = row === 3 ? 0 : row;
for (let i = 0; i < preGapCount; i++)
$('<div class="gap" />').appendTo(container);

return;
}

let className = (k.match(/[a-z]/i) || k.length > 1) ? 'key' : 'key special';
if (k === ' ') {
k = '.';
className = 'key hidden';
}
let className = (k.length === 1) ? 'key' : 'key special';
if (k === ' ') {
k = '.';
className = 'key hidden';
}

className += ' ' + k;
className += ' ' + k;

let elKey = $(`<div class="${className}">${k}</div>`)
.appendTo(container);
let elKey = $(`<div class="${className}">${k}</div>`)
.appendTo(container);

if (!className.includes('hidden'))
elKey.on('click', this.clickKey.bind(this, k));
});
if (!className.includes('hidden'))
elKey.on('click', this.clickKey.bind(this, k));
});
},

clickKey: function (key) {
@@ -133,7 +153,12 @@ define([

const handler = {
caps: () => {
this.kbUpper = (this.kbUpper + 1) % 3;
this.kbUpper = (this.kbUpper + 1) % 2;
this.renderKeyboard();
},

123: () => {
this.kbUpper = (this.kbUpper === 2) ? 0 : 2;
this.renderKeyboard();
},

@@ -141,12 +166,12 @@ define([
this.clickKey(' ');
},

backspace: () => {
'<<': () => {
elInput.val(elInput.val().slice(0, -1));
this.find('.input').html(elInput.val());
},

enter: () => {
send: () => {
this.sendChat({
which: 13
});
@@ -372,13 +397,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;


+ 66
- 41
src/client/ui/templates/messages/styles.less Целия файл

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

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

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

width: 480px;
padding: @pad;

pointer-events: none;

.input {
@@ -28,6 +25,7 @@
&.time {
display: none;
}

}

.filters {
@@ -42,13 +40,16 @@
.list-message {
filter: none;
}

}

}

&.active {
.list-message {
opacity: 1 !important;
}

}

.filters {
@@ -79,8 +80,11 @@
background-color: @blackA;
color: @grayB;
}

}

}

}

.list {
@@ -93,18 +97,14 @@
width: 100%;
padding: 5px 10px;
color: white;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

-moz-filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
word-wrap: break-word;
line-height: 18px;

@@ -120,16 +120,17 @@
color: @greenB;
}

a, &.q2 {
a,
&.q2 {
color: @blueB;
}

&.q3 {
color: @purple;
color: @purpleA;
}

&.q4 {
color: @orange;
color: @orangeA;
}

&.color-green {
@@ -147,12 +148,25 @@
&.color-tealC {
color: @tealC;
}

}

&.rep .rep {
display: block;
}

&.chat .chat {
display: block;
}

&.info .info {
display: block;
}

&.loot .loot {
display: block;
}

&.rep .rep { display: block; }
&.chat .chat { display: block; }
&.info .info { display: block; }
&.loot .loot { display: block; }
}

.el {
@@ -168,27 +182,24 @@
background-color: transparent;
text-align: left;
padding: 5px 10px;

filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

-moz-filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

width: 100%;
color: @white;
}

}

.mobile .uiMessages {
padding: 10px 0px;
padding: 0px;
pointer-events: all;

.btnClose.active {
@@ -204,6 +215,8 @@

.filters {
margin-bottom: 0px;
flex-shrink: 0;
background-color: @darkGray;
}

.list {
@@ -223,12 +236,16 @@

.input {
display: block;
height: 26px;
flex-shrink: 0;
}

.el.textbox:not(.input) {
&.message, &.time {
&.message,
&.time {
display: none;
}

}

.keyboard {
@@ -238,7 +255,8 @@
background-color: @blackC;
justify-content: center;
align-items: center;
height: 292px;
height: 128px;
flex-shrink: 0;

.key {
flex: 1;
@@ -246,12 +264,9 @@
color: @white;
padding: 8px 10px;
text-align: center;
border-top: 2px solid #373041;
border-left: 1px solid #373041;
border-right: 1px solid #373041;

&.special {
color: @orange;
color: @orangeA;
}

&.hidden {
@@ -261,12 +276,20 @@
&.space {
flex: 5;
}

}

.newline {
width: 100%;
}

.gap {
width: 5%;
flex-shrink: 0;
}

}

}

&:not(.typing) {
@@ -292,5 +315,7 @@
> * {
display: none;
}

}

}

+ 4
- 1
src/client/ui/templates/middleHud/styles.less Целия файл

@@ -14,7 +14,7 @@
margin-left: calc(12px - 60px);
text-align: center;
position: absolute;
left: calc(50vw - 86px);
left: calc(50vw - 88px);
top: calc(50vh - 113px);
width: 64px;
height: 64px;
@@ -27,6 +27,7 @@
height: 100%;
background: url(../../../images/uiIcons.png) -448px -64px;
}

}

.casting {
@@ -51,5 +52,7 @@
text-align: center;
color: @black;
}

}

}

+ 7
- 4
src/client/ui/templates/online/styles.less Целия файл

@@ -6,7 +6,7 @@
border: 5px solid @blackB;
text-align: center;
width: 450px;
height: 400px;
height: 396px;

> .heading {
color: @blueA;
@@ -21,16 +21,17 @@
}

.bottom {
height: calc(100% - 35px);
height: calc(100% - 36px);
background-color: @blackC;
padding: 10px;
padding: 8px;

.list {
width: 100%;
height: 100%;
overflow-y: auto;
background-color: @darkGray;

display: flex;
flex-direction: column;

.heading {
height: 24px;
@@ -39,6 +40,7 @@
> div {
float: left;
padding: 4px;
height: 24px;
&:nth-child(1) {
width: 10%;
@@ -63,6 +65,7 @@
> div {
float: left;
padding: 4px;
height: 24px;
&:nth-child(1) {
width: 10%;


+ 118
- 44
src/client/ui/templates/options/options.js Целия файл

@@ -6,7 +6,8 @@ define([
'ui/factory',
'js/objects/objects',
'js/system/client',
'js/sound/sound'
'js/sound/sound',
'js/config'
], function (
events,
template,
@@ -15,72 +16,135 @@ define([
factory,
objects,
client,
sound
sound,
config
) {
return {
tpl: template,
centered: true,

modal: true,
hasClose: true,

postRender: function () {
this.onEvent('onToggleOptions', this.toggle.bind(this));

//Can only toggle fullscreen directly in a listener, not deferred the way jQuery does it
this.el.find('.btnScreen')[0].addEventListener('click', this.toggleScreen.bind(this));
this.el.find('.btnNames').on('click', events.emit.bind(events, 'onKeyDown', 'v'));
this.el.find('.btnCharSelect').on('click', this.charSelect.bind(this));
this.el.find('.btnLogOut').on('click', this.logOut.bind(this));
this.el.find('.btnContinue').on('click', this.toggle.bind(this));
this.el.find('.btnPatreon').on('click', this.patreon.bind(this));
this.el.find('.btnIssue').on('click', this.reportIssue.bind(this));
this.find('.item.screen .name')[0].addEventListener('click', this.toggleScreen.bind(this));
this.find('.item.nameplates .name').on('click', events.emit.bind(events, 'onUiKeyDown', { key: 'v' }));
this.find('.item.quests .name').on('click', this.toggleQuests.bind(this));
this.find('.item.events .name').on('click', this.toggleEvents.bind(this));
this.find('.item.quality .name').on('click', this.toggleQualityIndicators.bind(this));
this.find('.item.unusable .name').on('click', this.toggleUnusableIndicators.bind(this));
this.find('.item.audio .name').on('click', this.toggleAudio.bind(this));

this.onEvent('onResize', this.onResize.bind(this));
this.onEvent('onUiKeyDown', this.onKeyDown.bind(this));
this.onEvent('onToggleAudio', this.onToggleAudio.bind(this));
this.onEvent('onToggleNameplates', this.onToggleNameplates.bind(this));
this.onEvent('onToggleQualityIndicators', this.onToggleQualityIndicators.bind(this));
this.onEvent('onToggleUnusableIndicators', this.onToggleUnusableIndicators.bind(this));
this.onEvent('onToggleEventsVisibility', this.onToggleEventsVisibility.bind(this));
this.onEvent('onToggleQuestsVisibility', this.onToggleQuestsVisibility.bind(this));
},
reportIssue: function () {
window.open('https://gitlab.com/Isleward/isleward/issues/new', '_blank');

toggleUnusableIndicators: function () {
config.toggle('unusableIndicators');

if (config.unusableIndicators === 'background' && config.qualityIndicators === 'background') {
config.toggle('qualityIndicators');
events.emit('onToggleQualityIndicators', config.qualityIndicators);
}

events.emit('onToggleUnusableIndicators', config.unusableIndicators);
},

patreon: function () {
window.open('https://patreon.com/bigbadwaffle', '_blank');
onToggleUnusableIndicators: function (state) {
const newValue = state[0].toUpperCase() + state.substr(1);

this.find('.item.unusable .value').html(newValue);
},

charSelect: function () {
this.el.addClass('disabled');
toggleQualityIndicators: function () {
config.toggle('qualityIndicators');

client.request({
module: 'cons',
method: 'unzone',
callback: this.onCharSelect.bind(this)
});
if (config.qualityIndicators === 'background' && config.unusableIndicators === 'background') {
config.toggle('unusableIndicators');
events.emit('onToggleUnusableIndicators', config.unusableIndicators);
}

events.emit('onToggleQualityIndicators', config.qualityIndicators);
},

onCharSelect: function () {
renderer.clean();
objects.onRezone();
renderer.buildTitleScreen();
sound.unload();
onToggleQualityIndicators: function (state) {
const newValue = state[0].toUpperCase() + state.substr(1);

events.emit('onShowCharacterSelect');
$('[class^="ui"]:not(.ui-container)').toArray().forEach(el => {
let ui = $(el).data('ui');
if (ui && ui.destroy)
ui.destroy();
});
factory.build('characters', {});
this.find('.item.quality .value').html(newValue);
},

toggleScreen: function () {
this.el.find('.btnScreen').html(renderer.toggleScreen());
const state = renderer.toggleScreen();
const newValue = (state === 'Windowed') ? 'Off' : 'On';

this.find('.item.screen .value').html(newValue);
},

toggleEvents: function () {
config.toggle('showEvents');

events.emit('onToggleEventsVisibility', config.showEvents);
},

toggleQuests: function () {
config.toggle('showQuests');

events.emit('onToggleQuestsVisibility', config.showQuests);
},

onToggleEventsVisibility: function (state) {
const newValue = state ? 'On' : 'Off';

this.find('.item.events .value').html(newValue);
},

onToggleQuestsVisibility: function (state) {
const newValue = state[0].toUpperCase() + state.substr(1);

this.find('.item.quests .value').html(newValue);
},

onResize: function () {
let isFullscreen = (window.innerHeight === screen.height);
if (isFullscreen)
this.el.find('.btnScreen').html('Windowed');
else
this.el.find('.btnScreen').html('Fullscreen');
const newValue = isFullscreen ? 'On' : 'Off';

this.find('.item.screen .value').html(newValue);
},

onToggleNameplates: function (state) {
const newValue = state ? 'On' : 'Off';

this.find('.item.nameplates .value').html(newValue);
},

toggleAudio: function () {
config.toggle('playAudio');

events.emit('onToggleAudio', config.playAudio);
},

onToggleAudio: function (isAudioOn) {
const newValue = isAudioOn ? 'On' : 'Off';

this.find('.item.audio .value').html(newValue);
},

build: function () {
this.onToggleNameplates(config.showNames);
this.onToggleAudio(config.playAudio);
this.onToggleEventsVisibility(config.showEvents);
this.onToggleQuestsVisibility(config.showQuests);
this.onToggleQualityIndicators(config.qualityIndicators);
this.onToggleUnusableIndicators(config.unusableIndicators);
},

toggle: function () {
@@ -91,19 +155,29 @@ define([
if (this.shown) {
this.show();
events.emit('onShowOverlay', this.el);

this.build();
} else {
this.hide();
events.emit('onHideOverlay', this.el);
events.emit('onToggleMainMenu');
}
},
onKeyDown: function (keyEvent) {
const { key } = keyEvent;
if (key === 'v') {
config.toggle('showNames');

logOut: function () {
window.location = window.location;
events.emit('onToggleNameplates', config.showNames);

const newValue = config.showNames ? 'On' : 'Off';
this.find('.item.nameplates .value').html(newValue);
}
},

onKeyDown: function (key) {
if (key === 'esc')
this.toggle();
afterHide: function () {
events.emit('onToggleMainMenu');
}
};
});

+ 56
- 31
src/client/ui/templates/options/styles.less Целия файл

@@ -2,48 +2,73 @@

.uiOptions {
display: none;

width: 400px;
background-color: @gray;
border: 4px solid @lightGray;
background-color: @blackC;
text-align: center;
padding: 0px 16px;

.btn {
color: @white;
width: 100%;
height: 32px;
background-color: @blue;
margin: 16px 0px;
padding-top: 8px;
cursor: pointer;

&.btnNames {
display: none;
}

&:hover {
background-color: lighten(@blue, 15%);
color: @black;
}
border: 5px solid @blackB;

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

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

}

.btnIssue {
background-color: @red;
.bottom {
padding: 10px;

.list {
display: flex;
flex-direction: column;

.item {
height: 30px;
display: flex;
justify-content: space-between;
align-items: center;

.name {
color: @blueB;
flex: 1;
height: 100%;
display: flex;
align-items: center;
padding: 0px 10px;
margin-right: 10px;
cursor: pointer;

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

}

.value {
color: @white;
display: flex;
align-items: center;
justify-content: flex-end;
}

}

&:hover {
background-color: lighten(@red, 15%);
color: @black;
}

}

}

.mobile .uiOptions {
.btnScreen {
.item.screen {
display: none;
}

.btnNames {
display: block;
}
}

+ 35
- 7
src/client/ui/templates/options/template.html Целия файл

@@ -1,9 +1,37 @@
<div class="uiOptions">
<div class="btn btnContinue">Continue</div>
<div class="btn btnScreen">Windowed</div>
<div class="btn btnCharSelect">Character Select</div>
<div class="btn btnNames">Toggle Nameplates</div>
<div class="btn btnLogOut">Log Out</div>
<div class="btn btnPatreon">Pledge on Patreon</div>
<div class="btn btnIssue">Report an Issue</div>
<div class="heading">
<div class="heading-text">Options</div>
</div>
<div class="bottom">
<div class="list">
<div class="item screen">
<div class="name">Fullscreen</div>
<div class="value">Off</div>
</div>
<div class="item nameplates">
<div class="name">Nameplates</div>
<div class="value">On</div>
</div>
<div class="item quests">
<div class="name">Quests</div>
<div class="value">On</div>
</div>
<div class="item events">
<div class="name">Events</div>
<div class="value">On</div>
</div>
<div class="item quality">
<div class="name">Quality Indicators</div>
<div class="value">Off</div>
</div>
<div class="item unusable">
<div class="name">Unusable Indicators</div>
<div class="value">Off</div>
</div>
<div class="item audio">
<div class="name">Audio</div>
<div class="value">On</div>
</div>
</div>
</div>
</div>

+ 7
- 5
src/client/ui/templates/passives/input.js Целия файл

@@ -45,7 +45,9 @@ define([

enabled: true,

init: function (el) {
init: function (el, zoom) {
this.zoom = zoom;

el
.on('mousedown', this.events.mouse.mouseDown.bind(this))
.on('mouseup', this.events.mouse.mouseUp.bind(this))
@@ -197,7 +199,7 @@ define([

touch: {
touchStart: function (e) {
let pos = this.events.touch.convertTouchPos(e);
let pos = this.events.touch.convertTouchPos.call(this, e);

this.mouse.raw = {
clientX: pos.x,
@@ -215,7 +217,7 @@ define([
},

touchMove: function (e) {
let pos = this.events.touch.convertTouchPos(e);
let pos = this.events.touch.convertTouchPos.call(this, e);

this.mouse.raw = {
clientX: pos.x,
@@ -244,8 +246,8 @@ define([
convertTouchPos: function (e) {
let rect = e.target.getBoundingClientRect();
return {
x: e.targetTouches[0].pageX - rect.left,
y: e.targetTouches[0].pageY - rect.top
x: (e.targetTouches[0].pageX - rect.left) * this.zoom,
y: (e.targetTouches[0].pageY - rect.top) * this.zoom
};
}
}


+ 11
- 13
src/client/ui/templates/passives/passives.js Целия файл

@@ -1,3 +1,7 @@
let zoom = window.devicePixelRatio;
if (isMobile)
zoom /= 2;

define([
'js/system/events',
'js/system/client',
@@ -51,7 +55,7 @@ define([
handlerResize: null,

postRender: function () {
input.init(this.el);
input.init(this.el, zoom);

this.data.nodes = temp.nodes;
this.data.links = temp.links.map(l => {
@@ -74,8 +78,6 @@ define([
this.handlerResize = this.onResize.bind(this);
window.addEventListener('resize', this.handlerResize);

let zoom = window.devicePixelRatio;

this.canvas = this.find('.canvas')[0];
this.size.w = this.canvas.width = this.find('.bottom').width() * zoom;
this.size.h = this.canvas.height = this.find('.bottom').height() * zoom;
@@ -120,8 +122,6 @@ define([
if (isMobile || !this.shown)
return;
let zoom = window.devicePixelRatio;

this.size.w = this.canvas.width = this.find('.bottom').width() * zoom;
this.size.h = this.canvas.height = this.find('.bottom').height() * zoom;

@@ -357,8 +357,8 @@ define([
text = 'Starting node for ' + node.spiritStart + ' spirits';

let tooltipPos = {
x: input.mouse.raw.clientX + 15,
y: input.mouse.raw.clientY
x: (input.mouse.raw.clientX + 15) / zoom,
y: (input.mouse.raw.clientY) / zoom
};

events.emit('onShowTooltip', text, this.el[0], tooltipPos);
@@ -399,10 +399,9 @@ define([

this.events.onMouseMove.call(this, e);

let windowZoom = window.devicePixelRatio;
this.panOrigin = {
x: e.raw.clientX * windowZoom,
y: e.raw.clientY * windowZoom
x: e.raw.clientX * zoom,
y: e.raw.clientY * zoom
};
},

@@ -419,12 +418,11 @@ define([
};
}

let windowZoom = window.devicePixelRatio;
let zoomPanMultiplier = this.currentZoom;
let scrollSpeed = constants.scrollSpeed / zoomPanMultiplier;

const rawX = e.raw.clientX * windowZoom;
const rawY = e.raw.clientY * windowZoom;
const rawX = e.raw.clientX * zoom;
const rawY = e.raw.clientY * zoom;

this.pos.x += (this.panOrigin.x - rawX) * scrollSpeed;
this.pos.y += (this.panOrigin.y - rawY) * scrollSpeed;


+ 20
- 2
src/client/ui/templates/quests/quests.js Целия файл

@@ -3,13 +3,15 @@ define([
'js/system/events',
'html!ui/templates/quests/template',
'html!ui/templates/quests/templateQuest',
'css!ui/templates/quests/styles'
'css!ui/templates/quests/styles',
'js/config'
], function (
client,
events,
tpl,
templateQuest,
styles
styles,
config
) {
return {
tpl: tpl,
@@ -28,6 +30,9 @@ define([
this.onEvent('onObtainQuest', this.onObtainQuest.bind(this));
this.onEvent('onUpdateQuest', this.onUpdateQuest.bind(this));
this.onEvent('onCompleteQuest', this.onCompleteQuest.bind(this));
this.onEvent('onToggleQuestsVisibility', this.onToggleQuestsVisibility.bind(this));

this.onToggleQuestsVisibility(config.showQuests);
},

onRezone: function () {
@@ -118,6 +123,19 @@ define([
toggleButtons: function (e) {
this.el.toggleClass('active');
e.stopPropagation();
},

onToggleQuestsVisibility: function (state) {
this.shown = state !== 'off';

if (this.shown)
this.show();
else
this.hide();

this.el.removeClass('minimal');
if (state === 'minimal')
this.el.addClass('minimal');
}
};
});

+ 23
- 7
src/client/ui/templates/quests/styles.less Целия файл

@@ -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,24 +86,34 @@
display: block;
color: @white;
}

}

}

}

.btn {
display: none;
}

&.minimal {
.quest.disabled {
display: none;
}

}

}

.mobile .uiQuests {
background-color: fade(@darkGray, 90%);

&:not(.active) {
position: absolute;
top: 10px;
right: 158px;
width: @btnSize;
height: @btnSize;
margin-left: 10px;
position: relative;

&:after {
content: '';
@@ -117,6 +129,7 @@
> * {
display: none;
}

}

&.active {
@@ -129,17 +142,20 @@
display: block;
position: absolute;
right: calc(100% + 10px);
top: 50%;
transform: translateY(-50%);
top: 0px;
background-color: fade(@darkGray, 90%);
width: @btnSize;
height: @btnSize;
padding-top: 0px;

.icon {
width: 100%;
height: 100%;
background: url('../../../images/uiIcons.png') -448px 0px;
}

}

}

}

+ 5
- 7
src/client/ui/templates/reputation/reputation.js Целия файл

@@ -30,25 +30,23 @@ define([
this.find('.info .description').html('');
this.find('.bar-outer').hide();

if (list.length === 0)
if (!list.length)
this.find('.heading-bottom').html("you haven't discovered any factions yet");
else
this.find('.heading-bottom').html('select a faction to see more info');

let elList = this.find('.list').empty();

list.forEach(function (l) {
list.forEach(l => {
if (l.noGainRep)
return;

let html = '<div class="faction">' + l.name.toLowerCase() + '</div>';

let el = $(html)
.appendTo(elList);
let el = $(html).appendTo(elList);

el
.on('click', this.onSelectFaction.bind(this, el, l));
}, this);
el.on('click', this.onSelectFaction.bind(this, el, l));
});
},

onSelectFaction: function (el, faction) {


+ 30
- 12
src/client/ui/templates/reputation/styles.less Целия файл

@@ -17,9 +17,11 @@
padding-top: 8px;
margin: auto;
}

}

.list, .info {
.list,
.info {
background-color: fade(@blackC, 90%);
float: left;
height: calc(100% - 36px);
@@ -44,7 +46,9 @@
&:hover {
background-color: @grayC;
}

}

}

.info {
@@ -59,6 +63,7 @@
.heading-bottom {
margin: auto;
}

}

.description {
@@ -96,18 +101,31 @@
position: absolute;
left: 0px;
top: 5px;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

-moz-filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
}

}

}

.list:empty {
display: none;

+ .info {
width: 100%;
}

}

}

.mobile .uiReputation {
z-index: 2;
}

+ 1
- 1
src/client/ui/templates/reputation/template.html Целия файл

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

+ 59
- 50
src/client/ui/templates/smithing/styles.less Целия файл

@@ -16,6 +16,7 @@
padding-top: 8px;
margin: auto;
}

}

.bottom {
@@ -51,12 +52,14 @@
text-align: center;
}

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

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

}

&.item-picker {
@@ -67,15 +70,18 @@
height: 64px;
background: url('../../../images/uiIcons.png') -256px -64px;
}

}

&.actionButton {
padding: 8px;

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

}

.item {
@@ -91,21 +97,19 @@
bottom: 3px;
position: absolute;
color: @white;
filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

-moz-filter:
drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);
-moz-filter: drop-shadow(0px -2px 0px @blackD)
drop-shadow(0px 2px 0px @blackD)
drop-shadow(2px 0px 0px @blackD)
drop-shadow(-2px 0px 0px @blackD);

&.red {
color: @red;
}

}

.icon {
@@ -114,21 +118,20 @@
position: absolute;
left: 8px;
top: 8px;
filter:
brightness(100%)
drop-shadow(0px -4px 0px @blackD)
drop-shadow(0px 4px 0px @blackD)
drop-shadow(4px 0px 0px @blackD)
drop-shadow(-4px 0px 0px @blackD);

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

}

}

&:first-child {
@@ -142,36 +145,42 @@

.col-btn {
height: calc((100% - 10px) / 2);
width: 100%;
color: #f2f5f5;
text-align: center;
padding-top: 10px;
background-color: @blackB;
margin-bottom: 10px;
cursor: pointer;
&.selected {
color: @orange;
}
&:hover {
background-color: @blackA;
}
&.col-half {
width: calc((100% - 10px) / 2);
float: left;
&:nth-child(2n + 1) {
width: 100%;
color: #f2f5f5;
text-align: center;
padding-top: 10px;
background-color: @blackB;
margin-bottom: 10px;
cursor: pointer;
&.selected {
color: @orangeA;
}
&:hover {
background-color: @blackA;
}
&.col-half {
width: calc((100% - 10px) / 2);
float: left;
&:nth-child(2n + 1) {
margin-right: 10px;
}
}
}

&:not(.col-half) {
}

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

}

}

}

}

}

+ 14
- 16
src/client/ui/templates/spells/styles.less Целия файл

@@ -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,11 +79,11 @@
display: flex;
flex-direction: column-reverse;


.spell {
position: relative;
margin: 8px 0px 0px 0px;
margin: 10px 0px 0px 0px;
background-color: fade(#3a3b4a, 90%);
border: none;

.hotkey {
display: none;
@@ -95,12 +91,14 @@

&.active:before {
content: '';
width: 4px;
height: 100%;
left: -4px;
top: 0%;
background-color: @greenB;
position: absolute;
width: 5px;
height: 100%;
left: -5px;
top: 0%;
background-color: @greenB;
position: absolute;
}

}

}

+ 33
- 18
src/client/ui/templates/workbench/styles.less Целия файл

@@ -18,6 +18,7 @@
padding-top: 8px;
margin: auto;
}

}

.bottom {
@@ -29,8 +30,9 @@
color: @blueB;
margin-bottom: 10px;
}
.left, .right {

.left,
.right {
float: left;
height: 100%;
padding: 10px;
@@ -55,8 +57,11 @@
&:hover {
background-color: @blackB;
}

}

}

}

.right {
@@ -70,9 +75,10 @@
height: calc(100% - 100px - 35px);

.title {
color: @orange;
color: @orangeA;
padding-bottom: 10px;
}

}

.materialList {
@@ -80,12 +86,12 @@
visibility: hidden;

.material {


&.need {
color: @redB;
}

}

}

.buttons {
@@ -93,23 +99,32 @@

> .btn {
height: 35px;
width: 100px;
color: @white;
text-align: center;
padding-top: 10px;
background-color: @blackB;
cursor: pointer;
float: left;
&:last-child {
width: 100px;
color: @white;
text-align: center;
padding-top: 10px;
background-color: @blackB;
cursor: pointer;
float: left;
&:last-child {
float: right;
}
}

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

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

}

}

}

}

.mobile .uiWorkbench {
z-index: 2;
}

+ 18
- 8
src/server/components/auth/checkLoginRewards.js Целия файл

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

+ 4
- 0
src/server/components/equipment.js Целия файл

@@ -28,6 +28,10 @@ module.exports = {
};
},

isSlotEmpty: function (slot) {
return !this.eq.has(slot);
},

autoEquip: function (itemId) {
if (!this.doAutoEq)
return;


+ 15
- 3
src/server/components/extensions/socialCommands.js Целия файл

@@ -6,6 +6,8 @@ let factions = require('../../config/factions');
let connections = require('../../security/connections');

const ban = require('../social/ban');
const rezone = require('../social/rezone');
const canChat = require('../social/canChat');

let commandRoles = {
//Regular players
@@ -39,7 +41,8 @@ let commandRoles = {
getXp: 10,
setPassword: 10,
giveSkin: 10,
getMaterials: 10
getMaterials: 10,
rezone: 10
};

//Commands that should be run on the main thread (not the zone thread)
@@ -286,13 +289,18 @@ module.exports = {
},

roll: function () {
let roll = 1 + ~~(Math.random() * 100);
if (!canChat(this.obj)) {
this.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;
}

const roll = 1 + ~~(Math.random() * 100);
cons.emit('event', {
event: 'onGetMessages',
data: {
messages: [{
class: 'color-grayB',
message: this.obj.name + ' rolled ' + roll,
message: `${this.obj.name} rolled ${roll}`,
type: 'chat'
}]
}
@@ -674,5 +682,9 @@ module.exports = {

ban: function (msg) {
ban(this, msg);
},

rezone: function (msg) {
rezone(this, msg);
}
};

+ 4
- 5
src/server/components/gatherer.js Целия файл

@@ -23,6 +23,8 @@ module.exports = {
if (nodes.length === 0)
return;

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

let firstNode = nodes[0];

this.gathering = firstNode;
@@ -30,17 +32,14 @@ module.exports = {
let ttlMax = firstNode.resourceNode.ttl || this.defaultTtlMax;

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

return;
}

rod = this.obj.inventory.findItem(rod);

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



+ 11
- 8
src/server/components/inventory.js Целия файл

@@ -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,9 +449,9 @@ module.exports = {
},

mailItem: async function (msg) {
return;
let item = this.findItem(msg.itemId);
if ((!item) || (item.noDrop) || (item.quest)) {

if (!item || item.noDrop || item.quest) {
this.resolveCallback(msg);
return;
}
@@ -476,12 +476,12 @@ module.exports = {
blocked = true;
}

if (!blocked) {
const mappedItem = this.simplifyItem(item);
this.obj.instance.mail.sendMail(msg.recipient, [mappedItem]);
}
const mappedItem = this.simplifyItem(item);
this.destroyItem(item.id);

if (!blocked)
this.obj.instance.mail.sendMail(msg.recipient, [mappedItem]);

this.resolveCallback(msg);
},

@@ -555,9 +555,12 @@ module.exports = {
this.items
.filter(i => !i.eq)
.map(i => {
//If we don't do this, [waist] goes before [undefined]
const useSlot = i.slot ? i.slot : 'z';

return {
item: i,
sortId: `${i.slot}${i.material}${i.quest}${i.spell}${i.quality}${i.level}${i.sprite}${i.id}`
sortId: `${useSlot}${i.material}${i.quest}${i.spell}${i.quality}${i.level}${i.sprite}${i.id}`
};
})
.sort((a, b) => {


+ 1
- 0
src/server/components/passives.js Целия файл

@@ -109,6 +109,7 @@ module.exports = {
this.selected.push(passiveTree.nodes.find(n => (n.spiritStart === this.obj.class)).id);

this.obj.spellbook.calcDps();
this.obj.equipment.unequipAttrRqrGear();
},

simplify: function (self) {


+ 3
- 1
src/server/components/quests.js Целия файл

@@ -30,7 +30,9 @@ module.exports = {
if (!quest.init(hideMessage)) {
this.quests.spliceWhere(q => (q === quest));
return false;
} return true;
}

return true;
},

complete: function (id) {


+ 13
- 0
src/server/components/social/canChat.js Целия файл

@@ -0,0 +1,13 @@
module.exports = (obj, time) => {
if (!time)
time = +new Date();
const playerLevel = obj.level;
const playedTime = obj.stats.stats.played * 1000;
const sessionStart = obj.player.sessionStart;
const sessionDelta = time - sessionStart;

const canChat = (playerLevel >= 3 || playedTime + sessionDelta >= 180000);
return canChat;
};

+ 14
- 15
src/server/components/social/chat.js Целия файл

@@ -1,11 +1,15 @@
let roles = require('../../config/roles');
let events = require('../../misc/events');
const profanities = require('../../misc/profanities');
const canChat = require('./canChat');

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 +23,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 +38,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 +51,12 @@ 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 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 (!canChat(obj, time)) {
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 +65,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 +82,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 +92,7 @@ module.exports = (cpnSocial, msg) => {
message: prefix + charname + ': ' + msg.data.message,
item: msg.data.item,
type: 'chat',
source: cpnSocial.obj.name
source: obj.name
}]
}
});


+ 18
- 0
src/server/components/social/rezone.js Целия файл

@@ -0,0 +1,18 @@
module.exports = async (cpnSocial, targetZone) => {
const { obj } = cpnSocial;

obj.fireEvent('beforeRezone');

obj.destroyed = true;

const simpleObj = obj.getSimple(true, false, true);

process.send({
method: 'rezone',
id: obj.serverId,
args: {
obj: simpleObj,
newZone: targetZone
}
});
};

+ 21
- 19
src/server/components/spellbook.js Целия файл

@@ -351,25 +351,6 @@ module.exports = {
if (!isAuto)
this.sendAnnouncement('Insufficient mana to cast spell');
success = false;
} else if (spell.manaReserve) {
let reserve = spell.manaReserve;

if (reserve.percentage) {
let reserveEvent = {
spell: spell.name,
reservePercent: reserve.percentage
};
this.obj.fireEvent('onBeforeReserveMana', reserveEvent);

if (!spell.active) {
if (1 - this.obj.stats.values.manaReservePercent < reserve.percentage) {
this.sendAnnouncement('Insufficient mana to cast spell');
success = false;
} else
this.obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent);
} else
this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
}
} else if (spell.has('range')) {
let distance = Math.max(Math.abs(action.target.x - this.obj.x), Math.abs(action.target.y - this.obj.y));
let range = spell.range;
@@ -410,6 +391,27 @@ module.exports = {
if (!castSuccess.success)
return false;

if (spell.manaReserve) {
let reserve = spell.manaReserve;

if (reserve.percentage) {
let reserveEvent = {
spell: spell.name,
reservePercent: reserve.percentage
};
this.obj.fireEvent('onBeforeReserveMana', reserveEvent);

if (!spell.active) {
if (1 - this.obj.stats.values.manaReservePercent < reserve.percentage) {
this.sendAnnouncement('Insufficient mana to cast spell');
success = false;
} else
this.obj.stats.addStat('manaReservePercent', reserveEvent.reservePercent);
} else
this.obj.stats.addStat('manaReservePercent', -reserveEvent.reservePercent);
}
}

if (spell.targetFurthest)
spell.target = this.obj.aggro.getFurthest();
else if (spell.targetRandom)


+ 4
- 3
src/server/components/stats.js Целия файл

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


+ 7
- 1
src/server/components/trade.js Целия файл

@@ -265,10 +265,16 @@ module.exports = {

let targetTrade = target.trade;

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

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

if (oldQuantity)
item.quantity = oldQuantity;

let worth = ~~(item.worth * targetTrade.markup.buy);

this.gold += worth;


+ 4
- 0
src/server/config/effects/effectStunned.js Целия файл

@@ -1,6 +1,10 @@
module.exports = {
type: 'stunned',

init: function () {
this.obj.spellbook.stopCasting();
},

events: {
beforeMove: function (targetPos) {
targetPos.success = false;


+ 28
- 0
src/server/config/eventPhases/phaseDespawnMob.js Целия файл

@@ -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 Целия файл

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

event.nextPhase = event.phases.length;

this.end = true;
}
};

+ 26
- 0
src/server/config/eventPhases/phaseGiveRewards.js Целия файл

@@ -0,0 +1,26 @@
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];
if (pRewards && pRewards.length)
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 Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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

+ 7
- 0
src/server/config/eventPhases/phaseSetDescription.js Целия файл

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

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

+ 90
- 69
src/server/config/eventPhases/phaseSpawnMob.js Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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

+ 46
- 4
src/server/config/maps/cave/map.json
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 291
- 0
src/server/config/maps/sewer/events/plagueOfRats.js Целия файл

@@ -0,0 +1,291 @@
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,
auto: true
}, {
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
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 2
- 0
src/server/config/maps/sewer/zone.js Целия файл

@@ -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 Целия файл

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

+ 5
- 0
src/server/config/quests/questBuilder.js Целия файл

@@ -8,6 +8,7 @@ module.exports = {
init: function (instance) {
this.instance = instance;
},

obtain: function (obj, template) {
let zoneName = template ? template.zoneName : obj.zoneName;
let zonePath = mapList.mapList.find(m => m.name === zoneName).path;
@@ -31,6 +32,10 @@ module.exports = {
if (config.infini.length === 0)
return;

const minPlayerLevel = ~~(obj.instance.map.zone.level[0] * 0.75);
if (obj.stats.values.level < minPlayerLevel)
return;

let pickQuest = null;
if ((template) && (template.type))
pickQuest = config.infini.find(c => c.type === template.type);


+ 7
- 6
src/server/config/quests/templates/questKillX.js Целия файл

@@ -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/recipes/etching.js Целия файл

@@ -2,7 +2,7 @@ module.exports = [{
id: 'runeWhirlwind',
name: 'Rune of Whirlwind',
default: false,
description: 'Wiggle-wiggly woo-woo.',
description: 'You furiously spin in a circle, striking all foes around you.',
item: {
name: 'Rune of Whirlwind',
generate: true,
@@ -20,7 +20,7 @@ module.exports = [{
id: 'runeAmbush',
name: 'Rune of Ambush',
default: false,
description: 'Wiggle-wiggly woo-woo.',
description: 'Step into the shadows and reappear behind your target before delivering a concussing blow.',
item: {
name: 'Rune of Ambush',
generate: true,


+ 2
- 2
src/server/config/serverConfig.js Целия файл

@@ -1,5 +1,5 @@
module.exports = {
version: '0.4.0',
version: '0.4.3',
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


+ 15
- 10
src/server/config/spells/spellAmbush.js Целия файл

@@ -75,27 +75,32 @@ module.exports = {
// the delta by -1
let offsetX = 0;
if (dx !== 0)
offsetX = (dx / Math.abs(dx)) * -1;
offsetX = dx / Math.abs(dx);

let offsetY = 0;
if (dy !== 0)
offsetY = (dy / Math.abs(dy)) * -1;
offsetY = dy / Math.abs(dy);

let targetPos = {
x: target.x,
y: target.y
};

let physics = obj.instance.physics;
const physics = obj.instance.physics;
const fnTileValid = this.isTileValid.bind(this, physics, x, y);
//Check where we should land
if (!this.isTileValid(physics, x, y, targetPos.x - offsetX, targetPos.y - offsetY)) {
if (!this.isTileValid(physics, x, y, targetPos.x - offsetX, targetPos.y))
targetPos.y -= offsetY;
else
targetPos.x -= offsetX;
if (!fnTileValid(targetPos.x + offsetX, targetPos.y + offsetY)) {
if (!fnTileValid(targetPos.x + offsetX, targetPos.y)) {
if (!fnTileValid(targetPos.x, targetPos.y + offsetY)) {
targetPos.x -= offsetX;
targetPos.y -= offsetY;
} else
targetPos.y += offsetY;
} else
targetPos.x += offsetX;
} else {
targetPos.x -= offsetX;
targetPos.y -= offsetY;
targetPos.x += offsetX;
targetPos.y += offsetY;
}

let targetEffect = target.effects.addEffect({


+ 2
- 1
src/server/config/spells/spellAura.js Целия файл

@@ -95,7 +95,8 @@ module.exports = {
if (!members.find(m => ~~m === ~~serverId)) {
delete effects[serverId];
const obj = objects.find(f => ~~f.serverId === ~~serverId);
obj.effects.removeEffect(effect);
if (obj)
obj.effects.removeEffect(effect);
}
});
},


+ 65
- 52
src/server/config/spells/spellFireblast.js Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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


+ 1
- 0
src/server/items/enchanter.js Целия файл

@@ -103,6 +103,7 @@ const reslot = (item, msg) => {
delete item.spell;
delete item.implicitStats;
delete item.power;
delete item.range;

extend(item, newItem);
};


+ 4
- 3
src/server/items/generators/stats.js Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

@@ -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 Целия файл

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


+ 1
- 1
src/server/security/rest.js Целия файл

@@ -19,7 +19,7 @@ module.exports = {

let pars = req.originalUrl.split('?').pop().split('&');
pars.forEach(p => {
let [par, val] = p.split('=');
let [par, val = ''] = p.split('=');
config[par] = val
.split('%20')
.join(' ');


+ 0
- 1
src/server/server.js Целия файл

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

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


+ 1
- 1
src/server/world/instancer.js Целия файл

@@ -93,7 +93,7 @@ module.exports = {
if (spawnEvent.changed)
msg.keepPos = false;

if ((msg.keepPos) && (!physics.isValid(obj.x, obj.y)))
if (msg.keepPos && (!physics.isValid(obj.x, obj.y) || !map.canPathFromPos(obj)))
msg.keepPos = false;

if (!msg.keepPos || !obj.has('x') || (map.mapFile.properties.isRandom && obj.instanceId !== map.seed)) {


Някои файлове не бяха показани, защото твърде много файлове са промени

Зареждане…
Отказ
Запис