Implemented and tested custom maps for multiplayer

This commit is contained in:
Anuken
2018-01-22 17:07:07 -05:00
parent 96170a1a84
commit 7fb6b4cb9f
8 changed files with 146 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.core;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.utils.IntSet;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.Mindustry;
@@ -14,9 +15,9 @@ import io.anuke.mindustry.entities.SyncEntity;
import io.anuke.mindustry.entities.enemies.Enemy;
import io.anuke.mindustry.entities.enemies.EnemyType;
import io.anuke.mindustry.graphics.Fx;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Net.SendMode;
import io.anuke.mindustry.net.NetworkIO;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.resource.Recipe;
import io.anuke.mindustry.resource.Recipes;
@@ -93,17 +94,22 @@ public class NetClient extends Module {
Net.handle(WorldData.class, data -> {
UCore.log("Recieved world data: " + data.stream.available() + " bytes.");
NetworkIO.load(data.stream);
NetworkIO.loadWorld(data.stream);
Vars.player.set(Vars.control.core.worldx(), Vars.control.core.worldy() - Vars.tilesize * 2);
connecting = false;
Vars.ui.loadfrag.hide();
Vars.ui.join.hide();
gotData = true;
Net.send(new ConnectConfirmPacket(), SendMode.tcp);
GameState.set(State.playing);
Net.setClientLoaded(true);
finishConnecting();
});
Net.handle(CustomMapPacket.class, packet -> {
//custom map is always sent before world data
Pixmap pixmap = NetworkIO.loadMap(packet.stream);
Vars.world.maps().setNetworkMap(pixmap);
MapAckPacket ack = new MapAckPacket();
Net.send(ack, SendMode.tcp);
});
Net.handle(SyncPacket.class, packet -> {
@@ -131,7 +137,6 @@ public class NetClient extends Module {
} else {
entity.read(data);
}
}
});
@@ -330,6 +335,15 @@ public class NetClient extends Module {
}
}
private void finishConnecting(){
Net.send(new ConnectConfirmPacket(), SendMode.tcp);
GameState.set(State.playing);
Net.setClientLoaded(true);
connecting = false;
Vars.ui.loadfrag.hide();
Vars.ui.join.hide();
}
public void beginConnecting(){
connecting = true;
}

View File

@@ -62,19 +62,34 @@ public class NetServer extends Module{
player.interpolator.target.set(player.x, player.y);
connections.put(id, player);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.write(player.id, weapons.get(packet.name, new ByteArray()), stream);
if(Vars.world.getMap().custom){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeMap(Vars.world.getMap(), stream);
CustomMapPacket data = new CustomMapPacket();
data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(id, data);
UCore.log("Packed " + stream.size() + " uncompressed bytes of data.");
WorldData data = new WorldData();
data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(id, data);
UCore.log("Sending custom map: Packed " + stream.size() + " uncompressed bytes of MAP data.");
}else{
//hack-- simulate the map ack packet recieved to send the world data to the client.
Net.handleServerReceived(id, new MapAckPacket());
}
Mindustry.platforms.updateRPC();
});
Net.handleServer(MapAckPacket.class, (id, packet) -> {
Player player = connections.get(id);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
NetworkIO.writeWorld(player.id, weapons.get(player.name, new ByteArray()), stream);
WorldData data = new WorldData();
data.stream = new ByteArrayInputStream(stream.toByteArray());
Net.sendStream(id, data);
UCore.log("Packed " + stream.size() + " uncompressed bytes of WORLD data.");
});
Net.handleServer(ConnectConfirmPacket.class, (id, packet) -> {
Player player = connections.get(id);

View File

@@ -17,6 +17,7 @@ import io.anuke.ucore.graphics.Pixmaps;
public class Maps implements Disposable{
private IntMap<Map> maps = new IntMap<>();
private ObjectMap<String, Map> mapNames = new ObjectMap<>();
private Map networkMap;
private int lastID;
private Json json = new Json();
@@ -30,7 +31,23 @@ public class Maps implements Disposable{
return maps.values();
}
public void setNetworkMap(Pixmap pixmap){
if(networkMap != null){
networkMap.pixmap.dispose();
networkMap = null;
}
networkMap = new Map();
networkMap.custom = true;
networkMap.pixmap = pixmap;
networkMap.visible = false;
networkMap.name = "network map";
networkMap.id = -1;
}
public Map getMap(int id){
if(id == -1){
return networkMap;
}
return maps.get(id);
}

View File

@@ -1,34 +1,74 @@
package io.anuke.mindustry.net;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.utils.ByteArray;
import com.badlogic.gdx.utils.TimeUtils;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.game.GameMode;
import io.anuke.mindustry.resource.Upgrade;
import io.anuke.mindustry.resource.Weapon;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.WorldGenerator;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.types.BlockPart;
import io.anuke.mindustry.world.blocks.types.Rock;
import io.anuke.ucore.UCore;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.Entities;
import java.io.*;
import java.nio.ByteBuffer;
public class NetworkIO {
public static void write(int playerID, ByteArray upgrades, OutputStream os){
public static void writeMap(Map map, OutputStream os){
try(DataOutputStream stream = new DataOutputStream(os)){
Pixmap pix = map.pixmap;
ByteBuffer buffer = pix.getPixels();
UCore.log("Buffer position: " + buffer.position());
stream.writeShort(map.getWidth());
stream.writeShort(map.getHeight());
for(int i = 0; i < map.getWidth() * map.getHeight(); i ++){
int color = buffer.getInt();
byte id = ColorMapper.getColorID(color);
if(id == -1) id = 0;
stream.writeByte(id);
}
buffer.position(0);
}catch (IOException e){
throw new RuntimeException(e);
}
}
public static Pixmap loadMap(InputStream is){
try(DataInputStream stream = new DataInputStream(is)){
short width = stream.readShort();
short height = stream.readShort();
Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);
ByteBuffer buffer = pixmap.getPixels();
buffer.position(0);
for(int i = 0; i < width * height; i ++){
byte id = stream.readByte();
buffer.putInt(ColorMapper.getColorByID(id));
}
return pixmap;
}catch (IOException e){
throw new RuntimeException(e);
}
}
public static void writeWorld(int playerID, ByteArray upgrades, OutputStream os){
try(DataOutputStream stream = new DataOutputStream(os)){
stream.writeFloat(Timers.time()); //timer time
stream.writeLong(TimeUtils.millis()); //timestamp
//--GENERAL STATE--
stream.writeByte(Vars.control.getMode().ordinal()); //gamemode
stream.writeByte(Vars.world.getMap().id); //map ID
stream.writeByte(Vars.world.getMap().custom ? -1 : Vars.world.getMap().id); //map ID
stream.writeInt(Vars.control.getWave()); //wave
stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown
@@ -139,12 +179,8 @@ public class NetworkIO {
}
}
public static void load(FileHandle file){
load(file.read());
}
public static void load(InputStream is){
/**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/
public static void loadWorld(InputStream is){
try(DataInputStream stream = new DataInputStream(is)){
float timerTime = stream.readFloat();

View File

@@ -513,4 +513,16 @@ public class Packets {
id = buffer.getInt();
}
}
public static class CustomMapPacket extends Streamable{
}
public static class MapAckPacket implements Packet{
@Override
public void write(ByteBuffer buffer) { }
@Override
public void read(ByteBuffer buffer) { }
}
}

View File

@@ -37,6 +37,8 @@ public class Registrator {
GameOverPacket.class,
FriendlyFireChangePacket.class,
PlayerDeathPacket.class,
CustomMapPacket.class,
MapAckPacket.class
};
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();

View File

@@ -57,11 +57,7 @@ public class PausedDialog extends FloatingDialog{
if(!Vars.gwt) {
content().addButton("$text.hostserver", () -> {
if (Vars.world.getMap().custom) {
ui.showError("$text.nohost");
} else {
ui.host.show();
}
ui.host.show();
}).disabled(b -> Net.active());
}
@@ -100,11 +96,7 @@ public class PausedDialog extends FloatingDialog{
lo.cell.disabled(b -> Net.active());
imagebutton ho = new imagebutton("icon-host", isize, () -> {
if(Vars.world.getMap().custom){
ui.showError("$text.nohost");
}else {
ui.host.show();
}
ui.host.show();
});
ho.text("$text.host").padTop(4f);
ho.cell.disabled(b -> Net.active());

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.world;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntIntMap;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.IntMap.Entry;
import com.badlogic.gdx.utils.ObjectIntMap;
@@ -10,6 +11,11 @@ import io.anuke.mindustry.world.blocks.Blocks;
import io.anuke.mindustry.world.blocks.SpecialBlocks;
public class ColorMapper{
/**maps color IDs to their actual RGBA8888 colors*/
private static int[] colorIDS;
/**Maps RGBA8888 colors to pair IDs.*/
private static IntIntMap reverseIDs = new IntIntMap();
private static ObjectIntMap<Block> reverseColors = new ObjectIntMap<>();
private static Array<BlockPair> pairs = new Array<>();
private static IntMap<BlockPair> colors = map(
@@ -40,6 +46,14 @@ public class ColorMapper{
public static BlockPair get(int color){
return colors.get(color);
}
public static int getColorByID(byte id){
return colorIDS[id];
}
public static byte getColorID(int color){
return (byte)reverseIDs.get(color, -1);
}
public static IntMap<BlockPair> getColors(){
return colors;
@@ -62,10 +76,14 @@ public class ColorMapper{
}
private static IntMap<BlockPair> map(Object...objects){
colorIDS = new int[objects.length/2];
IntMap<BlockPair> colors = new IntMap<>();
for(int i = 0; i < objects.length/2; i ++){
colors.put(Color.rgba8888(Color.valueOf((String)objects[i*2])), (BlockPair)objects[i*2+1]);
int color = Color.rgba8888(Color.valueOf((String)objects[i*2]));
colors.put(color, (BlockPair)objects[i*2+1]);
pairs.add((BlockPair)objects[i*2+1]);
colorIDS[i] = color;
reverseIDs.put(color, i);
}
for(Entry<BlockPair> e : colors.entries()){
reverseColors.put(e.value.wall == Blocks.air ? e.value.floor : e.value.wall, e.key);