04.02.2016, 11:40
Thanks for the feedback! 
@SystemX:
If you need some explanations, examples or statements why using Shoebill, you can write me, i really can recommend Shoebill, easily, fast and lots of external function support.
@FreAkeD:
Thanks, the only animation bug occurs when you are try to display a message like this:
, because i trimmed the text for this reason, that i did not know that textdraws will display whitespaces at the end, but i can fix this soon.
@valych:
Yeah, i try to reach the limits of SAMP and its performance so i play with textdraws and calculation, but this chat works great and have a advanced animation
You easily can change the appearance of the textdraw in createTextdraw(), it depends on the taste of every person^^
I improved the code to pre-calculate the string snippets and handle the chat with a better displayed performance!
ChatMessage.java
Chat.java

@SystemX:
If you need some explanations, examples or statements why using Shoebill, you can write me, i really can recommend Shoebill, easily, fast and lots of external function support.
@FreAkeD:
Thanks, the only animation bug occurs when you are try to display a message like this:
Код:
"WHITE SPACES"
@valych:
Yeah, i try to reach the limits of SAMP and its performance so i play with textdraws and calculation, but this chat works great and have a advanced animation

You easily can change the appearance of the textdraw in createTextdraw(), it depends on the taste of every person^^
I improved the code to pre-calculate the string snippets and handle the chat with a better displayed performance!
ChatMessage.java
Код:
/** * */ package me.alf21.util.chat; import java.util.regex.Matcher; import net.gtaun.shoebill.constant.TextDrawAlign; import net.gtaun.shoebill.constant.TextDrawFont; import net.gtaun.shoebill.data.Color; import net.gtaun.shoebill.object.Destroyable; import net.gtaun.shoebill.object.Player; import net.gtaun.shoebill.object.PlayerTextdraw; import net.gtaun.shoebill.object.Timer; /** * @author Alf21 * */ public class ChatMessage implements Destroyable{ private PlayerTextdraw textdraw; private int slot; private Timer timer; private boolean sliding; private String text; private int count; private Player player; private Long time; private String[] slideInStrings; private String[] slideOutStrings; public ChatMessage(Player player, String text, int slot) { this.player = player; this.text = removeNonColorCodes(text).trim(); this.slot = slot; this.count = 1; createTextdraw(0,0); //initialize the string snippets to slide the text in slideInStrings = new String[getTextLength()]; slideOutStrings = new String[getTextLength()-1]; //calculate the string snippets int currentEndIndex = 0; for(int i=0; i<slideInStrings.length; i++) { currentEndIndex++; currentEndIndex = cutTextIn(currentEndIndex); slideInStrings[i] = text.substring(0, currentEndIndex); } currentEndIndex = text.length(); for(int i=0; i<slideOutStrings.length; i++) { currentEndIndex--; currentEndIndex = cutTextOut(currentEndIndex); slideOutStrings[i] = text.substring(0, currentEndIndex); } time = System.currentTimeMillis(); } public void slideIn() { sliding = true; reset(); final int length = slideInStrings.length; final int interval = (int) Chat.slideTime/length; timer = Timer.create(interval, length, (factualInterval) -> { textdraw.hide(); textdraw.setText(slideInStrings[count-1]); textdraw.show(); if(count == length) { sliding = false; timer.stop(); } count++; }); timer.start(); } public void slideOut() { sliding = true; reset(); final int length = slideOutStrings.length; final int interval = (int) Chat.slideTime/length; timer = Timer.create(interval, length, (factualInterval) -> { textdraw.hide(); textdraw.setText(slideOutStrings[count-1]); textdraw.show(); if(count == length) { sliding = false; timer.stop(); } count++; }); timer.start(); } public void moveUp() { reset(); final int maxCount = 15; final int interval = (int) Chat.slideTime/maxCount; timer = Timer.create(interval, maxCount, (factualInterval) -> { textdraw.hide(); textdraw.destroy(); createTextdraw(0, (float) -count*(15f/(float) maxCount)); textdraw.show(); if(count == maxCount) { slot++; timer.stop(); } count++; }); timer.start(); } public void hide() { textdraw.hide(); } public void show() { textdraw.show(); } private void reset() { if(timer != null) timer.destroy(); count = 1; } private void createTextdraw(float x, float y) { textdraw = PlayerTextdraw.create(player, 630+x, 385-15*slot+y); textdraw.setText(text); textdraw.setAlignment(TextDrawAlign.RIGHT); textdraw.setBackgroundColor(Color.BLACK); textdraw.setFont(TextDrawFont.get(1)); textdraw.setLetterSize(0.5f, 1); textdraw.setColor(Color.WHITE); textdraw.setOutlineSize(1); textdraw.setProportional(true); textdraw.setSelectable(false); } /** * @return the sliding */ public boolean isSliding() { return sliding; } /** * @param sliding the sliding to set */ public void setSliding(boolean sliding) { this.sliding = sliding; } /** * @return the time when textdraw was created */ public Long getTime() { return time; } /** * @return the textdraw */ public PlayerTextdraw getTextdraw() { return textdraw; } /** * @return the player */ public Player getPlayer() { return player; } /* (non-Javadoc) * @see net.gtaun.shoebill.object.Destroyable#destroy() */ @Override public void destroy() { if(timer != null) { if(timer.isRunning()) timer.stop(); timer.destroy(); timer = null; } if(textdraw != null) { textdraw.hide(); textdraw.destroy(); textdraw = null; } } /* (non-Javadoc) * @see net.gtaun.shoebill.object.Destroyable#isDestroyed() */ @Override public boolean isDestroyed() { if(timer != null || textdraw != null) return false; return true; } private String removeNonColorCodes(String text) { final String prefixOfColorCodes = "COLOR"; //The prefix of any color code like "COLOR_RED" <- HERE>> if String == "": "_RED" final String connectionOfColorCodes = "\u005F"; //The connection label or Unicode, here: "\u005F" == "_" <- HERE>> if String == "": "COLORRED" String replaceStr = prefixOfColorCodes + connectionOfColorCodes; text = text.replaceAll("\u007E" + "w" + "\u007E", replaceStr + "WHITE"); text = text.replaceAll("\u007E" + "r" + "\u007E", replaceStr + "RED"); text = text.replaceAll("\u007E" + "g" + "\u007E", replaceStr + "GREEN"); text = text.replaceAll("\u007E" + "b" + "\u007E", replaceStr + "DARK" + connectionOfColorCodes + "BLUE"); text = text.replaceAll("\u007E" + "y" + "\u007E", replaceStr + "YELLOW"); text = text.replaceAll("\u007E" + "p" + "\u007E", replaceStr + "PURPLE"); text = text.replaceAll("\u007E" + "l" + "\u007E", replaceStr + "BLACK"); text = text.replaceAll("\u007E" + "h" + "\u007E", replaceStr + "LIGHTER"); text = text.replaceAll("\u007E+([\\w[\u003C\u003E&&[\\S]]]?)\u007E", ""); //replace unknown colors and Commands text = text.replaceAll("\u007E", ""); //replace other "~" text = text.replaceAll(replaceStr + "WHITE", "\u007E" + "w" + "\u007E"); text = text.replaceAll(replaceStr + "RED", "\u007E" + "r" + "\u007E"); text = text.replaceAll(replaceStr + "GREEN", "\u007E" + "g" + "\u007E"); text = text.replaceAll(replaceStr + "DARK" + connectionOfColorCodes + "BLUE", "\u007E" + "b" + "\u007E"); text = text.replaceAll(replaceStr + "YELLOW", "\u007E" + "y" + "\u007E"); text = text.replaceAll(replaceStr + "PURPLE", "\u007E" + "p" + "\u007E"); text = text.replaceAll(replaceStr + "BLACK", "\u007E" + "l" + "\u007E"); text = text.replaceAll(replaceStr + "LIGHTER", "\u007E" + "h" + "\u007E"); return text; } private int cutTextIn(int endIndex) { int currentEndIndex = endIndex; String string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex++; string = text.substring(0, currentEndIndex); } //check whether string contains color codes Matcher matcher = Chat.colorStringPattern.matcher(text); if(matcher.find()) { int matcherState = 0; matcher = Chat.colorStringEndPattern.matcher(string); Matcher matcher2 = Chat.wrongColorStringPattern.matcher(string); if(matcher.find() && !matcher2.find()) { matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher matcherState = 3; } else { matcher = Chat.tildeLetterPattern.matcher(string); if(matcher.find()) { matcher = Chat.tildeLetterPattern.matcher(string); //update the matcher matcherState = 4; } else { matcher = Chat.tildePattern.matcher(string); //update the matcher matcherState = 5; } } while(matcher.find()) { //also check whether color code is after this color code if(currentEndIndex+matcherState <= text.length()) { currentEndIndex += matcherState; string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex++; string = text.substring(0, currentEndIndex); } matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher if(!matcher.find()) { currentEndIndex -= 2; string = text.substring(0, currentEndIndex); } else matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher matcherState = 3; } } string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex++; string = text.substring(0, currentEndIndex); } matcher2 = Chat.tildePattern.matcher(string); if(matcher2.find()) { if(currentEndIndex+3 <= text.length()) currentEndIndex += 3; } } return currentEndIndex; } private int cutTextOut(int endIndex) { int currentEndIndex = endIndex; String string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex--; string = text.substring(0, currentEndIndex); } //check whether string contains color codes Matcher matcher = Chat.colorStringPattern.matcher(text); if(matcher.find()) { int matcherState = 0; matcher = Chat.colorStringEndPattern.matcher(string); if(matcher.find()) { matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher matcherState = 3; } else { matcher = Chat.tildeLetterPattern.matcher(string); String string2 = text.substring(0, currentEndIndex+1); Matcher matcher2 = Chat.colorStringEndPattern.matcher(string2); if(matcher.find() && matcher2.find()) { matcher = Chat.tildeLetterPattern.matcher(string); //update the matcher matcherState = 2; } else { matcher = Chat.tildePattern.matcher(string); //update the matcher matcherState = 1; } } while(matcher.find()) { //also check whether color code is before this color code if(currentEndIndex-matcherState > 0) { currentEndIndex -= matcherState; string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex--; string = text.substring(0, currentEndIndex); } matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher matcherState = 3; } } string = text.substring(0, currentEndIndex); while(string.trim().length() < string.length()){ currentEndIndex--; string = text.substring(0, currentEndIndex); } } return currentEndIndex; } private int getTextLength() { int length = text.replace(" ", "").length(); Matcher matcher = Chat.colorStringPattern.matcher(text); while(matcher.find()) { length -= 3; } return length; } }
Код:
/** * */ package me.alf21.util.chat; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import net.gtaun.shoebill.object.Destroyable; import net.gtaun.shoebill.object.Player; import net.gtaun.shoebill.object.Timer; /** * @author Alf21 */ public class Chat implements Destroyable { private Timer timer; public static final int updateDelay = 50; public static final int displayTime = 5000; public static final int slideTime = 500; public static final Pattern colorStringPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E"), //...~r~... colorStringEndPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E\\z"), //...~r~| tildeLetterPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\\z"), //...~r| tildePattern = Pattern.compile("\u007E\\z"), //...~| wrongColorStringPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E\\z"); //...~s~r| private ArrayList<ChatMessage> chatMessages; private ChatMessage currentChatMessage; private Map<Player, String> queue; public Chat() { reset(); initializeTimer(); } public void sendMessage(Player player, String text) { queue.put(player, text); if(!timer.isRunning()) timer.start(); } private void reset() { if(chatMessages != null && !chatMessages.isEmpty()) { chatMessages.stream().forEach((chatMessage) -> { chatMessage.hide(); chatMessage.destroy(); }); } chatMessages = new ArrayList<ChatMessage>(); queue = new HashMap<Player, String>(); currentChatMessage = null; } private void initializeTimer() { timer = Timer.create(updateDelay, (factualInterval) -> { Long systemTime = System.currentTimeMillis(); ArrayList<ChatMessage> removeMessages = new ArrayList<ChatMessage>(); //TODO Solution with Iterator if(chatMessages != null && !chatMessages.isEmpty()) { chatMessages.stream().forEach((chatMessage) -> { if(systemTime-chatMessage.getTime() >= slideTime) { if(currentChatMessage != null) { currentChatMessage = null; } } if(systemTime-chatMessage.getTime() >= displayTime+2*slideTime) { removeMessages.add(chatMessage); } else if(systemTime-chatMessage.getTime() >= displayTime+slideTime) { if(!chatMessage.isSliding()) { chatMessage.slideOut(); } } }); removeMessages.stream().forEach((removeMessage) -> { removeChatMessage(removeMessage); }); removeMessages.clear(); } if(currentChatMessage == null && !queue.isEmpty()) { Map.Entry<Player,String> entry = queue.entrySet().iterator().next(); Player player = entry.getKey(); currentChatMessage = new ChatMessage(player, entry.getValue(), 0); queue.remove(player); chatMessages.add(currentChatMessage); } if(currentChatMessage != null) { if(chatMessages.size() > 1) { chatMessages.stream().forEach((chatMessage) -> { if(chatMessage != currentChatMessage) chatMessage.moveUp(); }); } if(!currentChatMessage.isSliding()) { currentChatMessage.slideIn(); } } if(chatMessages != null && chatMessages.isEmpty()) { timer.stop(); reset(); } }); } private void removeChatMessage(ChatMessage chatMessage) { if(chatMessages.contains(chatMessage)) { chatMessages.remove(chatMessage); } chatMessage.destroy(); } /* (non-Javadoc) * @see net.gtaun.shoebill.object.Destroyable#destroy() */ @Override public void destroy() { if(timer != null) { if(timer.isRunning()) timer.stop(); timer.destroy(); timer = null; } if(currentChatMessage != null) { currentChatMessage.destroy(); currentChatMessage = null; } if(chatMessages != null) { chatMessages.stream().forEach((chatMessage) -> chatMessage.destroy()); chatMessages = null; } if(queue != null) { queue = null; } } /* (non-Javadoc) * @see net.gtaun.shoebill.object.Destroyable#isDestroyed() */ @Override public boolean isDestroyed() { if(timer != null || currentChatMessage != null || chatMessages != null || queue != null) return false; return true; } //TODO: add background box and increase if new message and resize on message destroy / destroy if there is no message }