From 68721df789cbe8d3fef79e9881093b561763ae69 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Tue, 24 Nov 2015 22:42:41 -0500 Subject: [PATCH 01/43] made right-clicking to interact easier, made secondary action key 'E', changed preferences dialog to say Preferences instead of settings --- lib/src/game/input.dart | 26 +++++----------------- web/files/html/windows/settingswindow.html | 6 ++--- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/lib/src/game/input.dart b/lib/src/game/input.dart index 186a0b9c..4e62e120 100644 --- a/lib/src/game/input.dart +++ b/lib/src/game/input.dart @@ -34,7 +34,7 @@ class InputManager { "JumpBindingPrimary": 32, "JumpBindingAlt": 32, "ActionBindingPrimary": 13, - "ActionBindingAlt": 13, + "ActionBindingAlt": 69, "MapBindingPrimary": 77, "MapBindingAlt":77, "CalendarBindingPrimary": 67, @@ -349,25 +349,11 @@ class InputManager { }); //listen for right-clicks on entities that we're close to - document.body.onContextMenu.listen((MouseEvent e) { - Element element = e.target as Element; - int groundY = 0, xOffset = 0, yOffset = 0; - if(element.attributes['ground_y'] != null) - groundY = int.parse(element.attributes['ground_y']); - else { - xOffset = camera.getX(); - yOffset = camera.getY(); - } - num x = e.offset.x + xOffset; - num y = e.offset.y - groundY + yOffset; - List ids = []; - CurrentPlayer.intersectingObjects.forEach((String id, Rectangle rect) { - if(x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) - ids.add(id); - }); - - if(ids.length > 0) - doObjectInteraction(e, ids); + document.body.onContextMenu.listen((MouseEvent e) async { + //just like pressing a key for 10ms + doObjectInteraction(); + await new Timer(new Duration(milliseconds:10),(){}); + activateControl('actionKey', false, 'mouse'); }); //only for mobile version diff --git a/web/files/html/windows/settingswindow.html b/web/files/html/windows/settingswindow.html index 2bfc449c..b5fb55c7 100644 --- a/web/files/html/windows/settingswindow.html +++ b/web/files/html/windows/settingswindow.html @@ -48,8 +48,8 @@
Action - enger - enter + enter + E
@@ -65,7 +65,7 @@
- Open Settings + Open Preferences P P
From a7c9661b571aea51c244f1994de17ad635385434 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Wed, 25 Nov 2015 16:42:33 -0500 Subject: [PATCH 02/43] fix right-click item regression --- lib/src/network/server_interop/so_item.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index a2b34d49..d7fa0253 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -3,6 +3,9 @@ part of couclient; ///slot should be in the format 'barSlot.bagSlot' indexed from 0 ///if bagSlot is not relevant, please use 'barSlot.-1' itemContextMenu(ItemDef i, String slot, MouseEvent event) { + event.preventDefault(); + event.stopPropagation(); + int barSlot = int.parse(slot.split('.').elementAt(0)); int bagSlot = int.parse(slot.split('.').elementAt(1)); List actions = []; From c5c0c3144abf43ec8d5091b60293d4e01eac95c8 Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 28 Nov 2015 14:17:44 -0600 Subject: [PATCH 03/43] minor code tweaks + comments --- lib/src/game/game.dart | 12 +++++++++--- web/index.html | 4 +++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/src/game/game.dart b/lib/src/game/game.dart index b76a4232..8c35726c 100644 --- a/lib/src/game/game.dart +++ b/lib/src/game/game.dart @@ -91,7 +91,12 @@ class Game { transmit("gameLoaded", loaded); logmessage("Game loaded!"); + // Update player letters every second + // (Don't worry, it checks to make sure the current street has letters + // before sending the request to the server, and adjusts accordingly) new Timer.periodic(new Duration(seconds: 1), (_) => updatePlayerLetters()); + + // Tell the server when we have changed streets, and to assign us a new letter new Service(["streetLoaded", "gameUnloading"], (_) { if (currentStreet.useLetters) { HttpRequest.getString("http://${Configs.utilServerAddress}/letters/newPlayerLetter?username=${game.username}"); @@ -148,9 +153,10 @@ class Game { } Future updatePlayerLetters() async { - Map players = {}; - players.addAll(otherPlayers); - players.addAll(({game.username: CurrentPlayer})); + Map players = new Map() + ..addAll(otherPlayers) + ..addAll(({game.username: CurrentPlayer})); + print(players); players.forEach((String username, Player player) async { Element parentE = player.playerParentElement; diff --git a/web/index.html b/web/index.html index ced005b4..8de85fe2 100644 --- a/web/index.html +++ b/web/index.html @@ -60,14 +60,16 @@

The game page is loading...

If you see this message for more than 10 seconds after your browser's loading indicator has stopped, your browser will probably not support the game.

-

Browsers not currently supported:

+

Browsers not currently supported:

  • Internet Explorer
  • Microsoft Edge
  • Apple Safari
  • Any browser with JavaScript disabled
+

Please see our compatibility page for more information.

+

You can also check the status of our services.

Thanks and we hope you can play soon,
 — Children of Ur Dev Team

From 348c1fa5cfddbbc457b66e5ea52a715d99d976a2 Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 28 Nov 2015 14:18:17 -0600 Subject: [PATCH 04/43] remove print statement --- lib/src/game/game.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/game/game.dart b/lib/src/game/game.dart index 8c35726c..0f7fb456 100644 --- a/lib/src/game/game.dart +++ b/lib/src/game/game.dart @@ -156,7 +156,6 @@ class Game { Map players = new Map() ..addAll(otherPlayers) ..addAll(({game.username: CurrentPlayer})); - print(players); players.forEach((String username, Player player) async { Element parentE = player.playerParentElement; From 84b79d74592647f16ef535c603dbb5175455cb4d Mon Sep 17 00:00:00 2001 From: Andy Date: Sun, 29 Nov 2015 14:31:38 -0600 Subject: [PATCH 05/43] Fix typo That's been there for a while, I wonder what this will magically fix/break. --- lib/src/display/overlays/overlay.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/display/overlays/overlay.dart b/lib/src/display/overlays/overlay.dart index da11cd17..ddb9ffd4 100644 --- a/lib/src/display/overlays/overlay.dart +++ b/lib/src/display/overlays/overlay.dart @@ -28,7 +28,7 @@ class Overlay extends InformationDisplay { displayElement.hidden = true; elementOpen = false; inputManager.ignoreKeys = false; - transmit("wordFocus", true); + transmit("worldFocus", true); } } @@ -36,4 +36,4 @@ void setUpOverlays() { newDay = new NewDayOverlay("newday"); imgMenu = new ImgOverlay("pauseMenu"); levelUp = new LevelUpOverlay("levelup"); -} \ No newline at end of file +} From a0b291280bc3f97f5056f06fac9e0fdd58f4ca74 Mon Sep 17 00:00:00 2001 From: klikini Date: Sun, 29 Nov 2015 14:55:32 -0600 Subject: [PATCH 06/43] notification of newly unlocked username color options --- lib/src/display/blog_notifier.dart | 4 ++-- lib/src/display/overlays/levelup.dart | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/src/display/blog_notifier.dart b/lib/src/display/blog_notifier.dart index df8b698c..1c8b84c6 100644 --- a/lib/src/display/blog_notifier.dart +++ b/lib/src/display/blog_notifier.dart @@ -3,7 +3,7 @@ part of couclient; class BlogNotifier { static const _LS_KEY = "cou_blog_post"; static const _RSS_URL = "http://childrenofur.com/feed/"; - static const _ICON_URL = "http://childrenofur.com/assets/icon_72.png"; + static const ICON_URL = "http://childrenofur.com/assets/icon_72.png"; static dynamic get _lastSaved { if (localStorage[_LS_KEY] != null) { @@ -21,7 +21,7 @@ class BlogNotifier { new Notification( "Children of Ur Blog", body: "Click here to read the new post: \n$title", - icon: _ICON_URL + icon: ICON_URL )..onClick.listen((_) { window.open(link, "_blank"); }); diff --git a/lib/src/display/overlays/levelup.dart b/lib/src/display/overlays/levelup.dart index 58d24a34..35e4b197 100644 --- a/lib/src/display/overlays/levelup.dart +++ b/lib/src/display/overlays/levelup.dart @@ -16,6 +16,21 @@ class LevelUpOverlay extends Overlay { transmit("worldFocus", false); }); } + + close() { + metabolics.level.then((int lvl) { + if (lvl % 10 == 0) { + new Notification( + "Level Up!", icon: BlogNotifier.ICON_URL, + body: "You've unlocked new username color options! Click here to visit your profile page, then log in to check them out." + ).onClick.listen((_) { + window.open("http://childrenofur.com/profile?username=${game.username}", "_blank"); + }); + } + }); + + super.close(); + } } LevelUpOverlay levelUp; \ No newline at end of file From f279b1431bf2fc2b48c9fc515dfc99a2e6d8268b Mon Sep 17 00:00:00 2001 From: klikini Date: Tue, 1 Dec 2015 21:41:55 -0600 Subject: [PATCH 07/43] "Keyboard Shortcuts for actions" #303 --- lib/src/display/ui_templates/menu_keys.dart | 120 ++++ .../ui_templates/right_click_menu.dart | 598 +++++++++--------- web/main.dart | 1 + 3 files changed, 422 insertions(+), 297 deletions(-) create mode 100644 lib/src/display/ui_templates/menu_keys.dart diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart new file mode 100644 index 00000000..1e58502a --- /dev/null +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -0,0 +1,120 @@ +part of couclient; + +class MenuKeys { + // Maps key to valid keycodes + // Use int for single binding, and List for multiple (eg. numpad and number row) + static final Map KEY_CODES = { + "A": 65, + "B": 66, + "C": 67, + "D": 68, + "E": 69, + "F": 70, + "G": 71, + "H": 72, + "I": 73, + "J": 74, + "K": 75, + "L": 76, + "M": 77, + "N": 78, + "O": 79, + "P": 80, + "Q": 81, + "R": 82, + "S": 83, + "T": 84, + "U": 85, + "V": 86, + "W": 87, + "X": 88, + "Y": 89, + "Z": 90, + "1": [ + 49, + 97 + ], + "2": [ + 50, + 98 + ], + "3": [ + 51, + 99 + ], + "4": [ + 52, + 100 + ], + "5": [ + 53, + 101 + ], + "6": [ + 54, + 102 + ], + "7": [ + 55, + 103 + ], + "8": [ + 56, + 104 + ], + "9": [ + 57, + 105 + ], + "0": [ + 48, + 97 + ] + }; + + // Returns if the keycode provided will trigger the given key + static bool keyCodeMatches(int keyCode, dynamic key) { + key = key.toString().toUpperCase(); + + if (KEY_CODES[key] is int) { + // Keycode matches? (1 key :: 1 keycode) + return KEY_CODES[key] == keyCode; + } else if (KEY_CODES[key] is List) { + // Keycode matches? (1 key :: multiple keycodes) + return (KEY_CODES[key] as List).where((int code) => code == keyCode).toList().length > 0; + } else { + return false; + } + } + + // Resets the keyboard event listener list + static void clearListeners() { + // Cancel all listeners to prevent future duplication + _listeners.forEach((StreamSubscription listener) { + listener.cancel(); + }); + + // Remove all items to allow for garbage collection + _listeners.clear(); + } + + // List of keyboard listeners (one for each menu item) + static List _listeners = []; + + // Start listening for a menu item + static void addListener(int index, Function callback) { + _listeners.add(document.onKeyDown.listen((KeyboardEvent e) { + // Probably not absolutely needed, but good practice + e + ..stopPropagation() + ..preventDefault(); + + if (keyCodeMatches(e.keyCode, index)) { + // Stop listening for the keys after the menu is gone + clearListeners(); + // Run the callback + Function.apply(callback, []); + } + })); + } +} diff --git a/lib/src/display/ui_templates/right_click_menu.dart b/lib/src/display/ui_templates/right_click_menu.dart index 1f4e6b1e..91f48900 100644 --- a/lib/src/display/ui_templates/right_click_menu.dart +++ b/lib/src/display/ui_templates/right_click_menu.dart @@ -1,9 +1,8 @@ part of couclient; class RightClickMenu { - - static Element create2(MouseEvent click, String title, List options, {String description: '', String itemName: ''}) { - /** + static Element create2(MouseEvent click, String title, List options, {String description: '', String itemName: ''}) { + /** * title: main text shown at the top * * description: smaller text, shown under title @@ -26,304 +25,309 @@ class RightClickMenu { * itemName: string of the item selected, will show the (i) button if not null and will open the item info window when the (i) is clicked */ - // allow only one open at a time + // allow only one open at a time - destroy(); + destroy(); - // define parts + // define parts - DivElement menu, infoButton, actionList; - SpanElement titleElement; + DivElement menu, infoButton, actionList; + SpanElement titleElement; - // menu base + // menu base - menu = new DivElement() - ..id = "RightClickMenu"; + menu = new DivElement()..id = "RightClickMenu"; - // main title + // main title - titleElement = new SpanElement() - ..id = "ClickTitle" - ..text = title; - - menu.append(titleElement); - - // show item info window - - if (itemName != '') { - infoButton = new DivElement() - ..id = "openItemWindow" - ..className = "InfoButton fa fa-info-circle" - ..setAttribute('item-name', itemName) - ..onClick.listen((_) { - new ItemWindow(itemName).displayItem(); - }); - menu.append(infoButton); - } - - // actions - - actionList = new DivElement() - ..id = "RCActionList"; - - menu.append(actionList); - - // position - - int x, y; - - // options - - List newOptions = new List(); - for(Map option in options) { - DivElement wrapper = new DivElement() - ..className = "action_wrapper"; - DivElement tooltip = new DivElement() - ..className = "action_error_tooltip"; - DivElement menuitem = new DivElement(); - menuitem - ..classes.add("RCItem") - ..text = option["name"]; - - if(option["enabled"]) { - menuitem.onClick.listen((_) async { - int timeRequired = option["timeRequired"]; - - bool completed = true; - if(timeRequired > 0) { - ActionBubble actionBubble = new ActionBubble(option['name'], timeRequired); - completed = await actionBubble.wait; - } - - if(completed) { - Map arguments = null; - if(option["arguments"] != null) { - arguments = option["arguments"]; - } - - if(option.containsKey('serverCallback')) { - sendAction(option["serverCallback"].toLowerCase(), option["entityId"], arguments); - } - if(option.containsKey('clientCallback')) { - option['clientCallback'](); - } - } - }); - - menuitem.onMouseOver.listen((e) { - e.target.classes.add("RCItemSelected"); - }); - - menuitem.onMouseOut.listen((e) { - e.target.classes.remove("RCItemSelected"); - }); - - document.onKeyUp.listen((KeyboardEvent k) { - if(k.keyCode == 27) { - destroy(); - } - }); - } else { - menuitem.classes.add('RCItemDisabled'); - } - - if (option["description"] != null) { - showActionError(tooltip, option["description"]); - } - - wrapper.append(menuitem); - wrapper.append(tooltip); - newOptions.add(wrapper); - } - - // keyboard navigation - - if (!newOptions[0].children[0].classes.contains("RCItemDisabled")) { - if(newOptions.length > 1) { - menu.onKeyPress.listen((e) { - if (e.keyCode == 40) { // down arrow - newOptions[0].children[0].classes.toggle("RCItemSelected"); - } - if (e.keyCode == 38) { // up arrow - newOptions[0].children[newOptions.length].classes.toggle("RCItemSelected"); - } - }); - } else if (newOptions.length == 1) { - newOptions[0].children[0].classes.toggle("RCItemSelected"); - } - } - - document.body.append(menu); - if(click != null) { - x = click.page.x - (menu.clientWidth ~/ 2); - y = click.page.y - (40 + (options.length * 30)); - } else { - num posX = CurrentPlayer.posX, posY = CurrentPlayer.posY; - int width = CurrentPlayer.width, height = CurrentPlayer.height; - num translateX = posX, translateY = view.worldElement.clientHeight - height; - if(posX > currentStreet.bounds.width - width / 2 - view.worldElement.clientWidth / 2) { - translateX = posX - currentStreet.bounds.width + view.worldElement.clientWidth; - } else if(posX + width / 2 > view.worldElement.clientWidth / 2) { - translateX = view.worldElement.clientWidth / 2 - width / 2; - } - if(posY + height / 2 < view.worldElement.clientHeight / 2) { - translateY = posY; - } else if(posY < currentStreet.bounds.height - height / 2 - view.worldElement.clientHeight / 2) { - translateY = view.worldElement.clientHeight / 2 - height / 2; - } else { - translateY = view.worldElement.clientHeight - (currentStreet.bounds.height - posY); - } - x = (translateX + menu.clientWidth + 10) ~/ 1; - y = (translateY + height / 2) ~/ 1; - } - - actionList.children.addAll(newOptions); - menu.style - ..opacity = '1.0' - ..transform = 'translateX(' + x.toString() + 'px) translateY(' + y.toString() + 'px)'; - - document.onClick.first.then((_) => destroy()); - return menu; - } - - static Element create(MouseEvent Click, String title, String description, List options, {String itemName: ''}) { - destroy(); - DivElement menu = new DivElement() - ..id = "RightClickMenu"; - DivElement infoButton = new DivElement() - ..id = "openItemWindow" - ..className = "InfoButton fa fa-info-circle" - ..onClick.listen((_) => new ItemWindow(itemName).displayItem()); - SpanElement titleElement = new SpanElement() - ..id = "ClickTitle" - ..text = title; - DivElement actionList = new DivElement() - ..id = "RCActionList"; - - if(itemName != '') { - infoButton.setAttribute('item-name', itemName); - } - - if(itemName != '') { - menu.append(infoButton); - } - menu.append(titleElement); - menu.append(actionList); - - int x, y; - - List newOptions = new List(); - for(List option in options) { - DivElement wrapper = new DivElement() - ..className = 'action_wrapper'; - DivElement tooltip = new DivElement() - ..className = 'action_error_tooltip'; - DivElement menuitem = new DivElement(); - menuitem - ..classes.add('RCItem') - ..text = (option[0] as String).split("|")[0]; - - if((option[0] as String).split("|")[3] == "true") { - menuitem.onClick.listen((_) async { - int timeRequired = int.parse((option[0] as String).split("|")[2]); - - bool completed = true; - if(timeRequired > 0) { - ActionBubble actionBubble = new ActionBubble((option[0] as String).split("|")[1], timeRequired); - completed = await actionBubble.wait; - } - - if(completed) { - // Action completed - Map arguments = null; - if(option.length > 3) { - arguments = option[3]; - } - sendAction((option[0] as String).split("|")[0].toLowerCase(), option[1], arguments); - } - }); - - menuitem.onMouseOver.listen((e) { - e.target.classes.add("RCItemSelected"); - }); - - menuitem.onMouseOut.listen((e) { - e.target.classes.remove("RCItemSelected"); - }); - - document.onKeyUp.listen((KeyboardEvent k) { - if(k.keyCode == 27) { - destroy(); - } - }); - } else { - menuitem.classes.add('RCItemDisabled'); - } - - showActionError(tooltip, (option[0] as String).split("|")[4]); - - wrapper.append(menuitem); - wrapper.append(tooltip); - newOptions.add(wrapper); - } - if (newOptions.length > 0 && - !newOptions[0].children[0].classes.contains("RCItemDisabled")) { - if(newOptions.length > 1) { - menu.onKeyPress.listen((e) { - if (e.keyCode == 40) { // down arrow - newOptions[0].children[0].classes.toggle("RCItemSelected"); - } - if (e.keyCode == 38) { // up arrow - newOptions[0].children[newOptions.length].classes.toggle("RCItemSelected"); - } - }); - } else if (newOptions.length == 1) { - newOptions[0].children[0].classes.toggle("RCItemSelected"); - } - } - - document.body.append(menu); - if(Click != null) { - x = Click.page.x - (menu.clientWidth ~/ 2); - y = Click.page.y - (40 + (options.length * 30)); - } else { - num posX = CurrentPlayer.posX, posY = CurrentPlayer.posY; - int width = CurrentPlayer.width, height = CurrentPlayer.height; - num translateX = posX, translateY = view.worldElement.clientHeight - height; - if(posX > currentStreet.bounds.width - width / 2 - view.worldElement.clientWidth / 2) { - translateX = posX - currentStreet.bounds.width + view.worldElement.clientWidth; - } else if(posX + width / 2 > view.worldElement.clientWidth / 2) { - translateX = view.worldElement.clientWidth / 2 - width / 2; - } - if(posY + height / 2 < view.worldElement.clientHeight / 2) { - translateY = posY; - } else if(posY < currentStreet.bounds.height - height / 2 - view.worldElement.clientHeight / 2) { - translateY = view.worldElement.clientHeight / 2 - height / 2; - } else { - translateY = view.worldElement.clientHeight - (currentStreet.bounds.height - posY); - } - x = (translateX + menu.clientWidth + 10) ~/ 1; - y = (translateY + height / 2) ~/ 1; - } - - actionList.children.addAll(newOptions); - menu.style - ..opacity = '1.0' - ..transform = 'translateX(' + x.toString() + 'px) translateY(' + y.toString() + 'px)'; - - document.onClick.first.then((_) => destroy()); - return menu; - } - - static void showActionError(Element tooltip, String errorText) { - tooltip.hidden = errorText == ''; - tooltip.text = errorText; - } - - static void destroy() { - Element menu = querySelector("#RightClickMenu"); - if(menu != null) { - menu.remove(); - } - } -} \ No newline at end of file + titleElement = new SpanElement() + ..id = "ClickTitle" + ..text = title; + + menu.append(titleElement); + + // show item info window + + if (itemName != '') { + infoButton = new DivElement() + ..id = "openItemWindow" + ..className = "InfoButton fa fa-info-circle" + ..setAttribute('item-name', itemName) + ..onClick.listen((_) { + new ItemWindow(itemName).displayItem(); + }); + menu.append(infoButton); + } + + // actions + + actionList = new DivElement()..id = "RCActionList"; + + menu.append(actionList); + + // position + + int x, y; + + // options + + List newOptions = new List(); + for (Map option in options) { + DivElement wrapper = new DivElement()..className = "action_wrapper"; + DivElement tooltip = new DivElement()..className = "action_error_tooltip"; + DivElement menuitem = new DivElement(); + menuitem + ..classes.add("RCItem") + ..text = option["name"]; + + if (option["enabled"]) { + menuitem.onClick.listen((_) async { + int timeRequired = option["timeRequired"]; + + bool completed = true; + if (timeRequired > 0) { + ActionBubble actionBubble = new ActionBubble(option['name'], timeRequired); + completed = await actionBubble.wait; + } + + if (completed) { + Map arguments = null; + if (option["arguments"] != null) { + arguments = option["arguments"]; + } + + if (option.containsKey('serverCallback')) { + sendAction(option["serverCallback"].toLowerCase(), option["entityId"], arguments); + } + if (option.containsKey('clientCallback')) { + option['clientCallback'](); + } + } + }); + + menuitem.onMouseOver.listen((e) { + e.target.classes.add("RCItemSelected"); + }); + + menuitem.onMouseOut.listen((e) { + e.target.classes.remove("RCItemSelected"); + }); + + document.onKeyUp.listen((KeyboardEvent k) { + if (k.keyCode == 27) { + destroy(); + } + }); + } else { + menuitem.classes.add('RCItemDisabled'); + } + + if (option["description"] != null) { + showActionError(tooltip, option["description"]); + } + + wrapper.append(menuitem); + wrapper.append(tooltip); + newOptions.add(wrapper); + } + + // keyboard navigation + + if (!newOptions[0].children[0].classes.contains("RCItemDisabled")) { + if (newOptions.length > 1) { + menu.onKeyPress.listen((e) { + if (e.keyCode == 40) { + // down arrow + newOptions[0].children[0].classes.toggle("RCItemSelected"); + } + if (e.keyCode == 38) { + // up arrow + newOptions[0].children[newOptions.length].classes.toggle("RCItemSelected"); + } + }); + } else if (newOptions.length == 1) { + newOptions[0].children[0].classes.toggle("RCItemSelected"); + } + } + + document.body.append(menu); + if (click != null) { + x = click.page.x - (menu.clientWidth ~/ 2); + y = click.page.y - (40 + (options.length * 30)); + } else { + num posX = CurrentPlayer.posX, posY = CurrentPlayer.posY; + int width = CurrentPlayer.width, height = CurrentPlayer.height; + num translateX = posX, translateY = view.worldElement.clientHeight - height; + if (posX > currentStreet.bounds.width - width / 2 - view.worldElement.clientWidth / 2) { + translateX = posX - currentStreet.bounds.width + view.worldElement.clientWidth; + } else if (posX + width / 2 > view.worldElement.clientWidth / 2) { + translateX = view.worldElement.clientWidth / 2 - width / 2; + } + if (posY + height / 2 < view.worldElement.clientHeight / 2) { + translateY = posY; + } else if (posY < currentStreet.bounds.height - height / 2 - view.worldElement.clientHeight / 2) { + translateY = view.worldElement.clientHeight / 2 - height / 2; + } else { + translateY = view.worldElement.clientHeight - (currentStreet.bounds.height - posY); + } + x = (translateX + menu.clientWidth + 10) ~/ 1; + y = (translateY + height / 2) ~/ 1; + } + + actionList.children.addAll(newOptions); + menu.style + ..opacity = '1.0' + ..transform = 'translateX(' + x.toString() + 'px) translateY(' + y.toString() + 'px)'; + + document.onClick.first.then((_) => destroy()); + return menu; + } + + static Element create(MouseEvent Click, String title, String description, List options, {String itemName: ''}) { + destroy(); + DivElement menu = new DivElement()..id = "RightClickMenu"; + DivElement infoButton = new DivElement() + ..id = "openItemWindow" + ..className = "InfoButton fa fa-info-circle" + ..onClick.listen((_) => new ItemWindow(itemName).displayItem()); + SpanElement titleElement = new SpanElement() + ..id = "ClickTitle" + ..text = title; + DivElement actionList = new DivElement()..id = "RCActionList"; + + if (itemName != '') { + infoButton.setAttribute('item-name', itemName); + } + + if (itemName != '') { + menu.append(infoButton); + } + menu.append(titleElement); + menu.append(actionList); + + int x, y; + + List newOptions = new List(); + int index = 1; + for (List option in options) { + DivElement wrapper = new DivElement()..className = 'action_wrapper'; + DivElement tooltip = new DivElement()..className = 'action_error_tooltip'; + DivElement menuitem = new DivElement(); + String actionText = (option[0] as String).split("|")[0]; + menuitem + ..classes.add('RCItem') + ..text = "${index.toString()}: $actionText"; + + MenuKeys.addListener(index, () { + // Trigger onClick listener (below) when correct key is pressed + menuitem.click(); + }); + + if ((option[0] as String).split("|")[3] == "true") { + menuitem.onClick.listen((_) async { + int timeRequired = int.parse((option[0] as String).split("|")[2]); + + bool completed = true; + if (timeRequired > 0) { + ActionBubble actionBubble = new ActionBubble((option[0] as String).split("|")[1], timeRequired); + completed = await actionBubble.wait; + } + + if (completed) { + // Action completed + Map arguments = null; + if (option.length > 3) { + arguments = option[3]; + } + sendAction((option[0] as String).split("|")[0].toLowerCase(), option[1], arguments); + } + }); + + menuitem.onMouseOver.listen((e) { + e.target.classes.add("RCItemSelected"); + }); + + menuitem.onMouseOut.listen((e) { + e.target.classes.remove("RCItemSelected"); + }); + + document.onKeyUp.listen((KeyboardEvent k) { + if (k.keyCode == 27) { + destroy(); + } + }); + } else { + menuitem.classes.add('RCItemDisabled'); + } + + showActionError(tooltip, (option[0] as String).split("|")[4]); + + wrapper.append(menuitem); + wrapper.append(tooltip); + newOptions.add(wrapper); + + index++; + } + if (newOptions.length > 0 && !newOptions[0].children[0].classes.contains("RCItemDisabled")) { + if (newOptions.length > 1) { + menu.onKeyPress.listen((e) { + if (e.keyCode == 40) { + // down arrow + newOptions[0].children[0].classes.toggle("RCItemSelected"); + } + if (e.keyCode == 38) { + // up arrow + newOptions[0].children[newOptions.length].classes.toggle("RCItemSelected"); + } + }); + } else if (newOptions.length == 1) { + newOptions[0].children[0].classes.toggle("RCItemSelected"); + } + } + + document.body.append(menu); + if (Click != null) { + x = Click.page.x - (menu.clientWidth ~/ 2); + y = Click.page.y - (40 + (options.length * 30)); + } else { + num posX = CurrentPlayer.posX, posY = CurrentPlayer.posY; + int width = CurrentPlayer.width, height = CurrentPlayer.height; + num translateX = posX, translateY = view.worldElement.clientHeight - height; + if (posX > currentStreet.bounds.width - width / 2 - view.worldElement.clientWidth / 2) { + translateX = posX - currentStreet.bounds.width + view.worldElement.clientWidth; + } else if (posX + width / 2 > view.worldElement.clientWidth / 2) { + translateX = view.worldElement.clientWidth / 2 - width / 2; + } + if (posY + height / 2 < view.worldElement.clientHeight / 2) { + translateY = posY; + } else if (posY < currentStreet.bounds.height - height / 2 - view.worldElement.clientHeight / 2) { + translateY = view.worldElement.clientHeight / 2 - height / 2; + } else { + translateY = view.worldElement.clientHeight - (currentStreet.bounds.height - posY); + } + x = (translateX + menu.clientWidth + 10) ~/ 1; + y = (translateY + height / 2) ~/ 1; + } + + actionList.children.addAll(newOptions); + menu.style + ..opacity = '1.0' + ..transform = 'translateX(' + x.toString() + 'px) translateY(' + y.toString() + 'px)'; + + document.onClick.first.then((_) => destroy()); + return menu; + } + + static void showActionError(Element tooltip, String errorText) { + tooltip.hidden = errorText == ''; + tooltip.text = errorText; + } + + static void destroy() { + Element menu = querySelector("#RightClickMenu"); + if (menu != null) { + menu.remove(); + transmit("right_click_menu", "destroy"); + } + } +} diff --git a/web/main.dart b/web/main.dart index 8140f3f3..01ee0b97 100644 --- a/web/main.dart +++ b/web/main.dart @@ -145,6 +145,7 @@ part 'package:couclient/src/game/entities/street_spirit.dart'; part 'package:couclient/src/display/ui_templates/interactions_menu.dart'; part 'package:couclient/src/display/ui_templates/right_click_menu.dart'; part 'package:couclient/src/display/ui_templates/howmany.dart'; +part 'package:couclient/src/display/ui_templates/menu_keys.dart'; part 'package:couclient/src/display/minimap.dart'; // Globals // From 2ba8c46f9c8b9e04efad5a8c9089782f58c5056a Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 17:41:34 -0600 Subject: [PATCH 08/43] fix https://github.com/ChildrenOfUr/cou-issues/issues/230 --- lib/src/game/action_bubble.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/game/action_bubble.dart b/lib/src/game/action_bubble.dart index c4e6ac5a..444ed9a9 100644 --- a/lib/src/game/action_bubble.dart +++ b/lib/src/game/action_bubble.dart @@ -7,7 +7,8 @@ class ActionBubble { SpanElement fill = new SpanElement(); ActionBubble(String actionName, this.duration) { - String text = actionName; + // Only the first word, ignore anything after the first space + String text = actionName.split(" ")[0]; outline ..text = text @@ -66,4 +67,4 @@ class ActionBubble { }); return completer.future; } -} \ No newline at end of file +} From a963a8f8d23c2fb760b9b6555d5b2687e5c370ca Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 17:47:31 -0600 Subject: [PATCH 09/43] fix map right-click regression --- lib/src/display/render/worldmap.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/display/render/worldmap.dart b/lib/src/display/render/worldmap.dart index f061dbc2..5db2fa60 100644 --- a/lib/src/display/render/worldmap.dart +++ b/lib/src/display/render/worldmap.dart @@ -103,12 +103,17 @@ class WorldMap { ..style.transform = 'rotate(' + streetPlacement['deg'].toString() + 'rad)'; - street.onClick.listen((e) { - new Timer(new Duration(milliseconds: 100), () { + street + ..onClick.listen((e) { + new Timer(new Duration(milliseconds: 100), () { createStreetMenu(e, street); }); - }); - street.onContextMenu.listen((e) => createStreetMenu(e, street)); + }) + ..onContextMenu.listen((e) { + new Timer(new Duration(milliseconds: 100), () { + createStreetMenu(e, street); + }); + }); if (object['tsid'].substring(1) == currentStreet.streetData['tsid'].substring(1)) { // current street From a3babbee73d7ccb74470bd6d9734e67272049d4d Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 17:53:33 -0600 Subject: [PATCH 10/43] volume/mute hack --- lib/src/display/widgets/volumeslider.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/display/widgets/volumeslider.dart b/lib/src/display/widgets/volumeslider.dart index fdd4851f..a2b3070b 100644 --- a/lib/src/display/widgets/volumeslider.dart +++ b/lib/src/display/widgets/volumeslider.dart @@ -3,7 +3,7 @@ part of couclient; class VolumeSliderWidget { bool muted = false; - Element volumeGlyph = querySelector('#volumeGlyph'); + Element volumeGlyph = querySelector('#volumeGlyph')..click()..click(); // HACK: toggling fixes mute issues Element volumeIcon = querySelector('#volumeGlyph > i'); VolumeSliderWidget() @@ -54,4 +54,4 @@ class VolumeSliderWidget // Update the audio mute state audio.setMute(muted); } -} \ No newline at end of file +} From 84557a01f98aa667b0e7f48a2bd9ce10dedacafd Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 18:08:47 -0600 Subject: [PATCH 11/43] sync up text, don't update toasts and chat at the same exact time --- lib/src/display/chatpanel.dart | 1207 ++++++++++++++++---------------- 1 file changed, 610 insertions(+), 597 deletions(-) diff --git a/lib/src/display/chatpanel.dart b/lib/src/display/chatpanel.dart index 1ef4a331..e38325ae 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -2,621 +2,634 @@ part of couclient; // Chats class Chat { - String title, lastWord = ""; - bool online, focused = false, tabInserted = false; - Element conversationElement, trigger; - int unreadMessages = 0, tabSearchIndex = 0, numMessages = 0, inputHistoryPointer = 0, emoticonPointer = 0; - static Chat otherChat = null, localChat = null; - List connectedUsers = new List(), inputHistory = new List(); - static StreamSubscription itemWindowLinks, mapWindowLinks; - static InputElement lastFocusedInput; - - static final NodeValidatorBuilder validator = new NodeValidatorBuilder() - ..allowHtml5() - ..allowElement('span', attributes: ['style']) // Username colors, item icons - ..allowElement('a', attributes: ['href', 'title', 'target', 'class']) // Links - ..allowElement('i', attributes: ['class', 'title']) // Emoticons - ..allowElement('p', attributes: ['style']) - ..allowElement('b') - ..allowElement('del'); - - // /me text - - // Emoticons - - bool get archived { - return !conversationElement.classes.contains('conversation'); - } - - void focus() { - this.focused = true; - conversationElement.querySelector("input").focus(); - } - - void blur() { - this.focused = false; - } - - Chat(this.title) { - title = title.trim(); - - // find the link in the chat panel that opens the chat - if (title != "Local Chat") { - trigger = querySelectorAll("#rightSide *").where((Element e) => e.dataset["chat"] == title).first; - } - - //look for an 'archived' version of this chat - //otherwise create a new one - conversationElement = getArchivedConversation(title); - if (conversationElement == null) { - - // start a timer for the first global chat created that refreshes the sidebar player list - if (title == "Global Chat") { - refreshOnlinePlayers(); - new Timer.periodic(new Duration(seconds: 5), (_) => refreshOnlinePlayers()); - } - - // clone the template - conversationElement = view.chatTemplate.querySelector('.conversation').clone(true); - Map emoticonArgs = { - "title": title, - "input": conversationElement.querySelector("input") - }; - conversationElement - ..querySelector('.title').text = title - ..querySelector(".insertemoji").onClick.listen((_) => transmit('insertEmoji', emoticonArgs)) - ..id = "chat-$title"; - openConversations.insert(0, this); - - if (title == "Local Chat") { - new Service(["gameLoaded", "streetLoaded"], (_) { - // Streets loaded, display a divider - this.addMessage("invalid_user", "LocationChangeEvent"); - // If this is the first one, empty the toast buffer into the chat - if (chatToastBuffer.length > 0) { - chatToastBuffer.forEach((String message) => this.addAlert(message, toast: true)); - chatToastBuffer.clear(); - } - }); - } - - //handle chat input getting focused/unfocused so that the character doesn't move while typing - InputElement chatInput = conversationElement.querySelector('input'); - chatInput.onFocus.listen((_) { - inputManager.ignoreKeys = true; - //need to set the focused variable to true and false for all the others - openConversations.forEach((Chat c) => c.blur()); - focus(); - transmit("worldFocus", false); - lastFocusedInput = chatInput; - }); - chatInput.onBlur.listen((_) { - inputManager.ignoreKeys = false; - //we'll want to set the focused to false for this chat - blur(); - }); - } else { - // mark as read - trigger.classes.remove("unread"); - NetChatManager.updateTabUnread(); - } - - if (title != "Local Chat") { - if (localChat != null) { - view.panel.insertBefore(conversationElement, localChat.conversationElement); - } else { - view.panel.append(conversationElement); - } - - if (otherChat != null) { - otherChat.archiveConversation(); - focus(); - } - - otherChat = this; - } - //don't ever have 2 local chats - else if (localChat == null) { - localChat = this; - view.panel.append(conversationElement); - //can't remove the local chat - conversationElement.querySelector('.fa-chevron-down').remove(); - } - - computePanelSize(); - - Element minimize = conversationElement.querySelector('.fa-chevron-down'); - if (minimize != null) { - minimize.onClick.listen((_) => this.archiveConversation()); - } - - processInput(conversationElement.querySelector('input')); - } - - void processEvent(Map data) { - if (data["message"] == " joined.") { - if (!connectedUsers.contains(data["username"])) { - connectedUsers.add(data["username"]); - } - } - - if (data["message"] == " left.") { - connectedUsers.remove(data["username"]); - removeOtherPlayer(data["username"]); - } - - if (data["statusMessage"] == "changeName") { - if (data["success"] == "true") { - removeOtherPlayer(data["username"]); - - //although this message is broadcast to everyone, only change usernames - //if we were the one to type /setname - if (data["newUsername"] == game.username) { - CurrentPlayer.username = data['newUsername']; - CurrentPlayer.loadAnimations(); - - //clear our inventory so we can get the new one - view.inventory.querySelectorAll('.box').forEach((Element box) => box.children.clear()); - firstConnect = true; - joined = ""; - sendJoinedMessage(currentStreet.label); - - //warn multiplayer server that it will receive messages - //from a new name but it should be the same person - data['street'] = currentStreet.label; - playerSocket.send(JSON.encode(data)); - - timeLast = 5.0; - } - - connectedUsers.remove(data["username"]); - connectedUsers.add(data["newUsername"]); - } - } else { - addMessage(data['username'], data['message']); - if (archived) { - trigger.classes.add("unread"); - NetChatManager.updateTabUnread(); - } - } - } - - void addMessage(String player, String message) { - ChatMessage chat = new ChatMessage(player, message); - Element dialog = conversationElement.querySelector('.dialog'); - chat.toHtml().then((String html) { - // display in panel - dialog.appendHtml(html, validator: Chat.validator); - - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - - // check for item links - if (itemWindowLinks != null) { - itemWindowLinks.cancel(); - } - if (dialog.querySelector(".item-chat-link") != null) { - itemWindowLinks = dialog.querySelectorAll(".item-chat-link").onClick.listen((Event e) { - e.preventDefault(); - if (e.target is AnchorElement) { - new ItemWindow(((e.target) as Element).text); - } - if (e.target is SpanElement) { - new ItemWindow(((e.target) as Element).parent.text); - } - }); - } - - // check for location links - if (mapWindowLinks != null) { - mapWindowLinks.cancel(); - } - if (dialog.querySelector(".location-chat-link") != null) { - mapWindowLinks = dialog.querySelectorAll(".location-chat-link").onClick.listen((Event e) { - e.preventDefault(); - String text = ((e.target) as Element).text; - - if ((e.target as Element).classes.contains("hub-chat-link")) { - String hubId = mapData.getHubId(text); - new WorldMap(hubId).loadhubdiv(hubId, text); - } else if ((e.target as Element).classes.contains("street-chat-link")) { - String hubId = mapData.streetData[text]["hub_id"].toString(); - new WorldMap(hubId).hubMap(hub_id: hubId, highlightStreet: text); - } - - mapWindow.open(); - }); - } - }); - } + String title, lastWord = ""; + bool online, focused = false, tabInserted = false; + Element conversationElement, trigger; + int unreadMessages = 0, + tabSearchIndex = 0, + numMessages = 0, + inputHistoryPointer = 0, + emoticonPointer = 0; + static Chat otherChat = null, localChat = null; + List connectedUsers = new List(), inputHistory = new List(); + static StreamSubscription itemWindowLinks, mapWindowLinks; + static InputElement lastFocusedInput; + + static final NodeValidatorBuilder validator = new NodeValidatorBuilder() + ..allowHtml5() + ..allowElement('span', attributes: ['style']) // Username colors, item icons + ..allowElement('a', attributes: ['href', 'title', 'target', 'class']) // Links + ..allowElement('i', attributes: ['class', 'title']) // Emoticons + ..allowElement('p', attributes: ['style']) + ..allowElement('b') + ..allowElement('del'); + + // /me text + + // Emoticons + + bool get archived { + return !conversationElement.classes.contains('conversation'); + } + + void focus() { + this.focused = true; + conversationElement.querySelector("input").focus(); + } + + void blur() { + this.focused = false; + } + + Chat(this.title) { + title = title.trim(); + + // find the link in the chat panel that opens the chat + if (title != "Local Chat") { + trigger = + querySelectorAll("#rightSide *").where((Element e) => e.dataset["chat"] == title).first; + } + + //look for an 'archived' version of this chat + //otherwise create a new one + conversationElement = getArchivedConversation(title); + if (conversationElement == null) { + // start a timer for the first global chat created that refreshes the sidebar player list + if (title == "Global Chat") { + refreshOnlinePlayers(); + new Timer.periodic(new Duration(seconds: 5), (_) => refreshOnlinePlayers()); + } + + // clone the template + conversationElement = view.chatTemplate.querySelector('.conversation').clone(true); + Map emoticonArgs = { + "title": title, + "input": conversationElement.querySelector("input") + }; + conversationElement + ..querySelector('.title').text = title + ..querySelector(".insertemoji").onClick.listen((_) => transmit('insertEmoji', emoticonArgs)) + ..id = "chat-$title"; + openConversations.insert(0, this); + + if (title == "Local Chat") { + new Service(["gameLoaded", "streetLoaded"], (_) { + // Streets loaded, display a divider + this.addMessage("invalid_user", "LocationChangeEvent"); + // If this is the first one, empty the toast buffer into the chat + if (chatToastBuffer.length > 0) { + chatToastBuffer.forEach((String message) => this.addAlert(message, toast: true)); + chatToastBuffer.clear(); + } + }); + } + + //handle chat input getting focused/unfocused so that the character doesn't move while typing + InputElement chatInput = conversationElement.querySelector('input'); + chatInput.onFocus.listen((_) { + inputManager.ignoreKeys = true; + //need to set the focused variable to true and false for all the others + openConversations.forEach((Chat c) => c.blur()); + focus(); + transmit("worldFocus", false); + lastFocusedInput = chatInput; + }); + chatInput.onBlur.listen((_) { + inputManager.ignoreKeys = false; + //we'll want to set the focused to false for this chat + blur(); + }); + } else { + // mark as read + trigger.classes.remove("unread"); + NetChatManager.updateTabUnread(); + } + + if (title != "Local Chat") { + if (localChat != null) { + view.panel.insertBefore(conversationElement, localChat.conversationElement); + } else { + view.panel.append(conversationElement); + } + + if (otherChat != null) { + otherChat.archiveConversation(); + focus(); + } + + otherChat = this; + } + //don't ever have 2 local chats + else if (localChat == null) { + localChat = this; + view.panel.append(conversationElement); + //can't remove the local chat + conversationElement.querySelector('.fa-chevron-down').remove(); + } + + computePanelSize(); + + Element minimize = conversationElement.querySelector('.fa-chevron-down'); + if (minimize != null) { + minimize.onClick.listen((_) => this.archiveConversation()); + } + + processInput(conversationElement.querySelector('input')); + } + + void processEvent(Map data) { + if (data["message"] == " joined.") { + if (!connectedUsers.contains(data["username"])) { + connectedUsers.add(data["username"]); + } + } + + if (data["message"] == " left.") { + connectedUsers.remove(data["username"]); + removeOtherPlayer(data["username"]); + } + + if (data["statusMessage"] == "changeName") { + if (data["success"] == "true") { + removeOtherPlayer(data["username"]); + + //although this message is broadcast to everyone, only change usernames + //if we were the one to type /setname + if (data["newUsername"] == game.username) { + CurrentPlayer.username = data['newUsername']; + CurrentPlayer.loadAnimations(); + + //clear our inventory so we can get the new one + view.inventory.querySelectorAll('.box').forEach((Element box) => box.children.clear()); + firstConnect = true; + joined = ""; + sendJoinedMessage(currentStreet.label); + + //warn multiplayer server that it will receive messages + //from a new name but it should be the same person + data['street'] = currentStreet.label; + playerSocket.send(JSON.encode(data)); + + timeLast = 5.0; + } + + connectedUsers.remove(data["username"]); + connectedUsers.add(data["newUsername"]); + } + } else { + addMessage(data['username'], data['message']); + if (archived) { + trigger.classes.add("unread"); + NetChatManager.updateTabUnread(); + } + } + } + + void addMessage(String player, String message) { + ChatMessage chat = new ChatMessage(player, message); + Element dialog = conversationElement.querySelector('.dialog'); + chat.toHtml().then((String html) { + // display in panel + dialog.appendHtml(html, validator: Chat.validator); + + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + + // check for and activate any item links + if (itemWindowLinks != null) { + // Cancel any existing links to prevent duplicate listeners + itemWindowLinks.cancel(); + } + if (dialog.querySelector(".item-chat-link") != null) { + itemWindowLinks = dialog.querySelectorAll(".item-chat-link").onClick.listen((Event e) { + e.preventDefault(); + if (e.target is AnchorElement) { + new ItemWindow(((e.target) as Element).text); + } + if (e.target is SpanElement) { + new ItemWindow(((e.target) as Element).parent.text); + } + }); + } + + // check for and activate any location links + if (mapWindowLinks != null) { + // Cancel any existing links to prevent duplicate listeners + mapWindowLinks.cancel(); + } + if (dialog.querySelector(".location-chat-link") != null) { + mapWindowLinks = dialog.querySelectorAll(".location-chat-link").onClick.listen((Event e) { + e.preventDefault(); + String text = ((e.target) as Element).text; + + if ((e.target as Element).classes.contains("hub-chat-link")) { + String hubId = mapData.getHubId(text); + new WorldMap(hubId).loadhubdiv(hubId, text); + } else if ((e.target as Element).classes.contains("street-chat-link")) { + String hubId = mapData.streetData[text]["hub_id"].toString(); + new WorldMap(hubId).hubMap(hub_id: hubId, highlightStreet: text); + } + + mapWindow.open(); + }); + } + }); + } void addAlert(String alert, {bool toast: false}) { String classes = "system "; - if (toast) { - classes += "chat-toast "; - } - String text = '

$alert

'; - Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(text, validator: validator); - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - } + void _add() { + String text = '

$alert

'; + Element dialog = conversationElement.querySelector('.dialog'); + dialog.appendHtml(text, validator: validator); - void displayList(List users) { - String alert = "Players in this channel:"; + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + } - for (int i = 0; i != users.length; i++) { - users[i] = '' + - users[i] + - ''; - alert = alert + " " + users[i]; - } + if (toast) { + classes += "chat-toast "; + new Timer(new Duration(milliseconds: 100), () { + _add(); + }); + } else { + _add(); + } + } + + void displayList(List users) { + String alert = "Players in this channel:"; + + for (int i = 0; i != users.length; i++) { + users[i] = '' + + users[i] + + ''; + alert = alert + " " + users[i]; + } - String text = '

$alert

'; + String text = '

$alert

'; - Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(text, validator: validator); + Element dialog = conversationElement.querySelector('.dialog'); + dialog.appendHtml(text, validator: validator); - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - } + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + } - /** + /** * Archive the conversation (detach it from the chat panel) so that we may reattach * it later with the history intact. **/ - void archiveConversation() { - conversationElement.classes.add("archive-${title.replaceAll(' ', '_')}"); - conversationElement.classes.remove("conversation"); - view.conversationArchive.append(conversationElement); - computePanelSize(); - otherChat = null; - } - - /** + void archiveConversation() { + conversationElement.classes.add("archive-${title.replaceAll(' ', '_')}"); + conversationElement.classes.remove("conversation"); + view.conversationArchive.append(conversationElement); + computePanelSize(); + otherChat = null; + } + + /** * Find an archived conversation and return it * returns null if no conversation exists **/ - Element getArchivedConversation(String title) { - String archiveClass = '.archive-${title.replaceAll(' ', '_')}'; - Element conversationElement = view.conversationArchive.querySelector(archiveClass); - if (conversationElement != null) { - conversationElement.classes.remove(archiveClass); - conversationElement.classes.add("conversation"); - - //move this conversation to the front of the openConversations list - for (Chat c in openConversations) { - if (c.title == title) { - openConversations.remove(c); - openConversations.insert(0, c); - break; - } - } - } - return conversationElement; - } - - void computePanelSize() { - List conversations = - view.panel.querySelectorAll('.conversation').toList(); - int num = conversations.length - 1; - conversations.forEach((Element conversation) { - if (conversation.hidden) { - num--; - } - }); - conversations.forEach((Element conversation) => conversation.style.height = "${100 / num}%"); - } - - void processInput(TextInputElement input) { - //onKeyUp seems to be too late to prevent TAB's default behavior - input.onKeyDown.listen((KeyboardEvent key) { - //pressed up arrow - if (key.keyCode == 38 && inputHistoryPointer < inputHistory.length) { - input.value = inputHistory.elementAt(inputHistoryPointer); - if (inputHistoryPointer < inputHistory.length - 1) { - inputHistoryPointer++; - } - } - //pressed down arrow - if (key.keyCode == 40) { - if (inputHistoryPointer > 0) { - inputHistoryPointer--; - input.value = inputHistory.elementAt(inputHistoryPointer); - } else { - input.value = ""; - } - } - //tab key, try to complete a user's name or an emoticon - if (input.value != "" && key.keyCode == 9) { - key.preventDefault(); - - //look for an emoticon instead of a username - if (input.value.endsWith(":")) { - emoticonComplete(input, key); - return; - } - - //let's suggest players to tab complete - tabComplete(input, key); - - return; - } - }); - - input.onKeyUp.listen((KeyboardEvent key) { - if (key.keyCode != 9) { - tabInserted = false; - } - - if (key.keyCode != 13) { - //listen for enter key - return; - } - - if (input.value.trim().length == 0) { - toast("You can't send a blank message"); - return; - } - - RegExp formatChars = new RegExp(r'|||||||'); - if(input.value.replaceAll(formatChars,'').length == 0) { - toast("You must have non-formatting content in your message"); - return; - } - - parseInput(input.value); - - inputHistory.insert(0, input.value); //add to beginning of list - inputHistoryPointer = 0; //point to beginning of list - if (inputHistory.length > 50) { - //don't allow the list to grow forever - inputHistory.removeLast(); - } - - input.value = ''; - }); - } - - void emoticonComplete(InputElement input, KeyboardEvent k) { - //don't allow a key like tab to change to a different chat - //if we don't get a hit and k=[tab], we will re-fire - k.stopImmediatePropagation(); - - String value = input.value; - bool emoticonInserted = false; - - //if the input is exactly one long (therefore it is just a colon) - if (value.length == 1) { - //String beforePart = value.substring(0,lastColon); - input.value = ":${EMOTICONS.elementAt(emoticonPointer)}:"; - emoticonPointer++; - emoticonInserted = true; - } - //if the input is more than 1 long and there's a space before the colon (word separation) - else if (value.endsWith(" :")) { - int lastColon = value.lastIndexOf(':'); - String beforePart = value.substring(0, lastColon); - input.value = "$beforePart:${EMOTICONS.elementAt(emoticonPointer)}:"; - emoticonPointer++; - emoticonInserted = true; - } - - //if the input is more than 1 long and there is an emoticon that we should replace - //to do this, we check if the value has 2 : and that the text between them - //exactly matches an emoticon - int previousColon = value.substring(0, value.length - 1).lastIndexOf(':'); - if (previousColon > -1) { - String beforeSegment = value.substring(0, previousColon); - String emoticonSegment = value.substring(previousColon, value.length); - for (int i = 0; i < EMOTICONS.length; i++) { - String emoticon = EMOTICONS[i]; - String emoticonNext; - if (i < EMOTICONS.length - 1) { - emoticonNext = EMOTICONS[i + 1]; - } else { - emoticonNext = EMOTICONS[0]; - } - if (emoticonSegment.contains(':$emoticon:')) { - input.value = "$beforeSegment:$emoticonNext:"; - emoticonPointer++; - emoticonInserted = true; - break; - } - } - } - - //make sure we don't point past the end of the array - if (emoticonPointer >= EMOTICONS.length) { - emoticonPointer = 0; - } - - //if we didn't manage to insert an emoticon and tab was pressed... - //try to advance chat focus because we stifled it earlier - if (!emoticonInserted && k.keyCode == 9) { - advanceChatFocus(k); - } - } - - Future tabComplete(TextInputElement input, KeyboardEvent k) async { - //don't allow a key like tab to change to a different chat - //if we don't get a hit and k=[tab], we will re-fire - k.stopImmediatePropagation(); - - String channel = 'Global Chat'; - bool inserted = false; - - if (title != channel) { - channel = currentStreet.label; - } - String url = 'http://' + Configs.utilServerAddress + "/listUsers?channel=$channel"; - connectedUsers = JSON.decode(await HttpRequest.requestCrossOrigin(url)); - - int startIndex = input.value.lastIndexOf(" ") == -1 ? 0 : input.value.lastIndexOf(" ") + 1; - String localLastWord = input.value.substring(startIndex); - if (!tabInserted) { - lastWord = input.value.substring(startIndex); - } - - for (; tabSearchIndex < connectedUsers.length; tabSearchIndex++) { - String username = connectedUsers.elementAt(tabSearchIndex); - if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && - username.toLowerCase() != localLastWord.toLowerCase()) { - input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; - tabInserted = true; - inserted = true; - tabSearchIndex++; - break; - } - } - //if we didn't find it yet and the tabSearchIndex was not 0, let's look at the beginning of the array as well - //otherwise the user will have to press the tab key again - if (!tabInserted) { - for (int index = 0; index < tabSearchIndex; index++) { - String username = connectedUsers.elementAt(index); - if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && - username.toLowerCase() != localLastWord.toLowerCase()) { - input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; - tabInserted = true; - inserted = true; - tabSearchIndex = index + 1; - break; - } - } - } - - if (!inserted && k.keyCode == 9) { - advanceChatFocus(k); - } - - if (tabSearchIndex == connectedUsers.length) { - //wrap around for next time - tabSearchIndex = 0; - } - } - - void parseInput(String input) { - // if its not a command, send it through. - if (parseCommand(input)) { - return; - } - else if (input.toLowerCase() == "/list") { - Map map = {}; - map["username"] = game.username; - map["statusMessage"] = "list"; - map["channel"] = title; - map["street"] = currentStreet.label; - transmit('outgoingChatEvent', map); - } else { - Map map = new Map(); - map["username"] = game.username; - map["message"] = input; - map["channel"] = title; - if (title == "Local Chat") { - map["street"] = currentStreet.label; - } - - transmit('outgoingChatEvent', map); - - //display chat bubble if we're talking in local (unless it's a /me message) - if (map["channel"] == "Local Chat" && - !(map["message"] as String).toLowerCase().startsWith("/me")) { - //remove any existing bubble - if (CurrentPlayer.chatBubble != null && - CurrentPlayer.chatBubble.bubble != null) { - CurrentPlayer.chatBubble.bubble.remove(); - } - CurrentPlayer.chatBubble = new ChatBubble(parseEmoji(map["message"]), - CurrentPlayer, CurrentPlayer.playerParentElement); - } - } - } - - // Update the list of online players in the sidebar - Future refreshOnlinePlayers() async { - if (this.title != "Global Chat") { - return -1; - } - - // Ignore yourself (can't chat with yourself, either) - List users = JSON.decode(await HttpRequest.requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); - users.removeWhere((String username) => username == game.username); - - // Reset the list - Element list = querySelector("#playerList"); - list.children.clear(); - - if (users.length == 0) { - // Nobody else is online - Element message = new LIElement() - ..classes.addAll(["noChatSpawn"]) - ..setInnerHtml(' Nobody else here'); - list.append(message); - return 0; - } else { - // Other players are online - users.forEach((String username) { - Element user = new LIElement() - ..classes.add("online") - ..style.pointerEvents = "none" - //..classes.addAll(["online", "chatSpawn"]) - //..dataset["chat"] = username - ..setInnerHtml(' $username'); - list.append(user); - }); - return users.length; - } - } + Element getArchivedConversation(String title) { + String archiveClass = '.archive-${title.replaceAll(' ', '_')}'; + Element conversationElement = view.conversationArchive.querySelector(archiveClass); + if (conversationElement != null) { + conversationElement.classes.remove(archiveClass); + conversationElement.classes.add("conversation"); + + //move this conversation to the front of the openConversations list + for (Chat c in openConversations) { + if (c.title == title) { + openConversations.remove(c); + openConversations.insert(0, c); + break; + } + } + } + return conversationElement; + } + + void computePanelSize() { + List conversations = view.panel.querySelectorAll('.conversation').toList(); + int num = conversations.length - 1; + conversations.forEach((Element conversation) { + if (conversation.hidden) { + num--; + } + }); + conversations.forEach((Element conversation) => conversation.style.height = "${100 / num}%"); + } + + void processInput(TextInputElement input) { + //onKeyUp seems to be too late to prevent TAB's default behavior + input.onKeyDown.listen((KeyboardEvent key) { + //pressed up arrow + if (key.keyCode == 38 && inputHistoryPointer < inputHistory.length) { + input.value = inputHistory.elementAt(inputHistoryPointer); + if (inputHistoryPointer < inputHistory.length - 1) { + inputHistoryPointer++; + } + } + //pressed down arrow + if (key.keyCode == 40) { + if (inputHistoryPointer > 0) { + inputHistoryPointer--; + input.value = inputHistory.elementAt(inputHistoryPointer); + } else { + input.value = ""; + } + } + //tab key, try to complete a user's name or an emoticon + if (input.value != "" && key.keyCode == 9) { + key.preventDefault(); + + //look for an emoticon instead of a username + if (input.value.endsWith(":")) { + emoticonComplete(input, key); + return; + } + + //let's suggest players to tab complete + tabComplete(input, key); + + return; + } + }); + + input.onKeyUp.listen((KeyboardEvent key) { + if (key.keyCode != 9) { + tabInserted = false; + } + + if (key.keyCode != 13) { + //listen for enter key + return; + } + + if (input.value.trim().length == 0) { + toast("You can't send a blank message"); + return; + } + + RegExp formatChars = new RegExp(r'|||||||'); + if (input.value.replaceAll(formatChars, '').length == 0) { + toast("You must have non-formatting content in your message"); + return; + } + + parseInput(input.value); + + inputHistory.insert(0, input.value); //add to beginning of list + inputHistoryPointer = 0; //point to beginning of list + if (inputHistory.length > 50) { + //don't allow the list to grow forever + inputHistory.removeLast(); + } + + input.value = ''; + }); + } + + void emoticonComplete(InputElement input, KeyboardEvent k) { + //don't allow a key like tab to change to a different chat + //if we don't get a hit and k=[tab], we will re-fire + k.stopImmediatePropagation(); + + String value = input.value; + bool emoticonInserted = false; + + //if the input is exactly one long (therefore it is just a colon) + if (value.length == 1) { + //String beforePart = value.substring(0,lastColon); + input.value = ":${EMOTICONS.elementAt(emoticonPointer)}:"; + emoticonPointer++; + emoticonInserted = true; + } + //if the input is more than 1 long and there's a space before the colon (word separation) + else if (value.endsWith(" :")) { + int lastColon = value.lastIndexOf(':'); + String beforePart = value.substring(0, lastColon); + input.value = "$beforePart:${EMOTICONS.elementAt(emoticonPointer)}:"; + emoticonPointer++; + emoticonInserted = true; + } + + //if the input is more than 1 long and there is an emoticon that we should replace + //to do this, we check if the value has 2 : and that the text between them + //exactly matches an emoticon + int previousColon = value.substring(0, value.length - 1).lastIndexOf(':'); + if (previousColon > -1) { + String beforeSegment = value.substring(0, previousColon); + String emoticonSegment = value.substring(previousColon, value.length); + for (int i = 0; i < EMOTICONS.length; i++) { + String emoticon = EMOTICONS[i]; + String emoticonNext; + if (i < EMOTICONS.length - 1) { + emoticonNext = EMOTICONS[i + 1]; + } else { + emoticonNext = EMOTICONS[0]; + } + if (emoticonSegment.contains(':$emoticon:')) { + input.value = "$beforeSegment:$emoticonNext:"; + emoticonPointer++; + emoticonInserted = true; + break; + } + } + } + + //make sure we don't point past the end of the array + if (emoticonPointer >= EMOTICONS.length) { + emoticonPointer = 0; + } + + //if we didn't manage to insert an emoticon and tab was pressed... + //try to advance chat focus because we stifled it earlier + if (!emoticonInserted && k.keyCode == 9) { + advanceChatFocus(k); + } + } + + Future tabComplete(TextInputElement input, KeyboardEvent k) async { + //don't allow a key like tab to change to a different chat + //if we don't get a hit and k=[tab], we will re-fire + k.stopImmediatePropagation(); + + String channel = 'Global Chat'; + bool inserted = false; + + if (title != channel) { + channel = currentStreet.label; + } + String url = 'http://' + Configs.utilServerAddress + "/listUsers?channel=$channel"; + connectedUsers = JSON.decode(await HttpRequest.requestCrossOrigin(url)); + + int startIndex = input.value.lastIndexOf(" ") == -1 ? 0 : input.value.lastIndexOf(" ") + 1; + String localLastWord = input.value.substring(startIndex); + if (!tabInserted) { + lastWord = input.value.substring(startIndex); + } + + for (; tabSearchIndex < connectedUsers.length; tabSearchIndex++) { + String username = connectedUsers.elementAt(tabSearchIndex); + if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && + username.toLowerCase() != localLastWord.toLowerCase()) { + input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; + tabInserted = true; + inserted = true; + tabSearchIndex++; + break; + } + } + //if we didn't find it yet and the tabSearchIndex was not 0, let's look at the beginning of the array as well + //otherwise the user will have to press the tab key again + if (!tabInserted) { + for (int index = 0; index < tabSearchIndex; index++) { + String username = connectedUsers.elementAt(index); + if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && + username.toLowerCase() != localLastWord.toLowerCase()) { + input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; + tabInserted = true; + inserted = true; + tabSearchIndex = index + 1; + break; + } + } + } + + if (!inserted && k.keyCode == 9) { + advanceChatFocus(k); + } + + if (tabSearchIndex == connectedUsers.length) { + //wrap around for next time + tabSearchIndex = 0; + } + } + + void parseInput(String input) { + // if its not a command, send it through. + if (parseCommand(input)) { + return; + } else if (input.toLowerCase() == "/list") { + Map map = {}; + map["username"] = game.username; + map["statusMessage"] = "list"; + map["channel"] = title; + map["street"] = currentStreet.label; + transmit('outgoingChatEvent', map); + } else { + Map map = new Map(); + map["username"] = game.username; + map["message"] = input; + map["channel"] = title; + if (title == "Local Chat") { + map["street"] = currentStreet.label; + } + + transmit('outgoingChatEvent', map); + + //display chat bubble if we're talking in local (unless it's a /me message) + if (map["channel"] == "Local Chat" && + !(map["message"] as String).toLowerCase().startsWith("/me")) { + //remove any existing bubble + if (CurrentPlayer.chatBubble != null && CurrentPlayer.chatBubble.bubble != null) { + CurrentPlayer.chatBubble.bubble.remove(); + } + CurrentPlayer.chatBubble = new ChatBubble( + parseEmoji(map["message"]), CurrentPlayer, CurrentPlayer.playerParentElement); + } + } + } + + // Update the list of online players in the sidebar + Future refreshOnlinePlayers() async { + if (this.title != "Global Chat") { + return -1; + } + + // Ignore yourself (can't chat with yourself, either) + List users = JSON.decode(await HttpRequest + .requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); + users.removeWhere((String username) => username == game.username); + + // Reset the list + Element list = querySelector("#playerList"); + list.children.clear(); + + if (users.length == 0) { + // Nobody else is online + Element message = new LIElement() + ..classes.addAll(["noChatSpawn"]) + ..setInnerHtml(' Nobody else here'); + list.append(message); + return 0; + } else { + // Other players are online + users.forEach((String username) { + Element user = new LIElement() + ..classes.add("online") + ..style.pointerEvents = "none" + //..classes.addAll(["online", "chatSpawn"]) + //..dataset["chat"] = username + ..setInnerHtml(' $username'); + list.append(user); + }); + return users.length; + } + } } // Manage focus bool advanceChatFocus(KeyboardEvent k) { - k.preventDefault(); - - bool found = false; - for (int i = 0; i < openConversations.length; i++) { - Chat convo = openConversations[i]; - - if (convo.focused) { - if (i < openConversations.length - 1) { - //unfocus the current - convo.blur(); - - //find the next non-archived conversation and focus it - for (int j = i + 1; j < openConversations.length; j++) { - if (!openConversations[j].archived) { - openConversations[j].focus(); - found = true; - } - } - - if (found) { - break; - } - } else { - // last chat in list, focus game - querySelector("#gameselector").focus(); - for (int i = 0; i < openConversations.length; i++) { - openConversations[i].blur(); - } - found = true; - } - } - } - - if (!found) { - // game is focused, focus first chat that is not archived - for (Chat c in openConversations) { - if (!c.archived) { - c.focus(); - break; - } - } - } - - return true; -} \ No newline at end of file + k.preventDefault(); + + bool found = false; + for (int i = 0; i < openConversations.length; i++) { + Chat convo = openConversations[i]; + + if (convo.focused) { + if (i < openConversations.length - 1) { + //unfocus the current + convo.blur(); + + //find the next non-archived conversation and focus it + for (int j = i + 1; j < openConversations.length; j++) { + if (!openConversations[j].archived) { + openConversations[j].focus(); + found = true; + } + } + + if (found) { + break; + } + } else { + // last chat in list, focus game + querySelector("#gameselector").focus(); + for (int i = 0; i < openConversations.length; i++) { + openConversations[i].blur(); + } + found = true; + } + } + } + + if (!found) { + // game is focused, focus first chat that is not archived + for (Chat c in openConversations) { + if (!c.archived) { + c.focus(); + break; + } + } + } + + return true; +} From 772fe4351915060b2bb9e3ff010d949fab9685a1 Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 18:21:44 -0600 Subject: [PATCH 12/43] Fix inventory icon positioning (not bags...yet) --- lib/src/network/server_interop/inventory.dart | 10 +- lib/src/network/server_interop/so_item.dart | 155 +++++++++--------- 2 files changed, 79 insertions(+), 86 deletions(-) diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 23e4a451..18d9e5fe 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -33,14 +33,6 @@ Future updateInventory([Map map]) async { } else { print("Attempted inventory update: failed."); return; - - // TODO: get this to work -// String serverData = await HttpRequest.getString( -// "http://${Configs.utilServerAddress}/getInventory/${game.email}" -// ); -// -// Map inventoryEntry = JSON.decode(serverData); -// dataSlots = JSON.decode(inventoryEntry["inventory_json"]); } List currentSlots = playerInventory.slots; @@ -113,4 +105,4 @@ Future updateInventory([Map map]) async { } transmit("inventoryUpdated", true); -} \ No newline at end of file +} diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index d7fa0253..986013e3 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -5,7 +5,7 @@ part of couclient; itemContextMenu(ItemDef i, String slot, MouseEvent event) { event.preventDefault(); event.stopPropagation(); - + int barSlot = int.parse(slot.split('.').elementAt(0)); int bagSlot = int.parse(slot.split('.').elementAt(1)); List actions = []; @@ -42,85 +42,86 @@ itemContextMenu(ItemDef i, String slot, MouseEvent event) { findNewSlot(Slot slot, int index, {bool update: false}) async { ItemDef item = slot.item; int count = slot.count; - ImageElement img = new ImageElement(src: item.spriteUrl); - await img.onLoad; - Element barSlot = view.inventory.children.elementAt(index); - barSlot.children.clear(); - String cssName = item.itemType.replaceAll(" ", "_"); - Element itemDiv = new DivElement(); - - //determine what we need to scale the sprite image to in order to fit - num scale = 1; - if (img.height > img.width / item.iconNum) { - scale = (barSlot.contentEdge.height - 10) / img.height; - } else { - scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); - } - - itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; - itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; - itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; - itemDiv.style.backgroundRepeat = 'no-repeat'; - itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; - itemDiv.style.backgroundPosition = "0 50%"; - itemDiv.style.margin = "auto"; - itemDiv.className = 'item-$cssName inventoryItem'; - - itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); - itemDiv.attributes['count'] = "1"; - itemDiv.attributes['itemMap'] = encode(item); - - String slotNum = '${barSlot.dataset["slot-num"]}.-1'; - itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); - barSlot.append(itemDiv); - - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - barSlot.append(itemCount); - if (count <= 1) { - itemCount.text = ""; - } + ImageElement img; + img = new ImageElement(src: item.spriteUrl)..onLoad.listen((_) { + Element barSlot = view.inventory.children.elementAt(index); + barSlot.children.clear(); + String cssName = item.itemType.replaceAll(" ", "_"); + Element itemDiv = new DivElement(); + + //determine what we need to scale the sprite image to in order to fit + num scale = 1; + if (img.height > img.width / item.iconNum) { + scale = (barSlot.contentEdge.height - 10) / img.height; + } else { + scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); + } - int offset = count; - if (item.iconNum != null && item.iconNum < count) { - offset = item.iconNum; - } - itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; + itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; + itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; + itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; + itemDiv.style.backgroundRepeat = 'no-repeat'; + itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; + itemDiv.style.backgroundPosition = "0 50%"; + itemDiv.style.margin = "auto"; + itemDiv.className = 'item-$cssName inventoryItem'; + + itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); + itemDiv.attributes['count'] = "1"; + itemDiv.attributes['itemMap'] = encode(item); + + String slotNum = '${barSlot.dataset["slot-num"]}.-1'; + itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); + barSlot.append(itemDiv); + + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + barSlot.append(itemCount); + if (count <= 1) { + itemCount.text = ""; + } - if (!update) { - itemDiv.classes.add("bounce"); - // remove the bounce class so that it's not still there for a drag and drop event - new Timer(new Duration(seconds: 1), () { - itemDiv.classes.remove("bounce"); - }); - } + int offset = count; + if (item.iconNum != null && item.iconNum < count) { + offset = item.iconNum; + } + itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; + + if (!update) { + itemDiv.classes.add("bounce"); + // remove the bounce class so that it's not still there for a drag and drop event + new Timer(new Duration(seconds: 1), () { + itemDiv.classes.remove("bounce"); + }); + } - // Containers - DivElement containerButton; - String bagWindowId; - if (item.isContainer == true) { - containerButton = new DivElement() - ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) - ..onClick.listen((_) { - if (containerButton.classes.contains("item-container-closed")) { - // Container is closed, open it - // Open the bag window - bagWindowId = new BagWindow(int.parse(itemDiv.parent.dataset["slot-num"]), item).id; - // Update the slot display - BagWindow.updateTriggerBtn(false, itemDiv); - } else { - // Container is open, close it - // Close the bag window - BagWindow.closeId(bagWindowId); - // Update the slot display - BagWindow.updateTriggerBtn(true, itemDiv); - } - }); - itemDiv.parent.append(containerButton); - } + // Containers + DivElement containerButton; + String bagWindowId; + if (item.isContainer == true) { + containerButton = new DivElement() + ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) + ..onClick.listen((_) { + if (containerButton.classes.contains("item-container-closed")) { + // Container is closed, open it + // Open the bag window + bagWindowId = new BagWindow(int.parse(itemDiv.parent.dataset["slot-num"]), item).id; + // Update the slot display + BagWindow.updateTriggerBtn(false, itemDiv); + } else { + // Container is open, close it + // Close the bag window + BagWindow.closeId(bagWindowId); + // Update the slot display + BagWindow.updateTriggerBtn(true, itemDiv); + } + }); + itemDiv.parent.append(containerButton); + } - transmit('inventoryUpdated'); + transmit('inventoryUpdated'); + }); } //void putInInventory(ImageElement img, ItemDef i, int index, {bool update: false}) { @@ -210,4 +211,4 @@ Map getDropMap(int count, int slotNum, int subSlotNum) { ..['tsid'] = currentStreet.streetData['tsid']; return dropMap; -} \ No newline at end of file +} From 78db222924dc11b587961f64703c411c84d9fa90 Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 18:25:26 -0600 Subject: [PATCH 13/43] again with bags! fix https://github.com/ChildrenOfUr/cou-issues/issues/217 --- lib/src/display/windows/bag_window.dart | 76 ++++++++++++------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index 17fb3035..ffb2da0f 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -140,45 +140,45 @@ class BagWindow extends Modal { } Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { - ImageElement img = new ImageElement(src: i.spriteUrl); - await img.onLoad; - - num scale = 1; - if (img.height > img.width / i.iconNum) { - scale = (slot.contentEdge.height - 10) / img.height; - } else { - scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); - } + ImageElement img; + img = new ImageElement(src: i.spriteUrl)..onLoad.listen((_) { + num scale = 1; + if (img.height > img.width / i.iconNum) { + scale = (slot.contentEdge.height - 10) / img.height; + } else { + scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); + } - item - ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) - ..attributes["name"] = i.name - ..attributes["count"] = count.toString() - ..attributes["itemmap"] = encode(i) - ..style.width = (slot.contentEdge.width - 10).toString() + "px" - ..style.height = (slot.contentEdge.height - 10).toString() + "px" - ..style.backgroundImage = 'url(${i.spriteUrl})' - ..style.backgroundRepeat = 'no-repeat' - ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" - ..style.margin = "auto"; - - int offset = count; - if (i.iconNum != null && i.iconNum < count) { - offset = i.iconNum; - } + item + ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) + ..attributes["name"] = i.name + ..attributes["count"] = count.toString() + ..attributes["itemmap"] = encode(i) + ..style.width = (slot.contentEdge.width - 10).toString() + "px" + ..style.height = (slot.contentEdge.height - 10).toString() + "px" + ..style.backgroundImage = 'url(${i.spriteUrl})' + ..style.backgroundRepeat = 'no-repeat' + ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" + ..style.margin = "auto"; + + int offset = count; + if (i.iconNum != null && i.iconNum < count) { + offset = i.iconNum; + } - item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; - - String slotString = '$sourceSlotNum.$bagSlotIndex'; - item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i,slotString,event)); - if (count > 1) { - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - item.parent.append(itemCount); - } else if (item.parent.querySelector(".itemCount") != null) { - item.parent.querySelector(".itemCount").text = ""; - } + item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; + + String slotString = '$sourceSlotNum.$bagSlotIndex'; + item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i,slotString,event)); + if (count > 1) { + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + item.parent.append(itemCount); + } else if (item.parent.querySelector(".itemCount") != null) { + item.parent.querySelector(".itemCount").text = ""; + } + }); } @override @@ -228,4 +228,4 @@ class BagWindow extends Modal { item.classes.remove("inv-item-disabled"); } } -} \ No newline at end of file +} From 8bf77caef4a8552bf263dd240e7471a92057eac7 Mon Sep 17 00:00:00 2001 From: klikini Date: Wed, 2 Dec 2015 18:45:05 -0600 Subject: [PATCH 14/43] fix some if statements --- lib/src/display/windows/vendor_window.dart | 38 ++++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/src/display/windows/vendor_window.dart b/lib/src/display/windows/vendor_window.dart index 1fb1bd4d..b9168ba4 100644 --- a/lib/src/display/windows/vendor_window.dart +++ b/lib/src/display/windows/vendor_window.dart @@ -60,7 +60,7 @@ class VendorWindow extends Modal { npcId = vendorMap['id']; String windowTitle = vendorMap['vendorName']; if(windowTitle.contains('Street Spirit:')) { - windowTitle = windowTitle.substring(15); + windowTitle = windowTitle.substring(15) + " Vendor"; } header.innerHtml = '' + windowTitle; currants.text = " ${commaFormatter.format(metabolics.currants)} currants"; @@ -106,13 +106,17 @@ class VendorWindow extends Modal { DivElement dropTarget = querySelector("#SellDropTarget"); Dropzone dropzone = new Dropzone(dropTarget, acceptor: new Acceptor.draggables([InvDragging._draggables])); dropzone.onDrop.listen((DropzoneEvent dropEvent) { + // TODO: fix this only getting called the first time + // https://github.com/ChildrenOfUr/cou-issues/issues/279 + // print("dropped item"); spawnBuyDetails(JSON.decode(dropEvent.draggableElement.attributes['itemMap']), vendorMap['id'], sellMode:true); }); - if(sellMode) + if(sellMode) { this.displayElement.querySelector('#SellTab').click(); - else + } else { this.displayElement.querySelector('#BuyTab').click(); + } this.open(); } @@ -166,24 +170,26 @@ class VendorWindow extends Modal { int newNum = buyNum.valueAsNumber.toInt(); numToBuy = _updateNumToBuy(item, newNum, sellMode:sellMode); } - catch(e) { - } + catch(e) {} }); + // Sell/Buy Button StreamSubscription bb = buyButton.onClick.listen((_) { int newValue; Map actionMap = {"itemType": item['itemType'], "num": numToBuy}; if(sellMode) { - if(numToBuy > getNumItems(item['itemType'])) + if(numToBuy > getNumItems(item['itemType'])) { return; + } newValue = metabolics.currants + (item['price'] * numToBuy * .7) ~/ 1; sendAction("sellItem", vendorId, actionMap); } else { - if(metabolics.currants < item['price'] * numToBuy) + if(metabolics.currants < item['price'] * numToBuy) { return; + } newValue = metabolics.currants - item['price'] * numToBuy; sendAction("buyItem", vendorId, actionMap); @@ -263,10 +269,11 @@ class VendorWindow extends Modal { bmax.cancel(); this.displayElement.querySelector('#buy-qty').hidden = true; - if(sellMode) + if(sellMode) { sell.hidden = false; - else + } else { buy.hidden = false; + } }); } @@ -274,18 +281,21 @@ class VendorWindow extends Modal { if(newNum < 1) newNum = 1; if(newNum >= 99) newNum = 99; - if(sellMode) + if(sellMode) { newNum = min(newNum, getNumItems(item['itemType'])); + } buyNum.valueAsNumber = newNum; int value = item['price'] * newNum; - if(sellMode) + if(sellMode) { value = (value * .7) ~/ 1; + } - if(sellMode) + if(sellMode) { buyButton.text = "Sell $newNum for $value\u20a1"; - else + } else { buyButton.text = "Buy $newNum for $value\u20a1"; + } return buyNum.valueAsNumber.toInt(); } @@ -308,4 +318,4 @@ class CustomAvatarHandler extends CloneAvatarHandler { document.body.append(super.avatar); super.setLeftTop(new Point(x, y)); } -} \ No newline at end of file +} From 8ede856a20bec072ad49d7587ef580226edac54c Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 5 Dec 2015 21:26:25 -0600 Subject: [PATCH 15/43] added light rain option --- lib/src/systems/weather.dart | 8 +- web/files/css/desktop/weather.css | 6 +- web/files/system/raindrop_scatter_light.svg | 234 ++++++++++++++++++++ 3 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 web/files/system/raindrop_scatter_light.svg diff --git a/lib/src/systems/weather.dart b/lib/src/systems/weather.dart index ab27d1f3..9ecfae33 100644 --- a/lib/src/systems/weather.dart +++ b/lib/src/systems/weather.dart @@ -193,6 +193,12 @@ class WeatherManager { if(createState == WeatherState.RAINING && !mapData.snowyWeather()) { // Rain + if (_intensity == WeatherIntensity.LIGHT) { + _raindrops.classes.add("light"); + } else { + _raindrops.classes.remove("light"); + } + // Sound effect _playRainSound(); @@ -295,4 +301,4 @@ class WeatherManager { } } } -} \ No newline at end of file +} diff --git a/web/files/css/desktop/weather.css b/web/files/css/desktop/weather.css index 06c3c7ff..669ae2a7 100644 --- a/web/files/css/desktop/weather.css +++ b/web/files/css/desktop/weather.css @@ -76,6 +76,10 @@ animation: weatherFall 1.5s linear infinite; } +#weather-raindrops.light { + background-image: url(../../../files/system/raindrop_scatter_light.svg); +} + /* Snow */ .snowy { @@ -125,4 +129,4 @@ paper-radio-button::shadow #ink { } paper-radio-button::shadow #offRadio { border-color: rgba(75, 46, 76, 0.38); -} \ No newline at end of file +} diff --git a/web/files/system/raindrop_scatter_light.svg b/web/files/system/raindrop_scatter_light.svg new file mode 100644 index 00000000..037e5b44 --- /dev/null +++ b/web/files/system/raindrop_scatter_light.svg @@ -0,0 +1,234 @@ + + + +image/svg+xml \ No newline at end of file From ddd1d110ce7d75813def5adf1cb5e6bc199e9475 Mon Sep 17 00:00:00 2001 From: klikini Date: Thu, 31 Dec 2015 12:44:08 -0600 Subject: [PATCH 16/43] Better local chat location dividers - Displays "first time in" or "back in" - Links to map --- lib/src/display/chatmessage.dart | 20 +++++++++++++++----- lib/src/display/chatpanel.dart | 9 ++++----- lib/src/display/render/worldmap.dart | 7 +------ lib/src/network/metabolics.dart | 11 +++++++---- lib/src/network/metabolics_service.dart | 3 ++- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/src/display/chatmessage.dart b/lib/src/display/chatmessage.dart index 95cfd13b..cd5f7d15 100644 --- a/lib/src/display/chatmessage.dart +++ b/lib/src/display/chatmessage.dart @@ -82,10 +82,20 @@ class ChatMessage { } } else if (message == "LocationChangeEvent" && player == "invalid_user") { // Switching streets message - html = - '

' - '${currentStreet.label}' - '

'; + void setLceHtml() { + String prefix = (metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) ? "Back" : "First time"); + html = + '

' + '$prefix in ${currentStreet.label}' + '

'; + } + + if (!metabolics.load.isCompleted) { + await metabolics.load.future; + setLceHtml(); + } else { + setLceHtml(); + } } else { // Normal message html = @@ -210,4 +220,4 @@ String parseLocationLinks(String message) { } return _parseStreetLinks(_parseHubLinks(message)); -} \ No newline at end of file +} diff --git a/lib/src/display/chatpanel.dart b/lib/src/display/chatpanel.dart index e38325ae..530c785d 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -73,9 +73,9 @@ class Chat { openConversations.insert(0, this); if (title == "Local Chat") { - new Service(["gameLoaded", "streetLoaded"], (_) { + new Service(["gameLoaded", "streetLoaded"], (_) async { // Streets loaded, display a divider - this.addMessage("invalid_user", "LocationChangeEvent"); + await this.addMessage("invalid_user", "LocationChangeEvent"); // If this is the first one, empty the toast buffer into the chat if (chatToastBuffer.length > 0) { chatToastBuffer.forEach((String message) => this.addAlert(message, toast: true)); @@ -185,10 +185,10 @@ class Chat { } } - void addMessage(String player, String message) { + Future addMessage(String player, String message) async { ChatMessage chat = new ChatMessage(player, message); Element dialog = conversationElement.querySelector('.dialog'); - chat.toHtml().then((String html) { + String html = await chat.toHtml(); // display in panel dialog.appendHtml(html, validator: Chat.validator); @@ -233,7 +233,6 @@ class Chat { mapWindow.open(); }); } - }); } void addAlert(String alert, {bool toast: false}) { diff --git a/lib/src/display/render/worldmap.dart b/lib/src/display/render/worldmap.dart index 5db2fa60..cf838fdf 100644 --- a/lib/src/display/render/worldmap.dart +++ b/lib/src/display/render/worldmap.dart @@ -62,10 +62,6 @@ class WorldMap { moteInfo = map.data_maps_streets['9'](); // check visited streets with server - HttpRequest.getString( - "http://${Configs.utilServerAddress}/getLocationHistory/${game.email}" - ).then((String value) { - List locationHistory = JSON.decode(value); // prepare ui elements view.mapTitle.text = hubInfo['name']; @@ -201,7 +197,7 @@ class WorldMap { if (tsid.startsWith("L")) { tsid = tsid.replaceFirst("L", "G"); } - if (locationHistory.contains(tsid)) { + if (metabolics.playerMetabolics.location_history.contains(tsid)) { street.classes.add("visited"); } } catch (e) { @@ -253,7 +249,6 @@ class WorldMap { HubMabDiv.hidden = false; //HubMapFG.hidden = false; WorldMapDiv.hidden = true; - }); } getStreetAngle(Map street) { diff --git a/lib/src/network/metabolics.dart b/lib/src/network/metabolics.dart index c7af656a..70637e3f 100644 --- a/lib/src/network/metabolics.dart +++ b/lib/src/network/metabolics.dart @@ -3,12 +3,15 @@ library metabolics; class Metabolics { int id, mood = 50, max_mood = 100, energy = 50, max_energy = 100; int currants = 0, img = 0, lifetime_img = 0, user_id = -1; - int AlphFavor = 0, CosmaFavor = 0, FriendlyFavor = 0, GrendalineFavor = 0; - int HumbabaFavor = 0, LemFavor = 0, MabFavor = 0, PotFavor = 0, SprigganFavor = 0; - int TiiFavor = 0, ZilleFavor = 0; + int alphfavor = 0, cosmafavor = 0, friendlyfavor = 0, grendalinefavor = 0; + int humbabafavor = 0, lemfavor = 0, mabfavor = 0, potfavor = 0, sprigganfavor = 0; + int tiifavor = 0, zillefavor = 0; + int alphfavor_max = 0, cosmafavor_max = 0, friendlyfavor_max = 0, grendalinefavor_max = 0; + int humbabafavor_max = 0, lemfavor_max = 0, mabfavor_max = 0, potfavor_max = 0, sprigganfavor_max = 0; + int tiifavor_max = 0, zillefavor_max = 0; num current_street_x = 1.0, current_street_y = 0.0; String current_street = 'LA58KK7B9O522PC'; String location_history = '[]'; int quoins_collected = 0; num quoin_multiplier = 1; -} \ No newline at end of file +} diff --git a/lib/src/network/metabolics_service.dart b/lib/src/network/metabolics_service.dart index 2eab38dc..784f3d3e 100644 --- a/lib/src/network/metabolics_service.dart +++ b/lib/src/network/metabolics_service.dart @@ -38,6 +38,7 @@ class MetabolicsService { playerMetabolics = decode(event.data, type:Metabolics); if (!load.isCompleted) { load.complete(); + transmit("metabolicsLoaded", playerMetabolics); } int newImg = lifetime_img; int newLvl; @@ -165,4 +166,4 @@ class MetabolicsService { String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); return int.parse(imgStr); } -} \ No newline at end of file +} From a93ca7a143f24e2a4908935259a092495da9e288 Mon Sep 17 00:00:00 2001 From: klikini Date: Thu, 31 Dec 2015 13:05:16 -0600 Subject: [PATCH 17/43] Add a sky to the spinny urth --- web/files/css/desktop/loader.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/files/css/desktop/loader.css b/web/files/css/desktop/loader.css index 9cde07a1..95cb281e 100644 --- a/web/files/css/desktop/loader.css +++ b/web/files/css/desktop/loader.css @@ -22,6 +22,11 @@ -moz-transition-property: opacity; -moz-transition-duration: 0.3s; -moz-transition-timing-function: ease-in-out; + /* Sky */ + background: #79fcfa; + background: -moz-radial-gradient(center, circle cover, #79fcfa 0%, #e8ffff 95%); + background: -webkit-radial-gradient(center, circle cover, #79fcfa 0%,#e8ffff 95%); + background: radial-gradient(circle at center, #79fcfa 0%,#e8ffff 95%); } #spinny-ur { @@ -212,4 +217,4 @@ #browser-error a { cursor: pointer; text-decoration: underline;; -} \ No newline at end of file +} From 587230460ce35621de5722b86315b42b63fc0e24 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Thu, 31 Dec 2015 15:35:21 -0500 Subject: [PATCH 18/43] bag will update immediately if item bought that goes in it (even if window is open), item dragging seems to work fine @klikini, care to test? --- lib/src/display/inv_dragging.dart | 228 ++++----- lib/src/display/windows/bag_window.dart | 462 +++++++++--------- lib/src/network/server_interop/inventory.dart | 176 +++---- lib/src/network/server_interop/so_item.dart | 292 +++++------ pubspec.yaml | 2 +- 5 files changed, 595 insertions(+), 565 deletions(-) diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index 25d0ea91..d5d48ef9 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -1,117 +1,125 @@ part of couclient; class InvDragging { - /// Track inventory updating - static Service _refresh; - /// Draggable items - static Draggable _draggables; - /// Drop targets - static Dropzone _dropzones; - /// State tracking - static Element _origBox; - - /** - * Map used to store *how* the item was moved. - * Keys: - * - General: - * - - item_number: element: span element containing the count label - * - - bag_btn: element: the toggle button for containers - * - On Pickup: - * - - fromBag: boolean: whether the item used to be in a bag - * - - fromBagIndex: int: which slot the toBag is in (only set if fromBag is true) - * - - fromIndex: int: which slot the item used to be in - * - On Drop: - * - - toBag: boolean: whether the item is going into a bag - * - - toBagIndex: int: which slot the toBag is in (only set if toBag is true) - * - - toIndex: int: which slot the item is going to - */ - static Map _move = {}; - - /// Checks if the specified slot is empty - static bool slotIsEmpty({int index, Element box, int bagWindow}) { - if (index != null) { - if (bagWindow == null) { - box = querySelectorAll("#inventory .box").toList()[index]; - } else { - box = querySelectorAll("#bagWindow$bagWindow").toList()[index]; - } - } - - return (box.children.length == 0); - } - - /// Set up event listeners based on the current inventory - static void init() { - if (_refresh == null) { - _refresh = new Service(["inventoryUpdated"], (_) => init()); - } - // Remove old data - if (_draggables != null) { - _draggables.destroy(); - } - if (_dropzones != null) { - _dropzones.destroy(); - } - - // Set up draggable elements - _draggables = new Draggable( - // List of item elements in boxes - querySelectorAll('.inventoryItem'), - // Display the item on the cursor - avatarHandler: new CustomAvatarHandler(), - // Allow free dragging. - horizontalOnly: false, - // Disable item interaction while dragging it - draggingClass: "item-flying" - )..onDragStart.listen((DraggableEvent e) => handlePickup(e)); - - // Set up acceptor slots - _dropzones = new Dropzone(querySelectorAll("#inventory .box")) - ..onDrop.listen((DropzoneEvent e) => handleDrop(e)); - } - - /// Runs when an item is picked up (drag start) - static void handlePickup(DraggableEvent e) { - _origBox = e.draggableElement.parent; - e.draggableElement.dataset["original-slot-num"] = _origBox.dataset["slot-num"]; - - _move = {}; - - if (querySelector("#windowHolder").contains(_origBox)) { - _move['fromIndex'] = int.parse(_origBox.parent.parent.dataset["source-bag"]); - _move["fromBagIndex"] = int.parse(_origBox.dataset["slot-num"]); - } else { - _move['fromIndex'] = int.parse(_origBox.dataset["slot-num"]); - } - } - - /// Runs when an item is dropped (drop) - static void handleDrop(DropzoneEvent e) { - if (querySelector("#windowHolder").contains(e.dropzoneElement)) { - _move["toIndex"] = int.parse(e.dropzoneElement.parent.parent.dataset["source-bag"]); - _move["toBagIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); - } else { - _move["toIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); - } - - sendAction("moveItem", "global_action_monster", _move); - } + /// Track inventory updating + static Service _refresh; + + /// Draggable items + static Draggable _draggables; + + /// Drop targets + static Dropzone _dropzones; + + /// State tracking + static Element _origBox; + + /** + * Map used to store *how* the item was moved. + * Keys: + * - General: + * - - item_number: element: span element containing the count label + * - - bag_btn: element: the toggle button for containers + * - On Pickup: + * - - fromBag: boolean: whether the item used to be in a bag + * - - fromBagIndex: int: which slot the toBag is in (only set if fromBag is true) + * - - fromIndex: int: which slot the item used to be in + * - On Drop: + * - - toBag: boolean: whether the item is going into a bag + * - - toBagIndex: int: which slot the toBag is in (only set if toBag is true) + * - - toIndex: int: which slot the item is going to + */ + static Map _move = {}; + + /// Checks if the specified slot is empty + static bool slotIsEmpty({int index, Element box, int bagWindow}) { + if (index != null) { + if (bagWindow == null) { + box = querySelectorAll("#inventory .box").toList()[index]; + } else { + box = querySelectorAll("#bagWindow$bagWindow").toList()[index]; + } + } + + return (box.children.length == 0); + } + + /// Set up event listeners based on the current inventory + static void init() { + if (_refresh == null) { + _refresh = new Service(["inventoryUpdated"], (_) => init()); + } + // Remove old data + if (_draggables != null) { + _draggables.destroy(); + } + if (_dropzones != null) { + _dropzones.destroy(); + } + + // Set up draggable elements + _draggables = new Draggable( + // List of item elements in boxes + querySelectorAll('.inventoryItem'), + // Display the item on the cursor + avatarHandler: new CustomAvatarHandler(), + // Allow free dragging. + horizontalOnly: false, + // Disable item interaction while dragging it + draggingClass: "item-flying" + ) + ..onDragStart.listen((DraggableEvent e) => handlePickup(e)); + + print('setup draggables'); + + // Set up acceptor slots + _dropzones = new Dropzone(querySelectorAll("#inventory .box")) + ..onDrop.listen((DropzoneEvent e) => handleDrop(e)); + + print('setup dropzones'); + } + + /// Runs when an item is picked up (drag start) + static void handlePickup(DraggableEvent e) { + _origBox = e.draggableElement.parent; + e.draggableElement.dataset["original-slot-num"] = _origBox.dataset["slot-num"]; + + _move = {}; + + if (querySelector("#windowHolder").contains(_origBox)) { + _move['fromIndex'] = int.parse(_origBox.parent.parent.dataset["source-bag"]); + _move["fromBagIndex"] = int.parse(_origBox.dataset["slot-num"]); + } else { + _move['fromIndex'] = int.parse(_origBox.dataset["slot-num"]); + } + } + + /// Runs when an item is dropped (drop) + static void handleDrop(DropzoneEvent e) { + if (querySelector("#windowHolder").contains(e.dropzoneElement)) { + _move["toIndex"] = int.parse(e.dropzoneElement.parent.parent.dataset["source-bag"]); + _move["toBagIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); + } else { + _move["toIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); + } + + sendAction("moveItem", "global_action_monster", _move); + } } class BagFilterAcceptor extends Acceptor { - BagFilterAcceptor(this.allowedItemTypes); - - List allowedItemTypes; - - @override - bool accepts(Element itemE, int draggable_id, Element box) { - ItemDef item = decode(itemE.attributes['itemmap'],type:ItemDef); - if (allowedItemTypes.length == 0) { - // Those that accept nothing learn to accept everything (except other containers) - return !item.isContainer; - } else { - // Those that are at least somewhat accepting are fine, though - return allowedItemTypes.contains(item.itemType); - } - } + BagFilterAcceptor(this.allowedItemTypes); + + List allowedItemTypes; + + @override + bool accepts(Element itemE, int draggable_id, Element box) { + ItemDef item = decode(itemE.attributes['itemmap'], type: ItemDef); + if (allowedItemTypes.length == 0) { + // Those that accept nothing learn to accept everything (except other containers) + return !item.isContainer; + } else { + // Those that are at least somewhat accepting are fine, though + return allowedItemTypes.contains(item.itemType); + } + } } \ No newline at end of file diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index ffb2da0f..396a5f79 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -2,230 +2,240 @@ part of couclient; class BagWindow extends Modal { - static List openWindows = []; - - static closeId(String id) { - openWindows.where((BagWindow w) => w.id == id).first.close(); - openWindows.removeWhere((BagWindow w) => w.id == id); - } - - static bool get isOpen { - return (querySelectorAll("#windowHolder > .bagWindow").length > 0); - } - - String id = 'bagWindow' + WindowManager.randomId.toString(); - String bagId; - int numSlots; - int sourceSlotNum; - Dropzone acceptors; - - BagWindow(this.sourceSlotNum, ItemDef sourceItem) { - load(sourceItem).then((DivElement windowElement) { - // Handle drag and drop - new Service(["inventoryUpdated"], (_) async { - if (acceptors != null) { - acceptors.destroy(); - } - acceptors = new Dropzone( - windowElement.querySelectorAll(".bagwindow-box"), - acceptor: new BagFilterAcceptor(sourceItem.subSlotFilter) - ) - ..onDrop.listen((DropzoneEvent e) => InvDragging.handleDrop(e)); - }); - new Service(['updateMetadata'], (sourceItem) async { - windowElement.querySelector("ur-well").replaceWith(await load(sourceItem, false)); - transmit('inventoryUpdated',true); - }); - - querySelector("#windowHolder").append(windowElement); - prepare(); - open(); - }); - } - - Future load(ItemDef sourceItem, [bool full = true]) async { - - // Header - - Element closeButton, icon, header; - SpanElement titleSpan; - - if (full) { - closeButton = new Element.tag("i") - ..classes.add("fa-li") - ..classes.add("fa") - ..classes.add("fa-times") - ..classes.add("close"); - - icon = new ImageElement() - ..classes.add("fa-li") - ..src = "files/system/icons/bag.svg"; - - titleSpan = new SpanElement() - ..classes.add("iw-title") - ..text = sourceItem.name; - - if (sourceItem.name.length >= 24) { - titleSpan.style.fontSize = "24px"; - } - - header = new Element.header() - ..append(icon) - ..append(titleSpan); - } - - // Content - - Element well = new Element.tag("ur-well"); - - int numSlots = sourceItem.subSlots; - List subSlots; - - if (sourceItem.metadata["slots"] == null) { - // Empty bag - subSlots = []; - while (subSlots.length < numSlots) { - subSlots.add(({ - "itemType": "", - "count": 0, - "metadata": {} - })); - } - } else { - // Bag has contents - subSlots = sourceItem.metadata["slots"]; - } - - if (subSlots.length != sourceItem.subSlots) { - throw new StateError("Number of slots in bag does not match bag size"); - } else { - int slotNum = 0; - await Future.forEach(subSlots, (Map bagSlot) async { - DivElement slot = new DivElement(); - // Slot - slot - ..classes.addAll(["box", "bagwindow-box"]) - ..dataset["slot-num"] = slotNum.toString(); - well.append(slot); - document.body.append(well); //for measuring - // Item - DivElement itemInSlot = new DivElement(); - slot.append(itemInSlot); - if (!bagSlot["itemType"].isEmpty) { - ItemDef item = decode(JSON.encode(bagSlot['item']),type:ItemDef); - await _sizeItem(slot,itemInSlot,item,bagSlot['count'],slotNum); - } - well.remove(); - - slotNum++; - }); - } - - // Window - - if (full) { - DivElement window = new DivElement() - ..id = id - ..classes.add("window") - ..classes.add("bagWindow") - ..append(header) - ..append(closeButton) - ..append(well) - ..dataset["source-bag"] = sourceSlotNum.toString(); - - return window; - } else { - return well; - } - } - - Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { - ImageElement img; - img = new ImageElement(src: i.spriteUrl)..onLoad.listen((_) { - num scale = 1; - if (img.height > img.width / i.iconNum) { - scale = (slot.contentEdge.height - 10) / img.height; - } else { - scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); - } - - item - ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) - ..attributes["name"] = i.name - ..attributes["count"] = count.toString() - ..attributes["itemmap"] = encode(i) - ..style.width = (slot.contentEdge.width - 10).toString() + "px" - ..style.height = (slot.contentEdge.height - 10).toString() + "px" - ..style.backgroundImage = 'url(${i.spriteUrl})' - ..style.backgroundRepeat = 'no-repeat' - ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" - ..style.margin = "auto"; - - int offset = count; - if (i.iconNum != null && i.iconNum < count) { - offset = i.iconNum; - } - - item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; - - String slotString = '$sourceSlotNum.$bagSlotIndex'; - item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i,slotString,event)); - if (count > 1) { - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - item.parent.append(itemCount); - } else if (item.parent.querySelector(".itemCount") != null) { - item.parent.querySelector(".itemCount").text = ""; - } - }); - } - - @override - open() { - super.open(); - openWindows.add(this); - - transmit('inventoryUpdated',true); - } - - @override - close() { - super.close(); - - // Delete the window - Element window = querySelector("#$id"); - if (window != null) { - window.remove(); - } - - // Update the source inventory icon - Element sourceBox = view.inventory.children.where((Element box) => box.dataset["slot-num"] == sourceSlotNum.toString()).first; - sourceBox.querySelector(".item-container-toggle").click(); - - transmit('inventoryUpdated',true); - } - - // Update the inventory icons (used by the inventory) - - static updateTriggerBtn(bool open, Element item) { - Element btn = item.parent.querySelector(".item-container-toggle"); - if (!open) { - // Closed, opening the bag - btn.classes - ..remove("item-container-closed") - ..remove("fa-plus") - ..add("item-container-open") - ..add("fa-times"); - item.classes.add("inv-item-disabled"); - } else { - // Opened, closing the bag - btn.classes - ..remove("item-container-open") - ..remove("fa-times") - ..add("item-container-closed") - ..add("fa-plus"); - item.classes.remove("inv-item-disabled"); - } - } + static List openWindows = []; + + static closeId(String id) { + openWindows + .where((BagWindow w) => w.id == id) + .first + .close(); + openWindows.removeWhere((BagWindow w) => w.id == id); + } + + static bool get isOpen { + return (querySelectorAll("#windowHolder > .bagWindow").length > 0); + } + + String id = 'bagWindow' + WindowManager.randomId.toString(); + String bagId; + int numSlots; + int sourceSlotNum; + + Dropzone acceptors; + + BagWindow(this.sourceSlotNum, ItemDef sourceItem) { + load(sourceItem).then((DivElement windowElement) { + // Handle drag and drop + new Service(["inventoryUpdated", 'metadataUpdated'], (_) { + if (acceptors != null) { + acceptors.destroy(); + } + acceptors = new Dropzone( + windowElement.querySelectorAll(".bagwindow-box"), + acceptor: new BagFilterAcceptor(sourceItem.subSlotFilter) + ) + ..onDrop.listen((DropzoneEvent e) => InvDragging.handleDrop(e)); + + windowElement.querySelectorAll('.box').forEach((Element e) { + new Draggable(e.children[0], avatarHandler: new CustomAvatarHandler(), + draggingClass: 'item-flying') + ..onDragStart.listen((DraggableEvent e) => InvDragging.handlePickup(e)); + }); + }); + + new Service(['updateMetadata'], (sourceItem) async { + Element newWell = await load(sourceItem, false); + windowElement.querySelector("ur-well").replaceWith(newWell); + print('replaced with new well'); + transmit('metadataUpdated', true); + }); + + querySelector("#windowHolder").append(windowElement); + prepare(); + open(); + }); + } + + Future load(ItemDef sourceItem, [bool full = true]) async { + // Header + + Element closeButton, icon, header; + SpanElement titleSpan; + + if (full) { + closeButton = new Element.tag("i") + ..classes.add("fa-li") + ..classes.add("fa") + ..classes.add("fa-times") + ..classes.add("close"); + + icon = new ImageElement() + ..classes.add("fa-li") + ..src = "files/system/icons/bag.svg"; + + titleSpan = new SpanElement() + ..classes.add("iw-title") + ..text = sourceItem.name; + + if (sourceItem.name.length >= 24) { + titleSpan.style.fontSize = "24px"; + } + + header = new Element.header() + ..append(icon)..append(titleSpan); + } + + // Content + + Element well = new Element.tag("ur-well"); + + int numSlots = sourceItem.subSlots; + List subSlots; + + if (sourceItem.metadata["slots"] == null) { + // Empty bag + subSlots = []; + while (subSlots.length < numSlots) { + subSlots.add(({ + "itemType": "", + "count": 0, + "metadata": {} + })); + } + } else { + // Bag has contents + subSlots = sourceItem.metadata["slots"]; + } + + if (subSlots.length != sourceItem.subSlots) { + throw new StateError("Number of slots in bag does not match bag size"); + } else { + int slotNum = 0; + await Future.forEach(subSlots, (Map bagSlot) async { + DivElement slot = new DivElement(); + // Slot + slot + ..classes.addAll(["box", "bagwindow-box"]) + ..dataset["slot-num"] = slotNum.toString(); + well.append(slot); + document.body.append(well); //for measuring + // Item + DivElement itemInSlot = new DivElement(); + slot.append(itemInSlot); + if (!bagSlot["itemType"].isEmpty) { + ItemDef item = decode(JSON.encode(bagSlot['item']), type: ItemDef); + await _sizeItem(slot, itemInSlot, item, bagSlot['count'], slotNum); + } + well.remove(); + + slotNum++; + }); + } + + // Window + + if (full) { + DivElement window = new DivElement() + ..id = id + ..classes.add("window") + ..classes.add("bagWindow") + ..append(header)..append(closeButton)..append(well) + ..dataset["source-bag"] = sourceSlotNum.toString(); + + return window; + } else { + return well; + } + } + + Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { + ImageElement img; + img = new ImageElement(src: i.spriteUrl) + ..onLoad.listen((_) { + num scale = 1; + if (img.height > img.width / i.iconNum) { + scale = (slot.contentEdge.height - 10) / img.height; + } else { + scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); + } + + item + ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) + ..attributes["name"] = i.name + ..attributes["count"] = count.toString() + ..attributes["itemmap"] = encode(i) + ..style.width = (slot.contentEdge.width - 10).toString() + "px" + ..style.height = (slot.contentEdge.height - 10).toString() + "px" + ..style.backgroundImage = 'url(${i.spriteUrl})' + ..style.backgroundRepeat = 'no-repeat' + ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" + ..style.margin = "auto"; + + int offset = count; + if (i.iconNum != null && i.iconNum < count) { + offset = i.iconNum; + } + + item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; + + String slotString = '$sourceSlotNum.$bagSlotIndex'; + item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i, slotString, event)); + if (count > 1) { + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + item.parent.append(itemCount); + } else if (item.parent.querySelector(".itemCount") != null) { + item.parent + .querySelector(".itemCount") + .text = ""; + } + }); + } + + @override + open() { + super.open(); + openWindows.add(this); + + transmit('inventoryUpdated', true); + } + + @override + close() { + super.close(); + + // Delete the window + Element window = querySelector("#$id"); + if (window != null) { + window.remove(); + } + + // Update the source inventory icon + Element sourceBox = view.inventory.children + .where((Element box) => box.dataset["slot-num"] == sourceSlotNum.toString()) + .first; + sourceBox.querySelector(".item-container-toggle").click(); + + transmit('inventoryUpdated', true); + } + + // Update the inventory icons (used by the inventory) + + static updateTriggerBtn(bool open, Element item) { + Element btn = item.parent.querySelector(".item-container-toggle"); + if (!open) { + // Closed, opening the bag + btn.classes + ..remove("item-container-closed")..remove("fa-plus") + ..add("item-container-open")..add("fa-times"); + item.classes.add("inv-item-disabled"); + } else { + // Opened, closing the bag + btn.classes + ..remove("item-container-open")..remove("fa-times") + ..add("item-container-closed")..add("fa-plus"); + item.classes.remove("inv-item-disabled"); + } + } } diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 18d9e5fe..2d83ba1a 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -3,106 +3,116 @@ part of couclient; Inventory playerInventory = new Inventory(); class Slot { - //a new instance of a Slot is empty by default - String itemType = ""; - ItemDef item = null; - int count = 0; + //a new instance of a Slot is empty by default + String itemType = ""; + ItemDef item = null; + int count = 0; } class Inventory { - // Sets how many slots each player has - final int invSize = 10; - List slots = []; + // Sets how many slots each player has + final int invSize = 10; + List slots = []; } class ItemDef { - String category, iconUrl, spriteUrl, toolAnimation, name, description, itemType, item_id; - int price, stacksTo, iconNum = 4, durability, durabilityUsed = 0, subSlots = 0; - num x, y; - bool onGround = false, isContainer = false; - List subSlotFilter; - List actions = []; - Map metadata = {}; + String category, iconUrl, spriteUrl, toolAnimation, name, description, itemType, item_id; + int price, + stacksTo, + iconNum = 4, + durability, + durabilityUsed = 0, + subSlots = 0; + num x, y; + bool onGround = false, + isContainer = false; + List subSlotFilter; + List actions = []; + Map metadata = {}; } Future updateInventory([Map map]) async { - List dataSlots = []; + List dataSlots = []; - if (map != null) { - dataSlots = map["slots"]; - } else { - print("Attempted inventory update: failed."); - return; - } + if (map != null) { + dataSlots = map["slots"]; + } else { + print("Attempted inventory update: failed."); + return; + } - List currentSlots = playerInventory.slots; - int slotNum = 0; - List slots = []; + List currentSlots = playerInventory.slots; + List slots = []; - //couldn't get the structure to decode correctly so I hacked together this - //it produces the right result, but looks terrible - dataSlots.forEach((Map m) { - Slot slot = new Slot(); - if (!m['itemType'].isEmpty) { - ItemDef item; - if (m['item']['metadata']['slots'] == null) { - item = decode(JSON.encode(m['item']), type:ItemDef); - } else { - Map metadata = (m['item'] as Map).remove('metadata'); - item = decode(JSON.encode(m['item']), type:ItemDef); - item.metadata = metadata; - } - slot.item = item; - slot.itemType = item.itemType; - slot.count = m['count']; - } - slots.add(slot); - slotNum++; - }); + //couldn't get the structure to decode correctly so I hacked together this + //it produces the right result, but looks terrible + dataSlots.forEach((Map m) { + Slot slot = new Slot(); + if (!m['itemType'].isEmpty) { + ItemDef item; + if (m['item']['metadata']['slots'] == null) { + item = decode(JSON.encode(m['item']), type: ItemDef); + } else { + Map metadata = (m['item'] as Map).remove('metadata'); + item = decode(JSON.encode(m['item']), type: ItemDef); + item.metadata = metadata; + } + slot.item = item; + slot.itemType = item.itemType; + slot.count = m['count']; + } + slots.add(slot); + }); - playerInventory.slots = slots; + playerInventory.slots = slots; - //if the current inventory differs (count, type, metatdata) then clear it - //and change it, else leave it alone - List uiSlots = querySelectorAll(".box").toList(); - for (int i = 0; i < 10; i++) { - Slot newSlot = slots.elementAt(i); + //if the current inventory differs (count, type, metatdata) then clear it + //and change it, else leave it alone + List uiSlots = querySelectorAll(".box").toList(); + List slotUpdateFutures = []; + for (int i = 0; i < 10; i++) { + Slot newSlot = slots.elementAt(i); - bool updateNeeded = false, update = false; + bool updateNeeded = false, + update = false; - //if we've never received our inventory before, update all slots - if (currentSlots.length == 0) { - updateNeeded = true; - } else { - Slot currentSlot = currentSlots.elementAt(i); + //if we've never received our inventory before, update all slots + if (currentSlots.length == 0) { + updateNeeded = true; + } else { + Slot currentSlot = currentSlots.elementAt(i); - if (currentSlot.itemType != newSlot.itemType) { - updateNeeded = true; - } else if (currentSlot.count != newSlot.count) { - updateNeeded = true; - update = true; - } else if (currentSlot.item != null && newSlot.item != null && - !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { - transmit('updateMetadata',newSlot.item); - continue; - } - } + if (currentSlot.itemType != newSlot.itemType) { + updateNeeded = true; + } else if (currentSlot.count != newSlot.count) { + updateNeeded = true; + update = true; + } else if (currentSlot.item != null && newSlot.item != null && + !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { + transmit('updateMetadata', newSlot.item); + } + } - if (updateNeeded) { - if(newSlot.count == 0) { - uiSlots.elementAt(i).children.clear(); - } - uiSlots.elementAt(i).children.forEach((Element child) { - if(child.attributes.containsKey('count')) { - child.attributes['count'] = "0"; - } - }); - for (int j = 0; j < newSlot.count; j++) { - findNewSlot(newSlot,i,update:update); -// addItemToInventory(newSlot.item, i, update:update); - } - } - } + if (updateNeeded) { + if (newSlot.count == 0) { + uiSlots + .elementAt(i) + .children + .clear(); + } else { + uiSlots + .elementAt(i) + .children + .forEach((Element child) { + if (child.attributes.containsKey('count')) { + child.attributes['count'] = "0"; + } + }); + slotUpdateFutures.add(findNewSlot(newSlot, i, update: update)); + } + } + } - transmit("inventoryUpdated", true); + await Future.wait(slotUpdateFutures); + transmit("inventoryUpdated", true); } diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index 986013e3..19e7f972 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -3,125 +3,125 @@ part of couclient; ///slot should be in the format 'barSlot.bagSlot' indexed from 0 ///if bagSlot is not relevant, please use 'barSlot.-1' itemContextMenu(ItemDef i, String slot, MouseEvent event) { - event.preventDefault(); - event.stopPropagation(); - - int barSlot = int.parse(slot.split('.').elementAt(0)); - int bagSlot = int.parse(slot.split('.').elementAt(1)); - List actions = []; - - if (i.actions != null) { - List actionsList = i.actions; - bool enabled = false; - actionsList.forEach((Action action) { - String error = ""; - List requires = []; - action.itemRequirements.all.forEach((String item, int num) => requires.add({'num':num, 'of':[item]})); - if (action.itemRequirements.any.length > 0) { - requires.add({'num':1, 'of':action.itemRequirements.any}); - } - enabled = hasRequirements(requires); - if (enabled) { - error = action.description; - } else { - error = getRequirementString(requires); - } - actions.add([ - capitalizeFirstLetter(action.name) + '|' + - action.name + '|${action.timeRequired}|$enabled|$error', - i.itemType, - "sendAction ${action.name} ${i.item_id}", - getDropMap(1, barSlot, bagSlot) - ]); - }); - } - Element menu = RightClickMenu.create(event, i.name, i.description, actions, itemName: i.name); - document.body.append(menu); + event.preventDefault(); + event.stopPropagation(); + + int barSlot = int.parse(slot.split('.').elementAt(0)); + int bagSlot = int.parse(slot.split('.').elementAt(1)); + List actions = []; + + if (i.actions != null) { + List actionsList = i.actions; + bool enabled = false; + actionsList.forEach((Action action) { + String error = ""; + List requires = []; + action.itemRequirements.all.forEach((String item, int num) => requires.add({'num':num, 'of':[item]})); + if (action.itemRequirements.any.length > 0) { + requires.add({'num':1, 'of':action.itemRequirements.any}); + } + enabled = hasRequirements(requires); + if (enabled) { + error = action.description; + } else { + error = getRequirementString(requires); + } + actions.add([ + capitalizeFirstLetter(action.name) + '|' + + action.name + '|${action.timeRequired}|$enabled|$error', + i.itemType, + "sendAction ${action.name} ${i.item_id}", + getDropMap(1, barSlot, bagSlot) + ]); + }); + } + Element menu = RightClickMenu.create(event, i.name, i.description, actions, itemName: i.name); + document.body.append(menu); } -findNewSlot(Slot slot, int index, {bool update: false}) async { - ItemDef item = slot.item; - int count = slot.count; - ImageElement img; - img = new ImageElement(src: item.spriteUrl)..onLoad.listen((_) { - Element barSlot = view.inventory.children.elementAt(index); - barSlot.children.clear(); - String cssName = item.itemType.replaceAll(" ", "_"); - Element itemDiv = new DivElement(); - - //determine what we need to scale the sprite image to in order to fit - num scale = 1; - if (img.height > img.width / item.iconNum) { - scale = (barSlot.contentEdge.height - 10) / img.height; - } else { - scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); - } - - itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; - itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; - itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; - itemDiv.style.backgroundRepeat = 'no-repeat'; - itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; - itemDiv.style.backgroundPosition = "0 50%"; - itemDiv.style.margin = "auto"; - itemDiv.className = 'item-$cssName inventoryItem'; - - itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); - itemDiv.attributes['count'] = "1"; - itemDiv.attributes['itemMap'] = encode(item); - - String slotNum = '${barSlot.dataset["slot-num"]}.-1'; - itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); - barSlot.append(itemDiv); - - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - barSlot.append(itemCount); - if (count <= 1) { - itemCount.text = ""; - } - - int offset = count; - if (item.iconNum != null && item.iconNum < count) { - offset = item.iconNum; - } - itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; - - if (!update) { - itemDiv.classes.add("bounce"); - // remove the bounce class so that it's not still there for a drag and drop event - new Timer(new Duration(seconds: 1), () { - itemDiv.classes.remove("bounce"); - }); - } - - // Containers - DivElement containerButton; - String bagWindowId; - if (item.isContainer == true) { - containerButton = new DivElement() - ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) - ..onClick.listen((_) { - if (containerButton.classes.contains("item-container-closed")) { - // Container is closed, open it - // Open the bag window - bagWindowId = new BagWindow(int.parse(itemDiv.parent.dataset["slot-num"]), item).id; - // Update the slot display - BagWindow.updateTriggerBtn(false, itemDiv); - } else { - // Container is open, close it - // Close the bag window - BagWindow.closeId(bagWindowId); - // Update the slot display - BagWindow.updateTriggerBtn(true, itemDiv); - } - }); - itemDiv.parent.append(containerButton); - } - - transmit('inventoryUpdated'); - }); +Future findNewSlot(Slot slot, int index, {bool update: false}) async { + ItemDef item = slot.item; + int count = slot.count; + + //create an image for this item and wait for it to load before sizing/positioning it + ImageElement img = new ImageElement(src: item.spriteUrl); + await img.onLoad.first; + + Element barSlot = view.inventory.children.elementAt(index); + barSlot.children.clear(); + String cssName = item.itemType.replaceAll(" ", "_"); + Element itemDiv = new DivElement(); + + //determine what we need to scale the sprite image to in order to fit + num scale = 1; + if (img.height > img.width / item.iconNum) { + scale = (barSlot.contentEdge.height - 10) / img.height; + } else { + scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); + } + + itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; + itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; + itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; + itemDiv.style.backgroundRepeat = 'no-repeat'; + itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; + itemDiv.style.backgroundPosition = "0 50%"; + itemDiv.style.margin = "auto"; + itemDiv.className = 'item-$cssName inventoryItem'; + + itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); + itemDiv.attributes['count'] = "1"; + itemDiv.attributes['itemMap'] = encode(item); + + String slotNum = '${barSlot.dataset["slot-num"]}.-1'; + itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); + barSlot.append(itemDiv); + + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + barSlot.append(itemCount); + if (count <= 1) { + itemCount.text = ""; + } + + int offset = count; + if (item.iconNum != null && item.iconNum < count) { + offset = item.iconNum; + } + itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; + + if (!update) { + itemDiv.classes.add("bounce"); + // remove the bounce class so that it's not still there for a drag and drop event + new Timer(new Duration(seconds: 1), () { + itemDiv.classes.remove("bounce"); + }); + } + + // Containers + DivElement containerButton; + String bagWindowId; + if (item.isContainer == true) { + containerButton = new DivElement() + ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) + ..onClick.listen((_) { + if (containerButton.classes.contains("item-container-closed")) { + // Container is closed, open it + // Open the bag window + bagWindowId = new BagWindow(int.parse(itemDiv.parent.dataset["slot-num"]), item).id; + // Update the slot display + BagWindow.updateTriggerBtn(false, itemDiv); + } else { + // Container is open, close it + // Close the bag window + BagWindow.closeId(bagWindowId); + // Update the slot display + BagWindow.updateTriggerBtn(true, itemDiv); + } + }); + itemDiv.parent.append(containerButton); + } } //void putInInventory(ImageElement img, ItemDef i, int index, {bool update: false}) { @@ -180,35 +180,37 @@ findNewSlot(Slot slot, int index, {bool update: false}) async { //} void takeItemFromInventory(String itemType, {int count: 1}) { - String cssName = itemType.replaceAll(" ", "_"); - int remaining = count; - for (Element item in view.inventory.querySelectorAll(".item-$cssName")) { - if (remaining < 1) { - break; - } - - int uiCount = int.parse(item.attributes['count']); - if (uiCount > count) { - item.attributes['count'] = (uiCount - count).toString(); - item.parent.querySelector('.itemCount').text = (uiCount - count).toString(); - } else { - item.parent.children.clear(); - } - - remaining -= uiCount; - } - transmit("inventoryUpdated"); + String cssName = itemType.replaceAll(" ", "_"); + int remaining = count; + for (Element item in view.inventory.querySelectorAll(".item-$cssName")) { + if (remaining < 1) { + break; + } + + int uiCount = int.parse(item.attributes['count']); + if (uiCount > count) { + item.attributes['count'] = (uiCount - count).toString(); + item.parent + .querySelector('.itemCount') + .text = (uiCount - count).toString(); + } else { + item.parent.children.clear(); + } + + remaining -= uiCount; + } + transmit("inventoryUpdated"); } Map getDropMap(int count, int slotNum, int subSlotNum) { - Map dropMap = new Map() - ..['slot'] = slotNum - ..['subSlot'] = subSlotNum - ..['count'] = count - ..['x'] = CurrentPlayer.posX - ..['y'] = CurrentPlayer.posY + CurrentPlayer.height / 2 - ..['streetName'] = currentStreet.label - ..['tsid'] = currentStreet.streetData['tsid']; - - return dropMap; + Map dropMap = new Map() + ..['slot'] = slotNum + ..['subSlot'] = subSlotNum + ..['count'] = count + ..['x'] = CurrentPlayer.posX + ..['y'] = CurrentPlayer.posY + CurrentPlayer.height / 2 + ..['streetName'] = currentStreet.label + ..['tsid'] = currentStreet.streetData['tsid']; + + return dropMap; } diff --git a/pubspec.yaml b/pubspec.yaml index 4da76cae..94777d8a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: couclient description: 'Children of Ur :: Webclient' dependencies: browser: any - dnd: any + dnd: '^0.3.0' firebase: '>=0.5.0 <0.6.0' gorgon: any intl: any From 0619e5063366bf70a649c07527b5f113f653cb6f Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 2 Jan 2016 15:05:20 -0600 Subject: [PATCH 19/43] Minimap/chat tweaks --- lib/src/display/minimap.dart | 2 +- web/files/css/desktop/interface/chat.css | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/display/minimap.dart b/lib/src/display/minimap.dart index f2a50ded..ee75b105 100644 --- a/lib/src/display/minimap.dart +++ b/lib/src/display/minimap.dart @@ -29,7 +29,7 @@ class Minimap { street['loading_image']['h'] / currentStreet.bounds.height; num expandedHeight = street['main_image']['h'] / currentStreet.bounds.height; - if((collapsedHeight < expandedHeight) || mapData.getMinimapExpandOverride()) { + if((collapsedHeight < expandedHeight) || mapData.getMinimapExpandOverride(street["label"])) { // street is taller than it is wide // (or overridden) // allow expansion diff --git a/web/files/css/desktop/interface/chat.css b/web/files/css/desktop/interface/chat.css index 74eaeb0d..e287ca55 100644 --- a/web/files/css/desktop/interface/chat.css +++ b/web/files/css/desktop/interface/chat.css @@ -382,4 +382,8 @@ i.emoticon { background-repeat: no-repeat; background-position: center center; cursor: pointer; -} \ No newline at end of file +} + +.chat-member-change-event .location-chat-link::before { + display: none; +} From 587f0daf31e7ec68015289ec5bd0ac0036e66d03 Mon Sep 17 00:00:00 2001 From: klikini Date: Mon, 4 Jan 2016 18:18:06 -0600 Subject: [PATCH 20/43] Rename a method, fix the mute button, fix levels --- lib/src/display/chatmessage.dart | 6 +-- lib/src/display/overlays/imgmenu.dart | 5 ++- lib/src/display/overlays/levelup.dart | 16 ++++++-- lib/src/display/widgets/volumeslider.dart | 11 ++++-- lib/src/network/metabolics.dart | 47 ++++++++++++++++------- lib/src/network/metabolics_service.dart | 16 ++------ lib/src/network/streetservice.dart | 2 +- 7 files changed, 65 insertions(+), 38 deletions(-) diff --git a/lib/src/display/chatmessage.dart b/lib/src/display/chatmessage.dart index cd5f7d15..80de4a7e 100644 --- a/lib/src/display/chatmessage.dart +++ b/lib/src/display/chatmessage.dart @@ -82,7 +82,7 @@ class ChatMessage { } } else if (message == "LocationChangeEvent" && player == "invalid_user") { // Switching streets message - void setLceHtml() { + void setLocationChangeEventHtml() { String prefix = (metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) ? "Back" : "First time"); html = '

' @@ -92,9 +92,9 @@ class ChatMessage { if (!metabolics.load.isCompleted) { await metabolics.load.future; - setLceHtml(); + setLocationChangeEventHtml(); } else { - setLceHtml(); + setLocationChangeEventHtml(); } } else { // Normal message diff --git a/lib/src/display/overlays/imgmenu.dart b/lib/src/display/overlays/imgmenu.dart index 7c49af0f..8b693e9b 100644 --- a/lib/src/display/overlays/imgmenu.dart +++ b/lib/src/display/overlays/imgmenu.dart @@ -39,10 +39,11 @@ class ImgOverlay extends Overlay { // Set up level indicator bar ///////////////////////////////////////////////////////////// - if (metabolics.lifetime_img < int.parse(await(HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=60")))) { + int l_curr = await metabolics.level; + + if (l_curr < 60) { // Calculate level/img stats - int l_curr = await metabolics.level; int l_imgCurr = await metabolics.img_req_for_curr_lvl; int l_imgNext = await metabolics.img_req_for_next_lvl; diff --git a/lib/src/display/overlays/levelup.dart b/lib/src/display/overlays/levelup.dart index 35e4b197..51a95c8c 100644 --- a/lib/src/display/overlays/levelup.dart +++ b/lib/src/display/overlays/levelup.dart @@ -6,15 +6,23 @@ class LevelUpOverlay extends Overlay { dropper = querySelector("#lu-dropper"); } - open() { - metabolics.level.then((int lvl) { - dropper.text = lvl.toString(); + open([int newLevel]) { + void display(int level) { + dropper.text = level.toString(); displayElement.hidden = false; audio.playSound('levelUp'); inputManager.ignoreKeys = true; displayElement.querySelector("#lu-button").onClick.first.then((_) => close()); transmit("worldFocus", false); - }); + } + + if (newLevel == null) { + metabolics.level.then((int level) { + display(level); + }); + } else { + display(newLevel); + } } close() { diff --git a/lib/src/display/widgets/volumeslider.dart b/lib/src/display/widgets/volumeslider.dart index a2b3070b..a8d120fd 100644 --- a/lib/src/display/widgets/volumeslider.dart +++ b/lib/src/display/widgets/volumeslider.dart @@ -3,8 +3,9 @@ part of couclient; class VolumeSliderWidget { bool muted = false; - Element volumeGlyph = querySelector('#volumeGlyph')..click()..click(); // HACK: toggling fixes mute issues + Element volumeGlyph = querySelector('#volumeGlyph'); Element volumeIcon = querySelector('#volumeGlyph > i'); + bool doToasts = false; VolumeSliderWidget() { @@ -22,10 +23,14 @@ class VolumeSliderWidget { if(muted == true) { muted = false; - toast("Sound unmuted"); + if (doToasts) { + toast("Sound unmuted"); + } } else { muted = true; - toast("Sound muted"); + if (doToasts) { + toast("Sound muted"); + } } update(); diff --git a/lib/src/network/metabolics.dart b/lib/src/network/metabolics.dart index 70637e3f..3a4bcd5f 100644 --- a/lib/src/network/metabolics.dart +++ b/lib/src/network/metabolics.dart @@ -1,17 +1,38 @@ library metabolics; class Metabolics { - int id, mood = 50, max_mood = 100, energy = 50, max_energy = 100; - int currants = 0, img = 0, lifetime_img = 0, user_id = -1; - int alphfavor = 0, cosmafavor = 0, friendlyfavor = 0, grendalinefavor = 0; - int humbabafavor = 0, lemfavor = 0, mabfavor = 0, potfavor = 0, sprigganfavor = 0; - int tiifavor = 0, zillefavor = 0; - int alphfavor_max = 0, cosmafavor_max = 0, friendlyfavor_max = 0, grendalinefavor_max = 0; - int humbabafavor_max = 0, lemfavor_max = 0, mabfavor_max = 0, potfavor_max = 0, sprigganfavor_max = 0; - int tiifavor_max = 0, zillefavor_max = 0; - num current_street_x = 1.0, current_street_y = 0.0; - String current_street = 'LA58KK7B9O522PC'; - String location_history = '[]'; - int quoins_collected = 0; - num quoin_multiplier = 1; + int alphfavor = 0, + alphfavor_max = 0, + cosmafavor = 0, + cosmafavor_max = 0, + currants = 0, + energy = 50, + friendlyfavor = 0, + friendlyfavor_max = 0, + grendalinefavor = 0, + grendalinefavor_max = 0, + humbabafavor = 0, + humbabafavor_max = 0, + id = -1, + img = 0, + lemfavor = 0, + lemfavor_max = 0, + lifetime_img = 0, + mabfavor = 0, + mabfavor_max = 0, + max_energy = 100, + max_mood = 100, + mood = 50, + potfavor = 0, + potfavor_max = 0, + quoins_collected = 0, + sprigganfavor = 0, + sprigganfavor_max = 0, + tiifavor = 0, + tiifavor_max = 0, + user_id = -1, + zillefavor = 0, + zillefavor_max = 0; + num current_street_x = 1.0, current_street_y = 0.0, quoin_multiplier = 1; + String current_street = 'LA58KK7B9O522PC', location_history = '[]'; } diff --git a/lib/src/network/metabolics_service.dart b/lib/src/network/metabolics_service.dart index 784f3d3e..f3e39165 100644 --- a/lib/src/network/metabolics_service.dart +++ b/lib/src/network/metabolics_service.dart @@ -32,22 +32,14 @@ class MetabolicsService { Map map = JSON.decode(event.data); if (map['collectQuoin'] != null && map['collectQuoin'] == "true") { collectQuoin(map); + } else if (map["levelUp"] != null) { + levelUp.open(map["levelUp"]); } else { - int oldImg = lifetime_img; - int oldLevel = await level; playerMetabolics = decode(event.data, type:Metabolics); if (!load.isCompleted) { load.complete(); transmit("metabolicsLoaded", playerMetabolics); } - int newImg = lifetime_img; - int newLvl; - if (newImg > oldImg) { - newLvl = await level; - if (oldLevel > newLvl - 2 && newLvl > oldLevel && webSocketMessages > 0) { - levelUp.open(); - } - } transmit('metabolicsUpdated', playerMetabolics); } update(); @@ -153,7 +145,7 @@ class MetabolicsService { List get location_history => JSON.decode(playerMetabolics.location_history); Future get level async { - String lvlStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getLevel?img=${img.toString()}"); + String lvlStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getLevel?img=${lifetime_img.toString()}"); return int.parse(lvlStr); } @@ -166,4 +158,4 @@ class MetabolicsService { String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); return int.parse(imgStr); } -} +} \ No newline at end of file diff --git a/lib/src/network/streetservice.dart b/lib/src/network/streetservice.dart index ceb491a1..0fdd91da 100644 --- a/lib/src/network/streetservice.dart +++ b/lib/src/network/streetservice.dart @@ -104,7 +104,7 @@ class StreetService { await street.load(); logmessage('[StreetService] Street assembled.'); - // notify minimap to update + // notify displays to update transmit('streetLoaded', streetAsMap); } } \ No newline at end of file From 67614eac857475b35cac41c0385cc69c0c4daea5 Mon Sep 17 00:00:00 2001 From: klikini Date: Mon, 4 Jan 2016 18:28:20 -0600 Subject: [PATCH 21/43] fix audio again, and maybe letters --- lib/src/game/game.dart | 41 ++++--------------- lib/src/network/server_interop/so_player.dart | 25 +++++++++++ 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/lib/src/game/game.dart b/lib/src/game/game.dart index 0f7fb456..eccb3495 100644 --- a/lib/src/game/game.dart +++ b/lib/src/game/game.dart @@ -70,6 +70,12 @@ class Game { CurrentPlayer.currentAnimation = CurrentPlayer.animations['idle']; transmit('playerLoaded', null); + // HACK: toggling fixes mute issues + view.slider + ..volumeGlyph.click() + ..volumeGlyph.click() + ..doToasts = true; + //stop loading sounds and load the street's song if(audio.loadingSound != null) { audio.loadingSound.stop(); @@ -91,11 +97,6 @@ class Game { transmit("gameLoaded", loaded); logmessage("Game loaded!"); - // Update player letters every second - // (Don't worry, it checks to make sure the current street has letters - // before sending the request to the server, and adjusts accordingly) - new Timer.periodic(new Duration(seconds: 1), (_) => updatePlayerLetters()); - // Tell the server when we have changed streets, and to assign us a new letter new Service(["streetLoaded", "gameUnloading"], (_) { if (currentStreet.useLetters) { @@ -151,32 +152,4 @@ class Game { //previousTag.makeCurrent(); } - - Future updatePlayerLetters() async { - Map players = new Map() - ..addAll(otherPlayers) - ..addAll(({game.username: CurrentPlayer})); - - players.forEach((String username, Player player) async { - Element parentE = player.playerParentElement; - - String username = parentE.id.replaceFirst("player-", ""); - if (username.startsWith("pc-")) { - username = username.replaceFirst("pc-", ""); - } - - if (currentStreet.useLetters) { - String letter = await HttpRequest.getString("http://${Configs.utilServerAddress}/letters/getPlayerLetter?username=$username"); - - DivElement letterDisplay = new DivElement() - ..classes.addAll(["letter", "letter-$letter"]); - - parentE - ..children.removeWhere((Element e) => e.classes.contains("letter")) - ..append(letterDisplay); - } else { - parentE.children.removeWhere((Element e) => e.classes.contains("letter")); - } - }); - } -} +} \ No newline at end of file diff --git a/lib/src/network/server_interop/so_player.dart b/lib/src/network/server_interop/so_player.dart index 1b504d7b..1813da32 100644 --- a/lib/src/network/server_interop/so_player.dart +++ b/lib/src/network/server_interop/so_player.dart @@ -45,6 +45,9 @@ _setupPlayerSocket() { } if (map['username'] == game.username) { + if (map["letter"] != null) { + attachPlayerLetter(map["letter"], CurrentPlayer); + } return; } @@ -168,6 +171,8 @@ updateOtherPlayer(Map map, Player otherPlayer) { facingRight = true; } otherPlayer.facingRight = facingRight; + + attachPlayerLetter(map["letter"], otherPlayer); } void removeOtherPlayer(String username) { @@ -177,4 +182,24 @@ void removeOtherPlayer(String username) { Element otherPlayer = querySelector("#player-" + sanitizeName(username.replaceAll(' ', '_'))); if (otherPlayer != null) otherPlayer.remove(); +} + +void attachPlayerLetter(String letter, Player player) { + if (letter == null || player == null) { + return; + } + + // Letters + if (currentStreet.useLetters) { + // Add + DivElement letterDisplay = new DivElement() + ..classes.addAll(["letter", "letter-$letter"]); + + player.playerParentElement + ..children.removeWhere((Element e) => e.classes.contains("letter")) + ..append(letterDisplay); + } else { + // Remove + player.playerParentElement.children.removeWhere((Element e) => e.classes.contains("letter")); + } } \ No newline at end of file From 91a7c6c9ccca46be8c3a8b8ca9c314375ebb3b6c Mon Sep 17 00:00:00 2001 From: klikini Date: Thu, 7 Jan 2016 18:51:06 -0600 Subject: [PATCH 22/43] fix sale price checking: https://github.com/ChildrenOfUr/cou-issues/issues/381 --- lib/src/display/meters.dart | 2 ++ lib/src/display/windows/vendor_window.dart | 27 ++++++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/src/display/meters.dart b/lib/src/display/meters.dart index f649ef2c..a2f1c67d 100644 --- a/lib/src/display/meters.dart +++ b/lib/src/display/meters.dart @@ -3,6 +3,7 @@ part of couclient; class Meters { Element meter = querySelector('ur-meters'); Element currantElement = querySelector('#currCurrants'); + Element currantLabel = querySelector("#currantLabel"); updateImgDisplay() { meter.attributes['imagination'] = metabolics.img.toString(); @@ -20,6 +21,7 @@ class Meters { updateCurrantsDisplay() { currantElement.text = commaFormatter.format(metabolics.currants); + currantLabel.text = (metabolics.currants != 1 ? "Currants" : "Currant"); } updateNameDisplay() { diff --git a/lib/src/display/windows/vendor_window.dart b/lib/src/display/windows/vendor_window.dart index b9168ba4..5d8fd167 100644 --- a/lib/src/display/windows/vendor_window.dart +++ b/lib/src/display/windows/vendor_window.dart @@ -63,7 +63,7 @@ class VendorWindow extends Modal { windowTitle = windowTitle.substring(15) + " Vendor"; } header.innerHtml = '' + windowTitle; - currants.text = " ${commaFormatter.format(metabolics.currants)} currants"; + currants.text = " ${commaFormatter.format(metabolics.currants)} currant${(metabolics.currants != 1 ? "s" : "")}"; new List.from(buy.children) ..forEach((child) => child.remove()); @@ -77,26 +77,29 @@ class VendorWindow extends Modal { Element price; if (item["discount"] != 1) { + // Item on sale, see price by clicking price = merch.append( new DivElement() ..text = "Sale!" ..classes.addAll(["price-tag", "sale-price-tag"]) ); - } else if (item['price'] < 9999) { + } else if (item['price'] >= 9999) { + // Really expensive item price = merch.append( - new DivElement() - ..text = '${item['price']}â‚¡' - ..className = 'price-tag' + new DivElement() + ..text = 'A Lot' + ..className = 'price-tag' ); } else { + // Normal item price = merch.append( - new DivElement() - ..text = 'A Lot' - ..className = 'price-tag' + new DivElement() + ..text = '${item['price']}â‚¡' + ..className = 'price-tag' ); } - if(item['price'] > metabolics.currants) { + if(item['price'] * item["discount"] > metabolics.currants) { price.classes.add("cantAfford"); } @@ -187,15 +190,15 @@ class VendorWindow extends Modal { sendAction("sellItem", vendorId, actionMap); } else { - if(metabolics.currants < item['price'] * numToBuy) { + if(metabolics.currants < item["discount"] * item["price"] * numToBuy) { return; } - newValue = metabolics.currants - item['price'] * numToBuy; + newValue = metabolics.currants - (item['price'] * item["discount"] * numToBuy).toInt(); sendAction("buyItem", vendorId, actionMap); } - currants.text = " ${commaFormatter.format(newValue)} currants"; + currants.text = " ${commaFormatter.format(newValue)} currant${(newValue != 1 ? "s" : "")}"; backToBuy.click(); }); From 2c52a6648799dbaaf9a2b7a0792a0593b9f02f65 Mon Sep 17 00:00:00 2001 From: klikini Date: Thu, 7 Jan 2016 19:04:38 -0600 Subject: [PATCH 23/43] entity menu selection color: https://github.com/ChildrenOfUr/cou-issues/issues/378 --- web/files/css/desktop/interactions.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/files/css/desktop/interactions.css b/web/files/css/desktop/interactions.css index 4ddb4de8..f5e98974 100644 --- a/web/files/css/desktop/interactions.css +++ b/web/files/css/desktop/interactions.css @@ -572,7 +572,7 @@ .entityContainer:hover, .entitySelected { border: 1px solid orange; - background-color: #F8F2E1; + background-color: #F8DA9E; color: orange; } From d6f9f549c77fcaad2f88bb1903ea898351c77f9a Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Fri, 8 Jan 2016 20:26:51 -0500 Subject: [PATCH 24/43] bag ui will be correct after you receive an item while the bag is closed. it will also not reset to the previous state when you open/close it --- lib/src/display/inv_dragging.dart | 230 ++++---- lib/src/display/windows/bag_window.dart | 492 +++++++++--------- lib/src/network/metabolics_service.dart | 25 +- lib/src/network/server_interop/inventory.dart | 209 ++++---- lib/src/network/server_interop/so_item.dart | 347 ++++++------ .../server_interop/so_multiplayer.dart | 89 +--- lib/src/systems/audio.dart | 12 +- 7 files changed, 670 insertions(+), 734 deletions(-) diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index d5d48ef9..70970112 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -1,125 +1,119 @@ part of couclient; class InvDragging { - /// Track inventory updating - static Service _refresh; - - /// Draggable items - static Draggable _draggables; - - /// Drop targets - static Dropzone _dropzones; - - /// State tracking - static Element _origBox; - - /** - * Map used to store *how* the item was moved. - * Keys: - * - General: - * - - item_number: element: span element containing the count label - * - - bag_btn: element: the toggle button for containers - * - On Pickup: - * - - fromBag: boolean: whether the item used to be in a bag - * - - fromBagIndex: int: which slot the toBag is in (only set if fromBag is true) - * - - fromIndex: int: which slot the item used to be in - * - On Drop: - * - - toBag: boolean: whether the item is going into a bag - * - - toBagIndex: int: which slot the toBag is in (only set if toBag is true) - * - - toIndex: int: which slot the item is going to - */ - static Map _move = {}; - - /// Checks if the specified slot is empty - static bool slotIsEmpty({int index, Element box, int bagWindow}) { - if (index != null) { - if (bagWindow == null) { - box = querySelectorAll("#inventory .box").toList()[index]; - } else { - box = querySelectorAll("#bagWindow$bagWindow").toList()[index]; - } - } - - return (box.children.length == 0); - } - - /// Set up event listeners based on the current inventory - static void init() { - if (_refresh == null) { - _refresh = new Service(["inventoryUpdated"], (_) => init()); - } - // Remove old data - if (_draggables != null) { - _draggables.destroy(); - } - if (_dropzones != null) { - _dropzones.destroy(); - } - - // Set up draggable elements - _draggables = new Draggable( - // List of item elements in boxes - querySelectorAll('.inventoryItem'), - // Display the item on the cursor - avatarHandler: new CustomAvatarHandler(), - // Allow free dragging. - horizontalOnly: false, - // Disable item interaction while dragging it - draggingClass: "item-flying" - ) - ..onDragStart.listen((DraggableEvent e) => handlePickup(e)); - - print('setup draggables'); - - // Set up acceptor slots - _dropzones = new Dropzone(querySelectorAll("#inventory .box")) - ..onDrop.listen((DropzoneEvent e) => handleDrop(e)); - - print('setup dropzones'); - } - - /// Runs when an item is picked up (drag start) - static void handlePickup(DraggableEvent e) { - _origBox = e.draggableElement.parent; - e.draggableElement.dataset["original-slot-num"] = _origBox.dataset["slot-num"]; - - _move = {}; - - if (querySelector("#windowHolder").contains(_origBox)) { - _move['fromIndex'] = int.parse(_origBox.parent.parent.dataset["source-bag"]); - _move["fromBagIndex"] = int.parse(_origBox.dataset["slot-num"]); - } else { - _move['fromIndex'] = int.parse(_origBox.dataset["slot-num"]); - } - } - - /// Runs when an item is dropped (drop) - static void handleDrop(DropzoneEvent e) { - if (querySelector("#windowHolder").contains(e.dropzoneElement)) { - _move["toIndex"] = int.parse(e.dropzoneElement.parent.parent.dataset["source-bag"]); - _move["toBagIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); - } else { - _move["toIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); - } - - sendAction("moveItem", "global_action_monster", _move); - } + /// Track inventory updating + static Service _refresh; + + /// Draggable items + static Draggable _draggables; + + /// Drop targets + static Dropzone _dropzones; + + /// State tracking + static Element _origBox; + + /** + * Map used to store *how* the item was moved. + * Keys: + * - General: + * - - item_number: element: span element containing the count label + * - - bag_btn: element: the toggle button for containers + * - On Pickup: + * - - fromBag: boolean: whether the item used to be in a bag + * - - fromBagIndex: int: which slot the toBag is in (only set if fromBag is true) + * - - fromIndex: int: which slot the item used to be in + * - On Drop: + * - - toBag: boolean: whether the item is going into a bag + * - - toBagIndex: int: which slot the toBag is in (only set if toBag is true) + * - - toIndex: int: which slot the item is going to + */ + static Map _move = {}; + + /// Checks if the specified slot is empty + static bool slotIsEmpty({int index, Element box, int bagWindow}) { + if (index != null) { + if (bagWindow == null) { + box = querySelectorAll("#inventory .box").toList()[index]; + } else { + box = querySelectorAll("#bagWindow$bagWindow").toList()[index]; + } + } + + return (box.children.length == 0); + } + + /// Set up event listeners based on the current inventory + static void init() { + if (_refresh == null) { + _refresh = new Service(["inventoryUpdated"], (_) => init()); + } + // Remove old data + if (_draggables != null) { + _draggables.destroy(); + } + if (_dropzones != null) { + _dropzones.destroy(); + } + + // Set up draggable elements + _draggables = new Draggable( + // List of item elements in boxes + querySelectorAll('.inventoryItem'), + // Display the item on the cursor + avatarHandler: new CustomAvatarHandler(), + // Disable item interaction while dragging it + draggingClass: "item-flying" + ) + ..onDragStart.listen((DraggableEvent e) => handlePickup(e)); + + // Set up acceptor slots + _dropzones = new Dropzone(querySelectorAll("#inventory .box")) + ..onDrop.listen((DropzoneEvent e) => handleDrop(e)); + } + + /// Runs when an item is picked up (drag start) + static void handlePickup(DraggableEvent e) { + _origBox = e.draggableElement.parent; + e.draggableElement.dataset["original-slot-num"] = _origBox.dataset["slot-num"]; + + _move = {}; + + if (querySelector("#windowHolder").contains(_origBox)) { + _move['fromIndex'] = int.parse(_origBox.parent.parent.dataset["source-bag"]); + _move["fromBagIndex"] = int.parse(_origBox.dataset["slot-num"]); + } else { + _move['fromIndex'] = int.parse(_origBox.dataset["slot-num"]); + } + } + + /// Runs when an item is dropped (drop) + static void handleDrop(DropzoneEvent e) { + if (querySelector("#windowHolder").contains(e.dropzoneElement)) { + _move["toIndex"] = int.parse(e.dropzoneElement.parent.parent.dataset["source-bag"]); + _move["toBagIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); + } else { + _move["toIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); + } + + sendAction("moveItem", "global_action_monster", _move); + } } class BagFilterAcceptor extends Acceptor { - BagFilterAcceptor(this.allowedItemTypes); - - List allowedItemTypes; - - @override - bool accepts(Element itemE, int draggable_id, Element box) { - ItemDef item = decode(itemE.attributes['itemmap'], type: ItemDef); - if (allowedItemTypes.length == 0) { - // Those that accept nothing learn to accept everything (except other containers) - return !item.isContainer; - } else { - // Those that are at least somewhat accepting are fine, though - return allowedItemTypes.contains(item.itemType); - } - } + BagFilterAcceptor(this.allowedItemTypes); + + List allowedItemTypes; + + @override + bool accepts(Element itemE, int draggable_id, Element box) { + ItemDef item = decode(itemE.attributes['itemmap'], type: ItemDef); + if (allowedItemTypes.length == 0) { + // Those that accept nothing learn to accept everything (except other containers) + return !item.isContainer; + } else { + // Those that are at least somewhat accepting are fine, though + return allowedItemTypes.contains(item.itemType); + } + } } \ No newline at end of file diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index 396a5f79..40ca13be 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -2,240 +2,260 @@ part of couclient; class BagWindow extends Modal { - static List openWindows = []; - - static closeId(String id) { - openWindows - .where((BagWindow w) => w.id == id) - .first - .close(); - openWindows.removeWhere((BagWindow w) => w.id == id); - } - - static bool get isOpen { - return (querySelectorAll("#windowHolder > .bagWindow").length > 0); - } - - String id = 'bagWindow' + WindowManager.randomId.toString(); - String bagId; - int numSlots; - int sourceSlotNum; - - Dropzone acceptors; - - BagWindow(this.sourceSlotNum, ItemDef sourceItem) { - load(sourceItem).then((DivElement windowElement) { - // Handle drag and drop - new Service(["inventoryUpdated", 'metadataUpdated'], (_) { - if (acceptors != null) { - acceptors.destroy(); - } - acceptors = new Dropzone( - windowElement.querySelectorAll(".bagwindow-box"), - acceptor: new BagFilterAcceptor(sourceItem.subSlotFilter) - ) - ..onDrop.listen((DropzoneEvent e) => InvDragging.handleDrop(e)); - - windowElement.querySelectorAll('.box').forEach((Element e) { - new Draggable(e.children[0], avatarHandler: new CustomAvatarHandler(), - draggingClass: 'item-flying') - ..onDragStart.listen((DraggableEvent e) => InvDragging.handlePickup(e)); - }); - }); - - new Service(['updateMetadata'], (sourceItem) async { - Element newWell = await load(sourceItem, false); - windowElement.querySelector("ur-well").replaceWith(newWell); - print('replaced with new well'); - transmit('metadataUpdated', true); - }); - - querySelector("#windowHolder").append(windowElement); - prepare(); - open(); - }); - } - - Future load(ItemDef sourceItem, [bool full = true]) async { - // Header - - Element closeButton, icon, header; - SpanElement titleSpan; - - if (full) { - closeButton = new Element.tag("i") - ..classes.add("fa-li") - ..classes.add("fa") - ..classes.add("fa-times") - ..classes.add("close"); - - icon = new ImageElement() - ..classes.add("fa-li") - ..src = "files/system/icons/bag.svg"; - - titleSpan = new SpanElement() - ..classes.add("iw-title") - ..text = sourceItem.name; - - if (sourceItem.name.length >= 24) { - titleSpan.style.fontSize = "24px"; - } - - header = new Element.header() - ..append(icon)..append(titleSpan); - } - - // Content - - Element well = new Element.tag("ur-well"); - - int numSlots = sourceItem.subSlots; - List subSlots; - - if (sourceItem.metadata["slots"] == null) { - // Empty bag - subSlots = []; - while (subSlots.length < numSlots) { - subSlots.add(({ - "itemType": "", - "count": 0, - "metadata": {} - })); - } - } else { - // Bag has contents - subSlots = sourceItem.metadata["slots"]; - } - - if (subSlots.length != sourceItem.subSlots) { - throw new StateError("Number of slots in bag does not match bag size"); - } else { - int slotNum = 0; - await Future.forEach(subSlots, (Map bagSlot) async { - DivElement slot = new DivElement(); - // Slot - slot - ..classes.addAll(["box", "bagwindow-box"]) - ..dataset["slot-num"] = slotNum.toString(); - well.append(slot); - document.body.append(well); //for measuring - // Item - DivElement itemInSlot = new DivElement(); - slot.append(itemInSlot); - if (!bagSlot["itemType"].isEmpty) { - ItemDef item = decode(JSON.encode(bagSlot['item']), type: ItemDef); - await _sizeItem(slot, itemInSlot, item, bagSlot['count'], slotNum); - } - well.remove(); - - slotNum++; - }); - } - - // Window - - if (full) { - DivElement window = new DivElement() - ..id = id - ..classes.add("window") - ..classes.add("bagWindow") - ..append(header)..append(closeButton)..append(well) - ..dataset["source-bag"] = sourceSlotNum.toString(); - - return window; - } else { - return well; - } - } - - Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { - ImageElement img; - img = new ImageElement(src: i.spriteUrl) - ..onLoad.listen((_) { - num scale = 1; - if (img.height > img.width / i.iconNum) { - scale = (slot.contentEdge.height - 10) / img.height; - } else { - scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); - } - - item - ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) - ..attributes["name"] = i.name - ..attributes["count"] = count.toString() - ..attributes["itemmap"] = encode(i) - ..style.width = (slot.contentEdge.width - 10).toString() + "px" - ..style.height = (slot.contentEdge.height - 10).toString() + "px" - ..style.backgroundImage = 'url(${i.spriteUrl})' - ..style.backgroundRepeat = 'no-repeat' - ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" - ..style.margin = "auto"; - - int offset = count; - if (i.iconNum != null && i.iconNum < count) { - offset = i.iconNum; - } - - item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; - - String slotString = '$sourceSlotNum.$bagSlotIndex'; - item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i, slotString, event)); - if (count > 1) { - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - item.parent.append(itemCount); - } else if (item.parent.querySelector(".itemCount") != null) { - item.parent - .querySelector(".itemCount") - .text = ""; - } - }); - } - - @override - open() { - super.open(); - openWindows.add(this); - - transmit('inventoryUpdated', true); - } - - @override - close() { - super.close(); - - // Delete the window - Element window = querySelector("#$id"); - if (window != null) { - window.remove(); - } - - // Update the source inventory icon - Element sourceBox = view.inventory.children - .where((Element box) => box.dataset["slot-num"] == sourceSlotNum.toString()) - .first; - sourceBox.querySelector(".item-container-toggle").click(); - - transmit('inventoryUpdated', true); - } - - // Update the inventory icons (used by the inventory) - - static updateTriggerBtn(bool open, Element item) { - Element btn = item.parent.querySelector(".item-container-toggle"); - if (!open) { - // Closed, opening the bag - btn.classes - ..remove("item-container-closed")..remove("fa-plus") - ..add("item-container-open")..add("fa-times"); - item.classes.add("inv-item-disabled"); - } else { - // Opened, closing the bag - btn.classes - ..remove("item-container-open")..remove("fa-times") - ..add("item-container-closed")..add("fa-plus"); - item.classes.remove("inv-item-disabled"); - } - } + static List openWindows = []; + static List bagWindows = []; + + static closeId(String id) { + openWindows + .where((BagWindow w) => w.id == id) + .first + .close(); + openWindows.removeWhere((BagWindow w) => w.id == id); + } + + static bool get isOpen { + return (querySelectorAll("#windowHolder > .bagWindow").length > 0); + } + + String id, + bagId; + int numSlots, sourceSlotNum; + //when set to true, the ui inside the bag will be updated when the bag is nex opened + bool dataUpdated = false; + ItemDef sourceItem; + + Dropzone acceptors; + + factory BagWindow(int sourceSlotNum, ItemDef sourceItem, {String id : null}) { + if (id == null) { + return new BagWindow._(sourceSlotNum, sourceItem); + } else { + for(BagWindow w in bagWindows) { + if (w.id == id) { + w.open(); + return w; + } + } + return new BagWindow._(sourceSlotNum, sourceItem); + } + } + + BagWindow._(this.sourceSlotNum, this.sourceItem) { + id = 'bagWindow' + WindowManager.randomId.toString(); + bagWindows.add(this); + + //load the ui of the window and open it when ready + load(sourceItem).then((DivElement windowElement) { + displayElement = windowElement; + // Handle drag and drop + new Service(["inventoryUpdated", 'metadataUpdated'], (_) { + if (acceptors != null) { + acceptors.destroy(); + } + acceptors = new Dropzone( + displayElement.querySelectorAll(".bagwindow-box"), + acceptor: new BagFilterAcceptor(sourceItem.subSlotFilter) + ) + ..onDrop.listen((DropzoneEvent e) => InvDragging.handleDrop(e)); + + displayElement.querySelectorAll('.box').forEach((Element e) { + new Draggable(e.children[0], avatarHandler: new CustomAvatarHandler(), + draggingClass: 'item-flying') + ..onDragStart.listen((DraggableEvent e) => InvDragging.handlePickup(e)); + }); + }); + + new Service(['updateMetadata'], (sourceItem) async { + this.sourceItem = sourceItem; + if(displayElement.hidden) { + dataUpdated = true; + } + }); + + querySelector("#windowHolder").append(displayElement); + prepare(); + open(); + }); + } + + Future updateWell(ItemDef sourceItem) async { + Element newWell = await load(sourceItem, false); + displayElement.querySelector("ur-well").replaceWith(newWell); + transmit('metadataUpdated', true); + } + + Future load(ItemDef sourceItem, [bool full = true]) async { + // Header + + Element closeButton, icon, header; + SpanElement titleSpan; + + if (full) { + closeButton = new Element.tag("i") + ..classes.add("fa-li") + ..classes.add("fa") + ..classes.add("fa-times") + ..classes.add("close"); + + icon = new ImageElement() + ..classes.add("fa-li") + ..src = "files/system/icons/bag.svg"; + + titleSpan = new SpanElement() + ..classes.add("iw-title") + ..text = sourceItem.name; + + if (sourceItem.name.length >= 24) { + titleSpan.style.fontSize = "24px"; + } + + header = new Element.header() + ..append(icon)..append(titleSpan); + } + + // Content + + Element well = new Element.tag("ur-well"); + + int numSlots = sourceItem.subSlots; + List subSlots; + + if (sourceItem.metadata["slots"] == null) { + // Empty bag + subSlots = []; + while (subSlots.length < numSlots) { + subSlots.add(({ + "itemType": "", + "count": 0, + "metadata": {} + })); + } + } else { + // Bag has contents + subSlots = sourceItem.metadata["slots"]; + } + + if (subSlots.length != sourceItem.subSlots) { + throw new StateError("Number of slots in bag does not match bag size"); + } else { + int slotNum = 0; + document.body.append(well); //for measuring + await Future.forEach(subSlots, (Map bagSlot) async { + DivElement slot = new DivElement(); + // Slot + slot + ..classes.addAll(["box", "bagwindow-box"]) + ..dataset["slot-num"] = slotNum.toString(); + well.append(slot); + // Item + DivElement itemInSlot = new DivElement(); + slot.append(itemInSlot); + if (!bagSlot["itemType"].isEmpty) { + ItemDef item = decode(JSON.encode(bagSlot['item']), type: ItemDef); + await _sizeItem(slot, itemInSlot, item, bagSlot['count'], slotNum); + } + + slotNum++; + }); + well.remove(); + } + + // Window + + if (full) { + DivElement window = new DivElement() + ..id = id + ..classes.add("window") + ..classes.add("bagWindow") + ..append(header)..append(closeButton)..append(well) + ..dataset["source-bag"] = sourceSlotNum.toString(); + + return window; + } else { + return well; + } + } + + Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { + ImageElement img; + img = new ImageElement(src: i.spriteUrl) + ..onLoad.listen((_) { + num scale = 1; + if (img.height > img.width / i.iconNum) { + scale = (slot.contentEdge.height - 10) / img.height; + } else { + scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); + } + + item + ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) + ..attributes["name"] = i.name + ..attributes["count"] = count.toString() + ..attributes["itemmap"] = encode(i) + ..style.width = (slot.contentEdge.width - 10).toString() + "px" + ..style.height = (slot.contentEdge.height - 10).toString() + "px" + ..style.backgroundImage = 'url(${i.spriteUrl})' + ..style.backgroundRepeat = 'no-repeat' + ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" + ..style.margin = "auto"; + + int offset = count; + if (i.iconNum != null && i.iconNum < count) { + offset = i.iconNum; + } + + item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; + + String slotString = '$sourceSlotNum.$bagSlotIndex'; + item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i, slotString, event)); + if (count > 1) { + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + item.parent.append(itemCount); + } else if (item.parent.querySelector(".itemCount") != null) { + item.parent + .querySelector(".itemCount") + .text = ""; + } + }); + } + + @override + open() { + super.open(); + openWindows.add(this); + + updateWell(sourceItem); + } + + @override + close() { + super.close(); + + // Update the source inventory icon + Element sourceBox = view.inventory.children + .where((Element box) => box.dataset["slot-num"] == sourceSlotNum.toString()) + .first; + sourceBox.querySelector(".item-container-toggle").click(); + } + + // Update the inventory icons (used by the inventory) + + static updateTriggerBtn(bool open, Element item) { + Element btn = item.parent.querySelector(".item-container-toggle"); + if (!open) { + // Closed, opening the bag + btn.classes + ..remove("item-container-closed")..remove("fa-plus") + ..add("item-container-open")..add("fa-times"); + item.classes.add("inv-item-disabled"); + } else { + // Opened, closing the bag + btn.classes + ..remove("item-container-open")..remove("fa-times") + ..add("item-container-closed")..add("fa-plus"); + item.classes.remove("inv-item-disabled"); + } + } } diff --git a/lib/src/network/metabolics_service.dart b/lib/src/network/metabolics_service.dart index f3e39165..dae189fd 100644 --- a/lib/src/network/metabolics_service.dart +++ b/lib/src/network/metabolics_service.dart @@ -32,14 +32,22 @@ class MetabolicsService { Map map = JSON.decode(event.data); if (map['collectQuoin'] != null && map['collectQuoin'] == "true") { collectQuoin(map); - } else if (map["levelUp"] != null) { - levelUp.open(map["levelUp"]); } else { - playerMetabolics = decode(event.data, type:Metabolics); + int oldImg = lifetime_img; + int oldLevel = await level; + playerMetabolics = decode(event.data, type: Metabolics); if (!load.isCompleted) { load.complete(); transmit("metabolicsLoaded", playerMetabolics); } + int newImg = lifetime_img; + int newLvl; + if (newImg > oldImg) { + newLvl = await level; + if (oldLevel > newLvl - 2 && newLvl > oldLevel && webSocketMessages > 0) { + levelUp.open(); + } + } transmit('metabolicsUpdated', playerMetabolics); } update(); @@ -145,17 +153,20 @@ class MetabolicsService { List get location_history => JSON.decode(playerMetabolics.location_history); Future get level async { - String lvlStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getLevel?img=${lifetime_img.toString()}"); + String lvlStr = await HttpRequest.getString( + "http://${Configs.utilServerAddress}/getLevel?img=${img.toString()}"); return int.parse(lvlStr); } Future get img_req_for_curr_lvl async { - String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${(await level).toString()}"); + String imgStr = await HttpRequest.getString( + "http://${Configs.utilServerAddress}/getImgForLevel?level=${(await level).toString()}"); return int.parse(imgStr); } Future get img_req_for_next_lvl async { - String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); + String imgStr = await HttpRequest.getString( + "http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); return int.parse(imgStr); } -} \ No newline at end of file +} diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 2d83ba1a..59a21cff 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -3,116 +3,119 @@ part of couclient; Inventory playerInventory = new Inventory(); class Slot { - //a new instance of a Slot is empty by default - String itemType = ""; - ItemDef item = null; - int count = 0; + //a new instance of a Slot is empty by default + String itemType = ""; + ItemDef item = null; + int count = 0; + + @override + String toString() => 'Slot: $itemType, $count'; } class Inventory { - // Sets how many slots each player has - final int invSize = 10; - List slots = []; + // Sets how many slots each player has + final int invSize = 10; + List slots = []; } class ItemDef { - String category, iconUrl, spriteUrl, toolAnimation, name, description, itemType, item_id; - int price, - stacksTo, - iconNum = 4, - durability, - durabilityUsed = 0, - subSlots = 0; - num x, y; - bool onGround = false, - isContainer = false; - List subSlotFilter; - List actions = []; - Map metadata = {}; + String category, iconUrl, spriteUrl, toolAnimation, name, description, itemType, item_id; + int price, + stacksTo, + iconNum = 4, + durability, + durabilityUsed = 0, + subSlots = 0; + num x, y; + bool onGround = false, + isContainer = false; + List subSlotFilter; + List actions = []; + Map metadata = {}; } Future updateInventory([Map map]) async { - List dataSlots = []; - - if (map != null) { - dataSlots = map["slots"]; - } else { - print("Attempted inventory update: failed."); - return; - } - - List currentSlots = playerInventory.slots; - List slots = []; - - //couldn't get the structure to decode correctly so I hacked together this - //it produces the right result, but looks terrible - dataSlots.forEach((Map m) { - Slot slot = new Slot(); - if (!m['itemType'].isEmpty) { - ItemDef item; - if (m['item']['metadata']['slots'] == null) { - item = decode(JSON.encode(m['item']), type: ItemDef); - } else { - Map metadata = (m['item'] as Map).remove('metadata'); - item = decode(JSON.encode(m['item']), type: ItemDef); - item.metadata = metadata; - } - slot.item = item; - slot.itemType = item.itemType; - slot.count = m['count']; - } - slots.add(slot); - }); - - playerInventory.slots = slots; - - //if the current inventory differs (count, type, metatdata) then clear it - //and change it, else leave it alone - List uiSlots = querySelectorAll(".box").toList(); - List slotUpdateFutures = []; - for (int i = 0; i < 10; i++) { - Slot newSlot = slots.elementAt(i); - - bool updateNeeded = false, - update = false; - - //if we've never received our inventory before, update all slots - if (currentSlots.length == 0) { - updateNeeded = true; - } else { - Slot currentSlot = currentSlots.elementAt(i); - - if (currentSlot.itemType != newSlot.itemType) { - updateNeeded = true; - } else if (currentSlot.count != newSlot.count) { - updateNeeded = true; - update = true; - } else if (currentSlot.item != null && newSlot.item != null && - !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { - transmit('updateMetadata', newSlot.item); - } - } - - if (updateNeeded) { - if (newSlot.count == 0) { - uiSlots - .elementAt(i) - .children - .clear(); - } else { - uiSlots - .elementAt(i) - .children - .forEach((Element child) { - if (child.attributes.containsKey('count')) { - child.attributes['count'] = "0"; - } - }); - slotUpdateFutures.add(findNewSlot(newSlot, i, update: update)); - } - } - } - - await Future.wait(slotUpdateFutures); - transmit("inventoryUpdated", true); + List dataSlots = []; + + if (map != null) { + dataSlots = map["slots"]; + } else { + print("Attempted inventory update: failed."); + return; + } + + List currentSlots = playerInventory.slots; + List slots = []; + + //couldn't get the structure to decode correctly so I hacked together this + //it produces the right result, but looks terrible + dataSlots.forEach((Map m) { + Slot slot = new Slot(); + if (!m['itemType'].isEmpty) { + ItemDef item; + if (m['item']['metadata']['slots'] == null) { + item = decode(JSON.encode(m['item']), type: ItemDef); + } else { + Map metadata = (m['item'] as Map).remove('metadata'); + item = decode(JSON.encode(m['item']), type: ItemDef); + item.metadata = metadata; + } + slot.item = item; + slot.itemType = item.itemType; + slot.count = m['count']; + } + slots.add(slot); + }); + + playerInventory.slots = slots; + + //if the current inventory differs (count, type, metatdata) then clear it + //and change it, else leave it alone + List uiSlots = querySelectorAll(".box").toList(); + List slotUpdateFutures = []; + for (int i = 0; i < 10; i++) { + Slot newSlot = slots.elementAt(i); + + bool updateNeeded = false, + update = false; + + //if we've never received our inventory before, update all slots + if (currentSlots.length == 0) { + updateNeeded = true; + } else { + Slot currentSlot = currentSlots.elementAt(i); + + if (currentSlot.itemType != newSlot.itemType) { + updateNeeded = true; + } else if (currentSlot.count != newSlot.count) { + updateNeeded = true; + update = true; + } else if (currentSlot.item != null && newSlot.item != null && + !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { + transmit('updateMetadata', newSlot.item); + } + } + + if (updateNeeded) { + if (newSlot.count == 0) { + uiSlots + .elementAt(i) + .children + .clear(); + } else { + uiSlots + .elementAt(i) + .children + .forEach((Element child) { + if (child.attributes.containsKey('count')) { + child.attributes['count'] = "0"; + } + }); + slotUpdateFutures.add(findNewSlot(newSlot, i, update: update)); + } + } + } + + await Future.wait(slotUpdateFutures); + transmit("inventoryUpdated", true); } diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index 19e7f972..f074cf00 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -3,214 +3,159 @@ part of couclient; ///slot should be in the format 'barSlot.bagSlot' indexed from 0 ///if bagSlot is not relevant, please use 'barSlot.-1' itemContextMenu(ItemDef i, String slot, MouseEvent event) { - event.preventDefault(); - event.stopPropagation(); - - int barSlot = int.parse(slot.split('.').elementAt(0)); - int bagSlot = int.parse(slot.split('.').elementAt(1)); - List actions = []; - - if (i.actions != null) { - List actionsList = i.actions; - bool enabled = false; - actionsList.forEach((Action action) { - String error = ""; - List requires = []; - action.itemRequirements.all.forEach((String item, int num) => requires.add({'num':num, 'of':[item]})); - if (action.itemRequirements.any.length > 0) { - requires.add({'num':1, 'of':action.itemRequirements.any}); - } - enabled = hasRequirements(requires); - if (enabled) { - error = action.description; - } else { - error = getRequirementString(requires); - } - actions.add([ - capitalizeFirstLetter(action.name) + '|' + - action.name + '|${action.timeRequired}|$enabled|$error', - i.itemType, - "sendAction ${action.name} ${i.item_id}", - getDropMap(1, barSlot, bagSlot) - ]); - }); - } - Element menu = RightClickMenu.create(event, i.name, i.description, actions, itemName: i.name); - document.body.append(menu); + event.preventDefault(); + event.stopPropagation(); + + int barSlot = int.parse(slot.split('.').elementAt(0)); + int bagSlot = int.parse(slot.split('.').elementAt(1)); + List actions = []; + + if (i.actions != null) { + List actionsList = i.actions; + bool enabled = false; + actionsList.forEach((Action action) { + String error = ""; + List requires = []; + action.itemRequirements.all.forEach((String item, int num) => requires.add({'num':num, 'of':[item]})); + if (action.itemRequirements.any.length > 0) { + requires.add({'num':1, 'of':action.itemRequirements.any}); + } + enabled = hasRequirements(requires); + if (enabled) { + error = action.description; + } else { + error = getRequirementString(requires); + } + actions.add([ + capitalizeFirstLetter(action.name) + '|' + + action.name + '|${action.timeRequired}|$enabled|$error', + i.itemType, + "sendAction ${action.name} ${i.item_id}", + getDropMap(1, barSlot, bagSlot) + ]); + }); + } + Element menu = RightClickMenu.create(event, i.name, i.description, actions, itemName: i.name); + document.body.append(menu); } Future findNewSlot(Slot slot, int index, {bool update: false}) async { - ItemDef item = slot.item; - int count = slot.count; - - //create an image for this item and wait for it to load before sizing/positioning it - ImageElement img = new ImageElement(src: item.spriteUrl); - await img.onLoad.first; - - Element barSlot = view.inventory.children.elementAt(index); - barSlot.children.clear(); - String cssName = item.itemType.replaceAll(" ", "_"); - Element itemDiv = new DivElement(); - - //determine what we need to scale the sprite image to in order to fit - num scale = 1; - if (img.height > img.width / item.iconNum) { - scale = (barSlot.contentEdge.height - 10) / img.height; - } else { - scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); - } - - itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; - itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; - itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; - itemDiv.style.backgroundRepeat = 'no-repeat'; - itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; - itemDiv.style.backgroundPosition = "0 50%"; - itemDiv.style.margin = "auto"; - itemDiv.className = 'item-$cssName inventoryItem'; - - itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); - itemDiv.attributes['count'] = "1"; - itemDiv.attributes['itemMap'] = encode(item); - - String slotNum = '${barSlot.dataset["slot-num"]}.-1'; - itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); - barSlot.append(itemDiv); - - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - barSlot.append(itemCount); - if (count <= 1) { - itemCount.text = ""; - } - - int offset = count; - if (item.iconNum != null && item.iconNum < count) { - offset = item.iconNum; - } - itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; - - if (!update) { - itemDiv.classes.add("bounce"); - // remove the bounce class so that it's not still there for a drag and drop event - new Timer(new Duration(seconds: 1), () { - itemDiv.classes.remove("bounce"); - }); - } - - // Containers - DivElement containerButton; - String bagWindowId; - if (item.isContainer == true) { - containerButton = new DivElement() - ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) - ..onClick.listen((_) { - if (containerButton.classes.contains("item-container-closed")) { - // Container is closed, open it - // Open the bag window - bagWindowId = new BagWindow(int.parse(itemDiv.parent.dataset["slot-num"]), item).id; - // Update the slot display - BagWindow.updateTriggerBtn(false, itemDiv); - } else { - // Container is open, close it - // Close the bag window - BagWindow.closeId(bagWindowId); - // Update the slot display - BagWindow.updateTriggerBtn(true, itemDiv); - } - }); - itemDiv.parent.append(containerButton); - } + ItemDef item = slot.item; + int count = slot.count; + + //create an image for this item and wait for it to load before sizing/positioning it + ImageElement img = new ImageElement(src: item.spriteUrl); + await img.onLoad.first; + + Element barSlot = view.inventory.children.elementAt(index); + barSlot.children.clear(); + String cssName = item.itemType.replaceAll(" ", "_"); + Element itemDiv = new DivElement(); + + //determine what we need to scale the sprite image to in order to fit + num scale = 1; + if (img.height > img.width / item.iconNum) { + scale = (barSlot.contentEdge.height - 10) / img.height; + } else { + scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); + } + + itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; + itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; + itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; + itemDiv.style.backgroundRepeat = 'no-repeat'; + itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; + itemDiv.style.backgroundPosition = "0 50%"; + itemDiv.style.margin = "auto"; + itemDiv.className = 'item-$cssName inventoryItem'; + + itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); + itemDiv.attributes['count'] = "1"; + itemDiv.attributes['itemMap'] = encode(item); + + String slotNum = '${barSlot.dataset["slot-num"]}.-1'; + itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); + barSlot.append(itemDiv); + + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + barSlot.append(itemCount); + if (count <= 1) { + itemCount.text = ""; + } + + int offset = count; + if (item.iconNum != null && item.iconNum < count) { + offset = item.iconNum; + } + itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; + + if (!update) { + itemDiv.classes.add("bounce"); + // remove the bounce class so that it's not still there for a drag and drop event + new Timer(new Duration(seconds: 1), () { + itemDiv.classes.remove("bounce"); + }); + } + + // Containers + DivElement containerButton; + String bagWindowId; + if (item.isContainer == true) { + containerButton = new DivElement() + ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) + ..onClick.listen((_) { + if (containerButton.classes.contains("item-container-closed")) { + // Container is closed, open it + // Open the bag window + bagWindowId = new BagWindow(index, item, id:bagWindowId).id; + // Update the slot display + BagWindow.updateTriggerBtn(false, itemDiv); + } else { + // Container is open, close it + // Close the bag window + BagWindow.closeId(bagWindowId); + // Update the slot display + BagWindow.updateTriggerBtn(true, itemDiv); + } + }); + itemDiv.parent.append(containerButton); + } } -//void putInInventory(ImageElement img, ItemDef i, int index, {bool update: false}) { -// bool found = false; -// -// String cssName = i.itemType.replaceAll(" ", "_"); -// for (Element item in view.inventory.querySelectorAll(".item-$cssName")) { -// int count = int.parse(item.attributes['count']); -// int stacksTo = i.stacksTo; -// -// if (count < stacksTo) { -// count++; -// int offset = count; -// if (i.iconNum != null && i.iconNum < count) { -// offset = i.iconNum; -// } -// -// item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; -// item.attributes['count'] = count.toString(); -// -// Element itemCount = item.parent.querySelector(".itemCount"); -// if (itemCount != null) { -// if (count > 1) { -// itemCount.text = count.toString(); -// } else { -// itemCount.text = ""; -// } -// } else { -// SpanElement itemCount = new SpanElement() -// ..text = count.toString() -// ..className = "itemCount"; -// item.parent.append(itemCount); -// if (count <= 1) { -// itemCount.text = ""; -// } -// } -// -// found = true; -// break; -// } -// } -// if (!found) { -// findNewSlot(i, img, index, update: update); -// } -// transmit("inventoryUpdated"); -//} - -//void addItemToInventory(ItemDef item, int index, {bool update: false}) { -// ImageElement img = new ImageElement(src: item.spriteUrl); -// img.onLoad.first.then((_) { -// //do some fancy 'put in bag' animation that I can't figure out right now -// //animate(img,map).then((_) => putInInventory(img,map)); -// -// putInInventory(img, item, index, update:update); -// }); -//} - void takeItemFromInventory(String itemType, {int count: 1}) { - String cssName = itemType.replaceAll(" ", "_"); - int remaining = count; - for (Element item in view.inventory.querySelectorAll(".item-$cssName")) { - if (remaining < 1) { - break; - } - - int uiCount = int.parse(item.attributes['count']); - if (uiCount > count) { - item.attributes['count'] = (uiCount - count).toString(); - item.parent - .querySelector('.itemCount') - .text = (uiCount - count).toString(); - } else { - item.parent.children.clear(); - } - - remaining -= uiCount; - } - transmit("inventoryUpdated"); + String cssName = itemType.replaceAll(" ", "_"); + int remaining = count; + for (Element item in view.inventory.querySelectorAll(".item-$cssName")) { + if (remaining < 1) { + break; + } + + int uiCount = int.parse(item.attributes['count']); + if (uiCount > count) { + item.attributes['count'] = (uiCount - count).toString(); + item.parent + .querySelector('.itemCount') + .text = (uiCount - count).toString(); + } else { + item.parent.children.clear(); + } + + remaining -= uiCount; + } + transmit("inventoryUpdated"); } Map getDropMap(int count, int slotNum, int subSlotNum) { - Map dropMap = new Map() - ..['slot'] = slotNum - ..['subSlot'] = subSlotNum - ..['count'] = count - ..['x'] = CurrentPlayer.posX - ..['y'] = CurrentPlayer.posY + CurrentPlayer.height / 2 - ..['streetName'] = currentStreet.label - ..['tsid'] = currentStreet.streetData['tsid']; - - return dropMap; + Map dropMap = new Map() + ..['slot'] = slotNum + ..['subSlot'] = subSlotNum + ..['count'] = count + ..['x'] = CurrentPlayer.posX + ..['y'] = CurrentPlayer.posY + CurrentPlayer.height / 2 + ..['streetName'] = currentStreet.label + ..['tsid'] = currentStreet.streetData['tsid']; + + return dropMap; } diff --git a/lib/src/network/server_interop/so_multiplayer.dart b/lib/src/network/server_interop/so_multiplayer.dart index 21f5473f..759c8b4c 100644 --- a/lib/src/network/server_interop/so_multiplayer.dart +++ b/lib/src/network/server_interop/so_multiplayer.dart @@ -2,92 +2,47 @@ part of couclient; String multiplayerServer = "ws://${Configs.websocketServerAddress}/playerUpdate"; String streetEventServer = "ws://${Configs.websocketServerAddress}/streetUpdate"; -String joined = "", creatingPlayer = ""; +String joined = "", + creatingPlayer = ""; WebSocket streetSocket, playerSocket; -bool reconnect = true, firstConnect = true, serverDown = false; +bool reconnect = true, + firstConnect = true, + serverDown = false; Map otherPlayers = new Map(); Map quoins = new Map(); Map entities = new Map(); multiplayerInit() { - _setupPlayerSocket(); - _setupStreetSocket(currentStreet.label); + _setupPlayerSocket(); + _setupStreetSocket(currentStreet.label); } void addQuoin(Map map) { - if (currentStreet == null) return; - - quoins[map['id']] = new Quoin(map); + if (currentStreet != null) { + quoins[map['id']] = new Quoin(map); + } } void addNPC(Map map) { - if (currentStreet == null) return; - - entities[map['id']] = new NPC(map); + if (currentStreet != null) { + entities[map['id']] = new NPC(map); + } } void addPlant(Map map) { - if (currentStreet == null) return; - - entities[map['id']] = new Plant(map); + if (currentStreet != null) { + entities[map['id']] = new Plant(map); + } } void addDoor(Map map) { - if (currentStreet == null) { - return; - } else { - entities[map['id']] = new Door(map); - } + if (currentStreet != null) { + entities[map['id']] = new Door(map); + } } void addItem(Map map) { - if (currentStreet == null) { - return; - } - - entities[map['id']] = new GroundItem(map); -} - -Future animate(ImageElement i, Map map) { - Completer c = new Completer(); - print(map["fromObject"]); - if (map["fromObject"] == "_self") { - map["fromObject"] = "pc-player-${game.username}"; - } - print(map["fromObject"]); - Element fromObject = querySelector("#${map['fromObject']}"); - DivElement item = new DivElement(); - - num fromX = num.parse(fromObject.attributes['translatex']) - camera.getX(); - num fromY = num.parse(fromObject.attributes['translatey']) - camera.getY() - fromObject.clientHeight; - item.className = "item"; - item.style.width = (i.naturalWidth / 4).toString() + "px"; - item.style.height = i.naturalHeight.toString() + "px"; - item.style.transformOrigin = "50% 50%"; - item.style.backgroundImage = 'url(${map['url']})'; - item.style.transform = "translate(${fromX}px,${fromY}px)"; - //print("from: " + item.style.transform); - querySelector("#GameScreen").append(item); - - //animation seems to happen instantaneously if there isn't a delay - //between adding the element to the document and changing its properties - //even this 1 millisecond delay seems to fix that - strange - new Timer(new Duration(milliseconds: 1), () { - Element playerParent = querySelector(".playerParent"); - item.style.transform = - "translate(${playerParent.attributes['translatex']}px,${playerParent.attributes['translatey']}px) scale(2)"; - //print("to: " + item.style.transform); - }); - new Timer(new Duration(seconds: 2), () { - item.style.transform = - "translate(${CurrentPlayer.posX}px,${CurrentPlayer.posY}px) scale(.5)"; - - //wait 1 second for animation to finish and then remove - new Timer(new Duration(seconds: 1), () { - item.remove(); - c.complete(); - }); - }); - - return c.future; + if (currentStreet != null) { + entities[map['id']] = new GroundItem(map); + } } \ No newline at end of file diff --git a/lib/src/systems/audio.dart b/lib/src/systems/audio.dart index 9e559db3..e66cc64d 100644 --- a/lib/src/systems/audio.dart +++ b/lib/src/systems/audio.dart @@ -138,10 +138,14 @@ class SoundManager { fadeInDuration = new Duration(seconds:5); } double currentPercentOfFade = 0.0; + //for some reason if(audio.gain >= 1.0) threw errors about int + //not being a subtype of double. So this steps method instead + int steps = fadeInDuration.inMilliseconds~/100; new Timer.periodic(new Duration(milliseconds:100), (Timer t) { currentPercentOfFade += 100 / fadeInDuration.inMilliseconds; audio.gain = currentPercentOfFade; - if(audio.gain >= 1.0) { + steps--; + if(steps <= 0) { t.cancel(); } }); @@ -180,10 +184,14 @@ class SoundManager { fadeOutDuration = new Duration(seconds:5); } double currentPercentOfFade = 0.0; + //for some reason if(audio.gain <=) threw errors about int + //not being a subtype of double. So this steps method instead + int steps = fadeOutDuration.inMilliseconds~/100; new Timer.periodic(new Duration(milliseconds:100), (Timer t) { currentPercentOfFade += 100 / fadeOutDuration.inMilliseconds; audio.gain = 1.0 - currentPercentOfFade; - if(audio.gain <= 0.0) { + steps--; + if(steps <= 0) { audio.stop(); t.cancel(); } From 3696b8ed19ec173a6ee51d362c8a5d21ffc12e58 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Fri, 8 Jan 2016 20:40:47 -0500 Subject: [PATCH 25/43] player can sell and donate from bags just like they can from the inventory bar --- lib/src/display/windows/bag_window.dart | 6 ++++++ lib/src/display/windows/shrine_window.dart | 7 ++++++- lib/src/display/windows/vendor_window.dart | 7 ++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index 40ca13be..b949f3dc 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -41,6 +41,7 @@ class BagWindow extends Modal { } BagWindow._(this.sourceSlotNum, this.sourceItem) { + bool creating = true; id = 'bagWindow' + WindowManager.randomId.toString(); bagWindows.add(this); @@ -69,12 +70,17 @@ class BagWindow extends Modal { this.sourceItem = sourceItem; if(displayElement.hidden) { dataUpdated = true; + } else { + if(!creating) { + updateWell(sourceItem); + } } }); querySelector("#windowHolder").append(displayElement); prepare(); open(); + creating = false; }); } diff --git a/lib/src/display/windows/shrine_window.dart b/lib/src/display/windows/shrine_window.dart index 10188bac..6906e720 100644 --- a/lib/src/display/windows/shrine_window.dart +++ b/lib/src/display/windows/shrine_window.dart @@ -76,8 +76,13 @@ class ShrineWindow extends Modal { } void makeDraggables() { - Dropzone dropzone = new Dropzone(dropTarget, acceptor: new Acceptor.draggables([InvDragging._draggables])); + Dropzone dropzone = new Dropzone(dropTarget); dropzone.onDrop.listen((DropzoneEvent dropEvent) { + //verify it is a valid item before acting on it + if(dropEvent.draggableElement.attributes['itemMap'] == null) { + return; + } + buttonHolder.style.visibility = 'visible'; item = JSON.decode(dropEvent.draggableElement.attributes['itemMap']); dropTarget.style.backgroundImage = 'url(' + item['iconUrl'] + ')'; diff --git a/lib/src/display/windows/vendor_window.dart b/lib/src/display/windows/vendor_window.dart index 5d8fd167..311a48ea 100644 --- a/lib/src/display/windows/vendor_window.dart +++ b/lib/src/display/windows/vendor_window.dart @@ -107,11 +107,16 @@ class VendorWindow extends Modal { } DivElement dropTarget = querySelector("#SellDropTarget"); - Dropzone dropzone = new Dropzone(dropTarget, acceptor: new Acceptor.draggables([InvDragging._draggables])); + Dropzone dropzone = new Dropzone(dropTarget); dropzone.onDrop.listen((DropzoneEvent dropEvent) { // TODO: fix this only getting called the first time // https://github.com/ChildrenOfUr/cou-issues/issues/279 // print("dropped item"); + + //verify it is a valid item before acting on it + if(dropEvent.draggableElement.attributes['itemMap'] == null) { + return; + } spawnBuyDetails(JSON.decode(dropEvent.draggableElement.attributes['itemMap']), vendorMap['id'], sellMode:true); }); From dec90834191c1eaff976c4a991bbf8cd66ec62ec Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 20:39:22 -0600 Subject: [PATCH 26/43] this one's for robert --- lib/src/display/inv_dragging.dart | 4 +- lib/src/display/ui_templates/menu_keys.dart | 259 ++++++++++++-------- 2 files changed, 152 insertions(+), 111 deletions(-) diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index d5d48ef9..0c88dcd7 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -69,13 +69,13 @@ class InvDragging { ) ..onDragStart.listen((DraggableEvent e) => handlePickup(e)); - print('setup draggables'); + // print('setup draggables'); // Set up acceptor slots _dropzones = new Dropzone(querySelectorAll("#inventory .box")) ..onDrop.listen((DropzoneEvent e) => handleDrop(e)); - print('setup dropzones'); + // print('setup dropzones'); } /// Runs when an item is picked up (drag start) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index 1e58502a..81d64da4 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -1,120 +1,161 @@ part of couclient; class MenuKeys { - // Maps key to valid keycodes - // Use int for single binding, and List for multiple (eg. numpad and number row) - static final Map KEY_CODES = { - "A": 65, - "B": 66, - "C": 67, - "D": 68, - "E": 69, - "F": 70, - "G": 71, - "H": 72, - "I": 73, - "J": 74, - "K": 75, - "L": 76, - "M": 77, - "N": 78, - "O": 79, - "P": 80, - "Q": 81, - "R": 82, - "S": 83, - "T": 84, - "U": 85, - "V": 86, - "W": 87, - "X": 88, - "Y": 89, - "Z": 90, - "1": [ - 49, - 97 - ], - "2": [ - 50, - 98 - ], - "3": [ - 51, - 99 - ], - "4": [ - 52, - 100 - ], - "5": [ - 53, - 101 - ], - "6": [ - 54, - 102 - ], - "7": [ - 55, - 103 - ], - "8": [ - 56, - 104 - ], - "9": [ - 57, - 105 - ], - "0": [ - 48, - 97 - ] - }; + // Maps key to valid keycodes + // Use int for single binding, and List for multiple (eg. numpad and number row) + static final Map KEY_CODES = { + "A": 65, + "B": 66, + "C": 67, + "D": 68, + "E": 69, + "F": 70, + "G": 71, + "H": 72, + "I": 73, + "J": 74, + "K": 75, + "L": 76, + "M": 77, + "N": 78, + "O": 79, + "P": 80, + "Q": 81, + "R": 82, + "S": 83, + "T": 84, + "U": 85, + "V": 86, + "W": 87, + "X": 88, + "Y": 89, + "Z": 90, + "1": [ + 49, + 97 + ], + "2": [ + 50, + 98 + ], + "3": [ + 51, + 99 + ], + "4": [ + 52, + 100 + ], + "5": [ + 53, + 101 + ], + "6": [ + 54, + 102 + ], + "7": [ + 55, + 103 + ], + "8": [ + 56, + 104 + ], + "9": [ + 57, + 105 + ], + "0": [ + 48, + 97 + ] + }; - // Returns if the keycode provided will trigger the given key - static bool keyCodeMatches(int keyCode, dynamic key) { - key = key.toString().toUpperCase(); + // Returns the key with the given keycode + static String keyWithCode(int keyCode) { + KEY_CODES.forEach((String key, int code) { + if (code == keyCode) { + return key; + } + }); - if (KEY_CODES[key] is int) { - // Keycode matches? (1 key :: 1 keycode) - return KEY_CODES[key] == keyCode; - } else if (KEY_CODES[key] is List) { - // Keycode matches? (1 key :: multiple keycodes) - return (KEY_CODES[key] as List).where((int code) => code == keyCode).toList().length > 0; - } else { - return false; - } - } + return ""; + } - // Resets the keyboard event listener list - static void clearListeners() { - // Cancel all listeners to prevent future duplication - _listeners.forEach((StreamSubscription listener) { - listener.cancel(); - }); + // Returns if the keycode provided will trigger the given key + static bool keyCodeMatches(int keyCode, dynamic key) { + key = key.toString().toUpperCase(); - // Remove all items to allow for garbage collection - _listeners.clear(); - } + if (KEY_CODES[key] is int) { + // Keycode matches? (1 key :: 1 keycode) + return KEY_CODES[key] == keyCode; + } else if (KEY_CODES[key] is List) { + // Keycode matches? (1 key :: multiple keycodes) + return (KEY_CODES[key] as List) + .where((int code) => code == keyCode) + .toList() + .length > 0; + } else { + return false; + } + } - // List of keyboard listeners (one for each menu item) - static List _listeners = []; + // Resets the keyboard event listener list + static void clearListeners() { + // Cancel all listeners to prevent future duplication + _listeners.forEach((StreamSubscription listener) { + listener.cancel(); + }); - // Start listening for a menu item - static void addListener(int index, Function callback) { - _listeners.add(document.onKeyDown.listen((KeyboardEvent e) { - // Probably not absolutely needed, but good practice - e - ..stopPropagation() - ..preventDefault(); + // Remove all items to allow for garbage collection + _listeners.clear(); + } - if (keyCodeMatches(e.keyCode, index)) { - // Stop listening for the keys after the menu is gone - clearListeners(); - // Run the callback - Function.apply(callback, []); - } - })); - } + // List of keyboard listeners (one for each menu item) + static List _listeners = []; + + // Inventory slot listener + static StreamSubscription _invSlotsListener = document.onKeyDown.listen((KeyboardEvent event) { + print("||| EVENT"); + if (_listeners.length != 0) { + // Menu open, do not trigger item interactions because the numbers are preoccupied + return; + } + + // Get the number pushed + int index; + try { + index = int.parse(keyWithCode(event.keyCode)); + } catch (e) { + // Letter pressed, not a number + return; + } + + // Get the box with that index + Element box = view.inventory.querySelectorAll(".box").singleWhere((Element e) { + return (e.dataset["slot-num"] == index.toString()); + }); + + if (event.shiftKey && box.querySelector(".item-container-toggle") != null) { + // Open container if shift is pressed + box.querySelector(".item-container-toggle").click(); + print("||| container"); + } else { + // "Click" the box + box.dispatchEvent(new MouseEvent("contextmenu")); + } + }); + + // Start listening for a menu item + static void addListener(int index, Function callback) { + _listeners.add(document.onKeyDown.listen((KeyboardEvent event) { + if (keyCodeMatches(event.keyCode, index)) { + // Stop listening for the keys after the menu is gone + clearListeners(); + // Run the callback + Function.apply(callback, []); + } + })); + } } From e6355b1479d3646a1fbccd574bfd81440c1a83c1 Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 20:46:11 -0600 Subject: [PATCH 27/43] fix residual menu listeners --- lib/src/display/ui_templates/menu_keys.dart | 3 ++- lib/src/display/ui_templates/right_click_menu.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index 81d64da4..95152b6d 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -142,8 +142,9 @@ class MenuKeys { box.querySelector(".item-container-toggle").click(); print("||| container"); } else { - // "Click" the box + // Click the box box.dispatchEvent(new MouseEvent("contextmenu")); + print("||| click"); } }); diff --git a/lib/src/display/ui_templates/right_click_menu.dart b/lib/src/display/ui_templates/right_click_menu.dart index 91f48900..159a30d5 100644 --- a/lib/src/display/ui_templates/right_click_menu.dart +++ b/lib/src/display/ui_templates/right_click_menu.dart @@ -327,6 +327,7 @@ class RightClickMenu { Element menu = querySelector("#RightClickMenu"); if (menu != null) { menu.remove(); + MenuKeys.clearListeners(); transmit("right_click_menu", "destroy"); } } From 2d4d4a2dc4b609077ad4a5ccc16034b335a5bfc4 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Fri, 8 Jan 2016 22:12:53 -0500 Subject: [PATCH 28/43] fix item bought after bag but before bag opens does not appear right away --- lib/src/display/windows/bag_window.dart | 19 +++++++++++++------ lib/src/network/server_interop/inventory.dart | 1 + lib/src/network/server_interop/so_item.dart | 3 ++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index b949f3dc..b9543384 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -26,21 +26,24 @@ class BagWindow extends Modal { Dropzone acceptors; - factory BagWindow(int sourceSlotNum, ItemDef sourceItem, {String id : null}) { + factory BagWindow(int sourceSlotNum, ItemDef sourceItem, {String id : null, bool open : true}) { + print('getting bag window for id $id'); if (id == null) { - return new BagWindow._(sourceSlotNum, sourceItem); + return new BagWindow._(sourceSlotNum, sourceItem, openWindow:open); } else { for(BagWindow w in bagWindows) { if (w.id == id) { - w.open(); + if(open) { + w.open(); + } return w; } } - return new BagWindow._(sourceSlotNum, sourceItem); + return new BagWindow._(sourceSlotNum, sourceItem, openWindow:open); } } - BagWindow._(this.sourceSlotNum, this.sourceItem) { + BagWindow._(this.sourceSlotNum, this.sourceItem, {bool openWindow : true}) { bool creating = true; id = 'bagWindow' + WindowManager.randomId.toString(); bagWindows.add(this); @@ -79,7 +82,11 @@ class BagWindow extends Modal { querySelector("#windowHolder").append(displayElement); prepare(); - open(); + if(openWindow) { + open(); + } else { + displayElement.hidden = true; + } creating = false; }); } diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 59a21cff..4a1ce609 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -92,6 +92,7 @@ Future updateInventory([Map map]) async { update = true; } else if (currentSlot.item != null && newSlot.item != null && !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { + print('metadata is different'); transmit('updateMetadata', newSlot.item); } } diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index f074cf00..640dfc69 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -103,13 +103,14 @@ Future findNewSlot(Slot slot, int index, {bool update: false}) async { DivElement containerButton; String bagWindowId; if (item.isContainer == true) { + bagWindowId = new BagWindow(index, item, open:false).id; containerButton = new DivElement() ..classes.addAll(["fa", "fa-fw", "fa-plus", "item-container-toggle", "item-container-closed"]) ..onClick.listen((_) { if (containerButton.classes.contains("item-container-closed")) { // Container is closed, open it // Open the bag window - bagWindowId = new BagWindow(index, item, id:bagWindowId).id; + new BagWindow(index, item, id:bagWindowId).id; // Update the slot display BagWindow.updateTriggerBtn(false, itemDiv); } else { From 6d71cbb207de1894b05f9f03613344f7feb82f84 Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 21:31:56 -0600 Subject: [PATCH 29/43] numbers trigger inv slots --- lib/src/display/ui_templates/menu_keys.dart | 100 +++++++++++------- .../ui_templates/right_click_menu.dart | 4 +- lib/src/display/view.dart | 3 + 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index 95152b6d..211c7d9f 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -74,11 +74,19 @@ class MenuKeys { // Returns the key with the given keycode static String keyWithCode(int keyCode) { - KEY_CODES.forEach((String key, int code) { - if (code == keyCode) { + for (String key in KEY_CODES.keys) { + var codes = KEY_CODES[key]; + + if (codes is int && codes == keyCode) { return key; + } else if (codes is List) { + for (int code in codes) { + if (code == keyCode) { + return key; + } + } } - }); + } return ""; } @@ -101,6 +109,9 @@ class MenuKeys { } } + // List of keyboard listeners (one for each menu item) + static List _listeners = []; + // Resets the keyboard event listener list static void clearListeners() { // Cancel all listeners to prevent future duplication @@ -112,42 +123,6 @@ class MenuKeys { _listeners.clear(); } - // List of keyboard listeners (one for each menu item) - static List _listeners = []; - - // Inventory slot listener - static StreamSubscription _invSlotsListener = document.onKeyDown.listen((KeyboardEvent event) { - print("||| EVENT"); - if (_listeners.length != 0) { - // Menu open, do not trigger item interactions because the numbers are preoccupied - return; - } - - // Get the number pushed - int index; - try { - index = int.parse(keyWithCode(event.keyCode)); - } catch (e) { - // Letter pressed, not a number - return; - } - - // Get the box with that index - Element box = view.inventory.querySelectorAll(".box").singleWhere((Element e) { - return (e.dataset["slot-num"] == index.toString()); - }); - - if (event.shiftKey && box.querySelector(".item-container-toggle") != null) { - // Open container if shift is pressed - box.querySelector(".item-container-toggle").click(); - print("||| container"); - } else { - // Click the box - box.dispatchEvent(new MouseEvent("contextmenu")); - print("||| click"); - } - }); - // Start listening for a menu item static void addListener(int index, Function callback) { _listeners.add(document.onKeyDown.listen((KeyboardEvent event) { @@ -159,4 +134,51 @@ class MenuKeys { } })); } + + // Inventory slot listener + static void invSlotsListener() { + document.onKeyDown.listen((KeyboardEvent event) { + if (_listeners.length != 0) { + // TODO: also stop if typing somewhere + // Menu open, do not trigger item interactions because the numbers are preoccupied + return; + } + + // Get the number pushed + int index; + try { + // Inv is 0-indexed, so subtract 1 from the key + index = int.parse(keyWithCode(event.keyCode)) - 1; + if (index == -1) { + // 0 is the last slot + index = 9; + } + } catch (e) { + // Letter pressed, not a number + return; + } + + // Get the box with that index + Element box = view.inventory.querySelectorAll(".box").singleWhere((Element e) { + return (e.dataset["slot-num"] == index.toString()); + }); + + if (box.querySelector(".inventoryItem") == null) { + // No item in that slot? + return; + } + + if (event.shiftKey && box.querySelector(".item-container-toggle") != null) { + // Open container if shift is pressed + box.querySelector(".item-container-toggle").click(); + } else { + // Click the box + int x = box.documentOffset.x + (box.clientWidth ~/ 2); + int y = box.documentOffset.y; + box.querySelector(".inventoryItem").dispatchEvent( + new MouseEvent("contextmenu", clientX: x, clientY: y) + ); + } + }); + } } diff --git a/lib/src/display/ui_templates/right_click_menu.dart b/lib/src/display/ui_templates/right_click_menu.dart index 159a30d5..309e5210 100644 --- a/lib/src/display/ui_templates/right_click_menu.dart +++ b/lib/src/display/ui_templates/right_click_menu.dart @@ -287,8 +287,8 @@ class RightClickMenu { document.body.append(menu); if (Click != null) { - x = Click.page.x - (menu.clientWidth ~/ 2); - y = Click.page.y - (40 + (options.length * 30)); + x = Click.client.x - (menu.clientWidth ~/ 2); + y = Click.client.y - (40 + (options.length * 30)); } else { num posX = CurrentPlayer.posX, posY = CurrentPlayer.posY; int width = CurrentPlayer.width, height = CurrentPlayer.height; diff --git a/lib/src/display/view.dart b/lib/src/display/view.dart index c3568099..5e0dfbef 100644 --- a/lib/src/display/view.dart +++ b/lib/src/display/view.dart @@ -201,6 +201,9 @@ class UserInterface { Chat.lastFocusedInput.focus(); }); }); + + // Inventory number keys + MenuKeys.invSlotsListener(); } resize() { From 9ef2691763b245a03049ffc15070e56c707d7943 Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 21:34:12 -0600 Subject: [PATCH 30/43] don't allow use of numpad for inv slots --- lib/src/display/ui_templates/menu_keys.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index 211c7d9f..be29d98a 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -2,7 +2,7 @@ part of couclient; class MenuKeys { // Maps key to valid keycodes - // Use int for single binding, and List for multiple (eg. numpad and number row) + // Use int for single binding, and List for multiple (number row THEN numpad) static final Map KEY_CODES = { "A": 65, "B": 66, @@ -80,10 +80,8 @@ class MenuKeys { if (codes is int && codes == keyCode) { return key; } else if (codes is List) { - for (int code in codes) { - if (code == keyCode) { - return key; - } + if (codes[0] == keyCode) { + return key; } } } From 84ac4099c0319fa6ed057c2e4f48b4b1cae75881 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Fri, 8 Jan 2016 22:54:08 -0500 Subject: [PATCH 31/43] remove debugging statements --- lib/src/display/windows/bag_window.dart | 1 - lib/src/network/server_interop/inventory.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index b9543384..3125be82 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -27,7 +27,6 @@ class BagWindow extends Modal { Dropzone acceptors; factory BagWindow(int sourceSlotNum, ItemDef sourceItem, {String id : null, bool open : true}) { - print('getting bag window for id $id'); if (id == null) { return new BagWindow._(sourceSlotNum, sourceItem, openWindow:open); } else { diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 4a1ce609..59a21cff 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -92,7 +92,6 @@ Future updateInventory([Map map]) async { update = true; } else if (currentSlot.item != null && newSlot.item != null && !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { - print('metadata is different'); transmit('updateMetadata', newSlot.item); } } From b3ae34e597d223061f7953d790526f5c9eea195e Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 21:54:24 -0600 Subject: [PATCH 32/43] fix typing with number row --- lib/src/display/ui_templates/menu_keys.dart | 8 +++++--- lib/src/display/view.dart | 8 ++++---- lib/src/display/windows/map_window.dart | 1 - 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index be29d98a..1863ea1c 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -136,9 +136,11 @@ class MenuKeys { // Inventory slot listener static void invSlotsListener() { document.onKeyDown.listen((KeyboardEvent event) { - if (_listeners.length != 0) { - // TODO: also stop if typing somewhere - // Menu open, do not trigger item interactions because the numbers are preoccupied + if (inputManager.ignoreKeys || _listeners.length != 0) { + // One of the following is true: + // 1. The user is focused in a text entry or a window + // 2. A menu is open and the numbers are preoccupied + // Either way, don't open the menu and let them type return; } diff --git a/lib/src/display/view.dart b/lib/src/display/view.dart index 5e0dfbef..a95eea5e 100644 --- a/lib/src/display/view.dart +++ b/lib/src/display/view.dart @@ -181,8 +181,8 @@ class UserInterface { ..onFocus.listen((_) => transmit("worldFocus", true)) ..onBlur.listen((_) => transmit("worldFocus", false)); - // manage inventory items -> chat link new Service(["gameLoaded"], (_) { + // Shift + click inventory items -> chat link inventory.querySelectorAll(".box").onClick.listen((MouseEvent e) { Element target = e.target; @@ -200,10 +200,10 @@ class UserInterface { Chat.lastFocusedInput.focus(); }); - }); - // Inventory number keys - MenuKeys.invSlotsListener(); + // Inventory number keys + MenuKeys.invSlotsListener(); + }); } resize() { diff --git a/lib/src/display/windows/map_window.dart b/lib/src/display/windows/map_window.dart index 2b6cf6b7..139467f1 100644 --- a/lib/src/display/windows/map_window.dart +++ b/lib/src/display/windows/map_window.dart @@ -17,7 +17,6 @@ class MapWindow extends Modal { this.close(); }); - searchBox.onInput.listen((_) => filter(searchBox.value)); searchBox.onFocus.listen((_) => inputManager.ignoreKeys = ignoreShortcuts = true); searchBox.onBlur.listen((_) { new Timer(new Duration(milliseconds: 100), () { From e2700f983a8af75cccb28d8f33463b5e8b65b7b4 Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 21:58:58 -0600 Subject: [PATCH 33/43] click lower on the item box --- lib/src/display/ui_templates/menu_keys.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/display/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart index 1863ea1c..f526ce36 100644 --- a/lib/src/display/ui_templates/menu_keys.dart +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -174,7 +174,7 @@ class MenuKeys { } else { // Click the box int x = box.documentOffset.x + (box.clientWidth ~/ 2); - int y = box.documentOffset.y; + int y = box.documentOffset.y + (box.clientHeight ~/ 2); box.querySelector(".inventoryItem").dispatchEvent( new MouseEvent("contextmenu", clientX: x, clientY: y) ); From c772d93fff2dd6c9a5048af15d923a780286d450 Mon Sep 17 00:00:00 2001 From: klikini Date: Fri, 8 Jan 2016 22:25:18 -0600 Subject: [PATCH 34/43] fix map search --- lib/src/display/windows/map_window.dart | 226 +++++++++++++----------- 1 file changed, 120 insertions(+), 106 deletions(-) diff --git a/lib/src/display/windows/map_window.dart b/lib/src/display/windows/map_window.dart index 139467f1..7407433b 100644 --- a/lib/src/display/windows/map_window.dart +++ b/lib/src/display/windows/map_window.dart @@ -1,110 +1,124 @@ part of couclient; class MapWindow extends Modal { - String id = 'mapWindow'; - Element trigger = querySelector("#mapButton"); - InputElement searchBox = querySelector("#mapwindow-search"); - Element searchResultsContainer = querySelector("#map-window-search-results"); - UListElement searchResults = querySelector("#map-window-search-results ul"); - - MapWindow() { - prepare(); - - setupUiButton(view.mapButton, openCallback:_drawWorldMap); - setupKeyBinding("Map", openCallback:_drawWorldMap); - - new Service(['teleportByMapWindow'], (event) { - this.close(); - }); - - searchBox.onFocus.listen((_) => inputManager.ignoreKeys = ignoreShortcuts = true); - searchBox.onBlur.listen((_) { - new Timer(new Duration(milliseconds: 100), () { - inputManager.ignoreKeys = ignoreShortcuts = false; - filter(""); - }); - }); - } - - _drawWorldMap() { - worldMap = new WorldMap(currentStreet.hub_id); - } - - @override - open() { - super.open(); - trigger.classes.remove('closed'); - trigger.classes.add('open'); - } - - @override - close() { - super.close(); - trigger.classes.remove('open'); - trigger.classes.add('closed'); - } - - filter(String entry) { - // Toggle list showing - if (entry == "") { - searchResultsContainer.hidden = true; - return; - } else { - searchResultsContainer.hidden = false; - } - - // Clear previous results - searchResults.children.clear(); - - int streetsLimit = 0; - mapData.streetData.forEach((String streetname, Map data) { - - // Format TSID - String tsid = data["tsid"]; - if (tsid == null) { - tsid = "NULL_TSID"; - } else { - tsid = tsid.substring(1); - } - - // Check if street matches search - if ( - (mapData.streetData[streetname] != null && mapData.streetData[streetname]["map_hidden"] != true) && - (streetname.toLowerCase().contains(entry.toLowerCase()) || - tsid.toLowerCase().contains(entry.substring(1).toLowerCase())) - ) { - if (streetsLimit < 13) { - - // Mark if current street - String streetOut; - if (currentStreet.label == streetname) { - streetOut = "$streetname"; - } else { - streetOut = streetname; - } - - // Selectable item - LIElement result = new LIElement() - ..setInnerHtml(streetOut); - - // Link to hub - if (mapData.streetData[streetname] != null) { - String hub_id = mapData.streetData[streetname]["hub_id"].toString(); - result.onClick.listen((Event e) { - e.preventDefault(); - worldMap.loadhubdiv(hub_id, streetname); - searchBox.value = ""; - }); - } else { - logmessage("[WorldMap] Could not find the hub_id for $streetname"); - } - - // Add to list - searchResults.append(result); - - streetsLimit++; - } - } - }); - } + String id = 'mapWindow'; + Element trigger = querySelector("#mapButton"); + InputElement searchBox = querySelector("#mapwindow-search"); + Element searchResultsContainer = querySelector("#map-window-search-results"); + UListElement searchResults = querySelector("#map-window-search-results ul"); + + MapWindow() { + prepare(); + + setupUiButton(view.mapButton, openCallback: _drawWorldMap); + setupKeyBinding("Map", openCallback: _drawWorldMap); + + new Service(['teleportByMapWindow'], (event) { + this.close(); + }); + + searchBox + ..onFocus.listen((_) => inputManager.ignoreKeys = ignoreShortcuts = true) + ..onKeyDown.listen((_) => filter(searchBox.value)) + ..onBlur.listen((_) { + new Timer(new Duration(milliseconds: 100), () { + inputManager.ignoreKeys = ignoreShortcuts = false; + filter(""); + }); + }); + } + + _drawWorldMap() { + worldMap = new WorldMap(currentStreet.hub_id); + } + + @override + open() { + super.open(); + trigger.classes.remove('closed'); + trigger.classes.add('open'); + } + + @override + close() { + super.close(); + trigger.classes.remove('open'); + trigger.classes.add('closed'); + } + + // Search for a string + filter(String entry) { + // Toggle list showing + if (entry.trim().length < 2) { + searchResultsContainer.hidden = true; + return; + } else { + searchResultsContainer.hidden = false; + } + + // Clear previous results + searchResults.children.clear(); + + for (String streetname in mapData.streetData.keys) { + Map data = mapData.streetData[streetname]; + + // Format TSID + String tsid = data["tsid"]; + if (tsid == null) { + tsid = "NULL_TSID"; + } else { + tsid = tsid.substring(1); + } + + bool checkVisibility() { + // Allowed to be shown on map? + return (mapData.streetData[streetname] != null && + !(mapData.streetData[streetname]["map_hidden"])); + } + + bool checkName() { + // Partial name match? + return (streetname.toLowerCase().contains(entry.toLowerCase())); + } + + bool checkTsid() { + // Exact TSID match? + return (tsid.toLowerCase().contains(entry.substring(1).toLowerCase())); + } + + // Check if street matches search + if (checkVisibility() && (checkName() || checkTsid())) { + // Mark if current street + String streetOut; + if (currentStreet.label == streetname) { + streetOut = "$streetname"; + } else { + streetOut = streetname; + } + + // Selectable item + LIElement result = new LIElement() + ..setInnerHtml(streetOut); + + // Link to hub + if (mapData.streetData[streetname] != null) { + String hub_id = mapData.streetData[streetname]["hub_id"].toString(); + result.onClick.listen((Event e) { + e.preventDefault(); + worldMap.loadhubdiv(hub_id, streetname); + searchBox.value = ""; + }); + } else { + logmessage("[WorldMap] Could not find the hub_id for $streetname"); + } + + // Add to list + if (searchResults.children.length <= 13) { + searchResults.append(result); + } else { + break; + } + } + } + } } \ No newline at end of file From df84b0600b853277b38666b0b49f0449ffe55583 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Sat, 9 Jan 2016 14:27:15 -0500 Subject: [PATCH 35/43] fixes ChildrenOfUr/cou-issues#384 (bag all update visually when only one should) --- lib/src/display/inv_dragging.dart | 6 ++ lib/src/display/windows/bag_window.dart | 68 +++++--------- lib/src/network/server_interop/inventory.dart | 3 +- lib/src/network/server_interop/so_item.dart | 88 ++++++++++--------- 4 files changed, 75 insertions(+), 90 deletions(-) diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index 70970112..276d893e 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -94,6 +94,12 @@ class InvDragging { _move["toBagIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); } else { _move["toIndex"] = int.parse(e.dropzoneElement.dataset["slot-num"]); + + //moving an item from one slot on the inventory bar to another shouldn't fail + //so even with that big assumption, we should tell bag windows associated with + //the old slot index that they are now to be associated with the new slot index + //this way they can listen to metadata update messages based on index + BagWindow.updateSourceSlot(_move['fromIndex'],_move['toIndex']); } sendAction("moveItem", "global_action_monster", _move); diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index 3125be82..ec097064 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -17,6 +17,15 @@ class BagWindow extends Modal { return (querySelectorAll("#windowHolder > .bagWindow").length > 0); } + static void updateSourceSlot(int oldSlotIndex, int newSlotIndex) { + for (BagWindow w in bagWindows) { + if (w.sourceSlotNum == oldSlotIndex) { + w.sourceSlotNum = newSlotIndex; + break; + } + } + } + String id, bagId; int numSlots, sourceSlotNum; @@ -68,8 +77,12 @@ class BagWindow extends Modal { }); }); - new Service(['updateMetadata'], (sourceItem) async { - this.sourceItem = sourceItem; + new Service(['updateMetadata'], (Map indexToItem) async { + int index = indexToItem['index']; + if(index != sourceSlotNum) { + return; + } + this.sourceItem = indexToItem['item']; if(displayElement.hidden) { dataUpdated = true; } else { @@ -151,6 +164,7 @@ class BagWindow extends Modal { throw new StateError("Number of slots in bag does not match bag size"); } else { int slotNum = 0; + well.style.opacity = '0'; document.body.append(well); //for measuring await Future.forEach(subSlots, (Map bagSlot) async { DivElement slot = new DivElement(); @@ -164,11 +178,14 @@ class BagWindow extends Modal { slot.append(itemInSlot); if (!bagSlot["itemType"].isEmpty) { ItemDef item = decode(JSON.encode(bagSlot['item']), type: ItemDef); - await _sizeItem(slot, itemInSlot, item, bagSlot['count'], slotNum); + ImageElement img = new ImageElement(src: item.spriteUrl); + String className = 'item-${item.itemType} inventoryItem bagInventoryItem'; + await sizeItem(img,itemInSlot,slot,item,bagSlot['count'],cssClass: className); } slotNum++; }); + well.style.opacity = '1'; //we're done measuring now well.remove(); } @@ -188,51 +205,6 @@ class BagWindow extends Modal { } } - Future _sizeItem(Element slot, Element item, ItemDef i, int count, int bagSlotIndex) async { - ImageElement img; - img = new ImageElement(src: i.spriteUrl) - ..onLoad.listen((_) { - num scale = 1; - if (img.height > img.width / i.iconNum) { - scale = (slot.contentEdge.height - 10) / img.height; - } else { - scale = (slot.contentEdge.width - 10) / (img.width / i.iconNum); - } - - item - ..classes.addAll(["item-${i.itemType}", "inventoryItem", "bagInventoryItem"]) - ..attributes["name"] = i.name - ..attributes["count"] = count.toString() - ..attributes["itemmap"] = encode(i) - ..style.width = (slot.contentEdge.width - 10).toString() + "px" - ..style.height = (slot.contentEdge.height - 10).toString() + "px" - ..style.backgroundImage = 'url(${i.spriteUrl})' - ..style.backgroundRepeat = 'no-repeat' - ..style.backgroundSize = "${img.width * scale}px ${img.height * scale}px" - ..style.margin = "auto"; - - int offset = count; - if (i.iconNum != null && i.iconNum < count) { - offset = i.iconNum; - } - - item.style.backgroundPosition = "calc(100% / ${i.iconNum - 1} * ${offset - 1}"; - - String slotString = '$sourceSlotNum.$bagSlotIndex'; - item.onContextMenu.listen((MouseEvent event) => itemContextMenu(i, slotString, event)); - if (count > 1) { - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - item.parent.append(itemCount); - } else if (item.parent.querySelector(".itemCount") != null) { - item.parent - .querySelector(".itemCount") - .text = ""; - } - }); - } - @override open() { super.open(); diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 59a21cff..5272d239 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -92,7 +92,8 @@ Future updateInventory([Map map]) async { update = true; } else if (currentSlot.item != null && newSlot.item != null && !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { - transmit('updateMetadata', newSlot.item); + Map indexToItem = {'index':i,'item':newSlot.item}; + transmit('updateMetadata', indexToItem); } } diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index 640dfc69..634cb600 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -45,51 +45,11 @@ Future findNewSlot(Slot slot, int index, {bool update: false}) async { //create an image for this item and wait for it to load before sizing/positioning it ImageElement img = new ImageElement(src: item.spriteUrl); - await img.onLoad.first; - Element barSlot = view.inventory.children.elementAt(index); barSlot.children.clear(); - String cssName = item.itemType.replaceAll(" ", "_"); Element itemDiv = new DivElement(); - //determine what we need to scale the sprite image to in order to fit - num scale = 1; - if (img.height > img.width / item.iconNum) { - scale = (barSlot.contentEdge.height - 10) / img.height; - } else { - scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); - } - - itemDiv.style.width = (barSlot.contentEdge.width - 10).toString() + "px"; - itemDiv.style.height = (barSlot.contentEdge.height - 10).toString() + "px"; - itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; - itemDiv.style.backgroundRepeat = 'no-repeat'; - itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; - itemDiv.style.backgroundPosition = "0 50%"; - itemDiv.style.margin = "auto"; - itemDiv.className = 'item-$cssName inventoryItem'; - - itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); - itemDiv.attributes['count'] = "1"; - itemDiv.attributes['itemMap'] = encode(item); - - String slotNum = '${barSlot.dataset["slot-num"]}.-1'; - itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); - barSlot.append(itemDiv); - - SpanElement itemCount = new SpanElement() - ..text = count.toString() - ..className = "itemCount"; - barSlot.append(itemCount); - if (count <= 1) { - itemCount.text = ""; - } - - int offset = count; - if (item.iconNum != null && item.iconNum < count) { - offset = item.iconNum; - } - itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; + await sizeItem(img,itemDiv,barSlot,item,count); if (!update) { itemDiv.classes.add("bounce"); @@ -125,6 +85,52 @@ Future findNewSlot(Slot slot, int index, {bool update: false}) async { } } +Future sizeItem(ImageElement img, Element itemDiv, Element slot, ItemDef item, int count, {String cssClass}) async { + await img.onLoad.first; + + //determine what we need to scale the sprite image to in order to fit + num scale = 1; + if (img.height > img.width / item.iconNum) { + scale = (slot.contentEdge.height - 10) / img.height; + } else { + scale = (slot.contentEdge.width - 10) / (img.width / item.iconNum); + } + + if (cssClass == null) { + cssClass = 'item-${item.itemType} inventoryItem'; + } + itemDiv.style.width = (slot.contentEdge.width - 10).toString() + "px"; + itemDiv.style.height = (slot.contentEdge.height - 10).toString() + "px"; + itemDiv.style.backgroundImage = 'url(${item.spriteUrl})'; + itemDiv.style.backgroundRepeat = 'no-repeat'; + itemDiv.style.backgroundSize = "${img.width * scale}px ${img.height * scale}px"; + itemDiv.style.backgroundPosition = "0 50%"; + itemDiv.style.margin = "auto"; + itemDiv.className = cssClass; + + itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); + itemDiv.attributes['count'] = "1"; + itemDiv.attributes['itemMap'] = encode(item); + + String slotNum = '${slot.dataset["slot-num"]}.-1'; + itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); + slot.append(itemDiv); + + SpanElement itemCount = new SpanElement() + ..text = count.toString() + ..className = "itemCount"; + slot.append(itemCount); + if (count <= 1) { + itemCount.text = ""; + } + + int offset = count; + if (item.iconNum != null && item.iconNum < count) { + offset = item.iconNum; + } + itemDiv.style.backgroundPosition = "calc(100% / ${item.iconNum - 1} * ${offset - 1})"; +} + void takeItemFromInventory(String itemType, {int count: 1}) { String cssName = itemType.replaceAll(" ", "_"); int remaining = count; From 829aa2e69b51b2359bea6b31769d0aa1d6eaef22 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Sat, 9 Jan 2016 14:46:43 -0500 Subject: [PATCH 36/43] fixes ChildrenOfUr/cou-issues#284 (vendor item selection not updating) --- lib/src/display/windows/vendor_window.dart | 36 ++- lib/src/display/windows/windows.dart | 325 +++++++++++---------- 2 files changed, 194 insertions(+), 167 deletions(-) diff --git a/lib/src/display/windows/vendor_window.dart b/lib/src/display/windows/vendor_window.dart index 311a48ea..160c1732 100644 --- a/lib/src/display/windows/vendor_window.dart +++ b/lib/src/display/windows/vendor_window.dart @@ -8,6 +8,7 @@ class VendorWindow extends Modal { Element buyMax, buyButton, buyItemCount, buyPriceTag, buyDescription, buyStacksTo, amtSelector; ImageElement buyItemImage; InputElement buyNum; + List listeners = []; factory VendorWindow() { if(vendorWindow == null) { @@ -55,6 +56,19 @@ class VendorWindow extends Modal { this.focus(); } + @override + openTab(String tabId) { + cleanupListeners(); + super.openTab(tabId); + } + + void cleanupListeners() { + // Clean up our event listeners + for(StreamSubscription s in listeners) { + s.cancel(); + } + } + // Calling the modal with a vendorMap opens a vendor window call(Map vendorMap, {bool sellMode:false}) { npcId = vendorMap['id']; @@ -182,7 +196,7 @@ class VendorWindow extends Modal { }); // Sell/Buy Button - StreamSubscription bb = buyButton.onClick.listen((_) { + listeners.add(buyButton.onClick.listen((_) { int newValue; Map actionMap = {"itemType": item['itemType'], "num": numToBuy}; @@ -205,10 +219,10 @@ class VendorWindow extends Modal { currants.text = " ${commaFormatter.format(newValue)} currant${(newValue != 1 ? "s" : "")}"; backToBuy.click(); - }); + })); // Plus Button - StreamSubscription bplus = buyPlus.onClick.listen((_) async { + listeners.add(buyPlus.onClick.listen((_) async { try { if (sellMode) { @@ -235,10 +249,10 @@ class VendorWindow extends Modal { catch(e) { logmessage("[Vendor] Plus Button Error: $e"); } - }); + })); // Minus Button - StreamSubscription bminus = buyMinus.onClick.listen((_) { + listeners.add(buyMinus.onClick.listen((_) { try { if (buyNum.valueAsNumber > 1) { // We can't go to 0 or negative @@ -249,10 +263,10 @@ class VendorWindow extends Modal { catch(e) { logmessage("[Vendor] Minus Button Error: $e"); } - }); + })); // Max Button - StreamSubscription bmax = buyMax.onClick.listen((_) async { + listeners.add(buyMax.onClick.listen((_) async { try { int newNum; if(sellMode) { @@ -267,14 +281,10 @@ class VendorWindow extends Modal { catch(e) { logmessage("[Vendor] Max Button Error: $e"); } - }); + })); backToBuy.onClick.first.then((_) { - // Clean up our event listeners - bb.cancel(); - bminus.cancel(); - bplus.cancel(); - bmax.cancel(); + cleanupListeners(); this.displayElement.querySelector('#buy-qty').hidden = true; if(sellMode) { diff --git a/lib/src/display/windows/windows.dart b/lib/src/display/windows/windows.dart index 2628973f..b607af99 100644 --- a/lib/src/display/windows/windows.dart +++ b/lib/src/display/windows/windows.dart @@ -5,176 +5,193 @@ class WindowManager { BugWindow bugs; VendorWindow vendor; MotdWindow motdWindow; + //GoWindow goWindow; CalendarWindow calendarWindow; RockWindow rockWindow; EmoticonPicker emoticonPicker; - WindowManager() { - // Declaring all the possible popup windows - settings = new SettingsWindow(); - mapWindow = new MapWindow(); - bugs = new BugWindow(); - vendor = new VendorWindow(); - motdWindow = new MotdWindow(); - //goWindow = new GoWindow(); - calendarWindow = new CalendarWindow(); - rockWindow = new RockWindow(); - emoticonPicker = new EmoticonPicker(); - } - - static int get randomId => random.nextInt(9999999); + WindowManager() { + // Declaring all the possible popup windows + settings = new SettingsWindow(); + mapWindow = new MapWindow(); + bugs = new BugWindow(); + vendor = new VendorWindow(); + motdWindow = new MotdWindow(); + //goWindow = new GoWindow(); + calendarWindow = new CalendarWindow(); + rockWindow = new RockWindow(); + emoticonPicker = new EmoticonPicker(); + } + + static int get randomId => random.nextInt(9999999); } class AuctionWindow extends Modal { - String id = 'auctionWindow'; + String id = 'auctionWindow'; - AuctionWindow() { - prepare(); - } + AuctionWindow() { + prepare(); + } } class MailboxWindow extends Modal { - String id = 'mailboxWindow'; - - MailboxWindow() { - prepare(); - } - - open() { - (querySelector("ur-mailbox") as Mailbox).refresh(); - inputManager.ignoreKeys = true; - super.open(); - } - - close() { - inputManager.ignoreKeys = false; - super.close(); - } + String id = 'mailboxWindow'; + + MailboxWindow() { + prepare(); + } + + open() { + (querySelector("ur-mailbox") as Mailbox).refresh(); + inputManager.ignoreKeys = true; + super.open(); + } + + close() { + inputManager.ignoreKeys = false; + super.close(); + } } Map modals = {}; /// A Dart interface to an html Modal abstract class Modal extends InformationDisplay { - String id; - StreamSubscription escListener; - - open() { - displayElement.hidden = false; - elementOpen = true; - this.focus(); - } - - close() { - _destroyEscListener(); - displayElement.hidden = true; - elementOpen = false; - - //see if there's another window that we want to focus - for (Element modal in querySelectorAll('.window')) { - if (!modal.hidden) { - modals[modal.id].focus(); - } - } - } - - focus() { - for (Element others in querySelectorAll('.window')) { - others.style.zIndex = '2'; - } - this.displayElement.style.zIndex = '3'; - displayElement.focus(); - } - - _createEscListener() { - if (escListener != null) { - return; - } - - escListener = document.onKeyUp.listen((KeyboardEvent e) { - //27 == esc key - if (e.keyCode == 27) { - close(); - } - }); - } - - _destroyEscListener() { - if (escListener != null) { - escListener.cancel(); - escListener = null; - } - } - - prepare() { - // GET 'window' //////////////////////////////////// - displayElement = querySelector('#$id'); - - // CLOSE BUTTON //////////////////////////////////// - if (displayElement.querySelector('.fa-times.close') != null) { - displayElement.querySelector('.fa-times.close').onClick.listen((_) => this.close()); - } - - // PREVENT PLAYER MOVEMENT WHILE WINDOW IS FOCUSED / - displayElement.querySelectorAll('input, textarea').onFocus.listen((_) { - inputManager.ignoreKeys = true; - inputManager.ignoreChatFocus = true; - }); - displayElement.querySelectorAll('input, textarea').onBlur.listen((_) { - inputManager.ignoreKeys = false; - inputManager.ignoreChatFocus = false; - }); - - //make div focusable. see: http://stackoverflow.com/questions/11280379/is-it-possible-to-write-onfocus-lostfocus-handler-for-a-div-using-js-or-jquery - displayElement.tabIndex = -1; - - displayElement.onFocus.listen((_) => _createEscListener()); - displayElement.onBlur.listen((_) => _destroyEscListener()); - - // TABS //////////////////////////////////////////// - displayElement.querySelectorAll('.tab').onClick.listen((MouseEvent m) { - Element tab = m.target as Element; - openTab(tab.text); - // show intended tab - tab.classes.add('active'); - }); - - // DRAGGING //////////////////////////////////////// - // init vars - if (displayElement.querySelector('header') != null) { - int new_x = 0, new_y = 0; - bool dragging = false; - - // mouse down listeners - displayElement.onMouseDown.listen((_) => this.focus()); - displayElement.querySelector('header').onMouseDown.listen((_) => dragging = true); - - // mouse is moving - document.onMouseMove.listen((MouseEvent m) { - if (dragging == true) { - new_x += m.movement.x; - new_y += m.movement.y; - displayElement.style - ..top = 'calc(50% + ${new_y}px)' - ..left = 'calc(50% + ${new_x}px)'; - } - }); - - // mouseUp listener - document.onMouseUp.listen((_) => dragging = false); - - modals[id] = this; - } - } - - openTab(String tabID) { - Element tabView = displayElement.querySelector('article #${tabID.toLowerCase()}'); - // hide all tabs - for (Element t in displayElement.querySelectorAll('article .tab-content')) - t.hidden = true; - for (Element t in displayElement.querySelectorAll('article .tab')) - t.classes.remove('active'); - tabView.hidden = false; - } + String id; + StreamSubscription escListener; + + open() { + displayElement.hidden = false; + elementOpen = true; + this.focus(); + } + + close() { + _destroyEscListener(); + displayElement.hidden = true; + elementOpen = false; + + //see if there's another window that we want to focus + for (Element modal in querySelectorAll('.window')) { + if (!modal.hidden) { + modals[modal.id].focus(); + } + } + } + + focus() { + for (Element others in querySelectorAll('.window')) { + others.style.zIndex = '2'; + } + this.displayElement.style.zIndex = '3'; + displayElement.focus(); + } + + _createEscListener() { + if (escListener != null) { + return; + } + + escListener = document.onKeyUp.listen((KeyboardEvent e) { + //27 == esc key + if (e.keyCode == 27) { + close(); + } + }); + } + + _destroyEscListener() { + if (escListener != null) { + escListener.cancel(); + escListener = null; + } + } + + prepare() { + // GET 'window' //////////////////////////////////// + displayElement = querySelector('#$id'); + + // CLOSE BUTTON //////////////////////////////////// + if (displayElement.querySelector('.fa-times.close') != null) { + displayElement + .querySelector('.fa-times.close') + .onClick + .listen((_) => this.close()); + } + + // PREVENT PLAYER MOVEMENT WHILE WINDOW IS FOCUSED / + displayElement + .querySelectorAll('input, textarea') + .onFocus + .listen((_) { + inputManager.ignoreKeys = true; + inputManager.ignoreChatFocus = true; + }); + displayElement + .querySelectorAll('input, textarea') + .onBlur + .listen((_) { + inputManager.ignoreKeys = false; + inputManager.ignoreChatFocus = false; + }); + + //make div focusable. see: http://stackoverflow.com/questions/11280379/is-it-possible-to-write-onfocus-lostfocus-handler-for-a-div-using-js-or-jquery + displayElement.tabIndex = -1; + + displayElement.onFocus.listen((_) => _createEscListener()); + displayElement.onBlur.listen((_) => _destroyEscListener()); + + // TABS //////////////////////////////////////////// + displayElement + .querySelectorAll('.tab') + .onClick + .listen((MouseEvent m) { + Element tab = m.target as Element; + openTab(tab.text); + // show intended tab + tab.classes.add('active'); + }); + + // DRAGGING //////////////////////////////////////// + // init vars + if (displayElement.querySelector('header') != null) { + int new_x = 0, + new_y = 0; + bool dragging = false; + + // mouse down listeners + displayElement.onMouseDown.listen((_) => this.focus()); + displayElement + .querySelector('header') + .onMouseDown + .listen((_) => dragging = true); + + // mouse is moving + document.onMouseMove.listen((MouseEvent m) { + if (dragging == true) { + new_x += m.movement.x; + new_y += m.movement.y; + displayElement.style + ..top = 'calc(50% + ${new_y}px)' + ..left = 'calc(50% + ${new_x}px)'; + } + }); + + // mouseUp listener + document.onMouseUp.listen((_) => dragging = false); + + modals[id] = this; + } + } + + openTab(String tabID) { + Element tabView = displayElement.querySelector('article #${tabID.toLowerCase()}'); + // hide all tabs + for (Element t in displayElement.querySelectorAll('article .tab-content')) + t.hidden = true; + for (Element t in displayElement.querySelectorAll('article .tab')) + t.classes.remove('active'); + tabView.hidden = false; + } } From ba1f788c4b0ac55b57e3bcaee0a2b22e6d48f018 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Sat, 9 Jan 2016 15:04:16 -0500 Subject: [PATCH 37/43] don't make client send move action to server if the item slot is the same --- lib/src/display/inv_dragging.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index 276d893e..93383558 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -99,7 +99,19 @@ class InvDragging { //so even with that big assumption, we should tell bag windows associated with //the old slot index that they are now to be associated with the new slot index //this way they can listen to metadata update messages based on index - BagWindow.updateSourceSlot(_move['fromIndex'],_move['toIndex']); + BagWindow.updateSourceSlot(_move['fromIndex'], _move['toIndex']); + } + + if (_move['fromIndex'] == _move['toIndex'] && + (_move['fromBagIndex'] == null ||_move['toBagIndex'] == null)) { + //don't send the action to the server if the item was dropped in place + return; + } + + if (_move['fromIndex'] == _move['toIndex'] && + (_move['fromBagIndex'] == _move['toBagIndex'])) { + //don't send the action to the server if the item was dropped in place + return; } sendAction("moveItem", "global_action_monster", _move); From 91184c2467a3776fe15321bb321ed885b82d49de Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 9 Jan 2016 14:53:21 -0600 Subject: [PATCH 38/43] #gitblamerobert --- lib/src/network/metabolics_service.dart | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/lib/src/network/metabolics_service.dart b/lib/src/network/metabolics_service.dart index dae189fd..f3e39165 100644 --- a/lib/src/network/metabolics_service.dart +++ b/lib/src/network/metabolics_service.dart @@ -32,22 +32,14 @@ class MetabolicsService { Map map = JSON.decode(event.data); if (map['collectQuoin'] != null && map['collectQuoin'] == "true") { collectQuoin(map); + } else if (map["levelUp"] != null) { + levelUp.open(map["levelUp"]); } else { - int oldImg = lifetime_img; - int oldLevel = await level; - playerMetabolics = decode(event.data, type: Metabolics); + playerMetabolics = decode(event.data, type:Metabolics); if (!load.isCompleted) { load.complete(); transmit("metabolicsLoaded", playerMetabolics); } - int newImg = lifetime_img; - int newLvl; - if (newImg > oldImg) { - newLvl = await level; - if (oldLevel > newLvl - 2 && newLvl > oldLevel && webSocketMessages > 0) { - levelUp.open(); - } - } transmit('metabolicsUpdated', playerMetabolics); } update(); @@ -153,20 +145,17 @@ class MetabolicsService { List get location_history => JSON.decode(playerMetabolics.location_history); Future get level async { - String lvlStr = await HttpRequest.getString( - "http://${Configs.utilServerAddress}/getLevel?img=${img.toString()}"); + String lvlStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getLevel?img=${lifetime_img.toString()}"); return int.parse(lvlStr); } Future get img_req_for_curr_lvl async { - String imgStr = await HttpRequest.getString( - "http://${Configs.utilServerAddress}/getImgForLevel?level=${(await level).toString()}"); + String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${(await level).toString()}"); return int.parse(imgStr); } Future get img_req_for_next_lvl async { - String imgStr = await HttpRequest.getString( - "http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); + String imgStr = await HttpRequest.getString("http://${Configs.utilServerAddress}/getImgForLevel?level=${((await level) + 1).toString()}"); return int.parse(imgStr); } -} +} \ No newline at end of file From a2cf0d6c5c7b1f3456304ab46b377eaf5e110765 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Sat, 9 Jan 2016 17:17:48 -0500 Subject: [PATCH 39/43] fix bug with dropping from bags and some other stuff like user left messages --- lib/src/display/chatmessage.dart | 339 +++-- lib/src/display/chatpanel.dart | 1257 +++++++++-------- lib/src/display/toast.dart | 177 +-- lib/src/display/windows/bag_window.dart | 5 +- lib/src/display/windows/note_window.dart | 47 +- lib/src/display/windows/settings_window.dart | 5 +- lib/src/network/server_interop/so_item.dart | 6 +- lib/src/network/server_interop/so_player.dart | 2 + lib/src/network/streetservice.dart | 4 +- 9 files changed, 940 insertions(+), 902 deletions(-) diff --git a/lib/src/display/chatmessage.dart b/lib/src/display/chatmessage.dart index 80de4a7e..e42d00ce 100644 --- a/lib/src/display/chatmessage.dart +++ b/lib/src/display/chatmessage.dart @@ -1,112 +1,111 @@ part of couclient; class ChatMessage { - String player, message; + String player, message; - ChatMessage(this.player, this.message); + ChatMessage(this.player, this.message); - Future toHtml() async { - if (message is! String) { - return ''; - } - String html, displayName = player; + Future toHtml() async { + if (message is! String) { + return ''; + } + String html, + displayName = player; - message = parseUrl(message); - message = parseEmoji(message); - message = parseLocationLinks(message); - message = parseItemLinks(message); + message = parseUrl(message); + message = parseEmoji(message); + message = parseLocationLinks(message); + message = parseItemLinks(message); - if ( + if ( message.toLowerCase().contains(game.username.toLowerCase()) && windowManager.settings.playMentionSound - ) { - // Popup - Notification.requestPermission(); - new Notification( - player, - body: message, - icon: "http://childrenofur.com/assets/icon_72.png" - ); - - // Sound effect - transmit('playSound', 'mention'); - } - - // Apply labels - String nameClass = "name "; - if (player != null) { - // You - if (player == game.username) { - nameClass += "you "; - } - // Dev/Guide - if (game.devs.contains(player)) { - nameClass += "dev "; - } else if (game.guides.contains(player)) { - nameClass += "guide "; - } - } - - if (game.username == player) { - displayName = "You"; - } - - if (player == null) { - // System message - html = '

$message

'; - } else if (message.startsWith('/me')) { - // /me message - message = message.replaceFirst('/me ', ''); - html = - '

' - '$player $message' - '

'; - } else if (message == " joined." || message == " left.") { - // Player joined or left - if (game.username != player) { - html = - '

' - '$displayName ' - '$message' - '

'; - if (player != game.username) { - if (message == " joined.") { - toast("$player has arrived"); - } - if (message == " left.") { - toast("$player left"); - } - } - } else { - html = ""; - } - } else if (message == "LocationChangeEvent" && player == "invalid_user") { - // Switching streets message - void setLocationChangeEventHtml() { - String prefix = (metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) ? "Back" : "First time"); - html = - '

' - '$prefix in ${currentStreet.label}' - '

'; - } - - if (!metabolics.load.isCompleted) { - await metabolics.load.future; - setLocationChangeEventHtml(); - } else { - setLocationChangeEventHtml(); - } - } else { - // Normal message - html = - '

' - '$displayName: ' - '$message' - '

'; - } - - return html; - } + ) { + // Popup + new Notification( + player, + body: message, + icon: "http://childrenofur.com/assets/icon_72.png" + ); + + // Sound effect + transmit('playSound', 'mention'); + } + + // Apply labels + String nameClass = "name "; + if (player != null) { + // You + if (player == game.username) { + nameClass += "you "; + } + // Dev/Guide + if (game.devs.contains(player)) { + nameClass += "dev "; + } else if (game.guides.contains(player)) { + nameClass += "guide "; + } + } + + if (game.username == player) { + displayName = "You"; + } + + if (player == null) { + // System message + html = '

$message

'; + } else if (message.startsWith('/me')) { + // /me message + message = message.replaceFirst('/me ', ''); + html = + '

' + '$player $message' + '

'; + } else if (message == " joined." || message == " left.") { + // Player joined or left + if (game.username != player) { + if (player != game.username) { + if (message == " joined.") { + toast("$player has arrived"); + } + if (message == " left.") { + toast("$player left"); + } + } + } + + html = ""; + } else if (message == "LocationChangeEvent" && player == "invalid_user") { + // Switching streets message + void setLocationChangeEventHtml() { + String prefix = (metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) + ? "Back" + : "First time"); + html = + '

' + '$prefix in ${currentStreet + .label}' + '

'; + } + + if (!metabolics.load.isCompleted) { + await metabolics.load.future; + setLocationChangeEventHtml(); + } else { + setLocationChangeEventHtml(); + } + } else { + // Normal message + html = + '

' + '$displayName: ' + '$message' + '

'; + } + + return html; + } } // chat functions @@ -127,7 +126,7 @@ Future getColorFromUsername(String username) async { // Download color from server String color = await HttpRequest.getString( "http://${Configs.utilServerAddress}/usernamecolors/get/$username" - ); + ); // Cache for later use cachedUsernameColors[username] = color; // Return for display @@ -136,88 +135,88 @@ Future getColorFromUsername(String username) async { } String parseEmoji(String message) { - String returnString = ""; - RegExp regex = new RegExp(":(.+?):"); - message.splitMapJoin(regex, onMatch: (Match m) { - String match = m[1]; - if (EMOTICONS.contains(match)) { - returnString += ''; - } else { - returnString += m[0]; - } - }, onNonMatch: (String s) => returnString += s); - - return returnString; + String returnString = ""; + RegExp regex = new RegExp(":(.+?):"); + message.splitMapJoin(regex, onMatch: (Match m) { + String match = m[1]; + if (EMOTICONS.contains(match)) { + returnString += ''; + } else { + returnString += m[0]; + } + }, onNonMatch: (String s) => returnString += s); + + return returnString; } String parseUrl(String message) { - /* + /* (https?:\/\/)? : the http or https schemes (optional) [\w-]+(\.[\w-]+)+\.? : domain name with at least two components; allows a trailing dot (:\d+)? : the port (optional) (\/\S*)? : the path (optional) */ - String regexString = r"((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)"; - //the r before the string makes dart interpret it as a raw string so that you don't have to escape characters like \ - - String returnString = ""; - RegExp regex = new RegExp(regexString); - message.splitMapJoin(regex, onMatch: (Match m) { - String url = m[0]; - if (!url.contains("http")) { - url = "http://" + url; - } - returnString += '${m[0]}'; - }, onNonMatch: (String s) => returnString += s); - - return returnString; + String regexString = r"((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)"; + //the r before the string makes dart interpret it as a raw string so that you don't have to escape characters like \ + + String returnString = ""; + RegExp regex = new RegExp(regexString); + message.splitMapJoin(regex, onMatch: (Match m) { + String url = m[0]; + if (!url.contains("http")) { + url = "http://" + url; + } + returnString += '${m[0]}'; + }, onNonMatch: (String s) => returnString += s); + + return returnString; } String parseItemLinks(String message) { - String returnString = ""; - RegExp regex = new RegExp("#(.+?)#"); - (message.splitMapJoin(regex, onMatch: (Match m) { - String match = m[1]; - if (Item.isItem(itemType: match)) { - String name = Item.getName(match); - String iconUrl = Item.getIcon(itemType: match); - returnString += '' - '' - '$name'; - } else { - returnString += m[0]; - } - }, onNonMatch: (String s) => returnString += s)); - - return returnString; + String returnString = ""; + RegExp regex = new RegExp("#(.+?)#"); + (message.splitMapJoin(regex, onMatch: (Match m) { + String match = m[1]; + if (Item.isItem(itemType: match)) { + String name = Item.getName(match); + String iconUrl = Item.getIcon(itemType: match); + returnString += '' + '' + '$name'; + } else { + returnString += m[0]; + } + }, onNonMatch: (String s) => returnString += s)); + + return returnString; } String parseLocationLinks(String message) { - String _parseHubLinks(String _message) { - mapData.hubNames.forEach((String hubName) { - _message = _message.replaceAll( - hubName, - '' - '$hubName' - ); - }); - - return _message; - } - - String _parseStreetLinks(String _message) { - mapData.streetNames.forEach((String streetName) { - _message = _message.replaceAll( - streetName, - '' - '$streetName' - ); - }); - - return _message; - } - - return _parseStreetLinks(_parseHubLinks(message)); + String _parseHubLinks(String _message) { + mapData.hubNames.forEach((String hubName) { + _message = _message.replaceAll( + hubName, + '' + '$hubName' + ); + }); + + return _message; + } + + String _parseStreetLinks(String _message) { + mapData.streetNames.forEach((String streetName) { + _message = _message.replaceAll( + streetName, + '' + '$streetName' + ); + }); + + return _message; + } + + return _parseStreetLinks(_parseHubLinks(message)); } diff --git a/lib/src/display/chatpanel.dart b/lib/src/display/chatpanel.dart index 530c785d..d9e3f5c4 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -2,633 +2,654 @@ part of couclient; // Chats class Chat { - String title, lastWord = ""; - bool online, focused = false, tabInserted = false; - Element conversationElement, trigger; - int unreadMessages = 0, - tabSearchIndex = 0, - numMessages = 0, - inputHistoryPointer = 0, - emoticonPointer = 0; - static Chat otherChat = null, localChat = null; - List connectedUsers = new List(), inputHistory = new List(); - static StreamSubscription itemWindowLinks, mapWindowLinks; - static InputElement lastFocusedInput; - - static final NodeValidatorBuilder validator = new NodeValidatorBuilder() - ..allowHtml5() - ..allowElement('span', attributes: ['style']) // Username colors, item icons - ..allowElement('a', attributes: ['href', 'title', 'target', 'class']) // Links - ..allowElement('i', attributes: ['class', 'title']) // Emoticons - ..allowElement('p', attributes: ['style']) - ..allowElement('b') - ..allowElement('del'); - - // /me text - - // Emoticons - - bool get archived { - return !conversationElement.classes.contains('conversation'); - } - - void focus() { - this.focused = true; - conversationElement.querySelector("input").focus(); - } - - void blur() { - this.focused = false; - } - - Chat(this.title) { - title = title.trim(); - - // find the link in the chat panel that opens the chat - if (title != "Local Chat") { - trigger = - querySelectorAll("#rightSide *").where((Element e) => e.dataset["chat"] == title).first; - } - - //look for an 'archived' version of this chat - //otherwise create a new one - conversationElement = getArchivedConversation(title); - if (conversationElement == null) { - // start a timer for the first global chat created that refreshes the sidebar player list - if (title == "Global Chat") { - refreshOnlinePlayers(); - new Timer.periodic(new Duration(seconds: 5), (_) => refreshOnlinePlayers()); - } - - // clone the template - conversationElement = view.chatTemplate.querySelector('.conversation').clone(true); - Map emoticonArgs = { - "title": title, - "input": conversationElement.querySelector("input") - }; - conversationElement - ..querySelector('.title').text = title - ..querySelector(".insertemoji").onClick.listen((_) => transmit('insertEmoji', emoticonArgs)) - ..id = "chat-$title"; - openConversations.insert(0, this); - - if (title == "Local Chat") { - new Service(["gameLoaded", "streetLoaded"], (_) async { - // Streets loaded, display a divider - await this.addMessage("invalid_user", "LocationChangeEvent"); - // If this is the first one, empty the toast buffer into the chat - if (chatToastBuffer.length > 0) { - chatToastBuffer.forEach((String message) => this.addAlert(message, toast: true)); - chatToastBuffer.clear(); - } - }); - } - - //handle chat input getting focused/unfocused so that the character doesn't move while typing - InputElement chatInput = conversationElement.querySelector('input'); - chatInput.onFocus.listen((_) { - inputManager.ignoreKeys = true; - //need to set the focused variable to true and false for all the others - openConversations.forEach((Chat c) => c.blur()); - focus(); - transmit("worldFocus", false); - lastFocusedInput = chatInput; - }); - chatInput.onBlur.listen((_) { - inputManager.ignoreKeys = false; - //we'll want to set the focused to false for this chat - blur(); - }); - } else { - // mark as read - trigger.classes.remove("unread"); - NetChatManager.updateTabUnread(); - } - - if (title != "Local Chat") { - if (localChat != null) { - view.panel.insertBefore(conversationElement, localChat.conversationElement); - } else { - view.panel.append(conversationElement); - } - - if (otherChat != null) { - otherChat.archiveConversation(); - focus(); - } - - otherChat = this; - } - //don't ever have 2 local chats - else if (localChat == null) { - localChat = this; - view.panel.append(conversationElement); - //can't remove the local chat - conversationElement.querySelector('.fa-chevron-down').remove(); - } - - computePanelSize(); - - Element minimize = conversationElement.querySelector('.fa-chevron-down'); - if (minimize != null) { - minimize.onClick.listen((_) => this.archiveConversation()); - } - - processInput(conversationElement.querySelector('input')); - } - - void processEvent(Map data) { - if (data["message"] == " joined.") { - if (!connectedUsers.contains(data["username"])) { - connectedUsers.add(data["username"]); - } - } - - if (data["message"] == " left.") { - connectedUsers.remove(data["username"]); - removeOtherPlayer(data["username"]); - } - - if (data["statusMessage"] == "changeName") { - if (data["success"] == "true") { - removeOtherPlayer(data["username"]); - - //although this message is broadcast to everyone, only change usernames - //if we were the one to type /setname - if (data["newUsername"] == game.username) { - CurrentPlayer.username = data['newUsername']; - CurrentPlayer.loadAnimations(); - - //clear our inventory so we can get the new one - view.inventory.querySelectorAll('.box').forEach((Element box) => box.children.clear()); - firstConnect = true; - joined = ""; - sendJoinedMessage(currentStreet.label); - - //warn multiplayer server that it will receive messages - //from a new name but it should be the same person - data['street'] = currentStreet.label; - playerSocket.send(JSON.encode(data)); - - timeLast = 5.0; - } - - connectedUsers.remove(data["username"]); - connectedUsers.add(data["newUsername"]); - } - } else { - addMessage(data['username'], data['message']); - if (archived) { - trigger.classes.add("unread"); - NetChatManager.updateTabUnread(); - } - } - } - - Future addMessage(String player, String message) async { - ChatMessage chat = new ChatMessage(player, message); - Element dialog = conversationElement.querySelector('.dialog'); - String html = await chat.toHtml(); - // display in panel - dialog.appendHtml(html, validator: Chat.validator); - - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - - // check for and activate any item links - if (itemWindowLinks != null) { - // Cancel any existing links to prevent duplicate listeners - itemWindowLinks.cancel(); - } - if (dialog.querySelector(".item-chat-link") != null) { - itemWindowLinks = dialog.querySelectorAll(".item-chat-link").onClick.listen((Event e) { - e.preventDefault(); - if (e.target is AnchorElement) { - new ItemWindow(((e.target) as Element).text); - } - if (e.target is SpanElement) { - new ItemWindow(((e.target) as Element).parent.text); - } - }); - } - - // check for and activate any location links - if (mapWindowLinks != null) { - // Cancel any existing links to prevent duplicate listeners - mapWindowLinks.cancel(); - } - if (dialog.querySelector(".location-chat-link") != null) { - mapWindowLinks = dialog.querySelectorAll(".location-chat-link").onClick.listen((Event e) { - e.preventDefault(); - String text = ((e.target) as Element).text; - - if ((e.target as Element).classes.contains("hub-chat-link")) { - String hubId = mapData.getHubId(text); - new WorldMap(hubId).loadhubdiv(hubId, text); - } else if ((e.target as Element).classes.contains("street-chat-link")) { - String hubId = mapData.streetData[text]["hub_id"].toString(); - new WorldMap(hubId).hubMap(hub_id: hubId, highlightStreet: text); - } - - mapWindow.open(); - }); - } - } - - void addAlert(String alert, {bool toast: false}) { - String classes = "system "; - - void _add() { - String text = '

$alert

'; - Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(text, validator: validator); - - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - } - - if (toast) { - classes += "chat-toast "; - new Timer(new Duration(milliseconds: 100), () { - _add(); - }); - } else { - _add(); - } - } - - void displayList(List users) { - String alert = "Players in this channel:"; - - for (int i = 0; i != users.length; i++) { - users[i] = '' + - users[i] + - ''; - alert = alert + " " + users[i]; - } - - String text = '

$alert

'; - - Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(text, validator: validator); - - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; - } - - /** + String title, + lastWord = ""; + bool online, + focused = false, + tabInserted = false; + Element conversationElement, trigger; + int unreadMessages = 0, + tabSearchIndex = 0, + numMessages = 0, + inputHistoryPointer = 0, + emoticonPointer = 0; + static Chat otherChat = null, + localChat = null; + List connectedUsers = new List(), + inputHistory = new List(); + static StreamSubscription itemWindowLinks, mapWindowLinks; + static InputElement lastFocusedInput; + + static final NodeValidatorBuilder validator = new NodeValidatorBuilder() + ..allowHtml5() + ..allowElement('span', attributes: ['style']) // Username colors, item icons + ..allowElement('a', attributes: ['href', 'title', 'target', 'class']) // Links + ..allowElement('i', attributes: ['class', 'title']) // Emoticons + ..allowElement('p', attributes: ['style'])..allowElement('b')..allowElement('del'); + + // /me text + + // Emoticons + + bool get archived { + return !conversationElement.classes.contains('conversation'); + } + + void focus() { + this.focused = true; + conversationElement.querySelector("input").focus(); + } + + void blur() { + this.focused = false; + } + + Chat(this.title) { + title = title.trim(); + + // find the link in the chat panel that opens the chat + if (title != "Local Chat") { + trigger = + querySelectorAll("#rightSide *") + .where((Element e) => e.dataset["chat"] == title) + .first; + } + + //look for an 'archived' version of this chat + //otherwise create a new one + conversationElement = getArchivedConversation(title); + if (conversationElement == null) { + // start a timer for the first global chat created that refreshes the sidebar player list + if (title == "Global Chat") { + refreshOnlinePlayers(); + new Timer.periodic(new Duration(seconds: 5), (_) => refreshOnlinePlayers()); + } + + // clone the template + conversationElement = view.chatTemplate.querySelector('.conversation').clone(true); + Map emoticonArgs = { + "title": title, + "input": conversationElement.querySelector("input") + }; + conversationElement + ..querySelector('.title').text = title + ..querySelector(".insertemoji").onClick.listen((_) => transmit('insertEmoji', emoticonArgs)) + ..id = "chat-$title"; + openConversations.insert(0, this); + + if (title == "Local Chat") { + new Service(["gameLoaded", "streetLoaded"], (_) async { + // Streets loaded, display a divider + await this.addMessage("invalid_user", "LocationChangeEvent"); + // If this is the first one, empty the toast buffer into the chat + if (chatToastBuffer.length > 0) { + chatToastBuffer.forEach((String message) => this.addAlert(message, toast: true)); + chatToastBuffer.clear(); + } + }); + } + + //handle chat input getting focused/unfocused so that the character doesn't move while typing + InputElement chatInput = conversationElement.querySelector('input'); + chatInput.onFocus.listen((_) { + inputManager.ignoreKeys = true; + //need to set the focused variable to true and false for all the others + openConversations.forEach((Chat c) => c.blur()); + focus(); + transmit("worldFocus", false); + lastFocusedInput = chatInput; + }); + chatInput.onBlur.listen((_) { + inputManager.ignoreKeys = false; + //we'll want to set the focused to false for this chat + blur(); + }); + } else { + // mark as read + trigger.classes.remove("unread"); + NetChatManager.updateTabUnread(); + } + + if (title != "Local Chat") { + if (localChat != null) { + view.panel.insertBefore(conversationElement, localChat.conversationElement); + } else { + view.panel.append(conversationElement); + } + + if (otherChat != null) { + otherChat.archiveConversation(); + focus(); + } + + otherChat = this; + } + //don't ever have 2 local chats + else if (localChat == null) { + localChat = this; + view.panel.append(conversationElement); + //can't remove the local chat + conversationElement.querySelector('.fa-chevron-down').remove(); + } + + computePanelSize(); + + Element minimize = conversationElement.querySelector('.fa-chevron-down'); + if (minimize != null) { + minimize.onClick.listen((_) => this.archiveConversation()); + } + + processInput(conversationElement.querySelector('input')); + } + + void processEvent(Map data) { + if (data["message"] == " joined.") { + if (!connectedUsers.contains(data["username"])) { + connectedUsers.add(data["username"]); + } + } + + if (data["message"] == " left.") { + connectedUsers.remove(data["username"]); + removeOtherPlayer(data["username"]); + } + + if (data["statusMessage"] == "changeName") { + if (data["success"] == "true") { + removeOtherPlayer(data["username"]); + + //although this message is broadcast to everyone, only change usernames + //if we were the one to type /setname + if (data["newUsername"] == game.username) { + CurrentPlayer.username = data['newUsername']; + CurrentPlayer.loadAnimations(); + + //clear our inventory so we can get the new one + view.inventory.querySelectorAll('.box').forEach((Element box) => box.children.clear()); + firstConnect = true; + joined = ""; + sendJoinedMessage(currentStreet.label); + + //warn multiplayer server that it will receive messages + //from a new name but it should be the same person + data['street'] = currentStreet.label; + playerSocket.send(JSON.encode(data)); + + timeLast = 5.0; + } + + connectedUsers.remove(data["username"]); + connectedUsers.add(data["newUsername"]); + } + } else { + addMessage(data['username'], data['message']); + if (archived) { + trigger.classes.add("unread"); + NetChatManager.updateTabUnread(); + } + } + } + + Future addMessage(String player, String message) async { + ChatMessage chat = new ChatMessage(player, message); + Element dialog = conversationElement.querySelector('.dialog'); + String html = await chat.toHtml(); + // display in panel + dialog.appendHtml(html, validator: Chat.validator); + + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + + // check for and activate any item links + if (itemWindowLinks != null) { + // Cancel any existing links to prevent duplicate listeners + itemWindowLinks.cancel(); + } + if (dialog.querySelector(".item-chat-link") != null) { + itemWindowLinks = dialog + .querySelectorAll(".item-chat-link") + .onClick + .listen((Event e) { + e.preventDefault(); + if (e.target is AnchorElement) { + new ItemWindow(((e.target) as Element).text); + } + if (e.target is SpanElement) { + new ItemWindow(((e.target) as Element).parent.text); + } + }); + } + + updateChatLocationLinks(dialog); + } + + void updateChatLocationLinks(Element dialog) { + // check for and activate any location links + if (mapWindowLinks != null) { + // Cancel any existing links to prevent duplicate listeners + mapWindowLinks.cancel(); + } + if (dialog.querySelector(".location-chat-link") != null) { + mapWindowLinks = dialog + .querySelectorAll(".location-chat-link") + .onClick + .listen((Event e) { + e.preventDefault(); + String text = ((e.target) as Element).text; + + if ((e.target as Element).classes.contains("hub-chat-link")) { + String hubId = mapData.getHubId(text); + new WorldMap(hubId).loadhubdiv(hubId, text); + } else if ((e.target as Element).classes.contains("street-chat-link")) { + String hubId = mapData.streetData[text]["hub_id"].toString(); + new WorldMap(hubId).hubMap(hub_id: hubId, highlightStreet: text); + } + + mapWindow.open(); + }); + } + } + + void addAlert(String alert, {bool toast: false}) { + String classes = "system "; + + void _add() { + String text = '

$alert

'; + Element dialog = conversationElement.querySelector('.dialog'); + dialog.appendHtml(parseLocationLinks(text), validator: validator); + + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + + updateChatLocationLinks(dialog); + } + + if (toast) { + classes += "chat-toast "; + new Timer(new Duration(milliseconds: 100), () { + _add(); + }); + } else { + _add(); + } + } + + void displayList(List users) { + String alert = "Players in this channel:"; + + for (int i = 0; i != users.length; i++) { + users[i] = '' + + users[i] + + ''; + alert = alert + " " + users[i]; + } + + String text = '

$alert

'; + + Element dialog = conversationElement.querySelector('.dialog'); + dialog.appendHtml(text, validator: validator); + + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; + } + + /** * Archive the conversation (detach it from the chat panel) so that we may reattach * it later with the history intact. **/ - void archiveConversation() { - conversationElement.classes.add("archive-${title.replaceAll(' ', '_')}"); - conversationElement.classes.remove("conversation"); - view.conversationArchive.append(conversationElement); - computePanelSize(); - otherChat = null; - } - - /** + void archiveConversation() { + conversationElement.classes.add("archive-${title.replaceAll(' ', '_')}"); + conversationElement.classes.remove("conversation"); + view.conversationArchive.append(conversationElement); + computePanelSize(); + otherChat = null; + } + + /** * Find an archived conversation and return it * returns null if no conversation exists **/ - Element getArchivedConversation(String title) { - String archiveClass = '.archive-${title.replaceAll(' ', '_')}'; - Element conversationElement = view.conversationArchive.querySelector(archiveClass); - if (conversationElement != null) { - conversationElement.classes.remove(archiveClass); - conversationElement.classes.add("conversation"); - - //move this conversation to the front of the openConversations list - for (Chat c in openConversations) { - if (c.title == title) { - openConversations.remove(c); - openConversations.insert(0, c); - break; - } - } - } - return conversationElement; - } - - void computePanelSize() { - List conversations = view.panel.querySelectorAll('.conversation').toList(); - int num = conversations.length - 1; - conversations.forEach((Element conversation) { - if (conversation.hidden) { - num--; - } - }); - conversations.forEach((Element conversation) => conversation.style.height = "${100 / num}%"); - } - - void processInput(TextInputElement input) { - //onKeyUp seems to be too late to prevent TAB's default behavior - input.onKeyDown.listen((KeyboardEvent key) { - //pressed up arrow - if (key.keyCode == 38 && inputHistoryPointer < inputHistory.length) { - input.value = inputHistory.elementAt(inputHistoryPointer); - if (inputHistoryPointer < inputHistory.length - 1) { - inputHistoryPointer++; - } - } - //pressed down arrow - if (key.keyCode == 40) { - if (inputHistoryPointer > 0) { - inputHistoryPointer--; - input.value = inputHistory.elementAt(inputHistoryPointer); - } else { - input.value = ""; - } - } - //tab key, try to complete a user's name or an emoticon - if (input.value != "" && key.keyCode == 9) { - key.preventDefault(); - - //look for an emoticon instead of a username - if (input.value.endsWith(":")) { - emoticonComplete(input, key); - return; - } - - //let's suggest players to tab complete - tabComplete(input, key); - - return; - } - }); - - input.onKeyUp.listen((KeyboardEvent key) { - if (key.keyCode != 9) { - tabInserted = false; - } - - if (key.keyCode != 13) { - //listen for enter key - return; - } - - if (input.value.trim().length == 0) { - toast("You can't send a blank message"); - return; - } - - RegExp formatChars = new RegExp(r'|||||||'); - if (input.value.replaceAll(formatChars, '').length == 0) { - toast("You must have non-formatting content in your message"); - return; - } - - parseInput(input.value); - - inputHistory.insert(0, input.value); //add to beginning of list - inputHistoryPointer = 0; //point to beginning of list - if (inputHistory.length > 50) { - //don't allow the list to grow forever - inputHistory.removeLast(); - } - - input.value = ''; - }); - } - - void emoticonComplete(InputElement input, KeyboardEvent k) { - //don't allow a key like tab to change to a different chat - //if we don't get a hit and k=[tab], we will re-fire - k.stopImmediatePropagation(); - - String value = input.value; - bool emoticonInserted = false; - - //if the input is exactly one long (therefore it is just a colon) - if (value.length == 1) { - //String beforePart = value.substring(0,lastColon); - input.value = ":${EMOTICONS.elementAt(emoticonPointer)}:"; - emoticonPointer++; - emoticonInserted = true; - } - //if the input is more than 1 long and there's a space before the colon (word separation) - else if (value.endsWith(" :")) { - int lastColon = value.lastIndexOf(':'); - String beforePart = value.substring(0, lastColon); - input.value = "$beforePart:${EMOTICONS.elementAt(emoticonPointer)}:"; - emoticonPointer++; - emoticonInserted = true; - } - - //if the input is more than 1 long and there is an emoticon that we should replace - //to do this, we check if the value has 2 : and that the text between them - //exactly matches an emoticon - int previousColon = value.substring(0, value.length - 1).lastIndexOf(':'); - if (previousColon > -1) { - String beforeSegment = value.substring(0, previousColon); - String emoticonSegment = value.substring(previousColon, value.length); - for (int i = 0; i < EMOTICONS.length; i++) { - String emoticon = EMOTICONS[i]; - String emoticonNext; - if (i < EMOTICONS.length - 1) { - emoticonNext = EMOTICONS[i + 1]; - } else { - emoticonNext = EMOTICONS[0]; - } - if (emoticonSegment.contains(':$emoticon:')) { - input.value = "$beforeSegment:$emoticonNext:"; - emoticonPointer++; - emoticonInserted = true; - break; - } - } - } - - //make sure we don't point past the end of the array - if (emoticonPointer >= EMOTICONS.length) { - emoticonPointer = 0; - } - - //if we didn't manage to insert an emoticon and tab was pressed... - //try to advance chat focus because we stifled it earlier - if (!emoticonInserted && k.keyCode == 9) { - advanceChatFocus(k); - } - } - - Future tabComplete(TextInputElement input, KeyboardEvent k) async { - //don't allow a key like tab to change to a different chat - //if we don't get a hit and k=[tab], we will re-fire - k.stopImmediatePropagation(); - - String channel = 'Global Chat'; - bool inserted = false; - - if (title != channel) { - channel = currentStreet.label; - } - String url = 'http://' + Configs.utilServerAddress + "/listUsers?channel=$channel"; - connectedUsers = JSON.decode(await HttpRequest.requestCrossOrigin(url)); - - int startIndex = input.value.lastIndexOf(" ") == -1 ? 0 : input.value.lastIndexOf(" ") + 1; - String localLastWord = input.value.substring(startIndex); - if (!tabInserted) { - lastWord = input.value.substring(startIndex); - } - - for (; tabSearchIndex < connectedUsers.length; tabSearchIndex++) { - String username = connectedUsers.elementAt(tabSearchIndex); - if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && - username.toLowerCase() != localLastWord.toLowerCase()) { - input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; - tabInserted = true; - inserted = true; - tabSearchIndex++; - break; - } - } - //if we didn't find it yet and the tabSearchIndex was not 0, let's look at the beginning of the array as well - //otherwise the user will have to press the tab key again - if (!tabInserted) { - for (int index = 0; index < tabSearchIndex; index++) { - String username = connectedUsers.elementAt(index); - if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && - username.toLowerCase() != localLastWord.toLowerCase()) { - input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; - tabInserted = true; - inserted = true; - tabSearchIndex = index + 1; - break; - } - } - } - - if (!inserted && k.keyCode == 9) { - advanceChatFocus(k); - } - - if (tabSearchIndex == connectedUsers.length) { - //wrap around for next time - tabSearchIndex = 0; - } - } - - void parseInput(String input) { - // if its not a command, send it through. - if (parseCommand(input)) { - return; - } else if (input.toLowerCase() == "/list") { - Map map = {}; - map["username"] = game.username; - map["statusMessage"] = "list"; - map["channel"] = title; - map["street"] = currentStreet.label; - transmit('outgoingChatEvent', map); - } else { - Map map = new Map(); - map["username"] = game.username; - map["message"] = input; - map["channel"] = title; - if (title == "Local Chat") { - map["street"] = currentStreet.label; - } - - transmit('outgoingChatEvent', map); - - //display chat bubble if we're talking in local (unless it's a /me message) - if (map["channel"] == "Local Chat" && - !(map["message"] as String).toLowerCase().startsWith("/me")) { - //remove any existing bubble - if (CurrentPlayer.chatBubble != null && CurrentPlayer.chatBubble.bubble != null) { - CurrentPlayer.chatBubble.bubble.remove(); - } - CurrentPlayer.chatBubble = new ChatBubble( - parseEmoji(map["message"]), CurrentPlayer, CurrentPlayer.playerParentElement); - } - } - } - - // Update the list of online players in the sidebar - Future refreshOnlinePlayers() async { - if (this.title != "Global Chat") { - return -1; - } - - // Ignore yourself (can't chat with yourself, either) - List users = JSON.decode(await HttpRequest - .requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); - users.removeWhere((String username) => username == game.username); - - // Reset the list - Element list = querySelector("#playerList"); - list.children.clear(); - - if (users.length == 0) { - // Nobody else is online - Element message = new LIElement() - ..classes.addAll(["noChatSpawn"]) - ..setInnerHtml(' Nobody else here'); - list.append(message); - return 0; - } else { - // Other players are online - users.forEach((String username) { - Element user = new LIElement() - ..classes.add("online") - ..style.pointerEvents = "none" - //..classes.addAll(["online", "chatSpawn"]) - //..dataset["chat"] = username - ..setInnerHtml(' $username'); - list.append(user); - }); - return users.length; - } - } + Element getArchivedConversation(String title) { + String archiveClass = '.archive-${title.replaceAll(' ', '_')}'; + Element conversationElement = view.conversationArchive.querySelector(archiveClass); + if (conversationElement != null) { + conversationElement.classes.remove(archiveClass); + conversationElement.classes.add("conversation"); + + //move this conversation to the front of the openConversations list + for (Chat c in openConversations) { + if (c.title == title) { + openConversations.remove(c); + openConversations.insert(0, c); + break; + } + } + } + return conversationElement; + } + + void computePanelSize() { + List conversations = view.panel.querySelectorAll('.conversation').toList(); + int num = conversations.length - 1; + conversations.forEach((Element conversation) { + if (conversation.hidden) { + num--; + } + }); + conversations.forEach((Element conversation) => conversation.style.height = "${100 / num}%"); + } + + void processInput(TextInputElement input) { + //onKeyUp seems to be too late to prevent TAB's default behavior + input.onKeyDown.listen((KeyboardEvent key) { + //pressed up arrow + if (key.keyCode == 38 && inputHistoryPointer < inputHistory.length) { + input.value = inputHistory.elementAt(inputHistoryPointer); + if (inputHistoryPointer < inputHistory.length - 1) { + inputHistoryPointer++; + } + } + //pressed down arrow + if (key.keyCode == 40) { + if (inputHistoryPointer > 0) { + inputHistoryPointer--; + input.value = inputHistory.elementAt(inputHistoryPointer); + } else { + input.value = ""; + } + } + //tab key, try to complete a user's name or an emoticon + if (input.value != "" && key.keyCode == 9) { + key.preventDefault(); + + //look for an emoticon instead of a username + if (input.value.endsWith(":")) { + emoticonComplete(input, key); + return; + } + + //let's suggest players to tab complete + tabComplete(input, key); + + return; + } + }); + + input.onKeyUp.listen((KeyboardEvent key) { + if (key.keyCode != 9) { + tabInserted = false; + } + + if (key.keyCode != 13) { + //listen for enter key + return; + } + + if (input.value + .trim() + .length == 0) { + toast("You can't send a blank message"); + return; + } + + RegExp formatChars = new RegExp(r'|||||||'); + if (input.value + .replaceAll(formatChars, '') + .length == 0) { + toast("You must have non-formatting content in your message"); + return; + } + + parseInput(input.value); + + inputHistory.insert(0, input.value); //add to beginning of list + inputHistoryPointer = 0; //point to beginning of list + if (inputHistory.length > 50) { + //don't allow the list to grow forever + inputHistory.removeLast(); + } + + input.value = ''; + }); + } + + void emoticonComplete(InputElement input, KeyboardEvent k) { + //don't allow a key like tab to change to a different chat + //if we don't get a hit and k=[tab], we will re-fire + k.stopImmediatePropagation(); + + String value = input.value; + bool emoticonInserted = false; + + //if the input is exactly one long (therefore it is just a colon) + if (value.length == 1) { + //String beforePart = value.substring(0,lastColon); + input.value = ":${EMOTICONS.elementAt(emoticonPointer)}:"; + emoticonPointer++; + emoticonInserted = true; + } + //if the input is more than 1 long and there's a space before the colon (word separation) + else if (value.endsWith(" :")) { + int lastColon = value.lastIndexOf(':'); + String beforePart = value.substring(0, lastColon); + input.value = "$beforePart:${EMOTICONS.elementAt(emoticonPointer)}:"; + emoticonPointer++; + emoticonInserted = true; + } + + //if the input is more than 1 long and there is an emoticon that we should replace + //to do this, we check if the value has 2 : and that the text between them + //exactly matches an emoticon + int previousColon = value.substring(0, value.length - 1).lastIndexOf(':'); + if (previousColon > -1) { + String beforeSegment = value.substring(0, previousColon); + String emoticonSegment = value.substring(previousColon, value.length); + for (int i = 0; i < EMOTICONS.length; i++) { + String emoticon = EMOTICONS[i]; + String emoticonNext; + if (i < EMOTICONS.length - 1) { + emoticonNext = EMOTICONS[i + 1]; + } else { + emoticonNext = EMOTICONS[0]; + } + if (emoticonSegment.contains(':$emoticon:')) { + input.value = "$beforeSegment:$emoticonNext:"; + emoticonPointer++; + emoticonInserted = true; + break; + } + } + } + + //make sure we don't point past the end of the array + if (emoticonPointer >= EMOTICONS.length) { + emoticonPointer = 0; + } + + //if we didn't manage to insert an emoticon and tab was pressed... + //try to advance chat focus because we stifled it earlier + if (!emoticonInserted && k.keyCode == 9) { + advanceChatFocus(k); + } + } + + Future tabComplete(TextInputElement input, KeyboardEvent k) async { + //don't allow a key like tab to change to a different chat + //if we don't get a hit and k=[tab], we will re-fire + k.stopImmediatePropagation(); + + String channel = 'Global Chat'; + bool inserted = false; + + if (title != channel) { + channel = currentStreet.label; + } + String url = 'http://' + Configs.utilServerAddress + "/listUsers?channel=$channel"; + connectedUsers = JSON.decode(await HttpRequest.requestCrossOrigin(url)); + + int startIndex = input.value.lastIndexOf(" ") == -1 ? 0 : input.value.lastIndexOf(" ") + 1; + String localLastWord = input.value.substring(startIndex); + if (!tabInserted) { + lastWord = input.value.substring(startIndex); + } + + for (; tabSearchIndex < connectedUsers.length; tabSearchIndex++) { + String username = connectedUsers.elementAt(tabSearchIndex); + if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && + username.toLowerCase() != localLastWord.toLowerCase()) { + input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; + tabInserted = true; + inserted = true; + tabSearchIndex++; + break; + } + } + //if we didn't find it yet and the tabSearchIndex was not 0, let's look at the beginning of the array as well + //otherwise the user will have to press the tab key again + if (!tabInserted) { + for (int index = 0; index < tabSearchIndex; index++) { + String username = connectedUsers.elementAt(index); + if (username.toLowerCase().startsWith(lastWord.toLowerCase()) && + username.toLowerCase() != localLastWord.toLowerCase()) { + input.value = input.value.substring(0, input.value.lastIndexOf(" ") + 1) + username; + tabInserted = true; + inserted = true; + tabSearchIndex = index + 1; + break; + } + } + } + + if (!inserted && k.keyCode == 9) { + advanceChatFocus(k); + } + + if (tabSearchIndex == connectedUsers.length) { + //wrap around for next time + tabSearchIndex = 0; + } + } + + void parseInput(String input) { + // if its not a command, send it through. + if (parseCommand(input)) { + return; + } else if (input.toLowerCase() == "/list") { + Map map = {}; + map["username"] = game.username; + map["statusMessage"] = "list"; + map["channel"] = title; + map["street"] = currentStreet.label; + transmit('outgoingChatEvent', map); + } else { + Map map = new Map(); + map["username"] = game.username; + map["message"] = input; + map["channel"] = title; + if (title == "Local Chat") { + map["street"] = currentStreet.label; + } + + transmit('outgoingChatEvent', map); + + //display chat bubble if we're talking in local (unless it's a /me message) + if (map["channel"] == "Local Chat" && + !(map["message"] as String).toLowerCase().startsWith("/me")) { + //remove any existing bubble + if (CurrentPlayer.chatBubble != null && CurrentPlayer.chatBubble.bubble != null) { + CurrentPlayer.chatBubble.bubble.remove(); + } + CurrentPlayer.chatBubble = new ChatBubble( + parseEmoji(map["message"]), CurrentPlayer, CurrentPlayer.playerParentElement); + } + } + } + + // Update the list of online players in the sidebar + Future refreshOnlinePlayers() async { + if (this.title != "Global Chat") { + return -1; + } + + // Ignore yourself (can't chat with yourself, either) + List users = JSON.decode(await HttpRequest + .requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); + users.removeWhere((String username) => username == game.username); + + // Reset the list + Element list = querySelector("#playerList"); + list.children.clear(); + + if (users.length == 0) { + // Nobody else is online + Element message = new LIElement() + ..classes.addAll(["noChatSpawn"]) + ..setInnerHtml(' Nobody else here'); + list.append(message); + return 0; + } else { + // Other players are online + users.forEach((String username) { + Element user = new LIElement() + ..classes.add("online") + ..style.pointerEvents = "none" + //..classes.addAll(["online", "chatSpawn"]) + //..dataset["chat"] = username + ..setInnerHtml(' $username'); + list.append(user); + }); + return users.length; + } + } } // Manage focus bool advanceChatFocus(KeyboardEvent k) { - k.preventDefault(); - - bool found = false; - for (int i = 0; i < openConversations.length; i++) { - Chat convo = openConversations[i]; - - if (convo.focused) { - if (i < openConversations.length - 1) { - //unfocus the current - convo.blur(); - - //find the next non-archived conversation and focus it - for (int j = i + 1; j < openConversations.length; j++) { - if (!openConversations[j].archived) { - openConversations[j].focus(); - found = true; - } - } - - if (found) { - break; - } - } else { - // last chat in list, focus game - querySelector("#gameselector").focus(); - for (int i = 0; i < openConversations.length; i++) { - openConversations[i].blur(); - } - found = true; - } - } - } - - if (!found) { - // game is focused, focus first chat that is not archived - for (Chat c in openConversations) { - if (!c.archived) { - c.focus(); - break; - } - } - } - - return true; + k.preventDefault(); + + bool found = false; + for (int i = 0; i < openConversations.length; i++) { + Chat convo = openConversations[i]; + + if (convo.focused) { + if (i < openConversations.length - 1) { + //unfocus the current + convo.blur(); + + //find the next non-archived conversation and focus it + for (int j = i + 1; j < openConversations.length; j++) { + if (!openConversations[j].archived) { + openConversations[j].focus(); + found = true; + } + } + + if (found) { + break; + } + } else { + // last chat in list, focus game + querySelector("#gameselector").focus(); + for (int i = 0; i < openConversations.length; i++) { + openConversations[i].blur(); + } + found = true; + } + } + } + + if (!found) { + // game is focused, focus first chat that is not archived + for (Chat c in openConversations) { + if (!c.archived) { + c.focus(); + break; + } + } + } + + return true; } diff --git a/lib/src/display/toast.dart b/lib/src/display/toast.dart index 85d4fb7a..354469e9 100644 --- a/lib/src/display/toast.dart +++ b/lib/src/display/toast.dart @@ -1,103 +1,108 @@ part of couclient; -toast(String message) { - Element toastContainer = querySelector('#toastHolder'); +toast(String message, {bool skipChat: false}) { + Element toastContainer = querySelector('#toastHolder'); - DivElement toast = new DivElement() - ..classes.add('toast') - ..style.opacity = '0.5' - ..text = message; + DivElement toast = new DivElement() + ..classes.add('toast') + ..style.opacity = '0.5' + ..text = message; - int textTime = 1000 + (toast.text.length * 100); - if (textTime > 30000) { - textTime = 30000; - } + int textTime = 1000 + (toast.text.length * 100); + if (textTime > 30000) { + textTime = 30000; + } - Duration timeOpacity = new Duration(milliseconds: textTime); - Duration timeHide = new Duration(milliseconds: timeOpacity.inMilliseconds + 500); + Duration timeOpacity = new Duration(milliseconds: textTime); + Duration timeHide = new Duration(milliseconds: timeOpacity.inMilliseconds + 500); - new Timer(timeOpacity, () { - toast.style.opacity = '0'; - }); - new Timer(timeHide, toast.remove); + new Timer(timeOpacity, () { + toast.style.opacity = '0'; + }); + new Timer(timeHide, toast.remove); - toastContainer.append(toast); + toastContainer.append(toast); - if (Chat.localChat != null) { - Chat.localChat.addAlert(message, toast: true); - } else { - chatToastBuffer.add(message); - } + if (Chat.localChat != null && !skipChat) { + Chat.localChat.addAlert(message, toast: true); + } else if (!skipChat) { + chatToastBuffer.add(message); + } } buff(String type) { - Element buffContainer = querySelector('#buffHolder'); - String message, messageInfo; - int length; // in seconds + Element buffContainer = querySelector('#buffHolder'); + String message, messageInfo; + int length; // in seconds - switch (type) { - case 'pie': - message = "Full of Pie"; - messageInfo = "Jump height decreased"; - length = 120; // 2 minutes - break; - case 'quoin': - message = "Double Quoins"; - messageInfo = "Quoin values doubled"; - length = 600; // 10 minutes - break; - case 'spinach': - message = "Spinach"; - messageInfo = "Jump height increased"; - length = 30; // 30 seconds - break; - default: - return; - } + switch (type) { + case 'pie': + message = "Full of Pie"; + messageInfo = "Jump height decreased"; + length = 120; + // 2 minutes + break; + case 'quoin': + message = "Double Quoins"; + messageInfo = "Quoin values doubled"; + length = 600; + // 10 minutes + break; + case 'spinach': + message = "Spinach"; + messageInfo = "Jump height increased"; + length = 30; + // 30 seconds + break; + default: + return; + } - DivElement buff = new DivElement() - ..classes.add('toast') - ..classes.add('buff') - ..id = "buff-" + type - ..style.opacity = '0.5'; - ImageElement icon = new ImageElement() - ..classes.add('buffIcon') - ..src = 'files/system/buffs/buff_' + type + '.png'; - SpanElement title = new SpanElement() - ..classes.add('title') - ..text = message; - SpanElement desc = new SpanElement() - ..classes.add('desc') - ..text = messageInfo; - DivElement progress = new DivElement() - ..classes.add('buff-progress') - ..style.width = "100%" - ..id = "buff-" + type + "-progress"; - buff.append(progress); - buff.append(icon); - buff.append(title); - buff.append(desc); + DivElement buff = new DivElement() + ..classes.add('toast') + ..classes.add('buff') + ..id = "buff-" + type + ..style.opacity = '0.5'; + ImageElement icon = new ImageElement() + ..classes.add('buffIcon') + ..src = 'files/system/buffs/buff_' + type + '.png'; + SpanElement title = new SpanElement() + ..classes.add('title') + ..text = message; + SpanElement desc = new SpanElement() + ..classes.add('desc') + ..text = messageInfo; + DivElement progress = new DivElement() + ..classes.add('buff-progress') + ..style.width = "100%" + ..id = "buff-" + type + "-progress"; + buff.append(progress); + buff.append(icon); + buff.append(title); + buff.append(desc); - Duration timeOpacity = new Duration(seconds: length); - Duration timeHide = new Duration(milliseconds: timeOpacity.inMilliseconds + 500); - new Timer(timeOpacity, () { buff.style.opacity = '0'; }); - new Timer(timeHide, buff.remove); - Stopwatch uStopwatch = new Stopwatch(); - Timer uTimer; - uTimer = new Timer.periodic(new Duration(seconds: 1), (Timer t) { - int seconds = uStopwatch.elapsed.inSeconds; - if (seconds < length) { - num width = 100 - ((100 / length) * seconds).round(); - progress.style.width = width.toString() + "%"; - } else { - uStopwatch.stop(); - uTimer.cancel(); - } - }); + Duration timeOpacity = new Duration(seconds: length); + Duration timeHide = new Duration(milliseconds: timeOpacity.inMilliseconds + 500); + new Timer(timeOpacity, () { + buff.style.opacity = '0'; + }); + new Timer(timeHide, buff.remove); + Stopwatch uStopwatch = new Stopwatch(); + Timer uTimer; + uTimer = new Timer.periodic(new Duration(seconds: 1), (Timer t) { + int seconds = uStopwatch.elapsed.inSeconds; + if (seconds < length) { + num width = 100 - ((100 / length) * seconds).round(); + progress.style.width = width.toString() + "%"; + } else { + uStopwatch.stop(); + uTimer.cancel(); + } + }); - buffContainer.append(buff); - uStopwatch.start(); + buffContainer.append(buff); + uStopwatch.start(); - // TODO: store buffs to server - // TODO: get buffs from server and skip to remaining time + // TODO: store buffs to server + // TODO: get buffs from server and skip to remaining time } \ No newline at end of file diff --git a/lib/src/display/windows/bag_window.dart b/lib/src/display/windows/bag_window.dart index ec097064..fca33f42 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -21,6 +21,7 @@ class BagWindow extends Modal { for (BagWindow w in bagWindows) { if (w.sourceSlotNum == oldSlotIndex) { w.sourceSlotNum = newSlotIndex; + w.updateWell(w.sourceItem); break; } } @@ -29,7 +30,7 @@ class BagWindow extends Modal { String id, bagId; int numSlots, sourceSlotNum; - //when set to true, the ui inside the bag will be updated when the bag is nex opened + //when set to true, the ui inside the bag will be updated when the bag is next opened bool dataUpdated = false; ItemDef sourceItem; @@ -180,7 +181,7 @@ class BagWindow extends Modal { ItemDef item = decode(JSON.encode(bagSlot['item']), type: ItemDef); ImageElement img = new ImageElement(src: item.spriteUrl); String className = 'item-${item.itemType} inventoryItem bagInventoryItem'; - await sizeItem(img,itemInSlot,slot,item,bagSlot['count'],cssClass: className); + await sizeItem(img,itemInSlot,slot,item,bagSlot['count'], sourceSlotNum, cssClass: className, bagSlotNum: slotNum); } slotNum++; diff --git a/lib/src/display/windows/note_window.dart b/lib/src/display/windows/note_window.dart index 3f8761e8..504befe4 100644 --- a/lib/src/display/windows/note_window.dart +++ b/lib/src/display/windows/note_window.dart @@ -41,7 +41,10 @@ class NoteWindow extends Modal { displayElement ..querySelector(".notewindow-read").hidden = true ..querySelector(".notewindow-write").hidden = false; - exitEditMode = displayElement.querySelector(".notewindow-write-btn").onClick.listen((_) { + exitEditMode = displayElement + .querySelector(".notewindow-write-btn") + .onClick + .listen((_) { EditMode_Exit(); exitEditMode.cancel(); }); @@ -61,21 +64,27 @@ class NoteWindow extends Modal { // Display values displayElement ..querySelector(".notewindow-read-title").text = note["title"] - ..querySelector(".notewindow-read-body").setInnerHtml(note["body"].replaceAll("\n", "
"), validator: HtmlValidator) + ..querySelector(".notewindow-read-body").setInnerHtml( + note["body"].replaceAll("\n", "
"), validator: HtmlValidator) ..querySelector(".notewindow-read-footer-date").text = note["date"]; // Handle user-specific content if (isWriter) { displayElement ..querySelector(".notewindow-read-editbtn").hidden = false ..querySelector(".notewindow-read-footer-username").text = "You"; - enterEditMode = displayElement.querySelector(".notewindow-read-editbtn").onClick.listen((_) { + enterEditMode = displayElement + .querySelector(".notewindow-read-editbtn") + .onClick + .listen((_) { EditMode_Enter(); enterEditMode.cancel(); }); } else { displayElement ..querySelector(".notewindow-read-editbtn").hidden = true - ..querySelector(".notewindow-read-footer-username").setInnerHtml('${note["writer"]}', validator: HtmlValidator); + ..querySelector(".notewindow-read-footer-username").setInnerHtml( + '${note["writer"]}', + validator: HtmlValidator); } } @@ -86,19 +95,19 @@ class NoteWindow extends Modal { "title": "Urgent Message!", "body": "Dear Fellow Glitches,\n" - "\n" - "This is an urgent notice pertaining to a\n" - "natrual gas leak from the gas plants\n" - "that has been recently detected. Please calmly\n" - "evacuate the street and beware of large\n" - "concentrations of Heavy Gas. If you feel light headed,\n" - "heavy, or have uncontrollable fits of laughter, please\n" - "visit the nearest poision control center.\n" - "\n" - "We are doing our best to assess the situation. Until\n" - "then, please do not inhale too deeply.\n" - "\n" - "-- Sandbox Gas and Electric", + "\n" + "This is an urgent notice pertaining to a\n" + "natrual gas leak from the gas plants\n" + "that has been recently detected. Please calmly\n" + "evacuate the street and beware of large\n" + "concentrations of Heavy Gas. If you feel light headed,\n" + "heavy, or have uncontrollable fits of laughter, please\n" + "visit the nearest poision control center.\n" + "\n" + "We are doing our best to assess the situation. Until\n" + "then, please do not inhale too deeply.\n" + "\n" + "-- Sandbox Gas and Electric", "writer": "RedDyeNo.5", "date": "1:16AM, 26 October 2011" }; @@ -107,8 +116,8 @@ class NoteWindow extends Modal { "title": "Hey guys!", "body": "Just testing this note window thing.\n" - "\n" - "Notice how there's no icon? The icon I want is in FontAwesome 4.4, which the CDN hasn't updated to yet.", + "\n" + "Notice how there's no icon? The icon I want is in FontAwesome 4.4, which the CDN hasn't updated to yet.", "writer": "Klikini", "date": "10:10AM, 19 August 2015" }; diff --git a/lib/src/display/windows/settings_window.dart b/lib/src/display/windows/settings_window.dart index b53986bc..cd7dcec7 100644 --- a/lib/src/display/windows/settings_window.dart +++ b/lib/src/display/windows/settings_window.dart @@ -129,7 +129,7 @@ class SettingsWindow extends Modal { @override close() { querySelector("#${id}").hidden = true; - toast("Settings saved"); + toast("Preferences saved"); super.close(); } @@ -149,6 +149,9 @@ class SettingsWindow extends Modal { bool get joinMessagesVisibility => _showJoinMessages; void setPlayMentionSound(bool enabled) { + if(enabled) { + Notification.requestPermission(); + } _playMentionSound = enabled; localStorage["playMentionSound"] = enabled.toString(); } diff --git a/lib/src/network/server_interop/so_item.dart b/lib/src/network/server_interop/so_item.dart index 634cb600..280ca358 100644 --- a/lib/src/network/server_interop/so_item.dart +++ b/lib/src/network/server_interop/so_item.dart @@ -49,7 +49,7 @@ Future findNewSlot(Slot slot, int index, {bool update: false}) async { barSlot.children.clear(); Element itemDiv = new DivElement(); - await sizeItem(img,itemDiv,barSlot,item,count); + await sizeItem(img,itemDiv,barSlot,item,count, int.parse(barSlot.dataset["slot-num"])); if (!update) { itemDiv.classes.add("bounce"); @@ -85,7 +85,7 @@ Future findNewSlot(Slot slot, int index, {bool update: false}) async { } } -Future sizeItem(ImageElement img, Element itemDiv, Element slot, ItemDef item, int count, {String cssClass}) async { +Future sizeItem(ImageElement img, Element itemDiv, Element slot, ItemDef item, int count, int barSlotNum, {String cssClass, int bagSlotNum: -1}) async { await img.onLoad.first; //determine what we need to scale the sprite image to in order to fit @@ -112,7 +112,7 @@ Future sizeItem(ImageElement img, Element itemDiv, Element slot, ItemDef item, i itemDiv.attributes['count'] = "1"; itemDiv.attributes['itemMap'] = encode(item); - String slotNum = '${slot.dataset["slot-num"]}.-1'; + String slotNum = '$barSlotNum.$bagSlotNum'; itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); slot.append(itemDiv); diff --git a/lib/src/network/server_interop/so_player.dart b/lib/src/network/server_interop/so_player.dart index 1813da32..7af6f231 100644 --- a/lib/src/network/server_interop/so_player.dart +++ b/lib/src/network/server_interop/so_player.dart @@ -55,8 +55,10 @@ _setupPlayerSocket() { //someone left this street if (map["changeStreet"] != currentStreet.label) { removeOtherPlayer(map["username"]); + toast('${map['username']} has left for ${map['changeStreet']}'); } else { createOtherPlayer(map); + toast('${map['username']} has arrived'); } } else if (map["disconnect"] != null) { removeOtherPlayer(map["username"]); diff --git a/lib/src/network/streetservice.dart b/lib/src/network/streetservice.dart index 0fdd91da..f9065fe8 100644 --- a/lib/src/network/streetservice.dart +++ b/lib/src/network/streetservice.dart @@ -34,7 +34,7 @@ class StreetService { String playerList = ''; List players = JSON.decode(await HttpRequest.getString('http://' + Configs.utilServerAddress + '/listUsers?channel=' + currentStreet.label)); // don't list if it's just you - if(players.length > 1) { + if(players.length > 0) { for(int i = 0; i != players.length; i++) { playerList += players[i]; if(i != players.length) { @@ -43,8 +43,6 @@ class StreetService { } playerList = playerList.substring(0, playerList.length - 2); toast("Players on this street: " + playerList); - } else { - toast("Nobody else is on this street"); } } } From 06369797340c4ebecc2a977e9bff42fdc5a67fe4 Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 9 Jan 2016 16:37:41 -0600 Subject: [PATCH 40/43] fix interactions gif location --- web/files/css/desktop/interactions.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/files/css/desktop/interactions.css b/web/files/css/desktop/interactions.css index f5e98974..9b45a462 100644 --- a/web/files/css/desktop/interactions.css +++ b/web/files/css/desktop/interactions.css @@ -297,7 +297,7 @@ display: inline-block; width: 113px; height: 63px; - background-image: url(../system/actions/using_pick.png); + background-image: url(../../system/actions/using_pick.png); -webkit-animation: miningAnimation 1.5s steps(33, end) infinite; -moz-animation: miningAnimation 1.5s steps(33, end) infinite; -o-animation: miningAnimation 1.5s steps(33, end) infinite; @@ -312,7 +312,7 @@ display: inline-block; width: 111px; height: 62px; - background-image: url(../system/actions/using_pick_fancy.png); + background-image: url(../../system/actions/using_pick_fancy.png); -webkit-animation: miningAnimationFancy 1.5s steps(33, end) infinite; -moz-animation: miningAnimationFancy 1.5s steps(33, end) infinite; -o-animation: miningAnimationFancy 1.5s steps(33, end) infinite; @@ -327,7 +327,7 @@ display: inline-block; width: 91px; height: 78px; - background-image: url(../system/actions/using_watering.png); + background-image: url(../../system/actions/using_watering.png); -webkit-animation: wateringAnimation 1.5s steps(30, end) infinite; -moz-animation: wateringAnimation 1.5s steps(30, end) infinite; -o-animation: wateringAnimation 1.5s steps(30, end) infinite; @@ -342,7 +342,7 @@ display: inline-block; width: 65px; height: 82px; - background-image: url(../system/actions/using_shovel.png); + background-image: url(../../system/actions/using_shovel.png); -webkit-animation: diggingAnimation 1.5s steps(53, end) infinite; -moz-animation: diggingAnimation 1.5s steps(53, end) infinite; -o-animation: diggingAnimation 1.5s steps(53, end) infinite; @@ -357,7 +357,7 @@ display: inline-block; width: 63px; height: 79px; - background-image: url(../system/actions/using_shovel_fancy.png); + background-image: url(../../system/actions/using_shovel_fancy.png); -webkit-animation: diggingAnimationFancy 1.5s steps(53, end) infinite; -moz-animation: diggingAnimationFancy 1.5s steps(53, end) infinite; -o-animation: diggingAnimationFancy 1.5s steps(53, end) infinite; From 71f60909b9a84bc534c6f6d0d3ef8cd1fe8bb107 Mon Sep 17 00:00:00 2001 From: Robert McDermot Date: Sat, 9 Jan 2016 18:33:16 -0500 Subject: [PATCH 41/43] always have the 'players on this street' message include your own name --- lib/src/network/streetservice.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/network/streetservice.dart b/lib/src/network/streetservice.dart index f9065fe8..c2fae917 100644 --- a/lib/src/network/streetservice.dart +++ b/lib/src/network/streetservice.dart @@ -33,6 +33,9 @@ class StreetService { String playerList = ''; List players = JSON.decode(await HttpRequest.getString('http://' + Configs.utilServerAddress + '/listUsers?channel=' + currentStreet.label)); + if (!players.contains(game.username)) { + players.add(game.username); + } // don't list if it's just you if(players.length > 0) { for(int i = 0; i != players.length; i++) { From f646353e205e815126777dbdb97af2edf7c801c9 Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 9 Jan 2016 17:55:05 -0600 Subject: [PATCH 42/43] fix chat links --- lib/src/display/chatmessage.dart | 186 ++++++++++--------- lib/src/display/chatpanel.dart | 41 +++- lib/src/display/windows/emoticon_picker.dart | 11 +- lib/src/game/chat_bubble.dart | 2 +- web/files/css/desktop/interface/chat.css | 53 +----- 5 files changed, 138 insertions(+), 155 deletions(-) diff --git a/lib/src/display/chatmessage.dart b/lib/src/display/chatmessage.dart index e42d00ce..9bb9258f 100644 --- a/lib/src/display/chatmessage.dart +++ b/lib/src/display/chatmessage.dart @@ -5,106 +5,109 @@ class ChatMessage { ChatMessage(this.player, this.message); - Future toHtml() async { - if (message is! String) { - return ''; - } - String html, - displayName = player; - - message = parseUrl(message); - message = parseEmoji(message); - message = parseLocationLinks(message); - message = parseItemLinks(message); - - if ( - message.toLowerCase().contains(game.username.toLowerCase()) - && windowManager.settings.playMentionSound - ) { + void notify() { + if (message.toLowerCase().contains(game.username.toLowerCase()) + && windowManager.settings.playMentionSound + && player != game.username) { // Popup new Notification( - player, - body: message, - icon: "http://childrenofur.com/assets/icon_72.png" - ); + player, + body: message, + icon: "http://childrenofur.com/assets/icon_72.png" + ); // Sound effect transmit('playSound', 'mention'); } + } - // Apply labels - String nameClass = "name "; + Future toHtml() async { + // Verify data + if (message is! String || player is! String) { + return ''; + } + + String displayName = player; + List nameClasses = ["name"]; + + // Get link to username + Future getUsernameLink() async { + return new AnchorElement() + ..classes = (new List.from(nameClasses) + ..add("noUnderline")) + ..href = "http://childrenofur.com/profile?username=$player" + ..target = "_blank" + ..title = "Open Profile Page" + ..text = displayName + ..style.color = (await getColorFromUsername(player)); + } + + // Notify of any mentions + notify(); + + // Set up labels if (player != null) { // You if (player == game.username) { - nameClass += "you "; + nameClasses.add("you"); + if (!message.startsWith("/me")) { + displayName = "You"; + } } + // Dev/Guide if (game.devs.contains(player)) { - nameClass += "dev "; + nameClasses.add("dev"); } else if (game.guides.contains(player)) { - nameClass += "guide "; + nameClasses.add("guide"); } } - if (game.username == player) { - displayName = "You"; - } + //TODO: .me is italic if (player == null) { // System message - html = '

$message

'; - } else if (message.startsWith('/me')) { + return (new ParagraphElement() + ..text = message + ..classes = ["system"] + ).outerHtml; + } else if (message.startsWith("/me")) { // /me message - message = message.replaceFirst('/me ', ''); - html = - '

' - '$player $message' - '

'; - } else if (message == " joined." || message == " left.") { - // Player joined or left - if (game.username != player) { - if (player != game.username) { - if (message == " joined.") { - toast("$player has arrived"); - } - if (message == " left.") { - toast("$player left"); - } - } - } - - html = ""; + return (new ParagraphElement() + ..classes = ["me"] + ..append(await getUsernameLink()) + ..appendText(message.replaceFirst("/me", "")) + ).outerHtml; } else if (message == "LocationChangeEvent" && player == "invalid_user") { - // Switching streets message - void setLocationChangeEventHtml() { - String prefix = (metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) - ? "Back" - : "First time"); - html = - '

' - '$prefix in ${currentStreet - .label}' - '

'; - } - + // Switching streets if (!metabolics.load.isCompleted) { await metabolics.load.future; - setLocationChangeEventHtml(); - } else { - setLocationChangeEventHtml(); } + + String prefix = ( + metabolics.playerMetabolics.location_history.contains(currentStreet.tsid_g) + ? "Back" + : "First time" + ); + + SpanElement messageSpan = new SpanElement() + ..classes = ["message"] + ..text = "$prefix in ${currentStreet.label}"; + + return (new ParagraphElement() + ..classes = ["chat-member-change-event"] + ..append(messageSpan) + ).outerHtml; } else { // Normal message - html = - '

' - '$displayName: ' - '$message' - '

'; + return (new ParagraphElement() + ..append(await getUsernameLink()) + ..appendHtml(" ") // en space + ..append(new SpanElement() + ..classes = ["message"] + ..text = message) + ).outerHtml; } - - return html; } } @@ -125,8 +128,8 @@ Future getColorFromUsername(String username) async { } else { // Download color from server String color = await HttpRequest.getString( - "http://${Configs.utilServerAddress}/usernamecolors/get/$username" - ); + "http://${Configs.utilServerAddress}/usernamecolors/get/$username" + ); // Cache for later use cachedUsernameColors[username] = color; // Return for display @@ -136,7 +139,7 @@ Future getColorFromUsername(String username) async { String parseEmoji(String message) { String returnString = ""; - RegExp regex = new RegExp(":(.+?):"); + RegExp regex = new RegExp("::(.+?)::"); message.splitMapJoin(regex, onMatch: (Match m) { String match = m[1]; if (EMOTICONS.contains(match)) { @@ -164,10 +167,15 @@ String parseUrl(String message) { RegExp regex = new RegExp(regexString); message.splitMapJoin(regex, onMatch: (Match m) { String url = m[0]; - if (!url.contains("http")) { - url = "http://" + url; + if (url.contains('"')) { + // Don't match URLs already in tags + returnString += url; + } else { + if (!url.contains("http")) { + url = "http://" + url; + } + returnString += '${m[0]}'; } - returnString += '${m[0]}'; }, onNonMatch: (String s) => returnString += s); return returnString; @@ -182,9 +190,9 @@ String parseItemLinks(String message) { String name = Item.getName(match); String iconUrl = Item.getIcon(itemType: match); returnString += '' - '' - '$name'; + '' + '$name'; } else { returnString += m[0]; } @@ -197,10 +205,10 @@ String parseLocationLinks(String message) { String _parseHubLinks(String _message) { mapData.hubNames.forEach((String hubName) { _message = _message.replaceAll( - hubName, - '' - '$hubName' - ); + hubName, + '' + '$hubName' + ); }); return _message; @@ -209,10 +217,10 @@ String parseLocationLinks(String message) { String _parseStreetLinks(String _message) { mapData.streetNames.forEach((String streetName) { _message = _message.replaceAll( - streetName, - '' - '$streetName' - ); + streetName, + '' + '$streetName' + ); }); return _message; diff --git a/lib/src/display/chatpanel.dart b/lib/src/display/chatpanel.dart index d9e3f5c4..1509edf3 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -20,10 +20,10 @@ class Chat { static StreamSubscription itemWindowLinks, mapWindowLinks; static InputElement lastFocusedInput; - static final NodeValidatorBuilder validator = new NodeValidatorBuilder() + static final NodeValidatorBuilder VALIDATOR = new NodeValidatorBuilder() ..allowHtml5() - ..allowElement('span', attributes: ['style']) // Username colors, item icons - ..allowElement('a', attributes: ['href', 'title', 'target', 'class']) // Links + ..allowElement('span', attributes: ['style']) // Item icons + ..allowElement('a', attributes: ['href', 'title', 'target', 'class', 'style']) // Links ..allowElement('i', attributes: ['class', 'title']) // Emoticons ..allowElement('p', attributes: ['style'])..allowElement('b')..allowElement('del'); @@ -193,9 +193,34 @@ class Chat { Future addMessage(String player, String message) async { ChatMessage chat = new ChatMessage(player, message); Element dialog = conversationElement.querySelector('.dialog'); - String html = await chat.toHtml(); - // display in panel - dialog.appendHtml(html, validator: Chat.validator); + + // Toast for player change events + if (message == " joined." || message == " left.") { + // Player joined or left + if (game.username != player) { + if (player != game.username) { + if (message == " joined.") { + toast("$player has arrived"); + } + if (message == " left.") { + toast("$player left"); + } + } + } + } else { + // display in panel + String html = await chat.toHtml(); + print(html); + // Parse styles, links, and emoji + html = html.replaceAll("<", "<"); + html = html.replaceAll(">", ">"); + html = parseUrl(html); + html = parseEmoji(html); + html = parseLocationLinks(html); + html = parseItemLinks(html); + print(html); + dialog.appendHtml(html, validator: Chat.VALIDATOR); + } //scroll to the bottom dialog.scrollTop = dialog.scrollHeight; @@ -256,7 +281,7 @@ class Chat { void _add() { String text = '

$alert

'; Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(parseLocationLinks(text), validator: validator); + dialog.appendHtml(parseLocationLinks(text), validator: VALIDATOR); //scroll to the bottom dialog.scrollTop = dialog.scrollHeight; @@ -289,7 +314,7 @@ class Chat { String text = '

$alert

'; Element dialog = conversationElement.querySelector('.dialog'); - dialog.appendHtml(text, validator: validator); + dialog.appendHtml(text, validator: VALIDATOR); //scroll to the bottom dialog.scrollTop = dialog.scrollHeight; diff --git a/lib/src/display/windows/emoticon_picker.dart b/lib/src/display/windows/emoticon_picker.dart index 6ca872df..b362b029 100644 --- a/lib/src/display/windows/emoticon_picker.dart +++ b/lib/src/display/windows/emoticon_picker.dart @@ -36,11 +36,12 @@ class EmoticonPicker extends Modal { }); emoticonButton.onClick.listen((_) { - if (target.value == "" || target.value.substring(target.value.length - 1) == " ") { - target.value += ":$emoticon:"; - } else { - target.value += " :$emoticon:"; - } + target.value += + "${( + target.value == "" || target.value.substring(target.value.length - 1) == " " + ? "" + : " " + )}::$emoticon::"; }); }); }); diff --git a/lib/src/game/chat_bubble.dart b/lib/src/game/chat_bubble.dart index 0a50287c..d2896571 100644 --- a/lib/src/game/chat_bubble.dart +++ b/lib/src/game/chat_bubble.dart @@ -28,7 +28,7 @@ class ChatBubble { // +0 // - textElement.setInnerHtml(text, validator: Chat.validator); + textElement.setInnerHtml(text, validator: Chat.VALIDATOR); arrowElement = new DivElement() ..classes.add("cb-arrow"); diff --git a/web/files/css/desktop/interface/chat.css b/web/files/css/desktop/interface/chat.css index e287ca55..74ba68b3 100644 --- a/web/files/css/desktop/interface/chat.css +++ b/web/files/css/desktop/interface/chat.css @@ -90,6 +90,7 @@ p.me { font-size: 14px; + font-style: italic; } .conversation input { @@ -178,58 +179,6 @@ p.me { display: inline-block; } -.username.blue { - color: blue; -} - -.username.deepskyblue { - color: deepskyblue; -} - -.username.fuchsia { - color: fuchsia; -} - -.username.gray { - color: gray; -} - -.username.green { - color: green; -} - -.username.olivedrab { - color: olivedrab; -} - -.username.maroon { - color: maroon; -} - -.username.navy { - color: navy; -} - -.username.olive { - color: olive; -} - -.username.orange { - color: orange; -} - -.username.purple { - color: purple; -} - -.username.red { - color: red; -} - -.username.teal { - color: teal; -} - /* Entity Interaction Completion */ .cb-content .awarded { From e41b229e46bdd8ede4ea14d5c262d9b40c7aeeee Mon Sep 17 00:00:00 2001 From: klikini Date: Sat, 9 Jan 2016 17:56:44 -0600 Subject: [PATCH 43/43] remove print statements --- lib/src/display/chatpanel.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/display/chatpanel.dart b/lib/src/display/chatpanel.dart index 1509edf3..fc7c844f 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -208,9 +208,9 @@ class Chat { } } } else { - // display in panel + // Assemble chat message elements String html = await chat.toHtml(); - print(html); + // Parse styles, links, and emoji html = html.replaceAll("<", "<"); html = html.replaceAll(">", ">"); @@ -218,7 +218,8 @@ class Chat { html = parseEmoji(html); html = parseLocationLinks(html); html = parseItemLinks(html); - print(html); + + // Display in panel dialog.appendHtml(html, validator: Chat.VALIDATOR); }