Compare commits

..

44 Commits
v101 ... v101.1

Author SHA1 Message Date
Anuken
f504dd2b5a Minor tweaks 2019-12-17 17:10:29 -05:00
Anuken
26881fbdb9 Added server mod error check 2019-12-17 10:58:43 -05:00
Anuken
8d921199fb Merge remote-tracking branch 'origin/master' 2019-12-16 18:37:18 -05:00
Anuken
4ffe233321 Added repair point range display 2019-12-16 18:37:11 -05:00
Anuken
e076de9dcd Merge pull request #1221 from TsjipTsjip/typoFixes
'arilleryPlastic' -> 'artilleryPlastic'
2019-12-16 13:58:59 -05:00
TsjipTsjip
5ce66b0dbb 'arilleryPlastic' -> 'artilleryPlastic' 2019-12-16 19:55:10 +01:00
Anuken
6971a76c8e І 2019-12-16 12:50:18 -05:00
Anuken
d47f86cc21 Cleanup 2019-12-16 10:12:39 -05:00
Anuken
487607e1d6 Removed references to GWT completely 2019-12-16 00:08:14 -05:00
Anuken
3cefc085bd Tentative RU bundle changes 2019-12-15 22:11:07 -05:00
Anuken
3d8e5bd36a Added new Cyrillic font 2019-12-15 21:31:55 -05:00
Anuken
e9ed0512f7 Removed Path API usage 2019-12-15 21:12:41 -05:00
Anuken
0cf39bf5c3 Eliminated 8 characters 2019-12-15 20:01:06 -05:00
Anuken
95a1474b9a Merge remote-tracking branch 'origin/master' 2019-12-15 19:36:38 -05:00
Anuken
53aedcee2c Cleanup 2019-12-15 19:35:04 -05:00
Anuken
9758a05002 Merge pull request #1218 from joshuaptfan/break-cancel
Prevent right-click from deconstructing when schematic is selected
2019-12-15 18:38:08 -05:00
joshuaptfan
18bb7ba936 Prevent right-click from deconstructing when schematic is selected; allow deconstruct cancel with left-click 2019-12-15 14:26:13 -08:00
Anuken
9f3dcdf727 Fixed #1215 - made all valid floors show up in drill mine list 2019-12-15 16:29:11 -05:00
Anuken
7d2354a653 Mod path resolving fixes 2019-12-15 16:00:50 -05:00
Anuken
ab21b88001 Mod sort fix 2019-12-15 15:22:19 -05:00
Anuken
a560978dcf Sync fixes / Fixed linear filtering resetting on mod load 2019-12-15 14:30:28 -05:00
Anuken
047f479a2f Fixed server 2019-12-15 14:00:07 -05:00
Anuken
d78d3daaf9 Updated script stub 2019-12-15 13:56:01 -05:00
Anuken
976d0f54b3 Merge branches 'master' and 'safe-mod-loading' of https://github.com/Anuken/Mindustry
# Conflicts:
#	core/src/io/anuke/mindustry/mod/Mods.java
2019-12-15 13:55:30 -05:00
Anuken
1c1db3990f Fully implemented safe content loading 2019-12-15 13:54:51 -05:00
Anuken
bcc8f65ac8 More cleanup 2019-12-14 20:49:55 -05:00
Anuken
e043f4bb66 API cleanup 2019-12-14 18:19:02 -05:00
Anuken
9d3dda035c Compile fixes 2019-12-14 12:53:37 -05:00
Anuken
6e16aab794 Merge remote-tracking branch 'origin/master' 2019-12-14 11:05:32 -05:00
Anuken
c1cf3183ac Fixed some Android-related script issues 2019-12-14 11:04:22 -05:00
Anuken
252d0f6aa1 aaaaa 2019-12-14 10:51:36 -05:00
Anuken
1f5a6e1bf8 Compile error fixes 2019-12-14 10:06:09 -05:00
Anuken
58e3143e2a Content cleaning 2019-12-14 09:33:07 -05:00
Anuken
9033ebcfd7 Merge pull request #1019 from Kieaer/patch-4
Add WithdrawEvent
2019-12-13 23:19:19 -05:00
Anuken
1f5e773c77 Fixed double-event firing 2019-12-13 23:15:49 -05:00
Anuken
7b1a0a42d4 Merge pull request #1206 from joshuaptfan/zoom-no-hold
Make scroll zoom by default, remove zoom hold keybind
2019-12-12 18:35:29 -05:00
Anuken
3d78175e50 Merge pull request #1208 from Kieaer/patch-6
Update MessageBlock.java
2019-12-12 18:33:20 -05:00
키에르
9d2133814c Update MessageBlock.java 2019-12-13 07:56:50 +09:00
joshuaptfan
6ce013a1eb Make diagonal placement keybind modify scroll to always zoom 2019-12-12 14:56:32 -08:00
joshuaptfan
5db8520b74 Add rotation checks to mobile zoom 2019-12-12 13:52:05 -08:00
joshuaptfan
736737f151 Make scroll zoom by default, remove zoom hold keybind 2019-12-12 12:23:32 -08:00
키에르
0c430527b8 Fix error 2019-11-09 00:41:31 +09:00
키에르
7871b5bdaa Update BlockInventoryFragment.java 2019-11-09 00:18:41 +09:00
키에르
124480f96b Update EventType.java 2019-11-09 00:17:29 +09:00
131 changed files with 802 additions and 698 deletions

9
.gitignore vendored
View File

@@ -55,15 +55,6 @@ crash-report-*
## Robovm ## Robovm
/ios/robovm-build/ /ios/robovm-build/
## GWT
/html/war/
/html/gwt-unitCache/
.apt_generated/
.gwt/
gwt-unitCache/
www-test/
.gwt-tmp/
## Android Studio and Intellij and Android in general ## Android Studio and Intellij and Android in general
/android/libs/armeabi/ /android/libs/armeabi/
/android/libs/armeabi-v7a/ /android/libs/armeabi-v7a/

View File

@@ -18,7 +18,6 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.game.Saves.*; import io.anuke.mindustry.game.Saves.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.mod.*;
import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.dialogs.*;
import java.io.*; import java.io.*;
@@ -71,11 +70,11 @@ public class AndroidLauncher extends AndroidApplication{
} }
@Override @Override
public void shareFile(FileHandle file){ public void shareFile(Fi file){
} }
@Override @Override
public void showFileChooser(boolean open, String extension, Cons<FileHandle> cons){ public void showFileChooser(boolean open, String extension, Cons<Fi> cons){
if(VERSION.SDK_INT >= VERSION_CODES.Q){ if(VERSION.SDK_INT >= VERSION_CODES.Q){
Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(open ? Intent.ACTION_OPEN_DOCUMENT : Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
@@ -86,7 +85,7 @@ public class AndroidLauncher extends AndroidApplication{
if(uri.getPath().contains("(invalid)")) return; if(uri.getPath().contains("(invalid)")) return;
Core.app.post(() -> Core.app.post(() -> cons.get(new FileHandle(uri.getPath()){ Core.app.post(() -> Core.app.post(() -> cons.get(new Fi(uri.getPath()){
@Override @Override
public InputStream read(){ public InputStream read(){
try{ try{
@@ -144,7 +143,7 @@ public class AndroidLauncher extends AndroidApplication{
useImmersiveMode = true; useImmersiveMode = true;
depth = 0; depth = 0;
hideStatusBar = true; hideStatusBar = true;
errorHandler = ModCrashHandler::handle; //errorHandler = ModCrashHandler::handle;
}}); }});
checkFiles(getIntent()); checkFiles(getIntent());
} }
@@ -186,7 +185,7 @@ public class AndroidLauncher extends AndroidApplication{
Core.app.post(() -> Core.app.post(() -> { Core.app.post(() -> Core.app.post(() -> {
if(save){ //open save if(save){ //open save
System.out.println("Opening save."); System.out.println("Opening save.");
FileHandle file = Core.files.local("temp-save." + saveExtension); Fi file = Core.files.local("temp-save." + saveExtension);
file.write(inStream, false); file.write(inStream, false);
if(SaveIO.isSaveValid(file)){ if(SaveIO.isSaveValid(file)){
try{ try{
@@ -199,7 +198,7 @@ public class AndroidLauncher extends AndroidApplication{
ui.showErrorMessage("$save.import.invalid"); ui.showErrorMessage("$save.import.invalid");
} }
}else if(map){ //open map }else if(map){ //open map
FileHandle file = Core.files.local("temp-map." + mapExtension); Fi file = Core.files.local("temp-map." + mapExtension);
file.write(inStream, false); file.write(inStream, false);
Core.app.post(() -> { Core.app.post(() -> {
System.out.println("Opening map."); System.out.println("Opening map.");

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry;
import android.annotation.*; import android.annotation.*;
import android.os.*; import android.os.*;
import android.os.Build.*;
import com.android.dex.*; import com.android.dex.*;
import com.android.dx.cf.direct.*; import com.android.dx.cf.direct.*;
import com.android.dx.command.dexer.*; import com.android.dx.command.dexer.*;
@@ -177,7 +178,8 @@ public class AndroidRhinoContext{
}catch(IOException e){ }catch(IOException e){
e.printStackTrace(); e.printStackTrace();
} }
return new DexClassLoader(dexFile.getPath(), ((AndroidApplication)Core.app).getContext().getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name); android.content.Context context = ((AndroidApplication)Core.app).getContext();
return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
} }
@Override @Override

Binary file not shown.

View File

@@ -100,8 +100,11 @@ mod.enabled = [lightgray]Enabled
mod.disabled = [scarlet]Disabled mod.disabled = [scarlet]Disabled
mod.disable = Disable mod.disable = Disable
mod.delete.error = Unable to delete mod. File may be in use. mod.delete.error = Unable to delete mod. File may be in use.
mod.requiresversion = [scarlet]Requires game version: [accent]{0} mod.requiresversion = [scarlet]Requires min game version: [accent]{0}
mod.missingdependencies = [scarlet]Missing dependencies: {0} mod.missingdependencies = [scarlet]Missing dependencies: {0}
mod.erroredcontent = [scarlet]Content Errors
mod.errors = Errors have occurred loading content.
mod.noerrorplay = [scarlet]You have mods with errors.[] Either disable the affected mods or fix the errors before playing.
mod.nowdisabled = [scarlet]Mod '{0}' is missing dependencies:[accent] {1}\n[lightgray]These mods need to be downloaded first.\nThis mod will be automatically disabled. mod.nowdisabled = [scarlet]Mod '{0}' is missing dependencies:[accent] {1}\n[lightgray]These mods need to be downloaded first.\nThis mod will be automatically disabled.
mod.enable = Enable mod.enable = Enable
mod.requiresrestart = The game will now close to apply the mod changes. mod.requiresrestart = The game will now close to apply the mod changes.
@@ -705,7 +708,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Deselect keybind.deselect.name = Deselect
keybind.shoot.name = Shoot keybind.shoot.name = Shoot
keybind.zoom_hold.name = Zoom Hold
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause
@@ -1046,7 +1048,7 @@ unit.eradicator.name = Eradicator
unit.lich.name = Lich unit.lich.name = Lich
unit.reaper.name = Reaper unit.reaper.name = Reaper
tutorial.next = [lightgray]<Tap to continue> tutorial.next = [lightgray]<Tap to continue>
tutorial.intro = You have entered the[scarlet] Mindustry Tutorial.[]\nUse [accent][[WASD][] to move.\n[accent]Hold [[Ctrl] while scrolling[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper tutorial.intro = You have entered the[scarlet] Mindustry Tutorial.[]\nUse[accent] [[WASD][] to move.\n[accent]Scroll[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper
tutorial.intro.mobile = You have entered the[scarlet] Mindustry Tutorial.[]\nSwipe the screen to move.\n[accent]Pinch with 2 fingers[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper tutorial.intro.mobile = You have entered the[scarlet] Mindustry Tutorial.[]\nSwipe the screen to move.\n[accent]Pinch with 2 fingers[] to zoom in and out.\nBegin by[accent] mining copper[]. Move close to it, then tap a copper ore vein near your core to do this.\n\n[accent]{0}/{1} copper
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.\nYou can also select the drill by tapping [accent][[2][] then [accent][[1][] quickly, regardless of which tab is open.\n[accent]Right-click[] to stop building. 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.\nYou can also select the drill by tapping [accent][[2][] then [accent][[1][] quickly, regardless of which tab is open.\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.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.

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Odznačit keybind.deselect.name = Odznačit
keybind.shoot.name = Střílet keybind.shoot.name = Střílet
keybind.zoom_hold.name = Přiblížení-podržení
keybind.zoom.name = přiblížení keybind.zoom.name = přiblížení
keybind.menu.name = Hlavní nabídka keybind.menu.name = Hlavní nabídka
keybind.pause.name = pauza keybind.pause.name = pauza

View File

@@ -672,7 +672,6 @@ keybind.pick.name = Block Auswählen
keybind.break_block.name = Block zerstören keybind.break_block.name = Block zerstören
keybind.deselect.name = Auswahl aufheben keybind.deselect.name = Auswahl aufheben
keybind.shoot.name = Schießen keybind.shoot.name = Schießen
keybind.zoom_hold.name = Zoom halten
keybind.zoom.name = Zoomen keybind.zoom.name = Zoomen
keybind.menu.name = Menü keybind.menu.name = Menü
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Destruir Bloque keybind.break_block.name = Destruir Bloque
keybind.deselect.name = Deseleccionar keybind.deselect.name = Deseleccionar
keybind.shoot.name = Disparar keybind.shoot.name = Disparar
keybind.zoom_hold.name = Mantener Zoom
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menú keybind.menu.name = Menú
keybind.pause.name = Pausa keybind.pause.name = Pausa

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Vali blokk
keybind.break_block.name = Hävita blokk keybind.break_block.name = Hävita blokk
keybind.deselect.name = Tühista valik keybind.deselect.name = Tühista valik
keybind.shoot.name = Tulista keybind.shoot.name = Tulista
keybind.zoom_hold.name = Suumimise režiim
keybind.zoom.name = Muuda suumi keybind.zoom.name = Muuda suumi
keybind.menu.name = Menüü keybind.menu.name = Menüü
keybind.pause.name = Paus keybind.pause.name = Paus

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Jaso blokea
keybind.break_block.name = Apurtu blokea keybind.break_block.name = Apurtu blokea
keybind.deselect.name = Deshautatu keybind.deselect.name = Deshautatu
keybind.shoot.name = Tirokatu keybind.shoot.name = Tirokatu
keybind.zoom_hold.name = Zoom mantenduz
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menua keybind.menu.name = Menua
keybind.pause.name = Pausatu keybind.pause.name = Pausatu

View File

@@ -547,7 +547,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Deselect keybind.deselect.name = Deselect
keybind.shoot.name = Shoot keybind.shoot.name = Shoot
keybind.zoom_hold.name = Zoom Hold
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -702,7 +702,6 @@ keybind.pick.name = Choisir un bloc
keybind.break_block.name = Supprimer un bloc keybind.break_block.name = Supprimer un bloc
keybind.deselect.name = Désélectionner keybind.deselect.name = Désélectionner
keybind.shoot.name = Tirer keybind.shoot.name = Tirer
keybind.zoom_hold.name = Maintenir pour zoomer
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Choisir un bloc
keybind.break_block.name = Supprimer un bloc keybind.break_block.name = Supprimer un bloc
keybind.deselect.name = Déselectionner keybind.deselect.name = Déselectionner
keybind.shoot.name = Tirer keybind.shoot.name = Tirer
keybind.zoom_hold.name = Tenir le zoom
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Memilih Blok
keybind.break_block.name = Menghancurkan Blok keybind.break_block.name = Menghancurkan Blok
keybind.deselect.name = Batal Memilih keybind.deselect.name = Batal Memilih
keybind.shoot.name = Menembak keybind.shoot.name = Menembak
keybind.zoom_hold.name = Tahan Mode Zoom
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Jeda keybind.pause.name = Jeda

View File

@@ -699,7 +699,6 @@ keybind.pick.name = Scegli Blocco
keybind.break_block.name = Rompi Blocco keybind.break_block.name = Rompi Blocco
keybind.deselect.name = Deseleziona keybind.deselect.name = Deseleziona
keybind.shoot.name = Spara keybind.shoot.name = Spara
keybind.zoom_hold.name = Attiva Zoom
keybind.zoom.name = Esegui Zoom keybind.zoom.name = Esegui Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pausa keybind.pause.name = Pausa

View File

@@ -652,7 +652,6 @@ keybind.pick.name = ブロックの選択
keybind.break_block.name = ブロックの破壊 keybind.break_block.name = ブロックの破壊
keybind.deselect.name = 選択解除 keybind.deselect.name = 選択解除
keybind.shoot.name = ショット keybind.shoot.name = ショット
keybind.zoom_hold.name = 長押しズーム
keybind.zoom.name = ズーム keybind.zoom.name = ズーム
keybind.menu.name = メニュー keybind.menu.name = メニュー
keybind.pause.name = ポーズ keybind.pause.name = ポーズ

View File

@@ -704,7 +704,6 @@ keybind.pick.name = 블록 선택
keybind.break_block.name = 블록 파괴 keybind.break_block.name = 블록 파괴
keybind.deselect.name = 선택해제 keybind.deselect.name = 선택해제
keybind.shoot.name = 사격 keybind.shoot.name = 사격
keybind.zoom_hold.name = 길게 확대
keybind.zoom.name = 확대 keybind.zoom.name = 확대
keybind.menu.name = 메뉴 keybind.menu.name = 메뉴
keybind.pause.name = 일시중지 keybind.pause.name = 일시중지

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Deselect keybind.deselect.name = Deselect
keybind.shoot.name = Shoot keybind.shoot.name = Shoot
keybind.zoom_hold.name = Zoom Hold
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -653,7 +653,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Deselect keybind.deselect.name = Deselect
keybind.shoot.name = Shoot keybind.shoot.name = Shoot
keybind.zoom_hold.name = Zoom Hold
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -699,7 +699,6 @@ keybind.pick.name = Wybierz Blok
keybind.break_block.name = Zniszcz Blok keybind.break_block.name = Zniszcz Blok
keybind.deselect.name = Odznacz keybind.deselect.name = Odznacz
keybind.shoot.name = Strzelanie keybind.shoot.name = Strzelanie
keybind.zoom_hold.name = Inicjator przybliżania
keybind.zoom.name = Przybliżanie keybind.zoom.name = Przybliżanie
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pauza keybind.pause.name = Pauza

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pegar bloco
keybind.break_block.name = Quebrar bloco keybind.break_block.name = Quebrar bloco
keybind.deselect.name = Deselecionar keybind.deselect.name = Deselecionar
keybind.shoot.name = Atirar keybind.shoot.name = Atirar
keybind.zoom_hold.name = segurar_zoom
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pausar keybind.pause.name = Pausar

View File

@@ -700,7 +700,6 @@ keybind.pick.name = Pegar bloco
keybind.break_block.name = Quebrar bloco keybind.break_block.name = Quebrar bloco
keybind.deselect.name = Deselecionar keybind.deselect.name = Deselecionar
keybind.shoot.name = Atirar keybind.shoot.name = Atirar
keybind.zoom_hold.name = segurar Zoom
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pausar keybind.pause.name = Pausar

View File

@@ -93,7 +93,7 @@ mods.alphainfo = Имейте в виду, что модификации нах
mods.alpha = [accent](Альфа) mods.alpha = [accent](Альфа)
mods = Модификации mods = Модификации
mods.none = [LIGHT_GRAY]Модификации не найдены! mods.none = [LIGHT_GRAY]Модификации не найдены!
mods.guide = Руководство по созданию модификаций mods.guide = Руководство по модам
mods.report = Доложить об ошибке mods.report = Доложить об ошибке
mods.openfolder = Открыть папку с модификациями mods.openfolder = Открыть папку с модификациями
mod.enabled = [lightgray]Включён mod.enabled = [lightgray]Включён
@@ -107,7 +107,7 @@ mod.enable = Вкл.
mod.requiresrestart = Теперь игра закроется, чтобы применить изменения в модификациях. mod.requiresrestart = Теперь игра закроется, чтобы применить изменения в модификациях.
mod.reloadrequired = [scarlet]Необходим перезапуск mod.reloadrequired = [scarlet]Необходим перезапуск
mod.import = Импортировать модификацию mod.import = Импортировать модификацию
mod.import.github = Импортировать модификацию с GitHub mod.import.github = Импортировать мод с GitHub
mod.item.remove = Этот предмет является частью модификации [accent]«{0}»[]. Чтобы удалить его, удалите саму модификацию. mod.item.remove = Этот предмет является частью модификации [accent]«{0}»[]. Чтобы удалить его, удалите саму модификацию.
mod.remove.confirm = Этот мод будет удалён. mod.remove.confirm = Этот мод будет удалён.
mod.author = [LIGHT_GRAY]Автор:[] {0} mod.author = [LIGHT_GRAY]Автор:[] {0}
@@ -315,7 +315,7 @@ waves.invalid = Неверные волны в буфере обмена.
waves.copied = Волны скопированы. waves.copied = Волны скопированы.
waves.none = Враги не были определены.\nОбратите внимание, что пустые волны будут автоматически заменены обычной волной. waves.none = Враги не были определены.\nОбратите внимание, что пустые волны будут автоматически заменены обычной волной.
editor.default = [lightgray]<По умолчанию> editor.default = [lightgray]<По умолчанию>
details = Подробная информация details = Подробности
edit = Редактировать… edit = Редактировать…
editor.name = Название: editor.name = Название:
editor.spawn = Создать боевую единицу editor.spawn = Создать боевую единицу
@@ -704,7 +704,6 @@ keybind.pick.name = Выбрать блок
keybind.break_block.name = Разрушить блок keybind.break_block.name = Разрушить блок
keybind.deselect.name = Снять выделение keybind.deselect.name = Снять выделение
keybind.shoot.name = Выстрел keybind.shoot.name = Выстрел
keybind.zoom_hold.name = Управление масштабом
keybind.zoom.name = Приблизить/Отдалить keybind.zoom.name = Приблизить/Отдалить
keybind.menu.name = Меню keybind.menu.name = Меню
keybind.pause.name = Пауза keybind.pause.name = Пауза

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Deselect keybind.deselect.name = Deselect
keybind.shoot.name = Shoot keybind.shoot.name = Shoot
keybind.zoom_hold.name = Zoom Hold
keybind.zoom.name = Zoom keybind.zoom.name = Zoom
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Pause keybind.pause.name = Pause

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Pick Block
keybind.break_block.name = Break Block keybind.break_block.name = Break Block
keybind.deselect.name = Eldeki yapiyi birak keybind.deselect.name = Eldeki yapiyi birak
keybind.shoot.name = Sik keybind.shoot.name = Sik
keybind.zoom_hold.name = Yaklasma basili tutmasi
keybind.zoom.name = Yaklas keybind.zoom.name = Yaklas
keybind.menu.name = Menu keybind.menu.name = Menu
keybind.pause.name = Durdur keybind.pause.name = Durdur

View File

@@ -652,7 +652,6 @@ keybind.pick.name = Blok Seç
keybind.break_block.name = Blok Kır keybind.break_block.name = Blok Kır
keybind.deselect.name = Seçimleri Kaldır keybind.deselect.name = Seçimleri Kaldır
keybind.shoot.name = Ateş Et keybind.shoot.name = Ateş Et
keybind.zoom_hold.name = Zumu Sabit Tutma
keybind.zoom.name = Zum keybind.zoom.name = Zum
keybind.menu.name = Menü keybind.menu.name = Menü
keybind.pause.name = Durdur keybind.pause.name = Durdur

View File

@@ -696,7 +696,6 @@ keybind.pick.name = Вибрати блок
keybind.break_block.name = Зламати блок keybind.break_block.name = Зламати блок
keybind.deselect.name = Скасувати keybind.deselect.name = Скасувати
keybind.shoot.name = Постріл keybind.shoot.name = Постріл
keybind.zoom_hold.name = Керування масштабом
keybind.zoom.name = Приблизити keybind.zoom.name = Приблизити
keybind.menu.name = Меню keybind.menu.name = Меню
keybind.pause.name = Пауза keybind.pause.name = Пауза

View File

@@ -699,7 +699,6 @@ keybind.pick.name = 选择方块
keybind.break_block.name = 破坏方块 keybind.break_block.name = 破坏方块
keybind.deselect.name = 取消选择 keybind.deselect.name = 取消选择
keybind.shoot.name = 射击 keybind.shoot.name = 射击
keybind.zoom_hold.name = 按住调整缩放
keybind.zoom.name = 缩放 keybind.zoom.name = 缩放
keybind.menu.name = 菜单 keybind.menu.name = 菜单
keybind.pause.name = 暂停 keybind.pause.name = 暂停

View File

@@ -680,7 +680,6 @@ keybind.pick.name = 選擇方塊
keybind.break_block.name = 移除方塊 keybind.break_block.name = 移除方塊
keybind.deselect.name = 取消選取 keybind.deselect.name = 取消選取
keybind.shoot.name = 射擊 keybind.shoot.name = 射擊
keybind.zoom_hold.name = 按住縮放
keybind.zoom.name = 縮放 keybind.zoom.name = 縮放
keybind.menu.name = 主選單 keybind.menu.name = 主選單
keybind.pause.name = 暫停遊戲 keybind.pause.name = 暫停遊戲

Binary file not shown.

View File

@@ -16,6 +16,7 @@ const run = method => new java.lang.Runnable(){run: method}
const boolf = method => new Boolf(){get: method} const boolf = method => new Boolf(){get: method}
const boolp = method => new Boolp(){get: method} const boolp = method => new Boolp(){get: method}
const cons = method => new Cons(){get: method} const cons = method => new Cons(){get: method}
const prov = method => new Prov(){get: method}
const newEffect = (lifetime, renderer) => new Effects.Effect(lifetime, new Effects.EffectRenderer({render: renderer})) const newEffect = (lifetime, renderer) => new Effects.Effect(lifetime, new Effects.EffectRenderer({render: renderer}))
const Calls = Packages.io.anuke.mindustry.gen.Call const Calls = Packages.io.anuke.mindustry.gen.Call
importPackage(Packages.io.anuke.arc) importPackage(Packages.io.anuke.arc)

View File

@@ -123,7 +123,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
for(ApplicationListener listener : modules){ for(ApplicationListener listener : modules){
listener.init(); listener.init();
} }
mods.each(Mod::init); mods.eachClass(Mod::init);
finished = true; finished = true;
Events.fire(new ClientLoadEvent()); Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight()); super.resize(graphics.getWidth(), graphics.getHeight());

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*;
import io.anuke.mindustry.ai.*; import io.anuke.mindustry.ai.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
@@ -124,21 +125,21 @@ public class Vars implements Loadable{
/** whether typing into the console is enabled - developers only */ /** whether typing into the console is enabled - developers only */
public static boolean enableConsole = false; public static boolean enableConsole = false;
/** application data directory, equivalent to {@link io.anuke.arc.Settings#getDataDirectory()} */ /** application data directory, equivalent to {@link io.anuke.arc.Settings#getDataDirectory()} */
public static FileHandle dataDirectory; public static Fi dataDirectory;
/** data subdirectory used for screenshots */ /** data subdirectory used for screenshots */
public static FileHandle screenshotDirectory; public static Fi screenshotDirectory;
/** data subdirectory used for custom mmaps */ /** data subdirectory used for custom mmaps */
public static FileHandle customMapDirectory; public static Fi customMapDirectory;
/** data subdirectory used for custom mmaps */ /** data subdirectory used for custom mmaps */
public static FileHandle mapPreviewDirectory; public static Fi mapPreviewDirectory;
/** tmp subdirectory for map conversion */ /** tmp subdirectory for map conversion */
public static FileHandle tmpDirectory; public static Fi tmpDirectory;
/** data subdirectory used for saves */ /** data subdirectory used for saves */
public static FileHandle saveDirectory; public static Fi saveDirectory;
/** data subdirectory used for mods */ /** data subdirectory used for mods */
public static FileHandle modDirectory; public static Fi modDirectory;
/** data subdirectory used for schematics */ /** data subdirectory used for schematics */
public static FileHandle schematicDirectory; public static Fi schematicDirectory;
/** map file extension */ /** map file extension */
public static final String mapExtension = "msav"; public static final String mapExtension = "msav";
/** save file extension */ /** save file extension */
@@ -195,6 +196,7 @@ public class Vars implements Loadable{
public static void init(){ public static void init(){
Serialization.init(); Serialization.init();
DefaultSerializers.typeMappings.put("io.anuke.mindustry.type.ContentType", "io.anuke.mindustry.ctype.ContentType");
if(loadLocales){ if(loadLocales){
//load locales //load locales
@@ -315,7 +317,7 @@ public class Vars implements Loadable{
try{ try{
//try loading external bundle //try loading external bundle
FileHandle handle = Core.files.local("bundle"); Fi handle = Core.files.local("bundle");
Locale locale = Locale.ENGLISH; Locale locale = Locale.ENGLISH;
Core.bundle = I18NBundle.createBundle(handle, locale); Core.bundle = I18NBundle.createBundle(handle, locale);
@@ -328,7 +330,7 @@ public class Vars implements Loadable{
}catch(Throwable e){ }catch(Throwable e){
//no external bundle found //no external bundle found
FileHandle handle = Core.files.internal("bundles/bundle"); Fi handle = Core.files.internal("bundles/bundle");
Locale locale; Locale locale;
String loc = Core.settings.getString("locale"); String loc = Core.settings.getString("locale");
if(loc.equals("default")){ if(loc.equals("default")){

View File

@@ -1542,7 +1542,7 @@ public class Blocks implements ContentList{
Items.silicon, Bullets.artilleryHoming, Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary, Items.pyratite, Bullets.artilleryIncendiary,
Items.blastCompound, Bullets.artilleryExplosive, Items.blastCompound, Bullets.artilleryExplosive,
Items.plastanium, Bullets.arilleryPlastic Items.plastanium, Bullets.artilleryPlastic
); );
size = 3; size = 3;
shots = 4; shots = 4;

View File

@@ -18,7 +18,7 @@ public class Bullets implements ContentList{
public static BulletType public static BulletType
//artillery //artillery
artilleryDense, arilleryPlastic, artilleryPlasticFrag, artilleryHoming, artilleryIncendiary, artilleryExplosive, artilleryUnit, artilleryDense, artilleryPlastic, artilleryPlasticFrag, artilleryHoming, artilleryIncendiary, artilleryExplosive, artilleryUnit,
//flak //flak
flakScrap, flakLead, flakPlastic, flakExplosive, flakSurge, flakGlass, glassFrag, flakScrap, flakLead, flakPlastic, flakExplosive, flakSurge, flakGlass, glassFrag,
@@ -65,7 +65,7 @@ public class Bullets implements ContentList{
despawnEffect = Fx.none; despawnEffect = Fx.none;
}}; }};
arilleryPlastic = new ArtilleryBulletType(3.4f, 0, "shell"){{ artilleryPlastic = new ArtilleryBulletType(3.4f, 0, "shell"){{
hitEffect = Fx.plasticExplosion; hitEffect = Fx.plasticExplosion;
knockback = 1f; knockback = 1f;
lifetime = 55f; lifetime = 55f;

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
@@ -25,6 +26,7 @@ public class ContentLoader{
private Array<Content>[] contentMap = new Array[ContentType.values().length]; private Array<Content>[] contentMap = new Array[ContentType.values().length];
private MappableContent[][] temporaryMapper; private MappableContent[][] temporaryMapper;
private @Nullable LoadedMod currentMod; private @Nullable LoadedMod currentMod;
private @Nullable Content lastAdded;
private ObjectSet<Cons<Content>> initialization = new ObjectSet<>(); private ObjectSet<Cons<Content>> initialization = new ObjectSet<>();
private ContentList[] content = { private ContentList[] content = {
new Fx(), new Fx(),
@@ -114,8 +116,8 @@ public class ContentLoader{
try{ try{
callable.get(content); callable.get(content);
}catch(Throwable e){ }catch(Throwable e){
if(content.mod != null){ if(content.minfo.mod != null){
mods.handleError(new ModLoadException(content, e), content.mod); mods.handleContentError(content, e);
}else{ }else{
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -146,11 +148,27 @@ public class ContentLoader{
//clear all content, currently not used //clear all content, currently not used
} }
/** Get last piece of content created for error-handling purposes. */
public @Nullable Content getLastAdded(){
return lastAdded;
}
/** Remove last content added in case of an exception. */
public void removeLast(){
if(lastAdded != null && contentMap[lastAdded.getContentType().ordinal()].peek() == lastAdded){
contentMap[lastAdded.getContentType().ordinal()].pop();
if(lastAdded instanceof MappableContent){
contentNameMap[lastAdded.getContentType().ordinal()].remove(((MappableContent)lastAdded).name);
}
}
}
public void handleContent(Content content){ public void handleContent(Content content){
this.lastAdded = content;
contentMap[content.getContentType().ordinal()].add(content); contentMap[content.getContentType().ordinal()].add(content);
} }
public void setCurrentMod(LoadedMod mod){ public void setCurrentMod(@Nullable LoadedMod mod){
this.currentMod = mod; this.currentMod = mod;
} }
@@ -163,7 +181,7 @@ public class ContentLoader{
throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')"); throw new IllegalArgumentException("Two content objects cannot have the same name! (issue: '" + content.name + "')");
} }
if(currentMod != null){ if(currentMod != null){
content.mod = currentMod; content.minfo.mod = currentMod;
} }
contentNameMap[content.getContentType().ordinal()].put(content.name, content); contentNameMap[content.getContentType().ordinal()].put(content.name, content);
} }

View File

@@ -7,16 +7,18 @@ import io.anuke.arc.files.*;
/** Handles files in a modded context. */ /** Handles files in a modded context. */
public class FileTree implements FileHandleResolver{ public class FileTree implements FileHandleResolver{
private ObjectMap<String, FileHandle> files = new ObjectMap<>(); private ObjectMap<String, Fi> files = new ObjectMap<>();
public void addFile(String path, FileHandle f){ public void addFile(String path, Fi f){
files.put(path, f); files.put(path, f);
} }
/** Gets an asset file.*/ /** Gets an asset file.*/
public FileHandle get(String path){ public Fi get(String path){
if(files.containsKey(path)){ if(files.containsKey(path)){
return files.get(path); return files.get(path);
}else if(files.containsKey("/" + path)){
return files.get("/" + path);
}else{ }else{
return Core.files.internal(path); return Core.files.internal(path);
} }
@@ -28,7 +30,7 @@ public class FileTree implements FileHandleResolver{
} }
@Override @Override
public FileHandle resolve(String fileName){ public Fi resolve(String fileName){
return get(fileName); return get(fileName);
} }
} }

View File

@@ -11,6 +11,7 @@ import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
@@ -22,7 +23,6 @@ import io.anuke.mindustry.net.Administration.*;
import io.anuke.mindustry.net.Net.*; import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Packets.*; import io.anuke.mindustry.net.Packets.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID; import io.anuke.mindustry.type.TypeID;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.modules.*; import io.anuke.mindustry.world.modules.*;

View File

@@ -219,7 +219,7 @@ public class NetServer implements ApplicationListener{
@Override @Override
public void init(){ public void init(){
mods.each(mod -> mod.registerClientCommands(clientCommands)); mods.eachClass(mod -> mod.registerClientCommands(clientCommands));
} }
private void registerCommands(){ private void registerCommands(){

View File

@@ -35,7 +35,7 @@ public interface Platform{
default void viewListingID(String mapid){} default void viewListingID(String mapid){}
/** Steam: Return external workshop maps to be loaded.*/ /** Steam: Return external workshop maps to be loaded.*/
default Array<FileHandle> getWorkshopContent(Class<? extends Publishable> type){ default Array<Fi> getWorkshopContent(Class<? extends Publishable> type){
return new Array<>(0); return new Array<>(0);
} }
@@ -100,7 +100,7 @@ public interface Platform{
} }
/** Only used for iOS or android: open the share menu for a map or save. */ /** Only used for iOS or android: open the share menu for a map or save. */
default void shareFile(FileHandle file){ default void shareFile(Fi file){
} }
/** /**
@@ -109,7 +109,7 @@ public interface Platform{
* @param open Whether to open or save files * @param open Whether to open or save files
* @param extension File extension to filter * @param extension File extension to filter
*/ */
default void showFileChooser(boolean open, String extension, Cons<FileHandle> cons){ default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
new FileChooser(open ? "$open" : "$save", file -> file.extension().toLowerCase().equals(extension), open, file -> { new FileChooser(open ? "$open" : "$save", file -> file.extension().toLowerCase().equals(extension), open, file -> {
if(!open){ if(!open){
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension)); cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));

View File

@@ -456,7 +456,7 @@ public class Renderer implements ApplicationListener{
buffer.end(); buffer.end();
Pixmap fullPixmap = new Pixmap(w, h, Pixmap.Format.RGBA8888); Pixmap fullPixmap = new Pixmap(w, h, Pixmap.Format.RGBA8888);
BufferUtils.copy(lines, 0, fullPixmap.getPixels(), lines.length); BufferUtils.copy(lines, 0, fullPixmap.getPixels(), lines.length);
FileHandle file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png"); Fi file = screenshotDirectory.child("screenshot-" + Time.millis() + ".png");
PixmapIO.writePNG(file, fullPixmap); PixmapIO.writePNG(file, fullPixmap);
fullPixmap.dispose(); fullPixmap.dispose();
ui.showInfoFade(Core.bundle.format("screenshot", file.toString())); ui.showInfoFade(Core.bundle.format("screenshot", file.toString()));

View File

@@ -138,7 +138,7 @@ public class UI implements ApplicationListener, Loadable{
Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver)); Core.assets.setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver));
Core.assets.setLoader(BitmapFont.class, null, new FreetypeFontLoader(resolver){ Core.assets.setLoader(BitmapFont.class, null, new FreetypeFontLoader(resolver){
@Override @Override
public BitmapFont loadSync(AssetManager manager, String fileName, FileHandle file, FreeTypeFontLoaderParameter parameter){ public BitmapFont loadSync(AssetManager manager, String fileName, Fi file, FreeTypeFontLoaderParameter parameter){
if(fileName.equals("outline")){ if(fileName.equals("outline")){
parameter.fontParameters.borderWidth = Scl.scl(2f); parameter.fontParameters.borderWidth = Scl.scl(2f);
parameter.fontParameters.spaceX -= parameter.fontParameters.borderWidth; parameter.fontParameters.spaceX -= parameter.fontParameters.borderWidth;
@@ -374,6 +374,37 @@ public class UI implements ApplicationListener, Loadable{
}}.show(); }}.show();
} }
public void showExceptions(String text, String... messages){
loadfrag.hide();
new Dialog(""){{
setFillParent(true);
cont.margin(15);
cont.add("$error.title").colspan(2);
cont.row();
cont.addImage().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add(text).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
cont.row();
//cont.pane(p -> {
for(int i = 0; i < messages.length; i += 2){
String btext = messages[i];
String details = messages[i + 1];
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(details).color(Color.lightGray).left()), true);
cont.add(btext).right();
cont.addButton("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().left();
cont.row();
cont.add(col).colspan(2).pad(2);
cont.row();
}
//}).colspan(2);
cont.addButton("$ok", this::hide).size(300, 50).fillX().colspan(2);
}}.show();
}
public void showText(String titleText, String text){ public void showText(String titleText, String text){
showText(titleText, text, Align.center); showText(titleText, text, Align.center);
} }

View File

@@ -7,8 +7,6 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
import java.io.*;
public class Version{ public class Version{
/** Build type. 'official' for official releases; 'custom' or 'bleeding edge' are also used. */ /** Build type. 'official' for official releases; 'custom' or 'bleeding edge' are also used. */
public static String type; public static String type;
@@ -26,29 +24,25 @@ public class Version{
public static void init(){ public static void init(){
if(!enabled) return; if(!enabled) return;
try{ Fi file = OS.isAndroid || OS.isIos ? Core.files.internal("version.properties") : new Fi("version.properties", FileType.internal);
FileHandle file = OS.isAndroid || OS.isIos ? Core.files.internal("version.properties") : new FileHandle("version.properties", FileType.Internal);
ObjectMap<String, String> map = new ObjectMap<>(); ObjectMap<String, String> map = new ObjectMap<>();
PropertiesUtils.load(map, file.reader()); PropertiesUtils.load(map, file.reader());
type = map.get("type"); type = map.get("type");
number = Integer.parseInt(map.get("number", "4")); number = Integer.parseInt(map.get("number", "4"));
modifier = map.get("modifier"); modifier = map.get("modifier");
if(map.get("build").contains(".")){ if(map.get("build").contains(".")){
String[] split = map.get("build").split("\\."); String[] split = map.get("build").split("\\.");
try{ try{
build = Integer.parseInt(split[0]); build = Integer.parseInt(split[0]);
revision = Integer.parseInt(split[1]); revision = Integer.parseInt(split[1]);
}catch(Throwable e){ }catch(Throwable e){
e.printStackTrace(); e.printStackTrace();
build = -1; build = -1;
}
}else{
build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1;
} }
}catch(IOException e){ }else{
throw new RuntimeException(e); build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1;
} }
} }
} }

View File

@@ -4,16 +4,14 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.type.*;
/** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */ /** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */
public abstract class Content implements Comparable<Content>{ public abstract class Content implements Comparable<Content>{
public final short id; public final short id;
/** The mod that loaded this piece of content. */ /** Info on which mod this content was loaded from. */
public @Nullable LoadedMod mod; public @NonNull ModContentInfo minfo = new ModContentInfo();
/** File that this content was loaded from. */
public @Nullable FileHandle sourceFile;
public Content(){ public Content(){
this.id = (short)Vars.content.getBy(getContentType()).size; this.id = (short)Vars.content.getBy(getContentType()).size;
@@ -37,6 +35,11 @@ public abstract class Content implements Comparable<Content>{
public void load(){ public void load(){
} }
/** @return whether an error ocurred during mod loading. */
public boolean hasErrored(){
return minfo.error != null;
}
@Override @Override
public int compareTo(Content c){ public int compareTo(Content c){
return Integer.compare(id, c.id); return Integer.compare(id, c.id);
@@ -46,4 +49,15 @@ public abstract class Content implements Comparable<Content>{
public String toString(){ public String toString(){
return getContentType().name() + "#" + id; return getContentType().name() + "#" + id;
} }
public static class ModContentInfo{
/** The mod that loaded this piece of content. */
public @Nullable LoadedMod mod;
/** File that this content was loaded from. */
public @Nullable Fi sourceFile;
/** The error that occurred during loading, if applicable. Null if no error occurred. */
public @Nullable String error;
/** Base throwable that caused the error. */
public @Nullable Throwable baseError;
}
} }

View File

@@ -1,4 +1,4 @@
package io.anuke.mindustry.type; package io.anuke.mindustry.ctype;
/** Do not rearrange, ever! */ /** Do not rearrange, ever! */
public enum ContentType{ public enum ContentType{
@@ -13,7 +13,8 @@ public enum ContentType{
effect, effect,
zone, zone,
loadout, loadout,
typeid; typeid,
error;
public static final ContentType[] all = values(); public static final ContentType[] all = values();
} }

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.editor; package io.anuke.mindustry.editor;
import io.anuke.arc.collection.StringMap; import io.anuke.arc.collection.StringMap;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.files.Fi;
import io.anuke.arc.func.Cons; import io.anuke.arc.func.Cons;
import io.anuke.arc.func.Boolf; import io.anuke.arc.func.Boolf;
import io.anuke.arc.graphics.Pixmap; import io.anuke.arc.graphics.Pixmap;
@@ -109,7 +109,7 @@ public class MapEditor{
} }
} }
public Map createMap(FileHandle file){ public Map createMap(Fi file){
return new Map(file, width(), height(), new StringMap(tags), true); return new Map(file, width(), height(), new StringMap(tags), true);
} }

View File

@@ -133,7 +133,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
}else{ }else{
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{
FileHandle result = Core.files.local(editor.getTags().get("name", "unknown") + "." + mapExtension); Fi result = Core.files.local(editor.getTags().get("name", "unknown") + "." + mapExtension);
MapIO.writeMap(result, editor.createMap(result)); MapIO.writeMap(result, editor.createMap(result));
platform.shareFile(result); platform.shareFile(result);
}catch(Exception e){ }catch(Exception e){
@@ -381,7 +381,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
editor.renderer().dispose(); editor.renderer().dispose();
} }
public void beginEditMap(FileHandle file){ public void beginEditMap(Fi file){
ui.loadAnd(() -> { ui.loadAnd(() -> {
try{ try{
shownWithMap = true; shownWithMap = true;

View File

@@ -12,6 +12,7 @@ import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.audio.*;
import io.anuke.arc.math.*; import io.anuke.arc.math.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.Content; import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.effect.*; import io.anuke.mindustry.entities.effect.*;
@@ -137,7 +138,7 @@ public abstract class BulletType extends Content{
} }
for(int i = 0; i < lightining; i++){ for(int i = 0; i < lightining; i++){
Lightning.create(b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength); Lightning.createLighting(Lightning.nextSeed(), b.getTeam(), Pal.surge, damage, b.x, b.y, Mathf.random(360f), lightningLength);
} }
} }
@@ -150,7 +151,7 @@ public abstract class BulletType extends Content{
public void update(Bullet b){ public void update(Bullet b){
if(homingPower > 0.0001f){ if(homingPower > 0.0001f){
TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange); TargetTrait target = Units.closestTarget(b.getTeam(), b.x, b.y, homingRange, e -> !e.isFlying() || collidesAir);
if(target != null){ if(target != null){
b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f)); b.velocity().setAngle(Mathf.slerpDelta(b.velocity().angle(), b.angleTo(target), 0.08f));
} }

View File

@@ -44,7 +44,11 @@ public class Lightning extends TimedEntity implements DrawTrait, TimeTrait{
/** Create a lighting branch at a location. Use Team.none to damage everyone. */ /** Create a lighting branch at a location. Use Team.none to damage everyone. */
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){ public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
Call.createLighting(lastSeed++, team, color, damage, x, y, targetAngle, length); Call.createLighting(nextSeed(), team, color, damage, x, y, targetAngle, length);
}
public static int nextSeed(){
return lastSeed++;
} }
/** Do not invoke! */ /** Do not invoke! */

View File

@@ -9,6 +9,7 @@ import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.units.*; import io.anuke.mindustry.entities.units.*;

View File

@@ -15,6 +15,7 @@ import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.pooling.*; import io.anuke.arc.util.pooling.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -139,9 +139,19 @@ public class EventType{
} }
/** Called when a player withdraws items from a block. Tutorial only.*/ /** Called when the player withdraws items from a block. */
public static class WithdrawEvent{ public static class WithdrawEvent{
public final Tile tile;
public final Player player;
public final Item item;
public final int amount;
public WithdrawEvent(Tile tile, Player player, Item item, int amount){
this.tile = tile;
this.player = player;
this.item = item;
this.amount = amount;
}
} }
/** Called when a player deposits items to a block.*/ /** Called when a player deposits items to a block.*/

View File

@@ -6,7 +6,7 @@ import io.anuke.arc.files.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
@@ -35,8 +35,8 @@ public class GlobalData{
}); });
} }
public void exportData(FileHandle file) throws IOException{ public void exportData(Fi file) throws IOException{
Array<FileHandle> files = new Array<>(); Array<Fi> files = new Array<>();
files.add(Core.settings.getSettingsFile()); files.add(Core.settings.getSettingsFile());
files.addAll(customMapDirectory.list()); files.addAll(customMapDirectory.list());
files.addAll(saveDirectory.list()); files.addAll(saveDirectory.list());
@@ -46,7 +46,7 @@ public class GlobalData{
String base = Core.settings.getDataDirectory().path(); String base = Core.settings.getDataDirectory().path();
try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){ try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){
for(FileHandle add : files){ for(Fi add : files){
if(add.isDirectory()) continue; if(add.isDirectory()) continue;
zos.putNextEntry(new ZipEntry(add.path().substring(base.length()))); zos.putNextEntry(new ZipEntry(add.path().substring(base.length())));
Streams.copyStream(add.read(), zos); Streams.copyStream(add.read(), zos);
@@ -56,12 +56,12 @@ public class GlobalData{
} }
} }
public void importData(FileHandle file){ public void importData(Fi file){
FileHandle dest = Core.files.local("zipdata.zip"); Fi dest = Core.files.local("zipdata.zip");
file.copyTo(dest); file.copyTo(dest);
FileHandle zipped = new ZipFileHandle(dest); Fi zipped = new ZipFi(dest);
FileHandle base = Core.settings.getDataDirectory(); Fi base = Core.settings.getDataDirectory();
if(!zipped.child("settings.bin").exists()){ if(!zipped.child("settings.bin").exists()){
throw new IllegalArgumentException("Not valid save data."); throw new IllegalArgumentException("Not valid save data.");
} }

View File

@@ -27,7 +27,7 @@ public class Saves{
private AsyncExecutor previewExecutor = new AsyncExecutor(1); private AsyncExecutor previewExecutor = new AsyncExecutor(1);
private boolean saving; private boolean saving;
private float time; private float time;
private FileHandle zoneFile; private Fi zoneFile;
private long totalPlaytime; private long totalPlaytime;
private long lastTimestamp; private long lastTimestamp;
@@ -48,7 +48,7 @@ public class Saves{
saves.clear(); saves.clear();
zoneFile = saveDirectory.child("-1.msav"); zoneFile = saveDirectory.child("-1.msav");
for(FileHandle file : saveDirectory.list()){ for(Fi file : saveDirectory.list()){
if(!file.name().contains("backup") && SaveIO.isSaveValid(file)){ if(!file.name().contains("backup") && SaveIO.isSaveValid(file)){
SaveSlot slot = new SaveSlot(file); SaveSlot slot = new SaveSlot(file);
saves.add(slot); saves.add(slot);
@@ -121,7 +121,7 @@ public class Saves{
return slot; return slot;
} }
public SaveSlot importSave(FileHandle file) throws IOException{ public SaveSlot importSave(Fi file) throws IOException{
SaveSlot slot = new SaveSlot(getNextSlotFile()); SaveSlot slot = new SaveSlot(getNextSlotFile());
slot.importFile(file); slot.importFile(file);
slot.setName(file.nameWithoutExtension()); slot.setName(file.nameWithoutExtension());
@@ -136,9 +136,9 @@ public class Saves{
return slot == null || slot.getZone() == null ? null : slot; return slot == null || slot.getZone() == null ? null : slot;
} }
public FileHandle getNextSlotFile(){ public Fi getNextSlotFile(){
int i = 0; int i = 0;
FileHandle file; Fi file;
while((file = saveDirectory.child(i + "." + saveExtension)).exists()){ while((file = saveDirectory.child(i + "." + saveExtension)).exists()){
i ++; i ++;
} }
@@ -151,11 +151,11 @@ public class Saves{
public class SaveSlot{ public class SaveSlot{
//public final int index; //public final int index;
public final FileHandle file; public final Fi file;
boolean requestedPreview; boolean requestedPreview;
SaveMeta meta; SaveMeta meta;
public SaveSlot(FileHandle file){ public SaveSlot(Fi file){
this.file = file; this.file = file;
} }
@@ -216,11 +216,11 @@ public class Saves{
return file.nameWithoutExtension(); return file.nameWithoutExtension();
} }
private FileHandle previewFile(){ private Fi previewFile(){
return mapPreviewDirectory.child("save_slot_" + index() + ".png"); return mapPreviewDirectory.child("save_slot_" + index() + ".png");
} }
private FileHandle loadPreviewFile(){ private Fi loadPreviewFile(){
return previewFile().sibling(previewFile().name() + ".spreview"); return previewFile().sibling(previewFile().name() + ".spreview");
} }
@@ -293,7 +293,7 @@ public class Saves{
Core.settings.save(); Core.settings.save();
} }
public void importFile(FileHandle from) throws IOException{ public void importFile(Fi from) throws IOException{
try{ try{
from.copyTo(file); from.copyTo(file);
}catch(Exception e){ }catch(Exception e){
@@ -301,7 +301,7 @@ public class Saves{
} }
} }
public void exportFile(FileHandle to) throws IOException{ public void exportFile(Fi to) throws IOException{
try{ try{
file.copyTo(to); file.copyTo(to);
}catch(Exception e){ }catch(Exception e){

View File

@@ -16,7 +16,8 @@ public class Schematic implements Publishable, Comparable<Schematic>{
public final Array<Stile> tiles; public final Array<Stile> tiles;
public StringMap tags; public StringMap tags;
public int width, height; public int width, height;
public @Nullable FileHandle file; public @Nullable
Fi file;
/** Associated mod. If null, no mod is associated with this schematic. */ /** Associated mod. If null, no mod is associated with this schematic. */
public @Nullable LoadedMod mod; public @Nullable LoadedMod mod;
@@ -94,15 +95,15 @@ public class Schematic implements Publishable, Comparable<Schematic>{
} }
@Override @Override
public FileHandle createSteamFolder(String id){ public Fi createSteamFolder(String id){
FileHandle directory = tmpDirectory.child("schematic_" + id).child("schematic." + schematicExtension); Fi directory = tmpDirectory.child("schematic_" + id).child("schematic." + schematicExtension);
file.copyTo(directory); file.copyTo(directory);
return directory; return directory;
} }
@Override @Override
public FileHandle createSteamPreview(String id){ public Fi createSteamPreview(String id){
FileHandle preview = tmpDirectory.child("schematic_preview_" + id + ".png"); Fi preview = tmpDirectory.child("schematic_preview_" + id + ".png");
schematics.savePreview(this, preview); schematics.savePreview(this, preview);
return preview; return preview;
} }

View File

@@ -13,12 +13,12 @@ import io.anuke.arc.util.io.Streams.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.BuilderTrait.*; import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*; import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*; import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.Placement.*; import io.anuke.mindustry.input.Placement.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;
import io.anuke.mindustry.world.blocks.production.*; import io.anuke.mindustry.world.blocks.production.*;
@@ -69,7 +69,7 @@ public class Schematics implements Loadable{
public void load(){ public void load(){
all.clear(); all.clear();
for(FileHandle file : schematicDirectory.list()){ for(Fi file : schematicDirectory.list()){
loadFile(file); loadFile(file);
} }
@@ -111,7 +111,7 @@ public class Schematics implements Loadable{
} }
} }
private @Nullable Schematic loadFile(FileHandle file){ private @Nullable Schematic loadFile(Fi file){
if(!file.extension().equals(schematicExtension)) return null; if(!file.extension().equals(schematicExtension)) return null;
try{ try{
@@ -144,7 +144,7 @@ public class Schematics implements Loadable{
} }
} }
public void savePreview(Schematic schematic, FileHandle file){ public void savePreview(Schematic schematic, Fi file){
FrameBuffer buffer = getBuffer(schematic); FrameBuffer buffer = getBuffer(schematic);
Draw.flush(); Draw.flush();
buffer.begin(); buffer.begin();
@@ -272,7 +272,7 @@ public class Schematics implements Loadable{
public void add(Schematic schematic){ public void add(Schematic schematic){
all.add(schematic); all.add(schematic);
try{ try{
FileHandle file = schematicDirectory.child(Time.millis() + "." + schematicExtension); Fi file = schematicDirectory.child(Time.millis() + "." + schematicExtension);
write(schematic, file); write(schematic, file);
schematic.file = file; schematic.file = file;
}catch(Exception e){ }catch(Exception e){
@@ -372,7 +372,7 @@ public class Schematics implements Loadable{
return read(new ByteArrayInputStream(Base64Coder.decode(schematic))); return read(new ByteArrayInputStream(Base64Coder.decode(schematic)));
} }
public static Schematic read(FileHandle file) throws IOException{ public static Schematic read(Fi file) throws IOException{
Schematic s = read(new DataInputStream(file.read(1024))); Schematic s = read(new DataInputStream(file.read(1024)));
if(!s.tags.containsKey("name")){ if(!s.tags.containsKey("name")){
s.tags.put("name", file.nameWithoutExtension()); s.tags.put("name", file.nameWithoutExtension());
@@ -425,7 +425,7 @@ public class Schematics implements Loadable{
} }
} }
public static void write(Schematic schematic, FileHandle file) throws IOException{ public static void write(Schematic schematic, Fi file) throws IOException{
write(schematic, file.write(false, 1024)); write(schematic, file.write(false, 1024));
} }

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.util.serialization.Json;
import io.anuke.arc.util.serialization.Json.Serializable; import io.anuke.arc.util.serialization.Json.Serializable;
import io.anuke.arc.util.serialization.JsonValue; import io.anuke.arc.util.serialization.JsonValue;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.BaseUnit; import io.anuke.mindustry.entities.type.BaseUnit;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;

View File

@@ -40,8 +40,7 @@ public enum Binding implements KeyBind{
block_select_08(KeyCode.NUM_8), block_select_08(KeyCode.NUM_8),
block_select_09(KeyCode.NUM_9), block_select_09(KeyCode.NUM_9),
block_select_10(KeyCode.NUM_0), block_select_10(KeyCode.NUM_0),
zoom_hold(KeyCode.CONTROL_LEFT, "view"), zoom(new Axis(KeyCode.SCROLL), "view"),
zoom(new Axis(KeyCode.SCROLL)),
menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE), menu(Core.app.getType() == ApplicationType.Android ? KeyCode.BACK : KeyCode.ESCAPE),
fullscreen(KeyCode.F11), fullscreen(KeyCode.F11),
pause(KeyCode.SPACE), pause(KeyCode.SPACE),

View File

@@ -149,8 +149,8 @@ public class DesktopInput extends InputHandler{
if(state.is(State.menu) || Core.scene.hasDialog()) return; if(state.is(State.menu) || Core.scene.hasDialog()) return;
//zoom things //zoom camera
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && Core.input.keyDown(Binding.zoom_hold)){ if(!Core.scene.hasScroll() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom)); renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
} }
@@ -166,11 +166,6 @@ public class DesktopInput extends InputHandler{
mode = none; mode = none;
} }
if(mode != none || isPlacing()){
selectRequests.clear();
lastSchematic = null;
}
if(player.isShooting && !canShoot()){ if(player.isShooting && !canShoot()){
player.isShooting = false; player.isShooting = false;
} }
@@ -182,8 +177,7 @@ public class DesktopInput extends InputHandler{
selectScale = 0f; selectScale = 0f;
} }
if(!Core.input.keyDown(Binding.zoom_hold) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){ if(!Core.input.keyDown(Binding.diagonal_placement) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){
rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4); rotation = Mathf.mod(rotation + (int)Core.input.axisTap(Binding.rotate), 4);
if(sreq != null){ if(sreq != null){
@@ -355,7 +349,9 @@ public class DesktopInput extends InputHandler{
if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){ if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){
BuildRequest req = getRequest(cursorX, cursorY); BuildRequest req = getRequest(cursorX, cursorY);
if(!selectRequests.isEmpty()){ if(Core.input.keyDown(Binding.break_block)){
mode = none;
}else if(!selectRequests.isEmpty()){
flushRequests(selectRequests); flushRequests(selectRequests);
}else if(isPlacing()){ }else if(isPlacing()){
selectX = cursorX; selectX = cursorX;
@@ -377,9 +373,12 @@ public class DesktopInput extends InputHandler{
}else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine }else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine
player.isShooting = true; player.isShooting = true;
} }
}else if(Core.input.keyTap(Binding.deselect) && block != null){ }else if(Core.input.keyTap(Binding.deselect) && isPlacing()){
block = null; block = null;
mode = none; mode = none;
}else if(Core.input.keyTap(Binding.deselect) && !selectRequests.isEmpty()){
selectRequests.clear();
lastSchematic = null;
}else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse()){ }else if(Core.input.keyTap(Binding.break_block) && !Core.scene.hasMouse()){
//is recalculated because setting the mode to breaking removes potential multiblock cursor offset //is recalculated because setting the mode to breaking removes potential multiblock cursor offset
deleting = false; deleting = false;

View File

@@ -587,8 +587,8 @@ public class MobileInput extends InputHandler implements GestureListener{
mode = none; mode = none;
} }
//zoom things //zoom camera
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && (Core.input.keyDown(Binding.zoom_hold))){ if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom)); renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
} }

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;

View File

@@ -6,10 +6,10 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.MapIO.*; import io.anuke.mindustry.io.MapIO.*;
import io.anuke.mindustry.maps.*; import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.LegacyColorMapper.*; import io.anuke.mindustry.world.LegacyColorMapper.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;
@@ -26,7 +26,7 @@ public class LegacyMapIO{
private static final Json json = new Json(); private static final Json json = new Json();
/* Convert a map from the old format to the new format. */ /* Convert a map from the old format to the new format. */
public static void convertMap(FileHandle in, FileHandle out) throws IOException{ public static void convertMap(Fi in, Fi out) throws IOException{
Map map = readMap(in, true); Map map = readMap(in, true);
String waves = map.tags.get("waves", "[]"); String waves = map.tags.get("waves", "[]");
@@ -45,7 +45,7 @@ public class LegacyMapIO{
MapIO.writeMap(out, map); MapIO.writeMap(out, map);
} }
public static Map readMap(FileHandle file, boolean custom) throws IOException{ public static Map readMap(Fi file, boolean custom) throws IOException{
try(DataInputStream stream = new DataInputStream(file.read(1024))){ try(DataInputStream stream = new DataInputStream(file.read(1024))){
StringMap tags = new StringMap(); StringMap tags = new StringMap();
@@ -76,11 +76,11 @@ public class LegacyMapIO{
readTiles(map.file, map.width, map.height, tiles); readTiles(map.file, map.width, map.height, tiles);
} }
private static void readTiles(FileHandle file, int width, int height, Tile[][] tiles) throws IOException{ private static void readTiles(Fi file, int width, int height, Tile[][] tiles) throws IOException{
readTiles(file, width, height, (x, y) -> tiles[x][y]); readTiles(file, width, height, (x, y) -> tiles[x][y]);
} }
private static void readTiles(FileHandle file, int width, int height, TileProvider tiles) throws IOException{ private static void readTiles(Fi file, int width, int height, TileProvider tiles) throws IOException{
try(BufferedInputStream input = file.read(bufferSize)){ try(BufferedInputStream input = file.read(bufferSize)){
//read map //read map

View File

@@ -22,7 +22,7 @@ import static io.anuke.mindustry.Vars.*;
public class MapIO{ public class MapIO{
private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; private static final int[] pngHeader = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
public static boolean isImage(FileHandle file){ public static boolean isImage(Fi file){
try(InputStream stream = file.read(32)){ try(InputStream stream = file.read(32)){
for(int i1 : pngHeader){ for(int i1 : pngHeader){
if(stream.read() != i1){ if(stream.read() != i1){
@@ -35,7 +35,7 @@ public class MapIO{
} }
} }
public static Map createMap(FileHandle file, boolean custom) throws IOException{ public static Map createMap(Fi file, boolean custom) throws IOException{
try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){ try(InputStream is = new InflaterInputStream(file.read(bufferSize)); CounterInputStream counter = new CounterInputStream(is); DataInputStream stream = new DataInputStream(counter)){
SaveIO.readHeader(stream); SaveIO.readHeader(stream);
int version = stream.readInt(); int version = stream.readInt();
@@ -46,7 +46,7 @@ public class MapIO{
} }
} }
public static void writeMap(FileHandle file, Map map) throws IOException{ public static void writeMap(Fi file, Map map) throws IOException{
try{ try{
SaveIO.write(file, map.tags); SaveIO.write(file, map.tags);
}catch(Exception e){ }catch(Exception e){

View File

@@ -1,7 +1,7 @@
package io.anuke.mindustry.io; package io.anuke.mindustry.io;
import io.anuke.arc.collection.*; import io.anuke.arc.collection.*;
import io.anuke.arc.files.FileHandle; import io.anuke.arc.files.Fi;
import io.anuke.arc.util.io.CounterInputStream; import io.anuke.arc.util.io.CounterInputStream;
import io.anuke.arc.util.io.FastDeflaterOutputStream; import io.anuke.arc.util.io.FastDeflaterOutputStream;
import io.anuke.mindustry.Vars; import io.anuke.mindustry.Vars;
@@ -34,7 +34,7 @@ public class SaveIO{
return versions.get(version); return versions.get(version);
} }
public static void save(FileHandle file){ public static void save(Fi file){
boolean exists = file.exists(); boolean exists = file.exists();
if(exists) file.moveTo(backupFileFor(file)); if(exists) file.moveTo(backupFileFor(file));
try{ try{
@@ -45,15 +45,15 @@ public class SaveIO{
} }
} }
public static DataInputStream getStream(FileHandle file){ public static DataInputStream getStream(Fi file){
return new DataInputStream(new InflaterInputStream(file.read(bufferSize))); return new DataInputStream(new InflaterInputStream(file.read(bufferSize)));
} }
public static DataInputStream getBackupStream(FileHandle file){ public static DataInputStream getBackupStream(Fi file){
return new DataInputStream(new InflaterInputStream(backupFileFor(file).read(bufferSize))); return new DataInputStream(new InflaterInputStream(backupFileFor(file).read(bufferSize)));
} }
public static boolean isSaveValid(FileHandle file){ public static boolean isSaveValid(Fi file){
try{ try{
return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize)))); return isSaveValid(new DataInputStream(new InflaterInputStream(file.read(bufferSize))));
}catch(Exception e){ }catch(Exception e){
@@ -71,7 +71,7 @@ public class SaveIO{
} }
} }
public static SaveMeta getMeta(FileHandle file){ public static SaveMeta getMeta(Fi file){
try{ try{
return getMeta(getStream(file)); return getMeta(getStream(file));
}catch(Exception e){ }catch(Exception e){
@@ -92,19 +92,19 @@ public class SaveIO{
} }
} }
public static FileHandle fileFor(int slot){ public static Fi fileFor(int slot){
return saveDirectory.child(slot + "." + Vars.saveExtension); return saveDirectory.child(slot + "." + Vars.saveExtension);
} }
public static FileHandle backupFileFor(FileHandle file){ public static Fi backupFileFor(Fi file){
return file.sibling(file.name() + "-backup." + file.extension()); return file.sibling(file.name() + "-backup." + file.extension());
} }
public static void write(FileHandle file, StringMap tags){ public static void write(Fi file, StringMap tags){
write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags); write(new FastDeflaterOutputStream(file.write(false, bufferSize)), tags);
} }
public static void write(FileHandle file){ public static void write(Fi file){
write(file, null); write(file, null);
} }
@@ -122,17 +122,17 @@ public class SaveIO{
} }
} }
public static void load(FileHandle file) throws SaveException{ public static void load(Fi file) throws SaveException{
load(file, world.context); load(file, world.context);
} }
public static void load(FileHandle file, WorldContext context) throws SaveException{ public static void load(Fi file, WorldContext context) throws SaveException{
try{ try{
//try and load; if any exception at all occurs //try and load; if any exception at all occurs
load(new InflaterInputStream(file.read(bufferSize)), context); load(new InflaterInputStream(file.read(bufferSize)), context);
}catch(SaveException e){ }catch(SaveException e){
e.printStackTrace(); e.printStackTrace();
FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); Fi backup = file.sibling(file.name() + "-backup." + file.extension());
if(backup.exists()){ if(backup.exists()){
load(new InflaterInputStream(backup.read(bufferSize)), context); load(new InflaterInputStream(backup.read(bufferSize)), context);
}else{ }else{

View File

@@ -12,7 +12,7 @@ public class SavePreviewLoader extends TextureLoader{
} }
@Override @Override
public void loadAsync(AssetManager manager, String fileName, FileHandle file, TextureParameter parameter){ public void loadAsync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
try{ try{
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter); super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
}catch(Exception e){ }catch(Exception e){

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.util.io.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -3,6 +3,7 @@ package io.anuke.mindustry.io;
import io.anuke.annotations.Annotations.ReadClass; import io.anuke.annotations.Annotations.ReadClass;
import io.anuke.annotations.Annotations.WriteClass; import io.anuke.annotations.Annotations.WriteClass;
import io.anuke.arc.graphics.Color; import io.anuke.arc.graphics.Color;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.Effects; import io.anuke.mindustry.entities.Effects;
import io.anuke.mindustry.entities.Effects.Effect; import io.anuke.mindustry.entities.Effects.Effect;
import io.anuke.mindustry.entities.type.Bullet; import io.anuke.mindustry.entities.type.Bullet;

View File

@@ -1,8 +1,8 @@
package io.anuke.mindustry.io.versions; package io.anuke.mindustry.io.versions;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
import io.anuke.mindustry.io.*; import io.anuke.mindustry.io.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.type.TypeID; import io.anuke.mindustry.type.TypeID;
import java.io.*; import java.io.*;

View File

@@ -22,7 +22,7 @@ public class Map implements Comparable<Map>, Publishable{
/** Metadata. Author description, display name, etc. */ /** Metadata. Author description, display name, etc. */
public final StringMap tags; public final StringMap tags;
/** Base file of this map. File can be named anything at all. */ /** Base file of this map. File can be named anything at all. */
public final FileHandle file; public final Fi file;
/** Format version. */ /** Format version. */
public final int version; public final int version;
/** Whether this map is managed, e.g. downloaded from the Steam workshop.*/ /** Whether this map is managed, e.g. downloaded from the Steam workshop.*/
@@ -40,7 +40,7 @@ public class Map implements Comparable<Map>, Publishable{
/** Associated mod. If null, no mod is associated. */ /** Associated mod. If null, no mod is associated. */
public @Nullable LoadedMod mod; public @Nullable LoadedMod mod;
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version, int build){ public Map(Fi file, int width, int height, StringMap tags, boolean custom, int version, int build){
this.custom = custom; this.custom = custom;
this.tags = tags; this.tags = tags;
this.file = file; this.file = file;
@@ -50,11 +50,11 @@ public class Map implements Comparable<Map>, Publishable{
this.build = build; this.build = build;
} }
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom, int version){ public Map(Fi file, int width, int height, StringMap tags, boolean custom, int version){
this(file, width, height, tags, custom, version, -1); this(file, width, height, tags, custom, version, -1);
} }
public Map(FileHandle file, int width, int height, StringMap tags, boolean custom){ public Map(Fi file, int width, int height, StringMap tags, boolean custom){
this(file, width, height, tags, custom, -1); this(file, width, height, tags, custom, -1);
} }
@@ -70,11 +70,11 @@ public class Map implements Comparable<Map>, Publishable{
return texture == null ? Core.assets.get("sprites/error.png") : texture; return texture == null ? Core.assets.get("sprites/error.png") : texture;
} }
public FileHandle previewFile(){ public Fi previewFile(){
return Vars.mapPreviewDirectory.child((workshop ? file.parent().name() : file.nameWithoutExtension()) + ".png"); return Vars.mapPreviewDirectory.child((workshop ? file.parent().name() : file.nameWithoutExtension()) + ".png");
} }
public FileHandle cacheFile(){ public Fi cacheFile(){
return Vars.mapPreviewDirectory.child(workshop ? file.parent().name() + "-workshop-cache.dat" : file.nameWithoutExtension() + "-cache.dat"); return Vars.mapPreviewDirectory.child(workshop ? file.parent().name() + "-workshop-cache.dat" : file.nameWithoutExtension() + "-cache.dat");
} }
@@ -184,14 +184,14 @@ public class Map implements Comparable<Map>, Publishable{
} }
@Override @Override
public FileHandle createSteamFolder(String id){ public Fi createSteamFolder(String id){
FileHandle mapFile = tmpDirectory.child("map_" + id).child("map.msav"); Fi mapFile = tmpDirectory.child("map_" + id).child("map.msav");
file.copyTo(mapFile); file.copyTo(mapFile);
return mapFile.parent(); return mapFile.parent();
} }
@Override @Override
public FileHandle createSteamPreview(String id){ public Fi createSteamPreview(String id){
return previewFile(); return previewFile();
} }

View File

@@ -17,7 +17,7 @@ public class MapPreviewLoader extends TextureLoader{
} }
@Override @Override
public void loadAsync(AssetManager manager, String fileName, FileHandle file, TextureParameter parameter){ public void loadAsync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
try{ try{
super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter); super.loadAsync(manager, fileName, file.sibling(file.nameWithoutExtension()), parameter);
}catch(Exception e){ }catch(Exception e){
@@ -28,7 +28,7 @@ public class MapPreviewLoader extends TextureLoader{
} }
@Override @Override
public Texture loadSync(AssetManager manager, String fileName, FileHandle file, TextureParameter parameter){ public Texture loadSync(AssetManager manager, String fileName, Fi file, TextureParameter parameter){
try{ try{
return super.loadSync(manager, fileName, file, parameter); return super.loadSync(manager, fileName, file, parameter);
}catch(Throwable e){ }catch(Throwable e){
@@ -43,7 +43,7 @@ public class MapPreviewLoader extends TextureLoader{
} }
@Override @Override
public Array<AssetDescriptor> getDependencies(String fileName, FileHandle file, TextureParameter parameter){ public Array<AssetDescriptor> getDependencies(String fileName, Fi file, TextureParameter parameter){
return Array.with(new AssetDescriptor<>("contentcreate", Content.class)); return Array.with(new AssetDescriptor<>("contentcreate", Content.class));
} }

View File

@@ -105,7 +105,7 @@ public class Maps{
* Does not add this map to the map list. * Does not add this map to the map list.
*/ */
public Map loadInternalMap(String name){ public Map loadInternalMap(String name){
FileHandle file = tree.get("maps/" + name + "." + mapExtension); Fi file = tree.get("maps/" + name + "." + mapExtension);
try{ try{
return MapIO.createMap(file, false); return MapIO.createMap(file, false);
@@ -119,7 +119,7 @@ public class Maps{
//defaults; must work //defaults; must work
try{ try{
for(String name : defaultMapNames){ for(String name : defaultMapNames){
FileHandle file = Core.files.internal("maps/" + name + "." + mapExtension); Fi file = Core.files.internal("maps/" + name + "." + mapExtension);
loadMap(file, false); loadMap(file, false);
} }
}catch(IOException e){ }catch(IOException e){
@@ -127,7 +127,7 @@ public class Maps{
} }
//custom //custom
for(FileHandle file : customMapDirectory.list()){ for(Fi file : customMapDirectory.list()){
try{ try{
if(file.extension().equalsIgnoreCase(mapExtension)){ if(file.extension().equalsIgnoreCase(mapExtension)){
loadMap(file, true); loadMap(file, true);
@@ -139,7 +139,7 @@ public class Maps{
} }
//workshop //workshop
for(FileHandle file : platform.getWorkshopContent(Map.class)){ for(Fi file : platform.getWorkshopContent(Map.class)){
try{ try{
Map map = loadMap(file, false); Map map = loadMap(file, false);
map.workshop = true; map.workshop = true;
@@ -183,7 +183,7 @@ public class Maps{
StringMap tags = new StringMap(baseTags); StringMap tags = new StringMap(baseTags);
String name = tags.get("name"); String name = tags.get("name");
if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?"); if(name == null) throw new IllegalArgumentException("Can't save a map with no name. How did this happen?");
FileHandle file; Fi file;
//find map with the same exact display name //find map with the same exact display name
Map other = maps.find(m -> m.name().equals(name)); Map other = maps.find(m -> m.name().equals(name));
@@ -244,8 +244,8 @@ public class Maps{
} }
/** Import a map, then save it. This updates all values and stored data necessary. */ /** Import a map, then save it. This updates all values and stored data necessary. */
public void importMap(FileHandle file) throws IOException{ public void importMap(Fi file) throws IOException{
FileHandle dest = findFile(); Fi dest = findFile();
file.copyTo(dest); file.copyTo(dest);
Map map = loadMap(dest, true); Map map = loadMap(dest, true);
@@ -446,7 +446,7 @@ public class Maps{
} }
/** Find a new filename to put a map to. */ /** Find a new filename to put a map to. */
private FileHandle findFile(){ private Fi findFile(){
//find a map name that isn't used. //find a map name that isn't used.
int i = maps.size; int i = maps.size;
while(customMapDirectory.child("map_" + i + "." + mapExtension).exists()){ while(customMapDirectory.child("map_" + i + "." + mapExtension).exists()){
@@ -455,7 +455,7 @@ public class Maps{
return customMapDirectory.child("map_" + i + "." + mapExtension); return customMapDirectory.child("map_" + i + "." + mapExtension);
} }
private Map loadMap(FileHandle file, boolean custom) throws IOException{ private Map loadMap(Fi file, boolean custom) throws IOException{
Map map = MapIO.createMap(file, custom); Map map = MapIO.createMap(file, custom);
if(map.name() == null){ if(map.name() == null){

View File

@@ -13,15 +13,15 @@ import io.anuke.mindustry.ui.dialogs.*;
import io.anuke.mindustry.world.*; import io.anuke.mindustry.world.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;
import static io.anuke.mindustry.Vars.updateEditorOnChange; import static io.anuke.mindustry.Vars.*;
public abstract class FilterOption{ public abstract class FilterOption{
public static final Boolf<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> floorsOnly = b -> (b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> wallsOnly = b -> (!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> floorsOptional = b -> b == Blocks.air || ((b instanceof Floor && !(b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> wallsOptional = b -> b == Blocks.air || ((!b.synthetic() && !(b instanceof Floor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full))); public static final Boolf<Block> wallsOresOptional = b -> b == Blocks.air || (((!b.synthetic() && !(b instanceof Floor)) || (b instanceof OverlayFloor)) && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)));
public static final Boolf<Block> oresOnly = b -> b instanceof OverlayFloor && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full)); public static final Boolf<Block> oresOnly = b -> b instanceof OverlayFloor && !headless && Core.atlas.isFound(b.icon(io.anuke.mindustry.ui.Cicon.full));
public static final Boolf<Block> anyOptional = b -> floorsOnly.get(b) || wallsOnly.get(b) || oresOnly.get(b) || b == Blocks.air; public static final Boolf<Block> anyOptional = b -> floorsOnly.get(b) || wallsOnly.get(b) || oresOnly.get(b) || b == Blocks.air;
public abstract void build(Table table); public abstract void build(Table table);

File diff suppressed because one or more lines are too long

View File

@@ -138,26 +138,20 @@ public class ContentParser{
} }
//try to parse "item/amount" syntax //try to parse "item/amount" syntax
try{ if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){
if(type == ItemStack.class && jsonData.isString() && jsonData.asString().contains("/")){ String[] split = jsonData.asString().split("/");
String[] split = jsonData.asString().split("/");
return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}"); return (T)fromJson(ItemStack.class, "{item: " + split[0] + ", amount: " + split[1] + "}");
}
}catch(Throwable ignored){
} }
//try to parse "liquid/amount" syntax //try to parse "liquid/amount" syntax
try{ if(jsonData.isString() && jsonData.asString().contains("/")){
if(jsonData.isString() && jsonData.asString().contains("/")){ String[] split = jsonData.asString().split("/");
String[] split = jsonData.asString().split("/"); if(type == LiquidStack.class){
if(type == LiquidStack.class){ return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
return (T)fromJson(LiquidStack.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}"); }else if(type == ConsumeLiquid.class){
}else if(type == ConsumeLiquid.class){ return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
return (T)fromJson(ConsumeLiquid.class, "{liquid: " + split[0] + ", amount: " + split[1] + "}");
}
} }
}catch(Throwable ignored){
} }
if(Content.class.isAssignableFrom(type)){ if(Content.class.isAssignableFrom(type)){
@@ -168,7 +162,7 @@ public class ContentParser{
T two = (T)Vars.content.getByName(ctype, jsonData.asString()); T two = (T)Vars.content.getByName(ctype, jsonData.asString());
if(two != null) return two; if(two != null) return two;
throw new IllegalArgumentException("\"" + jsonData.name + "\": No " + ctype + " found with name '" + jsonData.asString() + "'."); throw new IllegalArgumentException("\"" + jsonData.name + "\": No " + ctype + " found with name '" + jsonData.asString() + "'.\nMake sure '" + jsonData.asString() + "' is spelled correctly, and that it really exists!\nThis may also occur because its file failed to parse.");
} }
} }
@@ -258,11 +252,15 @@ public class ContentParser{
if(research[0] != null){ if(research[0] != null){
Block parent = find(ContentType.block, research[0]); Block parent = find(ContentType.block, research[0]);
TechNode baseNode = TechTree.create(parent, block); TechNode baseNode = TechTree.create(parent, block);
LoadedMod cur = currentMod;
postreads.add(() -> { postreads.add(() -> {
currentContent = block;
currentMod = cur;
TechNode parnode = TechTree.all.find(t -> t.block == parent); TechNode parnode = TechTree.all.find(t -> t.block == parent);
if(parnode == null){ if(parnode == null){
throw new ModLoadException("Block '" + parent.name + "' isn't in the tech tree, but '" + block.name + "' requires it to be researched.", block); throw new IllegalArgumentException("Block '" + parent.name + "' isn't in the tech tree, but '" + block.name + "' requires it to be researched.");
} }
if(!parnode.children.contains(baseNode)){ if(!parnode.children.contains(baseNode)){
parnode.children.add(baseNode); parnode.children.add(baseNode);
@@ -304,7 +302,7 @@ public class ContentParser{
if(value.has(key)){ if(value.has(key)){
return value.getString(key); return value.getString(key);
}else{ }else{
throw new IllegalArgumentException((currentContent == null ? "" : currentContent.sourceFile + ": ") + "You are missing a \"" + key + "\". It must be added before the file can be parsed."); throw new IllegalArgumentException("You are missing a \"" + key + "\". It must be added before the file can be parsed.");
} }
} }
@@ -382,13 +380,18 @@ public class ContentParser{
} }
} }
public void finishParsing(){ private void attempt(Runnable run){
try{ try{
reads.each(Runnable::run); run.run();
postreads.each(Runnable::run); }catch(Throwable t){
}catch(Exception e){ //don't overwrite double errors
Vars.mods.handleError(new ModLoadException("Error occurred parsing content: " + currentContent, currentContent, e), currentMod); markError(currentContent, t);
} }
}
public void finishParsing(){
reads.each(this::attempt);
postreads.each(this::attempt);
reads.clear(); reads.clear();
postreads.clear(); postreads.clear();
toBeParsed.clear(); toBeParsed.clear();
@@ -402,7 +405,7 @@ public class ContentParser{
* @param file file that this content is being parsed from * @param file file that this content is being parsed from
* @return the content that was parsed * @return the content that was parsed
*/ */
public Content parse(LoadedMod mod, String name, String json, FileHandle file, ContentType type) throws Exception{ public Content parse(LoadedMod mod, String name, String json, Fi file, ContentType type) throws Exception{
if(contentTypes.isEmpty()){ if(contentTypes.isEmpty()){
init(); init();
} }
@@ -421,14 +424,49 @@ public class ContentParser{
currentMod = mod; currentMod = mod;
boolean located = locate(type, name) != null; boolean located = locate(type, name) != null;
Content c = parsers.get(type).parse(mod.name, name, value); Content c = parsers.get(type).parse(mod.name, name, value);
c.minfo.sourceFile = file;
toBeParsed.add(c); toBeParsed.add(c);
if(!located){ if(!located){
c.sourceFile = file; c.minfo.mod = mod;
c.mod = mod;
} }
return c; return c;
} }
public void markError(Content content, LoadedMod mod, Fi file, Throwable error){
content.minfo.mod = mod;
content.minfo.sourceFile = file;
content.minfo.error = makeError(error, file);
content.minfo.baseError = error;
if(mod != null){
mod.erroredContent.add(content);
}
}
public void markError(Content content, Throwable error){
if(content.minfo != null && !content.hasErrored()){
markError(content, content.minfo.mod, content.minfo.sourceFile, error);
}
}
private String makeError(Throwable t, Fi file){
StringBuilder builder = new StringBuilder();
builder.append("[lightgray]").append("File: ").append(file.name()).append("[]\n\n");
if(t.getMessage() != null && t instanceof JsonParseException){
builder.append("[accent][[JsonParse][] ").append(":\n").append(t.getMessage());
}else{
Array<Throwable> causes = Strings.getCauses(t);
for(Throwable e : causes){
builder.append("[accent][[").append(e.getClass().getSimpleName().replace("Exception", ""))
.append("][] ")
.append(e.getMessage() != null ?
e.getMessage().replace("io.anuke.mindustry.", "").replace("io.anuke.arc.", "") : "").append("\n");
}
}
return builder.toString();
}
private <T extends MappableContent> T locate(ContentType type, String name){ private <T extends MappableContent> T locate(ContentType type, String name){
T first = Vars.content.getByName(type, name); //try vanilla replacement T first = Vars.content.getByName(type, name); //try vanilla replacement
return first != null ? first : Vars.content.getByName(type, currentMod.name + "-" + name); return first != null ? first : Vars.content.getByName(type, currentMod.name + "-" + name);

View File

@@ -6,7 +6,7 @@ import io.anuke.mindustry.*;
public class Mod{ public class Mod{
/** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/ /** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/
public FileHandle getConfig(){ public Fi getConfig(){
return Vars.mods.getConfig(this); return Vars.mods.getConfig(this);
} }

View File

@@ -1,67 +0,0 @@
package io.anuke.mindustry.mod;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.mod.Mods.*;
import io.anuke.mindustry.ui.*;
public class ModCrashHandler{
public static void handle(Throwable t){
Array<Throwable> list = Strings.getCauses(t);
Throwable modCause = list.find(e -> e instanceof ModLoadException);
if(modCause != null && Fonts.outline != null){
String text = "[scarlet][[A fatal crash has occured while loading a mod!][]\n\nReason:[accent] " + modCause.getMessage();
String bottom = "[scarlet]The associated mod has been disabled. Swipe out of the app and launch it again.";
GlyphLayout layout = new GlyphLayout();
Core.atlas = TextureAtlas.blankAtlas();
Colors.put("accent", Pal.accent);
Core.app.addListener(new ApplicationListener(){
@Override
public void update(){
Core.graphics.clear(0.1f, 0.1f, 0.1f, 1f);
float rad = Math.min(Core.graphics.getWidth(), Core.graphics.getHeight()) / 2f / 1.3f;
Draw.color(Color.scarlet, Color.black, Mathf.absin(Core.graphics.getFrameId(), 15f, 0.6f));
Lines.stroke(Scl.scl(40f));
//Lines.poly2(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, 3, rad, 0f);
float cx = Core.graphics.getWidth()/2f, cy = Core.graphics.getHeight()/2f;
for(int i = 0; i < 3; i++){
float angle1 = i * 120f + 90f;
float angle2 = (i + 1) * 120f + 90f;
Tmp.v1.trnsExact(angle1, rad - Lines.getStroke()/2f).add(cx, cy);
Tmp.v2.trnsExact(angle2, rad - Lines.getStroke()/2f).add(cx, cy);
Tmp.v3.trnsExact(angle1, rad + Lines.getStroke()/2f).add(cx, cy);
Tmp.v4.trnsExact(angle2, rad + Lines.getStroke()/2f).add(cx, cy);
Fill.quad(Tmp.v1.x, Tmp.v1.y, Tmp.v2.x, Tmp.v2.y, Tmp.v4.x, Tmp.v4.y, Tmp.v3.x, Tmp.v3.y);
}
Lines.lineAngleCenter(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f - Scl.scl(5f), 90f, rad/3.1f);
Fill.square(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f + rad/2f - Scl.scl(15f), Lines.getStroke()/2f);
Draw.reset();
Fonts.outline.getData().markupEnabled = true;
layout.setText(Fonts.outline, text, Color.white, Core.graphics.getWidth(), Align.left, true);
Fonts.outline.draw(text, Core.graphics.getWidth()/2f - layout.width/2f, Core.graphics.getHeight() - Scl.scl(50f), Core.graphics.getWidth(), Align.left, true);
layout.setText(Fonts.outline, bottom, Color.white, Core.graphics.getWidth(), Align.left, true);
Fonts.outline.draw(bottom, Core.graphics.getWidth()/2f - layout.width/2f, layout.height + Scl.scl(10f), Core.graphics.getWidth(), Align.left, true);
Draw.flush();
}
@Override
public void resize(int width, int height){
Draw.proj().setOrtho(0, 0, width, height);
}
});
}else{
throw new RuntimeException(t);
}
}
}

View File

@@ -9,19 +9,22 @@ import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Texture.*; import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.g2d.TextureAtlas.*; import io.anuke.arc.graphics.g2d.TextureAtlas.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
import io.anuke.arc.util.serialization.*; import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Jval.*; import io.anuke.arc.util.serialization.Jval.*;
import io.anuke.mindustry.core.*; import io.anuke.mindustry.core.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.graphics.MultiPacker.*; import io.anuke.mindustry.graphics.MultiPacker.*;
import io.anuke.mindustry.plugin.*; import io.anuke.mindustry.plugin.*;
import io.anuke.mindustry.type.*; import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@@ -32,52 +35,56 @@ public class Mods implements Loadable{
private Json json = new Json(); private Json json = new Json();
private @Nullable Scripts scripts; private @Nullable Scripts scripts;
private ContentParser parser = new ContentParser(); private ContentParser parser = new ContentParser();
private ObjectMap<String, Array<FileHandle>> bundles = new ObjectMap<>(); private ObjectMap<String, Array<Fi>> bundles = new ObjectMap<>();
private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites"); private ObjectSet<String> specialFolders = ObjectSet.with("bundles", "sprites", "sprites-override");
private int totalSprites; private int totalSprites;
private MultiPacker packer; private MultiPacker packer;
private Array<LoadedMod> loaded = new Array<>(); private Array<LoadedMod> mods = new Array<>();
private Array<LoadedMod> disabled = new Array<>();
private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>(); private ObjectMap<Class<?>, ModMeta> metas = new ObjectMap<>();
private boolean requiresReload; private boolean requiresReload;
public Mods(){
Events.on(ClientLoadEvent.class, e -> Core.app.post(this::checkWarnings));
Events.on(ContentReloadEvent.class, e -> Core.app.post(this::checkWarnings));
}
/** Returns a file named 'config.json' in a special folder for the specified plugin. /** Returns a file named 'config.json' in a special folder for the specified plugin.
* Call this in init(). */ * Call this in init(). */
public FileHandle getConfig(Mod mod){ public Fi getConfig(Mod mod){
ModMeta load = metas.get(mod.getClass()); ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!"); if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json"); return modDirectory.child(load.name).child("config.json");
} }
/** Returns a list of files per mod subdirectory. */ /** Returns a list of files per mod subdirectory. */
public void listFiles(String directory, Cons2<LoadedMod, FileHandle> cons){ public void listFiles(String directory, Cons2<LoadedMod, Fi> cons){
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
FileHandle file = mod.root.child(directory); Fi file = mod.root.child(directory);
if(file.exists()){ if(file.exists()){
for(FileHandle child : file.list()){ for(Fi child : file.list()){
cons.get(mod, child); cons.get(mod, child);
} }
} }
} });
} }
/** @return the loaded mod found by class, or null if not found. */ /** @return the loaded mod found by class, or null if not found. */
public @Nullable LoadedMod getMod(Class<? extends Mod> type){ public @Nullable LoadedMod getMod(Class<? extends Mod> type){
return loaded.find(l -> l.mod != null && l.mod.getClass() == type); return mods.find(m -> m.enabled() && m.main != null && m.main.getClass() == type);//loaded.find(l -> l.mod != null && l.mod.getClass() == type);
} }
/** Imports an external mod file.*/ /** Imports an external mod file.*/
public void importMod(FileHandle file) throws IOException{ public void importMod(Fi file) throws IOException{
FileHandle dest = modDirectory.child(file.name()); Fi dest = modDirectory.child(file.name());
if(dest.exists()){ if(dest.exists()){
throw new IOException("A mod with the same filename already exists!"); throw new IOException("A mod with the same filename already exists!");
} }
file.copyTo(dest); file.copyTo(dest);
try{ try{
loaded.add(loadMod(dest)); mods.add(loadMod(dest));
requiresReload = true; requiresReload = true;
}catch(IOException e){ }catch(IOException e){
dest.delete(); dest.delete();
@@ -91,19 +98,19 @@ public class Mods implements Loadable{
/** Repacks all in-game sprites. */ /** Repacks all in-game sprites. */
@Override @Override
public void loadAsync(){ public void loadAsync(){
if(loaded.isEmpty()) return; if(!mods.contains(LoadedMod::enabled)) return;
Time.mark(); Time.mark();
packer = new MultiPacker(); packer = new MultiPacker();
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
Array<FileHandle> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png")); Array<Fi> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png"));
Array<FileHandle> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png")); Array<Fi> overrides = mod.root.child("sprites-override").findAll(f -> f.extension().equals("png"));
packSprites(sprites, mod, true); packSprites(sprites, mod, true);
packSprites(overrides, mod, false); packSprites(overrides, mod, false);
Log.debug("Packed {0} images for mod '{1}'.", sprites.size + overrides.size, mod.meta.name); Log.debug("Packed {0} images for mod '{1}'.", sprites.size + overrides.size, mod.meta.name);
totalSprites += sprites.size + overrides.size; totalSprites += sprites.size + overrides.size;
} });
for(AtlasRegion region : Core.atlas.getRegions()){ for(AtlasRegion region : Core.atlas.getRegions()){
PageType type = getPage(region); PageType type = getPage(region);
@@ -115,8 +122,8 @@ public class Mods implements Loadable{
Log.debug("Time to pack textures: {0}", Time.elapsed()); Log.debug("Time to pack textures: {0}", Time.elapsed());
} }
private void packSprites(Array<FileHandle> sprites, LoadedMod mod, boolean prefix){ private void packSprites(Array<Fi> sprites, LoadedMod mod, boolean prefix){
for(FileHandle file : sprites){ for(Fi file : sprites){
try(InputStream stream = file.read()){ try(InputStream stream = file.read()){
byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512)); byte[] bytes = Streams.copyStreamToByteArray(stream, Math.max((int)file.length(), 512));
Pixmap pixmap = new Pixmap(bytes, 0, bytes.length); Pixmap pixmap = new Pixmap(bytes, 0, bytes.length);
@@ -149,7 +156,7 @@ public class Mods implements Loadable{
//generate new icons //generate new icons
for(Array<Content> arr : content.getContentMap()){ for(Array<Content> arr : content.getContentMap()){
arr.each(c -> { arr.each(c -> {
if(c instanceof UnlockableContent && c.mod != null){ if(c instanceof UnlockableContent && c.minfo.mod != null){
UnlockableContent u = (UnlockableContent)c; UnlockableContent u = (UnlockableContent)c;
u.createIcons(packer); u.createIcons(packer);
} }
@@ -176,7 +183,7 @@ public class Mods implements Loadable{
PageType.main; PageType.main;
} }
private PageType getPage(FileHandle file){ private PageType getPage(Fi file){
String parent = file.parent().name(); String parent = file.parent().name();
return return
parent.equals("environment") ? PageType.environment : parent.equals("environment") ? PageType.environment :
@@ -188,7 +195,7 @@ public class Mods implements Loadable{
/** Removes a mod file and marks it for requiring a restart. */ /** Removes a mod file and marks it for requiring a restart. */
public void removeMod(LoadedMod mod){ public void removeMod(LoadedMod mod){
if(mod.root instanceof ZipFileHandle){ if(mod.root instanceof ZipFi){
mod.root.delete(); mod.root.delete();
} }
@@ -198,8 +205,7 @@ public class Mods implements Loadable{
ui.showErrorMessage("$mod.delete.error"); ui.showErrorMessage("$mod.delete.error");
return; return;
} }
loaded.remove(mod); mods.remove(mod);
disabled.remove(mod);
requiresReload = true; requiresReload = true;
} }
@@ -219,17 +225,13 @@ public class Mods implements Loadable{
/** Loads all mods from the folder, but does not call any methods on them.*/ /** Loads all mods from the folder, but does not call any methods on them.*/
public void load(){ public void load(){
for(FileHandle file : modDirectory.list()){ for(Fi file : modDirectory.list()){
if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && (file.child("mod.json").exists() || file.child("mod.hjson").exists()))) continue; if(!file.extension().equals("jar") && !file.extension().equals("zip") && !(file.isDirectory() && (file.child("mod.json").exists() || file.child("mod.hjson").exists()))) continue;
Log.debug("[Mods] Loading mod {0}", file); Log.debug("[Mods] Loading mod {0}", file);
try{ try{
LoadedMod mod = loadMod(file); LoadedMod mod = loadMod(file);
if(mod.enabled() || headless){ mods.add(mod);
loaded.add(mod);
}else{
disabled.add(mod);
}
}catch(Exception e){ }catch(Exception e){
Log.err("Failed to load mod file {0}. Skipping.", file); Log.err("Failed to load mod file {0}. Skipping.", file);
Log.err(e); Log.err(e);
@@ -237,14 +239,10 @@ public class Mods implements Loadable{
} }
//load workshop mods now //load workshop mods now
for(FileHandle file : platform.getWorkshopContent(LoadedMod.class)){ for(Fi file : platform.getWorkshopContent(LoadedMod.class)){
try{ try{
LoadedMod mod = loadMod(file); LoadedMod mod = loadMod(file);
if(mod.enabled()){ mods.add(mod);
loaded.add(mod);
}else{
disabled.add(mod);
}
mod.addSteamID(file.name()); mod.addSteamID(file.name());
}catch(Exception e){ }catch(Exception e){
Log.err("Failed to load mod workshop file {0}. Skipping.", file); Log.err("Failed to load mod workshop file {0}. Skipping.", file);
@@ -252,28 +250,27 @@ public class Mods implements Loadable{
} }
} }
resolveDependencies(); resolveModState();
sortMods();
//sort mods to make sure servers handle them properly.
loaded.sort(Structs.comparing(m -> m.name));
buildFiles(); buildFiles();
} }
private void resolveDependencies(){ private void sortMods(){
Array<LoadedMod> incompatible = loaded.select(m -> !m.isSupported()); //sort mods to make sure servers handle them properly and they appear correctly in the dialog
loaded.removeAll(incompatible); mods.sort(Structs.comps(Structs.comparingInt(m -> m.state.ordinal()), Structs.comparing(m -> m.name)));
disabled.addAll(incompatible); }
for(LoadedMod mod : Array.<LoadedMod>withArrays(loaded, disabled)){ private void resolveModState(){
updateDependencies(mod); mods.each(this::updateDependencies);
for(LoadedMod mod : mods){
mod.state =
!mod.isSupported() ? ModState.unsupported :
mod.hasUnmetDependencies() ? ModState.missingDependencies :
!mod.shouldBeEnabled() ? ModState.disabled :
ModState.enabled;
} }
disabled.addAll(loaded.select(LoadedMod::hasUnmetDependencies));
loaded.removeAll(LoadedMod::hasUnmetDependencies);
disabled.each(mod -> setEnabled(mod, false));
disabled.distinct();
loaded.distinct();
} }
private void updateDependencies(LoadedMod mod){ private void updateDependencies(LoadedMod mod){
@@ -298,23 +295,23 @@ public class Mods implements Loadable{
private Array<LoadedMod> orderedMods(){ private Array<LoadedMod> orderedMods(){
ObjectSet<LoadedMod> visited = new ObjectSet<>(); ObjectSet<LoadedMod> visited = new ObjectSet<>();
Array<LoadedMod> result = new Array<>(); Array<LoadedMod> result = new Array<>();
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
if(!visited.contains(mod)){ if(!visited.contains(mod)){
topoSort(mod, result, visited); topoSort(mod, result, visited);
} }
} });
return result; return result;
} }
private LoadedMod locateMod(String name){ private LoadedMod locateMod(String name){
return loaded.find(mod -> mod.name.equals(name)); return mods.find(mod -> mod.enabled() && mod.name.equals(name));
} }
private void buildFiles(){ private void buildFiles(){
for(LoadedMod mod : orderedMods()){ for(LoadedMod mod : orderedMods()){
boolean zipFolder = !mod.file.isDirectory() && mod.root.parent() != null; boolean zipFolder = !mod.file.isDirectory() && mod.root.parent() != null;
String parentName = zipFolder ? mod.root.name() : null; String parentName = zipFolder ? mod.root.name() : null;
for(FileHandle file : mod.root.list()){ for(Fi file : mod.root.list()){
//ignore special folders like bundles or sprites //ignore special folders like bundles or sprites
if(file.isDirectory() && !specialFolders.contains(file.name())){ if(file.isDirectory() && !specialFolders.contains(file.name())){
//TODO calling child/parent on these files will give you gibberish; create wrapper class. //TODO calling child/parent on these files will give you gibberish; create wrapper class.
@@ -324,9 +321,9 @@ public class Mods implements Loadable{
} }
//load up bundles. //load up bundles.
FileHandle folder = mod.root.child("bundles"); Fi folder = mod.root.child("bundles");
if(folder.exists()){ if(folder.exists()){
for(FileHandle file : folder.list()){ for(Fi file : folder.list()){
if(file.name().startsWith("bundle") && file.extension().equals("properties")){ if(file.name().startsWith("bundle") && file.extension().equals("properties")){
String name = file.nameWithoutExtension(); String name = file.nameWithoutExtension();
bundles.getOr(name, Array::new).add(file); bundles.getOr(name, Array::new).add(file);
@@ -340,25 +337,79 @@ public class Mods implements Loadable{
while(bundle != null){ while(bundle != null){
String str = bundle.getLocale().toString(); String str = bundle.getLocale().toString();
String locale = "bundle" + (str.isEmpty() ? "" : "_" + str); String locale = "bundle" + (str.isEmpty() ? "" : "_" + str);
for(FileHandle file : bundles.getOr(locale, Array::new)){ for(Fi file : bundles.getOr(locale, Array::new)){
try{ try{
PropertiesUtils.load(bundle.getProperties(), file.reader()); PropertiesUtils.load(bundle.getProperties(), file.reader());
}catch(Exception e){ }catch(Exception e){
throw new RuntimeException("Error loading bundle: " + file + "/" + locale, e); Log.err("Error loading bundle: " + file + "/" + locale, e);
} }
} }
bundle = bundle.getParent(); bundle = bundle.getParent();
} }
} }
/** Check all warnings related to content and show relevant dialogs. Client only. */
private void checkWarnings(){
//show 'scripts have errored' info
if(scripts != null && scripts.hasErrored()){
Core.settings.getBoolOnce("scripts-errored2", () -> ui.showErrorMessage("$mod.scripts.unsupported"));
}
//show list of errored content
if(mods.contains(LoadedMod::hasContentErrors)){
ui.loadfrag.hide();
new Dialog(""){{
setFillParent(true);
cont.margin(15);
cont.add("$error.title");
cont.row();
cont.addImage().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
cont.row();
cont.add("$mod.errors").wrap().growX().center().get().setAlignment(Align.center);
cont.row();
cont.pane(p -> {
mods.each(m -> m.enabled() && m.hasContentErrors(), m -> {
p.add(m.name).color(Pal.accent).left();
p.row();
p.addImage().fillX().pad(4).color(Pal.accent);
p.row();
p.table(d -> {
d.left().marginLeft(15f);
for(Content c : m.erroredContent){
d.add(c.minfo.sourceFile.nameWithoutExtension()).left().padRight(10);
d.addImageTextButton("$details", Icon.arrowDownSmall, Styles.transt, () -> {
new Dialog(""){{
setFillParent(true);
cont.pane(e -> e.add(c.minfo.error)).grow();
cont.row();
cont.addImageTextButton("$ok", Icon.backSmall, this::hide).size(240f, 60f);
}}.show();
}).size(190f, 50f).left().marginLeft(6);
d.row();
}
}).left();
p.row();
});
});
cont.row();
cont.addButton("$ok", this::hide).size(300, 50);
}}.show();
}
}
public boolean hasContentErrors(){
return mods.contains(LoadedMod::hasContentErrors);
}
/** Reloads all mod content. How does this even work? I refuse to believe that it functions correctly.*/ /** Reloads all mod content. How does this even work? I refuse to believe that it functions correctly.*/
public void reloadContent(){ public void reloadContent(){
//epic memory leak //epic memory leak
//TODO make it less epic //TODO make it less epic
Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas")); Core.atlas = new TextureAtlas(Core.files.internal("sprites/sprites.atlas"));
loaded.clear(); mods.clear();
disabled.clear();
load(); load();
Sounds.dispose(); Sounds.dispose();
Sounds.load(); Sounds.load();
@@ -378,13 +429,10 @@ public class Mods implements Loadable{
content.load(); content.load();
content.loadColors(); content.loadColors();
data.load(); data.load();
Core.atlas.getTextures().each(t -> t.setFilter(Core.settings.getBool("linear") ? TextureFilter.Linear : TextureFilter.Nearest));
requiresReload = false; requiresReload = false;
Events.fire(new ContentReloadEvent()); Events.fire(new ContentReloadEvent());
if(scripts != null && scripts.hasErrored()){
Core.app.post(() -> Core.settings.getBoolOnce("scripts-errored", () -> ui.showErrorMessage("$mod.scripts.unsupported")));
}
} }
/** This must be run on the main thread! */ /** This must be run on the main thread! */
@@ -392,13 +440,13 @@ public class Mods implements Loadable{
Time.mark(); Time.mark();
try{ try{
for(LoadedMod mod : loaded){ eachEnabled(mod -> {
if(mod.root.child("scripts").exists()){ if(mod.root.child("scripts").exists()){
content.setCurrentMod(mod); content.setCurrentMod(mod);
mod.scripts = mod.root.child("scripts").findAll(f -> f.extension().equals("js")); mod.scripts = mod.root.child("scripts").findAll(f -> f.extension().equals("js"));
Log.debug("[{0}] Found {1} scripts.", mod.meta.name, mod.scripts.size); Log.debug("[{0}] Found {1} scripts.", mod.meta.name, mod.scripts.size);
for(FileHandle file : mod.scripts){ for(Fi file : mod.scripts){
try{ try{
if(scripts == null){ if(scripts == null){
scripts = platform.createScripts(); scripts = platform.createScripts();
@@ -408,13 +456,12 @@ public class Mods implements Loadable{
Core.app.post(() -> { Core.app.post(() -> {
Log.err("Error loading script {0} for mod {1}.", file.name(), mod.meta.name); Log.err("Error loading script {0} for mod {1}.", file.name(), mod.meta.name);
e.printStackTrace(); e.printStackTrace();
//if(!headless) ui.showException(e);
}); });
break; break;
} }
} }
} }
} });
}finally{ }finally{
content.setCurrentMod(null); content.setCurrentMod(null);
} }
@@ -427,10 +474,10 @@ public class Mods implements Loadable{
class LoadRun implements Comparable<LoadRun>{ class LoadRun implements Comparable<LoadRun>{
final ContentType type; final ContentType type;
final FileHandle file; final Fi file;
final LoadedMod mod; final LoadedMod mod;
public LoadRun(ContentType type, FileHandle file, LoadedMod mod){ public LoadRun(ContentType type, Fi file, LoadedMod mod){
this.type = type; this.type = type;
this.file = file; this.file = file;
this.mod = mod; this.mod = mod;
@@ -448,11 +495,11 @@ public class Mods implements Loadable{
for(LoadedMod mod : orderedMods()){ for(LoadedMod mod : orderedMods()){
if(mod.root.child("content").exists()){ if(mod.root.child("content").exists()){
FileHandle contentRoot = mod.root.child("content"); Fi contentRoot = mod.root.child("content");
for(ContentType type : ContentType.all){ for(ContentType type : ContentType.all){
FileHandle folder = contentRoot.child(type.name().toLowerCase() + "s"); Fi folder = contentRoot.child(type.name().toLowerCase() + "s");
if(folder.exists()){ if(folder.exists()){
for(FileHandle file : folder.list()){ for(Fi file : folder.list()){
if(file.extension().equals("json") || file.extension().equals("hjson")){ if(file.extension().equals("json") || file.extension().equals("hjson")){
runs.add(new LoadRun(type, file, mod)); runs.add(new LoadRun(type, file, mod));
} }
@@ -464,56 +511,43 @@ public class Mods implements Loadable{
//make sure mod content is in proper order //make sure mod content is in proper order
runs.sort(); runs.sort();
runs.each(l -> safeRun(l.mod, () -> { for(LoadRun l : runs){
Content current = content.getLastAdded();
try{ try{
//this binds the content but does not load it entirely //this binds the content but does not load it entirely
Content loaded = parser.parse(l.mod, l.file.nameWithoutExtension(), l.file.readString("UTF-8"), l.file, l.type); Content loaded = parser.parse(l.mod, l.file.nameWithoutExtension(), l.file.readString("UTF-8"), l.file, l.type);
Log.debug("[{0}] Loaded '{1}'.", l.mod.meta.name, Log.debug("[{0}] Loaded '{1}'.", l.mod.meta.name, (loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded));
(loaded instanceof UnlockableContent ? ((UnlockableContent)loaded).localizedName : loaded)); }catch(Throwable e){
}catch(Exception e){ if(current != content.getLastAdded() && content.getLastAdded() != null){
throw new RuntimeException("Failed to parse content file '" + l.file + "' for mod '" + l.mod.meta.name + "'.", e); parser.markError(content.getLastAdded(), l.mod, l.file, e);
}else{
ErrorContent error = new ErrorContent();
parser.markError(error, l.mod, l.file, e);
}
} }
})); }
//this finishes parsing content fields //this finishes parsing content fields
parser.finishParsing(); parser.finishParsing();
} }
/** @return all loaded mods. */ public void handleContentError(Content content, Throwable error){
public Array<LoadedMod> all(){ parser.markError(content, error);
return loaded;
}
/** @return all disabled mods. */
public Array<LoadedMod> disabled(){
return disabled;
}
/** @return a list of mod names only, without versions. */
public Array<String> getModNames(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version);
} }
/** @return a list of mods and versions, in the format name:version. */ /** @return a list of mods and versions, in the format name:version. */
public Array<String> getModStrings(){ public Array<String> getModStrings(){
return loaded.select(l -> !l.meta.hidden).map(l -> l.name + ":" + l.meta.version); return mods.select(l -> !l.meta.hidden && l.enabled()).map(l -> l.name + ":" + l.meta.version);
} }
/** Makes a mod enabled or disabled. shifts it.*/ /** Makes a mod enabled or disabled. shifts it.*/
public void setEnabled(LoadedMod mod, boolean enabled){ public void setEnabled(LoadedMod mod, boolean enabled){
if(mod.enabled() != enabled){ if(mod.enabled() != enabled){
Core.settings.putSave("mod-" + mod.name + "-enabled", enabled); Core.settings.putSave("mod-" + mod.name + "-enabled", enabled);
Core.settings.save();
requiresReload = true; requiresReload = true;
if(!enabled){ mod.state = enabled ? ModState.enabled : ModState.disabled;
loaded.remove(mod); mods.each(this::updateDependencies);
if(!disabled.contains(mod)) disabled.add(mod); sortMods();
}else{
if(!loaded.contains(mod)) loaded.add(mod);
disabled.remove(mod);
}
loaded.each(this::updateDependencies);
disabled.each(this::updateDependencies);
} }
} }
@@ -530,55 +564,37 @@ public class Mods implements Loadable{
return result; return result;
} }
/** Iterates through each mod with a main class.*/ public Array<LoadedMod> list(){
public void each(Cons<Mod> cons){ return mods;
loaded.each(p -> p.mod != null, p -> safeRun(p, () -> cons.get(p.mod)));
} }
public void handleError(Throwable t, LoadedMod mod){ /** Iterates through each mod with a main class. */
Array<Throwable> causes = Strings.getCauses(t); public void eachClass(Cons<Mod> cons){
Content content = null; mods.each(p -> p.main != null, p -> contextRun(p, () -> cons.get(p.main)));
for(Throwable e : causes){
if(e instanceof ModLoadException && ((ModLoadException) e).content != null){
content = ((ModLoadException) e).content;
}
}
String realCause = "<???>";
for(int i = causes.size -1 ; i >= 0; i--){
if(causes.get(i).getMessage() != null){
realCause = causes.get(i).getMessage();
break;
}
}
setEnabled(mod, false);
if(content != null){
throw new ModLoadException(Strings.format("Error loading '{0}' from mod '{1}' ({2}):\n{3}",
content, mod.meta.name, content.sourceFile == null ? "<unknown file>" : content.sourceFile.name(), realCause), content, t);
}else{
throw new ModLoadException("Error loading mod " + mod.meta.name, t);
}
} }
public void safeRun(LoadedMod mod, Runnable run){ /** Iterates through each enabled mod. */
public void eachEnabled(Cons<LoadedMod> cons){
mods.each(LoadedMod::enabled, cons);
}
public void contextRun(LoadedMod mod, Runnable run){
try{ try{
run.run(); run.run();
}catch(Throwable t){ }catch(Throwable t){
handleError(t, mod); throw new RuntimeException("Error loading mod " + mod.meta.name, t);
} }
} }
/** Loads a mod file+meta, but does not add it to the list. /** Loads a mod file+meta, but does not add it to the list.
* Note that directories can be loaded as mods.*/ * Note that directories can be loaded as mods.*/
private LoadedMod loadMod(FileHandle sourceFile) throws Exception{ private LoadedMod loadMod(Fi sourceFile) throws Exception{
FileHandle zip = sourceFile.isDirectory() ? sourceFile : new ZipFileHandle(sourceFile); Fi zip = sourceFile.isDirectory() ? sourceFile : new ZipFi(sourceFile);
if(zip.list().length == 1 && zip.list()[0].isDirectory()){ if(zip.list().length == 1 && zip.list()[0].isDirectory()){
zip = zip.list()[0]; zip = zip.list()[0];
} }
FileHandle metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("mod.hjson").exists() ? zip.child("mod.hjson") : zip.child("plugin.json"); Fi metaf = zip.child("mod.json").exists() ? zip.child("mod.json") : zip.child("mod.hjson").exists() ? zip.child("mod.hjson") : zip.child("plugin.json");
if(!metaf.exists()){ if(!metaf.exists()){
Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json'/'mod.js' file, skipping.", sourceFile); Log.warn("Mod {0} doesn't have a 'mod.json'/'plugin.json'/'mod.js' file, skipping.", sourceFile);
throw new IllegalArgumentException("No mod.json found."); throw new IllegalArgumentException("No mod.json found.");
@@ -589,13 +605,13 @@ public class Mods implements Loadable{
String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main; String mainClass = meta.main == null ? camelized.toLowerCase() + "." + camelized + "Mod" : meta.main;
String baseName = meta.name.toLowerCase().replace(" ", "-"); String baseName = meta.name.toLowerCase().replace(" ", "-");
if(loaded.contains(m -> m.name.equals(baseName)) || disabled.contains(m -> m.name.equals(baseName))){ if(mods.contains(m -> m.name.equals(baseName))){
throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported."); throw new IllegalArgumentException("A mod with the name '" + baseName + "' is already imported.");
} }
Mod mainMod; Mod mainMod;
FileHandle mainFile = zip; Fi mainFile = zip;
String[] path = (mainClass.replace('.', '/') + ".class").split("/"); String[] path = (mainClass.replace('.', '/') + ".class").split("/");
for(String str : path){ for(String str : path){
if(!str.isEmpty()){ if(!str.isEmpty()){
@@ -629,11 +645,11 @@ public class Mods implements Loadable{
/** Represents a plugin that has been loaded from a jar file.*/ /** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod implements Publishable{ public static class LoadedMod implements Publishable{
/** The location of this mod's zip file/folder on the disk. */ /** The location of this mod's zip file/folder on the disk. */
public final FileHandle file; public final Fi file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */ /** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
public final FileHandle root; public final Fi root;
/** The mod's main class; may be null. */ /** The mod's main class; may be null. */
public final @Nullable Mod mod; public final @Nullable Mod main;
/** Internal mod name. Used for textures. */ /** Internal mod name. Used for textures. */
public final String name; public final String name;
/** This mod's metadata. */ /** This mod's metadata. */
@@ -643,17 +659,25 @@ public class Mods implements Loadable{
/** All missing dependencies of this mod as strings. */ /** All missing dependencies of this mod as strings. */
public Array<String> missingDependencies = new Array<>(); public Array<String> missingDependencies = new Array<>();
/** Script files to run. */ /** Script files to run. */
public Array<FileHandle> scripts = new Array<>(); public Array<Fi> scripts = new Array<>();
/** Content with intialization code. */
public ObjectSet<Content> erroredContent = new ObjectSet<>();
/** Current state of this mod. */
public ModState state = ModState.enabled;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){ public LoadedMod(Fi file, Fi root, Mod main, ModMeta meta){
this.root = root; this.root = root;
this.file = file; this.file = file;
this.mod = mod; this.main = main;
this.meta = meta; this.meta = meta;
this.name = meta.name.toLowerCase().replace(" ", "-"); this.name = meta.name.toLowerCase().replace(" ", "-");
} }
public boolean enabled(){ public boolean enabled(){
return state == ModState.enabled || state == ModState.contentErrors;
}
public boolean shouldBeEnabled(){
return Core.settings.getBool("mod-" + name + "-enabled", true); return Core.settings.getBool("mod-" + name + "-enabled", true);
} }
@@ -661,6 +685,10 @@ public class Mods implements Loadable{
return !missingDependencies.isEmpty(); return !missingDependencies.isEmpty();
} }
public boolean hasContentErrors(){
return !erroredContent.isEmpty();
}
/** @return whether this mod is supported by the game verison */ /** @return whether this mod is supported by the game verison */
public boolean isSupported(){ public boolean isSupported(){
if(Version.build <= 0 || meta.minGameVersion == null) return true; if(Version.build <= 0 || meta.minGameVersion == null) return true;
@@ -706,12 +734,12 @@ public class Mods implements Loadable{
} }
@Override @Override
public FileHandle createSteamFolder(String id){ public Fi createSteamFolder(String id){
return file; return file;
} }
@Override @Override
public FileHandle createSteamPreview(String id){ public Fi createSteamPreview(String id){
return file.child("preview.png"); return file.child("preview.png");
} }
@@ -752,37 +780,11 @@ public class Mods implements Loadable{
} }
} }
/** Thrown when an error occurs while loading a mod.*/ public enum ModState{
public static class ModLoadException extends RuntimeException{ enabled,
public Content content; contentErrors,
public LoadedMod mod; missingDependencies,
unsupported,
public ModLoadException(String message, Throwable cause){ disabled,
super(message, cause);
}
public ModLoadException(String message, @Nullable Content content, Throwable cause){
super(message, cause);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
public ModLoadException(@Nullable Content content, Throwable cause){
super(cause);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
public ModLoadException(String message, @Nullable Content content){
super(message);
this.content = content;
if(content != null){
this.mod = content.mod;
}
}
} }
} }

View File

@@ -63,7 +63,7 @@ public class Scripts implements Disposable{
Log.log(level, "[{0}]: {1}", source, message); Log.log(level, "[{0}]: {1}", source, message);
} }
public void run(LoadedMod mod, FileHandle file){ public void run(LoadedMod mod, Fi file){
run(wrapper.replace("$SCRIPT_NAME$", mod.name + "/" + file.nameWithoutExtension()).replace("$CODE$", file.readString()).replace("$MOD_NAME$", mod.name), file.name()); run(wrapper.replace("$SCRIPT_NAME$", mod.name + "/" + file.nameWithoutExtension()).replace("$CODE$", file.readString()).replace("$MOD_NAME$", mod.name), file.name());
} }

View File

@@ -22,6 +22,7 @@ import static io.anuke.mindustry.Vars.net;
public class CrashSender{ public class CrashSender{
public static void send(Throwable exception, Cons<File> writeListener){ public static void send(Throwable exception, Cons<File> writeListener){
try{ try{
exception.printStackTrace(); exception.printStackTrace();
@@ -44,20 +45,19 @@ public class CrashSender{
}else{ }else{
Version.build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1; Version.build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1;
} }
}catch(Throwable ignored){ }catch(Throwable e){
ignored.printStackTrace(); e.printStackTrace();
Log.err("Failed to parse version."); Log.err("Failed to parse version.");
} }
} }
try{ try{
File file = new File(OS.getAppDataDirectoryString(Vars.appName), "crashes/crash-report-" + new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss").format(new Date()) + ".txt"); File file = new File(OS.getAppDataDirectoryString(Vars.appName), "crashes/crash-report-" + new SimpleDateFormat("MM_dd_yyyy_HH_mm_ss").format(new Date()) + ".txt");
new FileHandle(OS.getAppDataDirectoryString(Vars.appName)).child("crashes").mkdirs(); new Fi(OS.getAppDataDirectoryString(Vars.appName)).child("crashes").mkdirs();
new FileHandle(file).writeString(parseException(exception)); new Fi(file).writeString(parseException(exception));
writeListener.get(file); writeListener.get(file);
}catch(Throwable e){ }catch(Throwable e){
e.printStackTrace(); Log.err("Failed to save local crash report.", e);
Log.err("Failed to save local crash report.");
} }
try{ try{
@@ -69,6 +69,14 @@ public class CrashSender{
//if there's no settings init we don't know what the user wants but chances are it's an important crash, so send it anyway //if there's no settings init we don't know what the user wants but chances are it's an important crash, so send it anyway
} }
try{
//check any mods - if there are any, don't send reports
if(Vars.mods != null && !Vars.mods.list().isEmpty()){
return;
}
}catch(Throwable ignored){
}
//do not send exceptions that occur for versions that can't be parsed //do not send exceptions that occur for versions that can't be parsed
if(Version.number == 0){ if(Version.number == 0){
return; return;

View File

@@ -0,0 +1,12 @@
package io.anuke.mindustry.type;
import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
/** Represents a blank type of content that has an error. Replaces anything that failed to parse. */
public class ErrorContent extends Content{
@Override
public ContentType getContentType(){
return ContentType.error;
}
}

View File

@@ -4,6 +4,7 @@ import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*; import io.anuke.arc.graphics.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
import io.anuke.mindustry.world.blocks.*; import io.anuke.mindustry.world.blocks.*;

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
public class Liquid extends UnlockableContent{ public class Liquid extends UnlockableContent{

View File

@@ -5,6 +5,7 @@ import io.anuke.arc.graphics.Color;
import io.anuke.arc.graphics.g2d.TextureRegion; import io.anuke.arc.graphics.g2d.TextureRegion;
import io.anuke.arc.scene.ui.layout.Table; import io.anuke.arc.scene.ui.layout.Table;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.type.Player; import io.anuke.mindustry.entities.type.Player;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.graphics.Pal; import io.anuke.mindustry.graphics.Pal;

View File

@@ -20,9 +20,9 @@ public interface Publishable{
/** @return the tag that this content has. e.g. 'schematic' or 'map'. */ /** @return the tag that this content has. e.g. 'schematic' or 'map'. */
String steamTag(); String steamTag();
/** @return a folder with everything needed for this piece of content in it; does not need to be a copy. */ /** @return a folder with everything needed for this piece of content in it; does not need to be a copy. */
FileHandle createSteamFolder(String id); Fi createSteamFolder(String id);
/** @return a preview file PNG. */ /** @return a preview file PNG. */
FileHandle createSteamPreview(String id); Fi createSteamPreview(String id);
/** @return any extra tags to add to this item.*/ /** @return any extra tags to add to this item.*/
default Array<String> extraTags(){ default Array<String> extraTags(){
return new Array<>(0); return new Array<>(0);

View File

@@ -6,6 +6,7 @@ import io.anuke.arc.math.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;

View File

@@ -2,6 +2,7 @@ package io.anuke.mindustry.type;
import io.anuke.arc.func.*; import io.anuke.arc.func.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.entities.traits.*; import io.anuke.mindustry.entities.traits.*;
public class TypeID extends MappableContent{ public class TypeID extends MappableContent{

View File

@@ -8,6 +8,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;

View File

@@ -1,6 +1,7 @@
package io.anuke.mindustry.type; package io.anuke.mindustry.type;
import io.anuke.mindustry.ctype.Content; import io.anuke.mindustry.ctype.Content;
import io.anuke.mindustry.ctype.ContentType;
//currently unimplemented, see trello for implementation plans //currently unimplemented, see trello for implementation plans
public class WeatherEvent extends Content{ public class WeatherEvent extends Content{

View File

@@ -7,6 +7,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*; import io.anuke.mindustry.content.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*; import io.anuke.mindustry.game.*;
@@ -172,8 +173,8 @@ public class Zone extends UnlockableContent{
@Override @Override
public void init(){ public void init(){
if(generator instanceof MapGenerator && mod != null){ if(generator instanceof MapGenerator && minfo.mod != null){
((MapGenerator)generator).removePrefix(mod.name); ((MapGenerator)generator).removePrefix(minfo.mod.name);
} }
generator.init(loadout); generator.init(loadout);

View File

@@ -25,7 +25,7 @@ import static io.anuke.mindustry.gen.Tex.*;
public class Styles{ public class Styles{
public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver; public static Drawable black, black9, black8, black6, black3, none, flatDown, flatOver;
public static ButtonStyle defaultb, waveb; public static ButtonStyle defaultb, waveb;
public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet; public static TextButtonStyle defaultt, squaret, nodet, cleart, discordt, infot, clearPartialt, clearTogglet, clearToggleMenut, togglet, transt;
public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali; public static ImageButtonStyle defaulti, nodei, righti, emptyi, emptytogglei, selecti, cleari, clearFulli, clearPartiali, clearPartial2i, clearTogglei, clearTransi, clearToggleTransi, clearTogglePartiali;
public static ScrollPaneStyle defaultPane, horizontalPane, smallPane; public static ScrollPaneStyle defaultPane, horizontalPane, smallPane;
public static KeybindDialogStyle defaultKeybindDialog; public static KeybindDialogStyle defaultKeybindDialog;
@@ -110,6 +110,14 @@ public class Styles{
fontColor = Color.white; fontColor = Color.white;
disabledFontColor = Color.gray; disabledFontColor = Color.gray;
}}; }};
transt = new TextButtonStyle(){{
down = flatDown;
up = none;
over = flatOver;
font = Fonts.def;
fontColor = Color.white;
disabledFontColor = Color.gray;
}};
clearTogglet = new TextButtonStyle(){{ clearTogglet = new TextButtonStyle(){{
font = Fonts.def; font = Fonts.def;
fontColor = Color.white; fontColor = Color.white;

View File

@@ -10,9 +10,9 @@ import io.anuke.arc.util.*;
import io.anuke.mindustry.*; import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.*; import io.anuke.mindustry.ctype.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.gen.*; import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*; import io.anuke.mindustry.ui.*;
public class DatabaseDialog extends FloatingDialog{ public class DatabaseDialog extends FloatingDialog{

View File

@@ -18,20 +18,20 @@ import java.util.*;
import static io.anuke.mindustry.Vars.platform; import static io.anuke.mindustry.Vars.platform;
public class FileChooser extends FloatingDialog{ public class FileChooser extends FloatingDialog{
private static final FileHandle homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath()); private static final Fi homeDirectory = Core.files.absolute(Core.files.getExternalStoragePath());
private static FileHandle lastDirectory = homeDirectory; private static Fi lastDirectory = homeDirectory;
private Table files; private Table files;
private FileHandle directory = lastDirectory; private Fi directory = lastDirectory;
private ScrollPane pane; private ScrollPane pane;
private TextField navigation, filefield; private TextField navigation, filefield;
private TextButton ok; private TextButton ok;
private FileHistory stack = new FileHistory(); private FileHistory stack = new FileHistory();
private Boolf<FileHandle> filter; private Boolf<Fi> filter;
private Cons<FileHandle> selectListener; private Cons<Fi> selectListener;
private boolean open; private boolean open;
public FileChooser(String title, Boolf<FileHandle> filter, boolean open, Cons<FileHandle> result){ public FileChooser(String title, Boolf<Fi> filter, boolean open, Cons<Fi> result){
super(title); super(title);
setFillParent(true); setFillParent(true);
this.open = open; this.open = open;
@@ -154,8 +154,8 @@ public class FileChooser extends FloatingDialog{
} }
} }
private FileHandle[] getFileNames(){ private Fi[] getFileNames(){
FileHandle[] handles = directory.list(file -> !file.getName().startsWith(".")); Fi[] handles = directory.list(file -> !file.getName().startsWith("."));
Arrays.sort(handles, (a, b) -> { Arrays.sort(handles, (a, b) -> {
if(a.isDirectory() && !b.isDirectory()) return -1; if(a.isDirectory() && !b.isDirectory()) return -1;
@@ -183,7 +183,7 @@ public class FileChooser extends FloatingDialog{
files.clearChildren(); files.clearChildren();
files.top().left(); files.top().left();
FileHandle[] names = getFileNames(); Fi[] names = getFileNames();
Image upimage = new Image(Icon.folderParentSmall); Image upimage = new Image(Icon.folderParentSmall);
TextButton upbutton = new TextButton(".." + directory.toString(), Styles.clearTogglet); TextButton upbutton = new TextButton(".." + directory.toString(), Styles.clearTogglet);
@@ -204,7 +204,7 @@ public class FileChooser extends FloatingDialog{
ButtonGroup<TextButton> group = new ButtonGroup<>(); ButtonGroup<TextButton> group = new ButtonGroup<>();
group.setMinCheckCount(0); group.setMinCheckCount(0);
for(FileHandle file : names){ for(Fi file : names){
if(!file.isDirectory() && !filter.get(file)) continue; //skip non-filtered files if(!file.isDirectory() && !filter.get(file)) continue; //skip non-filtered files
String filename = file.name(); String filename = file.name();
@@ -255,14 +255,14 @@ public class FileChooser extends FloatingDialog{
} }
public class FileHistory{ public class FileHistory{
private Array<FileHandle> history = new Array<>(); private Array<Fi> history = new Array<>();
private int index; private int index;
public FileHistory(){ public FileHistory(){
} }
public void push(FileHandle file){ public void push(Fi file){
if(index != history.size) history.truncate(index); if(index != history.size) history.truncate(index);
history.add(file); history.add(file);
index++; index++;
@@ -296,7 +296,7 @@ public class FileChooser extends FloatingDialog{
System.out.println("\n\n\n\n\n\n"); System.out.println("\n\n\n\n\n\n");
int i = 0; int i = 0;
for(FileHandle file : history){ for(Fi file : history){
i++; i++;
if(index == i){ if(index == i){
System.out.println("[[" + file.toString() + "]]"); System.out.println("[[" + file.toString() + "]]");

View File

@@ -101,7 +101,7 @@ public class LoadDialog extends FloatingDialog{
}); });
}else{ }else{
try{ try{
FileHandle file = Core.files.local("save-" + slot.getName() + "." + saveExtension); Fi file = Core.files.local("save-" + slot.getName() + "." + saveExtension);
slot.exportFile(file); slot.exportFile(file);
platform.shareFile(file); platform.shareFile(file);
}catch(Exception e){ }catch(Exception e){

View File

@@ -2,7 +2,6 @@ package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.*; import io.anuke.arc.*;
import io.anuke.arc.Net.*; import io.anuke.arc.Net.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*; import io.anuke.arc.files.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.arc.util.io.*; import io.anuke.arc.util.io.*;
@@ -47,7 +46,7 @@ public class ModsDialog extends FloatingDialog{
ui.loadfrag.hide(); ui.loadfrag.hide();
}else{ }else{
try{ try{
FileHandle file = tmpDirectory.child(text.replace("/", "") + ".zip"); Fi file = tmpDirectory.child(text.replace("/", "") + ".zip");
Streams.copyStream(result.getResultAsStream(), file.write(false)); Streams.copyStream(result.getResultAsStream(), file.write(false));
mods.importMod(file); mods.importMod(file);
file.delete(); file.delete();
@@ -75,7 +74,7 @@ public class ModsDialog extends FloatingDialog{
hidden(() -> { hidden(() -> {
if(mods.requiresReload()){ if(mods.requiresReload()){
ui.loadAnd("$reloading", () -> { ui.loadAnd("$reloading", () -> {
mods.all().each(mod -> { mods.eachEnabled(mod -> {
if(mod.hasUnmetDependencies()){ if(mod.hasUnmetDependencies()){
ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", "))); ui.showErrorMessage(Core.bundle.format("mod.nowdisabled", mod.name, mod.missingDependencies.toString(", ")));
} }
@@ -107,14 +106,13 @@ public class ModsDialog extends FloatingDialog{
cont.defaults().width(mobile ? 500 : 560f).pad(4); cont.defaults().width(mobile ? 500 : 560f).pad(4);
cont.add("$mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center); cont.add("$mod.reloadrequired").visible(mods::requiresReload).center().get().setAlignment(Align.center);
cont.row(); cont.row();
if(!(mods.all().isEmpty() && mods.disabled().isEmpty())){ if(!mods.list().isEmpty()){
cont.pane(table -> { cont.pane(table -> {
table.margin(10f).top(); table.margin(10f).top();
Array<LoadedMod> all = Array.withArrays(mods.all(), mods.disabled());
boolean anyDisabled = false; boolean anyDisabled = false;
for(LoadedMod mod : all){ for(LoadedMod mod : mods.list()){
if(!mod.enabled() && !anyDisabled && mods.all().size > 0){ if(!mod.enabled() && !anyDisabled && mods.list().size > 0){
anyDisabled = true; anyDisabled = true;
table.row(); table.row();
table.addImage().growX().height(4f).pad(6f).color(Pal.gray); table.addImage().growX().height(4f).pad(6f).color(Pal.gray);
@@ -167,6 +165,9 @@ public class ModsDialog extends FloatingDialog{
}else if(mod.hasUnmetDependencies()){ }else if(mod.hasUnmetDependencies()){
t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX(); t.labelWrap(Core.bundle.format("mod.missingdependencies", mod.missingDependencies.toString(", "))).growX();
t.row(); t.row();
}else if(mod.hasContentErrors()){
t.labelWrap("$mod.erroredcontent").growX();
t.row();
} }
}).width(mobile ? 430f : 500f); }).width(mobile ? 430f : 500f);
table.row(); table.row();

View File

@@ -95,7 +95,7 @@ public class SettingsMenuDialog extends SettingsDialog{
Core.settings.putAll(map); Core.settings.putAll(map);
Core.settings.save(); Core.settings.save();
for(FileHandle file : dataDirectory.list()){ for(Fi file : dataDirectory.list()){
file.deleteDirectory(); file.deleteDirectory();
} }
@@ -106,7 +106,7 @@ public class SettingsMenuDialog extends SettingsDialog{
t.addButton("$data.export", style, () -> { t.addButton("$data.export", style, () -> {
if(ios){ if(ios){
FileHandle file = Core.files.local("mindustry-data-export.zip"); Fi file = Core.files.local("mindustry-data-export.zip");
try{ try{
data.exportData(file); data.exportData(file);
}catch(Exception e){ }catch(Exception e){

View File

@@ -42,6 +42,7 @@ public class BlockInventoryFragment extends Fragment{
int removed = tile.block().removeStack(tile, item, amount); int removed = tile.block().removeStack(tile, item, amount);
player.addItem(item, removed); player.addItem(item, removed);
Events.fire(new WithdrawEvent(tile, player, item, amount));
for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){ for(int j = 0; j < Mathf.clamp(removed / 3, 1, 8); j++){
Time.run(j * 3f, () -> Call.transferItemEffect(item, tile.drawx(), tile.drawy(), player)); Time.run(j * 3f, () -> Call.transferItemEffect(item, tile.drawx(), tile.drawy(), player));
} }
@@ -100,7 +101,7 @@ public class BlockInventoryFragment extends Fragment{
holding = false; holding = false;
holdTime = 0f; holdTime = 0f;
Events.fire(new WithdrawEvent()); if(net.client()) Events.fire(new WithdrawEvent(tile, player, lastItem, amount));
} }
} }
@@ -153,7 +154,7 @@ public class BlockInventoryFragment extends Fragment{
lastItem = item; lastItem = item;
holding = true; holding = true;
holdTime = 0f; holdTime = 0f;
Events.fire(new WithdrawEvent()); if(net.client()) Events.fire(new WithdrawEvent(tile, player, item, amount));
} }
return true; return true;
} }

View File

@@ -17,6 +17,7 @@ import io.anuke.arc.scene.ui.ImageButton.*;
import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*; import io.anuke.arc.util.*;
import io.anuke.mindustry.core.GameState.*; import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.ctype.ContentType;
import io.anuke.mindustry.ctype.UnlockableContent; import io.anuke.mindustry.ctype.UnlockableContent;
import io.anuke.mindustry.entities.*; import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.entities.type.*;

View File

@@ -88,11 +88,11 @@ public class MenuFragment extends Fragment{
container.defaults().size(size).pad(5).padTop(4f); container.defaults().size(size).pad(5).padTop(4f);
MobileButton MobileButton
play = new MobileButton(Icon.play2, "$campaign", ui.deploy::show), play = new MobileButton(Icon.play2, "$campaign", () -> checkPlay(ui.deploy::show)),
custom = new MobileButton(Icon.playCustom, "$customgame", ui.custom::show), custom = new MobileButton(Icon.playCustom, "$customgame", () -> checkPlay(ui.custom::show)),
maps = new MobileButton(Icon.load, "$loadgame", ui.load::show), maps = new MobileButton(Icon.load, "$loadgame", () -> checkPlay(ui.load::show)),
join = new MobileButton(Icon.add, "$joingame", ui.join::show), join = new MobileButton(Icon.add, "$joingame", () -> checkPlay(ui.join::show)),
editor = new MobileButton(Icon.editor, "$editor", ui.maps::show), editor = new MobileButton(Icon.editor, "$editor", () -> checkPlay(ui.maps::show)),
tools = new MobileButton(Icon.tools, "$settings", ui.settings::show), tools = new MobileButton(Icon.tools, "$settings", ui.settings::show),
mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show), mods = new MobileButton(Icon.wiki, "$mods", ui.mods::show),
donate = new MobileButton(Icon.link, "$website", () -> Core.net.openURI("https://anuke.itch.io/mindustry")), donate = new MobileButton(Icon.link, "$website", () -> Core.net.openURI("https://anuke.itch.io/mindustry")),
@@ -153,13 +153,13 @@ public class MenuFragment extends Fragment{
buttons(t, buttons(t,
new Buttoni("$play", Icon.play2Small, new Buttoni("$play", Icon.play2Small,
new Buttoni("$campaign", Icon.play2Small, ui.deploy::show), new Buttoni("$campaign", Icon.play2Small, () -> checkPlay(ui.deploy::show)),
new Buttoni("$joingame", Icon.addSmall, ui.join::show), new Buttoni("$joingame", Icon.addSmall, () -> checkPlay(ui.join::show)),
new Buttoni("$customgame", Icon.editorSmall, ui.custom::show), new Buttoni("$customgame", Icon.editorSmall, () -> checkPlay(ui.custom::show)),
new Buttoni("$loadgame", Icon.loadSmall, ui.load::show), new Buttoni("$loadgame", Icon.loadSmall, () -> checkPlay(ui.load::show)),
new Buttoni("$tutorial", Icon.infoSmall, control::playTutorial) new Buttoni("$tutorial", Icon.infoSmall, () -> checkPlay(control::playTutorial))
), ),
new Buttoni("$editor", Icon.editorSmall, ui.maps::show), steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null, new Buttoni("$editor", Icon.editorSmall, () -> checkPlay(ui.maps::show)), steam ? new Buttoni("$workshop", Icon.saveSmall, platform::openWorkshop) : null,
new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show), new Buttoni(Core.bundle.get("mods") + "\n" + Core.bundle.get("mods.alpha"), Icon.wikiSmall, ui.mods::show),
//not enough space for this button //not enough space for this button
//new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show), //new Buttoni("$schematics", Icon.pasteSmall, ui.schematics::show),
@@ -180,6 +180,14 @@ public class MenuFragment extends Fragment{
}).width(width).growY(); }).width(width).growY();
} }
private void checkPlay(Runnable run){
if(!mods.hasContentErrors()){
run.run();
}else{
ui.showInfo("$mod.noerrorplay");
}
}
private void fadeInMenu(){ private void fadeInMenu(){
submenu.clearActions(); submenu.clearActions();
submenu.actions(Actions.alpha(1f, 0.15f, Interpolation.fade)); submenu.actions(Actions.alpha(1f, 0.15f, Interpolation.fade));

Some files were not shown because too many files have changed in this diff Show More