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/chatmessage.dart b/lib/src/display/chatmessage.dart index 95cfd13b..9bb9258f 100644 --- a/lib/src/display/chatmessage.dart +++ b/lib/src/display/chatmessage.dart @@ -1,102 +1,114 @@ part of couclient; class ChatMessage { - String player, message; - - 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 - ) { - // Popup - Notification.requestPermission(); - new Notification( - player, - body: message, - icon: "http://childrenofur.com/assets/icon_72.png" - ); + String player, message; + + ChatMessage(this.player, this.message); + + 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" + ); + + // Sound effect + transmit('playSound', 'mention'); + } + } - // 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 - html = - '

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

'; - } else { - // Normal message - html = - '

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

'; - } - - return html; - } + 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) { + nameClasses.add("you"); + if (!message.startsWith("/me")) { + displayName = "You"; + } + } + + // Dev/Guide + if (game.devs.contains(player)) { + nameClasses.add("dev"); + } else if (game.guides.contains(player)) { + nameClasses.add("guide"); + } + } + + //TODO: .me is italic + + if (player == null) { + // System message + return (new ParagraphElement() + ..text = message + ..classes = ["system"] + ).outerHtml; + } else if (message.startsWith("/me")) { + // /me message + return (new ParagraphElement() + ..classes = ["me"] + ..append(await getUsernameLink()) + ..appendText(message.replaceFirst("/me", "")) + ).outerHtml; + } else if (message == "LocationChangeEvent" && player == "invalid_user") { + // Switching streets + if (!metabolics.load.isCompleted) { + await metabolics.load.future; + } + + 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 + return (new ParagraphElement() + ..append(await getUsernameLink()) + ..appendHtml(" ") // en space + ..append(new SpanElement() + ..classes = ["message"] + ..text = message) + ).outerHtml; + } + } } // chat functions @@ -116,7 +128,7 @@ 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; @@ -126,88 +138,93 @@ 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('"')) { + // Don't match URLs already in tags + returnString += url; + } else { + 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)); -} \ No newline at end of file + 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 1ef4a331..fc7c844f 100644 --- a/lib/src/display/chatpanel.dart +++ b/lib/src/display/chatpanel.dart @@ -2,23 +2,30 @@ part of couclient; // Chats class Chat { - String title, lastWord = ""; - bool online, focused = false, tabInserted = false; + 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(); + 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() + 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'); + ..allowElement('p', attributes: ['style'])..allowElement('b')..allowElement('del'); // /me text @@ -42,14 +49,16 @@ class Chat { // 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; + 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(); @@ -69,9 +78,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)); @@ -181,66 +190,114 @@ 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) { - // 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); + // 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 (e.target is SpanElement) { - new ItemWindow(((e.target) as Element).parent.text); + if (message == " left.") { + toast("$player left"); } - }); + } } + } else { + // Assemble chat message elements + String html = await chat.toHtml(); + + // Parse styles, links, and emoji + html = html.replaceAll("<", "<"); + html = html.replaceAll(">", ">"); + html = parseUrl(html); + html = parseEmoji(html); + html = parseLocationLinks(html); + html = parseItemLinks(html); + + // Display in panel + dialog.appendHtml(html, validator: Chat.VALIDATOR); + } - // 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); - } + //scroll to the bottom + dialog.scrollTop = dialog.scrollHeight; - mapWindow.open(); - }); - } - }); + // 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 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); + 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); + } - //scroll to the bottom - dialog.scrollTop = dialog.scrollHeight; + 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) { @@ -258,7 +315,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; @@ -300,8 +357,7 @@ class Chat { } void computePanelSize() { - List conversations = - view.panel.querySelectorAll('.conversation').toList(); + List conversations = view.panel.querySelectorAll('.conversation').toList(); int num = conversations.length - 1; conversations.forEach((Element conversation) { if (conversation.hidden) { @@ -357,13 +413,17 @@ class Chat { return; } - if (input.value.trim().length == 0) { + 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) { + if (input.value + .replaceAll(formatChars, '') + .length == 0) { toast("You must have non-formatting content in your message"); return; } @@ -502,8 +562,7 @@ class Chat { // if its not a command, send it through. if (parseCommand(input)) { return; - } - else if (input.toLowerCase() == "/list") { + } else if (input.toLowerCase() == "/list") { Map map = {}; map["username"] = game.username; map["statusMessage"] = "list"; @@ -525,12 +584,11 @@ class Chat { if (map["channel"] == "Local Chat" && !(map["message"] as String).toLowerCase().startsWith("/me")) { //remove any existing bubble - if (CurrentPlayer.chatBubble != null && - CurrentPlayer.chatBubble.bubble != null) { + if (CurrentPlayer.chatBubble != null && CurrentPlayer.chatBubble.bubble != null) { CurrentPlayer.chatBubble.bubble.remove(); } - CurrentPlayer.chatBubble = new ChatBubble(parseEmoji(map["message"]), - CurrentPlayer, CurrentPlayer.playerParentElement); + CurrentPlayer.chatBubble = new ChatBubble( + parseEmoji(map["message"]), CurrentPlayer, CurrentPlayer.playerParentElement); } } } @@ -542,7 +600,8 @@ class Chat { } // Ignore yourself (can't chat with yourself, either) - List users = JSON.decode(await HttpRequest.requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); + List users = JSON.decode(await HttpRequest + .requestCrossOrigin('http://${ Configs.utilServerAddress}/listUsers?channel=Global Chat')); users.removeWhere((String username) => username == game.username); // Reset the list @@ -619,4 +678,4 @@ bool advanceChatFocus(KeyboardEvent k) { } return true; -} \ No newline at end of file +} diff --git a/lib/src/display/inv_dragging.dart b/lib/src/display/inv_dragging.dart index 25d0ea91..93383558 100644 --- a/lib/src/display/inv_dragging.dart +++ b/lib/src/display/inv_dragging.dart @@ -3,10 +3,13 @@ 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; @@ -55,15 +58,14 @@ class InvDragging { // Set up draggable elements _draggables = new Draggable( - // List of item elements in boxes - querySelectorAll('.inventoryItem'), - // Display the item on the cursor + // 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 + // Disable item interaction while dragging it draggingClass: "item-flying" - )..onDragStart.listen((DraggableEvent e) => handlePickup(e)); + ) + ..onDragStart.listen((DraggableEvent e) => handlePickup(e)); // Set up acceptor slots _dropzones = new Dropzone(querySelectorAll("#inventory .box")) @@ -92,6 +94,24 @@ 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']); + } + + 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); @@ -105,7 +125,7 @@ class BagFilterAcceptor extends Acceptor { @override bool accepts(Element itemE, int draggable_id, Element box) { - ItemDef item = decode(itemE.attributes['itemmap'],type:ItemDef); + 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; 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/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/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 58d24a34..51a95c8c 100644 --- a/lib/src/display/overlays/levelup.dart +++ b/lib/src/display/overlays/levelup.dart @@ -6,15 +6,38 @@ 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() { + 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(); } } 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 +} diff --git a/lib/src/display/render/worldmap.dart b/lib/src/display/render/worldmap.dart index f061dbc2..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']; @@ -103,12 +99,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 @@ -196,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) { @@ -248,7 +249,6 @@ class WorldMap { HubMabDiv.hidden = false; //HubMapFG.hidden = false; WorldMapDiv.hidden = true; - }); } getStreetAngle(Map street) { 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/ui_templates/menu_keys.dart b/lib/src/display/ui_templates/menu_keys.dart new file mode 100644 index 00000000..f526ce36 --- /dev/null +++ b/lib/src/display/ui_templates/menu_keys.dart @@ -0,0 +1,184 @@ +part of couclient; + +class MenuKeys { + // Maps key to valid keycodes + // Use int for single binding, and List for multiple (number row THEN numpad) + 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 the key with the given keycode + static String keyWithCode(int 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) { + if (codes[0] == keyCode) { + return key; + } + } + } + + return ""; + } + + // 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; + } + } + + // 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(); + }); + + // Remove all items to allow for garbage collection + _listeners.clear(); + } + + // 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, []); + } + })); + } + + // Inventory slot listener + static void invSlotsListener() { + document.onKeyDown.listen((KeyboardEvent event) { + 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; + } + + // 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.clientHeight ~/ 2); + 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 1f4e6b1e..309e5210 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,310 @@ 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.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; + 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(); + MenuKeys.clearListeners(); + transmit("right_click_menu", "destroy"); + } + } +} diff --git a/lib/src/display/view.dart b/lib/src/display/view.dart index c3568099..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,6 +200,9 @@ class UserInterface { Chat.lastFocusedInput.focus(); }); + + // Inventory number keys + MenuKeys.invSlotsListener(); }); } diff --git a/lib/src/display/widgets/volumeslider.dart b/lib/src/display/widgets/volumeslider.dart index fdd4851f..a8d120fd 100644 --- a/lib/src/display/widgets/volumeslider.dart +++ b/lib/src/display/widgets/volumeslider.dart @@ -5,6 +5,7 @@ class VolumeSliderWidget bool muted = false; 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(); @@ -54,4 +59,4 @@ class VolumeSliderWidget // Update the audio mute state audio.setMute(muted); } -} \ 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 17fb3035..fca33f42 100644 --- a/lib/src/display/windows/bag_window.dart +++ b/lib/src/display/windows/bag_window.dart @@ -3,9 +3,13 @@ part of couclient; class BagWindow extends Modal { static List openWindows = []; + static List bagWindows = []; static closeId(String id) { - openWindows.where((BagWindow w) => w.id == id).first.close(); + openWindows + .where((BagWindow w) => w.id == id) + .first + .close(); openWindows.removeWhere((BagWindow w) => w.id == id); } @@ -13,38 +17,100 @@ class BagWindow extends Modal { return (querySelectorAll("#windowHolder > .bagWindow").length > 0); } - String id = 'bagWindow' + WindowManager.randomId.toString(); - String bagId; - int numSlots; - int sourceSlotNum; + static void updateSourceSlot(int oldSlotIndex, int newSlotIndex) { + for (BagWindow w in bagWindows) { + if (w.sourceSlotNum == oldSlotIndex) { + w.sourceSlotNum = newSlotIndex; + w.updateWell(w.sourceItem); + break; + } + } + } + + String id, + bagId; + int numSlots, sourceSlotNum; + //when set to true, the ui inside the bag will be updated when the bag is next opened + bool dataUpdated = false; + ItemDef sourceItem; + Dropzone acceptors; - BagWindow(this.sourceSlotNum, ItemDef sourceItem) { + factory BagWindow(int sourceSlotNum, ItemDef sourceItem, {String id : null, bool open : true}) { + if (id == null) { + return new BagWindow._(sourceSlotNum, sourceItem, openWindow:open); + } else { + for(BagWindow w in bagWindows) { + if (w.id == id) { + if(open) { + w.open(); + } + return w; + } + } + return new BagWindow._(sourceSlotNum, sourceItem, openWindow:open); + } + } + + BagWindow._(this.sourceSlotNum, this.sourceItem, {bool openWindow : true}) { + bool creating = true; + 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"], (_) async { + new Service(["inventoryUpdated", 'metadataUpdated'], (_) { if (acceptors != null) { acceptors.destroy(); } acceptors = new Dropzone( - windowElement.querySelectorAll(".bagwindow-box"), + 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 { - windowElement.querySelector("ur-well").replaceWith(await load(sourceItem, false)); - transmit('inventoryUpdated',true); + + new Service(['updateMetadata'], (Map indexToItem) async { + int index = indexToItem['index']; + if(index != sourceSlotNum) { + return; + } + this.sourceItem = indexToItem['item']; + if(displayElement.hidden) { + dataUpdated = true; + } else { + if(!creating) { + updateWell(sourceItem); + } + } }); - querySelector("#windowHolder").append(windowElement); + querySelector("#windowHolder").append(displayElement); prepare(); - open(); + if(openWindow) { + open(); + } else { + displayElement.hidden = true; + } + creating = false; }); } - Future load(ItemDef sourceItem, [bool full = true]) async { + 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; @@ -70,8 +136,7 @@ class BagWindow extends Modal { } header = new Element.header() - ..append(icon) - ..append(titleSpan); + ..append(icon)..append(titleSpan); } // Content @@ -100,6 +165,8 @@ 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(); // Slot @@ -107,18 +174,20 @@ class BagWindow extends Modal { ..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); + 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'], sourceSlotNum, cssClass: className, bagSlotNum: slotNum); } - well.remove(); slotNum++; }); + well.style.opacity = '1'; //we're done measuring now + well.remove(); } // Window @@ -128,9 +197,7 @@ class BagWindow extends Modal { ..id = id ..classes.add("window") ..classes.add("bagWindow") - ..append(header) - ..append(closeButton) - ..append(well) + ..append(header)..append(closeButton)..append(well) ..dataset["source-bag"] = sourceSlotNum.toString(); return window; @@ -139,71 +206,23 @@ 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); - } - - 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); + updateWell(sourceItem); } @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; + 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) @@ -213,19 +232,15 @@ class BagWindow extends Modal { if (!open) { // Closed, opening the bag btn.classes - ..remove("item-container-closed") - ..remove("fa-plus") - ..add("item-container-open") - ..add("fa-times"); + ..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"); + ..remove("item-container-open")..remove("fa-times") + ..add("item-container-closed")..add("fa-plus"); item.classes.remove("inv-item-disabled"); } } -} \ No newline at end of file +} 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/display/windows/map_window.dart b/lib/src/display/windows/map_window.dart index 2b6cf6b7..7407433b 100644 --- a/lib/src/display/windows/map_window.dart +++ b/lib/src/display/windows/map_window.dart @@ -1,111 +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.onInput.listen((_) => filter(searchBox.value)); - 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 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/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 1fb1bd4d..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,15 +56,28 @@ 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']; 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"; + currants.text = " ${commaFormatter.format(metabolics.currants)} currant${(metabolics.currants != 1 ? "s" : "")}"; new List.from(buy.children) ..forEach((child) => child.remove()); @@ -77,26 +91,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"); } @@ -104,15 +121,24 @@ 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); }); - if(sellMode) + if(sellMode) { this.displayElement.querySelector('#SellTab').click(); - else + } else { this.displayElement.querySelector('#BuyTab').click(); + } this.open(); } @@ -166,35 +192,37 @@ class VendorWindow extends Modal { int newNum = buyNum.valueAsNumber.toInt(); numToBuy = _updateNumToBuy(item, newNum, sellMode:sellMode); } - catch(e) { - } + catch(e) {} }); - StreamSubscription bb = buyButton.onClick.listen((_) { + // Sell/Buy Button + listeners.add(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["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(); - }); + })); // Plus Button - StreamSubscription bplus = buyPlus.onClick.listen((_) async { + listeners.add(buyPlus.onClick.listen((_) async { try { if (sellMode) { @@ -221,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 @@ -235,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) { @@ -253,20 +281,17 @@ 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) + if(sellMode) { sell.hidden = false; - else + } else { buy.hidden = false; + } }); } @@ -274,18 +299,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 +336,4 @@ class CustomAvatarHandler extends CloneAvatarHandler { document.body.append(super.avatar); super.setLeftTop(new Point(x, y)); } -} \ No newline at end of file +} 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; + } } 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 +} 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/lib/src/game/game.dart b/lib/src/game/game.dart index b76a4232..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,7 +97,7 @@ class Game { transmit("gameLoaded", loaded); logmessage("Game loaded!"); - 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}"); @@ -146,32 +152,4 @@ class Game { //previousTag.makeCurrent(); } - - Future updatePlayerLetters() async { - Map players = {}; - players.addAll(otherPlayers); - players.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/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/lib/src/network/metabolics.dart b/lib/src/network/metabolics.dart index c7af656a..3a4bcd5f 100644 --- a/lib/src/network/metabolics.dart +++ b/lib/src/network/metabolics.dart @@ -1,14 +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; - 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 + 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 2eab38dc..f3e39165 100644 --- a/lib/src/network/metabolics_service.dart +++ b/lib/src/network/metabolics_service.dart @@ -32,20 +32,13 @@ 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(); - } - int newImg = lifetime_img; - int newLvl; - if (newImg > oldImg) { - newLvl = await level; - if (oldLevel > newLvl - 2 && newLvl > oldLevel && webSocketMessages > 0) { - levelUp.open(); - } + transmit("metabolicsLoaded", playerMetabolics); } transmit('metabolicsUpdated', playerMetabolics); } @@ -152,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); } diff --git a/lib/src/network/server_interop/inventory.dart b/lib/src/network/server_interop/inventory.dart index 23e4a451..5272d239 100644 --- a/lib/src/network/server_interop/inventory.dart +++ b/lib/src/network/server_interop/inventory.dart @@ -7,6 +7,9 @@ class Slot { String itemType = ""; ItemDef item = null; int count = 0; + + @override + String toString() => 'Slot: $itemType, $count'; } class Inventory { @@ -17,9 +20,15 @@ class Inventory { class ItemDef { String category, iconUrl, spriteUrl, toolAnimation, name, description, itemType, item_id; - int price, stacksTo, iconNum = 4, durability, durabilityUsed = 0, subSlots = 0; + int price, + stacksTo, + iconNum = 4, + durability, + durabilityUsed = 0, + subSlots = 0; num x, y; - bool onGround = false, isContainer = false; + bool onGround = false, + isContainer = false; List subSlotFilter; List actions = []; Map metadata = {}; @@ -33,18 +42,9 @@ 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; - int slotNum = 0; List slots = []; //couldn't get the structure to decode correctly so I hacked together this @@ -54,10 +54,10 @@ Future updateInventory([Map map]) async { if (!m['itemType'].isEmpty) { ItemDef item; if (m['item']['metadata']['slots'] == null) { - item = decode(JSON.encode(m['item']), type:ItemDef); + 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 = decode(JSON.encode(m['item']), type: ItemDef); item.metadata = metadata; } slot.item = item; @@ -65,7 +65,6 @@ Future updateInventory([Map map]) async { slot.count = m['count']; } slots.add(slot); - slotNum++; }); playerInventory.slots = slots; @@ -73,10 +72,12 @@ Future updateInventory([Map map]) async { //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) { @@ -90,27 +91,32 @@ Future updateInventory([Map map]) async { updateNeeded = true; update = true; } else if (currentSlot.item != null && newSlot.item != null && - !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { - transmit('updateMetadata',newSlot.item); - continue; + !_metadataEqual(currentSlot.item.metadata, newSlot.item.metadata)) { + Map indexToItem = {'index':i,'item':newSlot.item}; + transmit('updateMetadata', indexToItem); } } 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 (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); -} \ 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 a2b34d49..280ca358 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 = []; @@ -36,45 +39,87 @@ itemContextMenu(ItemDef i, String slot, MouseEvent event) { document.body.append(menu); } -findNewSlot(Slot slot, int index, {bool update: false}) async { +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; Element barSlot = view.inventory.children.elementAt(index); barSlot.children.clear(); - String cssName = item.itemType.replaceAll(" ", "_"); Element itemDiv = new DivElement(); + await sizeItem(img,itemDiv,barSlot,item,count, int.parse(barSlot.dataset["slot-num"])); + + 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) { + 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 + 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); + } +} + +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 num scale = 1; if (img.height > img.width / item.iconNum) { - scale = (barSlot.contentEdge.height - 10) / img.height; + scale = (slot.contentEdge.height - 10) / img.height; } else { - scale = (barSlot.contentEdge.width - 10) / (img.width / item.iconNum); + scale = (slot.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"; + 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 = 'item-$cssName inventoryItem'; + itemDiv.className = cssClass; itemDiv.attributes['name'] = item.name.replaceAll(' ', ''); itemDiv.attributes['count'] = "1"; itemDiv.attributes['itemMap'] = encode(item); - String slotNum = '${barSlot.dataset["slot-num"]}.-1'; + String slotNum = '$barSlotNum.$bagSlotNum'; itemDiv.onContextMenu.listen((MouseEvent event) => itemContextMenu(item, slotNum, event)); - barSlot.append(itemDiv); + slot.append(itemDiv); SpanElement itemCount = new SpanElement() ..text = count.toString() ..className = "itemCount"; - barSlot.append(itemCount); + slot.append(itemCount); if (count <= 1) { itemCount.text = ""; } @@ -84,97 +129,8 @@ findNewSlot(Slot slot, int index, {bool update: false}) async { 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'); } -//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; @@ -186,7 +142,9 @@ void takeItemFromInventory(String itemType, {int count: 1}) { int uiCount = int.parse(item.attributes['count']); if (uiCount > count) { item.attributes['count'] = (uiCount - count).toString(); - item.parent.querySelector('.itemCount').text = (uiCount - count).toString(); + item.parent + .querySelector('.itemCount') + .text = (uiCount - count).toString(); } else { item.parent.children.clear(); } @@ -207,4 +165,4 @@ Map getDropMap(int count, int slotNum, int subSlotNum) { ..['tsid'] = currentStreet.streetData['tsid']; return dropMap; -} \ No newline at end of file +} 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/network/server_interop/so_player.dart b/lib/src/network/server_interop/so_player.dart index 1b504d7b..7af6f231 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; } @@ -52,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"]); @@ -168,6 +173,8 @@ updateOtherPlayer(Map map, Player otherPlayer) { facingRight = true; } otherPlayer.facingRight = facingRight; + + attachPlayerLetter(map["letter"], otherPlayer); } void removeOtherPlayer(String username) { @@ -177,4 +184,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 diff --git a/lib/src/network/streetservice.dart b/lib/src/network/streetservice.dart index ceb491a1..c2fae917 100644 --- a/lib/src/network/streetservice.dart +++ b/lib/src/network/streetservice.dart @@ -33,8 +33,11 @@ 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 > 1) { + if(players.length > 0) { for(int i = 0; i != players.length; i++) { playerList += players[i]; if(i != players.length) { @@ -43,8 +46,6 @@ class StreetService { } playerList = playerList.substring(0, playerList.length - 2); toast("Players on this street: " + playerList); - } else { - toast("Nobody else is on this street"); } } } @@ -104,7 +105,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 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(); } 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/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 diff --git a/web/files/css/desktop/interactions.css b/web/files/css/desktop/interactions.css index 4ddb4de8..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; @@ -572,7 +572,7 @@ .entityContainer:hover, .entitySelected { border: 1px solid orange; - background-color: #F8F2E1; + background-color: #F8DA9E; color: orange; } diff --git a/web/files/css/desktop/interface/chat.css b/web/files/css/desktop/interface/chat.css index 74eaeb0d..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 { @@ -382,4 +331,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; +} 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 +} 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/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
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 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

diff --git a/web/main.dart b/web/main.dart index 9d6366c4..6578a4a5 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 //