Browse Source

Merge branch 'v0.2.0' into '535-passive-tree'

# Conflicts:
#   src/server/components/passives.js
#   src/server/security/router.js
tags/v0.2.0^2
Big Bad Waffle 6 years ago
parent
commit
14820cd793
100 changed files with 7877 additions and 5249 deletions
  1. +1
    -0
      .gitignore
  2. +0
    -0
     
  3. BIN
     
  4. BIN
     
  5. BIN
     
  6. +51
    -5
      src/client/css/colors.less
  7. BIN
     
  8. BIN
     
  9. BIN
     
  10. BIN
     
  11. BIN
     
  12. BIN
     
  13. BIN
     
  14. +7
    -3
      src/client/js/app.js
  15. +25
    -0
      src/client/js/components/inventory.js
  16. +10
    -7
      src/client/js/components/keyboardMover.js
  17. +24
    -8
      src/client/js/components/player.js
  18. +9
    -6
      src/client/js/components/stats.js
  19. +10
    -2
      src/client/js/main.js
  20. +30
    -28
      src/client/js/misc/pathfinder.js
  21. +31
    -21
      src/client/js/misc/physics.js
  22. +11
    -2
      src/client/js/misc/statTranslations.js
  23. +23
    -5
      src/client/js/objects/objects.js
  24. +0
    -8
      src/client/js/rendering/renderer.js
  25. +72
    -0
      src/client/js/sound/sound.js
  26. +8
    -8
      src/client/js/system/events.js
  27. +0
    -59
      src/client/main.js
  28. +852
    -0
      src/client/plugins/howler.min.js
  29. +160
    -0
      src/client/plugins/howler.spatial.min.js
  30. +1
    -1
      src/client/ui/factory.js
  31. +1
    -1
      src/client/ui/templates/characters/characters.js
  32. +0
    -1
      src/client/ui/templates/createCharacter/createCharacter.js
  33. +28
    -9
      src/client/ui/templates/death/death.js
  34. +6
    -1
      src/client/ui/templates/death/styles.less
  35. +2
    -1
      src/client/ui/templates/death/template.html
  36. +10
    -3
      src/client/ui/templates/equipment/equipment.js
  37. +2
    -2
      src/client/ui/templates/help/template.html
  38. +1
    -1
      src/client/ui/templates/hud/hud.js
  39. +93
    -16
      src/client/ui/templates/inventory/inventory.js
  40. +27
    -4
      src/client/ui/templates/inventory/styles.less
  41. +4
    -2
      src/client/ui/templates/inventory/template.html
  42. +15
    -11
      src/client/ui/templates/leaderboard/leaderboard.js
  43. +3
    -3
      src/client/ui/templates/login/template.html
  44. +3
    -3
      src/client/ui/templates/messages/messages.js
  45. +2
    -2
      src/client/ui/templates/messages/styles.less
  46. +19
    -15
      src/client/ui/templates/options/options.js
  47. +9
    -0
      src/client/ui/templates/options/styles.less
  48. +1
    -0
      src/client/ui/templates/options/template.html
  49. +26
    -16
      src/client/ui/templates/party/styles.less
  50. +4
    -3
      src/client/ui/templates/party/templateInvite.html
  51. +18
    -16
      src/client/ui/templates/quests/quests.js
  52. +26
    -1
      src/client/ui/templates/quests/styles.less
  53. +3
    -1
      src/client/ui/templates/quests/templateQuest.html
  54. +13
    -11
      src/client/ui/templates/reputation/reputation.js
  55. +5
    -3
      src/client/ui/templates/spells/spells.js
  56. +31
    -4
      src/client/ui/templates/tooltipItem/styles.less
  57. +7
    -2
      src/client/ui/templates/tooltipItem/templateTooltip.html
  58. +98
    -8
      src/client/ui/templates/tooltipItem/tooltipItem.js
  59. +6
    -6
      src/client/ui/templates/tooltips/tooltips.js
  60. +19
    -4
      src/server/combat/combat.js
  61. +11
    -3
      src/server/components/aggro.js
  62. +90
    -7
      src/server/components/auth.js
  63. +3
    -1
      src/server/components/chatter.js
  64. +26
    -18
      src/server/components/components.js
  65. +26
    -32
      src/server/components/dialogue.js
  66. +7
    -3
      src/server/components/door.js
  67. +33
    -9
      src/server/components/effects.js
  68. +23
    -20
      src/server/components/equipment.js
  69. +9
    -3
      src/server/components/extensions/factionVendor.js
  70. +125
    -7
      src/server/components/extensions/socialCommands.js
  71. +134
    -110
      src/server/components/inventory.js
  72. +1
    -1
      src/server/components/mob.js
  73. +31
    -15
      src/server/components/player.js
  74. +7
    -5
      src/server/components/reputation.js
  75. +65
    -25
      src/server/components/social.js
  76. +12
    -2
      src/server/components/spellbook.js
  77. +194
    -90
      src/server/components/stats.js
  78. +4
    -1
      src/server/components/syncer.js
  79. +7
    -7
      src/server/components/trade.js
  80. +1
    -1
      src/server/components/wardrobe.js
  81. +0
    -72
      src/server/config/classes.js
  82. +2
    -2
      src/server/config/effects/effectHolyVengeance.js
  83. +2
    -2
      src/server/config/effects/effectRegenHp.js
  84. +5
    -3
      src/server/config/factions/akarei.js
  85. +17
    -0
      src/server/config/factions/players.js
  86. +23
    -8
      src/server/config/itemEffects/damageSelf.js
  87. +1
    -1
      src/server/config/itemEffects/healOnCrit.js
  88. +14
    -4
      src/server/config/maps/cave/zone.js
  89. +11
    -0
      src/server/config/maps/estuary/zone.js
  90. +3772
    -3274
      src/server/config/maps/fjolarok/map.json
  91. +1334
    -1141
      src/server/config/maps/sewer/map.json
  92. +16
    -6
      src/server/config/maps/sewer/zone.js
  93. +20
    -2
      src/server/config/prophecies/titangrip.js
  94. +7
    -2
      src/server/config/quests/questBuilder.js
  95. +10
    -1
      src/server/config/quests/templates/questGatherResource.js
  96. +6
    -1
      src/server/config/quests/templates/questKillX.js
  97. +14
    -1
      src/server/config/quests/templates/questLoot.js
  98. +4
    -0
      src/server/config/quests/templates/questLootGen.js
  99. +3
    -3
      src/server/config/quests/templates/questTemplate.js
  100. +0
    -53
      src/server/config/roleSkins.js

+ 1
- 0
.gitignore View File

@@ -3,6 +3,7 @@ storage.db
*.sublime-project
*.sublime-workspace
*.css
src/server/mods/iwd-*
!helpers/item-tooltip/styles.css
!helpers/passives/**/*.css
creds.js

+ 0
- 0
View File


BIN
View File


BIN
View File


BIN
View File


+ 51
- 5
src/client/css/colors.less View File

@@ -14,6 +14,7 @@
@red: #d43346;
@blue: #3fa7dd;
@green: #80f643;
@greenA: #80f643;
@greenB: #4ac441;
@greenC: #386646;

@@ -33,6 +34,7 @@
@orangeD: #953f36;

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

@blueA: #48edff;
@blueB: #3fa7dd;
@@ -48,6 +50,7 @@
@purpleD: #393268;

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

@grayB: #c0c3cf;
@grayC: #929398;
@@ -58,15 +61,15 @@
}

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

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

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

.q4 {
@@ -74,9 +77,52 @@
}

.color-red {
color: @red;
color: @red !important;
}

.color-redA {
color: @redA !important;
}

.color-blueA {
color: @blueA !important;
}
.color-blueB {
color: @blueB !important;
}

.color-greenB {
color: @greenB !important;
}

.color-yellowB {
color: @yellowB !important;
}

.color-green {
color: @green;
color: @green !important;
}

.color-brownC {
color: @brownC !important;
}
.color-brownD {
color: @brownD !important;
}

.color-grayA {
color: @white !important;
}
.color-grayB {
color: @grayB !important;
}
.color-grayC {
color: @grayC !important;
}
.color-grayD {
color: @grayD !important;
}

.color-pinkB {
color: @pinkB !important;
}

BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


BIN
View File


+ 7
- 3
src/client/js/app.js View File

@@ -17,9 +17,13 @@ require.config({
'helpers': 'js/misc/helpers',
'particles': 'plugins/pixi.particles',
'picture': 'plugins/pixi.picture',
'pixi': 'plugins/pixi.min'
'pixi': 'plugins/pixi.min',
'howler': 'plugins/howler.min'
},
shim: {
'howler': {
exports: 'howl'
},
'socket': {
exports: 'io'
},
@@ -55,8 +59,8 @@ require.config({

require([
'main'
], function(
], function (
main
) {
main.init();
});
});

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

@@ -59,6 +59,31 @@ define([

events.emit('onGetItems', this.items, rerender);
}
},

equipItemErrors: function (item) {
var errors = [];
var stats = this.obj.stats.values;

var playerLevel = (stats.originalLevel || stats.level);
if (item.level > playerLevel)
errors.push('level');

if ((item.requires) && (stats[item.requires[0].stat] < item.requires[0].value))
errors.push('stats');

if (item.factions) {
if (item.factions.some(function (f) {
return f.noEquip;
}))
errors.push('faction');
}

return errors;
},

canEquipItem: function (item) {
return (this.equipItemErrors.length == 0);
}
};
});

+ 10
- 7
src/client/js/components/keyboardMover.js View File

@@ -2,7 +2,7 @@ define([
'js/input',
'js/system/client',
'js/misc/physics'
], function(
], function (
input,
client,
physics
@@ -17,7 +17,10 @@ define([
y: 0
},

update: function() {
update: function () {
if (this.obj.dead)
return;

if (this.obj.moveAnimation)
this.obj.pather.clearPath();

@@ -40,17 +43,17 @@ define([
this.keyMove();
},

bump: function(dx, dy) {
bump: function (dx, dy) {
if (this.obj.pather.path.length > 0)
return;
this.obj.addComponent('bumpAnimation', {
deltaX: dx,
deltaY: dy
});
},

keyMove: function() {
keyMove: function () {
var delta = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
@@ -74,7 +77,7 @@ define([

this.addQueue(newX, newY);
},
addQueue: function(x, y) {
addQueue: function (x, y) {
if (this.obj.moveAnimation)
return;

@@ -95,4 +98,4 @@ define([
});
}
};
});
});

+ 24
- 8
src/client/js/components/player.js View File

@@ -1,9 +1,13 @@
define([
'js/rendering/renderer',
'js/system/events'
], function(
'js/system/events',
'js/misc/physics',
'js/sound/sound'
], function (
renderer,
events
events,
physics,
sound
) {
var scale = 40;

@@ -15,7 +19,7 @@ define([
y: 0
},

init: function() {
init: function () {
this.obj.addComponent('keyboardMover');
this.obj.addComponent('mouseMover');
this.obj.addComponent('serverActions');
@@ -25,7 +29,7 @@ define([
events.emit('onGetPortrait', this.obj.portrait);
},

update: function() {
update: function () {
var obj = this.obj;
var oldPos = this.oldPos;

@@ -38,7 +42,7 @@ define([
var instant = false;
if ((dx > 5) || (dy > 5))
instant = true;
if (dx != 0)
dx = dx / Math.abs(dx);
if (dy != 0)
@@ -51,9 +55,21 @@ define([
x: dx,
y: dy
}, instant);

sound.update(obj.x, obj.y);
},

extend: function (blueprint) {
if (blueprint.collisionChanges) {
blueprint.collisionChanges.forEach(function (c) {
physics.setCollision(c.x, c.y, c.collides);
});

delete blueprint.collisionChanges;
}
},

canvasFollow: function(delta, instant) {
canvasFollow: function (delta, instant) {
var obj = this.obj;
delta = delta || {
x: 0,
@@ -66,4 +82,4 @@ define([
}, instant);
},
};
});
});

+ 9
- 6
src/client/js/components/stats.js View File

@@ -1,7 +1,7 @@
define([
'js/system/events',
'js/rendering/renderer'
], function(
], function (
events,
renderer
) {
@@ -15,7 +15,7 @@ define([
hpSprite: null,
hpSpriteInner: null,

init: function(blueprint) {
init: function (blueprint) {
if (this.obj.self)
events.emit('onGetStats', this.values);

@@ -50,7 +50,10 @@ define([
this.updateHpSprite();
},

updateHpSprite: function() {
updateHpSprite: function () {
if (this.obj.dead)
return;

var obj = this.obj;

var yOffset = -12;
@@ -80,7 +83,7 @@ define([
this.hpSpriteInner.visible = this.hpSprite.visible;
},

extend: function(blueprint) {
extend: function (blueprint) {
var bValues = blueprint.values || {};

var values = this.values;
@@ -99,7 +102,7 @@ define([
this.updateHpSprite();
},

destroy: function() {
destroy: function () {
renderer.destroyObject({
sprite: this.hpSprite,
layerName: 'effects'
@@ -111,4 +114,4 @@ define([
});
}
};
});
});

+ 10
- 2
src/client/js/main.js View File

@@ -58,8 +58,16 @@ define([
window.onfocus = this.onFocus.bind(this, true);
window.onblur = this.onFocus.bind(this, false);
$(window).on('contextmenu', function (e) {
e.preventDefault();
return false;
var allowedList = ['txtUsername', 'txtPassword'];

var allowed = allowedList.some(function (item) {
return $(e.target).hasClass(item);
});

if (!allowed) {
e.preventDefault();
return false;
}
});

objects.init();


+ 30
- 28
src/client/js/misc/pathfinder.js View File

@@ -4,7 +4,7 @@
// Implements the astar search algorithm in javascript using a Binary Heap.
// Includes Binary Heap (with modifications) from Marijn Haverbeke.
// http://eloquentjavascript.net/appendix2.html
(function(definition) {
(function (definition) {
/* global module, define */
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = definition();
@@ -15,7 +15,7 @@
window.astar = exports.astar;
window.Graph = exports.Graph;
}
})(function() {
})(function () {

function pathTo(node) {
var curr = node;
@@ -28,7 +28,7 @@
}

function getHeap() {
return new BinaryHeap(function(node) {
return new BinaryHeap(function (node) {
return node.f;
});
}
@@ -42,10 +42,13 @@
* @param {Object} [options]
* @param {bool} [options.closest] Specifies whether to return the
path to the closest node if the target is unreachable.
* @param {Function} [options.heuristic] Heuristic function (see
* @param {
Function
}[options.heuristic] Heuristic
function (see
* astar.heuristics).
*/
search: function(graph, start, end, options) {
search: function (graph, start, end, options) {
start = graph.grid[start.x][start.y] || start;
end = graph.grid[end.x][end.y] || end;

@@ -77,8 +80,7 @@
if (distance) {
if (currentNode.h == distance)
return pathTo(currentNode);
}
else {
} else {
// End case -- result has been found, return the traced path.
if (currentNode === end) {
return pathTo(currentNode);
@@ -142,17 +144,17 @@
},
// See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
heuristics: {
manhattan: function(pos0, pos1) {
manhattan: function (pos0, pos1) {
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return Math.max(d1, d2);
},
manhattanDistance: function(pos0, pos1, distance) {
manhattanDistance: function (pos0, pos1, distance) {
var d1 = Math.abs(pos1.x - pos0.x);
var d2 = Math.abs(pos1.y - pos0.y);
return Math.abs(distance - Math.max(d1, d2)) + 1;
},
diagonal: function(pos0, pos1) {
diagonal: function (pos0, pos1) {
var D = 1;
var D2 = Math.sqrt(2);
var d1 = Math.abs(pos1.x - pos0.x);
@@ -160,7 +162,7 @@
return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
}
},
cleanNode: function(node) {
cleanNode: function (node) {
if (!node)
return;
node.f = 0;
@@ -198,25 +200,25 @@
this.init();
}

Graph.prototype.init = function() {
Graph.prototype.init = function () {
this.dirtyNodes = [];
for (var i = 0; i < this.nodes.length; i++) {
astar.cleanNode(this.nodes[i]);
}
};

Graph.prototype.cleanDirty = function() {
Graph.prototype.cleanDirty = function () {
for (var i = 0; i < this.dirtyNodes.length; i++) {
astar.cleanNode(this.dirtyNodes[i]);
}
this.dirtyNodes = [];
};

Graph.prototype.markDirty = function(node) {
Graph.prototype.markDirty = function (node) {
this.dirtyNodes.push(node);
};

Graph.prototype.neighbors = function(node) {
Graph.prototype.neighbors = function (node) {
var ret = [];
var x = node.x;
var y = node.y;
@@ -267,7 +269,7 @@
return ret;
};

Graph.prototype.toString = function() {
Graph.prototype.toString = function () {
var graphString = [];
var nodes = this.grid;
for (var x = 0; x < nodes.length; x++) {
@@ -287,11 +289,11 @@
this.weight = weight;
}

GridNode.prototype.toString = function() {
GridNode.prototype.toString = function () {
return "[" + this.x + " " + this.y + "]";
};

GridNode.prototype.getCost = function(fromNeighbor) {
GridNode.prototype.getCost = function (fromNeighbor) {
// Take diagonal weight into consideration.
if (fromNeighbor && fromNeighbor.x != this.x && fromNeighbor.y != this.y) {
return this.weight * 1.41421;
@@ -299,7 +301,7 @@
return this.weight;
};

GridNode.prototype.isWall = function() {
GridNode.prototype.isWall = function () {
return this.weight === 0;
};

@@ -309,14 +311,14 @@
}

BinaryHeap.prototype = {
push: function(element) {
push: function (element) {
// Add the new element to the end of the array.
this.content.push(element);

// Allow it to sink down.
this.sinkDown(this.content.length - 1);
},
pop: function() {
pop: function () {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
@@ -329,7 +331,7 @@
}
return result;
},
remove: function(node) {
remove: function (node) {
var i = this.content.indexOf(node);

// When it is found, the process seen in 'pop' is repeated
@@ -346,13 +348,13 @@
}
}
},
size: function() {
size: function () {
return this.content.length;
},
rescoreElement: function(node) {
rescoreElement: function (node) {
this.sinkDown(this.content.indexOf(node));
},
sinkDown: function(n) {
sinkDown: function (n) {
// Fetch the element that has to be sunk.
var element = this.content[n];

@@ -375,7 +377,7 @@
}
}
},
bubbleUp: function(n) {
bubbleUp: function (n) {
// Look up the target element and its score.
var length = this.content.length;
var element = this.content[n];
@@ -426,7 +428,7 @@
return {
astar: astar,
Graph: Graph,
GridNode: GridNode
gridNode: GridNode
};

});
});

+ 31
- 21
src/client/js/misc/physics.js View File

@@ -1,6 +1,6 @@
define([
'js/misc/pathfinder'
], function(
], function (
pathfinder
) {
var sqrt = Math.sqrt.bind(Math);
@@ -15,7 +15,7 @@ define([
width: 0,
height: 0,

init: function(collisionMap) {
init: function (collisionMap) {
this.collisionMap = collisionMap;

this.width = collisionMap.length;
@@ -28,7 +28,7 @@ define([
});
},

addRegion: function(obj) {
addRegion: function (obj) {
var lowX = obj.x;
var lowY = obj.y;
var highX = lowX + obj.width;
@@ -43,7 +43,7 @@ define([
}
},

addObject: function(obj, x, y, fromX, fromY) {
addObject: function (obj, x, y, fromX, fromY) {
var row = this.cells[x];

if (!row)
@@ -73,7 +73,7 @@ define([
cell.push(obj);
return true;
},
removeObject: function(obj, x, y, toX, toY) {
removeObject: function (obj, x, y, toX, toY) {
var row = this.cells[x];

if (!row)
@@ -108,7 +108,7 @@ define([
}
},

isValid: function(x, y) {
isValid: function (x, y) {
var row = this.cells[x];

if ((!row) || (row.length <= y) || (!this.graph.grid[x][y]))
@@ -117,7 +117,7 @@ define([
return true;
},

getCell: function(x, y) {
getCell: function (x, y) {
var row = this.cells[x];

if (!row)
@@ -130,7 +130,7 @@ define([

return cell;
},
getArea: function(x1, y1, x2, y2, filter) {
getArea: function (x1, y1, x2, y2, filter) {
var width = this.width;
var height = this.height;

@@ -177,7 +177,7 @@ define([
return result;
},

getOpenCellInArea: function(x1, y1, x2, y2) {
getOpenCellInArea: function (x1, y1, x2, y2) {
var width = this.width;
var height = this.height;

@@ -220,7 +220,7 @@ define([
return result;
},

getPath: function(from, to) {
getPath: function (from, to) {
var graph = this.graph;
var grid = graph.grid;

@@ -255,22 +255,24 @@ define([

return path;
},
isTileBlocking: function(x, y, mob, obj) {
isTileBlocking: function (x, y, mob, obj) {
if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height))
return true;

x = ~~x;
y = ~~y;

return !this.graph.grid[x][y];
var node = this.graph.grid[x][y];

return ((!node) || (node.weight == 0));
},
isCellOpen: function(x, y) {
isCellOpen: function (x, y) {
if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height))
return true;

return (this.cells[x][y].length == 0);
},
hasLos: function(fromX, fromY, toX, toY) {
hasLos: function (fromX, fromY, toX, toY) {
if ((fromX < 0) || (fromY < 0) || (fromX >= this.width) | (fromY >= this.height) || (toX < 0) || (toY < 0) || (toX >= this.width) | (toY >= this.height))
return false;

@@ -311,7 +313,7 @@ define([
return true;
},

getClosestPos: function(fromX, fromY, toX, toY, target) {
getClosestPos: function (fromX, fromY, toX, toY, target) {
var tried = {};

var hasLos = this.hasLos.bind(this, toX, toY);
@@ -337,8 +339,7 @@ define([
incX = -1;
lowX = x2;
highX = x1 - 1;
}
else {
} else {
incX = 1;
lowX = x1;
highX = x2 + 1;
@@ -348,8 +349,7 @@ define([
incY = -1;
lowY = y2;
highY = y1 - 1;
}
else {
} else {
incY = 1;
lowY = y1;
highY = y2 + 1;
@@ -405,7 +405,7 @@ define([
}
},

mobsCollide: function(x, y, obj) {
mobsCollide: function (x, y, obj) {
if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height))
return true;

@@ -427,6 +427,16 @@ define([
}

return false;
},

setCollision: function (x, y, collides) {
var node = this.graph.grid[x][y];
if (!node) {
var grid = this.graph.grid;
node = grid[x][y] = new pathfinder.gridNode(x, y, collides ? 0 : 1);
}

node.weight = collides ? 0 : 1;
}
};
});
});

+ 11
- 2
src/client/js/misc/statTranslations.js View File

@@ -12,10 +12,19 @@ define([
'int': 'intellect',
'dex': 'dexterity',
'armor': 'armor',

'blockAttackChance': 'chance to block attacks',
'blockSpellChance': 'chance to block spells',
'addCritChance': 'increased crit chance',
'addCritMultiplier': 'increased crit multiplier',

'dodgeAttackChance': 'chance to dodge attacks',
'dodgeSpellChance': 'chance to dodge spells',

'addCritChance': 'global crit chance',
'addCritMultiplier': 'global crit multiplier',
'addAttackCritChance': 'attack crit chance',
'addAttackCritMultiplier': 'attack crit multiplier',
'addSpellCritChance': 'spell crit chance',
'addSpellCritMultiplier': 'spell crit multiplier',
'magicFind': 'increased item quality',
'itemQuantity': 'increased item quantity',
'sprintChance': 'sprint chance',


+ 23
- 5
src/client/js/objects/objects.js View File

@@ -1,11 +1,13 @@
define([
'js/objects/objBase',
'js/system/events',
'js/rendering/renderer'
'js/rendering/renderer',
'js/sound/sound'
], function (
objBase,
events,
renderer
renderer,
sound
) {
var scale = 40;

@@ -146,8 +148,15 @@ define([

if (obj.sheetName) {
obj.sprite = renderer.buildObject(obj);
if (template.hidden)
if (template.hidden) {
obj.sprite.visible = false;
if (obj.nameSprite)
obj.nameSprite.visible = false;
if ((obj.stats) && (obj.stats.hpSprite)) {
obj.stats.hpSprite.visible = false;
obj.stats.hpSpriteInner.visible = false;
}
}
}

components.forEach(function (c) {
@@ -174,6 +183,8 @@ define([
events.emit('onGetPlayer', obj);
window.player = obj;

sound.init(obj.zoneName);

renderer.setPosition({
x: (obj.x - (renderer.width / (scale * 2))) * scale,
y: (obj.y - (renderer.height / (scale * 2))) * scale
@@ -254,6 +265,12 @@ define([
if (sprite) {
if (template.hidden != null) {
sprite.visible = !template.hidden;
if (obj.nameSprite)
obj.nameSprite.visible = this.showNames;
if ((obj.stats) && (obj.stats.hpSprite)) {
obj.stats.hpSprite.visible = !template.hidden;
obj.stats.hpSpriteInner.visible = !template.hidden;
}
}
}

@@ -310,8 +327,9 @@ define([
var objects = this.objects;
var oLen = objects.length;
for (var i = 0; i < oLen; i++) {
var ns = objects[i].nameSprite;
if (!ns)
var obj = objects[i];
var ns = obj.nameSprite;
if ((!ns) || (obj.dead))
continue;

ns.visible = showNames;


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

@@ -76,7 +76,6 @@ define([
PIXI.SCALE_MODES.DEFAULT = PIXI.SCALE_MODES.NEAREST;

events.on('onGetMap', this.onGetMap.bind(this));
events.on('onDeath', this.onDeath.bind(this));
events.on('onToggleFullscreen', this.toggleScreen.bind(this));

this.width = $('body').width();
@@ -240,13 +239,6 @@ define([
}
},

onDeath: function (pos) {
this.setPosition({
x: (pos.x - (this.width / (scale * 2))) * scale,
y: (pos.y - (this.height / (scale * 2))) * scale
}, true);
},

onResize: function () {
var zoom = window.devicePixelRatio;



+ 72
- 0
src/client/js/sound/sound.js View File

@@ -0,0 +1,72 @@
define([
'howler'
], function (
howler
) {
return {
sounds: [],

init: function (zone) {
this.unload();

if (zone != 'fjolarok')
return;

this.addSound('fire.ogg', 123, 123);
this.addSound('stream.ogg', 107, 69);
this.addSound('wind.ogg', 176, 104);
},

unload: function () {
this.sounds.forEach(function (s) {
if (s.sound)
s.sound.unload();
});

this.sounds = [];
},

update: function (x, y) {
this.sounds.forEach(function (s) {
var dx = Math.abs(s.x - x);
if (dx > 10) {
if (s.sound)
s.sound.volume(0);
return;
}
var dy = Math.abs(s.y - y);
if (dy > 10) {
if (s.sound)
s.sound.volume(0);
return;
}

var dist = 10 - Math.max(dx, dy);
dist = (dist * dist) / 100;
var volume = 0.3 * dist;

if (!s.sound) {
s.sound = new Howl({
src: ['audio/' + s.file],
autoplay: true,
loop: true,
volume: 0
});
}

s.sound.volume(volume);
});
},

addSound: function (file, x, y) {
var sound = {
file: file,
x: x,
y: y,
sound: null
};

this.sounds.push(sound);
}
};
});

+ 8
- 8
src/client/js/system/events.js View File

@@ -1,12 +1,12 @@
define([

], function(
], function (

) {
var events = {
events: {},
queue: [],
on: function(event, callback) {
on: function (event, callback) {
var list = this.events[event] || (this.events[event] = []);
list.push(callback);

@@ -25,13 +25,13 @@ define([

return callback;
},
clearQueue: function() {
clearQueue: function () {
//Hack to allow the player list to persist
this.queue.spliceWhere(function(q) {
return (q.event != 'onGetConnectedPlayer');
this.queue.spliceWhere(function (q) {
return ((q.event != 'onGetConnectedPlayer') && (q.event != 'onGetDisconnectedPlayer'));
});
},
off: function(event, callback) {
off: function (event, callback) {
var list = this.events[event] || [];
var lLen = list.length;
for (var i = 0; i < lLen; i++) {
@@ -45,7 +45,7 @@ define([
if (lLen == 0)
delete this.events[event];
},
emit: function(event) {
emit: function (event) {
var args = [].slice.call(arguments, 1);

var list = this.events[event];
@@ -70,4 +70,4 @@ define([
window.addons.init(events);

return events;
});
});

+ 0
- 59
src/client/main.js View File

@@ -1,59 +0,0 @@
const {
app,
BrowserWindow
} = require('electron')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
frame: true,
title: 'Isleward'
});
win.maximize();

// and load the index.html of the app.
//win.loadURL(`http://default-environment.9ymkeaciiv.eu-west-1.elasticbeanstalk.com/index.html`)
win.loadURL(`http://localhost:4000/index.html`)

// Open the DevTools.
//win.webContents.openDevTools()

// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})

app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

+ 852
- 0
src/client/plugins/howler.min.js View File

@@ -0,0 +1,852 @@
/*! howler.js v2.0.9 | (c) 2013-2018, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ ! function () {
"use strict";
var e = function () {
this.init()
};
e.prototype = {
init: function () {
var e = this || n;
return e._counter = 1e3, e._codecs = {}, e._howls = [], e._muted = !1, e._volume = 1, e._canPlayEvent = "canplaythrough", e._navigator = "undefined" != typeof window && window.navigator ? window.navigator : null, e.masterGain = null, e.noAudio = !1, e.usingWebAudio = !0, e.autoSuspend = !0, e.ctx = null, e.mobileAutoEnable = !0, e._setup(), e
},
volume: function (e) {
var t = this || n;
if (e = parseFloat(e), t.ctx || _(), void 0 !== e && e >= 0 && e <= 1) {
if (t._volume = e, t._muted) return t;
t.usingWebAudio && t.masterGain.gain.setValueAtTime(e, n.ctx.currentTime);
for (var o = 0; o < t._howls.length; o++)
if (!t._howls[o]._webAudio)
for (var r = t._howls[o]._getSoundIds(), a = 0; a < r.length; a++) {
var u = t._howls[o]._soundById(r[a]);
u && u._node && (u._node.volume = u._volume * e)
}
return t
}
return t._volume
},
mute: function (e) {
var t = this || n;
t.ctx || _(), t._muted = e, t.usingWebAudio && t.masterGain.gain.setValueAtTime(e ? 0 : t._volume, n.ctx.currentTime);
for (var o = 0; o < t._howls.length; o++)
if (!t._howls[o]._webAudio)
for (var r = t._howls[o]._getSoundIds(), a = 0; a < r.length; a++) {
var u = t._howls[o]._soundById(r[a]);
u && u._node && (u._node.muted = !!e || u._muted)
}
return t
},
unload: function () {
for (var e = this || n, t = e._howls.length - 1; t >= 0; t--) e._howls[t].unload();
return e.usingWebAudio && e.ctx && void 0 !== e.ctx.close && (e.ctx.close(), e.ctx = null, _()), e
},
codecs: function (e) {
return (this || n)._codecs[e.replace(/^x-/, "")]
},
_setup: function () {
var e = this || n;
if (e.state = e.ctx ? e.ctx.state || "running" : "running", e._autoSuspend(), !e.usingWebAudio)
if ("undefined" != typeof Audio) try {
var t = new Audio;
void 0 === t.oncanplaythrough && (e._canPlayEvent = "canplay")
} catch (n) {
e.noAudio = !0
} else e.noAudio = !0;
try {
var t = new Audio;
t.muted && (e.noAudio = !0)
} catch (e) {}
return e.noAudio || e._setupCodecs(), e
},
_setupCodecs: function () {
var e = this || n,
t = null;
try {
t = "undefined" != typeof Audio ? new Audio : null
} catch (n) {
return e
}
if (!t || "function" != typeof t.canPlayType) return e;
var o = t.canPlayType("audio/mpeg;").replace(/^no$/, ""),
r = e._navigator && e._navigator.userAgent.match(/OPR\/([0-6].)/g),
a = r && parseInt(r[0].split("/")[1], 10) < 33;
return e._codecs = {
mp3: !(a || !o && !t.canPlayType("audio/mp3;").replace(/^no$/, "")),
mpeg: !!o,
opus: !!t.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, ""),
ogg: !!t.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
oga: !!t.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, ""),
wav: !!t.canPlayType('audio/wav; codecs="1"').replace(/^no$/, ""),
aac: !!t.canPlayType("audio/aac;").replace(/^no$/, ""),
caf: !!t.canPlayType("audio/x-caf;").replace(/^no$/, ""),
m4a: !!(t.canPlayType("audio/x-m4a;") || t.canPlayType("audio/m4a;") || t.canPlayType("audio/aac;")).replace(/^no$/, ""),
mp4: !!(t.canPlayType("audio/x-mp4;") || t.canPlayType("audio/mp4;") || t.canPlayType("audio/aac;")).replace(/^no$/, ""),
weba: !!t.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ""),
webm: !!t.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/, ""),
dolby: !!t.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/, ""),
flac: !!(t.canPlayType("audio/x-flac;") || t.canPlayType("audio/flac;")).replace(/^no$/, "")
}, e
},
_enableMobileAudio: function () {
var e = this || n,
t = /iPhone|iPad|iPod|Android|BlackBerry|BB10|Silk|Mobi/i.test(e._navigator && e._navigator.userAgent),
o = !!("ontouchend" in window || e._navigator && e._navigator.maxTouchPoints > 0 || e._navigator && e._navigator.msMaxTouchPoints > 0);
if (!e._mobileEnabled && e.ctx && (t || o)) {
e._mobileEnabled = !1, e._mobileUnloaded || 44100 === e.ctx.sampleRate || (e._mobileUnloaded = !0, e.unload()), e._scratchBuffer = e.ctx.createBuffer(1, 1, 22050);
var r = function () {
n._autoResume();
var t = e.ctx.createBufferSource();
t.buffer = e._scratchBuffer, t.connect(e.ctx.destination), void 0 === t.start ? t.noteOn(0) : t.start(0), "function" == typeof e.ctx.resume && e.ctx.resume(), t.onended = function () {
t.disconnect(0), e._mobileEnabled = !0, e.mobileAutoEnable = !1, document.removeEventListener("touchstart", r, !0), document.removeEventListener("touchend", r, !0)
}
};
return document.addEventListener("touchstart", r, !0), document.addEventListener("touchend", r, !0), e
}
},
_autoSuspend: function () {
var e = this;
if (e.autoSuspend && e.ctx && void 0 !== e.ctx.suspend && n.usingWebAudio) {
for (var t = 0; t < e._howls.length; t++)
if (e._howls[t]._webAudio)
for (var o = 0; o < e._howls[t]._sounds.length; o++)
if (!e._howls[t]._sounds[o]._paused) return e;
return e._suspendTimer && clearTimeout(e._suspendTimer), e._suspendTimer = setTimeout(function () {
e.autoSuspend && (e._suspendTimer = null, e.state = "suspending", e.ctx.suspend().then(function () {
e.state = "suspended", e._resumeAfterSuspend && (delete e._resumeAfterSuspend, e._autoResume())
}))
}, 3e4), e
}
},
_autoResume: function () {
var e = this;
if (e.ctx && void 0 !== e.ctx.resume && n.usingWebAudio) return "running" === e.state && e._suspendTimer ? (clearTimeout(e._suspendTimer), e._suspendTimer = null) : "suspended" === e.state ? (e.ctx.resume().then(function () {
e.state = "running";
for (var n = 0; n < e._howls.length; n++) e._howls[n]._emit("resume")
}), e._suspendTimer && (clearTimeout(e._suspendTimer), e._suspendTimer = null)) : "suspending" === e.state && (e._resumeAfterSuspend = !0), e
}
};
var n = new e,
t = function (e) {
var n = this;
if (!e.src || 0 === e.src.length) return void console.error("An array of source files must be passed with any new Howl.");
n.init(e)
};
t.prototype = {
init: function (e) {
var t = this;
return n.ctx || _(), t._autoplay = e.autoplay || !1, t._format = "string" != typeof e.format ? e.format : [e.format], t._html5 = e.html5 || !1, t._muted = e.mute || !1, t._loop = e.loop || !1, t._pool = e.pool || 5, t._preload = "boolean" != typeof e.preload || e.preload, t._rate = e.rate || 1, t._sprite = e.sprite || {}, t._src = "string" != typeof e.src ? e.src : [e.src], t._volume = void 0 !== e.volume ? e.volume : 1, t._xhrWithCredentials = e.xhrWithCredentials || !1, t._duration = 0, t._state = "unloaded", t._sounds = [], t._endTimers = {}, t._queue = [], t._playLock = !1, t._onend = e.onend ? [{
fn: e.onend
}] : [], t._onfade = e.onfade ? [{
fn: e.onfade
}] : [], t._onload = e.onload ? [{
fn: e.onload
}] : [], t._onloaderror = e.onloaderror ? [{
fn: e.onloaderror
}] : [], t._onplayerror = e.onplayerror ? [{
fn: e.onplayerror
}] : [], t._onpause = e.onpause ? [{
fn: e.onpause
}] : [], t._onplay = e.onplay ? [{
fn: e.onplay
}] : [], t._onstop = e.onstop ? [{
fn: e.onstop
}] : [], t._onmute = e.onmute ? [{
fn: e.onmute
}] : [], t._onvolume = e.onvolume ? [{
fn: e.onvolume
}] : [], t._onrate = e.onrate ? [{
fn: e.onrate
}] : [], t._onseek = e.onseek ? [{
fn: e.onseek
}] : [], t._onresume = [], t._webAudio = n.usingWebAudio && !t._html5, void 0 !== n.ctx && n.ctx && n.mobileAutoEnable && n._enableMobileAudio(), n._howls.push(t), t._autoplay && t._queue.push({
event: "play",
action: function () {
t.play()
}
}), t._preload && t.load(), t
},
load: function () {
var e = this,
t = null;
if (n.noAudio) return void e._emit("loaderror", null, "No audio support.");
"string" == typeof e._src && (e._src = [e._src]);
for (var r = 0; r < e._src.length; r++) {
var u, i;
if (e._format && e._format[r]) u = e._format[r];
else {
if ("string" != typeof (i = e._src[r])) {
e._emit("loaderror", null, "Non-string found in selected audio sources - ignoring.");
continue
}
u = /^data:audio\/([^;,]+);/i.exec(i), u || (u = /\.([^.]+)$/.exec(i.split("?", 1)[0])), u && (u = u[1].toLowerCase())
}
if (u || console.warn('No file extension was found. Consider using the "format" property or specify an extension.'), u && n.codecs(u)) {
t = e._src[r];
break
}
}
return t ? (e._src = t, e._state = "loading", "https:" === window.location.protocol && "http:" === t.slice(0, 5) && (e._html5 = !0, e._webAudio = !1), new o(e), e._webAudio && a(e), e) : void e._emit("loaderror", null, "No codec support for selected audio sources.")
},
play: function (e, t) {
var o = this,
r = null;
if ("number" == typeof e) r = e, e = null;
else {
if ("string" == typeof e && "loaded" === o._state && !o._sprite[e]) return null;
if (void 0 === e) {
e = "__default";
for (var a = 0, u = 0; u < o._sounds.length; u++) o._sounds[u]._paused && !o._sounds[u]._ended && (a++, r = o._sounds[u]._id);
1 === a ? e = null : r = null
}
}
var i = r ? o._soundById(r) : o._inactiveSound();
if (!i) return null;
if (r && !e && (e = i._sprite || "__default"), "loaded" !== o._state) {
i._sprite = e, i._ended = !1;
var d = i._id;
return o._queue.push({
event: "play",
action: function () {
o.play(d)
}
}), d
}
if (r && !i._paused) return t || o._loadQueue("play"), i._id;
o._webAudio && n._autoResume();
var _ = Math.max(0, i._seek > 0 ? i._seek : o._sprite[e][0] / 1e3),
s = Math.max(0, (o._sprite[e][0] + o._sprite[e][1]) / 1e3 - _),
l = 1e3 * s / Math.abs(i._rate);
i._paused = !1, i._ended = !1, i._sprite = e, i._seek = _, i._start = o._sprite[e][0] / 1e3, i._stop = (o._sprite[e][0] + o._sprite[e][1]) / 1e3, i._loop = !(!i._loop && !o._sprite[e][2]);
var c = i._node;
if (o._webAudio) {
var f = function () {
o._refreshBuffer(i);
var e = i._muted || o._muted ? 0 : i._volume;
c.gain.setValueAtTime(e, n.ctx.currentTime), i._playStart = n.ctx.currentTime, void 0 === c.bufferSource.start ? i._loop ? c.bufferSource.noteGrainOn(0, _, 86400) : c.bufferSource.noteGrainOn(0, _, s) : i._loop ? c.bufferSource.start(0, _, 86400) : c.bufferSource.start(0, _, s), l !== 1 / 0 && (o._endTimers[i._id] = setTimeout(o._ended.bind(o, i), l)), t || setTimeout(function () {
o._emit("play", i._id)
}, 0)
};
"running" === n.state ? f() : (o.once("resume", f), o._clearTimer(i._id))
} else {
var p = function () {
c.currentTime = _, c.muted = i._muted || o._muted || n._muted || c.muted, c.volume = i._volume * n.volume(), c.playbackRate = i._rate;
try {
var r = c.play();
if ("undefined" != typeof Promise && r instanceof Promise) {
o._playLock = !0;
var a = function () {
o._playLock = !1, t || o._emit("play", i._id)
};
r.then(a, a)
} else t || o._emit("play", i._id);
if (c.paused) return void o._emit("playerror", i._id, "Playback was unable to start. This is most commonly an issue on mobile devices where playback was not within a user interaction.");
"__default" !== e ? o._endTimers[i._id] = setTimeout(o._ended.bind(o, i), l) : (o._endTimers[i._id] = function () {
o._ended(i), c.removeEventListener("ended", o._endTimers[i._id], !1)
}, c.addEventListener("ended", o._endTimers[i._id], !1))
} catch (e) {
o._emit("playerror", i._id, e)
}
},
m = window && window.ejecta || !c.readyState && n._navigator.isCocoonJS;
if (c.readyState >= 3 || m) p();
else {
var v = function () {
p(), c.removeEventListener(n._canPlayEvent, v, !1)
};
c.addEventListener(n._canPlayEvent, v, !1), o._clearTimer(i._id)
}
}
return i._id
},
pause: function (e) {
var n = this;
if ("loaded" !== n._state || n._playLock) return n._queue.push({
event: "pause",
action: function () {
n.pause(e)
}
}), n;
for (var t = n._getSoundIds(e), o = 0; o < t.length; o++) {
n._clearTimer(t[o]);
var r = n._soundById(t[o]);
if (r && !r._paused && (r._seek = n.seek(t[o]), r._rateSeek = 0, r._paused = !0, n._stopFade(t[o]), r._node))
if (n._webAudio) {
if (!r._node.bufferSource) continue;
void 0 === r._node.bufferSource.stop ? r._node.bufferSource.noteOff(0) : r._node.bufferSource.stop(0), n._cleanBuffer(r._node)
} else isNaN(r._node.duration) && r._node.duration !== 1 / 0 || r._node.pause();
arguments[1] || n._emit("pause", r ? r._id : null)
}
return n
},
stop: function (e, n) {
var t = this;
if ("loaded" !== t._state) return t._queue.push({
event: "stop",
action: function () {
t.stop(e)
}
}), t;
for (var o = t._getSoundIds(e), r = 0; r < o.length; r++) {
t._clearTimer(o[r]);
var a = t._soundById(o[r]);
a && (a._seek = a._start || 0, a._rateSeek = 0, a._paused = !0, a._ended = !0, t._stopFade(o[r]), a._node && (t._webAudio ? a._node.bufferSource && (void 0 === a._node.bufferSource.stop ? a._node.bufferSource.noteOff(0) : a._node.bufferSource.stop(0), t._cleanBuffer(a._node)) : isNaN(a._node.duration) && a._node.duration !== 1 / 0 || (a._node.currentTime = a._start || 0, a._node.pause())), n || t._emit("stop", a._id))
}
return t
},
mute: function (e, t) {
var o = this;
if ("loaded" !== o._state) return o._queue.push({
event: "mute",
action: function () {
o.mute(e, t)
}
}), o;
if (void 0 === t) {
if ("boolean" != typeof e) return o._muted;
o._muted = e
}
for (var r = o._getSoundIds(t), a = 0; a < r.length; a++) {
var u = o._soundById(r[a]);
u && (u._muted = e, u._interval && o._stopFade(u._id), o._webAudio && u._node ? u._node.gain.setValueAtTime(e ? 0 : u._volume, n.ctx.currentTime) : u._node && (u._node.muted = !!n._muted || e), o._emit("mute", u._id))
}
return o
},
volume: function () {
var e, t, o = this,
r = arguments;
if (0 === r.length) return o._volume;
if (1 === r.length || 2 === r.length && void 0 === r[1]) {
o._getSoundIds().indexOf(r[0]) >= 0 ? t = parseInt(r[0], 10) : e = parseFloat(r[0])
} else r.length >= 2 && (e = parseFloat(r[0]), t = parseInt(r[1], 10));
var a;
if (!(void 0 !== e && e >= 0 && e <= 1)) return a = t ? o._soundById(t) : o._sounds[0], a ? a._volume : 0;
if ("loaded" !== o._state) return o._queue.push({
event: "volume",
action: function () {
o.volume.apply(o, r)
}
}), o;
void 0 === t && (o._volume = e), t = o._getSoundIds(t);
for (var u = 0; u < t.length; u++)(a = o._soundById(t[u])) && (a._volume = e, r[2] || o._stopFade(t[u]), o._webAudio && a._node && !a._muted ? a._node.gain.setValueAtTime(e, n.ctx.currentTime) : a._node && !a._muted && (a._node.volume = e * n.volume()), o._emit("volume", a._id));
return o
},
fade: function (e, t, o, r) {
var a = this;
if ("loaded" !== a._state) return a._queue.push({
event: "fade",
action: function () {
a.fade(e, t, o, r)
}
}), a;
a.volume(e, r);
for (var u = a._getSoundIds(r), i = 0; i < u.length; i++) {
var d = a._soundById(u[i]);
if (d) {
if (r || a._stopFade(u[i]), a._webAudio && !d._muted) {
var _ = n.ctx.currentTime,
s = _ + o / 1e3;
d._volume = e, d._node.gain.setValueAtTime(e, _), d._node.gain.linearRampToValueAtTime(t, s)
}
a._startFadeInterval(d, e, t, o, u[i], void 0 === r)
}
}
return a
},
_startFadeInterval: function (e, n, t, o, r, a) {
var u = this,
i = n,
d = t - n,
_ = Math.abs(d / .01),
s = Math.max(4, _ > 0 ? o / _ : o),
l = Date.now();
e._fadeTo = t, e._interval = setInterval(function () {
var r = (Date.now() - l) / o;
l = Date.now(), i += d * r, i = Math.max(0, i), i = Math.min(1, i), i = Math.round(100 * i) / 100, u._webAudio ? e._volume = i : u.volume(i, e._id, !0), a && (u._volume = i), (t < n && i <= t || t > n && i >= t) && (clearInterval(e._interval), e._interval = null, e._fadeTo = null, u.volume(t, e._id), u._emit("fade", e._id))
}, s)
},
_stopFade: function (e) {
var t = this,
o = t._soundById(e);
return o && o._interval && (t._webAudio && o._node.gain.cancelScheduledValues(n.ctx.currentTime), clearInterval(o._interval), o._interval = null, t.volume(o._fadeTo, e), o._fadeTo = null, t._emit("fade", e)), t
},
loop: function () {
var e, n, t, o = this,
r = arguments;
if (0 === r.length) return o._loop;
if (1 === r.length) {
if ("boolean" != typeof r[0]) return !!(t = o._soundById(parseInt(r[0], 10))) && t._loop;
e = r[0], o._loop = e
} else 2 === r.length && (e = r[0], n = parseInt(r[1], 10));
for (var a = o._getSoundIds(n), u = 0; u < a.length; u++)(t = o._soundById(a[u])) && (t._loop = e, o._webAudio && t._node && t._node.bufferSource && (t._node.bufferSource.loop = e, e && (t._node.bufferSource.loopStart = t._start || 0, t._node.bufferSource.loopEnd = t._stop)));
return o
},
rate: function () {
var e, t, o = this,
r = arguments;
if (0 === r.length) t = o._sounds[0]._id;
else if (1 === r.length) {
var a = o._getSoundIds(),
u = a.indexOf(r[0]);
u >= 0 ? t = parseInt(r[0], 10) : e = parseFloat(r[0])
} else 2 === r.length && (e = parseFloat(r[0]), t = parseInt(r[1], 10));
var i;
if ("number" != typeof e) return i = o._soundById(t), i ? i._rate : o._rate;
if ("loaded" !== o._state) return o._queue.push({
event: "rate",
action: function () {
o.rate.apply(o, r)
}
}), o;
void 0 === t && (o._rate = e), t = o._getSoundIds(t);
for (var d = 0; d < t.length; d++)
if (i = o._soundById(t[d])) {
i._rateSeek = o.seek(t[d]), i._playStart = o._webAudio ? n.ctx.currentTime : i._playStart, i._rate = e, o._webAudio && i._node && i._node.bufferSource ? i._node.bufferSource.playbackRate.setValueAtTime(e, n.ctx.currentTime) : i._node && (i._node.playbackRate = e);
var _ = o.seek(t[d]),
s = (o._sprite[i._sprite][0] + o._sprite[i._sprite][1]) / 1e3 - _,
l = 1e3 * s / Math.abs(i._rate);
!o._endTimers[t[d]] && i._paused || (o._clearTimer(t[d]), o._endTimers[t[d]] = setTimeout(o._ended.bind(o, i), l)), o._emit("rate", i._id)
}
return o
},
seek: function () {
var e, t, o = this,
r = arguments;
if (0 === r.length) t = o._sounds[0]._id;
else if (1 === r.length) {
var a = o._getSoundIds(),
u = a.indexOf(r[0]);
u >= 0 ? t = parseInt(r[0], 10) : o._sounds.length && (t = o._sounds[0]._id, e = parseFloat(r[0]))
} else 2 === r.length && (e = parseFloat(r[0]), t = parseInt(r[1], 10));
if (void 0 === t) return o;
if ("loaded" !== o._state) return o._queue.push({
event: "seek",
action: function () {
o.seek.apply(o, r)
}
}), o;
var i = o._soundById(t);
if (i) {
if (!("number" == typeof e && e >= 0)) {
if (o._webAudio) {
var d = o.playing(t) ? n.ctx.currentTime - i._playStart : 0,
_ = i._rateSeek ? i._rateSeek - i._seek : 0;
return i._seek + (_ + d * Math.abs(i._rate))
}
return i._node.currentTime
}
var s = o.playing(t);
if (s && o.pause(t, !0), i._seek = e, i._ended = !1, o._clearTimer(t), s && o.play(t, !0), !o._webAudio && i._node && (i._node.currentTime = e), s && !o._webAudio) {
var l = function () {
o._playLock ? setTimeout(l, 0) : o._emit("seek", t)
};
setTimeout(l, 0)
} else o._emit("seek", t)
}
return o
},
playing: function (e) {
var n = this;
if ("number" == typeof e) {
var t = n._soundById(e);
return !!t && !t._paused
}
for (var o = 0; o < n._sounds.length; o++)
if (!n._sounds[o]._paused) return !0;
return !1
},
duration: function (e) {
var n = this,
t = n._duration,
o = n._soundById(e);
return o && (t = n._sprite[o._sprite][1] / 1e3), t
},
state: function () {
return this._state
},
unload: function () {
for (var e = this, t = e._sounds, o = 0; o < t.length; o++) {
if (t[o]._paused || e.stop(t[o]._id), !e._webAudio) {
/MSIE |Trident\//.test(n._navigator && n._navigator.userAgent) || (t[o]._node.src = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"), t[o]._node.removeEventListener("error", t[o]._errorFn, !1), t[o]._node.removeEventListener(n._canPlayEvent, t[o]._loadFn, !1)
}
delete t[o]._node, e._clearTimer(t[o]._id);
var a = n._howls.indexOf(e);
a >= 0 && n._howls.splice(a, 1)
}
var u = !0;
for (o = 0; o < n._howls.length; o++)
if (n._howls[o]._src === e._src) {
u = !1;
break
}
return r && u && delete r[e._src], n.noAudio = !1, e._state = "unloaded", e._sounds = [], e = null, null
},
on: function (e, n, t, o) {
var r = this,
a = r["_on" + e];
return "function" == typeof n && a.push(o ? {
id: t,
fn: n,
once: o
} : {
id: t,
fn: n
}), r
},
off: function (e, n, t) {
var o = this,
r = o["_on" + e],
a = 0;
if ("number" == typeof n && (t = n, n = null), n || t)
for (a = 0; a < r.length; a++) {
var u = t === r[a].id;
if (n === r[a].fn && u || !n && u) {
r.splice(a, 1);
break
}
} else if (e) o["_on" + e] = [];
else {
var i = Object.keys(o);
for (a = 0; a < i.length; a++) 0 === i[a].indexOf("_on") && Array.isArray(o[i[a]]) && (o[i[a]] = [])
}
return o
},
once: function (e, n, t) {
var o = this;
return o.on(e, n, t, 1), o
},
_emit: function (e, n, t) {
for (var o = this, r = o["_on" + e], a = r.length - 1; a >= 0; a--) r[a].id && r[a].id !== n && "load" !== e || (setTimeout(function (e) {
e.call(this, n, t)
}.bind(o, r[a].fn), 0), r[a].once && o.off(e, r[a].fn, r[a].id));
return o._loadQueue(e), o
},
_loadQueue: function (e) {
var n = this;
if (n._queue.length > 0) {
var t = n._queue[0];
t.event === e && (n._queue.shift(), n._loadQueue()), e || t.action()
}
return n
},
_ended: function (e) {
var t = this,
o = e._sprite;
if (!t._webAudio && e._node && !e._node.paused && !e._node.ended && e._node.currentTime < e._stop) return setTimeout(t._ended.bind(t, e), 100), t;
var r = !(!e._loop && !t._sprite[o][2]);
if (t._emit("end", e._id), !t._webAudio && r && t.stop(e._id, !0).play(e._id), t._webAudio && r) {
t._emit("play", e._id), e._seek = e._start || 0, e._rateSeek = 0, e._playStart = n.ctx.currentTime;
var a = 1e3 * (e._stop - e._start) / Math.abs(e._rate);
t._endTimers[e._id] = setTimeout(t._ended.bind(t, e), a)
}
return t._webAudio && !r && (e._paused = !0, e._ended = !0, e._seek = e._start || 0, e._rateSeek = 0, t._clearTimer(e._id), t._cleanBuffer(e._node), n._autoSuspend()), t._webAudio || r || t.stop(e._id), t
},
_clearTimer: function (e) {
var n = this;
if (n._endTimers[e]) {
if ("function" != typeof n._endTimers[e]) clearTimeout(n._endTimers[e]);
else {
var t = n._soundById(e);
t && t._node && t._node.removeEventListener("ended", n._endTimers[e], !1)
}
delete n._endTimers[e]
}
return n
},
_soundById: function (e) {
for (var n = this, t = 0; t < n._sounds.length; t++)
if (e === n._sounds[t]._id) return n._sounds[t];
return null
},
_inactiveSound: function () {
var e = this;
e._drain();
for (var n = 0; n < e._sounds.length; n++)
if (e._sounds[n]._ended) return e._sounds[n].reset();
return new o(e)
},
_drain: function () {
var e = this,
n = e._pool,
t = 0,
o = 0;
if (!(e._sounds.length < n)) {
for (o = 0; o < e._sounds.length; o++) e._sounds[o]._ended && t++;
for (o = e._sounds.length - 1; o >= 0; o--) {
if (t <= n) return;
e._sounds[o]._ended && (e._webAudio && e._sounds[o]._node && e._sounds[o]._node.disconnect(0), e._sounds.splice(o, 1), t--)
}
}
},
_getSoundIds: function (e) {
var n = this;
if (void 0 === e) {
for (var t = [], o = 0; o < n._sounds.length; o++) t.push(n._sounds[o]._id);
return t
}
return [e]
},
_refreshBuffer: function (e) {
var t = this;
return e._node.bufferSource = n.ctx.createBufferSource(), e._node.bufferSource.buffer = r[t._src], e._panner ? e._node.bufferSource.connect(e._panner) : e._node.bufferSource.connect(e._node), e._node.bufferSource.loop = e._loop, e._loop && (e._node.bufferSource.loopStart = e._start || 0, e._node.bufferSource.loopEnd = e._stop), e._node.bufferSource.playbackRate.setValueAtTime(e._rate, n.ctx.currentTime), t
},
_cleanBuffer: function (e) {
var t = this;
if (n._scratchBuffer) {
e.bufferSource.onended = null, e.bufferSource.disconnect(0);
try {
e.bufferSource.buffer = n._scratchBuffer
} catch (e) {}
}
return e.bufferSource = null, t
}
};
var o = function (e) {
this._parent = e, this.init()
};
o.prototype = {
init: function () {
var e = this,
t = e._parent;
return e._muted = t._muted, e._loop = t._loop, e._volume = t._volume, e._rate = t._rate, e._seek = 0, e._paused = !0, e._ended = !0, e._sprite = "__default", e._id = ++n._counter, t._sounds.push(e), e.create(), e
},
create: function () {
var e = this,
t = e._parent,
o = n._muted || e._muted || e._parent._muted ? 0 : e._volume;
return t._webAudio ? (e._node = void 0 === n.ctx.createGain ? n.ctx.createGainNode() : n.ctx.createGain(), e._node.gain.setValueAtTime(o, n.ctx.currentTime), e._node.paused = !0, e._node.connect(n.masterGain)) : (e._node = new Audio, e._errorFn = e._errorListener.bind(e), e._node.addEventListener("error", e._errorFn, !1), e._loadFn = e._loadListener.bind(e), e._node.addEventListener(n._canPlayEvent, e._loadFn, !1), e._node.src = t._src, e._node.preload = "auto", e._node.volume = o * n.volume(), e._node.load()), e
},
reset: function () {
var e = this,
t = e._parent;
return e._muted = t._muted, e._loop = t._loop, e._volume = t._volume, e._rate = t._rate, e._seek = 0, e._rateSeek = 0, e._paused = !0, e._ended = !0, e._sprite = "__default", e._id = ++n._counter, e
},
_errorListener: function () {
var e = this;
e._parent._emit("loaderror", e._id, e._node.error ? e._node.error.code : 0), e._node.removeEventListener("error", e._errorFn, !1)
},
_loadListener: function () {
var e = this,
t = e._parent;
t._duration = Math.ceil(10 * e._node.duration) / 10, 0 === Object.keys(t._sprite).length && (t._sprite = {
__default: [0, 1e3 * t._duration]
}), "loaded" !== t._state && (t._state = "loaded", t._emit("load"), t._loadQueue()), e._node.removeEventListener(n._canPlayEvent, e._loadFn, !1)
}
};
var r = {},
a = function (e) {
var n = e._src;
if (r[n]) return e._duration = r[n].duration, void d(e);
if (/^data:[^;]+;base64,/.test(n)) {
for (var t = atob(n.split(",")[1]), o = new Uint8Array(t.length), a = 0; a < t.length; ++a) o[a] = t.charCodeAt(a);
i(o.buffer, e)
} else {
var _ = new XMLHttpRequest;
_.open("GET", n, !0), _.withCredentials = e._xhrWithCredentials, _.responseType = "arraybuffer", _.onload = function () {
var n = (_.status + "")[0];
if ("0" !== n && "2" !== n && "3" !== n) return void e._emit("loaderror", null, "Failed loading audio file with status: " + _.status + ".");
i(_.response, e)
}, _.onerror = function () {
e._webAudio && (e._html5 = !0, e._webAudio = !1, e._sounds = [], delete r[n], e.load())
}, u(_)
}
},
u = function (e) {
try {
e.send()
} catch (n) {
e.onerror()
}
},
i = function (e, t) {
n.ctx.decodeAudioData(e, function (e) {
e && t._sounds.length > 0 && (r[t._src] = e, d(t, e))
}, function () {
t._emit("loaderror", null, "Decoding audio data failed.")
})
},
d = function (e, n) {
n && !e._duration && (e._duration = n.duration), 0 === Object.keys(e._sprite).length && (e._sprite = {
__default: [0, 1e3 * e._duration]
}), "loaded" !== e._state && (e._state = "loaded", e._emit("load"), e._loadQueue())
},
_ = function () {
try {
"undefined" != typeof AudioContext ? n.ctx = new AudioContext : "undefined" != typeof webkitAudioContext ? n.ctx = new webkitAudioContext : n.usingWebAudio = !1
} catch (e) {
n.usingWebAudio = !1
}
var e = /iP(hone|od|ad)/.test(n._navigator && n._navigator.platform),
t = n._navigator && n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),
o = t ? parseInt(t[1], 10) : null;
if (e && o && o < 9) {
var r = /safari/.test(n._navigator && n._navigator.userAgent.toLowerCase());
(n._navigator && n._navigator.standalone && !r || n._navigator && !n._navigator.standalone && !r) && (n.usingWebAudio = !1)
}
n.usingWebAudio && (n.masterGain = void 0 === n.ctx.createGain ? n.ctx.createGainNode() : n.ctx.createGain(), n.masterGain.gain.setValueAtTime(n._muted ? 0 : 1, n.ctx.currentTime), n.masterGain.connect(n.ctx.destination)), n._setup()
};
"function" == typeof define && define.amd && define([], function () {
return {
Howler: n,
Howl: t
}
}), "undefined" != typeof exports && (exports.Howler = n, exports.Howl = t), "undefined" != typeof window ? (window.HowlerGlobal = e, window.Howler = n, window.Howl = t, window.Sound = o) : "undefined" != typeof global && (global.HowlerGlobal = e, global.Howler = n, global.Howl = t, global.Sound = o)
}();
/*! Spatial Plugin */
! function () {
"use strict";
HowlerGlobal.prototype._pos = [0, 0, 0], HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0], HowlerGlobal.prototype.stereo = function (n) {
var e = this;
if (!e.ctx || !e.ctx.listener) return e;
for (var t = e._howls.length - 1; t >= 0; t--) e._howls[t].stereo(n);
return e
}, HowlerGlobal.prototype.pos = function (n, e, t) {
var o = this;
return o.ctx && o.ctx.listener ? (e = "number" != typeof e ? o._pos[1] : e, t = "number" != typeof t ? o._pos[2] : t, "number" != typeof n ? o._pos : (o._pos = [n, e, t], o.ctx.listener.setPosition(o._pos[0], o._pos[1], o._pos[2]), o)) : o
}, HowlerGlobal.prototype.orientation = function (n, e, t, o, r, a) {
var i = this;
if (!i.ctx || !i.ctx.listener) return i;
var p = i._orientation;
return e = "number" != typeof e ? p[1] : e, t = "number" != typeof t ? p[2] : t, o = "number" != typeof o ? p[3] : o, r = "number" != typeof r ? p[4] : r, a = "number" != typeof a ? p[5] : a, "number" != typeof n ? p : (i._orientation = [n, e, t, o, r, a], i.ctx.listener.setOrientation(n, e, t, o, r, a), i)
}, Howl.prototype.init = function (n) {
return function (e) {
var t = this;
return t._orientation = e.orientation || [1, 0, 0], t._stereo = e.stereo || null, t._pos = e.pos || null, t._pannerAttr = {
coneInnerAngle: void 0 !== e.coneInnerAngle ? e.coneInnerAngle : 360,
coneOuterAngle: void 0 !== e.coneOuterAngle ? e.coneOuterAngle : 360,
coneOuterGain: void 0 !== e.coneOuterGain ? e.coneOuterGain : 0,
distanceModel: void 0 !== e.distanceModel ? e.distanceModel : "inverse",
maxDistance: void 0 !== e.maxDistance ? e.maxDistance : 1e4,
panningModel: void 0 !== e.panningModel ? e.panningModel : "HRTF",
refDistance: void 0 !== e.refDistance ? e.refDistance : 1,
rolloffFactor: void 0 !== e.rolloffFactor ? e.rolloffFactor : 1
}, t._onstereo = e.onstereo ? [{
fn: e.onstereo
}] : [], t._onpos = e.onpos ? [{
fn: e.onpos
}] : [], t._onorientation = e.onorientation ? [{
fn: e.onorientation
}] : [], n.call(this, e)
}
}(Howl.prototype.init), Howl.prototype.stereo = function (e, t) {
var o = this;
if (!o._webAudio) return o;
if ("loaded" !== o._state) return o._queue.push({
event: "stereo",
action: function () {
o.stereo(e, t)
}
}), o;
var r = void 0 === Howler.ctx.createStereoPanner ? "spatial" : "stereo";
if (void 0 === t) {
if ("number" != typeof e) return o._stereo;
o._stereo = e, o._pos = [e, 0, 0]
}
for (var a = o._getSoundIds(t), i = 0; i < a.length; i++) {
var p = o._soundById(a[i]);
if (p) {
if ("number" != typeof e) return p._stereo;
p._stereo = e, p._pos = [e, 0, 0], p._node && (p._pannerAttr.panningModel = "equalpower", p._panner && p._panner.pan || n(p, r), "spatial" === r ? p._panner.setPosition(e, 0, 0) : p._panner.pan.setValueAtTime(e, Howler.ctx.currentTime)), o._emit("stereo", p._id)
}
}
return o
}, Howl.prototype.pos = function (e, t, o, r) {
var a = this;
if (!a._webAudio) return a;
if ("loaded" !== a._state) return a._queue.push({
event: "pos",
action: function () {
a.pos(e, t, o, r)
}
}), a;
if (t = "number" != typeof t ? 0 : t, o = "number" != typeof o ? -.5 : o, void 0 === r) {
if ("number" != typeof e) return a._pos;
a._pos = [e, t, o]
}
for (var i = a._getSoundIds(r), p = 0; p < i.length; p++) {
var s = a._soundById(i[p]);
if (s) {
if ("number" != typeof e) return s._pos;
s._pos = [e, t, o], s._node && (s._panner && !s._panner.pan || n(s, "spatial"), s._panner.setPosition(e, t, o)), a._emit("pos", s._id)
}
}
return a
}, Howl.prototype.orientation = function (e, t, o, r) {
var a = this;
if (!a._webAudio) return a;
if ("loaded" !== a._state) return a._queue.push({
event: "orientation",
action: function () {
a.orientation(e, t, o, r)
}
}), a;
if (t = "number" != typeof t ? a._orientation[1] : t, o = "number" != typeof o ? a._orientation[2] : o, void 0 === r) {
if ("number" != typeof e) return a._orientation;
a._orientation = [e, t, o]
}
for (var i = a._getSoundIds(r), p = 0; p < i.length; p++) {
var s = a._soundById(i[p]);
if (s) {
if ("number" != typeof e) return s._orientation;
s._orientation = [e, t, o], s._node && (s._panner || (s._pos || (s._pos = a._pos || [0, 0, -.5]), n(s, "spatial")), s._panner.setOrientation(e, t, o)), a._emit("orientation", s._id)
}
}
return a
}, Howl.prototype.pannerAttr = function () {
var e, t, o, r = this,
a = arguments;
if (!r._webAudio) return r;
if (0 === a.length) return r._pannerAttr;
if (1 === a.length) {
if ("object" != typeof a[0]) return o = r._soundById(parseInt(a[0], 10)), o ? o._pannerAttr : r._pannerAttr;
e = a[0], void 0 === t && (e.pannerAttr || (e.pannerAttr = {
coneInnerAngle: e.coneInnerAngle,
coneOuterAngle: e.coneOuterAngle,
coneOuterGain: e.coneOuterGain,
distanceModel: e.distanceModel,
maxDistance: e.maxDistance,
refDistance: e.refDistance,
rolloffFactor: e.rolloffFactor,
panningModel: e.panningModel
}), r._pannerAttr = {
coneInnerAngle: void 0 !== e.pannerAttr.coneInnerAngle ? e.pannerAttr.coneInnerAngle : r._coneInnerAngle,
coneOuterAngle: void 0 !== e.pannerAttr.coneOuterAngle ? e.pannerAttr.coneOuterAngle : r._coneOuterAngle,
coneOuterGain: void 0 !== e.pannerAttr.coneOuterGain ? e.pannerAttr.coneOuterGain : r._coneOuterGain,
distanceModel: void 0 !== e.pannerAttr.distanceModel ? e.pannerAttr.distanceModel : r._distanceModel,
maxDistance: void 0 !== e.pannerAttr.maxDistance ? e.pannerAttr.maxDistance : r._maxDistance,
refDistance: void 0 !== e.pannerAttr.refDistance ? e.pannerAttr.refDistance : r._refDistance,
rolloffFactor: void 0 !== e.pannerAttr.rolloffFactor ? e.pannerAttr.rolloffFactor : r._rolloffFactor,
panningModel: void 0 !== e.pannerAttr.panningModel ? e.pannerAttr.panningModel : r._panningModel
})
} else 2 === a.length && (e = a[0], t = parseInt(a[1], 10));
for (var i = r._getSoundIds(t), p = 0; p < i.length; p++)
if (o = r._soundById(i[p])) {
var s = o._pannerAttr;
s = {
coneInnerAngle: void 0 !== e.coneInnerAngle ? e.coneInnerAngle : s.coneInnerAngle,
coneOuterAngle: void 0 !== e.coneOuterAngle ? e.coneOuterAngle : s.coneOuterAngle,
coneOuterGain: void 0 !== e.coneOuterGain ? e.coneOuterGain : s.coneOuterGain,
distanceModel: void 0 !== e.distanceModel ? e.distanceModel : s.distanceModel,
maxDistance: void 0 !== e.maxDistance ? e.maxDistance : s.maxDistance,
refDistance: void 0 !== e.refDistance ? e.refDistance : s.refDistance,
rolloffFactor: void 0 !== e.rolloffFactor ? e.rolloffFactor : s.rolloffFactor,
panningModel: void 0 !== e.panningModel ? e.panningModel : s.panningModel
};
var l = o._panner;
l ? (l.coneInnerAngle = s.coneInnerAngle, l.coneOuterAngle = s.coneOuterAngle, l.coneOuterGain = s.coneOuterGain, l.distanceModel = s.distanceModel, l.maxDistance = s.maxDistance, l.refDistance = s.refDistance, l.rolloffFactor = s.rolloffFactor, l.panningModel = s.panningModel) : (o._pos || (o._pos = r._pos || [0, 0, -.5]), n(o, "spatial"))
}
return r
}, Sound.prototype.init = function (n) {
return function () {
var e = this,
t = e._parent;
e._orientation = t._orientation, e._stereo = t._stereo, e._pos = t._pos, e._pannerAttr = t._pannerAttr, n.call(this), e._stereo ? t.stereo(e._stereo) : e._pos && t.pos(e._pos[0], e._pos[1], e._pos[2], e._id)
}
}(Sound.prototype.init), Sound.prototype.reset = function (n) {
return function () {
var e = this,
t = e._parent;
return e._orientation = t._orientation, e._pos = t._pos, e._pannerAttr = t._pannerAttr, n.call(this)
}
}(Sound.prototype.reset);
var n = function (n, e) {
e = e || "spatial", "spatial" === e ? (n._panner = Howler.ctx.createPanner(), n._panner.coneInnerAngle = n._pannerAttr.coneInnerAngle, n._panner.coneOuterAngle = n._pannerAttr.coneOuterAngle, n._panner.coneOuterGain = n._pannerAttr.coneOuterGain, n._panner.distanceModel = n._pannerAttr.distanceModel, n._panner.maxDistance = n._pannerAttr.maxDistance, n._panner.refDistance = n._pannerAttr.refDistance, n._panner.rolloffFactor = n._pannerAttr.rolloffFactor, n._panner.panningModel = n._pannerAttr.panningModel, n._panner.setPosition(n._pos[0], n._pos[1], n._pos[2]), n._panner.setOrientation(n._orientation[0], n._orientation[1], n._orientation[2])) : (n._panner = Howler.ctx.createStereoPanner(), n._panner.pan.setValueAtTime(n._stereo, Howler.ctx.currentTime)), n._panner.connect(n._node), n._paused || n._parent.pause(n._id, !0).play(n._id, !0)
}
}();

+ 160
- 0
src/client/plugins/howler.spatial.min.js View File

@@ -0,0 +1,160 @@
/*! howler.js v2.0.9 | Spatial Plugin | (c) 2013-2018, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ ! function () {
"use strict";
HowlerGlobal.prototype._pos = [0, 0, 0], HowlerGlobal.prototype._orientation = [0, 0, -1, 0, 1, 0], HowlerGlobal.prototype.stereo = function (n) {
var e = this;
if (!e.ctx || !e.ctx.listener) return e;
for (var t = e._howls.length - 1; t >= 0; t--) e._howls[t].stereo(n);
return e
}, HowlerGlobal.prototype.pos = function (n, e, t) {
var o = this;
return o.ctx && o.ctx.listener ? (e = "number" != typeof e ? o._pos[1] : e, t = "number" != typeof t ? o._pos[2] : t, "number" != typeof n ? o._pos : (o._pos = [n, e, t], o.ctx.listener.setPosition(o._pos[0], o._pos[1], o._pos[2]), o)) : o
}, HowlerGlobal.prototype.orientation = function (n, e, t, o, r, a) {
var i = this;
if (!i.ctx || !i.ctx.listener) return i;
var p = i._orientation;
return e = "number" != typeof e ? p[1] : e, t = "number" != typeof t ? p[2] : t, o = "number" != typeof o ? p[3] : o, r = "number" != typeof r ? p[4] : r, a = "number" != typeof a ? p[5] : a, "number" != typeof n ? p : (i._orientation = [n, e, t, o, r, a], i.ctx.listener.setOrientation(n, e, t, o, r, a), i)
}, Howl.prototype.init = function (n) {
return function (e) {
var t = this;
return t._orientation = e.orientation || [1, 0, 0], t._stereo = e.stereo || null, t._pos = e.pos || null, t._pannerAttr = {
coneInnerAngle: void 0 !== e.coneInnerAngle ? e.coneInnerAngle : 360,
coneOuterAngle: void 0 !== e.coneOuterAngle ? e.coneOuterAngle : 360,
coneOuterGain: void 0 !== e.coneOuterGain ? e.coneOuterGain : 0,
distanceModel: void 0 !== e.distanceModel ? e.distanceModel : "inverse",
maxDistance: void 0 !== e.maxDistance ? e.maxDistance : 1e4,
panningModel: void 0 !== e.panningModel ? e.panningModel : "HRTF",
refDistance: void 0 !== e.refDistance ? e.refDistance : 1,
rolloffFactor: void 0 !== e.rolloffFactor ? e.rolloffFactor : 1
}, t._onstereo = e.onstereo ? [{
fn: e.onstereo
}] : [], t._onpos = e.onpos ? [{
fn: e.onpos
}] : [], t._onorientation = e.onorientation ? [{
fn: e.onorientation
}] : [], n.call(this, e)
}
}(Howl.prototype.init), Howl.prototype.stereo = function (e, t) {
var o = this;
if (!o._webAudio) return o;
if ("loaded" !== o._state) return o._queue.push({
event: "stereo",
action: function () {
o.stereo(e, t)
}
}), o;
var r = void 0 === Howler.ctx.createStereoPanner ? "spatial" : "stereo";
if (void 0 === t) {
if ("number" != typeof e) return o._stereo;
o._stereo = e, o._pos = [e, 0, 0]
}
for (var a = o._getSoundIds(t), i = 0; i < a.length; i++) {
var p = o._soundById(a[i]);
if (p) {
if ("number" != typeof e) return p._stereo;
p._stereo = e, p._pos = [e, 0, 0], p._node && (p._pannerAttr.panningModel = "equalpower", p._panner && p._panner.pan || n(p, r), "spatial" === r ? p._panner.setPosition(e, 0, 0) : p._panner.pan.setValueAtTime(e, Howler.ctx.currentTime)), o._emit("stereo", p._id)
}
}
return o
}, Howl.prototype.pos = function (e, t, o, r) {
var a = this;
if (!a._webAudio) return a;
if ("loaded" !== a._state) return a._queue.push({
event: "pos",
action: function () {
a.pos(e, t, o, r)
}
}), a;
if (t = "number" != typeof t ? 0 : t, o = "number" != typeof o ? -.5 : o, void 0 === r) {
if ("number" != typeof e) return a._pos;
a._pos = [e, t, o]
}
for (var i = a._getSoundIds(r), p = 0; p < i.length; p++) {
var s = a._soundById(i[p]);
if (s) {
if ("number" != typeof e) return s._pos;
s._pos = [e, t, o], s._node && (s._panner && !s._panner.pan || n(s, "spatial"), s._panner.setPosition(e, t, o)), a._emit("pos", s._id)
}
}
return a
}, Howl.prototype.orientation = function (e, t, o, r) {
var a = this;
if (!a._webAudio) return a;
if ("loaded" !== a._state) return a._queue.push({
event: "orientation",
action: function () {
a.orientation(e, t, o, r)
}
}), a;
if (t = "number" != typeof t ? a._orientation[1] : t, o = "number" != typeof o ? a._orientation[2] : o, void 0 === r) {
if ("number" != typeof e) return a._orientation;
a._orientation = [e, t, o]
}
for (var i = a._getSoundIds(r), p = 0; p < i.length; p++) {
var s = a._soundById(i[p]);
if (s) {
if ("number" != typeof e) return s._orientation;
s._orientation = [e, t, o], s._node && (s._panner || (s._pos || (s._pos = a._pos || [0, 0, -.5]), n(s, "spatial")), s._panner.setOrientation(e, t, o)), a._emit("orientation", s._id)
}
}
return a
}, Howl.prototype.pannerAttr = function () {
var e, t, o, r = this,
a = arguments;
if (!r._webAudio) return r;
if (0 === a.length) return r._pannerAttr;
if (1 === a.length) {
if ("object" != typeof a[0]) return o = r._soundById(parseInt(a[0], 10)), o ? o._pannerAttr : r._pannerAttr;
e = a[0], void 0 === t && (e.pannerAttr || (e.pannerAttr = {
coneInnerAngle: e.coneInnerAngle,
coneOuterAngle: e.coneOuterAngle,
coneOuterGain: e.coneOuterGain,
distanceModel: e.distanceModel,
maxDistance: e.maxDistance,
refDistance: e.refDistance,
rolloffFactor: e.rolloffFactor,
panningModel: e.panningModel
}), r._pannerAttr = {
coneInnerAngle: void 0 !== e.pannerAttr.coneInnerAngle ? e.pannerAttr.coneInnerAngle : r._coneInnerAngle,
coneOuterAngle: void 0 !== e.pannerAttr.coneOuterAngle ? e.pannerAttr.coneOuterAngle : r._coneOuterAngle,
coneOuterGain: void 0 !== e.pannerAttr.coneOuterGain ? e.pannerAttr.coneOuterGain : r._coneOuterGain,
distanceModel: void 0 !== e.pannerAttr.distanceModel ? e.pannerAttr.distanceModel : r._distanceModel,
maxDistance: void 0 !== e.pannerAttr.maxDistance ? e.pannerAttr.maxDistance : r._maxDistance,
refDistance: void 0 !== e.pannerAttr.refDistance ? e.pannerAttr.refDistance : r._refDistance,
rolloffFactor: void 0 !== e.pannerAttr.rolloffFactor ? e.pannerAttr.rolloffFactor : r._rolloffFactor,
panningModel: void 0 !== e.pannerAttr.panningModel ? e.pannerAttr.panningModel : r._panningModel
})
} else 2 === a.length && (e = a[0], t = parseInt(a[1], 10));
for (var i = r._getSoundIds(t), p = 0; p < i.length; p++)
if (o = r._soundById(i[p])) {
var s = o._pannerAttr;
s = {
coneInnerAngle: void 0 !== e.coneInnerAngle ? e.coneInnerAngle : s.coneInnerAngle,
coneOuterAngle: void 0 !== e.coneOuterAngle ? e.coneOuterAngle : s.coneOuterAngle,
coneOuterGain: void 0 !== e.coneOuterGain ? e.coneOuterGain : s.coneOuterGain,
distanceModel: void 0 !== e.distanceModel ? e.distanceModel : s.distanceModel,
maxDistance: void 0 !== e.maxDistance ? e.maxDistance : s.maxDistance,
refDistance: void 0 !== e.refDistance ? e.refDistance : s.refDistance,
rolloffFactor: void 0 !== e.rolloffFactor ? e.rolloffFactor : s.rolloffFactor,
panningModel: void 0 !== e.panningModel ? e.panningModel : s.panningModel
};
var l = o._panner;
l ? (l.coneInnerAngle = s.coneInnerAngle, l.coneOuterAngle = s.coneOuterAngle, l.coneOuterGain = s.coneOuterGain, l.distanceModel = s.distanceModel, l.maxDistance = s.maxDistance, l.refDistance = s.refDistance, l.rolloffFactor = s.rolloffFactor, l.panningModel = s.panningModel) : (o._pos || (o._pos = r._pos || [0, 0, -.5]), n(o, "spatial"))
}
return r
}, Sound.prototype.init = function (n) {
return function () {
var e = this,
t = e._parent;
e._orientation = t._orientation, e._stereo = t._stereo, e._pos = t._pos, e._pannerAttr = t._pannerAttr, n.call(this), e._stereo ? t.stereo(e._stereo) : e._pos && t.pos(e._pos[0], e._pos[1], e._pos[2], e._id)
}
}(Sound.prototype.init), Sound.prototype.reset = function (n) {
return function () {
var e = this,
t = e._parent;
return e._orientation = t._orientation, e._pos = t._pos, e._pannerAttr = t._pannerAttr, n.call(this)
}
}(Sound.prototype.reset);
var n = function (n, e) {
e = e || "spatial", "spatial" === e ? (n._panner = Howler.ctx.createPanner(), n._panner.coneInnerAngle = n._pannerAttr.coneInnerAngle, n._panner.coneOuterAngle = n._pannerAttr.coneOuterAngle, n._panner.coneOuterGain = n._pannerAttr.coneOuterGain, n._panner.distanceModel = n._pannerAttr.distanceModel, n._panner.maxDistance = n._pannerAttr.maxDistance, n._panner.refDistance = n._pannerAttr.refDistance, n._panner.rolloffFactor = n._pannerAttr.rolloffFactor, n._panner.panningModel = n._pannerAttr.panningModel, n._panner.setPosition(n._pos[0], n._pos[1], n._pos[2]), n._panner.setOrientation(n._orientation[0], n._orientation[1], n._orientation[2])) : (n._panner = Howler.ctx.createStereoPanner(), n._panner.pan.setValueAtTime(n._stereo, Howler.ctx.currentTime)), n._panner.connect(n._node), n._paused || n._parent.pause(n._id, !0).play(n._id, !0)
}
}();

+ 1
- 1
src/client/ui/factory.js View File

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

events.on('onEnterGame', this.onEnterGame.bind(this));
events.on('onKeyDown', this.onKeyDown.bind(this));
events.on('onResize', this.onResize.bind(this));
},
onEnterGame: function () {
events.clearQueue();
@@ -64,7 +65,6 @@ define([
return;

this.getTemplate(type, options);
$(window).on('resize', this.onResize.bind(this));
},
getTemplate: function (type, options) {
require([this.root + 'ui/templates/' + type + '/' + type], this.onGetTemplate.bind(this, options));


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

@@ -82,7 +82,7 @@ define([
.forEach(function (c, i) {
var name = c.name;
if (c.level != null)
name += '<font class="q2"> (' + c.level + ')</font>'
name += '<font class="color-yellowB"> (' + c.level + ')</font>'

var html = templateListItem
.replace('$NAME$', name);


+ 0
- 1
src/client/ui/templates/createCharacter/createCharacter.js View File

@@ -167,7 +167,6 @@ define([
var el = $(e.target);
var classes = ['owl', 'bear', 'lynx'];
var nextIndex = (classes.indexOf(this.class) + 1) % classes.length;
this.costume = -1;

var newClass = classes[nextIndex];



+ 28
- 9
src/client/ui/templates/death/death.js View File

@@ -3,7 +3,7 @@ define([
'js/system/client',
'html!ui/templates/death/template',
'css!ui/templates/death/styles'
], function(
], function (
events,
client,
template,
@@ -15,7 +15,7 @@ define([
modal: true,
centered: true,

postRender: function() {
postRender: function () {
this.onEvent('onDeath', this.onDeath.bind(this));
this.onEvent('onPermadeath', this.onPermadeath.bind(this));

@@ -23,30 +23,49 @@ define([
this.find('.btn-respawn').on('click', this.onRespawn.bind(this));
},

onLogout: function() {
onLogout: function () {
$('.uiOptions').data('ui').charSelect();
},

onRespawn: function() {
onRespawn: function () {
events.emit('onHideOverlay', this.el);
this.hide();

client.request({
cpn: 'player',
method: 'performAction',
data: {
cpn: 'stats',
method: 'respawn'
}
});
},

doShow: function() {
doShow: function () {
this.show();
events.emit('onShowOverlay', this.el);
},

onDeath: function(event) {
this.find('.msg').html('you were killed by [ <div class="inner">' + event.source + '</div> ]');
onDeath: function (event) {
if (!event.source) {
this.find('.msg').html('you are dead');
} else
this.find('.msg').html('you were killed by [ <div class="inner">' + event.source + '</div> ]');
this.find('.penalty')
.html('you lost ' + event.xpLoss + ' experience')
.show();

if (!event.xpLoss)
this.find('.penalty').hide();

this.el.removeClass('permadeath');
this.doShow();
},

onPermadeath: function(event) {
onPermadeath: function (event) {
this.find('.msg').html('you were killed by [ <div class="inner">' + event.source + '</div> ]');
this.el.addClass('permadeath');
this.doShow();
}
};
});
});

+ 6
- 1
src/client/ui/templates/death/styles.less View File

@@ -18,6 +18,11 @@
}
}

.penalty {
color: @yellowB;
margin-top: 15px;
}

.btn {
color: @white;
width: 100%;
@@ -50,4 +55,4 @@
display: none;
}
}
}
}

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

@@ -1,7 +1,8 @@
<div class="uiDeath">
<div class="msg"></div>
<div class="penalty"></div>
<div class="buttons">
<div class="btn btn-respawn">respawn</div>
<div class="btn btn-logout">log out</div>
</div>
</div>
</div>

+ 10
- 3
src/client/ui/templates/equipment/equipment.js View File

@@ -336,8 +336,12 @@ define([
vit: stats.vit
},
offense: {
'crit chance': (~~(stats.critChance * 10) / 10) + '%',
'crit multiplier': (~~(stats.critMultiplier * 10) / 10) + '%',
'global crit chance': (~~(stats.critChance * 10) / 10) + '%',
'global crit multiplier': (~~(stats.critMultiplier * 10) / 10) + '%',
'attack crit chance': (~~((stats.critChance + stats.attackCritChance) * 10) / 10) + '%',
'attack crit multiplier': (~~((stats.critMultiplier + stats.attackCritMultiplier) * 10) / 10) + '%',
'spell crit chance': (~~((stats.critChance + stats.spellCritChance) * 10) / 10) + '%',
'spell crit multiplier': (~~((stats.critMultiplier + stats.spellCritMultiplier) * 10) / 10) + '%',
gap1: '',
'arcane increase': stats.elementArcanePercent + '%',
'fire increase': stats.elementFirePercent + '%',
@@ -355,12 +359,15 @@ define([
'chance to block attacks': stats.blockAttackChance + '%',
'chance to block spells': stats.blockSpellChance + '%',
gap1: '',
'chance to dodge attacks': (~~(stats.dodgeAttackChance * 10) / 10) + '%',
'chance to dodge spells': (~~(stats.dodgeSpellChance * 10) / 10) + '%',
gap2: '',
'arcane resist': stats.elementArcaneResist,
'fire resist': stats.elementFireResist,
'frost resist': stats.elementFrostResist,
'holy resist': stats.elementHolyResist,
'poison resist': stats.elementPoisonResist,
gap2: '',
gap3: '',
'all resist': stats.elementAllResist
},
misc: {


+ 2
- 2
src/client/ui/templates/help/template.html View File

@@ -5,7 +5,7 @@
<div class="row"><span class="topic">Chat: </span><br />Press Enter to open the chat window</div>
<div class="row"><span class="topic">Inventory: </span><br />Press i to open your inventory</div>
<div class="row"><span class="topic">Equipment: </span><br />Right click an item in your inventory to equip it or press j for the equipment panel</div>
<div class="row"><span class="topic">Stats: </span><br />Wizards and Clerics need Int to deal more damage. Thieves need Dex and Warriors need Str. Necromancers need Int and Str</div>
<div class="row"><span class="topic">Stats: </span><br />Owl Spirits need Int to deal more damage. Lynxes need Dex and Bears need Str.</div>
<div class="row"><span class="topic">Show Nameplates: </span><br />V</div>
<div class="row"><span class="topic">Who's Online: </span><br />O</div>
</div>
</div>

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

@@ -54,7 +54,7 @@ define([
boxes.eq(1).find('.text').html(Math.floor(stats.mana) + '/' + ~~stats.manaMax);

var level = stats.level;
if (stats.originalLevel)
if ((stats.originalLevel) && (stats.originalLevel != level))
level = stats.originalLevel + ' (' + stats.level + ')';

boxes.eq(2).find('.text').html('level: ' + level);


+ 93
- 16
src/client/ui/templates/inventory/inventory.js View File

@@ -66,9 +66,14 @@ define([
.on('mousemove', this.onMouseMove.bind(this))
.on('mouseleave', this.onMouseDown.bind(this, null, null, false));

this.find('.split-box .amount').on('mousewheel', this.onChangeStackAmount.bind(this));
this.find('.split-box .amount')
.on('mousewheel', this.onChangeStackAmount.bind(this))
.on('input', this.onEnterStackAmount.bind(this));

this.find('.split-box').on('click', this.splitStackEnd.bind(this, true));
this.find('.split-box .button').on('click', this.splitStackEnd.bind(this));
this.find('.split-box .btnSplit').on('click', this.splitStackEnd.bind(this, null));
this.find('.split-box .btnLess').on('click', this.onChangeStackAmount.bind(this, null, -1));
this.find('.split-box .btnMore').on('click', this.onChangeStackAmount.bind(this, null, 1));
},

build: function () {
@@ -367,14 +372,20 @@ define([
var box = this.find('.split-box').show();
box.data('item', item);

box.find('.amount').html(1);
box.find('.amount')
.val('1')
.focus();
},

splitStackEnd: function (cancel, e) {
var box = this.find('.split-box');

if ((!e) || (e.target != box.find('.button')[0]))
if ((cancel) || (!e) || (e.target != box.find('.btnSplit')[0])) {
if ((cancel) && (!$(e.target).hasClass('button')))
box.hide();

return;
}

box.hide();

@@ -386,20 +397,36 @@ define([
method: 'splitStack',
data: {
itemId: box.data('item').id,
stackSize: ~~this.find('.split-box .amount').html()
stackSize: ~~this.find('.split-box .amount').val()
}
}
});
},

onChangeStackAmount: function (e) {
onChangeStackAmount: function (e, amount) {
var item = this.find('.split-box').data('item');
var delta = (e.originalEvent.deltaY > 0) ? -1 : 1;
var delta = e ? ((e.originalEvent.deltaY > 0) ? -1 : 1) : amount;
if (this.shiftDown)
delta *= 10;
var amount = this.find('.split-box .amount');

amount.html(Math.max(1, Math.min(item.quantity - 1, ~~amount.html() + delta)));
amount.val(Math.max(1, Math.min(item.quantity - 1, ~~amount.val() + delta)));
},

onEnterStackAmount: function (e) {
var el = this.find('.split-box .amount');
var val = el.val();
if (val != ~~val)
el.val('');
else if (val) {
var item = this.find('.split-box').data('item');
if (val < 0)
val = '';
else if (val > item.quantity - 1)
val = item.quantity - 1;

el.val(val);
}
},

hideTooltip: function () {
@@ -448,6 +475,62 @@ define([
compare = this.items.find(function (i) {
return ((i.eq) && (i.slot == item.slot));
});

// check special cases for mismatched weapon/offhand scenarios (only valid when comparing)
if ((!compare) && (this.shiftDown)) {
var equippedTwoHanded = this.items.find(function (i) {
return ((i.eq) && (i.slot == 'twoHanded'));
});

var equippedOneHanded = this.items.find(function (i) {
return ((i.eq) && (i.slot == 'oneHanded'));
});

var equippedOffhand = this.items.find(function (i) {
return ((i.eq) && (i.slot == 'offHand'));
});

if (item.slot == 'twoHanded') {
if (!equippedOneHanded) {
compare = equippedOffhand;
} else if (!equippedOffhand) {
compare = equippedOneHanded;
} else {
// compare against oneHanded and offHand combined by creating a virtual item that is the sum of the two
compare = $.extend(true, {}, equippedOneHanded);
compare.refItem = equippedOneHanded;

for (var s in equippedOffhand.stats) {
if (!compare.stats[s])
compare.stats[s] = 0;

compare.stats[s] += equippedOffhand.stats[s]
}
}
}

if (item.slot == 'oneHanded') {
compare = equippedTwoHanded;
}

// this case is kind of ugly, but we don't want to go in when comparing an offHand to (oneHanded + empty offHand) - that should just use the normal compare which is offHand to empty
if ((item.slot == 'offHand') && (equippedTwoHanded)) {
// since we're comparing an offhand to an equipped Twohander, we need to clone the 'spell' values over (setting damage to zero) so that we can properly display how much damage
// the player would lose by switching to the offhand (which would remove the twoHander)
// keep a reference to the original item for use in onHideToolTip
var spellClone = $.extend(true, {}, equippedTwoHanded.spell);
spellClone.name = '';
spellClone.values['damage'] = 0;

var clone = $.extend(true, {}, item, {
spell: spellClone
});
clone.refItem = item;
item = clone;

compare = equippedTwoHanded;
}
}
}

events.emit('onShowItemTooltip', item, ttPos, compare, false, this.shiftDown);
@@ -506,18 +589,12 @@ define([

if (!item)
return;
else if ((action == 'equip') && ((item.material) || (item.quest) || (item.type == 'mtx') || (item.level > playerLevel)))
else if ((action == 'equip') && ((item.material) || (item.quest) || (item.type == 'mtx') || (!window.player.inventory.canEquipItem(item))))
return;
else if ((action == 'learnAbility') && (item.level > playerLevel))
else if ((action == 'learnAbility') && (!window.player.inventory.canEquipItem(item)))
return;
else if ((action == 'activateMtx') && (item.type != 'mtx'))
return;
if ((item.factions) && (action == 'equip')) {
if (item.factions.some(function (f) {
return f.noEquip;
}))
return;
}

var cpn = 'inventory';
if (action == 'equip')


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

@@ -133,7 +133,7 @@
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 143px;
height: 147px;

> .heading {
color: @blueA;
@@ -153,23 +153,46 @@
padding: 10px;

.amount {
float: left;
color: @white;
text-align: center;
width: 100%;
width: calc(100% - 80px);
height: 36px;
padding-top: 8px;
padding-top: 3px;
border: none;
}

input, .textbox, input:-webkit-autofill {
color: @white;
-webkit-text-fill-color: @white;
background-color: @blackC;
}

.button {
width: 100%;
height: 36px;
margin-top: 5px;
background-color: @blueC;
color: @white;

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

&:not(.btnSplit) {
width: 40px;
float: left;
color: @white;
background-color: @blackB;

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

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


+ 4
- 2
src/client/ui/templates/inventory/template.html View File

@@ -13,8 +13,10 @@
<div class="heading-text">stack size</div>
</div>
<div class="bottom">
<div class="amount"></div>
<div class="button">split</div>
<div class="button btnLess">-</div>
<input type="text" class="textbox amount">
<div class="button btnMore">+</div>
<div class="button btnSplit">split</div>
</div>
</div>
</div>


+ 15
- 11
src/client/ui/templates/leaderboard/leaderboard.js View File

@@ -48,7 +48,7 @@ define([
else if (this.offset > this.maxOffset)
this.offset = this.maxOffset;

this.onGetList(this.records, true);
this.getList(true);
},

onMine: function () {
@@ -90,7 +90,9 @@ define([
this.prophecyFilter.push(prophecyName);
},

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

if (!this.prophecyFilter) {
var prophecies = window.player.prophecies;
this.prophecyFilter = prophecies ? prophecies.list : [];
@@ -101,34 +103,34 @@ define([
module: 'leaderboard',
method: 'requestList',
data: {
prophecies: this.prophecyFilter
prophecies: this.prophecyFilter,
offset: this.offset * this.pageSize
},
callback: this.onGetList.bind(this)
callback: this.onGetList.bind(this, keepOffset)
});
},

onGetList: function (result, keepOffset) {
onGetList: function (keepOffset, result) {
this.records = result;

if (!keepOffset) {
this.offset = 0;

var foundIndex = result.firstIndex(function (r) {
var foundIndex = this.records.list.firstIndex(function (r) {
return (r.name == window.player.name);
}, this);
if (foundIndex != -1)
this.offset = ~~(foundIndex / this.pageSize);
}

this.records = result;

var container = this.find('.list').empty();

var low = this.offset * this.pageSize;
var high = Math.min(result.length, low + this.pageSize);

this.maxOffset = Math.ceil(result.length / this.pageSize) - 1;

for (var i = low; i < high; i++) {
var r = result[i];
for (var i = 0; i < this.records.list.length; i++) {
var r = this.records.list[i];

var html = '<div class="row"><div class="col">' + r.level + '</div><div class="col">' + r.name + '</div></div>';
var el = $(html)
@@ -149,6 +151,8 @@ define([
}

this.updatePaging();

this.el.removeClass('disabled');
},

updatePaging: function () {


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

@@ -11,11 +11,11 @@
</div>
<div class="message"></div>
</div>
<div class="news" location="https://gitlab.com/Isleward/isleward/issues/477">[ Test server issues ]</div>
<div class="news" location="https://gitlab.com/Isleward/isleward/tags/v0.1.11">[ Latest Release Notes ]</div>
<div class="extra">
<div class="el button btnPatreon" location="http://patreon.com/bigbadwaffle">Pledge on Patreon</div>
<div class="el button btnPatreon" location="https://patreon.com/bigbadwaffle">Pledge on Patreon</div>
<div class="el button btnPaypal" location="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BR2CC82WUAVEA">Donate on Paypal</div>
<div class="el button btnWiki" location="http://wiki.isleward.com/Main_Page">Access the Wiki</div>
</div>
<div class="version" location="https://gitlab.com/Isleward/isleward/issues/418">v0.1.10</div>
<div class="version" location="https://gitlab.com/Isleward/isleward/tags/v0.1.11">v0.1.11</div>
</div>

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

@@ -213,13 +213,13 @@ define([
var textbox = this.find('input');
var val = textbox.val()
.split('<')
.join('')
.join('&lt;')
.split('>')
.join('');
.join('&gt;');

textbox.blur();

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

client.request({


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

@@ -107,11 +107,11 @@
}

&.q1 {
color: @blue;
color: @greenB;
}

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

&.q3 {


+ 19
- 15
src/client/ui/templates/options/options.js View File

@@ -6,7 +6,7 @@ define([
'ui/factory',
'js/objects/objects',
'js/system/client'
], function(
], function (
events,
template,
styles,
@@ -21,7 +21,7 @@ define([

modal: true,

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

@@ -30,15 +30,20 @@ define([
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));
},
reportIssue: function () {
window.open('https://gitlab.com/Isleward/isleward/issues/new', '_blank');
},

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

charSelect: function() {
charSelect: function () {
client.request({
module: 'cons',
method: 'unzone'
@@ -49,7 +54,7 @@ define([
renderer.buildTitleScreen();

events.emit('onShowCharacterSelect');
$('[class^="ui"]:not(.ui-container)').each(function(i, el) {
$('[class^="ui"]:not(.ui-container)').each(function (i, el) {
var ui = $(el).data('ui');
if ((ui) && (ui.destroy))
ui.destroy();
@@ -57,11 +62,11 @@ define([
factory.build('characters', {});
},

toggleScreen: function() {
toggleScreen: function () {
this.el.find('.btnScreen').html(renderer.toggleScreen());
},

onResize: function() {
onResize: function () {
var isFullscreen = (window.innerHeight == screen.height);
if (isFullscreen)
this.el.find('.btnScreen').html('Windowed');
@@ -69,28 +74,27 @@ define([
this.el.find('.btnScreen').html('Fullscreen');
},

toggle: function() {
toggle: function () {
this.onResize();
this.shown = !this.el.is(':visible');

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

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

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

+ 9
- 0
src/client/ui/templates/options/styles.less View File

@@ -23,4 +23,13 @@
color: @black;
}
}

.btnIssue {
background-color: @red;

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

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

@@ -4,4 +4,5 @@
<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>

+ 26
- 16
src/client/ui/templates/party/styles.less View File

@@ -7,7 +7,7 @@
.party {
position: absolute;
left: 16px;
top: 104px;
top: 154px;

.member {
width: 160px;
@@ -94,41 +94,51 @@
.invite {
position: absolute;

right: 10px;
bottom: 164px;
right: 356px;
bottom: 10px;
height: 144px;

background-color: @gray;
border: 4px solid @lightGray;
background-color: fade(@darkGray, 90%);
padding: 8px;

color: @white;

.text {
height: 16px;
margin-bottom: 16px;
text-align: center;

> * {
display: block;
width: 100%;
}

.name {
margin-top: 10px;
margin-bottom: -4px;
}
}

.buttons {
[class^='btn'] {
width: 96px;
background-color: @lightGray;
width: 100%;
background-color: @blackA;
text-align: center;
padding: 8px;
cursor: pointer;

&:hover {
background-color: lighten(@lightGray, 20%);
color: @black;
}
color: @white;

&.btnDecline {
float: left;
}

&.btnAccept {
float: right;
margin-bottom: 10px;
}

&:hover {
background-color: @grayD;
color: @black;
}
}
}
}
}
}

+ 4
- 3
src/client/ui/templates/party/templateInvite.html View File

@@ -1,9 +1,10 @@
<div class="invite">
<div class="text">
$NAME$ invited you to a party
<span class="color-yellowB">party invite</span>
<span class="name">$NAME$</span>
</div>
<div class="buttons">
<div class="btnDecline">Decline</div>
<div class="btnAccept">Accept</div>
<div class="btnDecline">Decline</div>
</div>
</div>
</div>

+ 18
- 16
src/client/ui/templates/quests/quests.js View File

@@ -4,7 +4,7 @@ define([
'html!ui/templates/quests/template',
'html!ui/templates/quests/templateQuest',
'css!ui/templates/quests/styles'
], function(
], function (
client,
events,
tpl,
@@ -17,7 +17,7 @@ define([
quests: [],
container: '.right',

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

this.onEvent('onObtainQuest', this.onObtainQuest.bind(this));
@@ -25,24 +25,27 @@ define([
this.onEvent('onCompleteQuest', this.onCompleteQuest.bind(this));
},

onRezone: function() {
onRezone: function () {
this.quests = [];
this.el.find('.list').empty();
},

onObtainQuest: function(quest) {
onObtainQuest: function (quest) {
var list = this.el.find('.list');

var html = templateQuest
.replace('$ZONE$', quest.zoneName)
.replace('$NAME$', quest.name)
.replace('$DESCRIPTION$', quest.description);
.replace('$DESCRIPTION$', quest.description)
.replace('$REWARD$', quest.xp + ' xp');

var el = $(html).appendTo(list);
var el = $(html)
.appendTo(list);

if (quest.isReady)
el.addClass('ready');

if (quest.active)
if (quest.active)
el.addClass('active');
else if (!quest.isReady)
el.addClass('disabled');
@@ -58,7 +61,7 @@ define([
var quests = list.find('.quest');

quests
.sort(function(a, b) {
.sort(function (a, b) {
a = $(a).hasClass('active') ? 1 : 0;
b = $(b).hasClass('active') ? 1 : 0;
return b - a;
@@ -66,8 +69,7 @@ define([
.appendTo(list);
},


onClick: function(el, quest) {
onClick: function (el, quest) {
if (!el.hasClass('ready'))
return;

@@ -82,8 +84,8 @@ define([
});
},

onUpdateQuest: function(quest) {
var q = this.quests.find(function(q) {
onUpdateQuest: function (quest) {
var q = this.quests.find(function (q) {
return (q.id == quest.id);
});

@@ -98,8 +100,8 @@ define([
}
},

onCompleteQuest: function(id) {
var q = this.quests.find(function(q) {
onCompleteQuest: function (id) {
var q = this.quests.find(function (q) {
return (q.id == id);
});

@@ -107,9 +109,9 @@ define([
return;

q.el.remove();
this.quests.spliceWhere(function(q) {
this.quests.spliceWhere(function (q) {
return (q.id == id);
});
}
}
});
});

+ 26
- 1
src/client/ui/templates/quests/styles.less View File

@@ -19,6 +19,12 @@
background-color: fade(lighten(#3a3b4a, 15%), 90%);
}

.zone {
color: @grayD;
margin-bottom: 4px;
text-align: right;
}

.name {
color: @white;
margin-bottom: 3px;
@@ -28,6 +34,21 @@
color: darken(@white, 35%);
}

.reward {
display: none;
color: @tealC;
}

&:hover {
> .description {
display: none;
}

> .reward {
display: block;
}
}

.ready-text {
display: none;
}
@@ -47,6 +68,10 @@
display: none;
}

.reward {
display: none;
}

.ready-text {
display: block;
color: @white;
@@ -54,4 +79,4 @@
}
}
}
}
}

+ 3
- 1
src/client/ui/templates/quests/templateQuest.html View File

@@ -1,5 +1,7 @@
<div class="quest">
<div class="zone">$ZONE$</div>
<div class="name">$NAME$</div>
<div class="description">$DESCRIPTION$</div>
<div class="reward">Reward: $REWARD$</div>
<div class="ready-text">Click to turn in</div>
</div>
</div>

+ 13
- 11
src/client/ui/templates/reputation/reputation.js View File

@@ -3,7 +3,7 @@ define([
'js/system/client',
'html!ui/templates/reputation/template',
'css!ui/templates/reputation/styles'
], function(
], function (
events,
client,
template,
@@ -17,12 +17,12 @@ define([

list: null,

postRender: function() {
postRender: function () {
this.onEvent('onGetReputations', this.onGetReputations.bind(this));
this.onEvent('onShowReputation', this.toggle.bind(this, true));
},

build: function() {
build: function () {
var list = this.list;

this.find('.info .heading-bottom').html('');
@@ -36,7 +36,10 @@ define([

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

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

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

var el = $(html)
@@ -47,7 +50,7 @@ define([
}, this);
},

onSelectFaction: function(el, faction) {
onSelectFaction: function (el, faction) {
this.find('.selected').removeClass('selected');
$(el).addClass('selected');

@@ -77,9 +80,9 @@ define([
this.find('.tier').html(tiers[tier].name.toLowerCase() + ' (' + percentage + '%)');
},

onGetReputations: function(list) {
onGetReputations: function (list) {
this.list = list;
this.list.sort(function(a, b) {
this.list.sort(function (a, b) {
if (a.name[0] < b.name[0])
return -1;
else
@@ -90,15 +93,14 @@ define([
this.build();
},

toggle: function() {
toggle: function () {
var shown = !this.el.is(':visible');

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

+ 5
- 3
src/client/ui/templates/spells/spells.js View File

@@ -34,7 +34,7 @@ define([
var x = -(icon[0] * 64);
var y = -(icon[1] * 64);

var hotkey = (i == 0) ? 'space' : spells[i].id;
var hotkey = (spells[i].id == 0) ? 'space' : spells[i].id;

var html = templateSpell
.replace('$HOTKEY$', hotkey);
@@ -53,7 +53,7 @@ define([
.next().html(hotkey);

this.onGetSpellCooldowns({
spell: i,
spell: spells[i].id,
cd: spells[i].cd * 350 //HACK - we don't actually know how long a tick is
});
}
@@ -101,7 +101,9 @@ define([
},

onGetSpellCooldowns: function (options) {
var spell = this.spells[options.spell];
var spell = this.spells.find(function (s) {
return (s.id == options.spell);
});
spell.ttl = options.cd;
spell.ttlStart = +new Date;
},


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

@@ -28,9 +28,18 @@
}
}

.stats {
> .implicitStats {
color: darken(@white, 20%);
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 2px solid @blackA;
}

> .stats {
color: darken(@white, 20%);
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 2px solid @blackA;

.gainStat {
color: @green;
@@ -39,10 +48,14 @@
.loseStat {
color: @red;
}

.enchanted {
color: @tealC !important;
}
}

.effects {
color: @tealC;
color: @white;
margin-bottom: 8px;
}

@@ -51,13 +64,21 @@
margin-bottom: 8px;
}

.level {
.requires {
margin-top: 8px;
color: darken(@white, 40%);

&.high-level {
color: @red;
}

.high-level {
color: @red;
}

> *:not(.high-level) {
color: darken(@white, 40%);
}
}

.material {
@@ -75,7 +96,13 @@
}

.damage {
.gainDamage {
color: @green;
}

.loseDamage {
color: @red;
}
}

.worth {


+ 7
- 2
src/client/ui/templates/tooltipItem/templateTooltip.html View File

@@ -3,13 +3,18 @@
<div class="type">$TYPE$</div>
<div class="power">$POWER$</div>
</div>
<div class="implicitStats">$IMPLICITSTATS$</div>
<div class="stats">$STATS$</div>
<div class="effects">$EFFECTS$</div>
<div class="faction">faction: $faction$</div>
<div class="material">crafting material</div>
<div class="quest">quest item</div>
<div class="spellName">$SPELLNAME$</div>
<div class="damage">$DAMAGE$</div>
<div class="level">level: $LEVEL$</div>
<div class="requires">
requires:
<div class="level">level: $LEVEL$</div>
<div class="stats">$ATTRIBUTE$: $ATTRIBUTEVALUE$</div>
<div class="faction">faction: $faction$</div>
</div>
<div class="worth"></div>
<div class="info"><br />[shift] to compare</div>

+ 98
- 8
src/client/ui/templates/tooltipItem/tooltipItem.js View File

@@ -14,14 +14,21 @@ define([
var percentageStats = [
'addCritChance',
'addCritMultiplier',
'addAttackCritChance',
'addAttackCritMultiplier',
'addSpellCritChance',
'addSpellCritMultiplier',
'sprintChance',
'dmgPercent',
'xpIncrease',
'blockAttackChance',
'blockSpellChance',
'dodgeAttackChance',
'dodgeSpellChance',
'attackSpeed',
'castSpeed',
'itemQuantity',
'magicFind',
'catchChance',
'catchSpeed',
'fishRarity',
@@ -44,7 +51,7 @@ define([
},

onHideItemTooltip: function (item) {
if (this.item != item)
if ((this.item != item) && (this.item.refItem) && (this.item.refItem != item))
return;

this.item = null;
@@ -55,6 +62,8 @@ define([
this.item = item;

var tempStats = $.extend(true, {}, item.stats);
var enchantedStats = item.enchantedStats || {};

if ((compare) && (shiftDown)) {
if (!item.eq) {
var compareStats = compare.stats;
@@ -74,11 +83,26 @@ define([
}
}
}
} else {
Object.keys(tempStats).forEach(function (s) {
if (enchantedStats[s]) {
tempStats[s] -= enchantedStats[s];
if (tempStats[s] <= 0)
delete tempStats[s];

tempStats['_' + s] = enchantedStats[s];
}
});
}

stats = Object.keys(tempStats)
.map(function (s) {
var statName = statTranslations.translate(s);
var isEnchanted = (s[0] == '_');
var statName = s;
if (isEnchanted)
statName = statName.substr(1);

statName = statTranslations.translate(statName);
var value = tempStats[s];

if (percentageStats.indexOf(s) > -1)
@@ -95,16 +119,43 @@ define([
else if (row.indexOf('+') > -1)
rowClass = 'gainStat';
}
if (isEnchanted)
rowClass += ' enchanted';

row = '<div class="' + rowClass + '">' + row + '</div>';

return row;
}, this)
.sort(function (a, b) {
return (a.length - b.length);
return (a.replace(' enchanted', '').length - b.replace(' enchanted', '').length);
})
.sort(function (a, b) {
if ((a.indexOf('enchanted') > -1) && (b.indexOf('enchanted') == -1))
return 1;
else if ((a.indexOf('enchanted') == -1) && (b.indexOf('enchanted') > -1))
return -1;
else
return 0;
})
.join('');

var implicitStats = (item.implicitStats || []).map(function (s) {
var stat = s.stat;
var statName = statTranslations.translate(stat);
var value = s.value;

if (percentageStats.indexOf(stat) > -1)
value += '%';
else if ((stat.indexOf('element') == 0) && (stat.indexOf('Resist') == -1))
value += '%';

var row = value + ' ' + statName;
var rowClass = '';
row = '<div class="' + rowClass + '">' + row + '</div>';

return row;
}).join('');

var name = item.name;
if (item.quantity > 1)
name += ' x' + item.quantity;
@@ -118,15 +169,43 @@ define([
.replace('$QUALITY$', item.quality)
.replace('$TYPE$', item.type)
.replace('$SLOT$', item.slot)
.replace('$IMPLICITSTATS$', implicitStats)
.replace('$STATS$', stats)
.replace('$LEVEL$', level);

if (item.requires) {
html = html
.replace('$ATTRIBUTE$', item.requires[0].stat)
.replace('$ATTRIBUTEVALUE$', item.requires[0].value);
}

if (item.power)
html = html.replace('$POWER$', ' ' + (new Array(item.power + 1)).join('+'));

if ((item.spell) && (item.spell.values)) {
var abilityValues = '';
for (var p in item.spell.values) {
abilityValues += p + ': ' + item.spell.values[p] + '<br/>';
if ((compare) && (shiftDown)) {
var delta = item.spell.values[p] - compare.spell.values[p];
// adjust by EPSILON to handle float point imprecision, otherwise 3.15 - 2 = 1.14 or 2 - 3.15 = -1.14
// have to move away from zero by EPSILON, not a simple add
if (delta >= 0) {
delta += Number.EPSILON;
} else {
delta -= Number.EPSILON;
}
delta = ~~((delta) * 100) / 100;
var rowClass = '';
if (delta > 0) {
rowClass = 'gainDamage';
delta = '+' + delta;
} else if (delta < 0) {
rowClass = 'loseDamage';
}
abilityValues += '<div class="' + rowClass + '">' + p + ': ' + delta + '</div>';
} else {
abilityValues += p + ': ' + item.spell.values[p] + '<br/>';
}
}
if (!item.ability)
abilityValues = abilityValues;
@@ -140,6 +219,16 @@ define([
else
this.tooltip.find('.level').show();

if (!item.implicitStats)
this.tooltip.find('.implicitStats').hide();
else
this.tooltip.find('.implicitStats').show();

if (!item.requires)
this.tooltip.find('.requires .stats').hide();
else
this.tooltip.find('.requires .stats').show();

if ((!item.type) || (item.type == item.name))
this.tooltip.find('.type').hide();
else {
@@ -151,10 +240,11 @@ define([
if (item.power)
this.tooltip.find('.power').show();

var playerStats = window.player.stats.values;
var level = playerStats.originalLevel || playerStats.level;
if (item.level > level)
this.tooltip.find('.level').addClass('high-level');
var equipErrors = window.player.inventory.equipItemErrors(item);
equipErrors.forEach(function (e) {
this.tooltip.find('.requires').addClass('high-level');
this.tooltip.find('.requires .' + e).addClass('high-level');
}, this);

if ((item.material) || (item.quest)) {
this.tooltip.find('.level').hide();


+ 6
- 6
src/client/ui/templates/tooltips/tooltips.js View File

@@ -3,7 +3,7 @@ define([
'css!ui/templates/tooltips/styles',
'html!ui/templates/tooltips/template',
'html!ui/templates/tooltips/templateTooltip'
], function(
], function (
events,
styles,
template,
@@ -18,14 +18,14 @@ define([

hoverEl: null,

postRender: function() {
postRender: function () {
this.tooltip = this.el.find('.tooltip');
this.onEvent('onShowTooltip', this.onShowTooltip.bind(this));
this.onEvent('onHideTooltip', this.onHideTooltip.bind(this));
},

onHideTooltip: function(el) {
onHideTooltip: function (el) {
if (this.hoverEl != el)
return;

@@ -33,7 +33,7 @@ define([
this.tooltip.hide();
},

onShowTooltip: function(text, el, pos, width, bottomAlign, rightAlign, zIndex) {
onShowTooltip: function (text, el, pos, width, bottomAlign, rightAlign, zIndex) {
this.hoverEl = el;

this.tooltip
@@ -64,4 +64,4 @@ define([
this.tooltip.css('zIndex', '');
}
};
});
});

+ 19
- 4
src/server/combat/combat.js View File

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

var amount = config.damage;
var blocked = false;
var dodged = false;
var isCrit = false;

//Don't block heals
@@ -22,9 +23,17 @@ define([
blocked = true;
amount = 0;
}

if (!blocked) {
var dodgeChance = config.isAttack ? tgtValues.dodgeAttackChance : tgtValues.dodgeSpellChance;
if (mathRandom() * 100 < dodgeChance) {
dodged = true;
amount = 0;
}
}
}

if (!blocked) {
if ((!blocked) && (!dodged)) {
var statValue = 0;
if (config.statType) {
var statType = config.statType;
@@ -38,13 +47,13 @@ define([

statValue = max(1, statValue);
var statMult = config.statMult || 1;
var dmgPercent = 100 + srcValues.dmgPercent;
var dmgPercent = 100 + (srcValues.dmgPercent || 0);

amount *= statValue * statMult;

if (config.element) {
var elementName = 'element' + config.element[0].toUpperCase() + config.element.substr(1);
dmgPercent += srcValues[elementName + 'Percent'];
dmgPercent += (srcValues[elementName + 'Percent'] || 0);

//Don't mitigate heals
if (!config.noMitigate) {
@@ -58,9 +67,14 @@ define([

if (!config.noCrit) {
var critChance = srcValues.critChance;
critChance += (config.isAttack) ? srcValues.attackCritChance : srcValues.spellCritChance;

var critMultiplier = srcValues.critMultiplier;
critMultiplier += (config.isAttack) ? srcValues.attackCritMultiplier : srcValues.spellCritMultiplier;

if ((config.crit) || (mathRandom() * 100 < critChance)) {
isCrit = true;
amount *= (srcValues.critMultiplier / 100);
amount *= (critMultiplier / 100);
}
}
}
@@ -68,6 +82,7 @@ define([
return {
amount: amount,
blocked: blocked,
dodged: dodged,
crit: isCrit,
element: config.element
};


+ 11
- 3
src/server/components/aggro.js View File

@@ -49,6 +49,9 @@ define([
},

move: function () {
if (this.obj.dead)
return;

var result = {
success: true
};
@@ -93,7 +96,7 @@ define([
//find mobs in range
var range = this.range;
var faction = this.faction;
var inRange = this.physics.getArea(x - range, y - range, x + range, y + range, (c => (((!c.player) || (!obj.player)) && (c.aggro) && (c.aggro.willAutoAttack(obj)))));
var inRange = this.physics.getArea(x - range, y - range, x + range, y + range, (c => (((!c.player) || (!obj.player)) && (!obj.dead) && (c.aggro) && (c.aggro.willAutoAttack(obj)))));

if (inRange.length == 0)
return;
@@ -235,13 +238,16 @@ define([
for (var i = 0; i < lLen; i++) {
var l = list[i];
if (!l) {
console.log('aggro obj empty???');
lLen--;
continue;
}
//Maybe the aggro component was removed?
var targetAggro = l.obj.aggro;
if (targetAggro)
if (targetAggro) {
targetAggro.unAggro(this.obj);
i--;
lLen--;
}
}

this.list = [];
@@ -259,11 +265,13 @@ define([

if (amount == null) {
list.splice(i, 1);
obj.aggro.unAggro(this.obj);
break;
} else {
l.threat -= amount;
if (l.threat <= 0) {
list.splice(i, 1);
obj.aggro.unAggro(this.obj);
break;
}
}


+ 90
- 7
src/server/components/auth.js View File

@@ -6,7 +6,10 @@ define([
'leaderboard/leaderboard',
'config/skins',
'config/roles',
'misc/profanities'
'misc/profanities',
'fixes/fixes',
'config/loginRewards',
'misc/mail'
], function (
bcrypt,
io,
@@ -15,7 +18,10 @@ define([
leaderboard,
skins,
roles,
profanities
profanities,
fixes,
loginRewards,
mail
) {
return {
type: 'auth',
@@ -25,6 +31,7 @@ define([
characters: {},
characterList: [],
stash: null,
accountInfo: null,

customChannels: [],

@@ -44,6 +51,60 @@ define([

this.charname = character.name;

this.checkLoginReward(data, character);
},

checkLoginReward: function (data, character) {
var accountInfo = this.accountInfo;

var scheduler = require('misc/scheduler');
var time = scheduler.getTime();
var lastLogin = accountInfo.lastLogin;
if ((!lastLogin) || (lastLogin.day != time.day)) {
var daysSkipped = 1;
if (lastLogin) {
if (time.day > lastLogin.day)
daysSkipped = time.day - lastLogin.day;
else {
var daysInMonth = scheduler.daysInMonth(lastLogin.month);
daysSkipped = (daysInMonth - lastLogin.day) + time.day;

for (var i = lastLogin.month + 1; i < time.month - 1; i++) {
daysSkipped += scheduler.daysInMonth(i);
}
}
}

if (daysSkipped == 1) {
accountInfo.loginStreak++;
if (accountInfo.loginStreak > 21)
accountInfo.loginStreak = 21;
} else {
accountInfo.loginStreak -= (daysSkipped - 1);
if (accountInfo.loginStreak < 1)
accountInfo.loginStreak = 1;
}

var rewards = loginRewards.generate(accountInfo.loginStreak);
mail.sendMail(character.name, rewards, this.onSendRewards.bind(this, data, character));
} else
this.onSendRewards(data, character);

accountInfo.lastLogin = time;
},

onSendRewards: function (data, character) {
delete mail.busy[character.name];

io.set({
ent: this.username,
field: 'accountInfo',
value: JSON.stringify(this.accountInfo),
callback: this.onUpdateAccountInfo.bind(this, data, character)
});
},

onUpdateAccountInfo: function (data, character) {
this.obj.player.sessionStart = +new Date;
this.obj.player.spawn(character, data.callback);

@@ -165,6 +226,7 @@ define([
}

var character = JSON.parse(result || '{}');
fixes.fixCharacter(character);

//Hack for old characters
if (!character.skinId)
@@ -219,6 +281,8 @@ define([

this.stash = JSON.parse(result || '[]');

fixes.fixStash(this.stash);

if (this.skins != null) {
this.verifySkin(character);
data.callback(character);
@@ -238,6 +302,8 @@ define([

onGetSkins: function (msg, character, result) {
this.skins = JSON.parse(result || '[]');
fixes.fixSkins(this.username, this.skins);

var list = [...this.skins, ...roles.getSkins(this.username)];
var skinList = skins.getSkinList(list);

@@ -335,6 +401,24 @@ define([
onLoginVerified: function (msg) {
this.username = msg.data.username;
connections.logOut(this.obj);

io.get({
ent: msg.data.username,
field: 'accountInfo',
callback: this.onGetAccountInfo.bind(this, msg)
});
},

onGetAccountInfo: function (msg, info) {
if (!info) {
info = {
loginStreak: 0
};
} else
info = JSON.parse(info);

this.accountInfo = info;

msg.callback();
},

@@ -354,11 +438,6 @@ define([
}
}

if (!profanities.isClean(credentials.username)) {
msg.callback(messages.login.invalid);
return;
}

io.get({
ent: credentials.username,
field: 'login',
@@ -384,6 +463,10 @@ define([
});
},
onRegister: function (msg, result) {
this.accountInfo = {
loginStreak: 0
};

io.set({
ent: msg.data.username,
field: 'characterList',


+ 3
- 1
src/server/components/chatter.js View File

@@ -39,9 +39,11 @@ define([
if (!this.global)
this.obj.syncer.set(false, 'chatter', 'msg', pick.msg);
else {
//HACK
//This shouldn't always be pink, but only events use this atm so it's fine
this.obj.instance.syncer.queue('onGetMessages', {
messages: {
class: 'q2',
class: 'color-pinkB',
message: this.obj.name + ': ' + pick.msg
}
});


+ 26
- 18
src/server/components/components.js View File

@@ -1,38 +1,46 @@
define([
'../misc/fileLister'
'misc/fileLister',
'misc/events',
'path'
], function(
fileLister
fileLister,
events,
pathUtilities
) {
var onReady = null;

var components = {
components: {},
waiting: null,
waiting: [],

init: function(callback) {
onReady = callback;
this.getComponentNames();
events.emit('onBeforeGetComponents', this.components);
this.getComponentFolder();
},
getComponentNames: function() {
this.waiting = fileLister.getFolder('./components/');
this.waiting = this.waiting.filter(w => (
(w.indexOf('components') == -1) &&

getComponentFolder: function() {
var files = fileLister.getFolder('./components/');
files = files.filter(w => (
(w.indexOf('components') == -1) &&
(w.indexOf('cpnBase') == -1) &&
(w.indexOf('projectile') == -1)
));
this.onGetComponentNames();
},
onGetComponentNames: function() {
for (var i = 0; i < this.waiting.length; i++) {
var name = this.waiting[i];
this.getComponent(name);
var fLen = files.length;
for (var i = 0; i < fLen; i++) {
this.getComponentFile(`./components/${files[i]}`);
}
},
getComponent: function(name) {
require([ './components/' + name ], this.onGetComponent.bind(this));

getComponentFile: function(path) {
var fileName = pathUtilities.basename(path);
fileName = fileName.replace('.js', '');
this.waiting.push(fileName);
require([ path ], this.onGetComponent.bind(this));
},

onGetComponent: function(template) {
this.waiting.spliceWhere(w => w == template.type + '.js');
this.waiting.spliceWhere(w => w == template.type);

this.components[template.type] = template;

@@ -44,4 +52,4 @@ define([
};

return components;
});
});

+ 26
- 32
src/server/components/dialogue.js View File

@@ -1,6 +1,6 @@
define([

], function(
], function (

) {
return {
@@ -11,19 +11,19 @@ define([

trigger: null,

init: function(blueprint) {
init: function (blueprint) {
this.states = blueprint.config;
},

destroy: function() {
destroy: function () {
if (this.trigger)
this.trigger.destroyed = true;
},

talk: function(msg) {
talk: function (msg) {
if (!msg)
return false;
return false;
var target = msg.target;

if ((target == null) && (!msg.targetName))
@@ -33,11 +33,10 @@ define([
target = this.obj.instance.objects.objects.find(o => o.id == target);
if (!target)
return false;
}
else if (msg.targetName != null) {
} else if (msg.targetName != null) {
target = this.obj.instance.objects.objects.find(o => ((o.name) && (o.name.toLowerCase() == msg.targetName.toLowerCase())));
if (!target)
return false;
return false;
}

if (!target.dialogue)
@@ -56,11 +55,11 @@ define([
this.obj.syncer.set(true, 'dialogue', 'state', state);
},

stopTalk: function() {
this.obj.syncer.set(true, 'dialogue', 'state', null);
stopTalk: function () {
this.obj.syncer.set(true, 'dialogue', 'state', null);
},

getState: function(sourceObj, state) {
getState: function (sourceObj, state) {
state = state || 1;

//Goto?
@@ -72,7 +71,7 @@ define([
var goto = (config.options[state] || {}).goto;
if (goto instanceof Array) {
var gotos = [];
goto.forEach(function(g) {
goto.forEach(function (g) {
var rolls = (g.chance * 100) || 100;
for (var i = 0; i < rolls; i++) {
gotos.push(g.number);
@@ -80,15 +79,14 @@ define([
});

state = gotos[~~(Math.random() * gotos.length)];
}
else
} else
state = goto;
}

this.sourceStates[sourceObj.id] = state;

if (!this.states) {
console.log(sourceObj.name, this.obj.name, state);
console.log(sourceObj.name, this.obj.name, state);
return null;
}
var stateConfig = this.states[state];
@@ -108,16 +106,14 @@ define([
return this.getState(sourceObj, stateConfig.goto.success);
else
return this.getState(sourceObj, stateConfig.goto.failure);
}
else {
} else {
if (result) {
useMsg = extend(true, [], useMsg);
useMsg[0].msg = result;
} else
return null;
}
}
else if (stateConfig.method) {
} else if (stateConfig.method) {
var methodResult = stateConfig.method.call(this.obj, sourceObj);
if (methodResult) {
useMsg = extend(true, [], useMsg);
@@ -137,7 +133,7 @@ define([

if (useMsg instanceof Array) {
var msgs = [];
useMsg.forEach(function(m, i) {
useMsg.forEach(function (m, i) {
var rolls = (m.chance * 100) || 100;
for (var j = 0; j < rolls; j++) {
msgs.push({
@@ -151,8 +147,7 @@ define([

result.msg = pick.msg.msg;
result.options = useMsg[pick.index].options;
}
else {
} else {
result.msg = useMsg;
result.options = stateConfig.options;
}
@@ -160,12 +155,12 @@ define([
if (!(result.options instanceof Array)) {
if (result.options[0] == '$')
result.options = this.states[result.options.replace('$', '')].options;
result.options = Object.keys(result.options);
}

result.options = result.options
.map(function(o) {
.map(function (o) {
var gotoState = this.states[(o + '').split('.')[0]];
var picked = gotoState.options[o];

@@ -192,7 +187,7 @@ define([
return result;
},

simplify: function(self) {
simplify: function (self) {
return {
type: 'dialogue'
};
@@ -200,22 +195,21 @@ define([

//These don't belong here, but I can't figure out where to put them right now
//They are actions that can be performed while chatting with someone
teleport: function(msg) {
this.obj.syncer.set(true, 'dialogue', 'state', null);
teleport: function (msg) {
this.obj.syncer.set(true, 'dialogue', 'state', null);

var portal = extend(true, {}, require('./components/portal'), msg);
portal.collisionEnter(this.obj);
},

getItem: function(msg, source) {
getItem: function (msg, source) {
var inventory = this.obj.inventory;
var exists = inventory.items.find(i => (i.name == msg.item.name));
if (!exists) {
inventory.getItem(msg.item);
return msg.successMsg || false;
}
else
} else
return msg.existsMsg || false;
}
};
});
});

+ 7
- 3
src/server/components/door.js View File

@@ -23,8 +23,10 @@ define([
this.destroyKey = blueprint.destroyKey;
this.autoClose = blueprint.autoClose;

if (this.closed)
if (this.closed) {
this.obj.instance.physics.setCollision(this.obj.x, this.obj.y, true);
this.obj.instance.objects.notifyCollisionChange(this.obj.x, this.obj.y, true);
}

var o = this.obj.instance.objects.buildObjects([{
properties: {
@@ -126,12 +128,12 @@ define([
return;

if (((key.singleUse) || (this.destroyKey)) && (key.keyId != 'world')) {
obj.inventory.destroyItem(key.id);
obj.inventory.destroyItem(key.id, 1);

obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: 'The ' + key.name + ' disintegrates on use',
type: 'info'
}]
@@ -143,6 +145,7 @@ define([
thisObj.cell = this.openSprite;
syncO.cell = this.openSprite;
this.obj.instance.physics.setCollision(thisObj.x, thisObj.y, false);
this.obj.instance.objects.notifyCollisionChange(thisObj.x, thisObj.y, false);

this.closed = false;
this.enterArea(obj);
@@ -150,6 +153,7 @@ define([
thisObj.cell = this.closedSprite;
syncO.cell = this.closedSprite;
this.obj.instance.physics.setCollision(thisObj.x, thisObj.y, true);
this.obj.instance.objects.notifyCollisionChange(thisObj.x, thisObj.y, true);

this.closed = true;
this.enterArea(obj);


+ 33
- 9
src/server/components/effects.js View File

@@ -9,6 +9,11 @@ define([
effects: [],
nextId: 0,

ccResistances: {
stunned: 0,
slowed: 0
},

init: function (blueprint) {
var effects = blueprint.effects || [];
var eLen = effects.length;
@@ -114,19 +119,33 @@ define([
}
},

canApplyEffect: function (type) {
var ccResistances = this.ccResistances;
if ((100 - ccResistances[type]) >= 50) {
ccResistances[type] += 50;
return true;
} else
return false;
},

addEffect: function (options) {
var exists = this.effects.find(e => e.type == options.type);
if (exists) {
exists.ttl += options.ttl;
if (!this.canApplyEffect(options.type))
return;

for (var p in options) {
if (p == 'ttl')
continue;
if (!options.new) {
var exists = this.effects.find(e => e.type == options.type);
if (exists) {
exists.ttl += options.ttl;

exists[p] = options[p];
}
for (var p in options) {
if (p == 'ttl')
continue;

exists[p] = options[p];
}

return exists;
return exists;
}
}

var typeTemplate = null;
@@ -269,6 +288,11 @@ define([
this.syncRemove(e.id, e.type, e.noMsg);
}
}

for (var p in this.ccResistances) {
if (this.ccResistances[p] > 0)
this.ccResistances[p]--;
}
}
};
});

+ 23
- 20
src/server/components/equipment.js View File

@@ -37,14 +37,9 @@ define([
var item = this.obj.inventory.findItem(itemId);
if (!item)
return;
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (item.level > (stats.originalLevel || stats.level))) {
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (!this.obj.inventory.canEquipItem(item))) {
item.eq = false;
return;
} else if ((item.factions) && (this.obj.player)) {
if (!this.obj.reputation.canEquipItem(item)) {
item.eq = false;
return;
}
}

var currentEqId = this.eq[item.slot];
@@ -61,19 +56,12 @@ define([
itemId = itemId.itemId;
}

var level = this.obj.stats.originalValues || this.obj.stats.values;

var item = this.obj.inventory.findItem(itemId);
if (!item)
return;
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (item.level > level)) {
else if ((!item.slot) || (item.material) || (item.quest) || (item.ability) || (!this.obj.inventory.canEquipItem(item))) {
item.eq = false;
return;
} else if ((item.factions) && (this.obj.player)) {
if (!this.obj.reputation.canEquipItem(item)) {
item.eq = false;
return;
}
}

if (!slot)
@@ -102,7 +90,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: equipMsg.msg || 'you cannot equip that item',
type: 'info'
}]
@@ -149,6 +137,10 @@ define([
this.obj.stats.addStat(s, val);
}

(item.implicitStats || []).forEach(function (s) {
this.obj.stats.addStat(s.stat, s.value);
}, this);

item.eq = true;
this.eq[slot] = itemId;
item.equipSlot = slot;
@@ -221,6 +213,10 @@ define([
this.obj.stats.addStat(s, -val);
}

(item.implicitStats || []).forEach(function (s) {
this.obj.stats.addStat(s.stat, -s.value);
}, this);

delete item.eq;
delete this.eq[item.equipSlot];
delete item.equipSlot;
@@ -297,7 +293,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q4',
class: 'color-redA',
message: 'you unequip your ' + item.name + ' as it zaps you',
type: 'rep'
}]
@@ -314,17 +310,24 @@ define([
var eq = this.eq;
for (var p in eq) {
var item = items.find(i => (i.id == eq[p]));
if ((!item.slot) || (item.slot == 'tool') || (item.level <= level))
if ((!item.slot) || (item.slot == 'tool')) {
continue;
}

var item = items.find(i => (i.id == eq[p]));
var nItemStats = generatorStats.rescale(item, level);
var nItemStats = item.stats;
if (item.level > level)
nItemStats = generatorStats.rescale(item, level);

var tempItem = extend(true, {}, item);
tempItem.stats = extend(true, {}, nItemStats);
this.obj.fireEvent('afterRescaleItemStats', tempItem);

for (var s in nItemStats) {
for (var s in tempItem.stats) {
if (!stats[s])
stats[s] = 0;

stats[s] += nItemStats[s];
stats[s] += tempItem.stats[s];
}
}



+ 9
- 3
src/server/components/extensions/factionVendor.js View File

@@ -108,13 +108,14 @@ define([

var itemCount = blueprint.items.min + ~~(Math.random() * (blueprint.items.max - blueprint.items.min));
for (var i = 0; i < itemCount; i++) {
var minLevel = Math.max(1, list.level * 0.75);
var maxLevel = list.level * 1.25;
var minLevel = blueprint.items.minLevel || Math.max(1, list.level * 0.75);
var maxLevel = blueprint.items.maxLevel || (list.level * 1.25);
var level = ~~(minLevel + (Math.random() * (maxLevel - minLevel)));

var item = generator.generate({
noSpell: true,
magicFind: 150,
slot: blueprint.items.slot,
level: level
});

@@ -157,6 +158,7 @@ define([

if (item.type == 'skin') {
var skinBlueprint = skins.getBlueprint(item.id);
item.skinId = item.id;
item.name = skinBlueprint.name;
item.sprite = skinBlueprint.sprite;
} else if (item.generate) {
@@ -165,6 +167,10 @@ define([
generated.worth = item.worth;
if (item.infinite)
generated.infinite = true;

if (item.factions)
generated.factions = item.factions;

item = generated;
}

@@ -195,7 +201,7 @@ define([
requestedBy.instance.syncer.queue('onGetMessages', {
id: requestedBy.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: `your reputation is too low to buy that item`,
type: 'info'
}]


+ 125
- 7
src/server/components/extensions/socialCommands.js View File

@@ -5,7 +5,8 @@ define([
'misc/random',
'items/config/slots',
'security/io',
'config/factions'
'config/factions',
'security/connections'
], function (
roles,
atlas,
@@ -13,12 +14,20 @@ define([
random,
configSlots,
io,
factions
factions,
connections
) {
var commandRoles = {
//Regular players
join: 0,
leave: 0,
unEq: 0,

//Mods
mute: 5,
unmute: 5,

//Admin
getItem: 10,
getGold: 10,
setLevel: 10,
@@ -28,12 +37,15 @@ define([
getReputation: 10,
loseReputation: 10,
setStat: 10,
die: 10
die: 10,
getXp: 10
};

var localCommands = [
'join',
'leave'
'leave',
'mute',
'unmute'
];

return {
@@ -115,7 +127,7 @@ define([
obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-yellowB',
message: 'joined channel: ' + value,
type: 'info'
}]
@@ -139,7 +151,7 @@ define([
obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-redA',
message: 'you are not currently in that channel',
type: 'info'
}]
@@ -167,7 +179,7 @@ define([
this.obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-yellowB',
message: 'left channel: ' + value,
type: 'info'
}]
@@ -186,6 +198,108 @@ define([
});
},

mute: function (target, reason) {
if (typeof (target) == 'object') {
var keys = Object.keys(target);
target = keys[0];
reason = keys[1];
}

if (target == this.obj.name)
return;

var o = connections.players.find(o => (o.name == target));
if (!o)
return;

var role = roles.getRoleLevel(o);
if (role >= this.roleLevel)
return;

var social = o.social;
if (social.muted) {
this.sendMessage('That player has already been muted', 'color-redA');
return;
}

var reasonMsg = '';
if (reason)
reasonMsg = ' (' + reason + ')';

social.muted = true;
this.sendMessage('Successfully muted ' + target, 'color-yellowB');
this.sendMessage('You have been muted' + reasonMsg, 'color-yellowB', o);

atlas.updateObject(o, {
components: [{
type: 'social',
muted: true
}]
});

io.set({
ent: new Date(),
field: 'modLog',
value: JSON.stringify({
source: this.obj.name,
command: 'mute',
target: target,
reason: reason
})
});
},

unmute: function (target, reason) {
if (typeof (target) == 'object') {
var keys = Object.keys(target);
target = keys[0];
reason = keys[1];
}

if (target == this.obj.name)
return;

var o = connections.players.find(o => (o.name == target));
if (!o)
return;

var role = roles.getRoleLevel(o);
if (role >= this.roleLevel)
return;

var social = o.social;
if (!social.muted) {
this.sendMessage('That player is not muted', 'color-redA');
return;
}

var reasonMsg = '';
if (reason)
reasonMsg = ' (' + reason + ')';

delete social.muted;
this.sendMessage('Successfully unmuted ' + target, 'color-yellowB');
this.sendMessage('You have been unmuted' + reasonMsg, 'color-yellowB', o);

atlas.updateObject(o, {
components: [{
type: 'social',
muted: null
}]
});

io.set({
ent: new Date(),
field: 'modLog',
value: JSON.stringify({
source: this.obj.name,
command: 'unmute',
target: target,
reason: reason
})
})
},

clearInventory: function () {
var inventory = this.obj.inventory;

@@ -360,6 +474,10 @@ define([
this.obj.stats.values[config.stat] = ~~config.value;
},

getXp: function (amount) {
this.obj.stats.getXp(amount, this.obj, this.obj);
},

die: function () {
this.obj.stats.takeDamage({
amount: 99999


+ 134
- 110
src/server/components/inventory.js View File

@@ -3,7 +3,7 @@ define([
'items/salvager',
'items/enchanter',
'objects/objects',
'config/classes',
'config/spirits',
'mtx/mtx',
'config/factions',
'items/itemEffects'
@@ -18,6 +18,7 @@ define([
itemEffects
) {
return {
//Properties
type: 'inventory',

inventorySize: 50,
@@ -25,6 +26,8 @@ define([

blueprint: null,

//Base Methods

init: function (blueprint, isTransfer) {
var items = blueprint.items || [];
var iLen = items.length;
@@ -68,51 +71,74 @@ define([
this.hookItemEvents();
},

hookItemEvents: function (items) {
var items = items || this.items;
var iLen = items.length;
for (var i = 0; i < iLen; i++) {
var item = items[i];
save: function () {
return {
type: 'inventory',
items: this.items
};
},

if (item.effects) {
item.effects.forEach(function (e) {
if (e.mtx) {
var mtxUrl = mtx.get(e.mtx);
var mtxModule = require(mtxUrl);
simplify: function (self) {
if (!self)
return null;

e.events = mtxModule.events;
} else if (e.factionId) {
var faction = factions.getFaction(e.factionId);
var statGenerator = faction.uniqueStat;
statGenerator.generate(item);
} else {
var effectUrl = itemEffects.get(e.type);
var effectModule = require(effectUrl);
var reputation = this.obj.reputation;

e.events = effectModule.events;
}
});
}
return {
type: 'inventory',
items: this.items
.map(function (i) {
var item = extend(true, {}, i);

if ((item.pos == null) && (!item.eq)) {
var pos = i;
for (var j = 0; j < iLen; j++) {
if (!items.some(fj => (fj.pos == j))) {
pos = j;
break;
if (item.effects) {
item.effects = item.effects.map(e => ({
factionId: e.factionId,
text: e.text,
properties: e.properties,
mtx: e.mtx,
type: e.type,
rolls: e.rolls
}));
}
}
item.pos = pos;
} else if ((!item.eq) && (items.some(ii => ((ii != item) && (ii.pos == item.pos))))) {
var pos = item.pos;
for (var j = 0; j < iLen; j++) {
if (!items.some(fi => ((fi != item) && (fi.pos == j)))) {
pos = j;
break;

if (item.factions) {
item.factions = item.factions.map(function (f) {
var faction = reputation.getBlueprint(f.id);
var factionTier = reputation.getTier(f.id);

var noEquip = null;
if (factionTier < f.tier)
noEquip = true;

if (!faction)
console.log(f);

return {
id: f.id,
name: faction.name,
tier: f.tier,
tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier],
noEquip: noEquip
};
}, this);
}
}
item.pos = pos;
}

return item;
})
};
},

update: function () {
var items = this.items;
var iLen = items.length;
for (var i = 0; i < iLen; i++) {
var item = items[i];
if (!item.cd)
continue;

item.cd--;

this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
}
},

@@ -165,7 +191,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: learnMsg.msg || 'you cannot learn that ability',
type: 'info'
}]
@@ -439,6 +465,7 @@ define([
callback: this.onCheckCharExists.bind(this, msg, item)
});
},

onCheckCharExists: function (msg, item, res) {
if (!res) {
this.resolveCallback(msg, 'Recipient does not exist');
@@ -454,6 +481,54 @@ define([

//Helpers

hookItemEvents: function (items) {
var items = items || this.items;
var iLen = items.length;
for (var i = 0; i < iLen; i++) {
var item = items[i];

if (item.effects) {
item.effects.forEach(function (e) {
if (e.mtx) {
var mtxUrl = mtx.get(e.mtx);
var mtxModule = require(mtxUrl);

e.events = mtxModule.events;
} else if (e.factionId) {
var faction = factions.getFaction(e.factionId);
var statGenerator = faction.uniqueStat;
statGenerator.generate(item);
} else {
var effectUrl = itemEffects.get(e.type);
var effectModule = require(effectUrl);

e.events = effectModule.events;
}
});
}

if ((item.pos == null) && (!item.eq)) {
var pos = i;
for (var j = 0; j < iLen; j++) {
if (!items.some(fj => (fj.pos == j))) {
pos = j;
break;
}
}
item.pos = pos;
} else if ((!item.eq) && (items.some(ii => ((ii != item) && (ii.pos == item.pos))))) {
var pos = item.pos;
for (var j = 0; j < iLen; j++) {
if (!items.some(fi => ((fi != item) && (fi.pos == j)))) {
pos = j;
break;
}
}
item.pos = pos;
}
}
},

setItemPosition: function (id) {
var item = this.findItem(id);
if (!item)
@@ -630,8 +705,8 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
message: 'you bags are too full to loot any more items',
class: 'color-redA',
message: 'your bags are too full to loot any more items',
type: 'info'
}]
}, [this.obj.serverId]);
@@ -735,7 +810,7 @@ define([
this.obj.equipment.equip(item.id);
} else {
if (!item.effects)
this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
this.obj.syncer.setArray(true, 'inventory', 'getItems', item, true);
else {
var result = extend(true, {}, item);
result.effects = result.effects.map(e => ({
@@ -765,7 +840,7 @@ define([
}, this);
}

this.obj.syncer.setArray(true, 'inventory', 'getItems', result);
this.obj.syncer.setArray(true, 'inventory', 'getItems', result, true);
}
}

@@ -950,75 +1025,24 @@ define([
this.items = [];
},

save: function () {
return {
type: 'inventory',
items: this.items
};
},

simplify: function (self) {
if (!self)
return null;

var reputation = this.obj.reputation;

return {
type: 'inventory',
items: this.items
.map(function (i) {
var item = extend(true, {}, i);

if (item.effects) {
item.effects = item.effects.map(e => ({
factionId: e.factionId,
text: e.text,
properties: e.properties,
mtx: e.mtx,
type: e.type,
rolls: e.rolls
}));
}

if (item.factions) {
item.factions = item.factions.map(function (f) {
var faction = reputation.getBlueprint(f.id);
var factionTier = reputation.getTier(f.id);
canEquipItem: function (item) {
var stats = this.obj.stats.values;

var noEquip = null;
if (factionTier < f.tier)
noEquip = true;
var playerLevel = (stats.originalLevel || stats.level);
if (item.level > playerLevel)
return false;

if (!faction)
console.log(f);
if ((item.requires) && (stats[item.requires[0].stat] < item.requires[0].value))
return false;

return {
id: f.id,
name: faction.name,
tier: f.tier,
tierName: ['Hated', 'Hostile', 'Unfriendly', 'Neutral', 'Friendly', 'Honored', 'Revered', 'Exalted'][f.tier],
noEquip: noEquip
};
}, this);
}

return item;
})
};
},

update: function () {
var items = this.items;
var iLen = items.length;
for (var i = 0; i < iLen; i++) {
var item = items[i];
if (!item.cd)
continue;

item.cd--;

this.obj.syncer.setArray(true, 'inventory', 'getItems', item);
if (item.factions) {
if (item.factions.some(function (f) {
return f.noEquip;
}))
return false;
}

return true;
}
};
});

+ 1
- 1
src/server/components/mob.js View File

@@ -207,7 +207,7 @@ define([

canChase: function (obj) {
var distanceFromHome = Math.max(Math.abs(this.originX - obj.x), Math.abs(this.originY - obj.y));
return (distanceFromHome <= this.maxChaseDistance)
return ((!this.goHome) && (distanceFromHome <= this.maxChaseDistance));
}
};
});

+ 31
- 15
src/server/components/player.js View File

@@ -1,11 +1,13 @@
define([
'world/atlas',
'config/classes',
'config/roles'
'config/spirits',
'config/roles',
'config/serverConfig'
], function (
atlas,
classes,
roles
roles,
serverConfig
) {
return {
type: 'player',
@@ -26,6 +28,9 @@ define([
spawn: function (character, cb) {
var obj = this.obj;

if (character.dead)
obj.dead = true;

extend(true, obj, {
layerName: 'mobs',
cell: character.cell,
@@ -33,9 +38,10 @@ define([
skinId: character.skinId,
name: character.name,
class: character.class,
zoneName: character.zoneName || 'fjolarok',
zoneName: character.zoneName || serverConfig.defaultZone,
x: character.x,
y: character.y,
hidden: character.dead,
account: character.account,
instanceId: character.instanceId
});
@@ -56,7 +62,6 @@ define([
for (var s in blueprintStats.stats) {
stats.stats[s] = blueprintStats.stats[s];
}
stats.vitScale = blueprintStats.vitScale;

var gainStats = classes.stats[character.class].gainStats;
for (var s in gainStats) {
@@ -121,7 +126,7 @@ define([
io.sockets.emit('events', {
onGetMessages: [{
messages: [{
class: 'q3',
class: 'color-blueB',
message: this.obj.name + ' has come online'
}]
}],
@@ -163,6 +168,9 @@ define([
var physics = this.obj.instance.physics;

physics.removeObject(this.obj, this.obj.x, this.obj.y);
this.obj.dead = true;

this.obj.aggro.die();

if (!permadeath) {
var level = this.obj.stats.values.level;
@@ -181,20 +189,21 @@ define([
this.obj.x = spawnPos.x;
this.obj.y = spawnPos.y;

var syncer = this.obj.syncer;
syncer.o.x = this.obj.x;
syncer.o.y = this.obj.y;

physics.addObject(this.obj, this.obj.x, this.obj.y);

this.obj.stats.die(source);
} else {
this.obj.stats.dead = true;

process.send({
method: 'object',
serverId: this.obj.serverId,
obj: {
dead: true
}
});
} else {
process.send({
method: 'object',
serverId: this.obj.serverId,
obj: {
dead: true,
permadead: true
}
});
@@ -202,11 +211,18 @@ define([

this.obj.fireEvent('onAfterDeath', source);

this.obj.aggro.die();
this.obj.spellbook.die();
this.obj.effects.die();
},

respawn: function () {
var syncer = this.obj.syncer;
syncer.o.x = this.obj.x;
syncer.o.y = this.obj.y;

this.obj.aggro.move();

this.obj.instance.physics.addObject(this.obj, this.obj.x, this.obj.y);
},

move: function (msg) {


+ 7
- 5
src/server/components/reputation.js View File

@@ -40,8 +40,10 @@ define([
factionBlueprint = factions.getFaction(factionId);
} catch (e) {}

if (factionBlueprint == null)
if (factionBlueprint == null) {
console.log('No faction blueprint found');
return;
}

factionBlueprint = extend(true, {}, factionBase, factionBlueprint);

@@ -127,25 +129,25 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q1',
class: (action == 'gained') ? 'color-greenB' : 'color-redA',
message: 'you ' + action + ' ' + Math.abs(gain) + ' reputation with ' + blueprint.name,
type: 'rep'
}]
}, [this.obj.serverId]);

if (faction.tier != oldTier) {
this.sendMessage(blueprint.tiers[faction.tier].name, blueprint.name);
this.sendMessage(blueprint.tiers[faction.tier].name, blueprint.name, (faction.tier > oldTier));
this.obj.equipment.unequipFactionGear(faction.id, faction.tier);
}

this.syncFaction(factionId, fullSync);
},

sendMessage: function (tierName, factionName) {
sendMessage: function (tierName, factionName, didIncrease) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q4',
class: didIncrease ? 'color-greenB' : 'color-redA',
message: 'you are now ' + tierName + ' with ' + factionName,
type: 'rep'
}]


+ 65
- 25
src/server/components/social.js View File

@@ -1,9 +1,11 @@
define([
'world/atlas',
'config/roles'
'config/roles',
'misc/events'
], function (
atlas,
roles
roles,
events
) {
return {
type: 'social',
@@ -14,6 +16,8 @@ define([

customChannels: null,

messageHistory: [],

init: function (blueprint) {
this.obj.extendComponent('social', 'socialCommands', {});
},
@@ -22,16 +26,17 @@ define([
return {
type: 'social',
party: this.party,
customChannels: self ? this.customChannels : null
customChannels: self ? this.customChannels : null,
muted: this.muted
};
},

sendMessage: function (msg) {
this.obj.socket.emit('event', {
sendMessage: function (msg, color, target) {
(target || this.obj).socket.emit('event', {
event: 'onGetMessages',
data: {
messages: [{
class: 'q0',
class: color || 'q0',
message: msg,
type: 'chat'
}]
@@ -44,7 +49,7 @@ define([
this.obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-redA',
message: 'you are not in a party',
type: 'info'
}]
@@ -63,7 +68,7 @@ define([
player.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-grayB',
message: '(party: ' + charname + '): ' + message,
type: 'chat'
}]
@@ -84,7 +89,7 @@ define([
this.obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-redA',
message: 'syntax: $channel message',
type: 'info'
}]
@@ -95,7 +100,7 @@ define([
this.obj.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-redA',
message: 'you are not currently in channel: ' + channel,
type: 'info'
}]
@@ -108,7 +113,7 @@ define([
pList[i].socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-grayB',
message: '[' + channel + '] ' + this.obj.auth.charname + ': ' + message,
type: channel.trim()
}]
@@ -125,22 +130,57 @@ define([

msg.data.message = msg.data.message
.split('<')
.join('')
.join('&lt;')
.split('>')
.join('');
.join('&gt;');

if (!msg.data.message)
return;

if (msg.data.message.trim() == '')
return;

this.onBeforeChat(msg.data);
if (msg.data.ignore)
return;

if (this.muted) {
this.sendMessage('You have been muted from talking', 'color-redA');
return;
}

var messageString = msg.data.message;

var history = this.messageHistory;

var time = +new Date;
history.spliceWhere(h => ((time - h.time) > 5000));

if (history.length > 0) {
if (history[history.length - 1].msg == messageString) {
this.sendMessage('You have already sent that message', 'color-redA');
return;
} else if (history.length >= 3) {
this.sendMessage('You are sending too many messages', 'color-redA');
return;
}
}

history.push({
msg: messageString,
time: time
});

var charname = this.obj.auth.charname;

var msgStyle = roles.getRoleMessageStyle(this.obj) || ('q');
var msgStyle = roles.getRoleMessageStyle(this.obj) || ('color-grayB');

var messageString = msg.data.message;
var msgEvent = {
source: charname,
msg: messageString
};
events.emit('onBeforeSendMessage', msgEvent);
messageString = msgEvent.msg;
if (messageString[0] == '@') {
var playerName = '';
//Check if there's a space in the name
@@ -163,7 +203,7 @@ define([
event: 'onGetMessages',
data: {
messages: [{
class: msgStyle,
class: 'color-yellowB',
message: '(you to ' + playerName + '): ' + messageString,
type: 'chat'
}]
@@ -174,7 +214,7 @@ define([
event: 'onGetMessages',
data: {
messages: [{
class: msgStyle,
class: 'color-yellowB',
message: '(' + this.obj.name + ' to you): ' + messageString,
type: 'chat'
}]
@@ -223,8 +263,8 @@ define([
if (!source)
return;

source.social.sendMessage('invite sent');
this.sendMessage(source.name + ' has invited you to join a party');
source.social.sendMessage('invite sent', 'color-yellowB');
this.sendMessage(source.name + ' has invited you to join a party', 'color-yellowB');

this.obj.socket.emit('event', {
event: 'onGetInvite',
@@ -259,7 +299,7 @@ define([
var msg = source.name + ' has joined the party';
if (p == sourceId)
msg = 'you have joined a party';
player.social.sendMessage(msg);
player.social.sendMessage(msg, 'color-yellowB');

player
.socket.emit('event', {
@@ -274,7 +314,7 @@ define([
if (!target)
return;

this.sendMessage(target.name + ' declined your party invitation');
this.sendMessage(target.name + ' declined your party invitation', 'color-redA');
},

//Gets called on the player that requested to leave
@@ -355,7 +395,7 @@ define([
//Gets called on the player that requested the removal
removeFromParty: function (msg) {
if (!this.isPartyLeader) {
this.sendMessage('you are not the party leader');
this.sendMessage('you are not the party leader', 'color-redA');
return;
}

@@ -371,7 +411,7 @@ define([
onGetParty: [this.party],
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-yellowB',
message: target.name + ' has been removed from the party'
}]
}]
@@ -381,7 +421,7 @@ define([
target.socket.emit('events', {
onGetMessages: [{
messages: [{
class: 'q0',
class: 'color-redA',
message: 'you have been removed from the party'
}]
}],
@@ -397,7 +437,7 @@ define([
this.isPartyLeader = null;
this.updatePartyOnThread();

this.sendMessage('your party has been disbanded');
this.sendMessage('your party has been disbanded', 'color-yellowB');
}
},



+ 12
- 2
src/server/components/spellbook.js View File

@@ -155,7 +155,6 @@ define([
var type = runeSpell.type;
var playerSpell = playerSpells.spells.find(s => (s.name.toLowerCase() == runeSpell.name.toLowerCase())) || playerSpells.spells.find(s => (s.type == type));
var playerSpellConfig = playerSpellsConfig.spells[runeSpell.name.toLowerCase()] || playerSpellsConfig.spells[runeSpell.type];

if (!playerSpellConfig)
return -1;

@@ -167,7 +166,7 @@ define([
var builtSpell = extend(true, {
type: runeSpell.type,
values: {}
}, playerSpell, playerSpellConfig);
}, playerSpell, playerSpellConfig, runeSpell);

for (var r in builtSpell.random) {
var range = builtSpell.random[r];
@@ -576,6 +575,17 @@ define([

events: {
beforeRezone: function () {
this.spells.forEach(function (s) {
if (s.active) {
s.active = false;

var reserve = s.manaReserve;

if ((reserve) && (reserve.percentage))
this.obj.stats.addStat('manaReservePercent', -reserve.percentage);
}
}, this);

var callbacks = this.callbacks;
var cLen = callbacks.length;
for (var i = 0; i < cLen; i++) {


+ 194
- 90
src/server/components/stats.js View File

@@ -1,11 +1,11 @@
define([
'config/animations',
'config/loginRewards',
'config/classes'
'config/spirits',
'misc/scheduler'
], function (
animations,
loginRewards,
classes
classes,
scheduler
) {
var baseStats = {
mana: 20,
@@ -26,16 +26,29 @@ define([
itemQuantity: 0,
regenHp: 0,
regenMana: 5,

addCritChance: 0,
addCritMultiplier: 0,
addAttackCritChance: 0,
addAttackCritMultiplier: 0,
addSpellCritChance: 0,
addSpellCritMultiplier: 0,

critChance: 5,
critMultiplier: 150,
attackCritChance: 0,
attackCritMultiplier: 0,
spellCritChance: 0,
spellCritMultiplier: 0,

armor: 0,
dmgPercent: 0,
vit: 0,

blockAttackChance: 0,
blockSpellChance: 0,
dodgeAttackChance: 0,
dodgeSpellChance: 0,

attackSpeed: 0,
castSpeed: 0,
@@ -72,7 +85,12 @@ define([
values: baseStats,
originalValues: null,

vitScale: 10,
statScales: {
vitToHp: 10,
strToArmor: 1,
intToMana: (1 / 6),
dexToDodge: (1 / 12)
},

syncer: null,

@@ -81,7 +99,8 @@ define([
played: 0,
lastLogin: null,
loginStreak: 0,
mobKillStreaks: {}
mobKillStreaks: {},
lootStats: {}
},

dead: false,
@@ -113,7 +132,7 @@ define([
},

update: function () {
if (((this.obj.mob) && (!this.obj.follower)) || (this.dead))
if (((this.obj.mob) && (!this.obj.follower)) || (this.obj.dead))
return;

var values = this.values;
@@ -173,36 +192,42 @@ define([
},

addStat: function (stat, value) {
var values = this.values;

if (['lvlRequire', 'allAttributes'].indexOf(stat) == -1)
this.values[stat] += value;
values[stat] += value;

var sendOnlyToSelf = (['hp', 'hpMax', 'mana', 'manaMax', 'vit'].indexOf(stat) == -1);

this.obj.syncer.setObject(sendOnlyToSelf, 'stats', 'values', stat, this.values[stat]);

if (stat == 'addCritChance') {
this.values.critChance += (0.05 * value);
this.obj.syncer.setObject(true, 'stats', 'values', 'critChance', this.values.critChance);
} else if (stat == 'addCritMultiplier') {
this.values.critMultiplier += value;
this.obj.syncer.setObject(true, 'stats', 'values', 'critMultiplier', this.values.critMultiplier);
this.obj.syncer.setObject(sendOnlyToSelf, 'stats', 'values', stat, values[stat]);
if (sendOnlyToSelf)
this.obj.syncer.setObject(false, 'stats', 'values', stat, values[stat]);

if (['addCritChance', 'addAttackCritChance', 'addSpellCritChance'].indexOf(stat) > -1) {
var morphStat = stat.substr(3);
morphStat = morphStat[0].toLowerCase() + morphStat.substr(1);
this.addStat(morphStat, (0.05 * value));
} else if (['addCritMultiplier', 'addAttackCritMultiplier', 'addSpellCritMultiplier'].indexOf(stat) > -1) {
var morphStat = stat.substr(3);
morphStat = morphStat[0].toLowerCase() + morphStat.substr(1);
this.addStat(morphStat, value);
} else if (stat == 'vit') {
this.values.hpMax += (value * this.vitScale);
this.obj.syncer.setObject(true, 'stats', 'values', 'hpMax', this.values.hpMax);
this.obj.syncer.setObject(false, 'stats', 'values', 'hpMax', this.values.hpMax);
this.addStat('hpMax', (value * this.statScales.vitToHp));
} else if (stat == 'allAttributes') {
['int', 'str', 'dex'].forEach(function (s) {
this.values[s] += value;
this.obj.syncer.setObject(true, 'stats', 'values', s, this.values[s]);
this.addStat(s, value)
}, this);
} else if (stat == 'elementAllResist') {
['arcane', 'frost', 'fire', 'holy', 'poison'].forEach(function (s) {
var element = 'element' + (s[0].toUpperCase() + s.substr(1)) + 'Resist';

this.values[element] += value;
this.obj.syncer.setObject(true, 'stats', 'values', element, this.values[element]);
this.addStat(element, value);
}, this);
}
} else if (stat == 'str')
this.addStat('armor', (value * this.statScales.strToArmor));
else if (stat == 'int')
this.addStat('manaMax', (value * this.statScales.intToMana));
else if (stat == 'dex')
this.addStat('dodgeAttackChance', (value * this.statScales.dexToDodge));
},

calcXpMax: function () {
@@ -251,10 +276,13 @@ define([
didLevelUp = true;
values.xp -= values.xpMax;
this.obj.syncer.setObject(true, 'stats', 'values', 'xp', values.xp);
if (this.originalValues)
if (this.originalValues) {
this.originalValues.level++;
else
values.level++;
}

if (values.originalLevel)
values.originalLevel++;
values.level++;

this.obj.fireEvent('onLevelUp', (this.originalValues || this.values).level);

@@ -265,8 +293,7 @@ define([

var gainStats = classes.stats[this.obj.class].gainStats;
for (var s in gainStats) {
values[s] += gainStats[s];
this.obj.syncer.setObject(true, 'stats', 'values', s, values[s]);
this.addStat(s, gainStats[s]);
}

this.obj.spellbook.calcDps();
@@ -299,19 +326,36 @@ define([

if (didLevelUp) {
var maxLevel = this.obj.instance.zone.level[1]
if (maxLevel < (this.originalValues || values).level)
if (maxLevel < (this.originalValues || values).level) {
this.rescale(maxLevel, false);
else {
} else {
this.obj.syncer.setObject(true, 'stats', 'values', 'hpMax', values.hpMax);
this.obj.syncer.setObject(true, 'stats', 'values', 'level', values.level);
this.obj.syncer.setObject(true, 'stats', 'values', 'level', this.values.level);
this.obj.syncer.setObject(true, 'stats', 'values', 'originalLevel', this.values.originalLevel);
this.obj.syncer.setObject(false, 'stats', 'values', 'hpMax', values.hpMax);
this.obj.syncer.setObject(false, 'stats', 'values', 'level', values.level);
this.obj.syncer.setObject(false, 'stats', 'values', 'level', this.values.level);
this.obj.syncer.setObject(true, 'stats', 'values', 'originalLevel', this.values.originalLevel);
}
}

var originalValues = this.originalValues;
if (originalValues) {
originalValues.xp = values.xp;
originalValues.xpMax = values.xpMax;
originalValues.xpTotal = values.xpTotal;
}
},

kill: function (target) {
if (target.player)
return;

var level = target.stats.values.level;
var mobDiffMult = 1;
if (target.isRare)
mobDiffMult = 2;
else if (target.isChampion)
mobDiffMult = 5;

//Who should get xp?
var aggroList = target.aggro.list;
@@ -341,9 +385,10 @@ define([
//We don't currently do this for quests/herb gathering
var sourceLevel = a.obj.stats.values.level;
var levelDelta = level - sourceLevel;
var amount = level * 10 * mult;

var amount = null;
if (Math.abs(levelDelta) <= 10)
amount = ~~(((sourceLevel + levelDelta) * 10) * Math.pow(1 - (Math.abs(levelDelta) / 10), 2) * mult);
amount = ~~(((sourceLevel + levelDelta) * 10) * Math.pow(1 - (Math.abs(levelDelta) / 10), 2) * mult * mobDiffMult);
else
amount = 0;

@@ -355,24 +400,87 @@ define([
},

die: function (source) {
this.values.hp = this.values.hpMax;
this.values.mana = this.values.manaMax;

this.obj.syncer.setObject(false, 'stats', 'values', 'hp', this.values.hp);
this.obj.syncer.setObject(false, 'stats', 'values', 'mana', this.values.mana);
var obj = this.obj;
var values = this.values;

this.syncer.queue('onGetDamage', {
id: this.obj.id,
id: obj.id,
event: true,
text: 'death'
});

obj.syncer.set(true, null, 'dead', true);

var obj = obj;
var syncO = obj.syncer.o;

obj.hidden = true;
obj.nonSelectable = true;
syncO.hidden = true;
syncO.nonSelectable = true;

var xpLoss = ~~Math.min(values.xp, values.xpMax / 10);

values.xp -= xpLoss;
obj.syncer.setObject(true, 'stats', 'values', 'xp', values.xp);

this.syncer.queue('onDeath', {
x: this.obj.x,
y: this.obj.y,
source: source.name
}, [this.obj.serverId]);
source: source.name,
xpLoss: xpLoss
}, [obj.serverId]);

obj.instance.syncer.queue('onGetObject', {
x: obj.x,
y: obj.y,
components: [{
type: 'attackAnimation',
row: 0,
col: 4
}]
});
},

respawn: function () {
this.obj.syncer.set(true, null, 'dead', false);

var obj = this.obj;
var syncO = obj.syncer.o;

this.obj.dead = false;
var values = this.values;

values.hp = values.hpMax;
values.mana = values.manaMax;

obj.syncer.setObject(false, 'stats', 'values', 'hp', values.hp);
obj.syncer.setObject(false, 'stats', 'values', 'mana', values.mana);

obj.hidden = false;
obj.nonSelectable = false;
syncO.hidden = false;
syncO.nonSelectable = false;

process.send({
method: 'object',
serverId: this.obj.serverId,
obj: {
dead: false
}
});

obj.instance.syncer.queue('onGetObject', {
x: obj.x,
y: obj.y,
components: [{
type: 'attackAnimation',
row: 0,
col: 4
}]
});

this.obj.player.respawn();
},

takeDamage: function (damage, threatMult, source) {
source.fireEvent('beforeDealDamage', damage, this.obj);
this.obj.fireEvent('beforeTakeDamage', damage, source);
@@ -390,6 +498,8 @@ define([
if (amount > this.values.hp)
amount = this.values.hp;

damage.dealt = amount;

this.values.hp -= amount;
var recipients = [];
if (this.obj.serverId != null)
@@ -402,7 +512,7 @@ define([
recipients.push(this.obj.follower.master.serverId);

if (recipients.length > 0) {
if (!damage.blocked) {
if ((!damage.blocked) && (!damage.dodged)) {
this.syncer.queue('onGetDamage', {
id: this.obj.id,
source: source.id,
@@ -449,7 +559,7 @@ define([

this.obj.instance.syncer.queue('onGetMessages', {
messages: {
class: 'color-red',
class: 'color-redA',
message: `(level ${this.values.level}) ${this.obj.name} has forever left the shores of the living.`
}
});
@@ -480,12 +590,12 @@ define([
var aggroList = this.obj.aggro.list;
var aLen = aggroList.length;
for (var i = 0; i < aLen; i++) {
var a = aggroList[i].obj;
var a = aggroList[i];

if (a.serverId == null)
if ((!a.threat) || (a.obj.serverId == null))
continue;

this.obj.inventory.dropBag(a.serverId, killSource);
this.obj.inventory.dropBag(a.obj.serverId, killSource);
}
}
}
@@ -554,9 +664,13 @@ define([
delete this.sessionDuration;
}

var values = extend(true, {}, this.originalValues || this.values);
values.hp = this.values.hp;
values.mana = this.values.mana;

return {
type: 'stats',
values: this.originalValues || this.values,
values: values,
stats: this.stats
};
},
@@ -589,47 +703,15 @@ define([

onLogin: function () {
var stats = this.stats;

var scheduler = require('misc/scheduler');
var time = scheduler.getTime();
var lastLogin = stats.lastLogin;
if ((!lastLogin) || (lastLogin.day != time.day)) {
var daysSkipped = 1;
if (lastLogin) {
if (time.day > lastLogin.day)
daysSkipped = time.day - lastLogin.day;
else {
var daysInMonth = scheduler.daysInMonth(lastLogin.month);
daysSkipped = (daysInMonth - lastLogin.day) + time.day;

for (var i = lastLogin.month + 1; i < time.month - 1; i++) {
daysSkipped += scheduler.daysInMonth(i);
}
}
}

if (daysSkipped == 1) {
stats.loginStreak++;
if (stats.loginStreak > 21)
stats.loginStreak = 21;
} else {
stats.loginStreak -= (daysSkipped - 1);
if (stats.loginStreak < 1)
stats.loginStreak = 1;
}

var mail = this.obj.instance.mail;
var rewards = loginRewards.generate(stats.loginStreak);
mail.sendMail(this.obj.name, rewards);
} else
this.obj.instance.mail.getMail(this.obj.name);

stats.lastLogin = time;

this.obj.instance.mail.getMail(this.obj.name);
},

rescale: function (level, isMob) {
if (level >= (this.originalValues || this.values).level)
return;
if (level > this.values.level)
level = this.values.level;

var sync = this.obj.syncer.setObject.bind(this.obj.syncer);

@@ -647,7 +729,7 @@ define([

var gainStats = classes.stats[this.obj.class].gainStats;
for (var s in gainStats) {
newValues[s] += (gainStats[s] * level);
this.addStat(s, (gainStats[s] * level));
}

newValues.level = level;
@@ -704,7 +786,26 @@ define([
return Math.max(0, (10000 - Math.pow(killStreak, 2)) / 10000);
},

canGetMobLoot: function (mob) {
if (!mob.inventory.dailyDrops)
return true;

var lootStats = this.stats.lootStats[mob.name];
var time = scheduler.getTime();
if (!lootStats) {
this.stats.lootStats[mob.name] = time;
} else
return ((lootStats.day != time.day), (lootStats.month != time.month));
},

events: {
transferComplete: function () {
var maxLevel = this.obj.instance.zone.level[1];
if (maxLevel > this.obj.stats.values.level)
maxLevel = this.obj.stats.values.level;
this.obj.stats.rescale(maxLevel);
},

afterKillMob: function (mob) {
var mobKillStreaks = this.stats.mobKillStreaks;
var mobName = mob.name;
@@ -726,7 +827,7 @@ define([
},

beforeGetXp: function (event) {
if (!event.target.mob)
if ((!event.target.mob) && (!event.target.player))
return;

event.amount *= this.getKillStreakCoefficient(event.target.name);
@@ -737,6 +838,9 @@ define([
return;

event.chanceMultiplier *= this.getKillStreakCoefficient(event.source.name);

if ((event.chanceMultiplier > 0) && (!this.canGetMobLoot(event.source)))
event.chanceMultiplier = 0;
},

afterMove: function (event) {


+ 4
- 1
src/server/components/syncer.js View File

@@ -78,7 +78,7 @@ define([

obj[property] = value;
},
setArray: function (self, cpnType, property, value) {
setArray: function (self, cpnType, property, value, noDuplicate) {
var o = this.o;
if (self)
o = this.oSelf;
@@ -94,6 +94,9 @@ define([
if (cpn[property] == null)
cpn[property] = [];

if ((noDuplicate) && (cpn[property].find(f => (f == value))))
return;

cpn[property].push(value);
},



+ 7
- 7
src/server/components/trade.js View File

@@ -41,7 +41,7 @@ define([
item.name = skinBlueprint.name;
item.sprite = skinBlueprint.sprite;
item.spritesheet = skinBlueprint.spritesheet;
id = item.id;
item.skinId = skinBlueprint.id;
}

item.id = id;
@@ -155,7 +155,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: `you can't afford that item`,
type: 'info'
}]
@@ -171,13 +171,13 @@ define([
}

if (item.type == 'skin') {
var haveSkin = this.obj.auth.doesOwnSkin(item.id);
var haveSkin = this.obj.auth.doesOwnSkin(item.skinId);

if (haveSkin) {
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-redA',
message: `you have already unlocked that skin`,
type: 'info'
}]
@@ -226,13 +226,13 @@ define([

this.obj.inventory.getItem(clonedItem);
} else {
this.obj.auth.saveSkin(item.id);
this.obj.auth.saveSkin(item.skinId);

this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
message: item.name + ' (unlocked)',
class: 'color-greenB',
message: 'Unlocked skin: ' + item.name,
type: 'info'
}]
}, [this.obj.serverId]);


+ 1
- 1
src/server/components/wardrobe.js View File

@@ -120,7 +120,7 @@ define([
onGetSkins: function (obj, skins) {
this.obj.instance.syncer.queue('onGetWardrobeSkins', {
id: this.obj.id,
skins: skins[obj.class]
skins: skins
}, [obj.serverId]);
}
};


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

@@ -1,72 +0,0 @@
define([
'../misc/events'
], function (
events
) {
var classes = {
list: ['bear', 'owl', 'lynx'],
portraits: {
bear: {
x: 0,
y: 0
},
owl: {
x: 2,
y: 0
},
lynx: {
x: 3,
y: 0
}
},
spells: {
owl: ['magic missile', 'ice spear'],
bear: ['slash', 'charge'],
lynx: ['flurry', 'smokebomb']
},
stats: {
owl: {
values: {
hpMax: 50
},
vitScale: 10,
gainStats: {
int: 1
}
},
bear: {
values: {
hpMax: 80
},
vitScale: 10,
gainStats: {
str: 1
}
},
lynx: {
values: {
hpMax: 70
},
vitScale: 10,
gainStats: {
dex: 1
}
}
},
weapons: {
owl: 'Gnarled Staff',
lynx: 'Dagger',
bear: 'Sword'
},

getSpritesheet: function (className) {
return this.stats[className].spritesheet || 'characters';
},

init: function () {
events.emit('onBeforeGetClasses', classes);
}
};

return classes;
});

+ 2
- 2
src/server/config/effects/effectHolyVengeance.js View File

@@ -10,9 +10,9 @@ define([

events: {
afterDealDamage: function(damage, target) {
damage.amount *= 0.5;
damage.dealt *= 0.5;
this.obj.stats.getHp(damage, this.obj);
}
}
};
});
});

+ 2
- 2
src/server/config/effects/effectRegenHp.js View File

@@ -9,11 +9,11 @@ define([
amount: 1,

init: function () {
this.obj.stats.addStat('regenHp', this.amount * 3);
this.obj.stats.addStat('regenHp', this.amount);
},

destroy: function () {
this.obj.stats.addStat('regenHp', -(this.amount * 3));
this.obj.stats.addStat('regenHp', -this.amount);
},

update: function () {


+ 5
- 3
src/server/config/factions/akarei.js View File

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

result = {
factionId: 'akarei',
chance: chanceRoll,
properties: {
chance: chanceRoll,
},
text: chanceRoll + '% chance on crit to cast a lightning bolt',
events: {}
};
@@ -60,7 +62,7 @@ define([
var effect = item.effects.find(e => (e.factionId == 'akarei'));

var roll = Math.random() * 100;
if (roll >= effect.chance)
if (roll >= effect.properties.chance)
return;

var cbExplode = function (target) {
@@ -70,7 +72,7 @@ define([
var damage = combat.getDamage({
source: this,
target: target,
damage: 1,
damage: item.level * 5,
element: 'arcane',
noCrit: true
});


+ 17
- 0
src/server/config/factions/players.js View File

@@ -0,0 +1,17 @@
define([

], function (

) {
return {
id: 'players',
name: 'Players',
description: `The players faction.`,

relations: {

},

noGainRep: true
};
});

+ 23
- 8
src/server/config/itemEffects/damageSelf.js View File

@@ -1,10 +1,12 @@
define([
'combat/combat'
], function (
combat
) {
return {
events: {
element: null,

onGetText: function (item) {
var rolls = item.effects.find(e => (e.type == 'damageSelf')).rolls;

@@ -12,14 +14,27 @@ define([
},

afterDealDamage: function (item, damage, target) {
var rolls = item.effects.find(e => (e.type == 'damageSelf')).rolls;
var effect = item.effects.find(e => (e.type == 'damageSelf'));
var rolls = effect.rolls;

var amount = (damage.dealt / 100) * rolls.percentage;

var newDamage = combat.getDamage({
source: {
stats: {
values: {}
}
},
isAttack: false,
target: this,
damage: amount,
element: effect.properties.element,
noCrit: true
});

var amount = (damage.amount / 100) * rolls.percentage;
newDamage.noEvents = true;

this.stats.takeDamage({
amount: amount,
noEvents: true
}, 1, this);
this.stats.takeDamage(newDamage, 1, this);
}
}
};


+ 1
- 1
src/server/config/itemEffects/healOnCrit.js View File

@@ -36,7 +36,7 @@ define([
if (chanceRoll >= (rolls.chance || 100))
return;

var amount = rolls.amount || ((damage.amount / 100) * rolls.percentage);
var amount = rolls.amount || ((damage.dealt / 100) * rolls.percentage);

this.stats.getHp({
amount: amount


+ 14
- 4
src/server/config/maps/cave/zone.js View File

@@ -269,16 +269,19 @@ module.exports = {
'akarei scout': {
level: 20,
faction: 'akarei',
attackable: false,
deathRep: -3
},
'biorn': {
level: 22,
attackable: false,
walkDistance: 0,
faction: 'akarei',
deathRep: -3
},
'veleif': {
level: 22,
attackable: false,
walkDistance: 0,
faction: 'akarei',
deathRep: -3
@@ -286,11 +289,13 @@ module.exports = {

'akarei artificer': {
level: 24,
attackable: false,
faction: 'akarei',
deathRep: -6
},
'thaumaturge yala': {
level: 30,
attackable: false,
walkDistance: 0,
faction: 'akarei',

@@ -308,8 +313,11 @@ module.exports = {
properties: {
cpnTrade: {
items: {
min: 5,
max: 10,
min: 2,
max: 5,
minLevel: 14,
maxLevel: 18,
slot: 'neck',
extra: []
},
faction: {
@@ -318,7 +326,7 @@ module.exports = {
},
markup: {
buy: 0.25,
sell: 10
sell: 20
}
}
}
@@ -330,6 +338,7 @@ module.exports = {
cpnBlocker: {
init: function () {
this.obj.instance.physics.setCollision(this.obj.x, this.obj.y, true);
this.obj.instance.objects.notifyCollisionChange(this.obj.x, this.obj.y, true);
}
}
}
@@ -523,6 +532,7 @@ module.exports = {
walls.forEach(function (w) {
w.destroyed = true;
physics.setCollision(w.x, w.y, false);
this.obj.instance.objects.notifyCollisionChange(w.x, w.y, false);

syncer.queue('onGetObject', {
x: w.x,
@@ -533,7 +543,7 @@ module.exports = {
col: 4
}]
});
});
}, this);
}
}
}


+ 11
- 0
src/server/config/maps/estuary/zone.js View File

@@ -25,6 +25,17 @@ module.exports = {
rolls: 1,
magicFind: 500
}
},

rare: {
hpMult: 7,
dmgMult: 3,

drops: {
chance: 100,
rolls: 1,
magicFind: 2000
}
}
},
'giant gull': {


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


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


+ 16
- 6
src/server/config/maps/sewer/zone.js View File

@@ -9,7 +9,7 @@ module.exports = {
},

rat: {
faction: 'flolgard',
faction: 'fjolgard',
grantRep: {
fjolgard: 6
},
@@ -23,10 +23,12 @@ module.exports = {
blueprints: [{
chance: 2,
type: 'key',
noSalvage: true,
name: 'Rusted Key',
keyId: 'rustedSewer',
singleUse: true,
sprite: [12, 1]
sprite: [12, 1],
quantity: 1
}]
}
},
@@ -37,7 +39,7 @@ module.exports = {
},

stinktooth: {
faction: 'flolgard',
faction: 'fjolgard',
grantRep: {
fjolgard: 15
},
@@ -51,10 +53,12 @@ module.exports = {
blueprints: [{
chance: 0.5,
type: 'key',
noSalvage: true,
name: 'Rusted Key',
keyId: 'rustedSewer',
singleUse: true,
sprite: [12, 1]
sprite: [12, 1],
quantity: 1
}]
}
},
@@ -105,10 +109,12 @@ module.exports = {
blueprints: [{
chance: 100,
type: 'key',
noSalvage: true,
name: 'Rusted Key',
keyId: 'rustedSewer',
singleUse: true,
sprite: [12, 1]
sprite: [12, 1],
quantity: 1
}]
}
},
@@ -122,7 +128,7 @@ module.exports = {
sewerdoor: {
properties: {
cpnDoor: {
autoClose: 15,
autoClose: 171,
locked: true,
key: 'rustedSewer',
destroyKey: true
@@ -138,6 +144,10 @@ module.exports = {
properties: {
cpnDoor: {}
}
},

treasure: {
cron: '0 2 * * *'
}
}
};

+ 20
- 2
src/server/config/prophecies/titangrip.js View File

@@ -1,7 +1,7 @@
define([
'items/generators/stats'
], function (
generatorStats
) {
return {
type: 'titangrip',
@@ -20,6 +20,10 @@ define([
return;

var stats = item.stats;
var maxLevel = this.obj.instance.zone.level[1];
if (maxLevel < item.level)
stats = generatorStats.rescale(item, maxLevel);

for (var s in stats) {
var val = stats[s];

@@ -31,11 +35,25 @@ define([
return;

var stats = item.stats;
var maxLevel = this.obj.instance.zone.level[1];
if (maxLevel < item.level)
stats = generatorStats.rescale(item, maxLevel);

for (var s in stats) {
var val = stats[s];

this.obj.stats.addStat(s, -val);
}
},

afterRescaleItemStats: function (item) {
if (['oneHanded', 'twoHanded'].indexOf(item.slot) == -1)
return;

var stats = item.stats;
for (var s in stats) {
stats[s] *= 2;
}
}
}
};


+ 7
- 2
src/server/config/quests/questBuilder.js View File

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

if (template)
quest.xp = template.xp;
else
quest.xp = Math.pow(this.instance.spawners.zone.level, 2) * 10;

//Calculate next id
var id = 0;
@@ -62,6 +60,13 @@ define([
quest.obj = obj;
quest.zoneName = zoneName;

if (!template) {
var level = this.instance.spawners.zone.level;
level = level[0];
var xp = ~~(level * 22 * quest.getXpMultiplier());
quest.xp = xp;
}

if (!oQuests.obtain(quest, !!template))
this.obtain(obj, template);
}


+ 10
- 1
src/server/config/quests/templates/questGatherResource.js View File

@@ -38,6 +38,15 @@ define([
return true;
},

getXpMultiplier: function () {
if (this.requiredQuality == 2)
return 8;
else if (this.requiredQuality == 1)
return 6;
else
return this.need;
},

updateDescription: function () {
var typeName = this.typeName;
if (this.requiredQuality > 0)
@@ -55,7 +64,7 @@ define([
afterGatherResource: function (gatherResult) {
if (gatherResult.nodeType != this.gatherType)
return;
else if ((this.requiredQuality) && (gatherResult.items[0].quality != this.requiredQuality))
else if ((this.requiredQuality) && (gatherResult.items[0].quality < this.requiredQuality))
return;

if ((this.obj.zoneName != this.zoneName) || (this.have >= this.need))


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

@@ -34,7 +34,8 @@ define([
(mobBlueprint.attackable) ||
(mobBlueprint.attackable == null)
) &&
(mobBlueprint.level <= ~~(this.obj.stats.values.level * 1.35))
(mobBlueprint.level <= ~~(this.obj.stats.values.level * 1.35)) &&
(mobCounts[m] > 1)
);
}, this);

@@ -57,6 +58,10 @@ define([
return true;
},

getXpMultiplier: function () {
return this.need;
},

events: {
afterKillMob: function (mob) {
if ((mob.name.toLowerCase() != this.mobName.toLowerCase()) || (this.have >= this.need))


+ 14
- 1
src/server/config/quests/templates/questLoot.js View File

@@ -64,6 +64,19 @@ define([
return true;
},

getXpMultiplier: function () {
var multiplier = 1;

if (!this.quality)
multiplier *= 8;
else if (this.quality == 2)
multiplier *= 6;
else if (this.quality == 1)
multiplier *= 4;

return multiplier;
},

events: {
afterLootMobItem: function (item) {
if (
@@ -71,7 +84,7 @@ define([
(this.obj.zoneName != this.zoneName) ||
(
(this.quality) &&
(item.quality != this.quality)
(item.quality < this.quality)
) ||
(
(this.slot.indexOf) &&


+ 4
- 0
src/server/config/quests/templates/questLootGen.js View File

@@ -48,6 +48,10 @@ define([
return true;
},

getXpMultiplier: function () {
return (this.need * 1.5);
},

oComplete: function () {
var inventory = this.obj.inventory;
var item = inventory.items.find((i => i.name == this.item.name).bind(this));


+ 3
- 3
src/server/config/quests/templates/questTemplate.js View File

@@ -16,7 +16,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-yellowB',
message: 'quest obtained (' + this.name + ')'
}]
}, [this.obj.serverId]);
@@ -34,7 +34,7 @@ define([
this.obj.instance.syncer.queue('onGetMessages', {
id: this.obj.id,
messages: [{
class: 'q0',
class: 'color-yellowB',
message: 'quest ready for turn-in (' + this.name + ')'
}]
}, [this.obj.serverId]);
@@ -53,7 +53,7 @@ define([
obj.instance.syncer.queue('onGetMessages', {
id: obj.id,
messages: [{
class: 'q0',
class: 'color-yellowB',
message: 'quest completed (' + this.name + ')'
}]
}, [obj.serverId]);


+ 0
- 53
src/server/config/roleSkins.js View File

@@ -1,53 +0,0 @@
define([

], function (

) {
var empty = [];
var regular = ['1.0', '1.2', '1.6'];

var mtx = [
['10.0', '10.1', '10.2', '10.3', '10.4'],
['11.0', '11.1', '11.2', '11.3', '11.4']
];
var pA = [];
var pB = [];
var pC = [];
var pD = [];

mtx.forEach(function (m) {
m.forEach(function (s, i) {
var has = [pD];
if (i <= 2)
has.push(pC);
if (i <= 1)
has.push(pB);
if (i == 0)
has.push(pA);

has.forEach(function (h) {
h.push(s);
});
});
});

return [
//Regular Player
empty.concat(...regular), [],
[],
[],
[],
//Moderator
empty.concat(...regular),
//Patron Level 1
empty.concat(...regular, ...pA),
//Patron Level 2
empty.concat(...regular, ...pB),
//Patron Level 3
empty.concat(...regular, ...pC),
//Patron Level 4
empty.concat(...regular, ...pD),
//Admin
['*']
];
});

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save