Help with MapRenderer

Discussion in 'Spigot Plugin Development' started by ZiomaleQ, Jul 14, 2018.

  1. Hello how can i Render map with custom data? Every time i want to call Render.class i want specify parameters like color, text. How can i do this?
     
  2. This is the code I use for rendering.
    Code (Text):


    //probably need to replace the world part
    World world = Bukkit.getServer().getWorld("world");

    MapView mapView = null;
    mapView = Bukkit.getServer().createMap(world);
    mapView.setScale(MapView.Scale.FARTHEST);

    mapView.getRenderers().clear();
    mapView.addRenderer(new MapRenderer() {
        @Override
        public void render(MapView mapView, MapCanvas mapCanvas, Player player) {
            //code goes here
           
            // to draw text you do this
    mapCanvas.drawText(x, y, MinecraftFont.Font, "any text here or a string");
        }
    });

     
    For the itemstack part of the map i do this afterwards

    Code (Text):


    new ItemStack(Material.MAP, 1, mapView.getId()

     
    For further information about maprenderer check this out https://www.spigotmc.org/threads/tutorial-maps.136533/

    If you found my reply helpful please rate this reply :)
     
    • Useful Useful x 2
  3. You could just draw all the stuff onto image and then draw image on map(s)..
     
  4. Yeah you can do that, I'm doing that in my case but rendering a bufferedimage and displaying that is far more complicated, is way more load for the server.
     
  5. Well ofc it is, that is why you should do it async..
    For example, rendering a 44 second video ([email protected]) with all cores of my cpu on maps takes 14 minutes with bukkit method, but only 33 seconds with BKCommonLib..
    So you need to pre-render and pre-create all packet data or do it on multiple threads if you want best performance..
    Netty seems to process packets for player on single thread so it won't be fast enough if there is a lot of big packets, you will need to bypass netty, create the packet(as byte array) and send it yourself..
     
    • Useful Useful x 1
  6. @Mareckoo01 i have to admit that I've never wirked with stuff like this in any way. I'd be interested if you have any code or informatipnal sites to see what you are talking abou?
     
  7. I'm actually working on a map plugin myself and I found your information really helpful would you be open to talk a bit more about these things in spigot pms or discord?
     
  8. It's actually really really easy, just make sure you cache the images. I'll show you my code once I'm on my pc.
     
    • Like Like x 1
  9. Note: I'm not that good at explaining stuff and i assumed few things so do your own research aswell..


    I tried several different ways of drawing image on maps but the best way is to do basicaly all processing on multiple threads and then send bytes to players.
    First thing you will need to do is convert the video to resolution and framerate you use, then to images and ogg sound files, maybe i did something wrong but i always need to reconvert those ogg files (ogg(from video) -> ogg).. I do that with ffmpeg and then create resource pack with sound files semi-manually.
    You will get best prerformance by creating the packets and saving them to file which would be loaded later.

    (old way)
    I decided to do 10s clips, ogg sound files are 10s long and files with packets contain data for 10s of video(= 200 frames since it runs at 20fps).
    That way if the server is too slow you will give it a way to catchup instead of having the sound desync(if you want to be tps bound or disk is too slow).
    I have it like so:
    Code (Java):
    //read this from bottom up, it will make more sense
    List<List<byte[]>> frames; // contains all frames
    ^ // this would contain 200 frames in my case
    List<byte[]> mapPackets; // contains all map packets for this frame
    ^ // if the display is 10x6 this would contain 60 packets
    byte[] mapPacket; // map packet as bytes that are sent to client and processed client side,
    // (client side)this is faster than creating it so it is not a problem to process bytes -> map packet object
    I have 2 "frames" variables so while it is playing frames from first it will load second async otherwise it would freeze for a bit(and then it loads for first while playing from second)..
    Another thing to keep in mind is that if you run a map at 20 fps it will use roughly 1.33Mbps if you use compression, so don't send map data if the image on map didn't change to save bandwidth, in my case i have 60 maps so that would be ~80Mbps if i send all of them every second.
    Otherwise without compression, map packet is roughly 16KiB(16384 bytes) big so sending 60 of them at 20 fps would be ~19MiB(19660800 bytes) per second(= ~160Mbps)
    I should have mentioned this before but i have 10x6 wall of item frames with maps so the resultion is 128*10 by 128*6 (closest to 16:9 ratio that is very common).
    Bandwidth usage can be reduced to 1/4th by having it be 5x3 if you don't mind lower resolution.


    As i said before, netty processes packets for single player on single thread so it won't be fast enough if you let it handle all the packets, so you need to bypass it.
    A TL;DR on how it works would be
    minecraft's packet handler -> minecraft's compressor, encryptor,... -> connection
    And when it receives stuff it goes the other way..
    For example if you take a look at TinyProtocol you will see that it adds it's handler before minecraft's packet handler, that way if server sends packet it will go from minecraft's packet handler to TinyProtocol's handler and then to other stuff or you can choose not to pass it to other stuff - cancel outgoing packet, and because receiving goes the other way, tinyprotocol's handler will receive the packet before minecraft's packet handler so you can choose not to pass it to next thing aswell - minecraft's packet handler or someone's else handler if they added it there.
    You can check what is in netty pipeline like so:
    Code (Java):
    Player player;
    ((CraftPlayer) player).getHandle().playerConnection.networkManager.channel.pipeline().forEach(e->{
        Bukkit.broadcastMessage(e.getKey() + " -=- " + e.getValue());
    });

    What i did and what i plan to do:
    * showing image on one map - bukkit method is good enough
    * showing gif on one map - bukkit method is good enough
    * showing image on multiple maps - bukkit method is good enough
    * showing gif on multiple maps - bukkit method is slow so you will start looking for something faster
    * playing a video on maps - i tried just creating the converted data and then creating the packet object and sending it (wasn't fast enough), tried storing it in different ways (i don't recommend storing it by encoding it with base64 and storing output in json because 200 frames would be ~2GiB json file and would need ~7GiB ram to load/save), i now use java's ObjectOutputStream to zip file
    * playing a video on maps with sound - sound could desync so you need shorter files
    *+ drawing all kinds of stuff on maps with sounds:
    + MCPaintIDE
    + different games 2D and 3D for example a game where you click circles while music is playing or a sandbox game?
    + web browser(requires too much effort so probably not)
    + etc..

    In the last version i would draw all components onto image and then send it.
    Code(needs cleanup)
    Usage:
    Code (Java):
    //in your class (i call it MapDisplay) you would have something like this
    ImageToMapRenderer renderer;

    init:
    renderer = new ImageToMapRenderer(...);

    on_tick:
    BufferedImage img = new BufferedImage(...);
    drawAllStuffOntoImage(img);
    renderer.renderAndSend(...);

    on_removal:
    renderer.shutdown(); // needs to be called to cleanup
    But this is not good enough so i'm working on a different way to do it.
    Also another thing is that packets are processed every tick, not instantly when server/client receives them so by going over 20 fps you just waste cpu time and bandwidth.. (you can bypass this on server but client would have to have a mod, and if the client has mod then there is a WAY better way to do this)
    And if the player holds left click it will send only 1 packet, holding right click will send a packet every 4 ticks but player needs to hold a item in hand otherwise it won't send any packet if you don't right click a block.


    Let me know if i made some typos/mistakes.
     
    • Winner Winner x 2
  10. As you said that it would be possile create a web browser or a game. What would you use to make buttons that you can press on the item frame?
    I think that you would be only able to get the item frame that is pressed not the pixel that is pressed, I'm probably wrong.
     
  11. You can calculate what pixel was pressed with NMS World#rayTrace() or like this (source)
     
  12. How can I use this method to get exact pixel that the player is looking at?
     
  13. If you mean the method i use then read the comments and look at what it does, even if you don't (fully)understand the math(like me,) you should see what variables it takes and what it does with them so you can implement it to fit your setup.
    Otherwise World#rayTrace returns MovingObjectPosition with the position it hit as Vec3D so you will need to calculate pixel location from that(looking into that is on my TODO list)..
     
    #13 Mareckoo01, Jul 17, 2018
    Last edited: Jul 17, 2018