Text On Map Help (MapCanvas)

Discussion in 'Plugin Development' started by KeybordPiano459, Jul 9, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    KeybordPiano459

    How do I get this (drawText) to work? Does it have to be my main class or a different one?
    At the moment, I have this in my code:
    Code:
    public void drawText(int x, int y, MapFont font, String text) {
    ("Hi");
    }
    Why doesn't it work?

    EDIT: This is my entire code:
    Code:
    package me.KeybordPiano459.MapTest;
     
    import org.bukkit.map.MapFont;
     
    public interface Main {
        public void drawText(int x, int y, MapFont font, String text) {
    ("Hello");
    }
    }
     
  2. Offline

    sayaad

    You need to send the map canvas to a player's map and update that map ID.

    Here is a link you can use.
     
  3. Offline

    KeybordPiano459

    I've seen that, how do I use it though?
     
  4. Offline

    sayaad

    The link I sent you is a documentation of the map API and some examples :3
     
  5. sayaad Did you ever work with maps? The documentation is crap!

    You need a MapCanvas to render onto the map (MapRenderer.render() needs it as argument and also only the MapCanvas has the real rendering functions), but the MapCanvas is just a interface, so you have to create a MapCanvas for yourself and create its drawImage(x, y, image) method (as well as the other methods like get/setPixel), but no documentation at all what that method has to do or any hint how to give the image to the renderer (the interface says it's a void method).

    Also the examples never show how to render. They show how to use the MapRenderer and that it needs a MapCanvas, but not how to create that damn MapCanvas. Try to copy the examples into your own code, you'll fail at the MapCanvas...

    Okay, I decompiled a plugin using the map API and saw how it's done. You don't need a map canvas. I updated V10phone for it and here are some codes:
    Code:java
    1. ppackage de.V10lator.V10phone;
    2.  
    3. import java.awt.Image;
    4.  
    5. import org.bukkit.entity.Player;
    6. import org.bukkit.map.MapCanvas;
    7. import org.bukkit.map.MapRenderer;
    8. import org.bukkit.map.MapView;
    9.  
    10. public class V10PhoneRenderer extends MapRenderer
    11. {
    12. private Image onScreen;
    13.  
    14. void setImageOnScreen(Image image)
    15. {
    16. onScreen = image;
    17. }
    18.  
    19. @Override
    20. public void render(MapView mv, MapCanvas mc, Player p)
    21. {
    22. if(onScreen != null)
    23. mc.drawImage(0, 0, onScreen);
    24. }
    25. }

    This is the class that actually writes on the screen. the render fucntion is called by bukkit internally, so no need to have a MapCanvas.

    Now the part that sends the player a new map:
    Code:java
    1. MapView mv = plugin.getServer().createMap(receiver.getWorld());
    2. receiver.getInventory().addItem(new ItemStack(Material.MAP, 1, mv.getId()));
    3. Mobile phone = new Mobile(receiver, plugin, mv);
    4. plugin.mobiles.put(receiver, phone);

    As you see it gives the MapView to the Mobile, which doesn't save it but give it to the DisplayBuffer, which has something to do with V10phones internals. The DisplayBuffer does this:
    Code:java
    1. public class DisplayBuffer
    2. {
    3. private final Mobile mobile;
    4. V10PhoneRenderer r = new V10PhoneRenderer();
    5. public BufferedImage image;
    6. private BufferedImage screen;
    7. public String text;
    8. private boolean fullscreen = true;
    9. public String[] buttons = {"",""};
    10. Font font = new Font("FuzzballFury 5px", Font.TRUETYPE_FONT, 10);
    11.  
    12. public DisplayBuffer(Mobile mobile, MapView mv)
    13. {
    14. this.mobile = mobile;
    15. font = font.deriveFont(5F);
    16. this.image = blank();
    17. this.text = "";
    18.  
    19. mv.getRenderers().clear();
    20. mv.addRenderer(r);
    21.  
    22. update(true);
    23. mobile.oldScreen = realUpdate(mobile.oldScreen);
    24. }
    25. ...

    So it removes all previos renderers and attaches a new one in its constructor (and also saves it for later use).

    Later, whenever a display update is needed it calls this:
    Code:java
    1. r.setImageOnScreen(image);

    Where Image is a pre-renderet image to get painted onto the map.

    But the result is suboptimal, the display refreshes in slices which makes it almost impossible to render animations. Also the map cursor is still paintet on top of the map (should be removeable).

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 26, 2016
  6. Offline

    desht

    Use player.sendMap() at the end of your render() method to send the entire map at once to the player. You should also take care in your render() to only send changes if the data you're rendering has actually changed (some kind of "dirty" flag in your object is useful here), to avoid excessive CPU/bandwidth consumption.

    See https://github.com/desht/ScrollingM...rollingmenusign/views/map/SMSMapRenderer.java for an example, where I render the contents of my commands menus onto a map. That also uses a SMSMapView object which represents the player's view (scroll position, etc.) on a SMSMenu object - a SMSMenu has one or more associated SMSView objects, SMSMapView being a subclass of SMSView, and each SMSMapView has one SMSRenderer to render the menu's contents to the player that's holding the map.

    Update: and yeah, the Map API's docs leave a little to be desired :) I need to credit dumptruckman for providing some useful code that I was able to peruse and gain an understanding of the API.
     
    V10lator likes this.
  7. Thanks. :)
    Sure, the DisplayBuffer does this, but right now it doesn't expose it to the MapRenderer cause it was just a quick&dirty update of the old codes to see if it works at all.
     
  8. Offline

    desht

    Right, so you'll want some kind of isChanged() method in your DisplayBuffer so that the renderer can check it, I guess. And an associated setChanged() so the renderer can reset the "dirty" state after the change has been rendered. The render() method does get called every tick while a player is holding the map in his hand, so you definitely want to spend as little time as possible in it.

    You might also want to look into contextual renderers, if you expect the same map ID to be held by multiple players and you want each player to see a different image on the map. That's not too hard - in your renderer constructor, just pass super(true); - that will initialize the Renderer object to use contextual canvases, i.e. it will have a separate MapCanvas object for each Player. Not sure if you're already aware of that part of the API, just putting it out there :)
     
  9. As the DisplayBuffer calls setImageOnScreen only if really needed (image changed) this is a bit overkilled. I simply changed the codes to this:
    Code:java
    1. package de.V10lator.V10phone;
    2.  
    3. import java.awt.Image;
    4.  
    5. public class V10PhoneRenderer extends MapRenderer
    6. {
    7. private Image onScreen;
    8.  
    9. void setImageOnScreen(Image image)
    10. {
    11. onScreen = image;
    12. }
    13.  
    14. @Override
    15. public void render(MapView mv, MapCanvas mc, Player p)
    16. {
    17. if(onScreen != null)
    18. {
    19. mc.drawImage(0, 0, onScreen);
    20. onScreen = null;
    21. }
    22. }
    23. }

    Which seems to work fine. But as soon as I change it to that:
    Code:java
    1. package de.V10lator.V10phone;
    2.  
    3. import java.awt.Image;
    4.  
    5. public class V10PhoneRenderer extends MapRenderer
    6. {
    7. private Image onScreen;
    8.  
    9. void setImageOnScreen(Image image)
    10. {
    11. onScreen = image;
    12. }
    13.  
    14. @Override
    15. public void render(MapView mv, MapCanvas mc, Player p)
    16. {
    17. if(onScreen != null)
    18. {
    19. mc.drawImage(0, 0, onScreen);
    20. p.sendMap(mv);
    21. onScreen = null;
    22. }
    23. }
    24. }

    The server freezes till it kicks the player and gives a (large) StackOverflowError (to big to parse at pastie). The solution is to not call p.sendMap(mv); inside of the render function but right after r.setImageOnScreen(image); ;)
    Thanks, will have a look into it. :)

    desht Do you know how to remove the damn cursor?
    I tried this:
    Code:java
    1. public class V10PhoneRenderer extends MapRenderer
    2. {
    3. private Image onScreen;
    4. private final MapCursorCollection mcc = new MapCursorCollection();
    5.  
    6. void setImageOnScreen(Image image)
    7. {
    8. onScreen = image;
    9. }
    10.  
    11. @Override
    12. public void render(MapView mv, MapCanvas mc, Player p)
    13. {
    14. if(onScreen != null)
    15. {
    16. mc.setCursors(mcc);
    17. mc.drawImage(0, 0, onScreen);
    18. onScreen = null;
    19. }
    20. }
    21. }

    as well as that:
    Code:java
    1. public class V10PhoneRenderer extends MapRenderer
    2. {
    3. private Image onScreen;
    4. // private final MapCursorCollection mcc = new MapCursorCollection();
    5.  
    6. void setImageOnScreen(Image image)
    7. {
    8. onScreen = image;
    9. }
    10.  
    11. @Override
    12. public void render(MapView mv, MapCanvas mc, Player p)
    13. {
    14. if(onScreen != null)
    15. {
    16. MapCursorCollection mcc = mc.getCursors();
    17. for(int i = 0; i < mcc.size(); i++)
    18. mcc.removeCursor(mcc.getCursor(i));
    19. mc.setCursors(mcc);
    20. mc.drawImage(0, 0, onScreen);
    21. onScreen = null;
    22. }
    23. }
    24. }

    But the cursor won't go away... :(

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 26, 2016
  10. Offline

    desht

    V10lator I didn't do anything specific to remove the cursors in my SMSMapRenderer and I don't have any cursors on the map, so I would guess removing the default renderer is what does the trick there. See setMapId() in https://github.com/desht/ScrollingM...desht/scrollingmenusign/views/SMSMapView.java.

    Update: yeah, looking at your original code, you have:
    PHP:
    mv.getRenderers().clear();
    But getRenderers() returns a copy of the view's renderer list, so calling clear() like that won't work. You need to call removeRenderer() like so:
    PHP:
    for (MapRenderer r view.getRenderers()) {
      
    mv.removeRenderer(r);
    }
     
    bobacadodl and V10lator like this.
  11. desht But I'm removing all renderers:
    Code:java
    1. mv.getRenderers().clear();
    2. mv.addRenderer(r);

    Or is clear() not working like expected here?
     
  12. Offline

    desht

    See above :)
     
  13. Arrgh... Am I allowed to call this a stupid decision of the bukkit team? ^^
    Anyway, your solution works perfectly. Thanks. :)
     
    desht likes this.
  14. Offline

    sayaad

    Oh great now I need to find a new documentation >.>

    Why didn't I start with the official documentation?

    *Head Desk*

    Bookmarking this page for future reference...
     
  15. Cause it's the same (or even more) crap? ;)
    But well, we figured it out. Would be nice if somebody would write a tutorial about it into the resources section so other devs are able to figure it out more easy...
     
Thread Status:
Not open for further replies.

Share This Page