This commit is contained in:
Anuken 2019-09-06 13:58:32 -04:00
commit ff54629550
77 changed files with 2674 additions and 2256 deletions

View file

@ -86,10 +86,9 @@ public class AssetsAnnotationProcessor extends AbstractProcessor{
//load.addStatement(name + " = io.anuke.arc.Core.audio."+loadMethod+"(io.anuke.arc.Core.files.internal(io.anuke.arc.Core.app.getType() != io.anuke.arc.Application.ApplicationType.iOS ? $S : $S))",
//filepath, filepath.replace(".ogg", ".mp3"));
String filename = "io.anuke.arc.Core.app.getType() != io.anuke.arc.Application.ApplicationType.iOS ? \"" + filepath + "\" : \"" + filepath.replace(".ogg", ".mp3")+"\"";
loadBegin.addStatement("io.anuke.arc.Core.assets.load(io.anuke.arc.Core.app.getType() != io.anuke.arc.Application.ApplicationType.iOS ? $S : $S, "+rtype+".class, " +
"new io.anuke.arc.assets.loaders."+classname.substring(0, classname.length()-1)+"Loader."+classname.substring(0, classname.length()-1)+"Parameter((m, name, type) -> " + name + " = m.get(\"" + filepath + "\")))",
filepath, filepath.replace(".ogg", ".mp3"));
loadBegin.addStatement("io.anuke.arc.Core.assets.load("+filename +", "+rtype+".class).loaded = a -> " + name + " = ("+rtype+")a", filepath, filepath.replace(".ogg", ".mp3"));
dispose.addStatement(name + ".dispose()");

View file

@ -179,7 +179,7 @@ project(":ios"){
}
props['app.id'] = 'io.anuke.mindustry'
props['app.version'] = '4.0'
props['app.version'] = '4.2.1'
props['app.mainclass'] = 'io.anuke.mindustry.IOSLauncher'
props['app.executable'] = 'IOSLauncher'
props['app.name'] = 'Mindustry'
@ -205,6 +205,7 @@ project(":core"){
apply plugin: "java"
task preGen{
outputs.upToDateWhen{ false }
generateLocales()
writeVersion()
}
@ -325,4 +326,4 @@ task deployAll{
dependsOn "desktop:packrMacOS"
dependsOn "server:deploy"
dependsOn "android:deploy"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -64,16 +64,18 @@ techtree = Tech Tree
research.list = [lightgray]Research:
research = Research
researched = [lightgray]{0} researched.
players = {0} players online
players.single = {0} player online
players = {0} players
players.single = {0} player
server.closing = [accent]Closing server...
server.kicked.kick = You have been kicked from the server!
server.kicked.whitelist = You are not whitelisted here.
server.kicked.serverClose = Server closed.
server.kicked.vote = You have been vote-kicked. Goodbye.
server.kicked.clientOutdated = Outdated client! Update your game!
server.kicked.serverOutdated = Outdated server! Ask the host to update!
server.kicked.banned = You are banned on this server.
server.kicked.typeMismatch = This server is not compatible with your build type.
server.kicked.playerLimit = This server is full. Wait for an empty slot.
server.kicked.recentKick = You have been kicked recently.\nWait before connecting again.
server.kicked.nameInUse = There is someone with that name\nalready on this server.
server.kicked.nameEmpty = Your chosen name is invalid.
@ -110,7 +112,7 @@ server.edit = Edit Server
server.outdated = [crimson]Outdated Server![]
server.outdated.client = [crimson]Outdated Client![]
server.version = [gray]v{0} {1}
server.custombuild = [yellow]Custom Build
server.custombuild = [accent]Custom Build
confirmban = Are you sure you want to ban this player?
confirmkick = Are you sure you want to kick this player?
confirmunban = Are you sure you want to unban this player?
@ -119,6 +121,9 @@ confirmunadmin = Are you sure you want to remove admin status from this player?
joingame.title = Join Game
joingame.ip = Address:
disconnect = Disconnected.
disconnect.error = Connection error.
disconnect.closed = Connection closed.
disconnect.timeout = Timed out.
disconnect.data = Failed to load world data!
connecting = [accent]Connecting...
connecting.data = [accent]Loading world data...
@ -152,7 +157,7 @@ off = Off
save.autosave = Autosave: {0}
save.map = Map: {0}
save.wave = Wave {0}
save.difficulty = Difficulty: {0}
save.mode = Gamemode: {0}
save.date = Last Saved: {0}
save.playtime = Playtime: {0}
warning = Warning.
@ -446,6 +451,7 @@ blocks.boosteffect = Boost Effect
blocks.maxunits = Max Active Units
blocks.health = Health
blocks.buildtime = Build Time
blocks.buildcost = Build Cost
blocks.inaccuracy = Inaccuracy
blocks.shots = Shots
blocks.reload = Shots/Second
@ -890,8 +896,8 @@ tutorial.intro = You have entered the[scarlet] Mindustry Tutorial.[]\nBegin by[a
tutorial.drill = Mining manually is inefficient.\n[accent]Drills []can mine automatically.\nClick the drill tab in the bottom right.\nSelect the[accent] mechanical drill[]. Place it on a copper vein by clicking.\n[accent]Right-click[] to stop building.
tutorial.drill.mobile = Mining manually is inefficient.\n[accent]Drills []can mine automatically.\nTap the drill tab in the bottom right.\nSelect the[accent] mechanical drill[].\nPlace it on a copper vein by tapping, then press the[accent] checkmark[] below to confirm your selection.\nPress the[accent] X button[] to cancel placement.
tutorial.blockinfo = Each block has different stats. Each drill can only mine certain ores.\nTo check a block's info and stats,[accent] tap the "?" button while selecting it in the build menu.[]\n\n[accent]Access the Mechanical Drill's stats now.[]
tutorial.conveyor = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent]Hold down the mouse to place in a line.[]\nHold[accent] CTRL[] while selecting a line to place diagonally.\n\n[accent]{0}/{1} conveyors placed in line\n[accent]0/1 items delivered
tutorial.conveyor.mobile = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent] Place in a line by holding down your finger for a few seconds[] and dragging in a direction.\n\n[accent]{0}/{1} conveyors placed in line\n[accent]0/1 items delivered
tutorial.conveyor = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent]Hold down the mouse to place in a line.[]\nHold[accent] CTRL[] while selecting a line to place diagonally.\n\n[accent]Place 2 conveyors with the line tool, then deliver an item into the core.
tutorial.conveyor.mobile = [accent]Conveyors[] are used to transport items to the core.\nMake a line of conveyors from the drill to the core.\n[accent] Place in a line by holding down your finger for a few seconds[] and dragging in a direction.\n\n[accent]Place 2 conveyors with the line tool, then deliver an item into the core.
tutorial.turret = Once an item enters your core, it can be used for building.\nKeep in mind that not all items can be used for building.\nItems that are not used for building, such as[accent] coal[] or[accent] scrap[], cannot be put into the core.\nDefensive structures must be built to repel the[lightgray] enemy[].\nBuild a[accent] duo turret[] near your base.
tutorial.drillturret = Duo turrets require[accent] copper ammo []to shoot.\nPlace a drill near the turret.\nLead conveyors into the turret to supply it with copper.\n\n[accent]Ammo delivered: 0/1
tutorial.pause = During battle, you are able to[accent] pause the game.[]\nYou may queue buildings while paused.\n\n[accent]Press space to pause.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 718 B

Before After
Before After

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 KiB

After

Width:  |  Height:  |  Size: 672 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 258 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 566 KiB

Before After
Before After

View file

@ -84,10 +84,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void resize(int width, int height){
super.resize(width, height);
if(assets == null) return;
if(!assets.isFinished()){
Draw.proj().setOrtho(0, 0, width, height);
}else{
super.resize(width, height);
}
}
@ -100,6 +102,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
for(ApplicationListener listener : modules){
listener.init();
}
super.resize(graphics.getWidth(), graphics.getHeight());
finished = true;
Events.fire(new ClientLoadEvent());
}

View file

@ -3,6 +3,7 @@ package io.anuke.mindustry;
import io.anuke.arc.Application.*;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*;
@ -43,6 +44,8 @@ public class Vars implements Loadable{
public static final String discordURL = "https://discord.gg/mindustry";
/** URL for sending crash reports to */
public static final String crashReportURL = "http://mins.us.to/report";
/** list of built-in servers.*/
public static final Array<String> defaultServers = Array.with(/*"mins.us.to"*/);
/** maximum distance between mine and core that supports automatic transferring */
public static final float mineTransferRange = 220f;
/** team of the player by default */

View file

@ -242,14 +242,13 @@ public class BlockIndexer{
int quadrantY = tile.y / quadrantSize;
itemSet.clear();
Tile rounded = world.tile(Mathf.clamp(quadrantX * quadrantSize + quadrantSize / 2, 0, world.width() - 1),
Mathf.clamp(quadrantY * quadrantSize + quadrantSize / 2, 0, world.height() - 1));
Tile rounded = world.tile(Mathf.clamp(quadrantX * quadrantSize + quadrantSize / 2, 0, world.width() - 1), Mathf.clamp(quadrantY * quadrantSize + quadrantSize / 2, 0, world.height() - 1));
//find all items that this quadrant contains
for(int x = quadrantX * quadrantSize; x < world.width() && x < (quadrantX + 1) * quadrantSize; x++){
for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
for(int x = Math.max(0, rounded.x - quadrantSize / 2); x < rounded.x + quadrantSize / 2 && x < world.width(); x++){
for(int y = Math.max(0, rounded.y - quadrantSize / 2); y < rounded.y + quadrantSize / 2 && y < world.height(); y++){
Tile result = world.tile(x, y);
if(result == null || result.drop() == null || !scanOres.contains(result.drop())) continue;
if(result == null || result.drop() == null || !scanOres.contains(result.drop()) || result.block() != Blocks.air) continue;
itemSet.add(result.drop());
}

View file

@ -500,6 +500,7 @@ public class Blocks implements ContentList{
consumes.items(new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10));
consumes.power(5f);
itemCapacity = 20;
int bottomRegion = reg("-bottom"), weaveRegion = reg("-weave");
@ -982,7 +983,6 @@ public class Blocks implements ContentList{
pulseConduit = new Conduit("pulse-conduit"){{
requirements(Category.liquid, ItemStack.with(Items.titanium, 1, Items.metaglass, 1));
liquidCapacity = 16f;
liquidFlowFactor = 4.9f;
health = 90;
}};
@ -1405,14 +1405,14 @@ public class Blocks implements ContentList{
}};
arc = new PowerTurret("arc"){{
requirements(Category.turret, ItemStack.with(Items.copper, 35, Items.lead, 35));
requirements(Category.turret, ItemStack.with(Items.copper, 35, Items.lead, 50));
shootType = Bullets.arc;
reload = 24f;
reload = 35f;
shootCone = 40f;
rotatespeed = 8f;
powerUse = 0.9f;
powerUse = 1.5f;
targetAir = false;
range = 95f;
range = 90f;
shootEffect = Fx.lightningShoot;
heatColor = Color.RED;
recoil = 1f;
@ -1775,4 +1775,4 @@ public class Blocks implements ContentList{
//endregion
}
}
}

View file

@ -7,12 +7,13 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.world;
import static io.anuke.mindustry.Vars.*;
public class Bullets implements ContentList{
public static BulletType
@ -631,7 +632,7 @@ public class Bullets implements ContentList{
@Override
public void init(Bullet b){
Lightning.create(b.getTeam(), Pal.lancerLaser, damage, b.x, b.y, b.rot(), 30);
Lightning.create(b.getTeam(), Pal.lancerLaser, damage * (b.getOwner() instanceof Player ? state.rules.playerDamageMultiplier : 1f), b.x, b.y, b.rot(), 30);
}
};

View file

@ -48,6 +48,10 @@ public class Control implements ApplicationListener, Loadable{
private boolean wasPaused = false;
public Control(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC);
@ -152,10 +156,6 @@ public class Control implements ApplicationListener, Loadable{
@Override
public void loadAsync(){
saves = new Saves();
tutorial = new Tutorial();
music = new MusicControl();
Draw.scl = 1f / Core.atlas.find("scale_marker").getWidth();
Core.input.setCatch(KeyCode.BACK, true);

View file

@ -107,7 +107,17 @@ public class NetClient implements ApplicationListener{
Time.runTask(3f, ui.loadfrag::hide);
ui.showError("$disconnect");
if(packet.reason != null){
if(packet.reason.equals("closed")){
ui.showSmall("$disconnect", "$disconnect.closed");
}else if(packet.reason.equals("timeout")){
ui.showSmall("$disconnect", "$disconnect.timeout");
}else if(packet.reason.equals("error")){
ui.showSmall("$disconnect", "$disconnect.error");
}
}else{
ui.showError("$disconnect");
}
});
Net.handleClient(WorldStream.class, data -> {

View file

@ -1,39 +1,32 @@
package io.anuke.mindustry.core;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.ApplicationListener;
import io.anuke.arc.Events;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.Colors;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Rectangle;
import io.anuke.arc.math.geom.Vector2;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.CommandHandler.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.Blocks;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest;
import io.anuke.mindustry.entities.traits.Entity;
import io.anuke.mindustry.entities.traits.SyncTrait;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.gen.RemoteReadServer;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Administration.PlayerInfo;
import io.anuke.mindustry.net.Administration.TraceInfo;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.zip.DeflaterOutputStream;
import java.nio.*;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
@ -76,7 +69,7 @@ public class NetServer implements ApplicationListener{
Net.handleServer(Disconnect.class, (id, packet) -> {
Player player = connections.get(id);
if(player != null){
onDisconnect(player);
onDisconnect(player, packet.reason);
}
connections.remove(id);
});
@ -111,6 +104,27 @@ public class NetServer implements ApplicationListener{
return;
}
if(admins.isIDBanned(uuid)){
kick(id, KickReason.banned);
return;
}
if(admins.getPlayerLimit() > 0 && playerGroup.size() >= admins.getPlayerLimit()){
kick(id, KickReason.playerLimit);
return;
}
if(!admins.isWhitelisted(packet.uuid, packet.usid)){
info.adminUsid = packet.usid;
info.lastName = packet.name;
info.id = packet.uuid;
admins.save();
Call.onInfoMessage(id, "You are not whitelisted here.");
Log.info("&lcDo &lywhitelist-add {0}&lc to whitelist the player &lb'{1}'", packet.uuid, packet.name);
kick(id, KickReason.whitelist);
return;
}
if(packet.versionType == null || ((packet.version == -1 || !packet.versionType.equals(Version.type)) && Version.build != -1 && !admins.allowsCustomClients())){
kick(id, !Version.type.equals(packet.versionType) ? KickReason.typeMismatch : KickReason.customClient);
return;
@ -184,6 +198,8 @@ public class NetServer implements ApplicationListener{
sendWorldData(player, id);
platform.updateRPC();
Events.fire(new PlayerJoin(player));
});
Net.handleServer(InvokePacket.class, (id, packet) -> {
@ -227,25 +243,34 @@ public class NetServer implements ApplicationListener{
});
//duration of a a kick in seconds
int kickDuration = 10 * 60;
int kickDuration = 15 * 60;
class VoteSession{
Player target;
ObjectSet<String> voted = new ObjectSet<>();
ObjectMap<Player, VoteSession> map;
VoteSession[] map;
Timer.Task task;
int votes;
public VoteSession(ObjectMap<Player, VoteSession> map, Player target){
public VoteSession(VoteSession[] map, Player target){
this.target = target;
this.map = map;
this.task = Timer.schedule(() -> {
if(!checkPass()){
Call.sendMessage(Strings.format("[lightgray]Vote failed. Not enough votes to kick[orange] {0}[lightgray].", target.name));
map[0] = null;
task.cancel();
}
map.remove(target);
task.cancel();
}, 60 * 1.5f);
}, 60 * 1);
}
void vote(Player player, int d){
votes += d;
voted.addAll(player.uuid, admins.getInfo(player.uuid).lastIP);
Call.sendMessage(Strings.format("[orange]{0}[lightgray] has voted to kick[orange] {1}[].[accent] ({2}/{3})\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
player.name, target.name, votes, votesRequired()));
//checkPass();
}
boolean checkPass(){
@ -253,6 +278,8 @@ public class NetServer implements ApplicationListener{
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] {0}[orange] will be kicked from the server.", target.name));
admins.getInfo(target.uuid).lastKicked = Time.millis() + kickDuration*1000;
kick(target.con.id, KickReason.vote);
map[0] = null;
task.cancel();
return true;
}
return false;
@ -260,10 +287,10 @@ public class NetServer implements ApplicationListener{
}
//cooldown between votes
int voteTime = 60 * 10;
int voteTime = 60 * 5;
Timekeeper vtime = new Timekeeper(voteTime);
//current kick sessions
ObjectMap<Player, VoteSession> currentlyKicking = new ObjectMap<>();
VoteSession[] currentlyKicking = {null};
clientCommands.<Player>register("votekick", "[player...]", "Vote to kick a player, with a cooldown.", (args, player) -> {
if(playerGroup.size() < 3){
@ -271,8 +298,8 @@ public class NetServer implements ApplicationListener{
return;
}
if(currentlyKicking.values().toArray().contains(v -> v.voted.contains(player.uuid) || v.voted.contains(admins.getInfo(player.uuid).lastIP))){
player.sendMessage("[scarlet]You've already voted. Sit down.");
if(player.isLocal){
player.sendMessage("[scarlet]Just kick them yourself if you're the host.");
return;
}
@ -295,24 +322,20 @@ public class NetServer implements ApplicationListener{
}
if(found != null){
if(player == found){
player.sendMessage("[scarlet]If you're interested in kicking yourself, just leave.");
}else if(found.isAdmin){
if(found.isAdmin){
player.sendMessage("[scarlet]Did you really expect to be able to kick an admin?");
}else if(found.isLocal){
player.sendMessage("[scarlet]Local players cannot be kicked.");
}else{
if(!currentlyKicking.containsKey(found) && !vtime.get()){
if(!vtime.get()){
player.sendMessage("[scarlet]You must wait " + voteTime/60 + " minutes between votekicks.");
return;
}
VoteSession session = currentlyKicking.getOr(found, () -> new VoteSession(currentlyKicking, found));
session.votes ++;
session.voted.addAll(player.uuid, admins.getInfo(player.uuid).lastIP);
Call.sendMessage(Strings.format("[orange]{0}[lightgray] has voted to kick[orange] {1}[].[accent] ({2}/{3})\n[lightgray]Type[orange] /votekick #{4}[] to agree.",
player.name, found.name, session.votes, votesRequired(), found.con.id));
session.checkPass();
vtime.reset();
VoteSession session = new VoteSession(currentlyKicking, found);
session.vote(player, 1);
vtime.reset();
currentlyKicking[0] = session;
}
}else{
player.sendMessage("[scarlet]No player[orange]'" + args[0] + "'[scarlet] found.");
@ -320,6 +343,31 @@ public class NetServer implements ApplicationListener{
}
});
clientCommands.<Player>register("vote", "<y/n>", "Vote to kick the current player.", (arg, player) -> {
if(currentlyKicking[0] == null){
player.sendMessage("[scarlet]Nobody is being voted on.");
}else{
if(currentlyKicking[0].voted.contains(player.uuid) || currentlyKicking[0].voted.contains(admins.getInfo(player.uuid).lastIP)){
player.sendMessage("[scarlet]You've already voted. Sit down.");
return;
}
if(currentlyKicking[0].target == player){
player.sendMessage("[scarlet]You can't vote on your own trial.");
return;
}
if(!arg[0].toLowerCase().equals("y") && !arg[0].toLowerCase().equals("n")){
player.sendMessage("[scarlet]Vote either 'y' (yes) or 'n' (no).");
return;
}
int sign = arg[0].toLowerCase().equals("y") ? 1 : -1;
currentlyKicking[0].vote(player, sign);
}
});
clientCommands.<Player>register("sync", "Re-synchronize world state.", (args, player) -> {
if(player.isLocal){
player.sendMessage("[scarlet]Re-synchronizing as the host is pointless.");
@ -331,7 +379,7 @@ public class NetServer implements ApplicationListener{
}
public int votesRequired(){
return playerGroup.size() * 2 / 3;
return 2 + (playerGroup.size() > 4 ? 1 : 0);
}
public Team assignTeam(Player current, Iterable<Player> players){
@ -361,7 +409,7 @@ public class NetServer implements ApplicationListener{
Log.debug("Packed {0} compressed bytes of world data.", stream.size());
}
public static void onDisconnect(Player player){
public static void onDisconnect(Player player, String reason){
//singleplayer multiplayer wierdness
if(player.con == null){
player.remove();
@ -369,12 +417,13 @@ public class NetServer implements ApplicationListener{
}
if(player.con.hasConnected){
Events.fire(new PlayerLeave(player));
Call.sendMessage("[accent]" + player.name + "[accent] has disconnected.");
Call.onPlayerDisconnect(player.id);
}
player.remove();
netServer.connections.remove(player.con.id);
Log.info("&lm[{1}] &lc{0} has disconnected.", player.name, player.uuid);
Log.info("&lm[{1}] &lc{0} has disconnected. &lg&fi({2})", player.name, player.uuid, reason);
}
private static float compound(float speed, float drag){
@ -700,7 +749,7 @@ public class NetServer implements ApplicationListener{
if(connection == null || !connection.isConnected() || !connections.containsKey(connection.id)){
//player disconnected, call d/c event
onDisconnect(player);
onDisconnect(player, "disappeared");
return;
}

View file

@ -360,6 +360,15 @@ public class UI implements ApplicationListener, Loadable{
}}.show();
}
public void showSmall(String titleText, String text){
new Dialog(titleText, "dialog"){{
cont.margin(10).add(text);
titleTable.row();
titleTable.addImage("whiteui").color(Pal.accent).height(3f).growX().pad(2f);
buttons.addButton("$ok", this::hide).size(90, 50).pad(4);
}}.show();
}
public void showConfirm(String title, String text, Runnable confirmed){
showConfirm(title, text, null, confirmed);
}

View file

@ -186,7 +186,6 @@ public class MapEditorDialog extends Dialog implements Disposable{
clearChildren();
margin(0);
shown(this::build);
update(() -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
@ -228,6 +227,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
platform.updateRPC();
if(!Core.settings.getBool("landscape")) platform.endForceLandscape();
});
shown(this::build);
}
@Override

View file

@ -1,7 +1,7 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.arc.math.geom.Position;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.world.Tile;
public interface SpawnerTrait extends TargetTrait, Position{
@ -9,6 +9,8 @@ public interface SpawnerTrait extends TargetTrait, Position{
void updateSpawning(Player unit);
boolean hasUnit(Unit unit);
@Override
default boolean isValid(){
return getTile().entity instanceof SpawnerTrait;

View file

@ -58,7 +58,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
//visual only.
if(Net.client()){
Tile tile = world.tile(unit.spawner);
if(tile != null && !Net.client()){
if(tile != null){
tile.block().unitRemoved(tile, unit);
}

View file

@ -125,8 +125,8 @@ public abstract class FlyingUnit extends BaseUnit{
if(!Net.client()){
updateRotation();
wobble();
}
wobble();
}
@Override

View file

@ -23,6 +23,7 @@ import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.InputHandler.PlaceDraw;
import io.anuke.mindustry.io.TypeIO;
import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.NetConnection;
import io.anuke.mindustry.type.*;
@ -37,6 +38,7 @@ import static io.anuke.mindustry.Vars.*;
public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public static final int timerSync = 2;
public static final int timerAbility = 3;
public static final int timerTransfer = 4;
private static final int timerShootLeft = 0;
private static final int timerShootRight = 1;
private static final float liftoffBoost = 0.2f;
@ -59,7 +61,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
public NetConnection con;
public boolean isLocal = false;
public Interval timer = new Interval(4);
public Interval timer = new Interval(6);
public TargetTrait target;
public TargetTrait moveTarget;
@ -800,6 +802,14 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
}
}
public PlayerInfo getInfo(){
if(uuid == null){
throw new IllegalArgumentException("Local players cannot be traced and do not have info.");
}else{
return netServer.admins.getInfo(uuid);
}
}
/** Resets all values of the player. */
public void reset(){
resetNoAdd();
@ -909,7 +919,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
buffer.writeInt(Color.rgba8888(color));
buffer.writeByte(mech.id);
buffer.writeInt(mining == null ? noSpawner : mining.pos());
buffer.writeInt(spawner == null ? noSpawner : spawner.getTile().pos());
buffer.writeInt(spawner == null || !spawner.hasUnit(this) ? noSpawner : spawner.getTile().pos());
buffer.writeShort((short)(baseRotation * 2));
writeBuilding(buffer);

View file

@ -5,6 +5,7 @@ import io.anuke.mindustry.entities.traits.BuilderTrait;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.type.Zone;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.entities.type.Player;
public class EventType{
@ -184,5 +185,22 @@ public class EventType{
public static class ResizeEvent{
}
public static class PlayerJoin{
public final Player player;
public PlayerJoin(Player player){
this.player = player;
}
}
public static class PlayerLeave{
public final Player player;
public PlayerLeave(Player player){
this.player = player;
}
}
}

View file

@ -69,6 +69,20 @@ public enum Gamemode{
this.validator = validator;
}
public static Gamemode bestFit(Rules rules){
if(rules.pvp){
return pvp;
}else if(rules.editor){
return editor;
}else if(rules.attackMode){
return attack;
}else if(rules.infiniteResources){
return sandbox;
}else{
return survival;
}
}
/** Applies this preset to this ruleset. */
public Rules apply(Rules in){
rules.accept(in);

View file

@ -14,7 +14,7 @@ import static io.anuke.mindustry.Vars.*;
/** Controls playback of multiple music tracks.*/
public class MusicControl{
private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.5f, musicWaveChance = 0.4f;
private static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.6f, musicWaveChance = 0.5f;
/** normal, ambient music, plays at any time */
public final Array<Music> ambientMusic = Array.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);

View file

@ -266,6 +266,10 @@ public class Saves{
return meta == null || meta.rules == null ? null : meta.rules.zone;
}
public Gamemode mode(){
return Gamemode.bestFit(meta.rules);
}
public int getBuild(){
return meta.build;
}

View file

@ -115,9 +115,7 @@ public class Tutorial{
outline("blockinfo");
}
},
conveyor(
line -> Strings.format(line, Math.min(placed(Blocks.conveyor), 2), 2),
() -> placed(Blocks.conveyor, 2) && event("lineconfirm") && event("coreitem")){
conveyor(() -> placed(Blocks.conveyor, 2) && event("lineconfirm") && event("coreitem")){
void draw(){
outline("category-distribution");
outline("block-conveyor");
@ -190,13 +188,12 @@ public class Tutorial{
protected final String line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
protected final Function<String, String> text;
protected final Array<String> sentences;
protected Array<String> sentences;
protected final BooleanProvider done;
TutorialStage(Function<String, String> text, BooleanProvider done){
this.text = text;
this.done = done;
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
TutorialStage(BooleanProvider done){
@ -205,6 +202,7 @@ public class Tutorial{
/** displayed tutorial stage text.*/
public String text(){
if(sentences == null) this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
String line = sentences.get(control.tutorial.sentence);
return line.contains("{") ? text.get(line) : line;
}

View file

@ -59,6 +59,7 @@ public abstract class InputHandler implements InputProcessor{
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Tile tile){
if(!player.timer.get(Player.timerTransfer, 40)) return;
if(Net.server() && (player.item().amount <= 0 || player.isTransferring)){
throw new ValidateException(player, "Player cannot transfer an item.");
}
@ -288,7 +289,7 @@ public abstract class InputHandler implements InputProcessor{
}
public void tryDropItems(Tile tile, float x, float y){
if(!droppingItem || player.item().amount <= 0 || canTapPlayer(x, y) || state.isPaused()){
if(!droppingItem || player.item().amount <= 0 || canTapPlayer(x, y) || state.isPaused() || !player.timer.check(Player.timerTransfer, 40)){
droppingItem = false;
return;
}

View file

@ -184,7 +184,7 @@ public class Maps{
FileHandle dest = findFile();
file.copyTo(dest);
loadMap(dest, true);
createNewPreview(loadMap(dest, true));
}
/** Attempts to run the following code;
@ -356,7 +356,7 @@ public class Maps{
//if it's here, then the preview failed to load or doesn't exist, make it
//this has to be done synchronously!
Pixmap pix = MapIO.generatePreview(map);
Core.app.post(() -> map.texture = new Texture(pix));
map.texture = new Texture(pix);
executor.submit(() -> {
try{
map.previewFile().writePNG(pix);
@ -404,7 +404,7 @@ public class Maps{
return customMapDirectory.child("map_" + i + "." + mapExtension);
}
private void loadMap(FileHandle file, boolean custom) throws IOException{
private Map loadMap(FileHandle file, boolean custom) throws IOException{
Map map = MapIO.createMap(file, custom);
if(map.name() == null){
@ -413,6 +413,7 @@ public class Maps{
maps.add(map);
maps.sort();
return map;
}
private void loadCustomMaps(){

View file

@ -1,7 +1,7 @@
package io.anuke.mindustry.net;
import io.anuke.annotations.Annotations.Serialize;
import io.anuke.arc.Core;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import static io.anuke.mindustry.Vars.headless;
@ -10,6 +10,7 @@ public class Administration{
/** All player info. Maps UUIDs to info. This persists throughout restarts. */
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
private Array<String> bannedIPs = new Array<>();
private Array<String> whitelist = new Array<>();
public Administration(){
Core.settings.defaults(
@ -20,9 +21,16 @@ public class Administration{
load();
}
public int getPlayerLimit(){
return Core.settings.getInt("playerlimit", 0);
}
public void setPlayerLimit(int limit){
Core.settings.putSave("playerlimit", limit);
}
public void setStrict(boolean on){
Core.settings.put("strict", on);
Core.settings.save();
Core.settings.putSave("strict", on);
}
public boolean getStrict(){
@ -186,6 +194,36 @@ public class Administration{
return true;
}
public boolean isWhitelistEnabled(){
return Core.settings.getBool("whitelist", false);
}
public void setWhitelist(boolean enabled){
Core.settings.putSave("whitelist", enabled);
}
public boolean isWhitelisted(String id, String usid){
return !isWhitelistEnabled() || whitelist.contains(usid + id);
}
public boolean whitelist(String id){
PlayerInfo info = getCreateInfo(id);
if(whitelist.contains(info.adminUsid + id)) return false;
whitelist.add(info.adminUsid + id);
save();
return true;
}
public boolean unwhitelist(String id){
PlayerInfo info = getCreateInfo(id);
if(whitelist.contains(info.adminUsid + id)){
whitelist.remove(info.adminUsid + id);
save();
return true;
}
return false;
}
public boolean isIPBanned(String ip){
return bannedIPs.contains(ip, false) || (findByIP(ip) != null && findByIP(ip).banned);
}
@ -242,6 +280,10 @@ public class Administration{
return null;
}
public Array<PlayerInfo> getWhitelisted(){
return playerInfo.values().toArray().select(p -> isWhitelisted(p.id, p.adminUsid));
}
private PlayerInfo getCreateInfo(String id){
if(playerInfo.containsKey(id)){
return playerInfo.get(id);
@ -256,6 +298,7 @@ public class Administration{
public void save(){
Core.settings.putObject("player-info", playerInfo);
Core.settings.putObject("banned-ips", bannedIPs);
Core.settings.putObject("whitelisted", whitelist);
Core.settings.save();
}
@ -263,6 +306,7 @@ public class Administration{
private void load(){
playerInfo = Core.settings.getObject("player-info", ObjectMap.class, ObjectMap::new);
bannedIPs = Core.settings.getObject("banned-ips", Array.class, Array::new);
whitelist = Core.settings.getObject("whitelisted", Array.class, Array::new);
}
@Serialize

View file

@ -1,15 +1,18 @@
package io.anuke.mindustry.net;
import io.anuke.mindustry.game.*;
public class Host{
public final String name;
public final String address;
public final String mapname;
public final int wave;
public final int players;
public final int players, playerLimit;
public final int version;
public final String versionType;
public final Gamemode mode;
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType){
public Host(String name, String address, String mapname, int wave, int players, int version, String versionType, Gamemode mode, int playerLimit){
this.name = name;
this.address = address;
this.players = players;
@ -17,5 +20,7 @@ public class Host{
this.wave = wave;
this.version = version;
this.versionType = versionType;
this.playerLimit = playerLimit;
this.mode = mode;
}
}

View file

@ -1,17 +1,15 @@
package io.anuke.mindustry.net;
import io.anuke.arc.Core;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.entities.Entities;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.arc.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.JsonIO;
import io.anuke.mindustry.io.SaveIO;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.Map;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.nio.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
@ -71,8 +69,9 @@ public class NetworkIO{
buffer.putInt(state.wave);
buffer.putInt(Version.build);
writeString(buffer, Version.type);
//TODO additional information:
// - gamemode ID/name (just pick the closest one?)
buffer.put((byte)Gamemode.bestFit(state.rules).ordinal());
buffer.putInt(netServer.admins.getPlayerLimit());
return buffer;
}
@ -83,13 +82,15 @@ public class NetworkIO{
int wave = buffer.getInt();
int version = buffer.getInt();
String vertype = readString(buffer);
Gamemode gamemode = Gamemode.all[buffer.get()];
int limit = buffer.getInt();
return new Host(host, hostAddress, map, wave, players, version, vertype);
return new Host(host, hostAddress, map, wave, players, version, vertype, gamemode, limit);
}
private static void writeString(ByteBuffer buffer, String string, int maxlen){
byte[] bytes = string.getBytes(charset);
//truncating this way may lead to wierd encoding errors at the ends of strings...
//todo truncating this way may lead to wierd encoding errors at the ends of strings...
if(bytes.length > maxlen){
bytes = Arrays.copyOfRange(bytes, 0, maxlen);
}

View file

@ -14,7 +14,7 @@ public class Packets{
public enum KickReason{
kick, clientOutdated, serverOutdated, banned, gameover(true), recentKick,
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch;
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch, whitelist, playerLimit;
public final boolean quiet;
@ -52,6 +52,7 @@ public class Packets{
public static class Disconnect implements Packet{
public int id;
public String reason;
@Override
public boolean isImportant(){

View file

@ -1,12 +1,11 @@
package io.anuke.mindustry.plugin;
import io.anuke.arc.collection.Array;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.io.*;
import java.lang.reflect.*;
import java.net.*;
import static io.anuke.mindustry.Vars.pluginDirectory;
@ -50,12 +49,9 @@ public class Plugins{
PluginMeta meta = JsonIO.read(PluginMeta.class, metaf.readString());
URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, jar.file().toURI().toURL());
Class<?> main = Class.forName(meta.main);
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, ClassLoader.getSystemClassLoader());
Class<?> main = classLoader.loadClass(meta.main);
return new LoadedPlugin(jar, zip, (Plugin)main.newInstance(), meta);
}

View file

@ -42,7 +42,7 @@ public class Zone extends UnlockableContent{
@Override
public void load(){
preview = Core.atlas.find(name);
preview = Core.atlas.find("zone-" + name);
}
public Rules getRules(){

View file

@ -13,11 +13,15 @@ public class ItemDisplay extends Table{
this(item, 0);
}
public ItemDisplay(Item item, int amount){
add(new ItemImage(new ItemStack(item, amount))).size(8 * 4);
add(item.localizedName()).padLeft(4);
public ItemDisplay(Item item, int amount, boolean showName){
add(new ItemImage(new ItemStack(item, amount))).size(8 * 4).padRight(amount > 99 ? 12 : 0);
if(showName) add(item.localizedName()).padLeft(4 + amount > 99 ? 4 : 0);
this.item = item;
this.amount = amount;
}
public ItemDisplay(Item item, int amount){
this(item, amount, true);
}
}

View file

@ -27,7 +27,7 @@ public class LiquidDisplay extends Table{
t.add(Strings.autoFixed(amount, 1));
add(t);
}
}}).size(8 * 4).padRight(3);
}}).size(8 * 4).padRight(3 + (amount != 0 && Strings.autoFixed(amount, 1).length() > 2 ? 8 : 0));
if(perSecond){
add(StatUnit.perSecond.localized()).padLeft(2).padRight(5).color(Color.LIGHT_GRAY);

View file

@ -182,10 +182,10 @@ public class JoinDialog extends FloatingDialog{
void setupServer(Server server, Host host){
server.lastHost = host;
server.content.clear();
server.content.table(t -> setupHostTable(t, host)).expand().left().bottom().padLeft(12f).padBottom(8);
buildServer(host, server.content);
}
void setupHostTable(Table content, Host host){
void buildServer(Host host, Table content){
String versionString;
if(host.version == -1){
@ -202,12 +202,14 @@ public class JoinDialog extends FloatingDialog{
versionString = Core.bundle.format("server.version", host.version, host.versionType);
}
content.add("[lightgray]" + host.name + " " + versionString).width(targetWidth() - 10f).left().get().setEllipsis(true);
content.row();
content.add("[lightgray]" + (host.players != 1 ? Core.bundle.format("players", host.players == 0 ? host.players : "[accent]" + host.players + "[lightgray]") : Core.bundle.format("players.single", "[accent]" + host.players + "[lightgray]"))).left();
content.row();
content.add("[lightgray]" + Core.bundle.format("save.map", host.mapname) + "[lightgray] / " + Core.bundle.format("save.wave", host.wave)).width(targetWidth() - 10f).left().get().setEllipsis(true);
content.table(t -> {
t.add("[lightgray]" + host.name + " " + versionString).width(targetWidth() - 10f).left().get().setEllipsis(true);
t.row();
t.add("[lightgray]" + (Core.bundle.format("players" + (host.players == 1 ? ".single" : ""), (host.players == 0 ? "[lightgray]" : "[accent]") + host.players + (host.playerLimit > 0 ? "[lightgray]/[accent]" + host.playerLimit : "")+ "[lightgray]"))).left();
t.row();
t.add("[lightgray]" + Core.bundle.format("save.map", host.mapname) + "[lightgray] / " + host.mode.toString()).width(targetWidth() - 10f).left().get().setEllipsis(true);
}).expand().left().bottom().padLeft(12f).padBottom(8);
}
void setup(){
@ -275,6 +277,9 @@ public class JoinDialog extends FloatingDialog{
local.background((Drawable)null);
local.table("button", t -> t.label(() -> "[accent]" + Core.bundle.get("hosts.discovering.any") + Strings.animated(Time.time(), 4, 10f, ".")).pad(10f)).growX();
Net.discoverServers(this::addLocalHost, this::finishLocalHosts);
for(String host : defaultServers){
Net.pingHost(host, port, this::addLocalHost, e -> {});
}
}
void finishLocalHosts(){
@ -299,11 +304,10 @@ public class JoinDialog extends FloatingDialog{
local.row();
local.addButton(b -> {
b.margin(5f);
b.left();
setupHostTable(b, host);
}, "clear", () -> connect(host.address, port)).width(w).pad(4f).get();
TextButton button = local.addButton("", "clear", () -> connect(host.address, port))
.width(w).pad(5f).get();
button.clearChildren();
buildServer(host, button);
}
void connect(String ip, int port){

View file

@ -137,13 +137,13 @@ public class LoadDialog extends FloatingDialog{
meta.row();
meta.labelWrap(Core.bundle.format("save.map", color + (slot.getMap() == null ? Core.bundle.get("unknown") : slot.getMap().name())));
meta.row();
meta.labelWrap(Core.bundle.format("save.wave", color + slot.getWave()));
meta.labelWrap(slot.mode().toString() + " /" + color + " " + Core.bundle.format("save.wave", color + slot.getWave()));
meta.row();
meta.labelWrap(() -> Core.bundle.format("save.autosave", color + Core.bundle.get(slot.isAutosave() ? "on" : "off")));
meta.row();
meta.labelWrap(() -> Core.bundle.format("save.playtime", color + slot.getPlayTime()));
meta.row();
meta.labelWrap(Core.bundle.format("save.date", color + slot.getDate()));
meta.labelWrap(color + slot.getDate());
meta.row();
}).left().growX().width(250f);

View file

@ -62,49 +62,52 @@ public class MapsDialog extends FloatingDialog{
if(!ios){
buttons.addImageTextButton("$editor.importmap", "icon-load", iconsize, () -> {
platform.showFileChooser("$editor.importmap", "Map File", file -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showError("$editor.errorimage");
return;
}
ui.loadAnd(() -> {
maps.tryCatchMapError(() -> {
if(MapIO.isImage(file)){
ui.showError("$editor.errorimage");
return;
}
Map map;
if(file.extension().equalsIgnoreCase(mapExtension)){
map = MapIO.createMap(file, true);
}else{
map = maps.makeLegacyMap(file);
}
Map map;
if(file.extension().equalsIgnoreCase(mapExtension)){
map = MapIO.createMap(file, true);
}else{
map = maps.makeLegacyMap(file);
}
//when you attempt to import a save, it will have no name, so generate one
String name = map.tags.getOr("name", () -> {
String result = "unknown";
int number = 0;
while(maps.byName(result + number++) != null) ;
return result + number;
});
//this will never actually get called, but it remains just in case
if(name == null){
ui.showError("$editor.errorname");
return;
}
Map conflict = maps.all().find(m -> m.name().equals(name));
if(conflict != null && !conflict.custom){
ui.showInfo(Core.bundle.format("editor.import.exists", name));
}else if(conflict != null){
ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> {
maps.tryCatchMapError(() -> {
maps.importMap(file);
setup();
});
//when you attempt to import a save, it will have no name, so generate one
String name = map.tags.getOr("name", () -> {
String result = "unknown";
int number = 0;
while(maps.byName(result + number++) != null);
return result + number;
});
}else{
maps.importMap(map.file);
setup();
}
//this will never actually get called, but it remains just in case
if(name == null){
ui.showError("$editor.errorname");
return;
}
Map conflict = maps.all().find(m -> m.name().equals(name));
if(conflict != null && !conflict.custom){
ui.showInfo(Core.bundle.format("editor.import.exists", name));
}else if(conflict != null){
ui.showConfirm("$confirm", "$editor.overwrite.confirm", () -> {
maps.tryCatchMapError(() -> {
maps.removeMap(conflict);
maps.importMap(map.file);
setup();
});
});
}else{
maps.importMap(map.file);
setup();
}
});
});
}, true, FileChooser.anyMapFiles);
}).size(210f, 64f);

View file

@ -5,6 +5,7 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.input.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.layout.*;
import static io.anuke.mindustry.Vars.renderer;
@ -40,7 +41,7 @@ public class MinimapDialog extends FloatingDialog{
renderer.minimap.drawEntities(x, y, width, height);
}
}).grow();
}).size(Math.min(Core.graphics.getWidth() / 1.1f, Core.graphics.getHeight() / 1.3f)).padTop(-20f);
}).size(Math.min(Core.graphics.getWidth() / 1.1f, Core.graphics.getHeight() / 1.3f) / UnitScl.dp.scl(1f)).padTop(-20f);
cont.addListener(new InputListener(){
@Override

View file

@ -139,7 +139,7 @@ public class SettingsMenuDialog extends SettingsDialog{
t.row();
//iOS doesn't have a file chooser.
if(!ios){
//if(!ios){
t.addButton("$data.import", style, () -> ui.showConfirm("$confirm", "$data.import.confirm", () -> platform.showFileChooser("$data.import", "Zip Files", file -> {
try{
data.importData(file);
@ -151,7 +151,7 @@ public class SettingsMenuDialog extends SettingsDialog{
ui.showError(Strings.parseException(e, true));
}
}, true, f -> f.equalsIgnoreCase("zip"))));
}
//}
});
ScrollPane pane = new ScrollPane(prefs);

View file

@ -40,7 +40,7 @@ public class BlockInventoryFragment extends Fragment{
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Tile tile, Item item, int amount){
if(player == null || tile == null) return;
if(player == null || tile == null || !player.timer.get(Player.timerTransfer, 20)) return;
int removed = tile.block().removeStack(tile, item, amount);
@ -71,6 +71,8 @@ public class BlockInventoryFragment extends Fragment{
}
public void hide(){
if(table == null) return;
table.actions(Actions.scaleTo(0f, 1f, 0.06f, Interpolation.pow3Out), Actions.run(() -> {
table.clearChildren();
table.clearListeners();

View file

@ -19,7 +19,6 @@ import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.type.Unit;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
@ -32,6 +31,7 @@ import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.power.*;
import io.anuke.mindustry.world.consumers.*;
import io.anuke.mindustry.world.meta.*;
import io.anuke.mindustry.world.meta.values.*;
import java.util.*;
@ -251,7 +251,7 @@ public class Block extends BlockStorage{
public void drawPlace(int x, int y, int rotation, boolean valid){
}
protected float drawPlaceText(String text, int x, int y, boolean valid){
public float drawPlaceText(String text, int x, int y, boolean valid){
if(renderer.pixelator.enabled()) return 0;
Color color = valid ? Pal.accent : Pal.remove;
@ -476,7 +476,10 @@ public class Block extends BlockStorage{
public void setStats(){
stats.add(BlockStat.size, "{0}x{0}", size);
stats.add(BlockStat.health, health, StatUnit.none);
stats.add(BlockStat.buildTime, buildCost / 60, StatUnit.seconds);
if(isBuildable()){
stats.add(BlockStat.buildTime, buildCost / 60, StatUnit.seconds);
stats.add(BlockStat.buildCost, new ItemListValue(false, buildRequirements));
}
consumes.display(stats);
@ -581,7 +584,7 @@ public class Block extends BlockStorage{
});
}
Damage.dynamicExplosion(x, y, flammability, explosiveness, power, tilesize * size / 2f, Pal.darkFlame);
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * size / 2f, Pal.darkFlame);
if(!tile.floor().solid && !tile.floor().isLiquid){
RubbleDecal.create(tile.drawx(), tile.drawy(), size);
}

View file

@ -28,7 +28,6 @@ public abstract class BlockStorage extends UnlockableContent{
public int itemCapacity = 10;
public float liquidCapacity = 10f;
public float liquidFlowFactor = 4.9f;
public final BlockStats stats = new BlockStats();
public final BlockBars bars = new BlockBars();

View file

@ -0,0 +1,71 @@
package io.anuke.mindustry.world.blocks;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
public class RespawnBlock{
public static void drawRespawn(Tile tile, float heat, float progress, float time, Player player, Mech to){
progress = Mathf.clamp(progress);
Draw.color(Pal.darkMetal);
Lines.stroke(2f * heat);
Fill.poly(tile.drawx(), tile.drawy(), 4, 10f * heat);
Draw.reset();
if(player != null){
TextureRegion region = to.iconRegion;
Draw.color(0f, 0f, 0f, 0.4f * progress);
Draw.rect("circle-shadow", tile.drawx(), tile.drawy(), region.getWidth() / 3f, region.getWidth() / 3f);
Draw.color();
Shaders.build.region = region;
Shaders.build.progress = progress;
Shaders.build.color.set(Pal.accent);
Shaders.build.time = -time / 10f;
Draw.shader(Shaders.build, true);
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.shader();
Draw.color(Pal.accentBack);
float pos = Mathf.sin(time, 6f, 8f);
Lines.lineAngleCenter(tile.drawx() + pos, tile.drawy(), 90, 16f - Math.abs(pos) * 2f);
Draw.reset();
}
Lines.stroke(2f * heat);
Draw.color(Pal.accentBack);
Lines.poly(tile.drawx(), tile.drawy(), 4, 8f * heat);
float oy = -7f, len = 6f * heat;
Lines.stroke(5f);
Draw.color(Pal.darkMetal);
Lines.line(tile.drawx() - len, tile.drawy() + oy, tile.drawx() + len, tile.drawy() + oy, CapStyle.none);
for(int i : Mathf.signs){
Fill.tri(tile.drawx() + len * i, tile.drawy() + oy - Lines.getStroke()/2f, tile.drawx() + len * i, tile.drawy() + oy + Lines.getStroke()/2f, tile.drawx() + (len + Lines.getStroke() * heat) * i, tile.drawy() + oy);
}
Lines.stroke(3f);
Draw.color(Pal.accent);
Lines.line(tile.drawx() - len, tile.drawy() + oy, tile.drawx() - len + len*2 * progress, tile.drawy() + oy, CapStyle.none);
for(int i : Mathf.signs){
Fill.tri(tile.drawx() + len * i, tile.drawy() + oy - Lines.getStroke()/2f, tile.drawx() + len * i, tile.drawy() + oy + Lines.getStroke()/2f, tile.drawx() + (len + Lines.getStroke() * heat) * i, tile.drawy() + oy);
}
Draw.reset();
if(Net.active() && player != null){
tile.block().drawPlaceText(player.name, tile.x, tile.y - (Math.max((tile.block().size-1)/2, 0)), true);
}
}
}

View file

@ -44,7 +44,7 @@ public class CooledTurret extends Turret{
TurretEntity entity = tile.entity();
Liquid liquid = entity.liquids.current();
float used = Math.min(Math.min(entity.liquids.get(liquid), maxUsed * Time.delta()), Math.max(0, ((reload - entity.reload) / coolantMultiplier) / liquid.heatCapacity));
float used = Math.min(Math.min(entity.liquids.get(liquid), maxUsed * Time.delta()), Math.max(0, ((reload - entity.reload) / coolantMultiplier) / liquid.heatCapacity)) * baseReloadSpeed(tile);
entity.reload += (used * liquid.heatCapacity) / liquid.heatCapacity;
entity.liquids.remove(liquid, used);

View file

@ -201,7 +201,7 @@ public abstract class Turret extends Block{
protected void turnToTarget(Tile tile, float targetRot){
TurretEntity entity = tile.entity();
entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * entity.delta());
entity.rotation = Angles.moveToward(entity.rotation, targetRot, rotatespeed * entity.delta() * baseReloadSpeed(tile));
}
public boolean shouldTurn(Tile tile){

View file

@ -37,7 +37,7 @@ public class ItemLiquidGenerator extends PowerGenerator{
protected Effects.Effect explodeEffect = Fx.generatespark;
protected Color heatColor = Color.valueOf("ff9b59");
protected TextureRegion topRegion, liquidRegion;
protected boolean randomlyExplode = false;
protected boolean randomlyExplode = true;
public ItemLiquidGenerator(boolean hasItems, boolean hasLiquids, String name){
super(name);
@ -127,8 +127,10 @@ public class ItemLiquidGenerator extends PowerGenerator{
if(randomlyExplode && Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.5f))){
//this block is run last so that in the event of a block destruction, no code relies on the block type
entity.damage(Mathf.random(11f));
Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f));
Core.app.post(() -> {
entity.damage(Mathf.random(11f));
Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f));
});
}
}else{
entity.productionEfficiency = 0.0f;

View file

@ -22,7 +22,6 @@ public class Pump extends LiquidBlock{
public Pump(String name){
super(name);
layer = Layer.overlay;
liquidFlowFactor = 3f;
group = BlockGroup.liquids;
floating = true;
}

View file

@ -3,9 +3,7 @@ package io.anuke.mindustry.world.blocks.storage;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
@ -16,6 +14,7 @@ import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
@ -32,6 +31,7 @@ public class CoreBlock extends StorageBlock{
flags = EnumSet.of(BlockFlag.target, BlockFlag.producer);
activeSound = Sounds.respawning;
activeSoundVolume = 1f;
layer = Layer.overlay;
}
@Remote(called = Loc.server)
@ -84,41 +84,11 @@ public class CoreBlock extends StorageBlock{
}
@Override
public void draw(Tile tile){
public void drawLayer(Tile tile){
CoreEntity entity = tile.entity();
Draw.rect(region, tile.drawx(), tile.drawy());
if(entity.heat > 0){
Draw.color(Pal.darkMetal);
Lines.stroke(2f * entity.heat);
Lines.poly(tile.drawx(), tile.drawy(), 4, 8f * entity.heat);
Draw.reset();
}
if(entity.spawnPlayer != null){
Unit player = entity.spawnPlayer;
TextureRegion region = player.getIconRegion();
Shaders.build.region = region;
Shaders.build.progress = entity.progress;
Shaders.build.color.set(Pal.accent);
Shaders.build.time = -entity.time / 10f;
Draw.shader(Shaders.build, true);
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.shader();
Draw.color(Pal.accent);
Lines.lineAngleCenter(
tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size),
tile.drawy(),
90,
size * Vars.tilesize / 2f);
Draw.reset();
if(entity.heat > 0.001f){
RespawnBlock.drawRespawn(tile, entity.heat, entity.progress, entity.time, entity.spawnPlayer, mech);
}
}
@ -173,6 +143,11 @@ public class CoreBlock extends StorageBlock{
float time;
float heat;
@Override
public boolean hasUnit(Unit unit){
return unit == spawnPlayer;
}
@Override
public void updateSpawning(Player player){
if(!netServer.isWaitingForPlayers() && spawnPlayer == null){

View file

@ -1,32 +1,24 @@
package io.anuke.mindustry.world.blocks.units;
import io.anuke.annotations.Annotations.Loc;
import io.anuke.annotations.Annotations.Remote;
import io.anuke.arc.Core;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.math.geom.Geometry;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.content.Fx;
import io.anuke.mindustry.content.Mechs;
import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.traits.SpawnerTrait;
import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.entities.type.TileEntity;
import io.anuke.mindustry.gen.Call;
import io.anuke.mindustry.graphics.Pal;
import io.anuke.mindustry.graphics.Shaders;
import io.anuke.mindustry.type.Mech;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.meta.BlockStat;
import io.anuke.mindustry.world.meta.StatUnit;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.meta.*;
import java.io.*;
import static io.anuke.mindustry.Vars.mobile;
import static io.anuke.mindustry.Vars.tilesize;
import static io.anuke.mindustry.Vars.*;
public class MechPad extends Block{
protected Mech mech;
@ -37,6 +29,7 @@ public class MechPad extends Block{
update = true;
solid = false;
hasPower = true;
layer = Layer.overlay;
}
@Override
@ -110,32 +103,11 @@ public class MechPad extends Block{
}
@Override
public void draw(Tile tile){
public void drawLayer(Tile tile){
MechFactoryEntity entity = tile.entity();
Draw.rect(Core.atlas.find(name), tile.drawx(), tile.drawy());
if(entity.player != null){
TextureRegion region = (!entity.sameMech && entity.player.mech == mech ? Mechs.starter.iconRegion : mech.iconRegion);
Shaders.build.region = region;
Shaders.build.progress = entity.progress;
Shaders.build.time = -entity.time / 5f;
Shaders.build.color.set(Pal.accent);
Draw.shader(Shaders.build);
Draw.rect(region, tile.drawx(), tile.drawy());
Draw.shader();
Draw.color(Pal.accent);
Lines.lineAngleCenter(
tile.drawx() + Mathf.sin(entity.time, 6f, Vars.tilesize / 3f * size),
tile.drawy(),
90,
size * Vars.tilesize / 2f + 1f);
Draw.reset();
RespawnBlock.drawRespawn(tile, entity.heat, entity.progress, entity.time, entity.player, (!entity.sameMech && entity.player.mech == mech ? Mechs.starter : mech));
}
}
@ -170,6 +142,11 @@ public class MechPad extends Block{
float time;
float heat;
@Override
public boolean hasUnit(Unit unit){
return unit == player;
}
@Override
public void updateSpawning(Player unit){
if(player == null){

View file

@ -9,6 +9,7 @@ public enum BlockStat{
health(StatCategory.general),
size(StatCategory.general),
buildTime(StatCategory.general),
buildCost(StatCategory.general),
itemCapacity(StatCategory.items),
itemsMoved(StatCategory.items),

View file

@ -7,15 +7,21 @@ import io.anuke.mindustry.world.meta.StatValue;
public class ItemListValue implements StatValue{
private final ItemStack[] stacks;
private final boolean displayName;
public ItemListValue(ItemStack... stacks){
this(true, stacks);
}
public ItemListValue(boolean displayName, ItemStack... stacks){
this.stacks = stacks;
this.displayName = displayName;
}
@Override
public void display(Table table){
for(ItemStack stack : stacks){
table.add(new ItemDisplay(stack.item, stack.amount)).padRight(5);
table.add(new ItemDisplay(stack.item, stack.amount, displayName)).padRight(5);
}
}
}

View file

@ -0,0 +1,14 @@
Crée des chaînes de ravitaillement pour tes défenses, produit les matériaux de construction pour aggrandir et protéger tes batiments contre des vagues d'ennemis. Joue avec tes amis grâce à des jeux multijoueurs co-op cross-plateforme, ou défie les dans des parties en PvP par équipe.
Caractéristiques:
- 24 cartes dans le jeu de base
- Une campagne, complète avec un arbre de recherche et des zones à débloquer
- 4 puissantes vagues de boss à vaincre
- Systèmes de transport d'énergie, liquides et objets
- 19 différent types de drones, méchas et vaisseaux
- 120+ blocs technologiques à maîtriser
- 75+ différents blocs environnementaux
- Multijoueur cross-plateforme via réseau local or serveurs dédiés
- Règles de jeu personnalisables: Changez le coût des structures, les stats des ennemis, les ressources de départ, fréquence des vagues et plus
- Un éditeur puissant dotés d'outils pour générer aléatoirement des minéraux, le terrain, des décorations et appliquer une symétrie de terrain.
- Personnaliser les vagues d'ennemis

View file

@ -0,0 +1 @@
L'industrie au service de ce tower defense.

View file

@ -0,0 +1 @@
Mindustry

View file

@ -73,6 +73,23 @@
<string>io.anuke.mindustry.mapfile</string>
</array>
</dict>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array>
<string>icon-72.png</string>
</array>
<key>CFBundleTypeName</key>
<string>Zip Data File</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.archive</string>
</array>
</dict>
</array>
</array>
<key>UTExportedTypeDeclarations</key>

View file

@ -3,6 +3,7 @@ package io.anuke.mindustry;
import com.badlogic.gdx.backends.iosrobovm.*;
import io.anuke.arc.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
@ -13,6 +14,7 @@ import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import org.robovm.apple.foundation.*;
import org.robovm.apple.uikit.*;
import org.robovm.objc.block.*;
import java.io.*;
import java.util.*;
@ -38,6 +40,51 @@ public class IOSLauncher extends IOSApplication.Delegate{
IOSApplicationConfiguration config = new IOSApplicationConfiguration();
return new IOSApplication(new ClientLauncher(){
@Override
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){
UIDocumentBrowserViewController cont = new UIDocumentBrowserViewController();
cont.setAllowsDocumentCreation(false);
cont.setDelegate(new UIDocumentBrowserViewControllerDelegate(){
@Override
public void didPickDocumentURLs(UIDocumentBrowserViewController controller, NSArray<NSURL> documentURLs){
}
@Override
public void didPickDocumentsAtURLs(UIDocumentBrowserViewController controller, NSArray<NSURL> documentURLs){
}
@Override
public void didRequestDocumentCreationWithHandler(UIDocumentBrowserViewController controller, VoidBlock2<NSURL, UIDocumentBrowserImportMode> importHandler){
}
@Override
public void didImportDocument(UIDocumentBrowserViewController controller, NSURL sourceURL, NSURL destinationURL){
cons.accept(Core.files.absolute(destinationURL.getAbsoluteString()));
}
@Override
public void failedToImportDocument(UIDocumentBrowserViewController controller, NSURL documentURL, NSError error){
}
@Override
public NSArray<UIActivity> applicationActivities(UIDocumentBrowserViewController controller, NSArray<NSURL> documentURLs){
return null;
}
@Override
public void willPresentActivityViewController(UIDocumentBrowserViewController controller, UIActivityViewController activityViewController){
}
});
UIApplication.getSharedApplication().getKeyWindow().getRootViewController().presentViewController(cont, true, () -> {
});
}
@Override
public void shareFile(FileHandle file){
Log.info("Attempting to share file " + file);

View file

@ -36,12 +36,13 @@ public class ArcNetClient implements ClientProvider{
}
@Override
public void disconnected(Connection connection){
public void disconnected(Connection connection, DcReason reason){
if(connection.getLastProtocolError() != null){
netClient.setQuiet();
}
Disconnect c = new Disconnect();
c.reason = reason.toString();
Core.app.post(() -> Net.handleClientReceived(c));
}
@ -181,6 +182,8 @@ public class ArcNetClient implements ClientProvider{
private void handleException(Exception e){
if(e instanceof ArcNetException){
Core.app.post(() -> Net.showError(new IOException("mismatch")));
}else if(e instanceof ClosedChannelException){
Core.app.post(() -> Net.showError(new IOException("alreadyconnected")));
}else{
Core.app.post(() -> Net.showError(e));
}

View file

@ -47,12 +47,13 @@ public class ArcNetServer implements ServerProvider{
}
@Override
public void disconnected(Connection connection){
public void disconnected(Connection connection, DcReason reason){
ArcConnection k = getByArcID(connection.getID());
if(k == null) return;
Disconnect c = new Disconnect();
c.id = k.id;
c.reason = reason.toString();
Core.app.post(() -> {
Net.handleServerReceived(k.id, c);
@ -102,6 +103,34 @@ public class ArcNetServer implements ServerProvider{
return null;
}
@Override
public void sendStream(int id, Streamable stream){
ArcConnection connection = getByID(id);
if(connection == null) return;
connection.connection.addListener(new InputStreamSender(stream.stream, 512){
int id;
@Override
protected void start(){
//send an object so the receiving side knows how to handle the following chunks
StreamBegin begin = new StreamBegin();
begin.total = stream.stream.available();
begin.type = Registrator.getID(stream.getClass());
connection.connection.sendTCP(begin);
id = begin.id;
}
@Override
protected Object next(byte[] bytes){
StreamChunk chunk = new StreamChunk();
chunk.id = id;
chunk.data = bytes;
return chunk; //wrap the byte[] with an object so the receiving side knows how to handle it.
}
});
}
@Override
public void host(int port) throws IOException{
connections.clear();
@ -159,7 +188,7 @@ public class ArcNetServer implements ServerProvider{
}catch(Exception e){
Log.err(e);
Log.info("Error sending packet. Disconnecting invalid client!");
connection.close();
connection.close(DcReason.error);
ArcConnection k = getByArcID(connection.getID());
if(k != null) connections.remove(k);
@ -168,7 +197,7 @@ public class ArcNetServer implements ServerProvider{
@Override
public void close(){
if(connection.isConnected()) connection.close();
if(connection.isConnected()) connection.close(DcReason.closed);
}
}

View file

@ -41,7 +41,7 @@ public class ServerControl implements ApplicationListener{
private static final int commandSocketPort = 6859;
private final CommandHandler handler = new CommandHandler("");
private final FileHandle logFolder = Core.files.local("logs/");
private final FileHandle logFolder = Core.settings.getDataDirectory().child("logs/");
private final io.anuke.mindustry.plugin.Plugins plugins = new Plugins();
private FileHandle currentLogFile;
@ -401,6 +401,68 @@ public class ServerControl implements ApplicationListener{
info("Server name is now &lc'{0}'.", arg[0]);
});
handler.register("playerlimit", "[off/somenumber]", "Set the server player limit.", arg -> {
if(arg.length == 0){
info("Player limit is currently &lc{0}.", netServer.admins.getPlayerLimit() == 0 ? "off" : netServer.admins.getPlayerLimit());
return;
}
if(arg[0].equals("off")){
netServer.admins.setPlayerLimit(0);
info("Player limit disabled.");
return;
}
if(Strings.canParsePostiveInt(arg[0]) && Strings.parseInt(arg[0]) > 0){
int lim = Strings.parseInt(arg[0]);
netServer.admins.setPlayerLimit(lim);
info("Player limit is now &lc{0}.", lim);
}else{
err("Limit must be a number above 0.");
}
});
handler.register("whitelist", "[on/off...]", "Enable/disable whitelisting.", arg -> {
if(arg.length == 0){
info("Whitelist is currently &lc{0}.", netServer.admins.isWhitelistEnabled() ? "on" : "off");
return;
}
boolean on = arg[0].equalsIgnoreCase("on");
netServer.admins.setWhitelist(on);
info("Whitelist is now &lc{0}.", on ? "on" : "off");
});
handler.register("whitelisted", "List the entire whitelist.", arg -> {
if(netServer.admins.getWhitelisted().isEmpty()){
info("&lyNo whitelisted players found.");
return;
}
info("&lyWhitelist:");
netServer.admins.getWhitelisted().each(p -> Log.info("- &ly{0}", p.lastName));
});
handler.register("whitelist-add", "<ID>", "Add a player to the whitelist by ID.", arg -> {
PlayerInfo info = netServer.admins.getInfoOptional(arg[0]);
if(info == null){
err("Player ID not found. You must use the ID displayed when a player joins a server.");
return;
}
netServer.admins.whitelist(arg[0]);
info("Player &ly'{0}'&lg has been whitelisted.", info.lastName);
});
handler.register("whitelist-remove", "<ID>", "Remove a player to the whitelist by ID.", arg -> {
PlayerInfo info = netServer.admins.getInfoOptional(arg[0]);
if(info == null){
err("Player ID not found. You must use the ID displayed when a player joins a server.");
return;
}
netServer.admins.unwhitelist(arg[0]);
info("Player &ly'{0}'&lg has been un-whitelisted.", info.lastName);
});
handler.register("crashreport", "<on/off>", "Disables or enables automatic crash reporting", arg -> {
boolean value = arg[0].equalsIgnoreCase("on");
Core.settings.put("crashreport", value);