Implemented and tested custom maps for multiplayer
This commit is contained in:
@@ -2,6 +2,7 @@ package io.anuke.mindustry.core;
|
|||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap;
|
||||||
import com.badlogic.gdx.utils.IntSet;
|
import com.badlogic.gdx.utils.IntSet;
|
||||||
import com.badlogic.gdx.utils.TimeUtils;
|
import com.badlogic.gdx.utils.TimeUtils;
|
||||||
import io.anuke.mindustry.Mindustry;
|
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.Enemy;
|
||||||
import io.anuke.mindustry.entities.enemies.EnemyType;
|
import io.anuke.mindustry.entities.enemies.EnemyType;
|
||||||
import io.anuke.mindustry.graphics.Fx;
|
import io.anuke.mindustry.graphics.Fx;
|
||||||
import io.anuke.mindustry.net.NetworkIO;
|
|
||||||
import io.anuke.mindustry.net.Net;
|
import io.anuke.mindustry.net.Net;
|
||||||
import io.anuke.mindustry.net.Net.SendMode;
|
import io.anuke.mindustry.net.Net.SendMode;
|
||||||
|
import io.anuke.mindustry.net.NetworkIO;
|
||||||
import io.anuke.mindustry.net.Packets.*;
|
import io.anuke.mindustry.net.Packets.*;
|
||||||
import io.anuke.mindustry.resource.Recipe;
|
import io.anuke.mindustry.resource.Recipe;
|
||||||
import io.anuke.mindustry.resource.Recipes;
|
import io.anuke.mindustry.resource.Recipes;
|
||||||
@@ -93,17 +94,22 @@ public class NetClient extends Module {
|
|||||||
|
|
||||||
Net.handle(WorldData.class, data -> {
|
Net.handle(WorldData.class, data -> {
|
||||||
UCore.log("Recieved world data: " + data.stream.available() + " bytes.");
|
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);
|
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;
|
gotData = true;
|
||||||
|
|
||||||
Net.send(new ConnectConfirmPacket(), SendMode.tcp);
|
finishConnecting();
|
||||||
GameState.set(State.playing);
|
});
|
||||||
Net.setClientLoaded(true);
|
|
||||||
|
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 -> {
|
Net.handle(SyncPacket.class, packet -> {
|
||||||
@@ -131,7 +137,6 @@ public class NetClient extends Module {
|
|||||||
} else {
|
} else {
|
||||||
entity.read(data);
|
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(){
|
public void beginConnecting(){
|
||||||
connecting = true;
|
connecting = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,19 +62,34 @@ public class NetServer extends Module{
|
|||||||
player.interpolator.target.set(player.x, player.y);
|
player.interpolator.target.set(player.x, player.y);
|
||||||
connections.put(id, player);
|
connections.put(id, player);
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
if(Vars.world.getMap().custom){
|
||||||
NetworkIO.write(player.id, weapons.get(packet.name, new ByteArray()), stream);
|
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.");
|
UCore.log("Sending custom map: Packed " + stream.size() + " uncompressed bytes of MAP data.");
|
||||||
|
}else{
|
||||||
WorldData data = new WorldData();
|
//hack-- simulate the map ack packet recieved to send the world data to the client.
|
||||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
Net.handleServerReceived(id, new MapAckPacket());
|
||||||
|
}
|
||||||
Net.sendStream(id, data);
|
|
||||||
|
|
||||||
Mindustry.platforms.updateRPC();
|
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) -> {
|
Net.handleServer(ConnectConfirmPacket.class, (id, packet) -> {
|
||||||
Player player = connections.get(id);
|
Player player = connections.get(id);
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.anuke.ucore.graphics.Pixmaps;
|
|||||||
public class Maps implements Disposable{
|
public class Maps implements Disposable{
|
||||||
private IntMap<Map> maps = new IntMap<>();
|
private IntMap<Map> maps = new IntMap<>();
|
||||||
private ObjectMap<String, Map> mapNames = new ObjectMap<>();
|
private ObjectMap<String, Map> mapNames = new ObjectMap<>();
|
||||||
|
private Map networkMap;
|
||||||
private int lastID;
|
private int lastID;
|
||||||
private Json json = new Json();
|
private Json json = new Json();
|
||||||
|
|
||||||
@@ -30,7 +31,23 @@ public class Maps implements Disposable{
|
|||||||
return maps.values();
|
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){
|
public Map getMap(int id){
|
||||||
|
if(id == -1){
|
||||||
|
return networkMap;
|
||||||
|
}
|
||||||
return maps.get(id);
|
return maps.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,74 @@
|
|||||||
package io.anuke.mindustry.net;
|
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.ByteArray;
|
||||||
import com.badlogic.gdx.utils.TimeUtils;
|
import com.badlogic.gdx.utils.TimeUtils;
|
||||||
import io.anuke.mindustry.Vars;
|
import io.anuke.mindustry.Vars;
|
||||||
import io.anuke.mindustry.game.GameMode;
|
import io.anuke.mindustry.game.GameMode;
|
||||||
import io.anuke.mindustry.resource.Upgrade;
|
import io.anuke.mindustry.resource.Upgrade;
|
||||||
import io.anuke.mindustry.resource.Weapon;
|
import io.anuke.mindustry.resource.Weapon;
|
||||||
import io.anuke.mindustry.world.Block;
|
import io.anuke.mindustry.world.*;
|
||||||
import io.anuke.mindustry.world.Tile;
|
|
||||||
import io.anuke.mindustry.world.WorldGenerator;
|
|
||||||
import io.anuke.mindustry.world.blocks.Blocks;
|
import io.anuke.mindustry.world.blocks.Blocks;
|
||||||
import io.anuke.mindustry.world.blocks.types.BlockPart;
|
import io.anuke.mindustry.world.blocks.types.BlockPart;
|
||||||
import io.anuke.mindustry.world.blocks.types.Rock;
|
import io.anuke.mindustry.world.blocks.types.Rock;
|
||||||
|
import io.anuke.ucore.UCore;
|
||||||
import io.anuke.ucore.core.Timers;
|
import io.anuke.ucore.core.Timers;
|
||||||
import io.anuke.ucore.entities.Entities;
|
import io.anuke.ucore.entities.Entities;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class NetworkIO {
|
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)){
|
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||||
|
|
||||||
stream.writeFloat(Timers.time()); //timer time
|
stream.writeFloat(Timers.time()); //timer time
|
||||||
stream.writeLong(TimeUtils.millis()); //timestamp
|
stream.writeLong(TimeUtils.millis()); //timestamp
|
||||||
|
|
||||||
//--GENERAL STATE--
|
//--GENERAL STATE--
|
||||||
stream.writeByte(Vars.control.getMode().ordinal()); //gamemode
|
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.writeInt(Vars.control.getWave()); //wave
|
||||||
stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown
|
stream.writeFloat(Vars.control.getWaveCountdown()); //wave countdown
|
||||||
@@ -139,12 +179,8 @@ public class NetworkIO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void load(FileHandle file){
|
/**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/
|
||||||
load(file.read());
|
public static void loadWorld(InputStream is){
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void load(InputStream is){
|
|
||||||
|
|
||||||
try(DataInputStream stream = new DataInputStream(is)){
|
try(DataInputStream stream = new DataInputStream(is)){
|
||||||
float timerTime = stream.readFloat();
|
float timerTime = stream.readFloat();
|
||||||
|
|||||||
@@ -513,4 +513,16 @@ public class Packets {
|
|||||||
id = buffer.getInt();
|
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) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public class Registrator {
|
|||||||
GameOverPacket.class,
|
GameOverPacket.class,
|
||||||
FriendlyFireChangePacket.class,
|
FriendlyFireChangePacket.class,
|
||||||
PlayerDeathPacket.class,
|
PlayerDeathPacket.class,
|
||||||
|
CustomMapPacket.class,
|
||||||
|
MapAckPacket.class
|
||||||
};
|
};
|
||||||
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();
|
private static ObjectIntMap<Class<?>> ids = new ObjectIntMap<>();
|
||||||
|
|
||||||
|
|||||||
@@ -57,11 +57,7 @@ public class PausedDialog extends FloatingDialog{
|
|||||||
|
|
||||||
if(!Vars.gwt) {
|
if(!Vars.gwt) {
|
||||||
content().addButton("$text.hostserver", () -> {
|
content().addButton("$text.hostserver", () -> {
|
||||||
if (Vars.world.getMap().custom) {
|
ui.host.show();
|
||||||
ui.showError("$text.nohost");
|
|
||||||
} else {
|
|
||||||
ui.host.show();
|
|
||||||
}
|
|
||||||
}).disabled(b -> Net.active());
|
}).disabled(b -> Net.active());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +96,7 @@ public class PausedDialog extends FloatingDialog{
|
|||||||
lo.cell.disabled(b -> Net.active());
|
lo.cell.disabled(b -> Net.active());
|
||||||
|
|
||||||
imagebutton ho = new imagebutton("icon-host", isize, () -> {
|
imagebutton ho = new imagebutton("icon-host", isize, () -> {
|
||||||
if(Vars.world.getMap().custom){
|
ui.host.show();
|
||||||
ui.showError("$text.nohost");
|
|
||||||
}else {
|
|
||||||
ui.host.show();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
ho.text("$text.host").padTop(4f);
|
ho.text("$text.host").padTop(4f);
|
||||||
ho.cell.disabled(b -> Net.active());
|
ho.cell.disabled(b -> Net.active());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.anuke.mindustry.world;
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color;
|
import com.badlogic.gdx.graphics.Color;
|
||||||
import com.badlogic.gdx.utils.Array;
|
import com.badlogic.gdx.utils.Array;
|
||||||
|
import com.badlogic.gdx.utils.IntIntMap;
|
||||||
import com.badlogic.gdx.utils.IntMap;
|
import com.badlogic.gdx.utils.IntMap;
|
||||||
import com.badlogic.gdx.utils.IntMap.Entry;
|
import com.badlogic.gdx.utils.IntMap.Entry;
|
||||||
import com.badlogic.gdx.utils.ObjectIntMap;
|
import com.badlogic.gdx.utils.ObjectIntMap;
|
||||||
@@ -10,6 +11,11 @@ import io.anuke.mindustry.world.blocks.Blocks;
|
|||||||
import io.anuke.mindustry.world.blocks.SpecialBlocks;
|
import io.anuke.mindustry.world.blocks.SpecialBlocks;
|
||||||
|
|
||||||
public class ColorMapper{
|
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 ObjectIntMap<Block> reverseColors = new ObjectIntMap<>();
|
||||||
private static Array<BlockPair> pairs = new Array<>();
|
private static Array<BlockPair> pairs = new Array<>();
|
||||||
private static IntMap<BlockPair> colors = map(
|
private static IntMap<BlockPair> colors = map(
|
||||||
@@ -40,6 +46,14 @@ public class ColorMapper{
|
|||||||
public static BlockPair get(int color){
|
public static BlockPair get(int color){
|
||||||
return colors.get(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(){
|
public static IntMap<BlockPair> getColors(){
|
||||||
return colors;
|
return colors;
|
||||||
@@ -62,10 +76,14 @@ public class ColorMapper{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IntMap<BlockPair> map(Object...objects){
|
private static IntMap<BlockPair> map(Object...objects){
|
||||||
|
colorIDS = new int[objects.length/2];
|
||||||
IntMap<BlockPair> colors = new IntMap<>();
|
IntMap<BlockPair> colors = new IntMap<>();
|
||||||
for(int i = 0; i < objects.length/2; i ++){
|
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]);
|
pairs.add((BlockPair)objects[i*2+1]);
|
||||||
|
colorIDS[i] = color;
|
||||||
|
reverseIDs.put(color, i);
|
||||||
}
|
}
|
||||||
for(Entry<BlockPair> e : colors.entries()){
|
for(Entry<BlockPair> e : colors.entries()){
|
||||||
reverseColors.put(e.value.wall == Blocks.air ? e.value.floor : e.value.wall, e.key);
|
reverseColors.put(e.value.wall == Blocks.air ? e.value.floor : e.value.wall, e.key);
|
||||||
|
|||||||
Reference in New Issue
Block a user