· 9 years ago · Oct 22, 2016, 02:36 AM
1/*global $:true playerID:true sendMessageDirect:true resting:true
2 meditating:true
3*/
4
5
6'use strict';
7
8/*****************************************************************************\
9| Form injection code, uses jquery |
10\*****************************************************************************/
11
12//Add some elements so the user can select paths and options
13$("<div id='pathAndLoop' class='panel col-xs-12 col-lg-8' style='padding:10px;float:left;margin-bottom:10px'>\
14 <div class='panel col-xs-12 col-lg-6' style='padding:10px;float:left;margin-bottom:10px'>\
15 <div id='roomSelect' class='panel col-xs-12 col-lg-12' style='padding:10px;float:left;margin-bottom:10px'>\
16 <p>Pathing and Looping - Old style: Select a starting and ending room then choose Walk or Run</p>\
17 <p>Starting Room / Loop</p>\
18 Group: <select id='startGroupSelect' onchange=\"groupSelected('startGroupSelect','startPathSelect')\" style='width:250px;'></select>\
19 <br />Room: <select id='startPathSelect' onchange='displayPath()' style='width:250px;'></select>\
20 <br />\
21 <button id='pathLoop'class='btn btn-default' onclick=\"lookupPathAndMove('startPathSelect','endPathSelect','stepDelay',false,true)\">Loop</button>\
22 <button class='btn btn-danger' onclick=\"stopWalking('end of loop')\">End Loop</button>\
23 <br /><br /><p>Ending Room (n/a for loops)</p>\
24 Group: <select id='endGroupSelect' onchange=\"groupSelected('endGroupSelect','endPathSelect')\" style='width:250px;'></select>\
25 <br />Room: <select id='endPathSelect' onchange='displayPath()' style='width:250px;'></select>\
26 <button id='pathWalk' class='btn btn-default' onclick=\"lookupPathAndMove('startPathSelect','endPathSelect','stepDelay',false,false)\">Walk</button>\
27 <button id='pathRun'class='btn btn-default' onclick=\"lookupPathAndMove('startPathSelect','endPathSelect','stepDelay',true,false)\">Run</button>\
28 <button class='btn btn-danger' onclick=\"stopWalking('now')\">STOP!</button>\
29 </div>\
30 <div id='pathControls' class='panel col-xs-12 col-lg-12' style='padding:10px;clear:left;margin-bottom:10px'>\
31 <br />Fine tune step delay (ms):\
32 <input type='number' id='stepDelay' name = 'stepDelay' min='1' value='2500' style='width:60px;'/>\
33 </div>\
34 </div>\
35 <div class='panel col-xs-12 col-lg-6' style='padding:10px;float:left;margin-bottom:10px'>\
36 <div id='divAutoPath' class='panel col-xs-12 col-lg-12' style='padding:10px;float:left;margin-bottom:10px'>\
37 <p>AutoMap Path, Select Destination \
38 <a href='#' id='editAutoRooms' data-toggle='modal' data-target='#mapEdit'>(edit map)</a>\</p>\
39 Group: <select id='autoGroup' onchange=\"autoGroupSelected('autoGroup', 'autoRoom', false)\" onfocus=\"autoGroupSelected('autoGroup', 'autoRoom', false)\" style='width:250px;'></select>\
40 <br />Room: <select id='autoRoom' onchange='displayAutoPath()' onfocus='displayAutoPath()' style='width:250px;'></select>\
41 <br />\
42 <br />\
43 <button id='autoMapWalk' class='btn btn-default'>Walk</button>\
44 <button id='autoMapRun' class='btn btn-default'>Run</button>\
45 <button class='btn btn-danger' onclick=\"stopWalking('now')\">STOP!</button>\
46 </div>\
47 <div id='autoCommands' class='panel col-xs-12 col-lg-12' style='padding:10px;clear:left;margin-bottom:10px'>\
48 <p>Auto Commands</p>\
49 <p>Enter a command and frequency below. Seperate multiple commands with a comma. Toggle sending on/off with the checkbox.</p>\
50 <p>Send auto commands:\
51 <input type='checkbox' id='sendAutoCmds' onchange=\"autoCmdCheck('autoCmd','autoCmdDelay','sendAutoCmds')\")/>\
52 </p>\
53 <p>Command(s):\
54 <input type='text' id='autoCmd' style='width:200px' value='' />\
55 </p>\
56 <p>Delay (sec):\
57 <input type='number' id='autoCmdDelay' name = 'autoCmdDelay' min='1' value='6' style='width:50px;'/>\
58 </p>\
59 </div>\
60 </div>\
61</div>\
62<div id='Path Management' class='panel col-xs-12 col-lg-8' style='padding:10px;float:left;margin-bottom:10px'>\
63 <div id='pathCreate' class='panel col-xs-12 col-lg-6' style='padding:10px;float:left;margin-bottom:10px'>\
64 <p>Path and Loop Creation</p>\
65 <p>Create a new path/loop by filling in the boxes below. Spaces will be removed.</p>\
66 <p>Starting room / Loop name:\
67 <input type='text' id='startingRoom' style='width:150px;' value='Start_Room_Name' />\
68 </p>\
69 <p>Ending room (n/a for loops):\
70 <input type='text' id='endingRoom' style='width:150px;' value='End_Room_Name' />\
71 </p>\
72 <p>Enter the steps for the path below, seperated by commas (n,s,e,w,ne,se,sw,nw,u,d):\
73 <br /><textarea id='newPath' rows='5' cols='40'></textarea>\
74 </p>\
75 <p>\
76 <button class='btn btn-default' onclick=\"newPath('CustomPaths_','path')\">Save Path</button>\
77 <button class='btn btn-default' onclick=\"newPath('CustomPaths_','loop')\">Save Loop</button>\
78 <button type='button' class='btn btn-success' style='float:right' data-toggle='modal' data-target='#importExportDialog'>Import/Export</button>\
79 </p>\
80 </div>\
81 <div id='pathGrouping' class='panel col-xs-12 col-lg-6' style='padding:10px;float:left;margin-bottom:10px'>\
82 <p>Path grouping</p>\
83 <p>Select a group from the dropdown box and add/remove paths. Note, paths are grouped based on the starting room.</p>\
84 <p>Select Group:  \
85 <select id='groupingGroupSelect' class='groupSelect' onchange=\"groupSelected('groupingGroupSelect','groupingCurPathSelect')\" style='width:225px;'></select>\
86 </p>\
87 <p>Current paths: \
88 <select id='groupingCurPathSelect' style='width:225px;'></select>\
89 </p>\
90 <p>All Paths:         \
91 <select id='groupingAllPathSelect' class='pathSelect' style='width:225px;'></select>\
92 </p>\
93 <p>\
94 <button class='btn btn-default' onclick=\"editGroup('add')\">Add Path</button>\
95 <button class='btn btn-default' onclick=\"editGroup('remove')\">Remove Path</button>\
96 <button class='btn btn-danger' onclick=\"deleteSelectedPath('CustomPaths_','groupingAllPathSelect','pathGroups_')\">Delete Path</button>\
97 </p>\
98 <p>\
99 <button class='btn btn-success' onclick=\"editGroup('new')\">New Group</button>\
100 <button class='btn btn-danger' onclick=\"editGroup('delete')\">Delete Group</button>\
101 </p>\
102 </div>\
103</div>\
104").insertAfter('#divExpTimer');
105
106//Div for addon status
107$("<div id='addonStatus'>\
108 <p>Addon Status: <label id=\"addonStatusText\"></label></p>\
109</div>").insertAfter('#mainScreen');
110
111//Modal dialog for path import/export
112$("<div id='importExportDialog' class='modal fade' tabindex='-1' role='dialog' aria-labelledby='pathImportExport'>\
113 <div class='modal-dialog'>\
114 <div class='modal-content'>\
115 <div class='modal-header'>\
116 <button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>\
117 <h4 class='modal-title'>Path Import and Export Tools</h4>\
118 </div>\
119 <div class='modal-body'>\
120 <p style='font-size:12px'>The following buttons and box will let you import and export paths. Special formatting is required for import so be careful when using this ability and only paste paths from a known good source. To import, paste a single, group, or mass path list into the box and click Import. For export, use the single, group, or all buttons. Single will export only the currently selected path, Group will export all paths in the selected group, and All will export all paths/groups.</p>\
121 Group: <select id='exportGroupSelect' class='groupSelect'></select><br />\
122 Path: <select id='exportPathSelect' class='pathSelect'></select><br />\
123 <textarea id='pathImportExport' rows='5' cols='70' style='max-width:550px'></textarea>\
124 </div>\
125 <div class='modal-footer'>\
126 <div style='margin:0 auto'>\
127 <button type='button' id='importPath' class='btn btn-success' data-dismiss='modal' style='float:left'>Import</button>\
128 </div>\
129 <div style='margin:0 auto; width:40%'>\
130 <button type='button' id='exportSingle' class='btn btn-primary' style='float:left'>Single</button>\
131 <button type='button' id='exportGroup' class='btn btn-primary' style='float:left'>Group</button>\
132 <button type='button' id='exportAll' class='btn btn-primary' style='float:left'>All</button>\
133 </div>\
134 <div style='margin:0 auto'>\
135 <button type='button' id='clearAllPaths' class='btn btn-danger'>Clear All Paths</button>\
136 <button type='button' class='btn btn-danger' data-dismiss='modal'>Close</button>\
137 </div>\
138 </div>\
139 </div>\
140 </div>\
141</div>").insertAfter('#divCharacterSetup');
142
143//Map window
144$("<div id='divMap' class='panel col-xs-12 col-lg-4' style='padding:10px;float:left;margin-bottom:10px'>\
145 <canvas id='mapCanvas' height='200' width='280'>Browser doesn't support canvas</canvas>\
146 <div id='canvasControls' style='float:right'>\
147 <a href='#' id='resetMap'>Reset</a><br />\
148 <a href='#' id='importMap'>Import</a><br />\
149 <input type='file' id='importFileBrowse' style='display:none'>\
150 <a href='#' id='exportMap'>Export</a><br />\
151 <a href='#' class='button' id='downloadMap'>Download</a><br />\
152 <a href='#' id='configMap' data-toggle='modal' data-target='#mapOptions'>Configure</a>\
153 <br /><br />\
154 <a href='#' id='bigMap'>Big Map</a><br />\
155 </div>\
156</div>").insertAfter('#divHealth');
157
158//Mapping option modal
159$("<div class='modal fade' id='mapOptions' data-backdrop='false' tabindex='-1' role='dialog' aria-labelledby='mapOptions' aria-hidden='true' style='top:10%;right:0%;left:auto;bottom:auto;'>\
160 <div class='modal-dialog modal-sm'>\
161 <div class='modal-content'>\
162 <div class='modal-header'>\
163 <button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>\
164 <h4 class='modal-title'>Configure the map</h4>\
165 </div>\
166 <div class='modal-body'>\
167 Rooms to draw: <input type='text' id='mapSize' style='width:150px;' value='1000' />\
168 <br />\
169 Room size (pixels): <input type='text' id='roomSize' style='width:150px;' value='10' />\
170 </div>\
171 <div class='modal-footer'>\
172 <button type='button' id='applyMapOptions' class='btn btn-success'>Apply</button>\
173 <button type='button' class='btn btn-danger' data-dismiss='modal'>Close</button>\
174 </div>\
175 </div>\
176 </div>\
177 </div>"
178).insertAfter('#divCharacterSetup');
179
180//Mapping room edit modal
181$("<div class='modal fade' id='mapEdit' data-backdrop='false' tabindex='-1' role='dialog' aria-labelledby='mapEdit'>\
182 <div class='modal-dialog'>\
183 <div class='modal-content'>\
184 <div class='modal-header'>\
185 <button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>\
186 <h4 class='modal-title'>Edit Auto Map Rooms</h4>\
187 </div>\
188 <div class='modal-body'>\
189 <p>Edit the <b>current room</b> (already loaded) or select a different room using the dropdowns below. \
190 Only unique rooms are shown other than the current room. Room commands should be seperated by a comma.\
191 <p>Select a group: <select id='mapGroupSel' onchange=\"autoGroupSelected('mapGroupSel', 'mapRoomSel', true)\" style='width:300px;'></select>\
192 <br />Select a room:  <select id='mapRoomSel' onchange=\"mapRoomSelected()\" onfocus=\"mapRoomSelected()\" style='width:300px;'></select></p>\
193 <p>Id: <input type='text' id='mapId' style='width:300px;' disabled /></p>\
194 <p>Name: <input type='text' id='mapName' style='width:300px;' disabled /></p>\
195 <p>Alt Name: <input type='text' id='mapAltName' style='width:300px;' /></p>\
196 <p>Group: <input type='text' id='mapGroup' style='width:300px;' /></p>\
197 <p>Room Commands: <input type='text' id='mapCmd' style='width:300px;' /></p>\
198 <p>Unlisted: <input type='checkbox' id='mapUnlisted'></p>\
199 </div>\
200 <div class='modal-footer'>\
201 <button type='button' id='applyMapEdits' class='btn btn-success'>Save</button>\
202 <button type='button' id='deleteRoom' class='btn btn-danger'>Delete Room</button>\
203 <button type='button' class='btn btn-danger' data-dismiss='modal'>Close</button>\
204 </div>\
205 </div>\
206 </div>\
207 </div> \
208").insertAfter('#divCharacterSetup');
209
210//Mapping export modal
211$("<div class='modal fade' id='mapImpExpModal' data-backdrop='false' tabindex='-1' role='dialog' aria-labelledby='mapImpExpModal'>\
212 <div class='modal-dialog'>\
213 <div class='modal-content'>\
214 <div class='modal-header'>\
215 <button type='button' class='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button>\
216 <h4 class='modal-title'>Import/Export Auto Map Rooms</h4>\
217 </div>\
218 <div class='modal-body'>\
219 <p>Use the buttons below to import and export the map. If you're importing, \
220 make sure to only use a good source because formatting is very important. Clicking \
221 export will prompt you to save a file after it is prepared.</p>\
222 <br />\
223 <textarea id='mapImpExpText' rows='5' cols='70' style='max-width:550px'></textarea>\
224 </div>\
225 <div class='modal-footer'>\
226 <button type='button' id='importMap' class='btn btn-success' style='float:left'>Import Map</button>\
227 <button type='button' id='exportMap' class='btn btn-success' style='float:left'>Export Map</button>\
228 <button type='button' class='btn btn-danger' data-dismiss='modal'>Close</button>\
229 </div>\
230 </div>\
231 </div>\
232 </div> \
233").insertAfter('#divCharacterSetup');
234
235//Constants used across many functions
236const START_PATH_SELECT_ID = 'startPathSelect';
237const END_PATH_SELECT_ID = 'endPathSelect';
238const START_GROUP_SELECT_ID = 'startGroupSelect';
239const END_GROUP_SELECT_ID = 'endGroupSelect';
240const PATH_LIST = 'CustomPaths_';
241const GROUP_LIST = 'pathGroups_';
242const DB_NAME = 'webmud_addon';
243const DB_VERSION = 3;
244
245//Global variables used to manage player state
246let inCombat = false;
247let playerMoving = false;
248let playerResting = false;
249let aiRunning = false;
250let stopWalkingFlag = '';
251let sendAutoCmds = false;
252let lastSwingTime = Date.now();
253let combatEndTime = Date.now();
254let partyInfo;
255let curPlayer;
256let map;
257let movedFrom = {};
258let ctx;
259let map_size = 999999;
260let room_size = 10;
261let db;
262let curRoomID;
263let playerFollowing = false;
264
265
266/*****************************************************************************\
267| Classes |
268\*****************************************************************************/
269
270class Player {
271 constructor(id, friends, settings) {
272 this._id = id;
273
274 if (friends) {
275 this._friends = friends;
276 } else {
277 this._friends = [];
278 }
279
280 this._settings = {
281 'curPaths':{'startGroup':'_All_Paths', 'endGroup':'_All_Paths',
282 'startPath':'', 'endPath':''},
283 'stepDelay':2500,
284 'autoCmds':{'commands':'', 'delay':6, 'enabled':false}
285 };
286
287 if (settings) {
288 if (settings.curPaths) {
289 this._settings.curPaths = settings.curPaths;
290 }
291
292 if (settings.stepDelay) {
293 this._settings.stepDelay = settings.stepDelay;
294 }
295
296 if (settings.autoCmds) {
297 this._settings.autoCmds = settings.autoCmds;
298 }
299 }
300 }
301
302 savePlayer() {
303 localStorage.setItem(this._id, JSON.stringify(this));
304 }
305
306 addFriend(friend) {
307 this._friends.push(friend);
308
309 this.savePlayer();
310 }
311
312 removeFriend(enemy) {
313 let i = this._friends.length;
314 while (i--) {
315 if (this._friends[i] === enemy) {
316 this._friends.splice(i,1);
317 }
318 }
319 }
320
321 isFriend(name) {
322 if (this._friends.indexOf(name) !== -1) {
323 return true;
324 } else {
325 return false;
326 }
327 }
328
329 updateSettings() {
330 //selected paths
331 this._settings.curPaths.startGroup = document.getElementById(START_GROUP_SELECT_ID).value;
332 this._settings.curPaths.endGroup = document.getElementById(END_GROUP_SELECT_ID).value;
333 this._settings.curPaths.startPath = document.getElementById(START_PATH_SELECT_ID).value;
334 this._settings.curPaths.endPath = document.getElementById(END_PATH_SELECT_ID).value;
335
336 //step delay
337 this._settings.stepDelay = document.getElementById('stepDelay').value;
338
339 //auto command settings
340 this._settings.autoCmds.commands = document.getElementById('autoCmd').value;
341 this._settings.autoCmds.delay = document.getElementById('autoCmdDelay').value;
342 this._settings.autoCmds.enabled = document.getElementById('sendAutoCmds').checked;
343
344 this.savePlayer();
345 }
346
347 loadSettings() {
348 //selected groups
349 selectIfExists(this._settings.curPaths.startGroup, START_GROUP_SELECT_ID);
350 selectIfExists(this._settings.curPaths.endGroup, END_GROUP_SELECT_ID);
351 //reload selectors after selecting grops so paths are available
352 reloadSelectors();
353 //selected paths
354 selectIfExists(this._settings.curPaths.startPath, START_PATH_SELECT_ID);
355 selectIfExists(this._settings.curPaths.endPath, END_PATH_SELECT_ID);
356
357 //step delay
358 document.getElementById('stepDelay').value = this._settings.stepDelay;
359
360 //auto commands
361 document.getElementById('autoCmd').value = this._settings.autoCmds.commands;
362 document.getElementById('autoCmdDelay').value = this._settings.autoCmds.delay;
363 document.getElementById('sendAutoCmds').checked = this._settings.autoCmds.enabled;
364 }
365}
366
367class GameMap {
368 constructor(roomStore, currentRoom) {
369 this._roomStore = roomStore;
370
371 if (currentRoom) {
372 this._currentRoom = currentRoom;
373 } else {
374 this._currentRoom = {};
375 }
376
377 this.export = false;
378 this._rooms = {};
379 this._lastMapRead = null;
380 this._lastMapDraw = {};
381
382 //variables used for the big map
383 this._bigMapWin = null;
384 this._mouseDrag = false;
385 //track the starting position for a mouse drag
386 this._mouseStartX;
387 this._mouseStartY;
388 //Track if the map is drawing a different z
389 this._bigMapZchange = false;
390 this._bigMapZroom = null;
391
392 //variables used to track new x + y variables for map dragging
393 this._ox = 0;
394 this._oy = 0;
395 //last draw ox + oy to track the drag delta
396 this._lx = 0;
397 this._ly = 0;
398
399 }
400
401 move(movedFrom, actionData) {
402 //let txn = db.transaction(['rooms'], 'readwrite');
403 //let store = txn.objectStore('rooms');
404 let data = Object.assign({}, actionData);
405 let movingFrom = Object.assign({}, movedFrom);
406 let movingTo;
407 let prevRoom;
408
409 //get the new room from the database, if it exists
410 getRoom(movingFrom.id)
411 .then(function(result) {
412 prevRoom = result;
413 return getRoom(actionData.RoomID);
414 })
415 .then(function(result) {
416 movingTo = result;
417 return Promise.resolve();
418 })
419 .then(function() {
420 if (!movingTo) {
421 return addRoom(data);
422 } else {
423 return Promise.resolve(movingTo);
424 }
425 })
426 .then(function(result) {
427 let mapUpdateNeeded = false;
428 movingTo = result;
429
430 if (prevRoom.exits[movingFrom.dir] !== movingTo.id) {
431 prevRoom.exits[movingFrom.dir] = movingTo.id;
432 mapUpdateNeeded = true;
433 }
434
435 if (movingTo.exits[oppositeDir(movingFrom.dir)] !== movingFrom.id) {
436 movingTo.exits[oppositeDir(movingFrom.dir)] = movingFrom.id;
437 mapUpdateNeeded = true;
438 }
439
440 //check to see if the room name in the database differs from live
441 let curRoomName = data.Name.toLowerCase().trim();
442 if (movingTo.name !== curRoomName) {
443 movingTo.name = curRoomName;
444 mapUpdateNeeded = true;
445 }
446
447 //The following code is likely unneeded but I need to verify in
448 //production once room changes have been published. This is fall back
449 //in case the room to id code maping doesn't properly update the room
450 //check to see if the room exits in the database differ from live
451 /*
452 for (let exit of data.ObviousExits) {
453 exit = translateDir(exit);
454 if ( !movingTo.exits.hasOwnProperty(exit) ) {
455 movingTo.exits[exit] = -1;
456 mapUpdateNeeded = true;
457 }
458 }
459 */
460
461 if (mapUpdateNeeded) {
462 updateRoom(prevRoom)
463 .then(function() { return updateRoom(movingTo); })
464 .then(function() {
465 mapUpdateNeeded = false;
466 return Promise.resolve(map.drawMap(ctx, room_size, map_size, actionData.RoomID));
467 });
468 } else {
469 return Promise.resolve(map.drawMap(ctx, room_size, map_size, actionData.RoomID));
470 }
471
472 })
473 .catch(function(err) {
474 console.error(err);
475 });
476 }
477
478 draw(ctx, roomSize, drawSize, currentRoomID) {
479 let x;
480 let y;
481 //reset the last map draw
482 this._lastMapDraw = {};
483
484 if (this._bigMapWin) {
485 x = this._ox;
486 y = this._oy;
487 } else {
488 x = (ctx.canvas.width / 2) - (roomSize / 2);
489 y = (ctx.canvas.height / 2) - (roomSize / 2);
490 }
491
492 let roomQueue = [];
493 //let roomsToDraw = drawSize;
494 let drawn = {};
495
496 //clear the canvas
497 ctx.beginPath();
498 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
499 ctx.fillStyle = 'black';
500 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
501
502 //Add the curernt room to the draw queue to start the map
503 roomQueue.push({
504 'id': currentRoomID,
505 'position': { 'x': x , 'y': y }
506 });
507
508 while (roomQueue.length) {
509 x = roomQueue[0].position.x;
510 y = roomQueue[0].position.y;
511 let drawId = roomQueue[0].id;
512
513 //draw room
514 if (drawId === currentRoomID) {
515 ctx.fillStyle = 'greenyellow';
516 ctx.lineWidth = 1;
517 } else {
518 ctx.fillStyle = 'black';
519 ctx.lineWidth = 1;
520 }
521
522 //give orange border if the room has an up or down exit
523 let curRoom = this._rooms[drawId];
524 if ( curRoom.exits.u || curRoom.exits.d ) {
525 ctx.strokeStyle = 'orange';
526
527 } else if (curRoom.altName && curRoom.altName !== '') {
528 ctx.strokeStyle = 'lime';
529
530 } else {
531 ctx.strokeStyle = 'white';
532 }
533
534 ctx.beginPath();
535 ctx.rect(x, y, roomSize, roomSize);
536 ctx.fill();
537 ctx.stroke();
538
539 //add the room to a mapping variable that the class can use in the big map
540 this._lastMapDraw[drawId] = {
541 'pos': { 'x': x, 'y': y }
542 };
543
544 //mark the room as drawn so it won't be redrawn
545 drawn[drawId] = true;
546
547 //loop through each of the room's exits
548 let exitKeys = Object.keys(curRoom.exits);
549 let len = exitKeys.length;
550
551 for (let i = 0; i < len; i++) {
552 //for (let exit in exitCheck.exits) {
553 //variables for the connector and room x y coordinates
554 let scx = x;
555 let scy = y;
556 let ecx = x;
557 let ecy = y;
558 let rx = x;
559 let ry = y;
560
561 let exitId = curRoom.exits[exitKeys[i]];
562 //let inQueue = this._containsRoom(exitId, roomQueue);
563 if (!drawn[exitId]) {
564 //determine the x y coordinates for the draw based on direction
565 switch (exitKeys[i]) {
566 case 'n':
567 scx = x + (roomSize / 2);
568 ecx = scx;
569 ecy = y - roomSize;
570 ry = y - (roomSize * 2);
571 break;
572
573 case 's':
574 scx = x + (roomSize / 2);
575 ecx = scx;
576 scy = y + roomSize;
577 ecy = y + (roomSize * 2);
578 ry = y + (roomSize * 2);
579 break;
580
581 case 'e':
582 scx = x + roomSize;
583 ecx = x + (roomSize * 2);
584 scy = y + (roomSize / 2);
585 ecy = scy;
586 rx = x + (roomSize * 2);
587 break;
588
589 case 'w':
590 ecx = x - roomSize;
591 scy = y + (roomSize / 2);
592 ecy = scy;
593 rx = x - (roomSize * 2);
594 break;
595
596 case 'ne':
597 scx = x + roomSize;
598 ecx = x + (roomSize * 2);
599 ecy = y - roomSize;
600 rx = x + (roomSize * 2);
601 ry = y - (roomSize * 2);
602 break;
603
604 case 'se':
605 scx = x + roomSize;
606 ecx = x + (roomSize * 2);
607 scy = y + roomSize;
608 ecy = y + (roomSize * 2);
609 rx = x + (roomSize * 2);
610 ry = y + (roomSize * 2);
611 break;
612
613 case 'nw':
614 ecx = x - roomSize;
615 ecy = y - roomSize;
616 rx = x - (roomSize * 2);
617 ry = y - (roomSize * 2);
618 break;
619
620 case 'sw':
621 ecx = x - roomSize;
622 scy = y + roomSize;
623 ecy = y + (roomSize * 2);
624 rx = x - (roomSize * 2);
625 ry = y + (roomSize * 2);
626 break;
627
628 default:
629
630 }
631
632 //set the stroke color for the connector
633 if (exitId === -1) {
634 ctx.strokeStyle = 'red';
635 ctx.lineWidth = 3;
636 } else {
637 ctx.strokeStyle = 'white';
638 ctx.lineWidth = 1;
639 }
640
641 //draw connector
642 ctx.beginPath();
643 ctx.moveTo(scx,scy);
644 ctx.lineTo(ecx,ecy);
645 ctx.stroke();
646
647 if (!roomQueue.includes(exitId) && exitId !== -1 &&
648 exitKeys[i] !== 'u' && exitKeys[i] !== 'd') {
649 //add connected room to the drawing queue except if it's an exit that
650 //changes z plane, has already been drawn, or has not yet been explored
651 roomQueue.push({
652 'id':exitId,
653 'position': { 'x': rx , 'y': ry }
654 });
655
656 }
657 } //end check if room already drawn
658
659 } //end the draw exits loop
660
661 //remove the completed room from the queue for the next loop
662 roomQueue.shift();
663 //i++;
664
665 } //end for roomsToDraw loop
666 } //end draw method
667
668 drawMap(ctx, roomSize, drawSize, currentRoomID) {
669
670 getLastWrite()
671 .then(result => {
672 if (result.stamp > this._lastMapRead || map.export) {
673 //local data is old, read the full database in
674 let txn = db.transaction(['rooms'], 'readonly');
675 let store = txn.objectStore('rooms');
676
677 store.getAll().onsuccess = function(event) {
678 //create the rooms object using the id as a lookup key and the room
679 //object as the value
680 let len = event.target.result.length;
681
682 for (let i = 0; i < len; i++) {
683 this._rooms[event.target.result[i].id] = event.target.result[i];
684 }
685
686 //this._rooms = event.target.result;
687 this._lastMapRead = Date.now();
688
689 this.draw(ctx, roomSize, drawSize, currentRoomID);
690
691 //export the map if the user requested it
692 if (map.export) {
693 let downloadLink = document.createElement('a');
694 downloadLink.target = '_blank';
695 downloadLink.href = ctx.canvas.toDataURL('image/png');
696 downloadLink.download = 'WebMud_Map.png';
697 document.body.appendChild(downloadLink);
698 downloadLink.click();
699 document.body.removeChild(downloadLink);
700
701 this.export = false;
702 }
703
704 }.bind(this);
705
706 } else {
707 //No need to re-read the database, just draw the map
708 this.draw(ctx, roomSize, drawSize, currentRoomID);
709 }
710
711 })
712 .catch(function(err) {
713 console.error(err);
714 });
715 }
716
717 startBigMap() {
718 let playerName = document.title.split(' - ')[0];
719
720 this._bigMapWin = window.open('', 'map');
721
722 this._bigMapWin.document.open();
723 this._bigMapWin.document.write(`
724 <!DOCTYPE html>
725 <html>
726 <head>
727 <title>Map:${ playerName }</title>
728 </head>
729 <body>
730 <link href='http://webmud.com/Content/css' rel='stylesheet'/>
731 <canvas id='bigMapCanvas'></canvas>
732 <p>
733 <div>
734 <div style='float:left'>
735 <div style='margin:0 auto'>Click and drag to move the map.
736 <br />Hover over a room for detail
737 <br />Shift+click a room to walk to that room.
738 <br />Shift+right_click to run to that room
739 <br />Alt+left click to follow a room's exit down
740 <br />Alt+right click to follow a room's exit up
741 <br />Scroll to zoom in/out
742 <br />Right click to re-center the map
743 </div>
744 </div>
745 <div style='margin:0 auto; width:50%'>
746 <span id='bigName'></span><br />
747 <span id='bigAltName'></span><br />
748 <span id='bigExits'></span><br />
749 </div>
750 </div>
751 </p>
752 </body>
753 </html>`);
754 this._bigMapWin.document.close();
755
756 //blank the old map so the user doesn't get confused
757 ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
758
759 //set the global ctx variable so that the big map is drawn on move
760 ctx = this._bigMapWin.document.getElementById('bigMapCanvas').getContext('2d');
761
762 //event listeners for the canvas
763 ctx.canvas.addEventListener('mousemove', (evt => {
764 let mousePos = this._getMousePos(ctx.canvas, evt);
765
766 if (this._mouseDrag) {
767 if (mousePos.x >= this._mouseStartX) {
768 this._ox += (mousePos.x - this._mouseStartX);
769 } else {
770 this._ox -= (this._mouseStartX - mousePos.x);
771 }
772
773 if (mousePos.y >= this._mouseStartY) {
774 this._oy += (mousePos.y - this._mouseStartY);
775
776 } else {
777 this._oy -= (this._mouseStartY - mousePos.y);
778 }
779
780 this._mouseStartX = mousePos.x;
781 this._mouseStartY = mousePos.y;
782
783 const xDelt = Math.abs(this._ox - this._lx);
784 const yDelt = Math.abs(this._oy - this._ly);
785
786 if (xDelt >= 30 || yDelt >= 30) {
787 this._lx = this._ox;
788 this._ly = this._oy;
789
790 this._drawBigMap();
791 }
792
793
794 } else {
795 //if not dragging, do mouseover text
796 let room = this._getMapRoom(mousePos);
797 let outputName = this._bigMapWin.document.getElementById('bigName');
798 let outputAltName = this._bigMapWin.document.getElementById('bigAltName');
799 let outputExits = this._bigMapWin.document.getElementById('bigExits');
800
801 if (room) {
802 outputName.innerHTML = 'Room Name: ' + room.name;
803 outputAltName.innerHTML = 'Alt Name: ' + room.altName;
804 outputExits.innerHTML = 'Exits: ' + Object.keys(room.exits).join(', ');
805 } else {
806 outputName.innerHTML = '';
807 outputAltName.innerHTML = '';
808 outputExits.innerHTML = '';
809 }
810
811
812 }
813 }), false);
814
815 ctx.canvas.addEventListener('mousedown', (evt => {
816 let mousePos = this._getMousePos(ctx.canvas, evt);
817 this._mouseDrag = true;
818
819 this._mouseStartX = mousePos.x;
820 this._mouseStartY = mousePos.y;
821 }), false);
822
823 ctx.canvas.addEventListener('mouseup', (() => {
824 this._mouseDrag = false;
825 }), false);
826
827 this._bigMapWin.addEventListener('resize', (evt => {
828 let win = evt.target;
829 let canvas = ctx.canvas;
830
831 canvas.width = win.innerWidth - (win.innerWidth * .02);
832 canvas.height = win.innerHeight - (win.innerHeight * .2);
833
834 this._ox = (canvas.width / 2) - (room_size / 2);
835 this._oy = (canvas.height / 2) - (room_size / 2);
836
837 this._drawBigMap();
838 }), false);
839
840 this._bigMapWin.addEventListener('beforeunload', (() => {
841 //reset room size
842 room_size = parseInt(document.getElementById('roomSize').value);
843
844 document.getElementById('divMap').hidden = false;
845 ctx = document.getElementById('mapCanvas').getContext('2d');
846
847 //reset map draw center
848 this._ox = (ctx.canvas.width / 2) - (room_size / 2);
849 this._oy = (ctx.canvas.height / 2) - (room_size / 2);
850
851 //reset z plane change variables
852 this._bigMapZchange = false;
853 this._bigMapZroom = null;
854
855 this._drawBigMap();
856 }), false);
857
858 this._bigMapWin.addEventListener('contextmenu', (evt => {
859 evt.preventDefault();
860 this._handleMapClick(evt);
861 }), false);
862
863 this._bigMapWin.addEventListener('click', (evt => {
864 evt.preventDefault();
865 this._handleMapClick(evt);
866 }), false);
867
868 this._bigMapWin.addEventListener('wheel', (evt => {
869 evt.preventDefault();
870
871 if (evt.deltaY < 0) {
872 room_size += 1;
873 } else if (evt.deltaY > 0 && room_size > 2) {
874 room_size -= 1;
875 }
876
877 this._drawBigMap();
878 }), false);
879
880 } //end startBigMap
881
882 _handleMapClick(event) {
883 const clickPos = this._getMousePos(ctx.canvas, event);
884 const room = this._getMapRoom(clickPos);
885 const shift = event.shiftKey;
886 const alt = event.altKey;
887 const button = event.button;
888
889 if (shift && !alt) {
890 let type;
891
892 //set the walk type based on the mouse button used
893 if (button === 0) {
894 //left click
895 type = 'walk';
896 } else if (button === 2) {
897 //right click
898 type = 'run';
899 }
900
901 if (room) {
902 autoMapGo(curRoomID, room.id, room.name, type);
903 }
904
905 } else if (alt && !shift) {
906 //alt click means follow the selected room up/down to the next Z level
907
908 if (room) {
909 const exitUp = room.exits.u;
910 const exitDown = room.exits.d;
911
912 if (button === 0 && exitDown) {
913 //left click, go down
914 this._bigMapZchange = true;
915 this._bigMapZroom = exitDown;
916
917 this._ox = clickPos.x;
918 this._oy = clickPos.y;
919
920 } else if (button === 2 && exitUp) {
921 //right click, go up
922 this._bigMapZchange = true;
923 this._bigMapZroom = exitUp;
924
925 this._ox = clickPos.x;
926 this._oy = clickPos.y;
927 }
928
929 this._drawBigMap();
930
931 }
932
933 } else if (button === 2 && !shift && !alt) {
934 //right click without shift modifier resets draw position
935 this._ox = (ctx.canvas.width / 2) - (room_size / 2);
936 this._oy = (ctx.canvas.height / 2) - (room_size / 2);
937
938 this._bigMapZchange = false;
939 this._bigMapZroom = null;
940
941 this._drawBigMap();
942 }
943
944 }
945
946 _getMapRoom(clickPos) {
947 let keys = Object.keys(this._lastMapDraw);
948 let len = keys.length;
949
950 for (let i = 0; i < len; i++) {
951 let room = this._lastMapDraw[keys[i]];
952
953 if (room.pos) {
954 const minX = room.pos.x;
955 const maxX = room.pos.x + room_size;
956 const minY = room.pos.y;
957 const maxY = room.pos.y + room_size;
958
959 if (clickPos.x > minX && clickPos.x < maxX &&
960 clickPos.y > minY && clickPos.y < maxY) {
961
962 return this._rooms[keys[i]];
963 }
964 }
965 }
966 return false;
967 }
968
969 _getMousePos(canvas, evt) {
970 let rect = canvas.getBoundingClientRect();
971 return {
972 x: evt.clientX - rect.left,
973 y: evt.clientY - rect.top
974 };
975
976 }
977
978 _drawBigMap() {
979 //custom draw function for the big map to handle change in z axis (up/down)
980 let drawRoomID;
981 if (this._bigMapZchange) {
982 drawRoomID = this._bigMapZroom;
983 } else {
984 drawRoomID = curRoomID;
985 }
986
987 this.drawMap(ctx, room_size, map_size, drawRoomID);
988 }
989
990 _containsRoom(roomId, array) {
991 for (let room in array) {
992 if (room.id === roomId) {
993 return true;
994 }
995 }
996 return false;
997 } //end _containsRoom
998} //end Map class
999
1000class Room {
1001 constructor(id, name, exits, altName, group, commands, unlisted) {
1002 this.id = id;
1003 this.name = name;
1004 this.exits = exits;
1005
1006 if (altName) {
1007 this.altName = altName;
1008 } else {
1009 this.altName = null;
1010 }
1011
1012 if (group) {
1013 this.group = group;
1014 } else {
1015 this.group = null;
1016 }
1017
1018 if (commands) {
1019 this.commands = commands;
1020 } else {
1021 this.commands = null;
1022 }
1023
1024 if (unlisted) {
1025 this.unlisted = unlisted;
1026 } else {
1027 unlisted = false;
1028 }
1029 }
1030}
1031
1032/*****************************************************************************\
1033| Initialization code (self invoked) |
1034\*****************************************************************************/
1035
1036(function() {
1037 //used to load all of the initial data stored in localStorage
1038 let custPaths = localStorage.getItem(PATH_LIST);
1039 let groupList = localStorage.getItem(GROUP_LIST);
1040
1041 //Save a starter path list if none currently exists
1042 if (custPaths === null) {
1043 storePath(PATH_LIST,'');
1044 custPaths = localStorage.getItem(PATH_LIST);
1045 }
1046 //Save a starter group list if none currently exists
1047 if (groupList === null) {
1048 storePath(GROUP_LIST,'_All_Paths');
1049 }
1050
1051 //Overwrite the _All_Paths group with the current list of all paths
1052 //from localStorage.
1053 storePath('_All_Paths',custPaths);
1054
1055 //load the selectors
1056 reloadSelectors();
1057
1058 //create the player object
1059 let storedPlayer = JSON.parse(localStorage.getItem(playerID));
1060
1061 if (storedPlayer) {
1062 curPlayer = new Player(playerID, storedPlayer._friends, storedPlayer._settings);
1063 } else {
1064 curPlayer = new Player(playerID);
1065 }
1066
1067 //load the player's saved settings
1068 curPlayer.loadSettings();
1069
1070 //start or stop auto commands after loading preferences
1071 autoCmdCheck('autoCmd','autoCmdDelay','sendAutoCmds');
1072
1073 //open the database
1074 openDatabase();
1075
1076 //alert the player that the addon loaded successfully
1077 notifyPlayer('yellow','WebMUD addon successfully loaded');
1078
1079})();
1080
1081
1082/*****************************************************************************\
1083| Path walking / looping code |
1084\*****************************************************************************/
1085
1086function lookupPathAndMove(startSelectId, endSelectId, delaySelectId, runPath, loopPath) {
1087 let start = document.getElementById(startSelectId).value;
1088 let end = document.getElementById(endSelectId).value;
1089 let stepDelay = document.getElementById(delaySelectId).value;
1090
1091 //make sure the stopWalkingFlag is blank to start
1092 stopWalkingFlag = '';
1093
1094 if (loopPath) {
1095 if (start.slice(0,5) !== 'loop_') {
1096 window.alert('In order to loop, you must select a loop as the starting \
1097 room. Loops begin with \"loop\" and can be created with the \
1098 \"Save Loop\" button under \"Path and Loop Creation\"');
1099
1100 } else {
1101 let path = localStorage.getItem(start);
1102 //start the path
1103 walkPath(path, stepDelay, loopPath, start);
1104 //tell the player
1105 notifyPlayer('greenyellow', 'Looping of ' + start + ' started.');
1106
1107 }
1108
1109 } else {
1110 //for not loops, find the shortest path and build the step string
1111 let path = findPath(start, end);
1112
1113 if (path) {
1114 let steps = buildPath(path);
1115
1116 //if the player want to run (no combat), disable AI combat
1117 if (runPath) {
1118 sendMessageDirect('DisableAI Combat');
1119 document.getElementById('chkAICombat').checked = false;
1120 }
1121
1122 //start the path
1123 walkPath(steps, stepDelay, loopPath, path.split(',').join(' => '));
1124 //tell the player
1125 notifyPlayer('greenyellow', 'Walking path: ' + path.split(',').join(' => '));
1126
1127 } else {
1128 let displayString = 'ERROR: No path found between ' + start + ' and ' + end;
1129 window.alert(displayString);
1130 }
1131 }
1132
1133 //save the player's current settings
1134 curPlayer.updateSettings();
1135}
1136
1137function walkPath(path, stepDelay, loopPath, selectedPath) {
1138 let pathArray = path.split(',');
1139 let genObj = genFunc(pathArray);
1140
1141 //disable movement buttons to prevent multiple clicks
1142 toggleMoveButtons(false);
1143
1144 let interval = setInterval(() => {
1145 //The following if statement checks a bunch of boolians so I use 'not' logic
1146 //which lets me keep the line short
1147 if (
1148 !(inCombat || playerMoving || resting || meditating || aiRunning) &&
1149 ((combatEndTime + 1000) < Date.now())
1150 ) {
1151
1152 //get the next step
1153 let val = genObj.next();
1154
1155 //if the path is done
1156 if (val.done || stopWalkingFlag === 'now') {
1157 //stop the timed interval since the path is done
1158 clearInterval(interval);
1159 //enable the movement buttons
1160 toggleMoveButtons(true);
1161
1162 if (!loopPath && stopWalkingFlag !== 'now') {
1163 //reverse the selected start/end group and path for quality of life
1164 switchPathSelection();
1165 }
1166
1167 //Determine the reason or stopping and notify the player
1168 if (loopPath && stopWalkingFlag === '') {
1169 //if the player wants to loop, recall the function using the
1170 //same conditions
1171 notifyPlayer('greenyellow', 'Loop Complete, re-looping ' + selectedPath);
1172
1173 walkPath(path, stepDelay, loopPath, selectedPath);
1174
1175 } else if (stopWalkingFlag === 'now') {
1176 //if the player asked to stop now, stop and alert them
1177 genObj = null; //reset the generator
1178 notifyPlayer('red', 'WALKING/LOOPING STOPPED NOW!');
1179 stopWalkingFlag = '';
1180
1181 } else if (stopWalkingFlag === 'end of loop') {
1182 //if the player was looping but wanted to stop at the end of the
1183 //current loop, stop and let them know the current loop is done
1184 notifyPlayer('yellow', 'STOPPED: end of current loop');
1185 stopWalkingFlag = '';
1186
1187 } else {
1188 //send a message to player telling them the path is complete
1189 notifyPlayer('lime', 'Path complete, you have arrived');
1190 stopWalkingFlag = '';
1191 //re-enable combat in case the user was running the path
1192 }
1193
1194 //re-enable combat if it was disabled for a run
1195 if (document.getElementById('chkAICombat').checked === false) {
1196 sendMessageDirect('EnableAI Combat');
1197 document.getElementById('chkAICombat').checked = true;
1198 }
1199
1200 //save the player's current settings
1201 curPlayer.updateSettings();
1202
1203 } else {
1204 //Send the next step
1205 sendMessageDirect(val.value[0]);
1206
1207 //update the player
1208 if (loopPath) {
1209 if (stopWalkingFlag === 'end of loop') {
1210 notifyPlayer('greenyellow', 'Stopping at end of current loop (' + selectedPath + '): ' +
1211 val.value[1] + ' steps left');
1212 } else {
1213 notifyPlayer('greenyellow', 'Looping ' + selectedPath + ' (' +
1214 val.value[1] + ' steps left)');
1215 }
1216
1217 } else {
1218 notifyPlayer('greenyellow', 'Walking path (' + val.value[1] +
1219 ' steps left): ' + selectedPath);
1220 }
1221
1222 }
1223 } else {
1224 //check for combat inactivity where combat is set to true but a swing
1225 //hasn't happened for a least 10000 miliseconds (10 seconds)
1226 if (inCombat === true && (lastSwingTime + 10000) < Date.now()) {
1227 inCombat = false;
1228 //reset swing timer so combat doesn't disable right away
1229 lastSwingTime = (Date.now() + 10000);
1230 }
1231
1232 //failsafe for when the AI doesn't properly break after resting
1233 if ( (resting || meditating) && playerResting === false ) {
1234 sendMessageDirect('break');
1235 }
1236
1237 //failsafe for when the AI doesn't properly break after party running
1238 if (aiRunning && partyInfo) {
1239 let memberResting = false;
1240 sendMessageDirect('par');
1241 //check to see if any of the party members are resting
1242 for (let member of partyInfo) {
1243 if (member.HPPercent < 95 || memberResting) {
1244 memberResting = true;
1245 }
1246 }
1247 aiRunning = memberResting;
1248 }
1249
1250 }
1251 }, stepDelay);
1252}
1253
1254function* genFunc(passedArr) {
1255 let Arr = passedArr;
1256 let i = Arr.length;
1257
1258 for(let item of Arr) {
1259 yield [item, --i];
1260 }
1261}
1262
1263function stopWalking(whenToStop) {
1264 stopWalkingFlag = whenToStop;
1265 combatEnd();
1266
1267 notifyPlayer('yellow', 'Stop walking selected, will stop walking: ' + whenToStop);
1268}
1269
1270function switchPathSelection() {
1271 let curStartGroup = document.getElementById(START_GROUP_SELECT_ID).value;
1272 let curEndGroup = document.getElementById(END_GROUP_SELECT_ID).value;
1273 let curStartRoom = document.getElementById(START_PATH_SELECT_ID).value;
1274 let curEndRoom = document.getElementById(END_PATH_SELECT_ID).value;
1275
1276 //swap the groups start <> end
1277 selectIfExists(curStartGroup, END_GROUP_SELECT_ID);
1278 selectIfExists(curEndGroup, START_GROUP_SELECT_ID);
1279 //reload selectors so paths are available
1280 reloadSelectors();
1281 //swap the paths start <> end
1282 selectIfExists(curStartRoom, END_PATH_SELECT_ID);
1283 selectIfExists(curEndRoom, START_PATH_SELECT_ID);
1284
1285}
1286
1287/*****************************************************************************\
1288| Path creation code |
1289\*****************************************************************************/
1290
1291function newPath(pathList, pathType) {
1292 let custPaths = localStorage.getItem(pathList);
1293 let startingRoom = removeSpaces(document.getElementById('startingRoom').value);
1294 let endingRoom = removeSpaces(document.getElementById('endingRoom').value);
1295
1296 //Get the path from the text box then remove extra spaces
1297 let newPath = document.getElementById('newPath').value.split(',');
1298
1299 for (let i = 0; i < newPath.length; i++) {
1300 newPath[i] = newPath[i].trim();
1301 }
1302 //rejoin the array back into a string so we can store the path
1303 newPath = newPath.join(',');
1304
1305 if (pathType === 'path') {
1306 let newPathName = startingRoom + '__2__' + endingRoom;
1307 let newPathNameReverse = endingRoom + '__2__' + startingRoom;
1308 let newPathReverse = reversePath(newPath);
1309
1310 custPaths = custPaths + ',' + newPathName + ',' + newPathNameReverse;
1311 storePath(pathList, custPaths);
1312 storePath(newPathName, newPath);
1313 storePath(newPathNameReverse, newPathReverse);
1314
1315 } else if (pathType === 'loop') {
1316
1317 let newLoopName = 'loop_' + startingRoom;
1318
1319 custPaths = custPaths + ',' + newLoopName;
1320 storePath(pathList, custPaths);
1321 storePath(newLoopName, newPath);
1322 }
1323
1324 //clear the boxes for the next path
1325 document.getElementById('startingRoom').value = '';
1326 document.getElementById('endingRoom').value = '';
1327 document.getElementById('newPath').value = '';
1328
1329 //Update the _All_Paths group
1330 storePath('_All_Paths', custPaths);
1331
1332 //reload selectors with the new paths
1333 reloadSelectors();
1334
1335
1336 //alert the user that the path saved successfully
1337 notifyPlayer('yellow', 'New path/loop saved');
1338}
1339
1340/*****************************************************************************\
1341| Path tools |
1342\*****************************************************************************/
1343
1344function deleteSelectedPath(pathList, pathSelectId, groupList) {
1345 let pathToDelete = document.getElementById(pathSelectId).value;
1346 let groupListArray = localStorage.getItem(groupList).split(',');
1347
1348 let playerConfirm = window.confirm('Are you sure you want to delete ' + pathToDelete + '?');
1349
1350 if (playerConfirm) {
1351 removePathFromGroups(pathList, pathToDelete);
1352
1353 //loop through all groups and remove the path if found
1354 groupListArray.forEach(function(group) {
1355 removePathFromGroups(group, pathToDelete);
1356 });
1357
1358 //delete the associated path from local storage
1359 localStorage.removeItem(pathToDelete);
1360
1361 //reload the path list
1362 reloadSelectors();
1363
1364 //notify the player
1365 notifyPlayer('red','Path ' + pathToDelete + ' successfully deleted');
1366 }
1367}
1368
1369function removePathFromGroups(pathList, pathToDelete) {
1370 let custPaths = localStorage.getItem(pathList);
1371 let custPathsArray = custPaths.split(',');
1372 let i = custPathsArray.length;
1373 let startingLength = custPathsArray.length;
1374
1375 //Loop through the array and remove the selected path
1376 while (i--){
1377 if (custPathsArray[i] === pathToDelete) {
1378 custPathsArray.splice(i,1);
1379 }
1380 }
1381
1382 //put the array back together into a string
1383 custPaths = custPathsArray.join(',');
1384
1385 //if the new array is shorter than the original, meaning a path was deleted
1386 //then write it to local storage
1387 if (custPathsArray.length < startingLength) {
1388 storePath(pathList, custPaths);
1389 }
1390}
1391
1392function getPathsToImport(pathList, groupList) {
1393 let importConfirm = window.confirm('Are you sure you want to import the \
1394 paths from the textbox below?');
1395
1396 if (importConfirm) {
1397 let pathImportList = document.getElementById('pathImportExport').value;
1398 importPaths(pathList, groupList, pathImportList);
1399 }
1400
1401 //clear the import box
1402 document.getElementById('pathImportExport').value = '';
1403}
1404
1405function importPaths(pathList, groupList, pathsToImport) {
1406 let pathsToImportArray = pathsToImport.split(';');
1407 let currentPaths = localStorage.getItem(pathList);
1408 let currentGroups = localStorage.getItem(groupList);
1409 let currentPathsArray = currentPaths.split(',');
1410 let currentGroupsArray = currentGroups.split(',');
1411 let processGroups = false;
1412
1413 pathsToImportArray.forEach(function(path) {
1414 if (path === '__STARTING_GROUPS__') {
1415 processGroups = true;
1416
1417 } else {
1418
1419 let pathArray = path.split(':');
1420 storePath(pathArray[0], pathArray[1]);
1421
1422 if (processGroups) {
1423 currentGroupsArray.push(pathArray[0]);
1424 } else {
1425 currentPathsArray.push(pathArray[0]);
1426 }
1427
1428 }
1429
1430 });
1431
1432 //remove dupe path names before writing by converting the array to a
1433 //set then using the spread operator to change it back and finally
1434 //joining it back together as a comma seperated string
1435 let uniqPaths = [...new Set(currentPathsArray)];
1436 currentPaths = uniqPaths.join(',');
1437 let uniqGroups = [...new Set(currentGroupsArray)];
1438 currentGroups = uniqGroups.join(',');
1439
1440 storePath(pathList, currentPaths);
1441 storePath(groupList, currentGroups);
1442
1443 //update the _All_Paths group
1444 storePath('_All_Paths', currentPaths);
1445
1446 //update the path selector and alert the user import is complete
1447 reloadSelectors();
1448 notifyPlayer('greenyellow','Path import complete');
1449}
1450
1451function exportPaths(pathList, groupList, exportType) {
1452 let pathsToExport = [];
1453 let exportString = '';
1454
1455 if (exportType === 'single') {
1456 let path = document.getElementById('exportPathSelect').value;
1457 let pathSteps = localStorage.getItem(path);
1458 pathsToExport.push(path + ':' + pathSteps);
1459
1460 } else {
1461
1462 let currentGroups = '';
1463 let currentPaths = '';
1464 let currentGroupsArray = [];
1465 let currentPathsArray = [];
1466
1467
1468 if (exportType === 'group') {
1469 currentGroups = document.getElementById('exportGroupSelect').value;
1470 currentGroupsArray = currentGroups.split(',');
1471 currentPaths = localStorage.getItem(currentGroups);
1472 currentPathsArray = currentPaths.split(',');
1473
1474 } else if (exportType === 'all') {
1475 currentPaths = localStorage.getItem(pathList);
1476 currentPathsArray = currentPaths.split(',');
1477 currentGroups = localStorage.getItem(groupList);
1478 currentGroupsArray = currentGroups.split(',');
1479 }
1480
1481 currentPathsArray.forEach(function(path) {
1482 let pathSteps = localStorage.getItem(path);
1483 pathsToExport.push(path + ':' + pathSteps);
1484 });
1485
1486 //add indicator for the start of groups
1487 pathsToExport.push('__STARTING_GROUPS__');
1488
1489 currentGroupsArray.forEach(function(group) {
1490 let groupPaths = localStorage.getItem(group);
1491 pathsToExport.push(group + ':' + groupPaths);
1492 });
1493 }
1494
1495 //Join the array back together into a string for export
1496 exportString = pathsToExport.join(';');
1497
1498 document.getElementById('pathImportExport').value = exportString;
1499
1500 window.alert('Export complete: copy the text in the box below');
1501}
1502
1503function storePath(pathKey, pathValue) {
1504 localStorage.setItem(pathKey, pathValue);
1505}
1506
1507function clearAllPaths(pathList, groupList){
1508 let pathListArray = localStorage.getItem(pathList).split(',');
1509 let groupListArray = localStorage.getItem(groupList).split(',');
1510
1511 let playerConfirm = window.confirm('Are you sure you want to delete ALL paths and groups?');
1512
1513 if (playerConfirm) {
1514 //delete all of the paths
1515 pathListArray.forEach(function(path) {
1516 //delete the associated path from local storage
1517 localStorage.removeItem(path);
1518 });
1519 //delete all of the groups
1520 groupListArray.forEach(function(group) {
1521 //delete the associated path from local storage
1522 localStorage.removeItem(group);
1523 });
1524 //Remove all paths from the CustomPaths_ list and _All_Paths list
1525 localStorage.setItem('_All_Paths','');
1526 localStorage.setItem('CustomPaths_','');
1527 localStorage.setItem('pathGroups_','_All_Paths');
1528 }
1529
1530 //reload the path list
1531 reloadSelectors();
1532
1533 //notify the player
1534 notifyPlayer('red','ALL paths successfully deleted');
1535}
1536
1537
1538function reversePath(dir) {
1539 let revDir = '';
1540
1541 //loop through the array and replace directions with their opposites
1542 //only n,s,e,w,u,d are needed because each character is evaluated so
1543 //se becomes nw
1544 for (let i = 0; i < dir.length; i++) {
1545 switch (dir[i]) {
1546 case 'n':
1547 revDir += 's';
1548 break;
1549 case 's':
1550 revDir += 'n';
1551 break;
1552 case 'e':
1553 revDir += 'w';
1554 break;
1555 case 'w':
1556 revDir += 'e';
1557 break;
1558 case 'u':
1559 revDir += 'd';
1560 break;
1561 case 'd':
1562 revDir += 'u';
1563 break;
1564 default:
1565 revDir += dir[i];
1566 break;
1567 }
1568 }
1569
1570 //combine the replacement list then reverse it for the correct order and
1571 //return the results
1572 return revDir.split(',').reverse().join();
1573}
1574
1575/*****************************************************************************\
1576| Auto command code |
1577\*****************************************************************************/
1578
1579function autoCmdCheck(cmdBox, delayId, cmdCheckBox) {
1580 //When the user clicks the auto commands checkbox, check to see if it's
1581 //checked or unchecked and set the appropriate flag
1582 let cmdDelay = document.getElementById(delayId).value;
1583 let commands = document.getElementById(cmdBox).value;
1584
1585 if (document.getElementById(cmdCheckBox).checked) {
1586 sendAutoCmds = true;
1587 startAutoCmds(commands, cmdDelay * 1000);
1588
1589 //tell the player commands started
1590 notifyPlayer('yellow','Auto Commands Started');
1591
1592 } else {
1593 sendAutoCmds = false;
1594
1595 }
1596
1597 //update the player with new commands
1598 curPlayer.updateSettings();
1599}
1600
1601function startAutoCmds(commands, cmdDelay) {
1602 let commandArray = commands.split(',');
1603
1604 if (sendAutoCmds) {
1605 commandArray.forEach(function(cmd) {
1606 sendMessageDirect(cmd);
1607 });
1608 } else {
1609 //stop auto commands once the checkbox is unchecked
1610 notifyPlayer('yellow','Auto Commands Stopped');
1611 return;
1612 }
1613
1614 setTimeout(startAutoCmds, cmdDelay, commands, cmdDelay);
1615
1616}
1617
1618
1619/*****************************************************************************\
1620| Path grouping code |
1621\*****************************************************************************/
1622
1623function editGroup(editType) {
1624 let selectedGroup = document.getElementById('groupingGroupSelect').value;
1625 let pathToRemove = document.getElementById('groupingCurPathSelect').value;
1626 let pathToAdd = document.getElementById('groupingAllPathSelect').value;
1627
1628 if (editType === 'new') {
1629 newGroup();
1630
1631 } else if (selectedGroup === '_All_Paths') {
1632 //Check if the user is trying to modify _All_Paths
1633 window.alert('Modifying the _All_Paths group is not allowed');
1634
1635 } else {
1636 //In all other cases, modify the group selected
1637 if (editType === 'add') {
1638 addToGroup(selectedGroup, pathToAdd);
1639 } else if (editType === 'remove') {
1640 removeFromGroup(selectedGroup, pathToRemove);
1641 } else if (editType === 'delete') {
1642 deleteGroup(selectedGroup);
1643 }
1644 }
1645}
1646
1647function addToGroup(selectedGroup, pathToAdd) {
1648 let selectedGroupPaths = localStorage.getItem(selectedGroup);
1649 //add the path to the selected group's paths and save to a variable
1650 let updatedPaths = selectedGroupPaths + ',' + pathToAdd;
1651 //write the updated path list to the group
1652 storePath(selectedGroup, updatedPaths);
1653
1654 //alert the player
1655 notifyPlayer('lime', pathToAdd + ' added to group ' + selectedGroup);
1656 //reload all of the selection lists
1657 reloadSelectors();
1658}
1659
1660function removeFromGroup(selectedGroup, pathToRemove) {
1661 let selectedGroupPaths = localStorage.getItem(selectedGroup);
1662 let groupPathsArray = selectedGroupPaths.split(',');
1663 let i = groupPathsArray.length;
1664
1665 //loop through the array and remove the path
1666 while (i--){
1667 if (groupPathsArray[i] === pathToRemove) {
1668 groupPathsArray.splice(i,1);
1669 }
1670 }
1671
1672 //rejoin the array
1673 selectedGroupPaths = groupPathsArray.join(',');
1674
1675 //write the updated path list back to local storage
1676 storePath(selectedGroup, selectedGroupPaths);
1677
1678 //alert the player
1679 notifyPlayer('yellow', pathToRemove + ' removed from group ' + selectedGroup);
1680
1681 //reload all of the selection lists
1682 reloadSelectors();
1683}
1684
1685function newGroup() {
1686 let groupToAdd = window.prompt('Please enter a name for your new group');
1687 let groupList = localStorage.getItem(GROUP_LIST);
1688
1689 if (groupToAdd) {
1690 //Clean the new name, add it to the current group list, and write out to
1691 //local storage
1692 groupToAdd = removeSpaces(groupToAdd);
1693 groupList += ',' + groupToAdd;
1694 storePath(GROUP_LIST, groupList);
1695
1696 //Add a blank group under the new name to local storage
1697 storePath(groupToAdd, '');
1698
1699 //reload selectors
1700 reloadSelectors();
1701
1702 notifyPlayer('lime', 'New group ' + groupToAdd + ' successfully added');
1703 }
1704}
1705
1706function deleteGroup(groupToDelete) {
1707 let groupList = localStorage.getItem(GROUP_LIST);
1708 let groupListArray = groupList.split(',');
1709 let i = groupListArray.length;
1710
1711 //Ask the player if they're sure
1712 let playerConfirm = window.confirm('Are you sure you want to delete ' + groupToDelete + '?');
1713
1714 if (playerConfirm) {
1715 //Loop through the array and remove the selected path
1716 while (i--){
1717 if (groupListArray[i] === groupToDelete) {
1718 groupListArray.splice(i,1);
1719 }
1720 }
1721
1722 //put the array back together into a string and write the
1723 //modified group list to localstorage
1724 groupList = groupListArray.join();
1725 storePath(GROUP_LIST, groupList);
1726
1727 //delete the associated path from local storage
1728 localStorage.removeItem(groupToDelete);
1729
1730 //reload the group lists
1731 reloadSelectors();
1732
1733 //notify the player
1734 notifyPlayer('red', 'Group ' + groupToDelete + ' successfully deleted');
1735 }
1736}
1737
1738function saveGroup() {
1739 let pathTextArea = document.getElementById('pathTextArea').value;
1740 let pathGroupName = document.getElementById('pathGroupName').value;
1741 let newGroup = document.getElementById('newGroup').checked;
1742 let newChain = document.getElementById('newChain').checked;
1743 let custPaths = localStorage.getItem(PATH_LIST);
1744
1745 if (!pathTextArea) {
1746 alert('Path list is blank. Try adding some paths first.');
1747 } else if (!pathGroupName) {
1748 alert('Path group name is blank. Add one and try again');
1749 } else {
1750 //Clean up the name/path before storing
1751 pathGroupName = removeSpaces(pathGroupName);
1752 pathTextArea = removeSpaces(pathTextArea);
1753
1754 //prepend a (+) for a group or (c) for a chain
1755 if (newGroup) {
1756 pathGroupName = '\(g\)' + pathGroupName;
1757 } else if (newChain) {
1758 pathGroupName = '\(c\)' + pathGroupName;
1759 }
1760
1761 //write the new path group/chain to local storage
1762 storePath(pathGroupName, pathTextArea);
1763
1764 //add the new group/chain to the group paths list
1765 custPaths += ',' + pathGroupName;
1766 storePath(GROUP_LIST, custPaths);
1767
1768 //reload the group and paths dropdown
1769 reloadSelectors();
1770
1771 //clear the boxes for the next path
1772 document.getElementById('pathTextArea').value = '';
1773 document.getElementById('pathGroupName').value = '';
1774
1775 //tell the player the new group/chain has been added
1776 notifyPlayer('green','New path group/chain added');
1777 }
1778}
1779
1780
1781/*****************************************************************************\
1782| Pathfinding Code |
1783| Note: uses code in the other authors section for Dijkstra algorithm |
1784\*****************************************************************************/
1785
1786function displayPath() {
1787 let start = document.getElementById(START_PATH_SELECT_ID).value;
1788 let end = document.getElementById(END_PATH_SELECT_ID).value;
1789
1790 let pathString = findPath(start, end);
1791
1792 if (pathString) {
1793 let pathArray = pathString.split(',');
1794 let steps = 0;
1795 let displayString = '';
1796
1797 pathArray.forEach(function(path) {
1798 let pathSteps = localStorage.getItem(path).split(',');
1799 steps += pathSteps.length;
1800 displayString += ' => ' + path;
1801 });
1802
1803 displayString = 'Path found (' + steps +' steps): ' + displayString.slice(4);
1804 notifyPlayer('green', displayString);
1805 } else {
1806 let displayString = 'No path found between ' + start + ' and ' + end;
1807 notifyPlayer('yellow', displayString);
1808 }
1809}
1810
1811function buildPath(pathString) {
1812 let pathArray = pathString.split(',');
1813 let pathSteps = '';
1814
1815 pathArray.forEach(function(path) {
1816 let steps = localStorage.getItem(path);
1817 pathSteps += steps + ',';
1818 //add a comma at the end to allow seperation with the next path
1819 });
1820
1821 //clip the last comma off the string since there isn't another connection
1822 pathSteps = pathSteps.slice(0, -1);
1823 return pathSteps;
1824}
1825
1826function findPath(startRoom, endRoom) {
1827 let pathMap = buildPathMap();
1828 let graph = new Graph(pathMap);
1829 let path = graph.findShortestPath(startRoom, endRoom);
1830 let pathName = [];
1831
1832 if (path) {
1833 //reconstruct the path names from start/ending rooms
1834 for (let i = 0; i < (path.length - 1); i++) {
1835 pathName.push(path[i] + '__2__' + path[i + 1]);
1836 }
1837 //return a comma seperated string of actual paths in the found path
1838 return pathName.join(',');
1839
1840 } else {
1841 //if no path could be found, return null
1842 return null;
1843 }
1844}
1845
1846/*
1847 function to build a path map of all existing paths that is passed to a
1848 pathfinding function
1849*/
1850function buildPathMap() {
1851 let pathArray = localStorage.getItem('CustomPaths_').split(',');
1852 let connectGraph = {};
1853 let startRoomArray = [];
1854
1855 //Get all starting rooms
1856 pathArray.forEach(function(path) {
1857 let rooms = path.split('__2__');
1858 if (rooms[1]) {
1859 startRoomArray.push(rooms[0]);
1860 }
1861 });
1862 //pull a list of only unique starting rooms which are used as nodes
1863 let uniqRooms = [...new Set(startRoomArray)];
1864
1865 //go through each of the unique rooms, find connecting nodes, and write to
1866 //an object with the node as an object property then the connections and
1867 //distance (in steps) as sub properties of the node
1868 uniqRooms.forEach(function(room) {
1869 //omit null rooms if they exist
1870 if (room) {
1871 let connections = {};
1872 pathArray.forEach(function(path) {
1873 //omit null paths if they exist
1874 if (path) {
1875 //split the path name into starting and ending room
1876 let node = path.split('__2__');
1877 //find the distance bettween the nodes by counting path steps
1878 let distance = localStorage.getItem(path).split(',').length;
1879 //omit loops by checking for null ending nodes
1880 if (node[1]) {
1881 if (room === node[0]) {
1882 //write sub property to an object for later merging
1883 connections[node[1]] = distance;
1884 }
1885 }
1886 }
1887 });
1888 //merge in sub properties to the main node
1889 connectGraph[room] = connections;
1890 }
1891 });
1892
1893 return connectGraph;
1894}
1895
1896
1897/*****************************************************************************\
1898| Auto mapping code |
1899\*****************************************************************************/
1900
1901/*
1902let isDragging = false;
1903
1904$("#mapCanvas").mousedown(function(){
1905 isDragging = true;
1906});
1907
1908$(window).mouseup(function(){
1909 isDragging = false;
1910});
1911
1912$(window).mousemove(function(event) {
1913 if (isDragging == true) {
1914 let x = event.clientX - event.target.offsetLeft - (event.target.width / 2);
1915 let y = event.clientY - event.target.offsetTop - (event.target.height / 2);
1916 ctx.setTransform(1,0,0,1,x,y);
1917 map.drawMap(ctx,room_size,map_size);
1918 }
1919});
1920*/
1921
1922function openDatabase() {
1923 let request = indexedDB.open(DB_NAME, DB_VERSION);
1924
1925 request.onerror = function(event) {
1926 console.error('Could not open DB: ' + event.target.error);
1927 };
1928
1929 request.onupgradeneeded = function(event) {
1930 db = event.target.result;
1931
1932 //add the room store if it doesn't already exist
1933 if (!db.objectStoreNames.contains('rooms')) {
1934 let store = db.createObjectStore('rooms', { keyPath: 'id' });
1935
1936 store.createIndex('name', 'name', { unique: false });
1937 store.createIndex('altName', 'altName', { unique: true });
1938 store.createIndex('group', 'group', { unique: false });
1939 }
1940
1941 //add the update timestamp if it doens't already exist
1942 if (!db.objectStoreNames.contains('status')) {
1943 db.createObjectStore('status', { keyPath: 'id', autoIncrement: true });
1944 }
1945
1946 }; //end db upgrade
1947
1948 request.onblocked = (() => {
1949 alert('The auto mapper needs to upgrade the database. Please close all other webmud windows so the addon can do the upgrade then reload this page.');
1950
1951 db.close();
1952 });
1953
1954 request.onsuccess = function(event) {
1955 console.log('DB Opened');
1956 db = event.target.result;
1957
1958 //database upgrade event
1959 db.onversionchange = function() {
1960 db.close();
1961 alert('When the webmud addon was opened in another window, it updated the automap database. The automapper and autopaths will not work again until you reload the page and the addon.');
1962 };
1963
1964 reloadAutoSelectors();
1965
1966 //start mapping by sending an enter to show the room
1967 sendMessageDirect('');
1968 };
1969}
1970
1971function translateDir(dir) {
1972 let dirStr;
1973 switch (dir) {
1974 case 0:
1975 case 'north':
1976 dirStr = 'n';
1977 break;
1978
1979 case 1:
1980 case 'south':
1981 dirStr = 's';
1982 break;
1983
1984 case 2:
1985 case 'east':
1986 dirStr = 'e';
1987 break;
1988
1989 case 3:
1990 case 'west':
1991 dirStr = 'w';
1992 break;
1993
1994 case 4:
1995 case 'northeast':
1996 dirStr = 'ne';
1997 break;
1998
1999 case 5:
2000 case 'northwest':
2001 dirStr = 'nw';
2002 break;
2003
2004 case 6:
2005 case 'southeast':
2006 dirStr = 'se';
2007 break;
2008
2009 case 7:
2010 case 'southwest':
2011 dirStr = 'sw';
2012 break;
2013
2014 case 8:
2015 case 'up':
2016 dirStr = 'u';
2017 break;
2018
2019 case 9:
2020 case 'down':
2021 dirStr = 'd';
2022 break;
2023
2024 default:
2025
2026 }
2027 return dirStr;
2028}
2029
2030function oppositeDir(dir) {
2031 let oppDir;
2032 switch (dir) {
2033 case 'n':
2034 oppDir = 's';
2035 break;
2036 case 's':
2037 oppDir = 'n';
2038 break;
2039 case 'e':
2040 oppDir = 'w';
2041 break;
2042 case 'w':
2043 oppDir = 'e';
2044 break;
2045 case 'ne':
2046 oppDir = 'sw';
2047 break;
2048 case 'nw':
2049 oppDir = 'se';
2050 break;
2051 case 'se':
2052 oppDir = 'nw';
2053 break;
2054 case 'sw':
2055 oppDir = 'ne';
2056 break;
2057 case 'u':
2058 oppDir = 'd';
2059 break;
2060 case 'd':
2061 oppDir = 'u';
2062 break;
2063
2064 default:
2065
2066 }
2067 return oppDir;
2068}
2069
2070function startNewMap(actionData) {
2071 let data = actionData;
2072
2073 roomIdToIndex = [];
2074
2075 roomIdToIndex.push(data.RoomID);
2076
2077 let id = roomIdToIndex.indexOf(data.RoomID);
2078 let name = data.Name;
2079 let exits = {};
2080
2081 for (let exit of data.ObviousExits) {
2082 exits[translateDir(exit)] = -1;
2083 }
2084
2085 let room = new Room(id, name, exits);
2086 let rooms = [];
2087 rooms.push(room);
2088 map = new GameMap(rooms, room);
2089
2090}
2091
2092function getRoom(id) {
2093 let txn = db.transaction(['rooms'], 'readonly');
2094 let store = txn.objectStore('rooms');
2095
2096 return new Promise(function(resolve, reject) {
2097 let trans = store.get(id);
2098
2099 trans.onsuccess = function(event) {
2100 resolve(event.target.result);
2101 };
2102
2103 trans.onerror = function(event) {
2104 reject(Error(event.target.error));
2105 };
2106 });
2107}
2108
2109function getRoomByName(name, useAltName) {
2110 let txn = db.transaction(['rooms'], 'readonly');
2111 let store = txn.objectStore('rooms');
2112 let idx;
2113
2114 if (useAltName) {
2115 idx = store.index('altName');
2116 } else {
2117 idx = store.index('name');
2118 }
2119
2120 return new Promise(function(resolve, reject) {
2121 let trans = idx.get(name);
2122
2123 trans.onsuccess = function(event) {
2124 resolve(event.target.result);
2125 };
2126
2127 trans.onerror = function(event) {
2128 reject(Error(event.target.error));
2129 };
2130 });
2131}
2132
2133function getAllRooms() {
2134 let txn = db.transaction(['rooms'], 'readonly');
2135 let store = txn.objectStore('rooms');
2136
2137 return new Promise(function(resolve, reject) {
2138 let trans = store.getAll();
2139
2140 trans.onsuccess = function(event) {
2141 resolve(event.target.result);
2142 };
2143
2144 trans.onerror = function(event) {
2145 reject(Error(event.target.error));
2146 };
2147 });
2148}
2149
2150function addRoom(actionData) {
2151 let txn = db.transaction(['rooms'], 'readwrite');
2152 let store = txn.objectStore('rooms');
2153
2154 let id = actionData.RoomID;
2155 let name = actionData.Name.toLowerCase().trim();
2156 let altName = null;
2157 let commands = null;
2158 let unlisted = false;
2159
2160 let exits = {};
2161
2162 for (let exit of actionData.ObviousExits) {
2163 exits[translateDir(exit)] = -1;
2164 }
2165
2166 let group;
2167 let nameSplit = name.split(',');
2168
2169 if (nameSplit[1]) {
2170 group = nameSplit[0].toLowerCase().trim();
2171 } else {
2172 group = '_not_grouped';
2173 }
2174
2175 let newRoom = new Room(id, name, exits, altName, group, commands, unlisted);
2176
2177 return new Promise(function(resolve, reject) {
2178 let trans = store.put(newRoom);
2179
2180 trans.onsuccess = function() {
2181 reloadAutoSelectors();
2182 updateLastWrite();
2183 resolve(newRoom);
2184 };
2185
2186 trans.onerror = function(event) {
2187 reject(Error(event.target.error));
2188 };
2189 });
2190}
2191
2192function updateRoom(room) {
2193 let txn = db.transaction(['rooms'], 'readwrite');
2194 let store = txn.objectStore('rooms');
2195
2196 //format the room before saving
2197 if (room.altName) {
2198 room.altName = room.altName.toLowerCase().trim();
2199 }
2200
2201 if (room.group) {
2202 room.group = room.group.toLowerCase().trim();
2203 }
2204
2205 return new Promise(function(resolve, reject) {
2206 let trans = store.put(room);
2207
2208 trans.onsuccess = function(event) {
2209 updateLastWrite();
2210 resolve(event.target.result);
2211 };
2212
2213 trans.onerror = function(event) {
2214 reject(Error(event.target.error));
2215 };
2216 });
2217}
2218
2219function loadMap(actionData) {
2220 //start auto mapping
2221 ctx = document.getElementById('mapCanvas').getContext('2d');
2222
2223 getRoom(actionData.RoomID)
2224 .then(function(room) {
2225 if (!room) {
2226 return addRoom(actionData);
2227 } else {
2228 return Promise.resolve(room);
2229 }
2230 })
2231 .then(function(room) {
2232 map = new GameMap('rooms', room);
2233
2234 //now that map is loaded, show the room again to start the map
2235 sendMessageDirect('');
2236
2237 })
2238 .catch(function(err) {
2239 console.error(err);
2240 });
2241}
2242
2243function resetMap() {
2244 let txn = db.transaction(['rooms'], 'readwrite');
2245 let store = txn.objectStore('rooms');
2246
2247 store.clear().onsuccess = function() {
2248 //clear the current map
2249 map = null;
2250 //show the room to load a new map
2251 sendMessageDirect('');
2252 };
2253
2254}
2255
2256function downloadMap() {
2257 if (map) {
2258 let exCanvas = document.createElement('canvas');
2259 exCanvas.width = 8000;
2260 exCanvas.height = 8000;
2261 let exCtx = exCanvas.getContext('2d');
2262
2263 map.export = true;
2264 //draw size is set to 999,999 for the export so all rooms are drawn
2265 map.drawMap(exCtx, room_size, 999999, curRoomID);
2266 exCanvas = null;
2267 }
2268}
2269
2270/*
2271 function to build a path map of all known rooms in the map that is passed
2272 to a pathfinding function.
2273
2274 Async, returns a Promise
2275*/
2276function buildAutoPathMap() {
2277 let txn = db.transaction(['rooms'], 'readonly');
2278 let store = txn.objectStore('rooms');
2279
2280 let returnObject = {};
2281 let connectGraph = {};
2282
2283 return new Promise(function(resolve, reject) {
2284 let trans = store.getAll();
2285
2286 trans.onsuccess = function(event) {
2287 let rooms = event.target.result;
2288
2289 for (let room of rooms) {
2290 let connections = {};
2291
2292 for (let exit in room.exits) {
2293 let exitId = room.exits[exit];
2294 connections[exitId] = 1;
2295 }
2296 connectGraph[room.id] = connections;
2297 }
2298
2299 returnObject.rooms = rooms;
2300 returnObject.connectGraph = connectGraph;
2301
2302 resolve(returnObject);
2303 };
2304
2305 trans.onerror = function(event) {
2306 reject(Error(event.target.error));
2307 };
2308
2309 });
2310}
2311
2312function findAutoPath(startID, endID) {
2313 let rooms = {};
2314
2315 return new Promise(function(resolve, reject) {
2316 buildAutoPathMap()
2317 .then(function(result) {
2318 let pathMap = result.connectGraph;
2319 rooms = result.rooms;
2320
2321 let graph = new Graph(pathMap);
2322 let path = graph.findShortestPath(startID, endID);
2323
2324 return Promise.resolve(path);
2325 })
2326 .then(function(result) {
2327 let pathList = result;
2328 let path = [];
2329
2330 let lookup = {};
2331
2332 for (let i = 0; i < rooms.length; i++) {
2333 lookup[rooms[i].id] = rooms[i];
2334 }
2335
2336 for (let i = 0; i < pathList.length; i++) {
2337 let exits = lookup[pathList[i]].exits;
2338 for (let exit in exits) {
2339 if ( exits[exit] === pathList[i + 1] ) {
2340 path.push(exit);
2341 } //end if check
2342 } //end for exit in exits loop
2343 } //end for pathList loop
2344
2345 //return comma seperated string of the path
2346 resolve(path.join(','));
2347 })
2348 .catch(function(err) {
2349 reject(Error(err));
2350 });
2351 });
2352}
2353
2354function reloadAutoSelectors() {
2355 //reload the autopath group and path selectors (partially async)
2356 let allRooms = [];
2357
2358 getAllRooms()
2359 .then(function(result) {
2360 allRooms = result;
2361 let uniqueAutos = getUniqueAutos(allRooms, false);
2362 let groupArray = uniqueAutos.groups;
2363
2364 let curAutoGroup = document.getElementById('autoGroup').value;
2365 let selectorToLoad = document.getElementById('autoGroup');
2366 loadSelector(groupArray, selectorToLoad);
2367 selectIfExists(curAutoGroup, 'autoGroup');
2368
2369 autoGroupSelected('autoGroup', 'autoRoom', false);
2370 })
2371 .catch(function(err) {
2372 console.error(err);
2373 });
2374}
2375
2376function getUniqueAutos(allRooms, showUnlisted) {
2377 let groupArray = [];
2378 let roomArray = [];
2379 let returnObject = {};
2380 let notUnique = [];
2381
2382 for (let room of allRooms) {
2383 let groupIndex = groupArray.indexOf(room.group);
2384 if (groupIndex === -1) {
2385 groupArray.push(room.group);
2386 }
2387
2388 if (!room.unlisted || showUnlisted) {
2389 //process room if it should be listed
2390 let roomIndex;
2391 let roomUnqIdx;
2392
2393 //process alt name if it's set
2394 if (room.altName || room.altName === '') {
2395 roomIndex = roomArray.indexOf(room.altName);
2396 roomUnqIdx = notUnique.indexOf(room.altName);
2397
2398 if (room.altName) {
2399 if (roomIndex === -1 && roomUnqIdx === -1) {
2400 roomArray.push(room.altName);
2401
2402 } else if (roomIndex !== -1) {
2403 roomArray.splice(roomIndex,1);
2404 notUnique.push(room.altName);
2405 }
2406 } //end if altName check
2407
2408 } else {
2409 //process normal room name since alt name doesn't exist
2410 roomIndex = roomArray.indexOf(room.name);
2411 roomUnqIdx = notUnique.indexOf(room.name);
2412
2413 if (roomIndex === -1 && roomUnqIdx === -1) {
2414 roomArray.push(room.name);
2415
2416 } else if (roomIndex !== -1) {
2417
2418 roomArray.splice(roomIndex,1);
2419 notUnique.push(room.name);
2420 }
2421 } //end else
2422 } //end if unlisted/showUnlisted check
2423 } //end for room of allrooms
2424
2425 returnObject.groups = groupArray;
2426 returnObject.rooms = roomArray;
2427
2428 return returnObject;
2429}
2430
2431function autoGroupSelected(group, selector, showUnlisted) {
2432 let txn = db.transaction(['rooms'], 'readonly');
2433 let store = txn.objectStore('rooms');
2434 let index = store.index('group');
2435
2436 let selectedGroup = document.getElementById(group).value;
2437
2438 return new Promise(function(resolve, reject) {
2439 let trans = index.getAll(selectedGroup);
2440
2441 trans.onsuccess = function(event) {
2442 let uniques = getUniqueAutos(event.target.result, showUnlisted);
2443
2444 let selectorToLoad = document.getElementById(selector);
2445 loadSelector(uniques.rooms, selectorToLoad);
2446
2447 resolve(event.target.result);
2448 };
2449
2450 trans.onerror = function(event) {
2451 reject(Error(event.target.error));
2452 };
2453 });
2454}
2455
2456function displayAutoPath() {
2457 let start = curRoomID;
2458 let end = document.getElementById('autoRoom').value;
2459 let dest = end;
2460 let useAltName = checkAltName(end);
2461
2462 getRoomByName(end, useAltName)
2463 .then(function(result) {
2464 end = result.id;
2465
2466 findAutoPath(start, end)
2467 .then(function(result) {
2468 let pathString = result;
2469
2470 if (pathString) {
2471 let pathArray = pathString.split(',');
2472 let steps = pathArray.length;
2473
2474 notifyPlayer('green', 'Path found to ' + dest + ' (' + steps + ' steps)');
2475 } else {
2476 let displayString = 'No path found to destination room';
2477 notifyPlayer('yellow', displayString);
2478 }
2479 });
2480 })
2481 .catch(function(err) {
2482 console.error(err);
2483 });
2484}
2485
2486function autoMapGo(start, end, endName, type) {
2487 findAutoPath(start, end)
2488 .then(function(result) {
2489 let pathString = result;
2490 let steps = pathString.split(',').length;
2491 let delay = document.getElementById('stepDelay').value;
2492 let dest = 'AutoMap walk to ' + endName;
2493
2494 if (pathString) {
2495 notifyPlayer('yellowgreen', dest + ' (' + steps + ' steps)');
2496
2497 if (type === 'run') {
2498 sendMessageDirect('DisableAI Combat');
2499 document.getElementById('chkAICombat').checked = false;
2500 }
2501
2502 walkPath(pathString, delay, false, dest);
2503
2504 } else {
2505 let displayString = 'No path found to destination room';
2506 notifyPlayer('yellow', displayString);
2507 }
2508 })
2509 .catch(function(err) {
2510 console.error(err);
2511 });
2512}
2513
2514function autoMapMoveByName(start, endName, type) {
2515 let useAltName = checkAltName(endName);
2516
2517 getRoomByName(endName, useAltName)
2518 .then(function(result) {
2519 let end = result.id;
2520
2521 if (useAltName) {
2522 endName = result.altName;
2523 }
2524
2525 autoMapGo(start, end, endName, type);
2526 })
2527 .catch(function(err) {
2528 console.error(err);
2529 });
2530
2531}
2532
2533function reloadAutoEditSel() {
2534 //reload the autopath group and path selectors (partially async)
2535 let allRooms = [];
2536
2537 getAllRooms()
2538 .then(function(result) {
2539 allRooms = result;
2540 let uniqueAutos = getUniqueAutos(allRooms, true);
2541 let groupArray = uniqueAutos.groups;
2542
2543 let curAutoGroup = document.getElementById('mapGroupSel').value;
2544 let selectorToLoad = document.getElementById('mapGroupSel');
2545 loadSelector(groupArray, selectorToLoad);
2546 selectIfExists(curAutoGroup, 'mapGroupSel');
2547
2548 autoGroupSelected('mapGroupSel', 'mapRoomSel', true);
2549 })
2550 .catch(function(err) {
2551 console.error(err);
2552 });
2553}
2554
2555function mapRoomSelected() {
2556 //get detail about the current room then load it into the edit fields
2557 let roomName = document.getElementById('mapRoomSel').value;
2558 let useAltName = checkAltName(roomName);
2559
2560 getRoomByName(roomName, useAltName)
2561 .then(function(result) {
2562 let roomDetail = result;
2563 loadRoomFields(roomDetail);
2564 })
2565 .catch(function(err) {
2566 console.error(err);
2567 });
2568}
2569
2570function loadRoomFields(room) {
2571 document.getElementById('mapId').value = room.id;
2572 document.getElementById('mapName').value = room.name;
2573 document.getElementById('mapUnlisted').checked = room.unlisted;
2574
2575 //the following entries may be null so must first be checked
2576 let altName = document.getElementById('mapAltName');
2577 if (room.altName) {
2578 altName.value = room.altName;
2579 } else {
2580 altName.value = '';
2581 }
2582
2583 let group = document.getElementById('mapGroup');
2584 if (room.group) {
2585 group.value = room.group;
2586 } else {
2587 group.value = '';
2588 }
2589
2590 let mapCmd = document.getElementById('mapCmd');
2591 if(room.commands) {
2592 mapCmd.value = room.commands;
2593 } else {
2594 mapCmd.value = '';
2595 }
2596}
2597
2598function editAutoRooms() {
2599 //load the selectors
2600 reloadAutoEditSel();
2601
2602 //get detail about the current room then load it into the edit fields
2603 getRoom(curRoomID)
2604 .then(function(result) {
2605 let curRoomDetail = result;
2606 loadRoomFields(curRoomDetail);
2607 })
2608 .catch(function(err) {
2609 console.error(err);
2610 });
2611}
2612
2613function saveRoomEdits() {
2614 let id = document.getElementById('mapId').value;
2615 let name = document.getElementById('mapName').value;
2616 let altName = document.getElementById('mapAltName').value;
2617 let group = document.getElementById('mapGroup').value;
2618 let commands = document.getElementById('mapCmd').value;
2619 let unlisted = document.getElementById('mapUnlisted').checked;
2620 let exits;
2621
2622 if (group === '' || !group) {
2623 group = '_not_grouped';
2624 }
2625
2626 //add an _ to the altName to signify it's an alternate name
2627 if ( altName && (altName !== '') && !checkAltName(altName) ) {
2628 altName += '_';
2629 }
2630
2631 getRoom(id)
2632 .then(function(result) {
2633 exits = result.exits;
2634 let room = new Room(id, name, exits, altName, group, commands, unlisted);
2635
2636 updateRoom(room)
2637 .then(function() {
2638 alert('Room updated');
2639 })
2640 .then(function() {
2641 return reloadAutoEditSel();
2642 })
2643 .then(function() {
2644 return reloadAutoSelectors();
2645 });
2646 })
2647 .catch(function(err) {
2648 console.error(err);
2649 });
2650
2651}
2652
2653function deleteRoomById(id) {
2654 let txn = db.transaction(['rooms'], 'readwrite');
2655 let store = txn.objectStore('rooms');
2656
2657 let trans = store.delete(id);
2658
2659 return new Promise(function(resolve, reject) {
2660 trans.onsuccess = function(event) {
2661 updateLastWrite();
2662 resolve(event.target.result);
2663 };
2664
2665 trans.onerror = function(event) {
2666 reject(Error(event.target.error));
2667 };
2668 });
2669}
2670
2671function deleteAutoRoom(id) {
2672 deleteRoomById(id)
2673 .then(function() {
2674 alert('Room Deleted');
2675 })
2676 .then(function() {
2677 return reloadAutoEditSel();
2678 })
2679 .then(function() {
2680 reloadAutoSelectors();
2681 //send an enter to re-add the room
2682 sendMessageDirect('');
2683 })
2684 .catch(function(err) {
2685 console.error(err);
2686 });
2687}
2688
2689function checkAltName(roomName) {
2690 if (roomName.slice(-1) === '_') {
2691 return true;
2692 } else {
2693 return false;
2694 }
2695}
2696
2697function exportMap() {
2698 //get all the map rooms then turn them into a json string and export
2699 getAllRooms()
2700 .then(function(result) {
2701 let mapExport = JSON.stringify(result);
2702
2703 let exportLink = document.createElement('a');
2704 exportLink.target = '_blank';
2705 exportLink.href = 'data:text/plain,' + encodeURIComponent(mapExport);
2706 exportLink.download = 'WebMud_Map_Export.txt';
2707 document.body.appendChild(exportLink);
2708 exportLink.click();
2709 document.body.removeChild(exportLink);
2710 //window.open(exportLink.href);
2711
2712 /*
2713 let exportArea = document.getElementById('mapImpExpText');
2714
2715 exportArea.value = mapExport;
2716 */
2717 });
2718}
2719
2720function importMapFromFile(event) {
2721 let file = event.target.files[0];
2722
2723 if (file.type.match('text/plain')) {
2724 let reader = new FileReader();
2725
2726 //read data from file in as text
2727 reader.readAsText(file);
2728
2729 //once the file is loaded, process it
2730 reader.onload = (evt => {
2731 let data = evt.target.result;
2732 let mapParse;
2733
2734 //verify the file contents are valid JSON
2735 try {
2736 mapParse = JSON.parse(data);
2737
2738 } catch (err) {
2739
2740 console.error(err);
2741 alert('The selected file did not contain a valid map export.');
2742 return false;
2743 }
2744
2745 //now that we know the file is valid, import it
2746 importMap(mapParse);
2747 });
2748
2749 } else {
2750 alert('Invalid file selected');
2751 }
2752
2753}
2754
2755function importMap(mapData) {
2756 let playerConfirm = window.confirm('This will update your current map, are you sure?');
2757
2758 if (playerConfirm) {
2759 //check the first room to see if it exists to verify parsing success
2760 if ( mapData[0].hasOwnProperty('id') ) {
2761 for (let room of mapData) {
2762 updateRoom(room).then();
2763 }
2764
2765 //show the room to load a new map
2766 sendMessageDirect('');
2767
2768 //reload the group/room selectors then alert the player
2769 reloadAutoSelectors();
2770
2771 alert('Import complete');
2772
2773 } else {
2774
2775 alert('The selected file did not contain a valid map export.');
2776
2777 }//end map validity check
2778 } //end player confirm
2779} //end importMap function
2780
2781function updateLastWrite() {
2782 let txn = db.transaction(['status'], 'readwrite');
2783 let store = txn.objectStore('status');
2784
2785 let writeStamp = {
2786 id: 'last write',
2787 stamp: Date.now()
2788 };
2789
2790 return new Promise( (resolve, reject) => {
2791 let trans = store.put(writeStamp);
2792
2793 trans.onsuccess = function(event) {
2794 resolve(event.target.result);
2795 };
2796
2797 trans.onerror = function(event) {
2798 reject(Error(event.target.error));
2799 };
2800 });
2801}
2802
2803function getLastWrite() {
2804 let txn = db.transaction(['status'], 'readonly');
2805 let store = txn.objectStore('status');
2806
2807 return new Promise( (resolve, reject) => {
2808 let trans = store.get('last write');
2809
2810 trans.onsuccess = function(event) {
2811 let result = event.target.result;
2812 if (result) {
2813 resolve(event.target.result);
2814 } else {
2815
2816 let writeStamp = {
2817 id: 'last write',
2818 stamp: Date.now()
2819 };
2820
2821 resolve(writeStamp);
2822 }
2823
2824 };
2825
2826 trans.onerror = function(event) {
2827 reject(Error(event.target.error));
2828 };
2829 });
2830}
2831
2832
2833/*****************************************************************************\
2834| General functions |
2835\*****************************************************************************/
2836
2837function notifyPlayer(msgColor, msgText) {
2838 //Used to alert the player by appending messages to the main window
2839 let status = document.getElementById('addonStatusText');
2840 status.style = 'color:' + msgColor;
2841 status.innerHTML = msgText;
2842}
2843
2844function removeSpaces(str) {
2845 return str.replace(/\s+/g, '');
2846}
2847
2848function reloadSelectors() {
2849 //Save the current state of the group selectors
2850 let curStartGroup = document.getElementById(START_GROUP_SELECT_ID).value;
2851 let curEndGroup = document.getElementById(END_GROUP_SELECT_ID).value;
2852 let curGroupingGroup = document.getElementById('groupingGroupSelect').value;
2853
2854 //reload the group selectors
2855 let groupListArray = localStorage.getItem(GROUP_LIST).split(',');
2856
2857 let selectorToLoad = document.getElementById(START_GROUP_SELECT_ID);
2858 loadSelector(groupListArray, selectorToLoad);
2859
2860 selectorToLoad = document.getElementById(END_GROUP_SELECT_ID);
2861 loadSelector(groupListArray, selectorToLoad);
2862
2863 selectorToLoad = document.getElementsByClassName('groupSelect');
2864 loadSelector(groupListArray, selectorToLoad);
2865
2866 //select previously selected group, if it exists
2867 selectIfExists(curStartGroup, START_GROUP_SELECT_ID);
2868 selectIfExists(curEndGroup, END_GROUP_SELECT_ID);
2869 selectIfExists(curGroupingGroup, 'groupingGroupSelect');
2870
2871 //reload the path selector based on the group
2872 loadRooms(document.getElementById(START_GROUP_SELECT_ID).value, START_PATH_SELECT_ID, 'start');
2873 loadRooms(document.getElementById(END_GROUP_SELECT_ID).value, END_PATH_SELECT_ID, 'end');
2874
2875 //reload the group path selector based on the selected group
2876 curGroupingGroup = document.getElementById('groupingGroupSelect').value;
2877 let pathArray = localStorage.getItem(curGroupingGroup).split(',');
2878
2879 selectorToLoad = document.getElementById('groupingCurPathSelect');
2880 loadSelector(pathArray, selectorToLoad);
2881
2882 //reload the all_paths selector
2883 pathArray = localStorage.getItem(PATH_LIST).split(',');
2884 selectorToLoad = document.getElementsByClassName('pathSelect');
2885 loadSelector(pathArray, selectorToLoad);
2886
2887}
2888
2889function loadRooms(pathList, selectorId, type) {
2890 let pathArray = localStorage.getItem(pathList).split(',');
2891 let startRoomArray = [];
2892 let endRoomArray = [];
2893 let selectorToLoad = document.getElementById(selectorId);
2894
2895 pathArray.forEach(function(path) {
2896 let rooms = path.split('__2__');
2897 if (rooms[0]) {
2898 startRoomArray.push(rooms[0]);
2899 }
2900 if (rooms[1]) {
2901 endRoomArray.push(rooms[1]);
2902 }
2903 });
2904
2905 if (type === 'start') {
2906 let uniqRooms = [...new Set(startRoomArray)];
2907 loadSelector(uniqRooms, selectorToLoad);
2908 } else {
2909 let uniqRooms = [...new Set(endRoomArray)];
2910 loadSelector(uniqRooms, selectorToLoad);
2911 }
2912}
2913
2914function loadSelector(optionArray, selectorToLoad) {
2915 //Sort the array before populating the list
2916 optionArray.sort();
2917
2918 let selectors = selectorToLoad.length;
2919
2920 if (!(selectorToLoad instanceof HTMLCollection)) {
2921 //false: only one selector given, process as single
2922
2923 //Clear the current options
2924 selectorToLoad.options.length = 0;
2925
2926 //Populate the list from the array
2927 optionArray.forEach(function(item) {
2928 if (item) {
2929 let opt = document.createElement('option');
2930 opt.textContent = item;
2931 opt.value = item;
2932 selectorToLoad.add(opt);
2933 }
2934 });
2935
2936 } else {
2937 //true: collection given, process as multiple
2938 for (let i = 0; i < selectors; i++) {
2939 //Clear the current options
2940 selectorToLoad[i].options.length = 0;
2941
2942 //Populate the list from the array
2943 for (let item of optionArray) {
2944 if (item) {
2945 let opt = document.createElement('option');
2946 opt.textContent = item;
2947 opt.value = item;
2948 selectorToLoad[i].add(opt);
2949 } //end item if
2950 } //end for item
2951 } //end for selectors
2952 } //end else
2953} //end function loadSelector
2954
2955/*
2956 This function is used by the selectors for the onchange event
2957*/
2958function groupSelected(groupSelectId, pathSelectId) {
2959 //save currently selected paths
2960 let curStartRoom = document.getElementById(START_PATH_SELECT_ID).value;
2961 let curEndRoom = document.getElementById(END_PATH_SELECT_ID).value;
2962 let curGroupingRoom = document.getElementById('groupingCurPathSelect').value;
2963
2964 //reload all of the selectors
2965 reloadSelectors();
2966
2967 //reselect previously selected paths for all path selectors if it still exists
2968 selectIfExists(curStartRoom, START_PATH_SELECT_ID);
2969 selectIfExists(curEndRoom, END_PATH_SELECT_ID);
2970 selectIfExists(curGroupingRoom, 'groupingCurPathSelect');
2971}
2972
2973function selectIfExists(valToSel, selectorId) {
2974 let exists = document.querySelector('#' + selectorId + ' [value="' + valToSel + '"]');
2975 if (valToSel && exists) {
2976 document.querySelector('#' + selectorId + ' [value="' + valToSel + '"]').selected = true;
2977 }
2978}
2979
2980function combatEnd() {
2981 inCombat = false;
2982 combatEndTime = Date.now();
2983}
2984
2985function combatStart() {
2986 lastSwingTime = Date.now();
2987 inCombat = true;
2988}
2989
2990function toggleMoveButtons(turnOn) {
2991 let walk = document.getElementById('pathWalk');
2992 let run = document.getElementById('pathRun');
2993 let loop = document.getElementById('pathLoop');
2994 let autoMapWalk = document.getElementById('autoMapWalk');
2995 let autoMapRun = document.getElementById('autoMapRun');
2996
2997 if (turnOn) {
2998 //enable the buttons
2999 walk.disabled = false;
3000 run.disabled = false;
3001 loop.disabled = false;
3002 autoMapWalk.disabled = false;
3003 autoMapRun.disabled = false;
3004 } else {
3005 //disable the buttons
3006 walk.disabled = true;
3007 run.disabled = true;
3008 loop.disabled = true;
3009 autoMapWalk.disabled = true;
3010 autoMapRun.disabled = true;
3011 }
3012}
3013
3014
3015/*****************************************************************************\
3016| Event handling code |
3017\*****************************************************************************/
3018
3019//Deal with events from the WebMud hub
3020/*
3021 Start combat .attack
3022 End combat .breakCombat or .death
3023 Start move .playerMove
3024 End move .showRoom
3025 AI Command .aiCommand
3026 Party messages .partyMessage
3027
3028 Note: setting the variables then passing them actionData is necessary
3029 otherwise the main webmud window won't show the message normally
3030*/
3031
3032//Physical combat started, set combat flag to true
3033let wm_moveToAttack = window.attack;
3034window.attack = function(actionData) {
3035 wm_moveToAttack(actionData);
3036 combatStart();
3037};
3038
3039//Spell combat started, set combat flag to true
3040let wm_castSpell = window.castSpell;
3041window.castSpell = function(actionData) {
3042 wm_castSpell(actionData);
3043 switch (actionData.Result) {
3044 case -11:
3045 //attempt to cast but fail
3046 if (actionData.CasterID === playerID) {
3047 lastSwingTime = Date.now();
3048 }
3049 return;
3050 case 4:
3051 //Player cast (good and bad)
3052 if (actionData.CasterID === playerID && actionData.EvilInCombat) {
3053 lastSwingTime = Date.now();
3054 }
3055 return;
3056 case 7:
3057 //single target move to cast
3058 if (actionData.CasterID === playerID) {
3059 combatStart();
3060 }
3061 return;
3062 case 8:
3063 //room move to cast
3064 if (actionData.CasterID === playerID) {
3065 combatStart();
3066 }
3067 return;
3068 }
3069};
3070
3071//Exp earned, set combat flag to false
3072let wm_gainExperience = window.gainExperience;
3073window.gainExperience = function(actionData) {
3074 wm_gainExperience(actionData);
3075 combatEnd();
3076};
3077
3078//Move started, set move flag to true
3079let wm_playerMove = window.playerMove;
3080window.playerMove = function(actionData) {
3081 wm_playerMove(actionData);
3082 movedFrom = {};
3083
3084 if (actionData.Result === 1 || actionData.Result === 2) {
3085 playerMoving = true;
3086 inCombat = false;
3087 movedFrom = {
3088 'id':curRoomID,
3089 'dir':translateDir(actionData.Direction)
3090 };
3091 }
3092
3093};
3094
3095//Move ended, set move flag to false
3096let wm_showRoom = window.showRoom;
3097window.showRoom = function(actionData) {
3098 wm_showRoom(actionData);
3099 playerMoving = false;
3100
3101 if (movedFrom.hasOwnProperty('id') || !curRoomID || playerFollowing) {
3102 //only update room id if player actually moved (not looked)
3103 curRoomID = actionData.RoomID;
3104
3105 //reset the follow flag
3106 playerFollowing = false;
3107 }
3108
3109 //check to workaround a webmud bug with players being too heavy
3110 if (movedFrom.id === curRoomID) {
3111 movedFrom = {};
3112 }
3113
3114 if (!map) {
3115 loadMap(actionData);
3116
3117 } else {
3118
3119 if (movedFrom.hasOwnProperty('id')) {
3120 map.move(movedFrom, actionData);
3121 //reset movedFrom
3122 movedFrom = {};
3123
3124 } else {
3125 map.drawMap(ctx, room_size, map_size, curRoomID);
3126 }
3127
3128 }
3129};
3130
3131//If plalyer is looking vs moving, keep room ID the same
3132let wm_followLeader = window.followLeader;
3133window.followLeader = function(actionData) {
3134 playerFollowing = true;
3135 wm_followLeader(actionData);
3136};
3137
3138//Note time of last combat swing to fix movement combat bug
3139let wm_combatSwing = window.combatSwing;
3140window.combatSwing = function(actionData) {
3141 wm_combatSwing(actionData);
3142 if (actionData.AttackerID === playerID) {
3143 lastSwingTime = Date.now();
3144 }
3145};
3146
3147//Deal with AI commands
3148let wm_aiCommand = window.aiCommand;
3149window.aiCommand = function(actionData) {
3150 wm_aiCommand(actionData);
3151 if (actionData.Hint === 'Running - Low HP') {
3152 aiRunning = true;
3153 } else if (actionData.Hint === 'Moving back - Health/Mana OK') {
3154 aiRunning = false;
3155 playerResting = false;
3156 }
3157
3158 if (actionData.TypedCommand === 'rest') {
3159 playerResting = true;
3160 }
3161};
3162
3163//Deal with party messages
3164let wm_partyMessage = window.partyMessage;
3165window.partyMessage = function(actionData) {
3166 wm_partyMessage(actionData);
3167 let slicedMessage = actionData.MessageText.slice(0,8);
3168 if (slicedMessage === 'Running!') {
3169 aiRunning = true;
3170 }
3171};
3172
3173//Keep track of party health
3174let wm_party = window.party;
3175window.party = function(actionData) {
3176 wm_party(actionData);
3177 partyInfo = actionData.PartyMemberInfos;
3178};
3179
3180//Remove resting flag when player is a full HP/MA
3181let wm_updateHPMA = window.updateHPMA;
3182window.updateHPMA = function(actionData) {
3183 wm_updateHPMA(actionData);
3184 if (actionData.HP === actionData.MaxHP && actionData.MA === actionData.MaxMA) {
3185 playerResting = false;
3186 }
3187};
3188
3189//event listener for the dialog modal
3190document.getElementById('importExportDialog').addEventListener('click', function(e) {
3191 switch (e.target.id) {
3192 case 'importPath':
3193 getPathsToImport('CustomPaths_', 'pathGroups_');
3194 break;
3195
3196 case 'exportSingle':
3197 exportPaths('CustomPaths_', 'pathGroups_', 'single');
3198 break;
3199
3200 case 'exportGroup':
3201 exportPaths('CustomPaths_', 'pathGroups_', 'group');
3202 break;
3203
3204 case 'exportAll':
3205 exportPaths('CustomPaths_', 'pathGroups_', 'all');
3206 break;
3207
3208 case 'clearAllPaths':
3209 clearAllPaths('CustomPaths_','pathGroups_');
3210 break;
3211
3212 default:
3213
3214 }
3215},false);
3216
3217//event listener for the map options modal
3218document.getElementById('mapOptions').addEventListener('click', function(e) {
3219 switch (e.target.id) {
3220 case 'applyMapOptions':
3221 map_size = parseInt(document.getElementById('mapSize').value);
3222 room_size = parseInt(document.getElementById('roomSize').value);
3223 map.drawMap(ctx, room_size, map_size, curRoomID);
3224 break;
3225
3226 default:
3227
3228 }
3229},false);
3230
3231//event listener for the map
3232document.getElementById('divMap').addEventListener('click', function(e) {
3233 switch (e.target.id) {
3234 case 'downloadMap':
3235 downloadMap();
3236 break;
3237
3238 case 'resetMap':
3239 resetMap();
3240 break;
3241
3242 case 'importMap':
3243 document.getElementById('importFileBrowse').click();
3244 break;
3245
3246 case 'exportMap':
3247 exportMap();
3248 break;
3249
3250 case 'bigMap':
3251 document.getElementById('divMap').hidden = true;
3252 map.startBigMap();
3253 break;
3254
3255 default:
3256
3257 }
3258},false);
3259
3260//event listener for the import map file picker
3261document.getElementById('importFileBrowse').addEventListener('change', (e => {
3262 importMapFromFile(e);
3263}));
3264
3265//event listener for the map import/export modal
3266document.getElementById('mapImpExpModal').addEventListener('click', function(e) {
3267 switch (e.target.id) {
3268 case 'importMap':
3269 importMap();
3270 break;
3271
3272 case 'exportMap':
3273 exportMap();
3274 break;
3275
3276 default:
3277
3278 }
3279},false);
3280
3281//event listener for auto path
3282document.getElementById('divAutoPath').addEventListener('click', function(e) {
3283 let end = document.getElementById('autoRoom').value;
3284 switch (e.target.id) {
3285 case 'editAutoRooms':
3286 editAutoRooms();
3287 break;
3288
3289 case 'autoMapWalk':
3290 autoMapMoveByName(curRoomID, end, 'walk');
3291 break;
3292
3293 case 'autoMapRun':
3294 autoMapMoveByName(curRoomID, end, 'run');
3295 break;
3296
3297 default:
3298
3299 }
3300},false);
3301
3302//event listener for map edit
3303document.getElementById('mapEdit').addEventListener('click', function(e) {
3304 let id = document.getElementById('mapId').value;
3305
3306 switch (e.target.id) {
3307 case 'applyMapEdits':
3308 saveRoomEdits();
3309 break;
3310
3311 case 'deleteRoom':
3312 deleteAutoRoom(id);
3313 break;
3314
3315 default:
3316
3317 }
3318},false);
3319
3320//keypress events for numpad walking
3321$(document).keyup(function(e) {
3322 switch (e.which) {
3323 case 96: //numpad 0
3324 sendMessageDirect('rest');
3325 $('#message').val('');
3326 break;
3327
3328 case 97: //numpad 1
3329 sendMessageDirect('sw');
3330 $('#message').val('');
3331 break;
3332
3333 case 98: //numpad 2
3334 sendMessageDirect('s');
3335 $('#message').val('');
3336 break;
3337
3338 case 99: //numpad 3
3339 sendMessageDirect('se');
3340 $('#message').val('');
3341 break;
3342
3343 case 100: //numpad 4
3344 sendMessageDirect('w');
3345 $('#message').val('');
3346 break;
3347
3348 case 101: //numpad 5
3349 sendMessageDirect('sn');
3350 $('#message').val('');
3351 break;
3352
3353 case 102: //numpad 6
3354 sendMessageDirect('e');
3355 $('#message').val('');
3356 break;
3357
3358 case 103: //numpad 7
3359 sendMessageDirect('nw');
3360 $('#message').val('');
3361 break;
3362
3363 case 104: //numpad 8
3364 sendMessageDirect('n');
3365 $('#message').val('');
3366 break;
3367
3368 case 105: //numpad 9
3369 sendMessageDirect('ne');
3370 $('#message').val('');
3371 break;
3372
3373 case 107: //numpad +
3374 sendMessageDirect('u');
3375 $('#message').val('');
3376 break;
3377
3378 case 109: //numpad -
3379 sendMessageDirect('d');
3380 $('#message').val('');
3381 break;
3382
3383 case 110: //numpad .
3384 sendMessageDirect('med');
3385 $('#message').val('');
3386 break;
3387
3388 default:
3389
3390 }
3391
3392});
3393
3394
3395
3396//The following MIT license and credits apply to the Graph class, which is an
3397//implementation of the Dijkstra algorithm. Code was obtained from github on
3398//2016-03-21 at https://github.com/andrewhayward/dijkstra
3399/*
3400The MIT License (MIT)
3401
3402Copyright (c) 2014 Andrew Hayward
3403
3404Permission is hereby granted, free of charge, to any person obtaining a copy
3405of this software and associated documentation files (the "Software"), to deal
3406in the Software without restriction, including without limitation the rights
3407to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3408copies of the Software, and to permit persons to whom the Software is
3409furnished to do so, subject to the following conditions:
3410
3411The above copyright notice and this permission notice shall be included in all
3412copies or substantial portions of the Software.
3413
3414THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3415IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3416FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3417AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3418LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3419OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3420SOFTWARE.
3421*/
3422
3423var Graph = (function (undefined) {
3424
3425 var extractKeys = function(obj) {
3426 var keys = [], key;
3427 for (key in obj) {
3428 Object.prototype.hasOwnProperty.call(obj,key) && keys.push(key);
3429 }
3430 return keys;
3431 };
3432
3433 var sorter = function (a, b) {
3434 return parseFloat (a) - parseFloat (b);
3435 };
3436
3437 var findPaths = function (map, start, end, infinity) {
3438 infinity = infinity || Infinity;
3439
3440 var costs = {},
3441 open = {'0': [start]},
3442 predecessors = {},
3443 keys;
3444
3445 var addToOpen = function (cost, vertex) {
3446 var key = '' + cost;
3447 if (!open[key]) open[key] = [];
3448 open[key].push(vertex);
3449 };
3450
3451 costs[start] = 0;
3452
3453 while (open) {
3454 if(!(keys = extractKeys(open)).length) break;
3455
3456 keys.sort(sorter);
3457
3458 var key = keys[0],
3459 bucket = open[key],
3460 node = bucket.shift(),
3461 currentCost = parseFloat(key),
3462 adjacentNodes = map[node] || {};
3463
3464 if (!bucket.length) delete open[key];
3465
3466 for (var vertex in adjacentNodes) {
3467 if (Object.prototype.hasOwnProperty.call(adjacentNodes, vertex)) {
3468 var cost = adjacentNodes[vertex],
3469 totalCost = cost + currentCost,
3470 vertexCost = costs[vertex];
3471
3472 if ((vertexCost === undefined) || (vertexCost > totalCost)) {
3473 costs[vertex] = totalCost;
3474 addToOpen(totalCost, vertex);
3475 predecessors[vertex] = node;
3476 }
3477 }
3478 }
3479 }
3480
3481 if (costs[end] === undefined) {
3482 return null;
3483 } else {
3484 return predecessors;
3485 }
3486
3487 };
3488
3489 var extractShortest = function (predecessors, end) {
3490 var nodes = [],
3491 u = end;
3492
3493 while (u) {
3494 nodes.push(u);
3495 u = predecessors[u];
3496 }
3497
3498 nodes.reverse();
3499 return nodes;
3500 };
3501
3502 var findShortestPath = function (map, nodes) {
3503 var start = nodes.shift(),
3504 end,
3505 predecessors,
3506 path = [],
3507 shortest;
3508
3509 while (nodes.length) {
3510 end = nodes.shift();
3511 predecessors = findPaths(map, start, end);
3512
3513 if (predecessors) {
3514 shortest = extractShortest(predecessors, end);
3515 if (nodes.length) {
3516 path.push.apply(path, shortest.slice(0, -1));
3517 } else {
3518 return path.concat(shortest);
3519 }
3520 } else {
3521 return null;
3522 }
3523
3524 start = end;
3525 }
3526 };
3527
3528 var toArray = function (list, offset) {
3529 try {
3530 return Array.prototype.slice.call(list, offset);
3531 } catch (e) {
3532 var a = [];
3533 for (var i = offset || 0, l = list.length; i < l; ++i) {
3534 a.push(list[i]);
3535 }
3536 return a;
3537 }
3538 };
3539
3540 var Graph = function (map) {
3541 this.map = map;
3542 };
3543
3544 Graph.prototype.findShortestPath = function (start, end) {
3545 if (Object.prototype.toString.call(start) === '[object Array]') {
3546 return findShortestPath(this.map, start);
3547 } else if (arguments.length === 2) {
3548 return findShortestPath(this.map, [start, end]);
3549 } else {
3550 return findShortestPath(this.map, toArray(arguments));
3551 }
3552 };
3553
3554 Graph.findShortestPath = function (map, start, end) {
3555 if (Object.prototype.toString.call(start) === '[object Array]') {
3556 return findShortestPath(map, start);
3557 } else if (arguments.length === 3) {
3558 return findShortestPath(map, [start, end]);
3559 } else {
3560 return findShortestPath(map, toArray(arguments, 1));
3561 }
3562 };
3563
3564 return Graph;
3565
3566})();
3567
3568
3569/*****************************************************************************\
3570| Jaeger's Combat Stats Watcher |
3571\*****************************************************************************/
3572var JaegerWM;
3573(function(JaegerWM) {
3574 var CombatStatsWatcher = (function() {
3575 function CombatStatsWatcher() {
3576 var _this = this;
3577 this.swingCount = 0;
3578 this.hitCount = 0;
3579 this.hitDamage = 0;
3580 this.bsCount = 0;
3581 this.bsDamage = 0;
3582 this.bsAttempt = 0;
3583 this.critCount = 0;
3584 this.critDamage = 0;
3585 this.missCount = 0;
3586 this.swingsAtYouCount = 0;
3587 this.dodgeCount = 0;
3588 this.glanceCount = 0;
3589 this.roundCount = 0;
3590 this.roundStart = Date.now();
3591 this.inRound = false;
3592 this.wmCombatSwing = combatSwing;
3593 combatSwing = function(actionData) {
3594 _this.combatSwingOverride(actionData);
3595 };
3596 $('#divExpTimer table tr:last').before('<tr>' +
3597 '<td>Round Damage (SPR):</td>' +
3598 '<td><span id="roundStats">0 (0)</span></td>' +
3599 '</tr>');
3600 $('#divExpTimer table tr:last').before('<tr>' +
3601 '<td>Hit Damage (%):</td>' +
3602 '<td><span id="hitStats">0 (0.00 %)</span></td>' +
3603 '</tr>');
3604 $('#divExpTimer table tr:last').before('<tr>' +
3605 '<td>BS Damage (%):</td>' +
3606 '<td><span id="bsStats">0 (0.00 %)</span></td>' +
3607 '</tr>');
3608 $('#divExpTimer table tr:last').before('<tr>' +
3609 '<td>Crit Damage (%):</td>' +
3610 '<td><span id="critStats">0 (0.00 %)</span></td>' +
3611 '</tr>');
3612 $('#divExpTimer table tr:last').before('<tr>' +
3613 '<td>Miss Percentage:</td>' +
3614 '<td><span id="missStats">0.00 %</span></td>' +
3615 '</tr>');
3616 $('#divExpTimer table tr:last').before('<tr>' +
3617 '<td>Dodge Percentage:</td>' +
3618 '<td><span id="dodgeStats">0.00 %</span></td>' +
3619 '</tr>');
3620 $('#divExpTimer table tr:last').before('<tr>' +
3621 '<td>Glance Percentage:</td>' +
3622 '<td><span id="glanceStats">0.00 %</span></td>' +
3623 '</tr>');
3624 $('#btnResetExpTimer').click(function() {
3625 _this.resetCombatStats();
3626 });
3627 setInterval(function() {
3628 _this.setStatsUI();
3629 }, 2000);
3630 }
3631 Object.defineProperty(CombatStatsWatcher.prototype, "roundDamageAvg", {
3632 get: function() {
3633 if (this.roundCount === 0)
3634 return "0";
3635 return Math.round((this.hitDamage + this.critDamage) / this.roundCount).toString();
3636 },
3637 enumerable: true,
3638 configurable: true
3639 });
3640 Object.defineProperty(CombatStatsWatcher.prototype, "swingsPerRound", {
3641 get: function() {
3642 if (this.roundCount === 0)
3643 return "0";
3644 return Math.round(this.swingCount / this.roundCount).toString();
3645 },
3646 enumerable: true,
3647 configurable: true
3648 });
3649 Object.defineProperty(CombatStatsWatcher.prototype, "hitPercent", {
3650 get: function() {
3651 return this.getPercent(this.hitCount, this.swingCount);
3652 },
3653 enumerable: true,
3654 configurable: true
3655 });
3656 Object.defineProperty(CombatStatsWatcher.prototype, "hitDamageAvg", {
3657 get: function() {
3658 if (this.hitCount === 0)
3659 return "0";
3660 return Math.round(this.hitDamage / this.hitCount).toString();
3661 },
3662 enumerable: true,
3663 configurable: true
3664 });
3665 Object.defineProperty(CombatStatsWatcher.prototype, "bsPercent", {
3666 get: function() {
3667 return this.getPercent(this.bsCount, this.bsAttempt);
3668 },
3669 enumerable: true,
3670 configurable: true
3671 });
3672 Object.defineProperty(CombatStatsWatcher.prototype, "bsDamageAvg", {
3673 get: function() {
3674 if (this.bsCount === 0)
3675 return "0";
3676 return Math.round(this.bsDamage / this.bsCount).toString();
3677 },
3678 enumerable: true,
3679 configurable: true
3680 });
3681 Object.defineProperty(CombatStatsWatcher.prototype, "critPercent", {
3682 get: function() {
3683 return this.getPercent(this.critCount, this.swingCount);
3684 },
3685 enumerable: true,
3686 configurable: true
3687 });
3688 Object.defineProperty(CombatStatsWatcher.prototype, "critDamageAvg", {
3689 get: function() {
3690 if (this.critCount === 0)
3691 return "0";
3692 return Math.round(this.critDamage / this.critCount).toString();
3693 },
3694 enumerable: true,
3695 configurable: true
3696 });
3697 Object.defineProperty(CombatStatsWatcher.prototype, "missPercent", {
3698 get: function() {
3699 return this.getPercent(this.missCount, this.swingCount);
3700 },
3701 enumerable: true,
3702 configurable: true
3703 });
3704 Object.defineProperty(CombatStatsWatcher.prototype, "dodgePercent", {
3705 get: function() {
3706 return this.getPercent(this.dodgeCount, this.swingsAtYouCount);
3707 },
3708 enumerable: true,
3709 configurable: true
3710 });
3711 Object.defineProperty(CombatStatsWatcher.prototype, "glancePercent", {
3712 get: function() {
3713 return this.getPercent(this.glanceCount, this.swingsAtYouCount);
3714 },
3715 enumerable: true,
3716 configurable: true
3717 });
3718 CombatStatsWatcher.prototype.setStatsUI = function() {
3719 $('#roundStats').text(this.roundDamageAvg + " (" + this.swingsPerRound + " SPR)");
3720 $('#hitStats').text(this.hitDamageAvg + " (" + this.hitPercent + " %)");
3721 $('#bsStats').text(this.bsDamageAvg + " (" + this.bsPercent + " %)");
3722 $('#critStats').text(this.critDamageAvg + " (" + this.critPercent + " %)");
3723 $('#missStats').text(this.missPercent + " %");
3724 $('#dodgeStats').text(this.dodgePercent + " %");
3725 $('#glanceStats').text(this.glancePercent + " %");
3726 };
3727 CombatStatsWatcher.prototype.resetCombatStats = function() {
3728 this.swingCount = 0;
3729 this.hitCount = 0;
3730 this.hitDamage = 0;
3731 this.bsCount = 0;
3732 this.bsDamage = 0;
3733 this.bsAttempt = 0;
3734 this.critCount = 0;
3735 this.critDamage = 0;
3736 this.missCount = 0;
3737 this.swingsAtYouCount = 0;
3738 this.dodgeCount = 0;
3739 this.glanceCount = 0;
3740 this.roundCount = 0;
3741 };
3742 CombatStatsWatcher.prototype.combatSwingOverride = function(actionData) {
3743 this.wmCombatSwing(actionData);
3744 if (actionData.AttackerID === playerID) {
3745 var now = Date.now();
3746 if (!this.inRound || (now - this.roundStart) > 1500) {
3747 this.roundStart = now;
3748 this.inRound = true;
3749 this.roundCount++;
3750 }
3751 if (actionData.Surprise) {
3752 this.bsAttempt++;
3753 } else {
3754 this.swingCount++;
3755 }
3756 switch (actionData.SwingResult) {
3757 case 1:
3758 if (actionData.Surprise) {
3759 this.bsCount++;
3760 this.bsDamage += actionData.Damage;
3761 } else {
3762 this.hitCount++;
3763 this.hitDamage += actionData.Damage;
3764 }
3765 break;
3766 case 2:
3767 this.critCount++;
3768 this.critDamage += actionData.Damage;
3769 break;
3770 case 0:
3771 this.missCount++;
3772 break;
3773 }
3774 } else if (actionData.TargetID === playerID) {
3775 this.inRound = false;
3776 this.swingsAtYouCount++;
3777 switch (actionData.SwingResult) {
3778 case 3:
3779 this.dodgeCount++;
3780 break;
3781 case 4:
3782 this.glanceCount++;
3783 break;
3784 }
3785 } else {
3786 this.inRound = false;
3787 }
3788 };
3789 CombatStatsWatcher.prototype.getPercent = function(count, total) {
3790 return total === 0 ? "0.00" : ((count / total) * 100).toFixed(2);
3791 };
3792 return CombatStatsWatcher;
3793 }());
3794 JaegerWM.CombatStatsWatcher = CombatStatsWatcher;
3795})(JaegerWM || (JaegerWM = {}));
3796
3797let statsWatcher;
3798setTimeout(function() {
3799 statsWatcher = new JaegerWM.CombatStatsWatcher();
3800}, 2000);