1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-11 08:15:35 +03:00
This commit is contained in:
Anuken 2019-08-21 20:10:54 -04:00
commit a7a49a823b
74 changed files with 519 additions and 625 deletions

3
.gitignore vendored
View File

@ -23,8 +23,6 @@ saves/
steam_appid.txt
/test_files/
/annotations/build/
/desktop-sdl/build/
desktop-sdl/build/
/android/assets/mindustry-maps/
/android/assets/mindustry-saves/
/core/assets/gifexport/
@ -35,6 +33,7 @@ desktop-sdl/build/
ios/robovm.properties
packr-out/
config/
changelog
*.gif
version.properties

View File

@ -13,7 +13,7 @@ deploy:
api_key:
secure: Cv5wFtWt62/A24EvSEQvMow7gKPbZ3oATEFPuSghhB2TQz1dA40Zee3Qvk4LFlpLrhYo4K0ZSczCZRGpR+hCd8+Dpww52bheYEvWuh3ZQfvu/fXtEx2j5PwP1qMpmIgSxETV/gkD7l9FImdh0VzktYiAvQfmi0bEocG9/D4QwjFpNat7iwBdcMiw1MvAygpdIWRsjiw0RKlB2mWarmoHhQ7Gu7qlU3j50uaEvcrtmU0pBUPggNQwQRv32i9NPvNFxrqqlUjDLIS8JFea99zCkp8BwYqbEvBIMzd+Qip1/stLJJA3+cDUClbsDtg8rAVetzpOrdLEEBmqShFe5MDl2yEHcsgpN9CFsyTaUfvB3P3rVjizvycMm42IsUkXQiarm5xTQ/TIA8Rd8AHiSKuweNCg1Fd5SFaRtKy8JVLXuxyfUccmyje6hhz2L4lS2Wfj3mAG7sqZUCXhWP79EKdGkiPOjKv4CwXEKmuH3BMVqPlNUZJr9Eg3sV1FG0h2l+MVOOnR635qdUbb49sYojYxVruMLX0BH1c4ZCu230m8CUoWA1Em1QNI75ya7+9Y5T6AsgWDVpBvdUo9fWNbdp+VQ0GskFQsJD5wtnxbcbHeFiERAgGBm7z6qt9u9LrQpBH+dsW52ADvYsu3L4nQEa+sdMHwTTwmGY+iUvsxu0DqxGg=
file:
- desktop/build/libs/desktop-release.jar
- desktop/build/libs/Mindustry.jar
- server/build/libs/server-release.jar
on:
repo: Anuken/Mindustry

View File

@ -36,7 +36,7 @@ If the terminal returns `Permission denied` or `Command not found` on Mac/Linux,
---
Gradle may take up to several minutes to download files. Be patient. <br>
After building, the output .JAR file should be in `/desktop/build/libs/desktop-release.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
After building, the output .JAR file should be in `/desktop/build/libs/Mindustry.jar` for desktop builds, and in `/server/build/libs/server-release.jar` for server builds.
### Downloads

View File

@ -8,7 +8,7 @@ buildscript{
}
dependencies{
classpath 'com.android.tools.build:gradle:3.4.2'
classpath 'com.android.tools.build:gradle:3.5.0'
}
}

View File

@ -146,23 +146,6 @@ allprojects{
project(":desktop"){
apply plugin: "java"
dependencies{
compile project(":core")
compile project(":net")
if(debugged()) compile project(":debug")
compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
compile arcModule("backends:backend-lwjgl3")
compile 'com.github.MinnDevelopment:java-discord-rpc:v2.0.2'
}
}
project(":desktop-sdl"){
apply plugin: "java"
dependencies{
compile project(":core")
compile project(":net")
@ -318,3 +301,24 @@ project(":net"){
compile "org.lz4:lz4-java:1.4.1"
}
}
task deployAll{
task cleanDeployOutput{
doFirst{
if("${getBuildVersion()}" == "custom build" || "${getBuildVersion()}" == "") throw new IllegalArgumentException("----\n\nSET A BUILD NUMBER FIRST!\n\n----")
if(!project.hasProperty("release")) throw new IllegalArgumentException("----\n\nSET THE RELEASE PROJECT PROPERTY FIRST!\n\n----")
delete{
delete "deploy/"
}
}
}
dependsOn cleanDeployOutput
dependsOn "desktop:packrLinux64"
dependsOn "desktop:packrWindows64"
dependsOn "desktop:packrWindows32"
dependsOn "desktop:packrMacOS"
dependsOn "server:deploy"
dependsOn "android:deploy"
}

View File

@ -75,6 +75,7 @@ server.kicked.nameEmpty = Your chosen name is invalid.
server.kicked.idInUse = You are already on this server! Connecting with two accounts is not permitted.
server.kicked.customClient = This server does not support custom builds. Download an official version.
server.kicked.gameover = Game over!
server.versions = Your version:[accent] {0}[]\nServer version:[accent] {1}[]
host.info = The [accent]host[] button hosts a server on port [scarlet]6567[]. \nAnybody on the same [lightgray]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[lightgray]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. Note that public networks sometimes do not allow server discovery.
join.info = Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[lightgray]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP.
hostserver = Host Multiplayer Game

Binary file not shown.

View File

@ -227,12 +227,14 @@ public class TechTree implements ContentList{
node(turbineGenerator, () -> {
node(thermalGenerator, () -> {
node(rtgGenerator, () -> {
node(differentialGenerator, () -> {
node(thoriumReactor, () -> {
node(impactReactor, () -> {
node(differentialGenerator, () -> {
node(thoriumReactor, () -> {
node(impactReactor, () -> {
});
node(rtgGenerator, () -> {
});
});
});
});

View File

@ -84,7 +84,7 @@ public class Control implements ApplicationListener{
});
Events.on(PlayEvent.class, event -> {
player.setTeam(state.rules.pvp ? netServer.assignTeam(playerGroup.all()) : defaultTeam);
player.setTeam(state.rules.pvp ? netServer.assignTeam(player, playerGroup.all()) : defaultTeam);
player.setDead(true);
player.add();

View File

@ -176,7 +176,7 @@ public class NetServer implements ApplicationListener{
//playing in pvp mode automatically assigns players to teams
if(state.rules.pvp){
player.setTeam(assignTeam(playerGroup.all()));
player.setTeam(assignTeam(player, playerGroup.all()));
Log.info("Auto-assigned player {0} to team {1}.", player.name, player.getTeam());
}
@ -194,13 +194,13 @@ public class NetServer implements ApplicationListener{
});
}
public Team assignTeam(Iterable<Player> players){
public Team assignTeam(Player current, Iterable<Player> players){
//find team with minimum amount of players and auto-assign player to that.
return Structs.findMin(Team.all, team -> {
if(state.teams.isActive(team) && !state.teams.get(team).cores.isEmpty()){
int count = 0;
for(Player other : players){
if(other.getTeam() == team){
if(other.getTeam() == team && other != current){
count++;
}
}

View File

@ -527,7 +527,9 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
spawner = null;
}
avoidOthers();
if(isLocal || Net.server()){
avoidOthers();
}
Tile tile = world.tileWorld(x, y);

View File

@ -214,7 +214,12 @@ public class Maps implements Disposable{
return filters;
}else{
return JsonIO.read(Array.class, str);
try{
return JsonIO.read(Array.class, str);
}catch(Exception e){
e.printStackTrace();
return readFilters("");
}
}
}

View File

@ -1,21 +1,20 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.annotations.Annotations.Serialize;
import io.anuke.arc.Core;
import io.anuke.arc.collection.Array;
import io.anuke.arc.graphics.Color;
import io.anuke.arc.math.Mathf;
import io.anuke.arc.scene.style.Drawable;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.Cell;
import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.Strings;
import io.anuke.arc.util.Time;
import io.anuke.mindustry.Vars;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.game.Version;
import io.anuke.mindustry.net.Host;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Packets.*;
import static io.anuke.mindustry.Vars.*;
@ -97,9 +96,14 @@ public class JoinDialog extends FloatingDialog{
TextButton button = buttons[0] = remote.addButton("[accent]" + server.displayIP(), "clear", () -> {
if(!buttons[0].childrenPressed()){
connect(server.ip, server.port);
if(server.lastHost != null && server.lastHost.version != Version.build && Version.build != -1 && server.lastHost.version != -1){
ui.showInfo("[scarlet]" + (server.lastHost.version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated).toString() + "\n[]" +
Core.bundle.format("server.versions", Version.build, server.lastHost.version));
}else{
connect(server.ip, server.port);
}
}
}).width(targetWidth()).height(130f).pad(4f).get();
}).width(targetWidth()).pad(4f).get();
button.getLabel().setWrap(true);

View File

@ -113,9 +113,16 @@ public class ZoneInfoDialog extends FloatingDialog{
t.add("$zone.resources").padRight(6);
if(zone.resources.length > 0){
for(Item item : zone.resources){
t.addImage(item.icon(Item.Icon.medium)).size(8 * 3);
}
t.table(r -> {
t.left();
int i = 0;
for(Item item : zone.resources){
r.addImage(item.icon(Item.Icon.medium)).size(8 * 3);
if(++i % 4 == 0){
r.row();
}
}
});
}else{
t.add("$none");
}

View File

@ -242,9 +242,9 @@ public class HudFragment extends Fragment{
IntFormat fps = new IntFormat("fps");
IntFormat ping = new IntFormat("ping");
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left();
info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style("outline");
info.row();
info.label(() -> ping.get(Net.getPing())).visible(Net::client).left();
info.label(() -> ping.get(Net.getPing())).visible(Net::client).left().style("outline");
}).top().left();
});

View File

@ -294,9 +294,7 @@ public class Block extends BlockStorage{
/** Called after the block is placed by this client. */
@CallSuper
public void playerPlaced(Tile tile){
if(outputsPower && !consumesPower){
PowerNode.lastPlaced = tile.pos();
}
}
/** Called after the block is placed by anyone. */

View File

@ -2,6 +2,7 @@ package io.anuke.mindustry.world.blocks.power;
import io.anuke.annotations.Annotations.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
@ -20,9 +21,7 @@ import io.anuke.mindustry.world.meta.*;
import static io.anuke.mindustry.Vars.*;
public class PowerNode extends PowerBlock{
//last distribution block placed
public static int lastPlaced = -1;
protected ObjectSet<PowerGraph> graphs = new ObjectSet<>();
protected Vector2 t1 = new Vector2(), t2 = new Vector2();
protected TextureRegion laser, laserEnd;
@ -99,23 +98,11 @@ public class PowerNode extends PowerBlock{
() -> Mathf.clamp(entity.power.graph.getPowerProduced() / entity.power.graph.getPowerNeeded())));
}
@Override
public void playerPlaced(Tile tile){
Tile before = world.tile(lastPlaced);
if(linkValid(tile, before) && !before.entity.proximity().contains(tile)){
Call.linkPowerNodes(null, tile, before);
}
lastPlaced = tile.pos();
super.playerPlaced(tile);
}
@Override
public void placed(Tile tile){
if(Net.client()) return;
Predicate<Tile> valid = other -> other != null && other != tile && ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower)) && linkValid(tile, other)
Predicate<Tile> valid = other -> other != null && other != tile && ((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) && linkValid(tile, other)
&& !other.entity.proximity().contains(tile) && other.entity.power.graph != tile.entity.power.graph;
tempTiles.clear();
@ -132,6 +119,28 @@ public class PowerNode extends PowerBlock{
super.placed(tile);
}
private void getPotentialLinks(Tile tile, Consumer<Tile> others){
Predicate<Tile> valid = other -> other != null && other != tile && other.entity != null && other.entity.power != null &&
((!other.block().outputsPower && other.block().consumesPower) || (other.block().outputsPower && !other.block().consumesPower) || other.block() instanceof PowerNode) &&
overlaps(tile.x * tilesize + offset(), tile.y *tilesize + offset(), other, laserRange * tilesize)
&& !other.entity.proximity().contains(tile) && !graphs.contains(other.entity.power.graph);
tempTiles.clear();
graphs.clear();
Geometry.circle(tile.x, tile.y, (int)(laserRange + 1), (x, y) -> {
Tile other = world.ltile(x, y);
if(valid.test(other)){
tempTiles.add(other);
}
});
tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile)));
tempTiles.each(valid, t -> {
graphs.add(t.entity.power.graph);
others.accept(t);
});
}
@Override
public void setStats(){
super.setStats();
@ -213,6 +222,11 @@ public class PowerNode extends PowerBlock{
Draw.color(Pal.placing);
Drawf.circles(x * tilesize + offset(), y * tilesize + offset(), laserRange * tilesize);
getPotentialLinks(tile, other -> {
Drawf.square(other.drawx(), other.drawy(), other.block().size * tilesize / 2f + 2f, Pal.place);
});
/*
for(int cx = (int)(x - laserRange - 1); cx <= x + laserRange + 1; cx++){
for(int cy = (int)(y - laserRange - 1); cy <= y + laserRange + 1; cy++){
Tile link = world.ltile(cx, cy);
@ -221,7 +235,7 @@ public class PowerNode extends PowerBlock{
Drawf.square(link.drawx(), link.drawy(), link.block().size * tilesize / 2f + 2f, link.pos() == lastPlaced ? Pal.place : Pal.accent);
}
}
}
}*/
Draw.reset();
}

View File

@ -12,6 +12,11 @@ public class LiquidConverter extends GenericCrafter{
hasLiquids = true;
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void init(){
ConsumeLiquidBase cl = consumes.get(ConsumeType.liquid);

View File

@ -1,99 +0,0 @@
apply plugin: "java"
sourceCompatibility = 1.8
sourceSets.main.java.srcDirs = [ "src/" ]
project.ext.mainClassName = "io.anuke.mindustry.desktopsdl.DesktopLauncher"
project.ext.assetsDir = new File("../core/assets")
def IKVM_DIR = System.env.IKVM_HOME
def getTarget = { return project.hasProperty("target") ? project.properties["target"] : "windows" }
task run(dependsOn: classes, type: JavaExec){
main = project.mainClassName
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
workingDir = project.assetsDir
ignoreExitValue = true
if(System.getProperty("os.name").toLowerCase().contains("mac")){
jvmArgs("-XstartOnFirstThread", "-Djava.awt.headless=true")
}
if(project.hasProperty("args")){
args Eval.me(project.getProperties()["args"])
}
if(args.contains("debug")){
main = "io.anuke.mindustry.DebugLauncher"
}
}
task dist(type: Jar, dependsOn: classes){
from files(sourceSets.main.output.classesDirs)
from files(sourceSets.main.output.resourcesDir)
from {configurations.compile.collect {zipTree(it)}}
from files(project.assetsDir)
//use target = all for all platforms
def target = getTarget()
if(target.contains("windows")) exclude('**.so', "**.dylib")
if(target == "mac") exclude('**.so', "**.dll")
if(target == "linux") exclude('**.dll', "**.dylib")
archivesBaseName = appName + "-" + target
manifest{
attributes 'Main-Class': project.mainClassName
}
}
task steamdist(dependsOn: dist){
doLast{
copy{
from "build/libs/Mindustry-linux-release.jar"
into "/home/anuke/.steam/steam/steamapps/common/Mindustry"
rename { String fileName ->
fileName.replace("Mindustry-linux-release", "desktop-release")
}
}
}
}
task ikZip(type: Zip){
def filename = "$appName-${getTarget()}-${version}"
from "build/libs/$filename"
archiveName = "${generateDeployName(getTarget())}.zip"
}
task ikdist{
dependsOn dist
finalizedBy ikZip
doLast{
def filename = "$appName-${getTarget()}-${version}"
def folder = "build/libs/$filename"
def baseArgs = System.properties['os.name'].toLowerCase().contains('windows') ? [] : ["mono"]
def args = baseArgs + ["$IKVM_DIR/ikvmc.exe", "-target:winexe", "-out:build/libs/${filename}.exe", "build/libs/${filename}.jar"]
if(file("../core/assets/sprites/icon.ico").exists()){
args += ["-win32icon:../core/assets/sprites/icon.ico"]
}else if(file("../core/assets/icons/icon.ico").exists()){
args += ["-win32icon:../core/assets/icons/icon.ico"]
}
exec{
commandLine args
}
copy{
from file("build/libs/${filename}.exe")
into file(folder)
}
copy{
from(getTarget().contains("32") ? "$IKVM_DIR/libraries_32" : "$IKVM_DIR/libraries")
into folder
}
}
}

View File

@ -1,31 +0,0 @@
package io.anuke.mindustry.desktopsdl;
import io.anuke.arc.Files.*;
import io.anuke.arc.backends.sdl.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.desktopsdl.steam.*;
import io.anuke.mindustry.net.*;
public class DesktopLauncher{
public static void main(String[] arg){
try{
Platform.instance = new DesktopPlatform(arg);
Net.setClientProvider(new ArcNetClient());
Net.setServerProvider(new ServerSteam(new ArcNetServer()));
new SdlApplication(new Mindustry(), new SdlConfig(){{
title = "Mindustry";
maximized = true;
depth = 0;
stencil = 0;
width = 900;
height = 700;
setWindowIcon(FileType.Internal, "icons/icon_64.png");
}});
}catch(Throwable e){
DesktopPlatform.handleCrash(e);
}
}
}

View File

@ -1,180 +0,0 @@
package io.anuke.mindustry.desktopsdl;
import club.minnced.discord.rpc.*;
import com.codedisaster.steamworks.*;
import io.anuke.arc.*;
import io.anuke.arc.backends.sdl.jni.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.desktopsdl.steam.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.ui.dialogs.*;
import java.net.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
public class DesktopPlatform extends Platform{
static boolean useDiscord = OS.is64Bit, useSteam = true;
final static String applicationId = "610508934456934412";
String[] args;
public DesktopPlatform(String[] args){
this.args = args;
testMobile = Array.with(args).contains("-testMobile");
if(useDiscord){
try{
DiscordEventHandlers handlers = new DiscordEventHandlers();
DiscordRPC.INSTANCE.Discord_Initialize(applicationId, handlers, true, "");
Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC.INSTANCE::Discord_Shutdown));
}catch(Throwable t){
useDiscord = false;
Log.err("Failed to initialize discord.", t);
}
}
if(useSteam){
Vars.steam = true;
try{
SteamAPI.loadLibraries();
if(!SteamAPI.init()){
Log.info("Steam client not running. Make sure Steam is running!");
}else{
//times per second
float interval = 20f;
Interval i = new Interval();
//run steam callbacks
Events.on(GameLoadEvent.class, event -> {
//update callbacks
Core.app.addListener(new ApplicationListener(){
@Override
public void update(){
if(i.get(interval)){
if(SteamAPI.isSteamRunning()){
SteamAPI.runCallbacks();
}
}
}
});
Core.app.post(() -> new ClientSteam());
});
//steam shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(SteamAPI::shutdown));
}
}catch(Exception e){
Log.err("Failed to load Steam native libraries.");
e.printStackTrace();
}
}
}
static void handleCrash(Throwable e){
Consumer<Runnable> dialog = Runnable::run;
boolean badGPU = false;
if(e.getMessage() != null && (e.getMessage().contains("Couldn't create window") || e.getMessage().contains("OpenGL 2.0 or higher"))){
dialog.accept(() -> message(
e.getMessage().contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + e.getMessage() :
"Your graphics card does not support OpenGL 2.0!\n" +
"Try to update your graphics drivers.\n\n" +
"(If that doesn't work, your computer just doesn't support Mindustry.)"));
badGPU = true;
}
boolean fbgp = badGPU;
CrashSender.send(e, file -> {
if(!fbgp){
dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + (e.getMessage() == null ? "" : "\n" + e.getMessage())));
}
});
}
@Override
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, Predicate<String> filetype){
new FileChooser(text, file -> filetype.test(file.extension().toLowerCase()), open, cons).show();
}
@Override
public void updateRPC(){
if(!useDiscord) return;
DiscordRichPresence presence = new DiscordRichPresence();
if(!state.is(State.menu)){
String map = world.getMap() == null ? "Unknown Map" : world.isZone() ? world.getZone().localizedName : Strings.capitalize(world.getMap().name());
String mode = state.rules.pvp ? "PvP" : state.rules.attackMode ? "Attack" : "Survival";
String players = Net.active() && playerGroup.size() > 1 ? " | " + playerGroup.size() + " Players" : "";
presence.state = mode + players;
if(!state.rules.waves){
presence.details = map;
}else{
presence.details = map + " | Wave " + state.wave;
presence.largeImageText = "Wave " + state.wave;
}
}else{
if(ui.editor != null && ui.editor.isShown()){
presence.state = "In Editor";
}else if(ui.deploy != null && ui.deploy.isShown()){
presence.state = "In Launch Selection";
}else{
presence.state = "In Menu";
}
}
presence.largeImageKey = "logo";
DiscordRPC.INSTANCE.Discord_UpdatePresence(presence);
}
@Override
public String getUUID(){
try{
Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
NetworkInterface out;
for(out = e.nextElement(); (out.getHardwareAddress() == null || !validAddress(out.getHardwareAddress())) && e.hasMoreElements(); out = e.nextElement()) ;
byte[] bytes = out.getHardwareAddress();
byte[] result = new byte[8];
System.arraycopy(bytes, 0, result, 0, bytes.length);
String str = new String(Base64Coder.encode(result));
if(str.equals("AAAAAAAAAOA=") || str.equals("AAAAAAAAAAA=")) throw new RuntimeException("Bad UUID.");
return str;
}catch(Exception e){
return super.getUUID();
}
}
private static void message(String message){
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MESSAGEBOX_ERROR, "oh no", message);
}
private boolean validAddress(byte[] bytes){
if(bytes == null) return false;
byte[] result = new byte[8];
System.arraycopy(bytes, 0, result, 0, bytes.length);
return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=") && !new String(Base64Coder.encode(result)).equals("AAAAAAAAAAA=");
}
}

View File

@ -1,43 +1,28 @@
import com.badlogicgames.packr.Packr
import com.badlogicgames.packr.PackrConfig
apply plugin: "java"
sourceCompatibility = 1.8
sourceSets.main.java.srcDirs = ["src/"]
sourceSets.main.java.srcDirs = [ "src/" ]
project.ext.mainClassName = "io.anuke.mindustry.desktop.DesktopLauncher"
project.ext.assetsDir = new File("../core/assets")
def IKVM_DIR = System.env.IKVM_HOME
import com.badlogicgames.packr.Packr
import com.badlogicgames.packr.PackrConfig
def JDK_DIR = "$System.env.PACKR_DIR"
def ICON_DIR = new File("core/assets/icons/icon.icns")
ext.getPlatform = {
def lc = project.hasProperty("platform") ? platform.toLowerCase() : ""
if(lc == "windows64"){
return PackrConfig.Platform.Windows64
}else if(lc == "windows32"){
return PackrConfig.Platform.Windows32
}else if(lc == "linux"){
return PackrConfig.Platform.Linux64
}else if(lc == "mac"){
return PackrConfig.Platform.MacOS
}else{
throw new InvalidUserDataException("Invalid platform. Set platform with -Pplatform=windows64/windows32/linux/mac")
}
}
task run(dependsOn: classes, type: JavaExec){
main = project.mainClassName
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
workingDir = project.assetsDir
if(System.getProperty("os.name").toLowerCase().contains("mac")){
jvmArgs "-XstartOnFirstThread"
}
ignoreExitValue = true
if(System.getProperty("os.name").toLowerCase().contains("mac")){
jvmArgs("-XstartOnFirstThread", "-Djava.awt.headless=true")
}
if(project.hasProperty("args")){
args Eval.me(project.getProperties()["args"])
}
@ -47,132 +32,122 @@ task run(dependsOn: classes, type: JavaExec){
}
}
task debug(dependsOn: classes, type: JavaExec){
main = project.mainClassName
classpath = sourceSets.main.runtimeClasspath
standardInput = System.in
workingDir = project.assetsDir
ignoreExitValue = true
debug = true
}
task dist(type: Jar){
dependsOn classes
writeVersion()
task dist(type: Jar, dependsOn: classes){
from files(sourceSets.main.output.classesDirs)
from files(sourceSets.main.output.resourcesDir)
from{ configurations.compile.collect{ zipTree(it) } }
from {configurations.compile.collect {zipTree(it)}}
from files(project.assetsDir)
archiveName = "${appName}.jar"
manifest{
attributes 'Main-Class': project.mainClassName
}
}
//note: call desktop:dist beforehand
task packrCmd(){
task steamdist(dependsOn: dist){
doLast{
def config = new PackrConfig()
config.with{
config.executable = appName
verbose = true
platform = getPlatform()
bundleIdentifier = getPackage() + ".mac"
iconResource = ICON_DIR
outDir = file("packr-out/")
mainClass = project.ext.mainClassName
classpath = ["desktop/build/libs/desktop-release.jar"]
removePlatformLibs = ["desktop/build/libs/desktop-release.jar"]
vmArgs = ["Djava.net.preferIPv4Stack=true"]
minimizeJre = "desktop/packr_minimize.json"
jdk = JDK_DIR + "jdk-${getPlatform().toString().toLowerCase()}.zip"
if(getPlatform() == PackrConfig.Platform.MacOS){
vmArgs += "XstartOnFirstThread"
copy{
from "build/libs/Mindustry-linux-release.jar"
into "/home/anuke/.steam/steam/steamapps/common/Mindustry"
rename { String fileName ->
fileName.replace("Mindustry-linux-release", "desktop-release")
}
}
new Packr().pack(config)
}
}
task copyTemplate(){
doLast{
copy{
into "packr-out/"
from "${JDK_DIR}/templates/${getPlatform().toString().toLowerCase()}"
}
copy{
into "packr-out/"
from "build/libs/desktop-release.jar"
}
}
}
task packrZip(){
task clearOut(type: Delete){
doLast{
delete "packr-out/"
}
}
PackrConfig.Platform.values().each{ platform ->
task "packr${platform.toString()}"{
def platformName = platform.toString().replace('64', '').replace('32', '').replace('MacOS', 'Mac')
task fixMac(type: Copy){
dependsOn "packrCmd"
into "packr-out/" + appName + ".app/Contents/"
from "packr-out/Contents/"
doLast{
delete{
delete "packr-out/Contents/"
}
}
}
task fixWindows32(type: Copy){
dependsOn "packrCmd"
into "packr-out/jre/bin/"
from JDK_DIR + "zip.dll"
rename("zip.dll", "ojdkbuild_zlib.dll")
dependsOn dist
doLast{
copy{
into "packr-out/jre/bin/"
from JDK_DIR + "zip.dll"
into "build/packr/"
rename("${appName}.jar", "desktop.jar")
from "build/libs/${appName}.jar"
}
delete{
delete "build/packr/output/"
}
if(platform == PackrConfig.Platform.Windows32 || platform == PackrConfig.Platform.Windows64){
copy{
into "build/packr/output"
from "${JDK_DIR}/templates/${platform.toString().toLowerCase()}"
}
copy{
into "build/packr/output/jre"
rename("${appName}.jar", "desktop.jar")
from "build/libs/${appName}.jar"
}
}else{
def config = new PackrConfig()
config.with{
config.executable = appName
config.platform = platform
verbose = true
bundleIdentifier = getPackage() + ".mac"
iconResource = ICON_DIR
outDir = file("build/packr/output")
mainClass = project.ext.mainClassName
classpath = ["desktop/build/packr/desktop.jar"]
removePlatformLibs = ["desktop/build/packr/desktop.jar"]
vmArgs = ["Djava.net.preferIPv4Stack=true"]
minimizeJre = "desktop/packr_minimize.json"
jdk = JDK_DIR + "jdk-${platform.toString().toLowerCase()}.zip"
if(platform == PackrConfig.Platform.MacOS){
vmArgs += "XstartOnFirstThread"
}
}
new Packr().pack(config)
if(platform == PackrConfig.Platform.Linux64){
copy{
into "build/packr/output/jre/"
from "build/packr/output/desktop.jar"
}
delete{
delete "build/packr/output/desktop.jar"
}
file("build/packr/output/config.json").text = file("build/packr/output/config.json").text.replace("desktop.jar", "jre/desktop.jar")
}
if(platform == PackrConfig.Platform.MacOS){
copy{
into "build/packr/${appName}.app/Contents/"
from "build/packr/Contents/"
}
delete{
delete "build/packr/Contents/"
}
}
}
}
task "zip${platform.toString()}"(type: Zip){
from "build/packr/output"
archiveName "${generateDeployName(platform.toString())}.zip"
destinationDir(file("../deploy"))
doLast{
delete{
delete "build/packr/"
}
}
}
finalizedBy "zip${platform.toString()}"
}
finalizedBy "clearOut"
if(project.hasProperty("platform")){
def plat = getPlatform()
if(plat == PackrConfig.Platform.Windows32 || plat == PackrConfig.Platform.Windows64){
dependsOn "copyTemplate"
}else{
dependsOn "packrCmd"
if(getPlatform() == PackrConfig.Platform.MacOS){
dependsOn "fixMac"
}
if(getPlatform() == PackrConfig.Platform.Windows32){
dependsOn "fixWindows32"
}
}
task rzip(type: Zip){
from "packr-out/"
archiveName "${generateDeployName(getPlatform().toString())}.zip"
destinationDir(file("packr-export"))
}
finalizedBy 'rzip'
}
}
}

View File

@ -27,13 +27,25 @@
"com/sun/jndi",
"com/sun/xml",
"com/sun/script",
"sum/awt",
"sun/awt",
"sun/java2d",
"sun/font",
"sun/rmi",
"sun/swing",
"java/util/stream",
"com/sun/media",
"sun/print",
"sun/awt",
"sun/instrument",
"sun/security/tools",
"sun/security/smartcardio",
"sun/audio",
"javax/xml",
"javax/sound",
"javax/script",
"java/beans",
"java/applet",
"java/rmi",
"com/sun/naming",
"java/awt",
"com/sun/org/apache/xpath",

View File

@ -1,27 +1,30 @@
package io.anuke.mindustry.desktop;
import io.anuke.arc.backends.lwjgl3.Lwjgl3Application;
import io.anuke.arc.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import io.anuke.mindustry.Mindustry;
import io.anuke.mindustry.core.Platform;
import io.anuke.arc.Files.*;
import io.anuke.arc.backends.sdl.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.desktop.steam.*;
import io.anuke.mindustry.net.*;
public class DesktopLauncher{
public static void main(String[] arg){
try{
Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
config.setTitle("Mindustry");
config.setMaximized(true);
config.setBackBufferConfig(8, 8, 8, 8, 0, 0, 0);
config.setWindowedMode(900, 600);
config.setWindowIcon("icons/icon_64.png");
Platform.instance = new DesktopPlatform(arg);
Net.setClientProvider(new ArcNetClient());
Net.setServerProvider(new ArcNetServer());
new Lwjgl3Application(new Mindustry(), config);
Net.setServerProvider(new SteamServerImpl(new ArcNetServer()));
new SdlApplication(new Mindustry(), new SdlConfig(){{
title = "Mindustry";
maximized = true;
depth = 0;
stencil = 0;
width = 900;
height = 700;
setWindowIcon(FileType.Internal, "icons/icon_64.png");
}});
}catch(Throwable e){
DesktopPlatform.handleCrash(e);
}

View File

@ -1,26 +1,30 @@
package io.anuke.mindustry.desktop;
import club.minnced.discord.rpc.*;
import io.anuke.arc.collection.Array;
import io.anuke.arc.files.FileHandle;
import io.anuke.arc.function.Consumer;
import io.anuke.arc.function.Predicate;
import com.codedisaster.steamworks.*;
import io.anuke.arc.*;
import io.anuke.arc.backends.sdl.jni.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.function.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.Base64Coder;
import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.net.CrashSender;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.net.Net;
import io.anuke.mindustry.ui.dialogs.FileChooser;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.ui.dialogs.*;
import java.net.NetworkInterface;
import java.net.*;
import java.util.*;
import static io.anuke.mindustry.Vars.*;
public class DesktopPlatform extends Platform{
static boolean useDiscord = OS.is64Bit;
static boolean useDiscord = OS.is64Bit, useSteam = true;
final static String applicationId = "610508934456934412";
String[] args;
@ -40,19 +44,55 @@ public class DesktopPlatform extends Platform{
Log.err("Failed to initialize discord.", t);
}
}
if(useSteam){
Vars.steam = true;
try{
SteamAPI.loadLibraries();
if(!SteamAPI.init()){
Log.info("Steam client not running. Make sure Steam is running!");
}else{
//times per second
float interval = 20f;
Interval i = new Interval();
//run steam callbacks
Events.on(GameLoadEvent.class, event -> {
//update callbacks
Core.app.addListener(new ApplicationListener(){
@Override
public void update(){
if(i.get(interval)){
if(SteamAPI.isSteamRunning()){
SteamAPI.runCallbacks();
}
}
}
});
//Core.app.post(() -> new ClientSteam());
});
//steam shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(SteamAPI::shutdown));
}
}catch(Exception e){
Log.err("Failed to load Steam native libraries.");
e.printStackTrace();
}
}
}
static void handleCrash(Throwable e){
Consumer<Runnable> dialog = r -> new Thread(r).start();
Consumer<Runnable> dialog = Runnable::run;
boolean badGPU = false;
if(e.getMessage() != null && (e.getMessage().contains("Couldn't create window") || e.getMessage().contains("OpenGL 2.0 or higher"))){
dialog.accept(() -> TinyFileDialogs.tinyfd_messageBox("oh no",
dialog.accept(() -> message(
e.getMessage().contains("Couldn't create window") ? "A graphics initialization error has occured! Try to update your graphics drivers:\n" + e.getMessage() :
"Your graphics card does not support OpenGL 2.0!\n" +
"Try to update your graphics drivers.\n\n" +
"(If that doesn't work, your computer just doesn't support Mindustry.)", "ok", "error", true));
"(If that doesn't work, your computer just doesn't support Mindustry.)"));
badGPU = true;
}
@ -60,7 +100,7 @@ public class DesktopPlatform extends Platform{
CrashSender.send(e, file -> {
if(!fbgp){
dialog.accept(() -> TinyFileDialogs.tinyfd_messageBox("oh no", "A crash has occured. It has been saved in:\n" + file.getAbsolutePath(), "ok", "error", true));
dialog.accept(() -> message("A crash has occured. It has been saved in:\n" + file.getAbsolutePath() + "\n" + (e.getMessage() == null ? "" : "\n" + e.getMessage())));
}
});
}
@ -118,7 +158,7 @@ public class DesktopPlatform extends Platform{
String str = new String(Base64Coder.encode(result));
if(str.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID.");
if(str.equals("AAAAAAAAAOA=") || str.equals("AAAAAAAAAAA=")) throw new RuntimeException("Bad UUID.");
return str;
}catch(Exception e){
@ -126,10 +166,14 @@ public class DesktopPlatform extends Platform{
}
}
private static void message(String message){
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MESSAGEBOX_ERROR, "oh no", message);
}
private boolean validAddress(byte[] bytes){
if(bytes == null) return false;
byte[] result = new byte[8];
System.arraycopy(bytes, 0, result, 0, bytes.length);
return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=");
return !new String(Base64Coder.encode(result)).equals("AAAAAAAAAOA=") && !new String(Base64Coder.encode(result)).equals("AAAAAAAAAAA=");
}
}

View File

@ -1,4 +1,4 @@
package io.anuke.mindustry.desktopsdl.steam;
package io.anuke.mindustry.desktop.steam;
import com.codedisaster.steamworks.*;
import com.codedisaster.steamworks.SteamFriends.*;
@ -6,12 +6,11 @@ import com.codedisaster.steamworks.SteamMatchmaking.*;
import com.codedisaster.steamworks.SteamNetworking.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.desktop.steam.SteamServerImpl.*;
import java.nio.*;
public class ClientSteam implements SteamNetworkingCallback, SteamMatchmakingCallback{
public class SteamClientImpl implements SteamNetworkingCallback, SteamMatchmakingCallback{
private SteamNetworking snet;
private SteamMatchmaking smat;
@ -19,7 +18,7 @@ public class ClientSteam implements SteamNetworkingCallback, SteamMatchmakingCal
//maps steam ID -> valid net connection
private IntMap<SteamConnection> connections = new IntMap<>();
public ClientSteam(){
public SteamClientImpl(){
//snet = new SteamNetworking(this);
//smat = new SteamMatchmaking(this);
@ -53,46 +52,6 @@ public class ClientSteam implements SteamNetworkingCallback, SteamMatchmakingCal
snet.acceptP2PSessionWithUser(steamIDRemote);
}
class SteamConnection extends NetConnection{
public final SteamID connection;
public SteamConnection(int id, String address, SteamID connection){
super(id, address);
this.connection = connection;
}
@Override
public boolean isConnected(){
return false;//connection.isConnected();
}
@Override
public void send(Object object, SendMode mode){
//TODO
/*
try{
if(mode == SendMode.tcp){
connection.sendTCP(object);
}else{
connection.sendUDP(object);
}
}catch(Exception e){
Log.err(e);
Log.info("Error sending packet. Disconnecting invalid client!");
connection.close();
ArcNetServer.KryoConnection k = getByKryoID(connection.getID());
if(k != null) connections.remove(k);
}*/
}
@Override
public void close(){
//TODO
//if(connection.isConnected()) connection.close();
}
}
@Override
public void onFavoritesListChanged(int ip, int queryPort, int connPort, int appID, int flags, boolean add, int accountID){
@ -185,8 +144,8 @@ public class ClientSteam implements SteamNetworkingCallback, SteamMatchmakingCal
}
});
friends.activateGameOverlay(OverlayDialog.Friends);
//friends.activateGameOverlayInviteDialog(steamIDLobby);
//friends.activateGameOverlay(OverlayDialog.Friends);
friends.activateGameOverlayInviteDialog(steamIDLobby);
}
}

View File

@ -0,0 +1,52 @@
package io.anuke.mindustry.desktop.steam;
import com.codedisaster.steamworks.*;
import com.codedisaster.steamworks.SteamFriends.*;
public class SteamFriendsImpl implements SteamFriendsCallback{
public final SteamFriends friends;
public SteamFriendsImpl(){
friends = new SteamFriends(this);
}
@Override
public void onSetPersonaNameResponse(boolean b, boolean b1, SteamResult steamResult){
}
@Override
public void onPersonaStateChange(SteamID steamID, PersonaChange personaChange){
}
@Override
public void onGameOverlayActivated(boolean b){
}
@Override
public void onGameLobbyJoinRequested(SteamID steamID, SteamID steamID1){
}
@Override
public void onAvatarImageLoaded(SteamID steamID, int i, int i1, int i2){
}
@Override
public void onFriendRichPresenceUpdate(SteamID steamID, int i){
}
@Override
public void onGameRichPresenceJoinRequested(SteamID steamID, String s){
}
@Override
public void onGameServerChangeRequested(String s, String s1){
}
}

View File

@ -1,4 +1,4 @@
package io.anuke.mindustry.desktopsdl.steam;
package io.anuke.mindustry.desktop.steam;
import com.codedisaster.steamworks.*;
import com.codedisaster.steamworks.SteamMatchmaking.*;
@ -6,28 +6,28 @@ import com.codedisaster.steamworks.SteamNetworking.*;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.desktopsdl.steam.ClientSteam.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*;
import java.io.*;
import java.nio.*;
public class ServerSteam implements ServerProvider, SteamNetworkingCallback, SteamMatchmakingCallback{
public class SteamServerImpl implements ServerProvider, SteamNetworkingCallback, SteamMatchmakingCallback{
private final static int maxLobbyPlayers = 32;
private final PacketSerializer serializer = new PacketSerializer();
private final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024 * 4);
private final SteamNetworking snet = new SteamNetworking(this);
private final SteamMatchmaking smat = new SteamMatchmaking(this);
private final SteamFriendsImpl friends = new SteamFriendsImpl();
private final ServerProvider server;
//buffer for steam API calls
private ByteBuffer steamBuffer = ByteBuffer.allocateDirect(1024 * 128);
//maps steam ID -> valid net connection
private IntMap<SteamConnection> steamConnections = new IntMap<>();
private SteamID currentLobby;
public ServerSteam(ServerProvider server){
public SteamServerImpl(ServerProvider server){
this.server = server;
}
@ -39,26 +39,6 @@ public class ServerSteam implements ServerProvider, SteamNetworkingCallback, Ste
smat.createLobby(LobbyType.values()[Core.settings.getInt("lobbytype", 1)], maxLobbyPlayers);
}
@Override
public void sendStream(int id, Streamable stream){
server.sendStream(id, stream);
}
@Override
public void send(Object object, SendMode mode){
server.send(object, mode);
}
@Override
public void sendTo(int id, Object object, SendMode mode){
server.sendTo(id, object, mode);
}
@Override
public void sendExcept(int id, Object object, SendMode mode){
server.sendExcept(id, object, mode);
}
@Override
public void close(){
server.close();
@ -66,6 +46,9 @@ public class ServerSteam implements ServerProvider, SteamNetworkingCallback, Ste
//TODO kick everyone who is in this lobby?
smat.leaveLobby(currentLobby);
currentLobby = null;
for(SteamConnection con : steamConnections.values()){
con.close();
}
}
}
@ -75,7 +58,7 @@ public class ServerSteam implements ServerProvider, SteamNetworkingCallback, Ste
}
@Override
public Array<? extends NetConnection> getConnections(){
public Iterable<? extends NetConnection> getConnections(){
return server.getConnections();
}
@ -157,4 +140,37 @@ public class ServerSteam implements ServerProvider, SteamNetworkingCallback, Ste
//accept users on request
snet.acceptP2PSessionWithUser(steamIDRemote);
}
public class SteamConnection extends NetConnection{
final SteamID sid;
public SteamConnection(SteamID sid){
super(sid.getAccountID() + "");
this.sid = sid;
}
@Override
public void send(Object object, SendMode mode){
try{
writeBuffer.limit(writeBuffer.capacity());
writeBuffer.position(0);
serializer.write(writeBuffer, object);
writeBuffer.flip();
snet.sendP2PPacket(sid, writeBuffer, mode == SendMode.tcp ? P2PSend.Reliable : P2PSend.UnreliableNoDelay, 0);
}catch(Exception e){
Log.err(e);
Log.info("Error sending packet. Disconnecting invalid client!");
close();
SteamConnection k = steamConnections.get(sid.getAccountID());
if(k != null) steamConnections.remove(sid.getAccountID());
}
}
@Override
public void close(){
snet.closeP2PSessionWithUser(sid);
}
}
}

View File

@ -1,4 +1,4 @@
package io.anuke.mindustry.desktopsdl.steam;
package io.anuke.mindustry.desktop.steam;
import com.codedisaster.steamworks.*;
import com.codedisaster.steamworks.SteamRemoteStorage.*;

2
fastlane/Appfile Normal file
View File

@ -0,0 +1,2 @@
json_key_file(ENV["FASTLANE_KEY_FILE"])
package_name("io.anuke.mindustry")

46
fastlane/Fastfile Normal file
View File

@ -0,0 +1,46 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
#lane :playstore do
# gradle(
# task: 'assemble',
# build_type: 'Release'
# )
# upload_to_play_store # Uploads the APK built in the gradle step above and releases it to all production users
#end
end

View File

@ -0,0 +1,3 @@
- Fixed incorrect attribution in credits; music was made by "A Drop A Day"
- Removed unneeded 3.5 upgrade dialog that sometimes showed up in main menu
- Added information dialog regarding v4 beta

View File

@ -0,0 +1,4 @@
- Fixed incorrect attribution in credits; music was made by "A Drop A Day"
- Removed unneeded 3.5 upgrade dialog that sometimes showed up in main menu
- Added information dialog regarding v4 beta
- Fixed Discord link

View File

@ -0,0 +1 @@
sound.

View File

@ -0,0 +1 @@
everything

View File

@ -0,0 +1 @@
Fixed some minor sound/multiplayer issues

View File

@ -0,0 +1 @@
Bugfixes

View File

@ -0,0 +1 @@
Added ambient sounds for machines.

View File

@ -0,0 +1,2 @@
The first release of version 4.0; an update that has been in the works for over a year.
Extensive changes, including new gamemodes, customizable rules, a new editor, new graphics, new enemies, unit production, new progression, a campaign, and more. See the in-game changelog link for specific details of what has been added and removed over the past year.

View File

@ -0,0 +1,2 @@
Fixed multiplayer not syncing positions and shots correctly on certain networks or situations.
Various other bugfixes.

View File

@ -0,0 +1,5 @@
- Fixed most formatted numbers being missing from Spanish translation
- Fixed wave timer being extremely slow at high TPS with multithreading enabled
- Fixed scrolling on player menu causing the whole screen to pan
- Updated Polish translation
- Updated Russian translation with new text (Thanks to @Prosta4okua)

View File

@ -0,0 +1,14 @@
Create elaborate supply chains of conveyor belts to feed ammo into your turrets, produce materials to use for building, and defend your structures from waves of enemies. Play with your friends in cross-platform multiplayer co-op games, or challenge them in team-based PvP matches.
Feature include:
- 24 built-in maps
- A campaign, complete with a tech tree and unlockable areas
- 4 powerful wave bosses to defeat
- Energy, liquid and item transportation systems
- 19 different types of drones, mechs and ships
- 120+ technology blocks to master
- 75+ different environmental blocks
- Cross-platform multiplayer via local networks or dedicated servers
- Custom game rules: Change block costs, enemy stats, starting items, wave timing and more
- A powerful editor, with tools to randomly generate ores, terrain, decoration and apply symmetry to maps
- Customizable map wave layouts

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

View File

@ -0,0 +1 @@
A factory-based sandbox tower defense game.

View File

@ -0,0 +1 @@
Mindustry

View File

@ -0,0 +1,15 @@
Создавайте сложные логистические сети для переноса боеприпасов для турелей, добывайте ресурсы для строительства, и защищайте их от различных волн врагов.
Играйте с друзьями в кроссплатформенные кооперативные игры, или бросьте им вызов в командных PvP-матчах.
Особенности игры:
- 24 встроенные карты
- Кампания, с полноценным технологическим древом и прогрессией карт
- 4 вида мощных боссов
- Системы для транспорта электроэнергии, жидкостей и ресурсов
- 19 видов дронов, кораблей и наземных боевых единиц
- Более 120 блоков в техногическом древе
- Более 75 видов натуральных блоков
- Кроссплатформенный мультиплеер с поддержкой как и локальных сетей, так и серверов
- Пользовательские настройки игры — изменяйте цену блоков, силу врагов, количество стартовых ресурсов, интервал между волнами и т.д.
- Редактор карт с бесчисленными возможностями, инструментами для случайной генерации руд, рельефа, декораций, а также для симметрии карт
- Настраиваемые раскладки волн для карт

View File

@ -0,0 +1 @@
A factory-based sandbox tower defense game.

View File

@ -0,0 +1 @@
Mindustry

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 KiB

After

Width:  |  Height:  |  Size: 706 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 KiB

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 KiB

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 204 KiB

View File

@ -40,7 +40,7 @@ public class IOSLauncher extends IOSApplication.Delegate{
@Override
public void shareFile(FileHandle file){
Log.info("Attempting to share file " + file);
FileHandle to = Core.files.absolute(getDocumentsDirectory()).child(file.name()/* + ".png"*/);
FileHandle to = Core.files.absolute(getDocumentsDirectory()).child(file.name());
file.copyTo(to);
NSURL url = new NSURL(to.file());
@ -53,7 +53,6 @@ public class IOSLauncher extends IOSApplication.Delegate{
@Override
public void beginForceLandscape(){
Log.info("begin force landscape");
forced = true;
UINavigationController.attemptRotationToDeviceOrientation();
}

View File

@ -700,10 +700,11 @@ public class ServerControl implements ApplicationListener{
run.run();
logic.play();
state.rules = world.getMap().applyRules(lastMode);
for(Player p : players){
p.reset();
if(state.rules.pvp){
p.setTeam(netServer.assignTeam(new ArrayIterable<>(players)));
p.setTeam(netServer.assignTeam(p, new ArrayIterable<>(players)));
}
netServer.sendWorldData(p, p.con.id);
}

View File

@ -1,4 +1,4 @@
include 'desktop', 'desktop-sdl', 'core', 'net', 'server', 'ios', 'annotations', 'tools', 'tests'
include 'desktop', 'core', 'net', 'server', 'ios', 'annotations', 'tools', 'tests'
def use = { String name ->
include(name)