diff --git a/.gitignore b/.gitignore index 296fbf66f7..5a3526c397 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /core/assets/mindustry-saves/ /core/assets/mindustry-maps/ +/core/assets/bundles/output/ /deploy/ /desktop/packr-out/ /desktop/packr-export/ @@ -9,9 +10,12 @@ /desktop/mindustry-maps/ /desktop/gifexport/ /core/lib/ +/core/assets-raw/sprites/generated/ /annotations/build/ /kryonet/build/ +/packer/build/ /server/build/ +/annotations/build/ /android/assets/mindustry-maps/ /android/assets/mindustry-saves/ /core/assets/gifexport/ diff --git a/.travis.yml b/.travis.yml index 06a66fa107..56ba77b664 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,14 @@ jdk: android: components: - - android-26 + - android-27 # Additional components - extra-google-google_play_services - extra-google-m2repository - extra-android-m2repository - - addon-google_apis-google-26 + - addon-google_apis-google-27 + - build-tools-27.0.3 script: - ./gradlew desktop:dist diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 01d804c3fd..64fc9d218b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -9,22 +9,58 @@ + android:resizeableActivity="false" + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:roundIcon="@mipmap/ic_launcher_round" + android:isGame="true" + android:appCategory="game" + android:label="@string/app_name" + android:theme="@style/GdxTheme" android:fullBackupContent="@xml/backup_rules"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index bc3c955d02..a2d912a486 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,8 @@ +apply plugin: "com.android.application" + +configurations { natives } + repositories { mavenCentral() jcenter() @@ -8,9 +12,20 @@ repositories { } dependencies { - implementation 'com.android.support:support-v4:22.1.1' + implementation project(":core") + implementation project(":kryonet") + implementation 'com.android.support:support-v4:25.3.1' implementation 'org.sufficientlysecure:donations:2.5' implementation 'com.google.android.gms:play-services-auth:11.8.0' + + implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" + implementation "com.badlogicgames.gdx:gdx-ai:$aiVersion" + implementation "com.badlogicgames.gdx:gdx-controllers-android:$gdxVersion" } task deploy(type: Copy){ @@ -22,8 +37,8 @@ task deploy(type: Copy){ } android { - buildToolsVersion '26.0.2' - compileSdkVersion 26 + buildToolsVersion '27.0.3' + compileSdkVersion 27 sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -35,7 +50,7 @@ android { jniLibs.srcDirs = ['libs'] } - instrumentTest.setRoot('tests') + androidTest.setRoot('tests') } packagingOptions { exclude 'META-INF/robovm/ios/robovm.xml' diff --git a/android/res/drawable-hdpi/ic_launcher.png b/android/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index b43bb84e6e..0000000000 Binary files a/android/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-hdpi/ic_launcher_round.png b/android/res/drawable-hdpi/ic_launcher_round.png deleted file mode 100644 index c79dccd9df..0000000000 Binary files a/android/res/drawable-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/res/drawable-mdpi/ic_launcher.png b/android/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 91d97a6051..0000000000 Binary files a/android/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xhdpi/ic_launcher.png b/android/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 2adf4e8f9a..0000000000 Binary files a/android/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xxhdpi/ic_launcher.png b/android/res/drawable-xxhdpi/ic_launcher.png deleted file mode 100644 index a2131b698e..0000000000 Binary files a/android/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable-xxxhdpi/ic_launcher.png b/android/res/drawable-xxxhdpi/ic_launcher.png deleted file mode 100644 index f625a1e78e..0000000000 Binary files a/android/res/drawable-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/res/drawable/ic_launcher.png b/android/res/drawable/ic_launcher.png deleted file mode 100644 index b43bb84e6e..0000000000 Binary files a/android/res/drawable/ic_launcher.png and /dev/null differ diff --git a/android/res/layout-v14/gdxdialogs_inputtext.xml b/android/res/layout-v14/gdxdialogs_inputtext.xml deleted file mode 100755 index e54b6a5016..0000000000 --- a/android/res/layout-v14/gdxdialogs_inputtext.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/android/res/layout/gdxdialogs_inputtext.xml b/android/res/layout/gdxdialogs_inputtext.xml index 656600a753..e54b6a5016 100755 --- a/android/res/layout/gdxdialogs_inputtext.xml +++ b/android/res/layout/gdxdialogs_inputtext.xml @@ -5,28 +5,7 @@ android:layout_height="fill_parent" android:orientation="vertical" android:padding="10dp" > - - - - - - + - - - \ No newline at end of file diff --git a/android/res/xml/backup_rules.xml b/android/res/xml/backup_rules.xml new file mode 100644 index 0000000000..bfa7569b2e --- /dev/null +++ b/android/res/xml/backup_rules.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/src/io/anuke/mindustry/AndroidLauncher.java b/android/src/io/anuke/mindustry/AndroidLauncher.java index a37db1b058..d532734999 100644 --- a/android/src/io/anuke/mindustry/AndroidLauncher.java +++ b/android/src/io/anuke/mindustry/AndroidLauncher.java @@ -3,14 +3,18 @@ package io.anuke.mindustry; import android.Manifest; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings.Secure; import android.telephony.TelephonyManager; import android.util.Log; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Base64Coder; import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GooglePlayServicesNotAvailableException; @@ -19,163 +23,263 @@ import com.google.android.gms.security.ProviderInstaller; import io.anuke.kryonet.DefaultThreadImpl; import io.anuke.kryonet.KryoClient; import io.anuke.kryonet.KryoServer; -import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; +import io.anuke.mindustry.io.SaveIO; +import io.anuke.mindustry.io.Saves.SaveSlot; import io.anuke.mindustry.net.Net; -import io.anuke.ucore.core.Settings; +import io.anuke.mindustry.ui.dialogs.FileChooser; +import io.anuke.ucore.function.Consumer; import io.anuke.ucore.scene.ui.TextField; import io.anuke.ucore.scene.ui.layout.Unit; +import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Strings; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.text.DateFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.Locale; -import java.util.Random; + +import static io.anuke.mindustry.Vars.*; public class AndroidLauncher extends AndroidApplication{ - boolean doubleScaleTablets = true; - int WRITE_REQUEST_CODE = 1; + public static final int PERMISSION_REQUEST_CODE = 1; - @Override - protected void onCreate(Bundle savedInstanceState){ - super.onCreate(savedInstanceState); + boolean doubleScaleTablets = true; + FileChooser chooser; - AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); - config.useImmersiveMode = true; + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); - Platform.instance = new Platform(){ - DateFormat format = SimpleDateFormat.getDateTimeInstance(); + AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); + config.useImmersiveMode = true; - @Override - public boolean hasDiscord() { - return isPackageInstalled("com.discord"); - } + Platform.instance = new Platform(){ + DateFormat format = SimpleDateFormat.getDateTimeInstance(); - @Override - public String format(Date date){ - return format.format(date); - } + @Override + public boolean hasDiscord(){ + return isPackageInstalled("com.discord"); + } - @Override - public String format(int number){ - return NumberFormat.getIntegerInstance().format(number); - } + @Override + public String format(Date date){ + return format.format(date); + } - @Override - public void addDialog(TextField field, int length){ - TextFieldDialogListener.add(field, 0, length); - } + @Override + public String format(int number){ + return NumberFormat.getIntegerInstance().format(number); + } - @Override - public String getLocaleName(Locale locale){ - return locale.getDisplayName(locale); - } + @Override + public void addDialog(TextField field, int length){ + TextFieldDialogListener.add(field, 0, length); + } - @Override - public void openDonations() { - showDonations(); - } + @Override + public String getLocaleName(Locale locale){ + return locale.getDisplayName(locale); + } - @Override - public void requestWritePerms() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && - checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE}, WRITE_REQUEST_CODE); - }else{ + @Override + public void openDonations(){ + showDonations(); + } - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_REQUEST_CODE); - } + @Override + public ThreadProvider getThreadProvider(){ + return new DefaultThreadImpl(); + } - if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, WRITE_REQUEST_CODE); - } - } - } - } + @Override + public boolean isDebug(){ + return false; + } - @Override - public ThreadProvider getThreadProvider() { - return new DefaultThreadImpl(); - } + @Override + public String getUUID(){ + try{ + String s = Secure.getString(getContext().getContentResolver(), + Secure.ANDROID_ID); - @Override - public boolean isDebug() { - return false; - } - - @Override - public byte[] getUUID() { - try { - String s = Secure.getString(getContext().getContentResolver(), - Secure.ANDROID_ID); - - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - - if(new String(Base64Coder.encode(data)).equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); - - return data; - }catch (Exception e){ - - String uuid = Settings.getString("uuid", ""); - if(uuid.isEmpty()){ - byte[] result = new byte[8]; - new Random().nextBytes(result); - uuid = new String(Base64Coder.encode(result)); - Settings.putString("uuid", uuid); - Settings.save(); - return result; + int len = s.length(); + byte[] data = new byte[len / 2]; + for(int i = 0; i < len; i += 2){ + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); } - return Base64Coder.decode(uuid); - } - } - }; - try { - ProviderInstaller.installIfNeeded(this); - } catch (GooglePlayServicesRepairableException e) { - GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); - apiAvailability.getErrorDialog(this, e.getConnectionStatusCode(), 0).show(); - } catch (GooglePlayServicesNotAvailableException e) { - Log.e("SecurityException", "Google Play Services not available."); - } + String result = new String(Base64Coder.encode(data)); - if(doubleScaleTablets && isTablet(this.getContext())){ - Unit.dp.addition = 0.5f; - } - - config.hideStatusBar = true; + if(result.equals("AAAAAAAAAOA=")) throw new RuntimeException("Bad UUID."); + + return result; + }catch(Exception e){ + return super.getUUID(); + } + } + + @Override + public void shareFile(FileHandle file){ + + } + + @Override + public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){ + chooser = new FileChooser(text, file -> file.extension().equalsIgnoreCase(filetype), open, cons); + + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && + checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)){ + chooser.show(); + chooser = null; + }else{ + ArrayList perms = new ArrayList<>(); + + if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + + if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ + perms.add(Manifest.permission.READ_EXTERNAL_STORAGE); + } + + requestPermissions(perms.toArray(new String[perms.size()]), PERMISSION_REQUEST_CODE); + } + } + + @Override + public void beginForceLandscape(){ + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + + @Override + public void endForceLandscape(){ + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); + } + + @Override + public boolean canDonate(){ + return true; + } + }; + + try{ + ProviderInstaller.installIfNeeded(this); + }catch(GooglePlayServicesRepairableException e){ + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); + apiAvailability.getErrorDialog(this, e.getConnectionStatusCode(), 0).show(); + }catch(GooglePlayServicesNotAvailableException e){ + Log.e("SecurityException", "Google Play Services not available."); + } + + if(doubleScaleTablets && isTablet(this.getContext())){ + Unit.dp.addition = 0.5f; + } + + config.hideStatusBar = true; Net.setClientProvider(new KryoClient()); Net.setServerProvider(new KryoServer()); initialize(new Mindustry(), config); - } - - private boolean isPackageInstalled(String packagename) { - try { - getPackageManager().getPackageInfo(packagename, 0); - return true; - } catch (Exception e) { - return false; - } - } - - private boolean isTablet(Context context) { - TelephonyManager manager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); - return manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE; - } - - private void showDonations(){ - Intent intent = new Intent(this, DonationsActivity.class); - startActivity(intent); - } + + checkFiles(getIntent()); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){ + if(requestCode == PERMISSION_REQUEST_CODE){ + for(int i : grantResults){ + if(i != PackageManager.PERMISSION_GRANTED) return; + } + + if(chooser != null){ + chooser.show(); + } + } + } + + private void checkFiles(Intent intent){ + try{ + Uri uri = intent.getData(); + if(uri != null){ + File myFile = null; + String scheme = uri.getScheme(); + if(scheme.equals("file")){ + String fileName = uri.getEncodedPath(); + myFile = new File(fileName); + }else if(!scheme.equals("content")){ + //error + return; + } + + boolean save = uri.getPath().endsWith(saveExtension); + boolean map = uri.getPath().endsWith(mapExtension); + + InputStream inStream; + if(myFile != null) inStream = new FileInputStream(myFile); + else inStream = getContentResolver().openInputStream(uri); + + Gdx.app.postRunnable(() -> { + + if(save){ //open save + System.out.println("Opening save."); + FileHandle file = Gdx.files.local("temp-save." + saveExtension); + file.write(inStream, false); + + if(SaveIO.isSaveValid(file)){ + try{ + SaveSlot slot = control.getSaves().importSave(file); + ui.load.runLoadSave(slot); + }catch(IOException e){ + ui.showError(Bundles.format("text.save.import.fail", Strings.parseException(e, false))); + } + }else{ + ui.showError("$text.save.import.invalid"); + } + + }else if(map){ //open map + Gdx.app.postRunnable(() -> { + System.out.println("Opening map."); + if(!ui.editor.isShown()){ + ui.editor.show(); + } + + ui.editor.beginEditMap(inStream); + }); + } + }); + } + + }catch(IOException e){ + e.printStackTrace(); + } + } + + private boolean isPackageInstalled(String packagename){ + try{ + getPackageManager().getPackageInfo(packagename, 0); + return true; + }catch(Exception e){ + return false; + } + } + + private boolean isTablet(Context context){ + TelephonyManager manager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return manager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE; + } + + private void showDonations(){ + Intent intent = new Intent(this, DonationsActivity.class); + startActivity(intent); + } } diff --git a/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java b/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java index fd6fa9fec2..56a1b92c72 100644 --- a/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java +++ b/android/src/io/anuke/mindustry/AndroidTextFieldDialog.java @@ -11,21 +11,20 @@ import android.widget.EditText; import com.badlogic.gdx.Gdx; public class AndroidTextFieldDialog{ - private Activity activity; - private EditText userInput; - private AlertDialog.Builder builder; - private TextPromptListener listener; - private boolean isBuild; + private Activity activity; + private EditText userInput; + private AlertDialog.Builder builder; + private TextPromptListener listener; + private boolean isBuild; - public AndroidTextFieldDialog() { - this.activity = (Activity)Gdx.app; - load(); - } + public AndroidTextFieldDialog(){ + this.activity = (Activity) Gdx.app; + load(); + } - public AndroidTextFieldDialog show() { + public AndroidTextFieldDialog show(){ - activity.runOnUiThread(() -> { - Gdx.app.error("Android Dialogs", AndroidTextFieldDialog.class.getSimpleName() + " now shown."); + activity.runOnUiThread(() -> { AlertDialog dialog = builder.create(); dialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -34,12 +33,12 @@ public class AndroidTextFieldDialog{ }); - return this; - } + return this; + } - private AndroidTextFieldDialog load() { + private AndroidTextFieldDialog load(){ - activity.runOnUiThread(() -> { + activity.runOnUiThread(() -> { AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); LayoutInflater li = LayoutInflater.from(activity); @@ -48,7 +47,7 @@ public class AndroidTextFieldDialog{ alertDialogBuilder.setView(promptsView); - userInput = (EditText) promptsView.findViewById(getResourceId("gdxDialogsEditTextInput", "id")); + userInput = promptsView.findViewById(getResourceId("gdxDialogsEditTextInput", "id")); alertDialogBuilder.setCancelable(false); builder = alertDialogBuilder; @@ -56,64 +55,65 @@ public class AndroidTextFieldDialog{ isBuild = true; }); - // Wait till TextPrompt is built. - while (!isBuild) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { } - } + // Wait till TextPrompt is built. + while(!isBuild){ + try{ + Thread.sleep(10); + }catch(InterruptedException e){ + } + } - return this; - } + return this; + } - public int getResourceId(String pVariableName, String pVariableType) { - try { - return activity.getResources().getIdentifier(pVariableName, pVariableType, activity.getPackageName()); - } catch (Exception e) { - Gdx.app.error("Android Dialogs", "Cannot find resouce with name: " + pVariableName - + " Did you copy the layouts to /res/layouts and /res/layouts_v14 ?"); - e.printStackTrace(); - return -1; - } - } + public int getResourceId(String pVariableName, String pVariableType){ + try{ + return activity.getResources().getIdentifier(pVariableName, pVariableType, activity.getPackageName()); + }catch(Exception e){ + Gdx.app.error("Android Dialogs", "Cannot find resouce with name: " + pVariableName + + " Did you copy the layouts to /res/layouts and /res/layouts_v14 ?"); + e.printStackTrace(); + return -1; + } + } - public AndroidTextFieldDialog setText(CharSequence value) { - userInput.append(value); - return this; - } + public AndroidTextFieldDialog setText(CharSequence value){ + userInput.append(value); + return this; + } - public AndroidTextFieldDialog setCancelButtonLabel(CharSequence label) { - builder.setNegativeButton(label, (dialog, id) -> dialog.cancel()); - return this; - } + public AndroidTextFieldDialog setCancelButtonLabel(CharSequence label){ + builder.setNegativeButton(label, (dialog, id) -> dialog.cancel()); + return this; + } - public AndroidTextFieldDialog setConfirmButtonLabel(CharSequence label) { - builder.setPositiveButton(label, (dialog, id) -> { - if (listener != null && !userInput.getText().toString().isEmpty()) { + public AndroidTextFieldDialog setConfirmButtonLabel(CharSequence label){ + builder.setPositiveButton(label, (dialog, id) -> { + if(listener != null && !userInput.getText().toString().isEmpty()){ listener.confirm(userInput.getText().toString()); } }); - return this; - } + return this; + } - public AndroidTextFieldDialog setTextPromptListener(TextPromptListener listener) { - this.listener = listener; - return this; - } + public AndroidTextFieldDialog setTextPromptListener(TextPromptListener listener){ + this.listener = listener; + return this; + } - public AndroidTextFieldDialog setInputType(int type) { - userInput.setInputType(type); - return this; - } + public AndroidTextFieldDialog setInputType(int type){ + userInput.setInputType(type); + return this; + } - public AndroidTextFieldDialog setMaxLength(int length) { - userInput.setFilters(new InputFilter[] { new InputFilter.LengthFilter(length) }); - return this; - } - - public interface TextPromptListener{ - void confirm(String text); - } + public AndroidTextFieldDialog setMaxLength(int length){ + userInput.setFilters(new InputFilter[]{new InputFilter.LengthFilter(length)}); + return this; + } + + public interface TextPromptListener{ + void confirm(String text); + } } diff --git a/android/src/io/anuke/mindustry/DonationsActivity.java b/android/src/io/anuke/mindustry/DonationsActivity.java index 690ccc3413..c6e61c61b2 100644 --- a/android/src/io/anuke/mindustry/DonationsActivity.java +++ b/android/src/io/anuke/mindustry/DonationsActivity.java @@ -8,12 +8,9 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.View; import android.widget.Button; - import org.sufficientlysecure.donations.DonationsFragment; -public class DonationsActivity extends FragmentActivity { - DonationsFragment donationsFragment; - +public class DonationsActivity extends FragmentActivity{ /** * Google */ @@ -21,13 +18,14 @@ public class DonationsActivity extends FragmentActivity { private static final String[] GOOGLE_CATALOG = new String[]{ "mindustry.donation.1", "mindustry.donation.2", "mindustry.donation.5", "mindustry.donation.10", "mindustry.donation.15", - "mindustry.donation.25", "mindustry.donation.50" }; + "mindustry.donation.25", "mindustry.donation.50"}; + DonationsFragment donationsFragment; /** * Called when the activity is first created. */ @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setTheme(R.style.GdxTheme); @@ -35,7 +33,7 @@ public class DonationsActivity extends FragmentActivity { setContentView(R.layout.donations_activity); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - if (BuildConfig.DONATIONS_GOOGLE) { + if(BuildConfig.DONATIONS_GOOGLE){ donationsFragment = DonationsFragment.newInstance(BuildConfig.DEBUG, true, GOOGLE_PUBKEY, GOOGLE_CATALOG, getResources().getStringArray(R.array.donation_google_catalog_values), false, null, null, null, false, null, null, false, null); @@ -48,9 +46,10 @@ public class DonationsActivity extends FragmentActivity { public void onStart(){ super.onStart(); - Button b = ((Button)findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); - b.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View view) { + Button b = ((Button) findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); + b.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View view){ donationsFragment.donateGoogleOnClick(donationsFragment.getView()); b.setEnabled(false); } @@ -58,20 +57,19 @@ public class DonationsActivity extends FragmentActivity { } - /** * Needed for Google Play In-app Billing. It uses startIntentSenderForResult(). The result is not propagated to * the Fragment like in startActivityForResult(). Thus we need to propagate manually to our Fragment. */ @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(int requestCode, int resultCode, Intent data){ super.onActivityResult(requestCode, resultCode, data); - Button b = ((Button)findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); + Button b = ((Button) findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button)); b.setEnabled(true); FragmentManager fragmentManager = getSupportFragmentManager(); Fragment fragment = fragmentManager.findFragmentByTag("donationsFragment"); - if (fragment != null) { + if(fragment != null){ fragment.onActivityResult(requestCode, resultCode, data); //TODO donation event, set settings? } diff --git a/android/src/io/anuke/mindustry/TextFieldDialogListener.java b/android/src/io/anuke/mindustry/TextFieldDialogListener.java index c6f328d785..be1cc6d924 100644 --- a/android/src/io/anuke/mindustry/TextFieldDialogListener.java +++ b/android/src/io/anuke/mindustry/TextFieldDialogListener.java @@ -11,57 +11,57 @@ import io.anuke.ucore.scene.event.InputListener; import io.anuke.ucore.scene.ui.TextField; public class TextFieldDialogListener extends ClickListener{ - private TextField field; - private int type; - private int max; + private TextField field; + private int type; + private int max; - public static void add(TextField field, int type, int max){ - field.addListener(new TextFieldDialogListener(field, type, max)); - field.addListener(new InputListener(){ - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - Gdx.input.setOnscreenKeyboardVisible(false); - return false; - } - }); - } + //type - 0 is text, 1 is numbers, 2 is decimals + public TextFieldDialogListener(TextField field, int type, int max){ + this.field = field; + this.type = type; + this.max = max; + } - public static void add(TextField field){ - add(field, 0, 16); - } + public static void add(TextField field, int type, int max){ + field.addListener(new TextFieldDialogListener(field, type, max)); + field.addListener(new InputListener(){ + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ + Gdx.input.setOnscreenKeyboardVisible(false); + return false; + } + }); + } - //type - 0 is text, 1 is numbers, 2 is decimals - public TextFieldDialogListener(TextField field, int type, int max){ - this.field = field; - this.type = type; - this.max = max; - } + public static void add(TextField field){ + add(field, 0, 16); + } - public void clicked(final InputEvent event, float x, float y){ - - if(Gdx.app.getType() == ApplicationType.Desktop) return; - - AndroidTextFieldDialog dialog = new AndroidTextFieldDialog(); + public void clicked(final InputEvent event, float x, float y){ - dialog.setTextPromptListener(text -> { + if(Gdx.app.getType() == ApplicationType.Desktop) return; + + AndroidTextFieldDialog dialog = new AndroidTextFieldDialog(); + + dialog.setTextPromptListener(text -> { field.clearText(); field.appendText(text); field.fire(new ChangeListener.ChangeEvent()); Gdx.graphics.requestRendering(); }); - if(type == 0){ - dialog.setInputType(InputType.TYPE_CLASS_TEXT); - }else if(type == 1){ - dialog.setInputType(InputType.TYPE_CLASS_NUMBER); - }else if(type == 2){ - dialog.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL); - } + if(type == 0){ + dialog.setInputType(InputType.TYPE_CLASS_TEXT); + }else if(type == 1){ + dialog.setInputType(InputType.TYPE_CLASS_NUMBER); + }else if(type == 2){ + dialog.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL); + } - dialog.setConfirmButtonLabel("OK").setText(field.getText()); - dialog.setCancelButtonLabel("Cancel"); - dialog.setMaxLength(max); - dialog.show(); - event.cancel(); + dialog.setConfirmButtonLabel("OK").setText(field.getText()); + dialog.setCancelButtonLabel("Cancel"); + dialog.setMaxLength(max); + dialog.show(); + event.cancel(); - } + } } diff --git a/annotations/build.gradle b/annotations/build.gradle new file mode 100644 index 0000000000..afd5e6ab73 --- /dev/null +++ b/annotations/build.gradle @@ -0,0 +1,4 @@ +apply plugin: "java" + +sourceCompatibility = 1.8 +sourceSets.main.java.srcDirs = [ "src/" ] diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java new file mode 100644 index 0000000000..cc60478fee --- /dev/null +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -0,0 +1,111 @@ +package io.anuke.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Goal: To create a system to send events to the server from the client and vice versa, without creating a new packet type each time.
+ * These events may optionally also trigger on the caller client/server as well.
+ */ +public class Annotations{ + + public enum PacketPriority{ + /** Gets put in a queue and processed if not connected. */ + normal, + /** Gets handled immediately, regardless of connection status. */ + high, + /** Does not get handled unless client is connected. */ + low + } + + /** A set of two booleans, one specifying server and one specifying client. */ + public enum Loc{ + /** Method can only be invoked on the client from the server. */ + server(true, false), + /** Method can only be invoked on the server from the client. */ + client(false, true), + /** Method can be invoked from anywhere */ + both(true, true), + /** Neither server nor client. */ + none(false, false); + + /** If true, this method can be invoked ON clients FROM servers. */ + public final boolean isServer; + /** If true, this method can be invoked ON servers FROM clients. */ + public final boolean isClient; + + Loc(boolean server, boolean client){ + this.isServer = server; + this.isClient = client; + } + } + + public enum Variant{ + /** Method can only be invoked targeting one player. */ + one(true, false), + /** Method can only be invoked targeting all players. */ + all(false, true), + /** Method targets both one player and all players. */ + both(true, true); + + public final boolean isOne, isAll; + + Variant(boolean isOne, boolean isAll){ + this.isOne = isOne; + this.isAll = isAll; + } + } + + /** Marks a method as invokable remotely across a server/client connection. */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface Remote{ + /** Specifies the locations from which this method can be invoked. */ + Loc targets() default Loc.server; + + /** Specifies which methods are generated. Only affects server-to-client methods. */ + Variant variants() default Variant.all; + + /** The local locations where this method is called locally, when invoked. */ + Loc called() default Loc.none; + + /** Whether to forward this packet to all other clients upon recieval. Client only. */ + boolean forward() default false; + + /** + * Whether the packet for this method is sent with UDP instead of TCP. + * UDP is faster, but is prone to packet loss and duplication. + */ + boolean unreliable() default false; + + /** The simple class name where this method is placed. */ + String in() default "Call"; + + /** Priority of this event. */ + PacketPriority priority() default PacketPriority.normal; + } + + /** + * Specifies that this method will be used to write classes of the type returned by {@link #value()}.
+ * This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second + * being the type returned by {@link #value()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface WriteClass{ + Class value(); + } + + /** + * Specifies that this method will be used to read classes of the type returned by {@link #value()}.
+ * This method must return the type returned by {@link #value()}, + * and have one parameter, being of type {@link java.nio.ByteBuffer}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.CLASS) + public @interface ReadClass{ + Class value(); + } +} diff --git a/annotations/src/io/anuke/annotations/ClassEntry.java b/annotations/src/io/anuke/annotations/ClassEntry.java new file mode 100644 index 0000000000..a9be2ec134 --- /dev/null +++ b/annotations/src/io/anuke/annotations/ClassEntry.java @@ -0,0 +1,15 @@ +package io.anuke.annotations; + +import java.util.ArrayList; + +/** Represents a class witha list method entries to include in it. */ +public class ClassEntry{ + /** All methods in this generated class. */ + public final ArrayList methods = new ArrayList<>(); + /** Simple class name. */ + public final String name; + + public ClassEntry(String name){ + this.name = name; + } +} diff --git a/annotations/src/io/anuke/annotations/IOFinder.java b/annotations/src/io/anuke/annotations/IOFinder.java new file mode 100644 index 0000000000..9276b9b887 --- /dev/null +++ b/annotations/src/io/anuke/annotations/IOFinder.java @@ -0,0 +1,90 @@ +package io.anuke.annotations; + +import io.anuke.annotations.Annotations.ReadClass; +import io.anuke.annotations.Annotations.WriteClass; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.type.MirroredTypeException; +import javax.tools.Diagnostic.Kind; +import java.util.HashMap; +import java.util.Set; + +/** + * This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass} + * and {@link io.anuke.annotations.Annotations.ReadClass} annotations. + */ +public class IOFinder{ + + /** + * Finds all class serializers for all types and returns them. Logs errors when necessary. + * Maps fully qualified class names to their serializers. + */ + public HashMap findSerializers(RoundEnvironment env){ + HashMap result = new HashMap<>(); + + //get methods with the types + Set writers = env.getElementsAnnotatedWith(WriteClass.class); + Set readers = env.getElementsAnnotatedWith(ReadClass.class); + + //look for writers first + for(Element writer : writers){ + WriteClass writean = writer.getAnnotation(WriteClass.class); + String typeName = getValue(writean); + + //make sure there's only one read method + if(readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count() > 1){ + Utils.messager.printMessage(Kind.ERROR, "Multiple writer methods for type '" + typeName + "'", writer); + } + + //make sure there's only one write method + long count = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count(); + if(count == 0){ + Utils.messager.printMessage(Kind.ERROR, "Writer method does not have an accompanying reader: ", writer); + }else if(count > 1){ + Utils.messager.printMessage(Kind.ERROR, "Writer method has multiple reader for type: ", writer); + } + + Element reader = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).findFirst().get(); + + //add to result list + result.put(typeName, new ClassSerializer(Utils.getMethodName(reader), Utils.getMethodName(writer), typeName)); + } + + return result; + } + + private String getValue(WriteClass write){ + try{ + Class type = write.value(); + return type.getName(); + }catch(MirroredTypeException e){ + return e.getTypeMirror().toString(); + } + } + + private String getValue(ReadClass read){ + try{ + Class type = read.value(); + return type.getName(); + }catch(MirroredTypeException e){ + return e.getTypeMirror().toString(); + } + } + + /** Information about read/write methods for a specific class type. */ + public static class ClassSerializer{ + /** Fully qualified method name of the reader. */ + public final String readMethod; + /** Fully qualified method name of the writer. */ + public final String writeMethod; + /** Fully qualified class type name. */ + public final String classType; + + public ClassSerializer(String readMethod, String writeMethod, String classType){ + this.readMethod = readMethod; + this.writeMethod = writeMethod; + this.classType = classType; + } + } +} diff --git a/annotations/src/io/anuke/annotations/MethodEntry.java b/annotations/src/io/anuke/annotations/MethodEntry.java new file mode 100644 index 0000000000..3566516372 --- /dev/null +++ b/annotations/src/io/anuke/annotations/MethodEntry.java @@ -0,0 +1,53 @@ +package io.anuke.annotations; + +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.PacketPriority; +import io.anuke.annotations.Annotations.Variant; + +import javax.lang.model.element.ExecutableElement; + +/** Class that repesents a remote method to be constructed and put into a class. */ +public class MethodEntry{ + /** Simple target class name. */ + public final String className; + /** Fully qualified target method to call. */ + public final String targetMethod; + /** Whether this method can be called on a client/server. */ + public final Loc where; + /** + * Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true. + * Only applicable to client (server-invoked) methods. + */ + public final Variant target; + /** Whether this method is called locally as well as remotely. */ + public final Loc local; + /** Whether this method is unreliable and uses UDP. */ + public final boolean unreliable; + /** Whether to forward this method call to all other clients when a client invokes it. Server only. */ + public final boolean forward; + /** Unique method ID. */ + public final int id; + /** The element method associated with this entry. */ + public final ExecutableElement element; + /** The assigned packet priority. Only used in clients. */ + public final PacketPriority priority; + + public MethodEntry(String className, String targetMethod, Loc where, Variant target, + Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){ + this.className = className; + this.forward = forward; + this.targetMethod = targetMethod; + this.where = where; + this.target = target; + this.local = local; + this.id = id; + this.element = element; + this.unreliable = unreliable; + this.priority = priority; + } + + @Override + public int hashCode(){ + return targetMethod.hashCode(); + } +} diff --git a/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java new file mode 100644 index 0000000000..b0402b2c55 --- /dev/null +++ b/annotations/src/io/anuke/annotations/RemoteMethodAnnotationProcessor.java @@ -0,0 +1,157 @@ +package io.anuke.annotations; + +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.IOFinder.ClassSerializer; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import java.util.*; +import java.util.stream.Collectors; + + +/** The annotation processor for generating remote method call code. */ +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedAnnotationTypes({ + "io.anuke.annotations.Annotations.Remote", + "io.anuke.annotations.Annotations.WriteClass", + "io.anuke.annotations.Annotations.ReadClass", +}) +public class RemoteMethodAnnotationProcessor extends AbstractProcessor{ + /** Maximum size of each event packet. */ + public static final int maxPacketSize = 4096; + /** Name of the base package to put all the generated classes. */ + private static final String packageName = "io.anuke.mindustry.gen"; + + /** Name of class that handles reading and invoking packets on the server. */ + private static final String readServerName = "RemoteReadServer"; + /** Name of class that handles reading and invoking packets on the client. */ + private static final String readClientName = "RemoteReadClient"; + + /** Processing round number. */ + private int round; + + //class serializers + private HashMap serializers; + //all elements with the Remote annotation + private Set elements; + //map of all classes to generate by name + private HashMap classMap; + //list of all method entries + private ArrayList methods; + //list of all method entries + private ArrayList classes; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv){ + super.init(processingEnv); + //put all relevant utils into utils class + Utils.typeUtils = processingEnv.getTypeUtils(); + Utils.elementUtils = processingEnv.getElementUtils(); + Utils.filer = processingEnv.getFiler(); + Utils.messager = processingEnv.getMessager(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv){ + if(round > 1) return false; //only process 2 rounds + + round++; + + try{ + + //round 1: find all annotations, generate *writers* + if(round == 1){ + //get serializers + serializers = new IOFinder().findSerializers(roundEnv); + + //last method ID used + int lastMethodID = 0; + //find all elements with the Remote annotation + elements = roundEnv.getElementsAnnotatedWith(Remote.class); + //map of all classes to generate by name + classMap = new HashMap<>(); + //list of all method entries + methods = new ArrayList<>(); + //list of all method entries + classes = new ArrayList<>(); + + List orderedElements = new ArrayList<>(elements); + orderedElements.sort(Comparator.comparing(Object::toString)); + + //create methods + for(Element element : orderedElements){ + Remote annotation = element.getAnnotation(Remote.class); + + //check for static + if(!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)){ + Utils.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element); + } + + //can't generate none methods + if(annotation.targets() == Loc.none){ + Utils.messager.printMessage(Kind.ERROR, "A @Remote method's targets() cannot be equal to 'none':", element); + } + + //get and create class entry if needed + if(!classMap.containsKey(annotation.in())){ + ClassEntry clas = new ClassEntry(annotation.in()); + classMap.put(annotation.in(), clas); + classes.add(clas); + + Utils.messager.printMessage(Kind.NOTE, "Generating class '" + clas.name + "'."); + } + + ClassEntry entry = classMap.get(annotation.in()); + + //create and add entry + MethodEntry method = new MethodEntry(entry.name, Utils.getMethodName(element), annotation.targets(), annotation.variants(), + annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, (ExecutableElement) element, annotation.priority()); + + entry.methods.add(method); + methods.add(method); + } + + //create read/write generators + RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); + + //generate the methods to invoke (write) + writegen.generateFor(classes, packageName); + + return true; + }else if(round == 2){ //round 2: generate all *readers* + RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); + + //generate server readers + readgen.generateFor(methods.stream().filter(method -> method.where.isClient).collect(Collectors.toList()), readServerName, packageName, true); + //generate client readers + readgen.generateFor(methods.stream().filter(method -> method.where.isServer).collect(Collectors.toList()), readClientName, packageName, false); + + //create class for storing unique method hash + TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC); + hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) + .initializer("$1L", Objects.hash(methods)).build()); + + //build and write resulting hash class + TypeSpec spec = hashBuilder.build(); + JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + + return true; + } + + }catch(Exception e){ + e.printStackTrace(); + throw new RuntimeException(e); + } + + return false; + } +} diff --git a/annotations/src/io/anuke/annotations/RemoteReadGenerator.java b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java new file mode 100644 index 0000000000..5898e369a1 --- /dev/null +++ b/annotations/src/io/anuke/annotations/RemoteReadGenerator.java @@ -0,0 +1,146 @@ +package io.anuke.annotations; + +import com.squareup.javapoet.*; +import io.anuke.annotations.IOFinder.ClassSerializer; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.tools.Diagnostic.Kind; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; + +/** Generates code for reading remote invoke packets on the client and server. */ +public class RemoteReadGenerator{ + private final HashMap serializers; + + /** Creates a read generator that uses the supplied serializer setup. */ + public RemoteReadGenerator(HashMap serializers){ + this.serializers = serializers; + } + + /** + * Generates a class for reading remote invoke packets. + * + * @param entries List of methods to use/ + * @param className Simple target class name. + * @param packageName Full target package name. + * @param needsPlayer Whether this read method requires a reference to the player sender. + */ + public void generateFor(List entries, String className, String packageName, boolean needsPlayer) + throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException{ + + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC); + + //create main method builder + MethodSpec.Builder readMethod = MethodSpec.methodBuilder("readPacket") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ByteBuffer.class, "buffer") //buffer to read form + .addParameter(int.class, "id") //ID of method type to read + .returns(void.class); + + if(needsPlayer){ + //since the player type isn't loaded yet, creating a type def is necessary + //this requires reflection since the TypeName constructor is private for some reason + Constructor cons = TypeName.class.getDeclaredConstructor(String.class); + cons.setAccessible(true); + + TypeName playerType = cons.newInstance("io.anuke.mindustry.entities.Player"); + //add player parameter + readMethod.addParameter(playerType, "player"); + } + + CodeBlock.Builder readBlock = CodeBlock.builder(); //start building block of code inside read method + boolean started = false; //whether an if() statement has been written yet + + for(MethodEntry entry : entries){ + //write if check for this entry ID + if(!started){ + started = true; + readBlock.beginControlFlow("if(id == " + entry.id + ")"); + }else{ + readBlock.nextControlFlow("else if(id == " + entry.id + ")"); + } + + readBlock.beginControlFlow("try"); + + //concatenated list of variable names for method invocation + StringBuilder varResult = new StringBuilder(); + + //go through each parameter + for(int i = 0; i < entry.element.getParameters().size(); i++){ + VariableElement var = entry.element.getParameters().get(i); + + if(!needsPlayer || i != 0){ //if client, skip first parameter since it's always of type player and doesn't need to be read + //full type name of parameter + String typeName = var.asType().toString(); + //name of parameter + String varName = var.getSimpleName().toString(); + //captialized version of type name for reading primitives + String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); + + //write primitives automatically + if(Utils.isPrimitive(typeName)){ + if(typeName.equals("boolean")){ + readBlock.addStatement("boolean " + varName + " = buffer.get() == 1"); + }else{ + readBlock.addStatement(typeName + " " + varName + " = buffer.get" + capName + "()"); + } + }else{ + //else, try and find a serializer + ClassSerializer ser = serializers.get(typeName); + + if(ser == null){ //make sure a serializer exists! + Utils.messager.printMessage(Kind.ERROR, "No @ReadClass method to read class type: '" + typeName + "'", var); + return; + } + + //add statement for reading it + readBlock.addStatement(typeName + " " + varName + " = " + ser.readMethod + "(buffer)"); + } + + //append variable name to string builder + varResult.append(var.getSimpleName()); + if(i != entry.element.getParameters().size() - 1) varResult.append(", "); + }else{ + varResult.append("player"); + if(i != entry.element.getParameters().size() - 1) varResult.append(", "); + } + } + + //execute the relevant method before the forward + //if it throws a ValidateException, the method won't be forwarded + readBlock.addStatement("$N." + entry.element.getSimpleName() + "(" + varResult.toString() + ")", ((TypeElement) entry.element.getEnclosingElement()).getQualifiedName().toString()); + + //call forwarded method, don't forward on the client reader + if(entry.forward && entry.where.isServer && needsPlayer){ + //call forwarded method + readBlock.addStatement(packageName + "." + entry.className + "." + entry.element.getSimpleName() + + "__forward(player.con.id" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")"); + } + + readBlock.nextControlFlow("catch (java.lang.Exception e)"); + readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to to read remote method '" + entry.element.getSimpleName() + "'!\", e)"); + readBlock.endControlFlow(); + } + + //end control flow if necessary + if(started){ + readBlock.nextControlFlow("else"); + readBlock.addStatement("throw new $1N(\"Invalid read method ID: \" + id + \"\")", RuntimeException.class.getName()); //handle invalid method IDs + readBlock.endControlFlow(); + } + + //add block and method to class + readMethod.addCode(readBlock.build()); + classBuilder.addMethod(readMethod.build()); + + //build and write resulting class + TypeSpec spec = classBuilder.build(); + JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + } +} diff --git a/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java new file mode 100644 index 0000000000..86502f96a3 --- /dev/null +++ b/annotations/src/io/anuke/annotations/RemoteWriteGenerator.java @@ -0,0 +1,226 @@ +package io.anuke.annotations; + +import com.squareup.javapoet.*; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.IOFinder.ClassSerializer; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.tools.Diagnostic.Kind; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; + +/** Generates code for writing remote invoke packets on the client and server. */ +public class RemoteWriteGenerator{ + private final HashMap serializers; + + /** Creates a write generator that uses the supplied serializer setup. */ + public RemoteWriteGenerator(HashMap serializers){ + this.serializers = serializers; + } + + /** Generates all classes in this list. */ + public void generateFor(List entries, String packageName) throws IOException{ + + for(ClassEntry entry : entries){ + //create builder + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(entry.name).addModifiers(Modifier.PUBLIC); + + //add temporary write buffer + classBuilder.addField(FieldSpec.builder(ByteBuffer.class, "TEMP_BUFFER", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL) + .initializer("ByteBuffer.allocate($1L)", RemoteMethodAnnotationProcessor.maxPacketSize).build()); + + //go through each method entry in this class + for(MethodEntry methodEntry : entry.methods){ + //write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method + if(methodEntry.where.isClient || methodEntry.target.isAll){ + writeMethodVariant(classBuilder, methodEntry, true, false); + } + + //write the 'send event to one player' variant, which is only applicable on the server + if(methodEntry.where.isServer && methodEntry.target.isOne){ + writeMethodVariant(classBuilder, methodEntry, false, false); + } + + //write the forwarded method version + if(methodEntry.where.isServer && methodEntry.forward){ + writeMethodVariant(classBuilder, methodEntry, true, true); + } + } + + //build and write resulting class + TypeSpec spec = classBuilder.build(); + JavaFile.builder(packageName, spec).build().writeTo(Utils.filer); + } + } + + /** Creates a specific variant for a method entry. */ + private void writeMethodVariant(TypeSpec.Builder classBuilder, MethodEntry methodEntry, boolean toAll, boolean forwarded){ + ExecutableElement elem = methodEntry.element; + + //create builder + MethodSpec.Builder method = MethodSpec.methodBuilder(elem.getSimpleName().toString() + (forwarded ? "__forward" : "")) //add except suffix when forwarding + .addModifiers(Modifier.STATIC, Modifier.SYNCHRONIZED) + .returns(void.class); + + //forwarded methods aren't intended for use, and are not public + if(!forwarded){ + method.addModifiers(Modifier.PUBLIC); + } + + //validate client methods to make sure + if(methodEntry.where.isClient){ + if(elem.getParameters().isEmpty()){ + Utils.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem); + return; + } + + if(!elem.getParameters().get(0).asType().toString().equals("io.anuke.mindustry.entities.Player")){ + Utils.messager.printMessage(Kind.ERROR, "Client invoke methods should have a first parameter of type Player.", elem); + return; + } + } + + //if toAll is false, it's a 'send to one player' variant, so add the player as a parameter + if(!toAll){ + method.addParameter(int.class, "playerClientID"); + } + + //add sender to ignore + if(forwarded){ + method.addParameter(int.class, "exceptSenderID"); + } + + //call local method if applicable, shouldn't happen when forwarding method as that already happens by default + if(!forwarded && methodEntry.local != Loc.none){ + //add in local checks + if(methodEntry.local != Loc.both){ + method.beginControlFlow("if(" + getCheckString(methodEntry.local) + " || !io.anuke.mindustry.net.Net.active())"); + } + + //concatenate parameters + int index = 0; + StringBuilder results = new StringBuilder(); + for(VariableElement var : elem.getParameters()){ + //special case: calling local-only methods uses the local player + if(index == 0 && methodEntry.where == Loc.client){ + results.append("io.anuke.mindustry.Vars.players[0]"); + }else{ + results.append(var.getSimpleName()); + } + if(index != elem.getParameters().size() - 1) results.append(", "); + index++; + } + + //add the statement to call it + method.addStatement("$N." + elem.getSimpleName() + "(" + results.toString() + ")", + ((TypeElement) elem.getEnclosingElement()).getQualifiedName().toString()); + + if(methodEntry.local != Loc.both){ + method.endControlFlow(); + } + } + + //start control flow to check if it's actually client/server so no netcode is called + method.beginControlFlow("if(" + getCheckString(methodEntry.where) + ")"); + + //add statement to create packet from pool + method.addStatement("$1N packet = $2N.obtain($1N.class)", "io.anuke.mindustry.net.Packets.InvokePacket", "io.anuke.ucore.util.Pooling"); + //assign buffer + method.addStatement("packet.writeBuffer = TEMP_BUFFER"); + //assign priority + method.addStatement("packet.priority = (byte)" + methodEntry.priority.ordinal()); + //assign method ID + method.addStatement("packet.type = (byte)" + methodEntry.id); + //rewind buffer + method.addStatement("TEMP_BUFFER.position(0)"); + + for(int i = 0; i < elem.getParameters().size(); i++){ + //first argument is skipped as it is always the player caller + if((!methodEntry.where.isServer/* || methodEntry.mode == Loc.both*/) && i == 0){ + continue; + } + + VariableElement var = elem.getParameters().get(i); + + //add parameter to method + method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString()); + + //name of parameter + String varName = var.getSimpleName().toString(); + //name of parameter type + String typeName = var.asType().toString(); + //captialized version of type name for writing primitives + String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); + //special case: method can be called from anywhere to anywhere + //thus, only write the player when the SERVER is writing data, since the client is the only one who reads it + boolean writePlayerSkipCheck = methodEntry.where == Loc.both && i == 0; + + if(writePlayerSkipCheck){ //write begin check + method.beginControlFlow("if(io.anuke.mindustry.net.Net.server())"); + } + + if(Utils.isPrimitive(typeName)){ //check if it's a primitive, and if so write it + if(typeName.equals("boolean")){ //booleans are special + method.addStatement("TEMP_BUFFER.put(" + varName + " ? (byte)1 : 0)"); + }else{ + method.addStatement("TEMP_BUFFER.put" + + capName + "(" + varName + ")"); + } + }else{ + //else, try and find a serializer + ClassSerializer ser = serializers.get(typeName); + + if(ser == null){ //make sure a serializer exists! + Utils.messager.printMessage(Kind.ERROR, "No @WriteClass method to write class type: '" + typeName + "'", var); + return; + } + + //add statement for writing it + method.addStatement(ser.writeMethod + "(TEMP_BUFFER, " + varName + ")"); + } + + if(writePlayerSkipCheck){ //write end check + method.endControlFlow(); + } + } + + //assign packet length + method.addStatement("packet.writeLength = TEMP_BUFFER.position()"); + + String sendString; + + if(forwarded){ //forward packet + if(!methodEntry.local.isClient){ //if the client doesn't get it called locally, forward it back after validation + sendString = "send("; + }else{ + sendString = "sendExcept(exceptSenderID, "; + } + }else if(toAll){ //send to all players / to server + sendString = "send("; + }else{ //send to specific client from server + sendString = "sendTo(playerClientID, "; + } + + //send the actual packet + method.addStatement("io.anuke.mindustry.net.Net." + sendString + "packet, " + + (methodEntry.unreliable ? "io.anuke.mindustry.net.Net.SendMode.udp" : "io.anuke.mindustry.net.Net.SendMode.tcp") + ")"); + + + //end check for server/client + method.endControlFlow(); + + //add method to class, finally + classBuilder.addMethod(method.build()); + } + + private String getCheckString(Loc loc){ + return loc.isClient && loc.isServer ? "io.anuke.mindustry.net.Net.server() || io.anuke.mindustry.net.Net.client()" : + loc.isClient ? "io.anuke.mindustry.net.Net.client()" : + loc.isServer ? "io.anuke.mindustry.net.Net.server()" : "false"; + } +} diff --git a/annotations/src/io/anuke/annotations/Utils.java b/annotations/src/io/anuke/annotations/Utils.java new file mode 100644 index 0000000000..551d953bc4 --- /dev/null +++ b/annotations/src/io/anuke/annotations/Utils.java @@ -0,0 +1,24 @@ +package io.anuke.annotations; + +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +public class Utils{ + public static Types typeUtils; + public static Elements elementUtils; + public static Filer filer; + public static Messager messager; + + public static String getMethodName(Element element){ + return ((TypeElement) element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); + } + + public static boolean isPrimitive(String type){ + return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int") + || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char"); + } +} diff --git a/build.gradle b/build.gradle index 8bce5a80b6..ec66d5463b 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { dependencies { classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.0' classpath 'de.richsource.gradle.plugins:gwt-gradle-plugin:0.6' - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.3' classpath "com.badlogicgames.gdx:gdx-tools:1.9.8" } } @@ -21,13 +21,13 @@ allprojects { version = 'release' ext { - versionNumber = '3.5' - versionType = 'release' + versionNumber = '4.0' + versionType = 'alpha' appName = 'Mindustry' gdxVersion = '1.9.8' roboVMVersion = '2.3.0' aiVersion = '1.8.1' - uCoreVersion = 'd5af97f93813d8767423521b1fcc5a5e0f7241d9' + uCoreVersion = 'c502931313' getVersionString = { String buildVersion = getBuildVersion() @@ -86,6 +86,7 @@ project(":desktop") { compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" compile "com.badlogicgames.gdx:gdx-controllers-lwjgl3:$gdxVersion" compile 'com.github.MinnDevelopment:java-discord-rpc:v2.0.0' + compile 'com.yuvimasory:orange-extensions:1.3.0' } } @@ -96,6 +97,8 @@ project(":html") { dependencies { compile project(":core") + compileOnly project(":annotations") + compile "com.badlogicgames.gdx:gdx-backend-gwt:$gdxVersion" compile "com.badlogicgames.gdx:gdx:$gdxVersion:sources" compile "com.badlogicgames.gdx:gdx-backend-gwt:$gdxVersion:sources" @@ -107,25 +110,10 @@ project(":html") { compile "com.sksamuel.gwt:gwt-websockets:1.0.4" compile "com.sksamuel.gwt:gwt-websockets:1.0.4:sources" } -} -project(":android") { - apply plugin: "android" - - configurations { natives } - - dependencies { - implementation project(":core") - implementation project(":kryonet") - implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" - natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" - implementation "com.badlogicgames.gdx:gdx-ai:$aiVersion" - implementation "com.badlogicgames.gdx:gdx-controllers-android:$gdxVersion" - } + compileJava.options.compilerArgs = [ + "-processor", "io.anuke.annotations.RemoteMethodAnnotationProcessor" + ] } project(":ios") { @@ -142,16 +130,14 @@ project(":ios") { compile "com.badlogicgames.gdx:gdx-backend-robovm:$gdxVersion" compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-ios" } - - robovm { - - } } project(":core") { apply plugin: "java" dependencies { + compileOnly project(":annotations") + boolean comp = System.properties["release"] == null || System.properties["release"] == "false" if(!comp){ @@ -160,25 +146,35 @@ project(":core") { println("Compiling DEBUG build.") } - if(new File('../uCore').exists() && comp){ + if(new File(projectDir.parent, '../uCore').exists() && comp){ compile project(":uCore") }else{ compile "com.github.anuken:ucore:$uCoreVersion" } - if(new File('../GDXGifRecorder').exists() && comp) { + if(new File(projectDir.parent, '../GDXGifRecorder').exists() && comp) { compile project(":GDXGifRecorder") } + compile "com.badlogicgames.gdx:gdx:$gdxVersion" - compile "com.badlogicgames.gdx:gdx-ai:$aiVersion" compile "com.badlogicgames.gdx:gdx-controllers:$gdxVersion" } + + compileJava.options.compilerArgs = [ + "-processor", "io.anuke.annotations.RemoteMethodAnnotationProcessor" + ] } project(":server") { apply plugin: "java" + configurations { + compile.exclude module: android + } + dependencies { + compileOnly project(":annotations") + compile project(":core") compile project(":kryonet") compile "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion" @@ -186,6 +182,22 @@ project(":server") { } } +project(":packer") { + apply plugin: "java" + + dependencies { + compile project(":core") + } +} + +project(":annotations") { + apply plugin: "java" + + dependencies { + compile 'com.squareup:javapoet:1.11.0' + } +} + project(":kryonet") { apply plugin: "java" diff --git a/core/assets-raw/sprites/blocks/blackstoneblock1.png b/core/assets-raw/sprites/blocks/blackstoneblock1.png deleted file mode 100644 index ceac507a11..0000000000 Binary files a/core/assets-raw/sprites/blocks/blackstoneblock1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/blackstoneblock2.png b/core/assets-raw/sprites/blocks/blackstoneblock2.png deleted file mode 100644 index 0e804da468..0000000000 Binary files a/core/assets-raw/sprites/blocks/blackstoneblock2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/blackstoneblock3.png b/core/assets-raw/sprites/blocks/blackstoneblock3.png deleted file mode 100644 index ebe4fb638d..0000000000 Binary files a/core/assets-raw/sprites/blocks/blackstoneblock3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/block-2x2.png b/core/assets-raw/sprites/blocks/block-2x2.png deleted file mode 100644 index 1616811924..0000000000 Binary files a/core/assets-raw/sprites/blocks/block-2x2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/block-3x3.png b/core/assets-raw/sprites/blocks/block-3x3.png deleted file mode 100644 index 2752d719a0..0000000000 Binary files a/core/assets-raw/sprites/blocks/block-3x3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/block.png b/core/assets-raw/sprites/blocks/block.png deleted file mode 100644 index 7fe4b2a3a9..0000000000 Binary files a/core/assets-raw/sprites/blocks/block.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/chainturret-icon.png b/core/assets-raw/sprites/blocks/chainturret-icon.png deleted file mode 100644 index e18f11a28e..0000000000 Binary files a/core/assets-raw/sprites/blocks/chainturret-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/chainturret-icon_old.png b/core/assets-raw/sprites/blocks/chainturret-icon_old.png deleted file mode 100644 index 12e4f5f7d5..0000000000 Binary files a/core/assets-raw/sprites/blocks/chainturret-icon_old.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/chainturret.png b/core/assets-raw/sprites/blocks/chainturret.png deleted file mode 100644 index 5ed8460c85..0000000000 Binary files a/core/assets-raw/sprites/blocks/chainturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/chainturret_old.png b/core/assets-raw/sprites/blocks/chainturret_old.png deleted file mode 100644 index 3b03c296f1..0000000000 Binary files a/core/assets-raw/sprites/blocks/chainturret_old.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coal1.png b/core/assets-raw/sprites/blocks/coal1.png deleted file mode 100644 index 8f784eff3c..0000000000 Binary files a/core/assets-raw/sprites/blocks/coal1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coal2.png b/core/assets-raw/sprites/blocks/coal2.png deleted file mode 100644 index 65b531eac5..0000000000 Binary files a/core/assets-raw/sprites/blocks/coal2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coal3.png b/core/assets-raw/sprites/blocks/coal3.png deleted file mode 100644 index 1457793e91..0000000000 Binary files a/core/assets-raw/sprites/blocks/coal3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coaldrill.png b/core/assets-raw/sprites/blocks/coaldrill.png deleted file mode 100644 index 1f633ad9ec..0000000000 Binary files a/core/assets-raw/sprites/blocks/coaldrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coalgenerator.png b/core/assets-raw/sprites/blocks/coalgenerator.png deleted file mode 100644 index 7500e84951..0000000000 Binary files a/core/assets-raw/sprites/blocks/coalgenerator.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/coalpurifier.png b/core/assets-raw/sprites/blocks/coalpurifier.png deleted file mode 100644 index a0ab6216b9..0000000000 Binary files a/core/assets-raw/sprites/blocks/coalpurifier.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/combustiongenerator.png b/core/assets-raw/sprites/blocks/combustiongenerator.png deleted file mode 100644 index 968b4224a7..0000000000 Binary files a/core/assets-raw/sprites/blocks/combustiongenerator.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/compositewall.png b/core/assets-raw/sprites/blocks/compositewall.png deleted file mode 100644 index a4bbc1c9cb..0000000000 Binary files a/core/assets-raw/sprites/blocks/compositewall.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conduit.png b/core/assets-raw/sprites/blocks/conduit.png deleted file mode 100644 index cccbd3da93..0000000000 Binary files a/core/assets-raw/sprites/blocks/conduit.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conduitbottom.png b/core/assets-raw/sprites/blocks/conduitbottom.png deleted file mode 100644 index a869a8cdae..0000000000 Binary files a/core/assets-raw/sprites/blocks/conduitbottom.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conduittop.png b/core/assets-raw/sprites/blocks/conduittop.png deleted file mode 100644 index 8a9894c8b5..0000000000 Binary files a/core/assets-raw/sprites/blocks/conduittop.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conveyor.png b/core/assets-raw/sprites/blocks/conveyor.png deleted file mode 100644 index 7f7f7e47d5..0000000000 Binary files a/core/assets-raw/sprites/blocks/conveyor.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conveyormove.png b/core/assets-raw/sprites/blocks/conveyormove.png deleted file mode 100644 index 2976fc1ae9..0000000000 Binary files a/core/assets-raw/sprites/blocks/conveyormove.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/conveyortunnel.png b/core/assets-raw/sprites/blocks/conveyortunnel.png deleted file mode 100644 index f1f4bf4e48..0000000000 Binary files a/core/assets-raw/sprites/blocks/conveyortunnel.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/crucible.png b/core/assets-raw/sprites/blocks/crucible.png deleted file mode 100644 index 42cb0367a3..0000000000 Binary files a/core/assets-raw/sprites/blocks/crucible.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-arrow.png b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-arrow.png new file mode 100644 index 0000000000..2d8e45aa34 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-arrow.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-bridge.png b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-bridge.png new file mode 100644 index 0000000000..3ec3a10f7e Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-bridge.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-end.png b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-end.png new file mode 100644 index 0000000000..c2d5660ad5 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor-end.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/bridge-conveyor.png b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor.png new file mode 100644 index 0000000000..ef1ade6a87 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/bridge-conveyor.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/conveyor.png b/core/assets-raw/sprites/blocks/distribution/conveyor.png new file mode 100644 index 0000000000..284b091968 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/conveyor.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/distributor.png b/core/assets-raw/sprites/blocks/distribution/distributor.png new file mode 100644 index 0000000000..b42760f027 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/distributor.png differ diff --git a/core/assets-raw/sprites/blocks/junction.png b/core/assets-raw/sprites/blocks/distribution/junction.png similarity index 100% rename from core/assets-raw/sprites/blocks/junction.png rename to core/assets-raw/sprites/blocks/distribution/junction.png diff --git a/core/assets-raw/sprites/blocks/distribution/mass-driver-turret.png b/core/assets-raw/sprites/blocks/distribution/mass-driver-turret.png new file mode 100644 index 0000000000..350e7c66f4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/mass-driver-turret.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/mass-driver.png b/core/assets-raw/sprites/blocks/distribution/mass-driver.png new file mode 100644 index 0000000000..f1d779149d Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/mass-driver.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/overflow-gate.png b/core/assets-raw/sprites/blocks/distribution/overflow-gate.png new file mode 100644 index 0000000000..e67780b897 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/overflow-gate.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/phase-conveyor-arrow.png b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-arrow.png new file mode 100644 index 0000000000..2b4b2e7d97 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-arrow.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/phase-conveyor-bridge.png b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-bridge.png new file mode 100644 index 0000000000..bfd04fb31f Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-bridge.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/phase-conveyor-end.png b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-end.png new file mode 100644 index 0000000000..3f6044d038 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/phase-conveyor-end.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/phase-conveyor.png b/core/assets-raw/sprites/blocks/distribution/phase-conveyor.png new file mode 100644 index 0000000000..380d216415 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/phase-conveyor.png differ diff --git a/core/assets-raw/sprites/blocks/router.png b/core/assets-raw/sprites/blocks/distribution/router.png similarity index 100% rename from core/assets-raw/sprites/blocks/router.png rename to core/assets-raw/sprites/blocks/distribution/router.png diff --git a/core/assets-raw/sprites/blocks/sorter.png b/core/assets-raw/sprites/blocks/distribution/sorter.png similarity index 100% rename from core/assets-raw/sprites/blocks/sorter.png rename to core/assets-raw/sprites/blocks/distribution/sorter.png diff --git a/core/assets-raw/sprites/blocks/distribution/splitter.png b/core/assets-raw/sprites/blocks/distribution/splitter.png new file mode 100644 index 0000000000..0d86dcb2a7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/splitter.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/titanium-conveyor.png b/core/assets-raw/sprites/blocks/distribution/titanium-conveyor.png new file mode 100644 index 0000000000..d25210902a Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/titanium-conveyor.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/warp-gate-top.png b/core/assets-raw/sprites/blocks/distribution/warp-gate-top.png new file mode 100644 index 0000000000..902a47be8d Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/warp-gate-top.png differ diff --git a/core/assets-raw/sprites/blocks/distribution/warp-gate.png b/core/assets-raw/sprites/blocks/distribution/warp-gate.png new file mode 100644 index 0000000000..a83c625823 Binary files /dev/null and b/core/assets-raw/sprites/blocks/distribution/warp-gate.png differ diff --git a/core/assets-raw/sprites/blocks/door-large-icon.png b/core/assets-raw/sprites/blocks/door-large-icon.png deleted file mode 100644 index 87dd1a0fdf..0000000000 Binary files a/core/assets-raw/sprites/blocks/door-large-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/door-large-open.png b/core/assets-raw/sprites/blocks/door-large-open.png deleted file mode 100644 index 42037513d6..0000000000 Binary files a/core/assets-raw/sprites/blocks/door-large-open.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/door-large.png b/core/assets-raw/sprites/blocks/door-large.png deleted file mode 100644 index a40f67f0be..0000000000 Binary files a/core/assets-raw/sprites/blocks/door-large.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/door-open.png b/core/assets-raw/sprites/blocks/door-open.png deleted file mode 100644 index dafd8a137b..0000000000 Binary files a/core/assets-raw/sprites/blocks/door-open.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/door.png b/core/assets-raw/sprites/blocks/door.png deleted file mode 100644 index bcbe7a5d03..0000000000 Binary files a/core/assets-raw/sprites/blocks/door.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/doubleturret.png b/core/assets-raw/sprites/blocks/doubleturret.png deleted file mode 100644 index b31f837139..0000000000 Binary files a/core/assets-raw/sprites/blocks/doubleturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/drills/blast-drill-rim.png b/core/assets-raw/sprites/blocks/drills/blast-drill-rim.png new file mode 100644 index 0000000000..70cca857d0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/blast-drill-rim.png differ diff --git a/core/assets-raw/sprites/blocks/drills/blast-drill-rotator.png b/core/assets-raw/sprites/blocks/drills/blast-drill-rotator.png new file mode 100644 index 0000000000..275d72a825 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/blast-drill-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/blast-drill-top.png b/core/assets-raw/sprites/blocks/drills/blast-drill-top.png new file mode 100644 index 0000000000..77543d3e0d Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/blast-drill-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/blast-drill.png b/core/assets-raw/sprites/blocks/drills/blast-drill.png new file mode 100644 index 0000000000..a1f5d3765f Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/blast-drill.png differ diff --git a/core/assets-raw/sprites/blocks/drills/carbide-drill-rotator.png b/core/assets-raw/sprites/blocks/drills/carbide-drill-rotator.png new file mode 100644 index 0000000000..6445608818 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/carbide-drill-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/carbide-drill-top.png b/core/assets-raw/sprites/blocks/drills/carbide-drill-top.png new file mode 100644 index 0000000000..47facc08d6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/carbide-drill-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/carbide-drill.png b/core/assets-raw/sprites/blocks/drills/carbide-drill.png new file mode 100644 index 0000000000..186258204d Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/carbide-drill.png differ diff --git a/core/assets-raw/sprites/blocks/drills/laser-drill-rotator.png b/core/assets-raw/sprites/blocks/drills/laser-drill-rotator.png new file mode 100644 index 0000000000..1e856fa7af Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/laser-drill-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/laser-drill-top.png b/core/assets-raw/sprites/blocks/drills/laser-drill-top.png new file mode 100644 index 0000000000..1c92ebe9f7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/laser-drill-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/laser-drill.png b/core/assets-raw/sprites/blocks/drills/laser-drill.png new file mode 100644 index 0000000000..2edf982c8f Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/laser-drill.png differ diff --git a/core/assets-raw/sprites/blocks/drills/oil-extractor-liquid.png b/core/assets-raw/sprites/blocks/drills/oil-extractor-liquid.png new file mode 100644 index 0000000000..5e3f1cea3a Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/oil-extractor-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/drills/oil-extractor-rotator.png b/core/assets-raw/sprites/blocks/drills/oil-extractor-rotator.png new file mode 100644 index 0000000000..118a8595ba Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/oil-extractor-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/oil-extractor-top.png b/core/assets-raw/sprites/blocks/drills/oil-extractor-top.png new file mode 100644 index 0000000000..8f5e693961 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/oil-extractor-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/oil-extractor.png b/core/assets-raw/sprites/blocks/drills/oil-extractor.png new file mode 100644 index 0000000000..7a73cd3611 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/oil-extractor.png differ diff --git a/core/assets-raw/sprites/blocks/drills/plasma-drill-rim.png b/core/assets-raw/sprites/blocks/drills/plasma-drill-rim.png new file mode 100644 index 0000000000..f828a5924b Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/plasma-drill-rim.png differ diff --git a/core/assets-raw/sprites/blocks/drills/plasma-drill-rotator.png b/core/assets-raw/sprites/blocks/drills/plasma-drill-rotator.png new file mode 100644 index 0000000000..1a1cccbd05 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/plasma-drill-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/plasma-drill-top.png b/core/assets-raw/sprites/blocks/drills/plasma-drill-top.png new file mode 100644 index 0000000000..04f9c4d1c4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/plasma-drill-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/plasma-drill.png b/core/assets-raw/sprites/blocks/drills/plasma-drill.png new file mode 100644 index 0000000000..30a8a86d99 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/plasma-drill.png differ diff --git a/core/assets-raw/sprites/blocks/drills/tungsten-drill-rotator.png b/core/assets-raw/sprites/blocks/drills/tungsten-drill-rotator.png new file mode 100644 index 0000000000..390dcd53a3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/tungsten-drill-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/tungsten-drill-top.png b/core/assets-raw/sprites/blocks/drills/tungsten-drill-top.png new file mode 100644 index 0000000000..f14021f914 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/tungsten-drill-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/tungsten-drill.png b/core/assets-raw/sprites/blocks/drills/tungsten-drill.png new file mode 100644 index 0000000000..9ce0ba779c Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/tungsten-drill.png differ diff --git a/core/assets-raw/sprites/blocks/drills/water-extractor-liquid.png b/core/assets-raw/sprites/blocks/drills/water-extractor-liquid.png new file mode 100644 index 0000000000..1404f2afd6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/water-extractor-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/drills/water-extractor-rotator.png b/core/assets-raw/sprites/blocks/drills/water-extractor-rotator.png new file mode 100644 index 0000000000..8f6b5ece25 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/water-extractor-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/drills/water-extractor-top.png b/core/assets-raw/sprites/blocks/drills/water-extractor-top.png new file mode 100644 index 0000000000..6917779757 Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/water-extractor-top.png differ diff --git a/core/assets-raw/sprites/blocks/drills/water-extractor.png b/core/assets-raw/sprites/blocks/drills/water-extractor.png new file mode 100644 index 0000000000..446172f8bd Binary files /dev/null and b/core/assets-raw/sprites/blocks/drills/water-extractor.png differ diff --git a/core/assets-raw/sprites/blocks/duriumwall-large-icon.png b/core/assets-raw/sprites/blocks/duriumwall-large-icon.png deleted file mode 100644 index 02b6bc82ce..0000000000 Binary files a/core/assets-raw/sprites/blocks/duriumwall-large-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/blackrock1.png b/core/assets-raw/sprites/blocks/environment/blackrock1.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackrock1.png rename to core/assets-raw/sprites/blocks/environment/blackrock1.png diff --git a/core/assets-raw/sprites/blocks/blackrockshadow1.png b/core/assets-raw/sprites/blocks/environment/blackrockshadow1.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackrockshadow1.png rename to core/assets-raw/sprites/blocks/environment/blackrockshadow1.png diff --git a/core/assets-raw/sprites/blocks/blackstone1.png b/core/assets-raw/sprites/blocks/environment/blackstone1.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackstone1.png rename to core/assets-raw/sprites/blocks/environment/blackstone1.png diff --git a/core/assets-raw/sprites/blocks/blackstone2.png b/core/assets-raw/sprites/blocks/environment/blackstone2.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackstone2.png rename to core/assets-raw/sprites/blocks/environment/blackstone2.png diff --git a/core/assets-raw/sprites/blocks/blackstone3.png b/core/assets-raw/sprites/blocks/environment/blackstone3.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackstone3.png rename to core/assets-raw/sprites/blocks/environment/blackstone3.png diff --git a/core/assets-raw/sprites/blocks/blackstoneedge.png b/core/assets-raw/sprites/blocks/environment/blackstoneedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/blackstoneedge.png rename to core/assets-raw/sprites/blocks/environment/blackstoneedge.png diff --git a/core/assets-raw/sprites/blocks/environment/coal1.png b/core/assets-raw/sprites/blocks/environment/coal1.png new file mode 100644 index 0000000000..04e254f3ea Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/coal1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/coal2.png b/core/assets-raw/sprites/blocks/environment/coal2.png new file mode 100644 index 0000000000..2bdd6177c1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/coal2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/coal3.png b/core/assets-raw/sprites/blocks/environment/coal3.png new file mode 100644 index 0000000000..1ef60a97b7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/coal3.png differ diff --git a/core/assets-raw/sprites/blocks/deepwater.png b/core/assets-raw/sprites/blocks/environment/deepwater.png similarity index 100% rename from core/assets-raw/sprites/blocks/deepwater.png rename to core/assets-raw/sprites/blocks/environment/deepwater.png diff --git a/core/assets-raw/sprites/blocks/dirt1.png b/core/assets-raw/sprites/blocks/environment/dirt1.png similarity index 100% rename from core/assets-raw/sprites/blocks/dirt1.png rename to core/assets-raw/sprites/blocks/environment/dirt1.png diff --git a/core/assets-raw/sprites/blocks/dirt2.png b/core/assets-raw/sprites/blocks/environment/dirt2.png similarity index 100% rename from core/assets-raw/sprites/blocks/dirt2.png rename to core/assets-raw/sprites/blocks/environment/dirt2.png diff --git a/core/assets-raw/sprites/blocks/dirt3.png b/core/assets-raw/sprites/blocks/environment/dirt3.png similarity index 100% rename from core/assets-raw/sprites/blocks/dirt3.png rename to core/assets-raw/sprites/blocks/environment/dirt3.png diff --git a/core/assets-raw/sprites/blocks/dirtedge.png b/core/assets-raw/sprites/blocks/environment/dirtedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/dirtedge.png rename to core/assets-raw/sprites/blocks/environment/dirtedge.png diff --git a/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-1.png new file mode 100644 index 0000000000..b7db44f2c8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-2.png new file mode 100644 index 0000000000..95c438eebe Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/grass-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge.png new file mode 100644 index 0000000000..d13e2f3167 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/grass-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/grass-cliff-side.png b/core/assets-raw/sprites/blocks/environment/grass-cliff-side.png new file mode 100644 index 0000000000..19ed554d1e Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/grass-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/grass1.png b/core/assets-raw/sprites/blocks/environment/grass1.png similarity index 100% rename from core/assets-raw/sprites/blocks/grass1.png rename to core/assets-raw/sprites/blocks/environment/grass1.png diff --git a/core/assets-raw/sprites/blocks/grass2.png b/core/assets-raw/sprites/blocks/environment/grass2.png similarity index 100% rename from core/assets-raw/sprites/blocks/grass2.png rename to core/assets-raw/sprites/blocks/environment/grass2.png diff --git a/core/assets-raw/sprites/blocks/grass3.png b/core/assets-raw/sprites/blocks/environment/grass3.png similarity index 100% rename from core/assets-raw/sprites/blocks/grass3.png rename to core/assets-raw/sprites/blocks/environment/grass3.png diff --git a/core/assets-raw/sprites/blocks/environment/grassedge.png b/core/assets-raw/sprites/blocks/environment/grassedge.png new file mode 100644 index 0000000000..eadc1f3b14 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/grassedge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-1.png new file mode 100644 index 0000000000..752311f80d Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-2.png new file mode 100644 index 0000000000..fb2b33d3f8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/ice-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge.png new file mode 100644 index 0000000000..2819b78843 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/ice-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/ice-cliff-side.png b/core/assets-raw/sprites/blocks/environment/ice-cliff-side.png new file mode 100644 index 0000000000..fba14c2f59 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/ice-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/ice1.png b/core/assets-raw/sprites/blocks/environment/ice1.png similarity index 100% rename from core/assets-raw/sprites/blocks/ice1.png rename to core/assets-raw/sprites/blocks/environment/ice1.png diff --git a/core/assets-raw/sprites/blocks/ice2.png b/core/assets-raw/sprites/blocks/environment/ice2.png similarity index 100% rename from core/assets-raw/sprites/blocks/ice2.png rename to core/assets-raw/sprites/blocks/environment/ice2.png diff --git a/core/assets-raw/sprites/blocks/ice3.png b/core/assets-raw/sprites/blocks/environment/ice3.png similarity index 100% rename from core/assets-raw/sprites/blocks/ice3.png rename to core/assets-raw/sprites/blocks/environment/ice3.png diff --git a/core/assets-raw/sprites/blocks/iceedge.png b/core/assets-raw/sprites/blocks/environment/iceedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/iceedge.png rename to core/assets-raw/sprites/blocks/environment/iceedge.png diff --git a/core/assets-raw/sprites/blocks/icerock1.png b/core/assets-raw/sprites/blocks/environment/icerock1.png similarity index 100% rename from core/assets-raw/sprites/blocks/icerock1.png rename to core/assets-raw/sprites/blocks/environment/icerock1.png diff --git a/core/assets-raw/sprites/blocks/icerock2.png b/core/assets-raw/sprites/blocks/environment/icerock2.png similarity index 100% rename from core/assets-raw/sprites/blocks/icerock2.png rename to core/assets-raw/sprites/blocks/environment/icerock2.png diff --git a/core/assets-raw/sprites/blocks/icerockshadow1.png b/core/assets-raw/sprites/blocks/environment/icerockshadow1.png similarity index 100% rename from core/assets-raw/sprites/blocks/icerockshadow1.png rename to core/assets-raw/sprites/blocks/environment/icerockshadow1.png diff --git a/core/assets-raw/sprites/blocks/icerockshadow2.png b/core/assets-raw/sprites/blocks/environment/icerockshadow2.png similarity index 100% rename from core/assets-raw/sprites/blocks/icerockshadow2.png rename to core/assets-raw/sprites/blocks/environment/icerockshadow2.png diff --git a/core/assets-raw/sprites/blocks/lava.png b/core/assets-raw/sprites/blocks/environment/lava.png similarity index 100% rename from core/assets-raw/sprites/blocks/lava.png rename to core/assets-raw/sprites/blocks/environment/lava.png diff --git a/core/assets-raw/sprites/blocks/lavaedge.png b/core/assets-raw/sprites/blocks/environment/lavaedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/lavaedge.png rename to core/assets-raw/sprites/blocks/environment/lavaedge.png diff --git a/core/assets-raw/sprites/blocks/environment/lead1.png b/core/assets-raw/sprites/blocks/environment/lead1.png new file mode 100644 index 0000000000..50b937bc47 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/lead1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/lead2.png b/core/assets-raw/sprites/blocks/environment/lead2.png new file mode 100644 index 0000000000..8f6be87470 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/lead2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/lead3.png b/core/assets-raw/sprites/blocks/environment/lead3.png new file mode 100644 index 0000000000..55effab148 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/lead3.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor1.png b/core/assets-raw/sprites/blocks/environment/metalfloor1.png new file mode 100644 index 0000000000..7f7bfc043a Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor2.png b/core/assets-raw/sprites/blocks/environment/metalfloor2.png new file mode 100644 index 0000000000..3d8b339c0a Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor3.png b/core/assets-raw/sprites/blocks/environment/metalfloor3.png new file mode 100644 index 0000000000..5412971c9e Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor3.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor4.png b/core/assets-raw/sprites/blocks/environment/metalfloor4.png new file mode 100644 index 0000000000..5d73bcedbc Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor4.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor5.png b/core/assets-raw/sprites/blocks/environment/metalfloor5.png new file mode 100644 index 0000000000..7800144b67 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor5.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalfloor6.png b/core/assets-raw/sprites/blocks/environment/metalfloor6.png new file mode 100644 index 0000000000..11118a7fc2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalfloor6.png differ diff --git a/core/assets-raw/sprites/blocks/environment/metalflooredge.png b/core/assets-raw/sprites/blocks/environment/metalflooredge.png new file mode 100644 index 0000000000..a018513597 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/metalflooredge.png differ diff --git a/core/assets-raw/sprites/blocks/oil.png b/core/assets-raw/sprites/blocks/environment/oil.png similarity index 100% rename from core/assets-raw/sprites/blocks/oil.png rename to core/assets-raw/sprites/blocks/environment/oil.png diff --git a/core/assets-raw/sprites/blocks/oiledge.png b/core/assets-raw/sprites/blocks/environment/oiledge.png similarity index 100% rename from core/assets-raw/sprites/blocks/oiledge.png rename to core/assets-raw/sprites/blocks/environment/oiledge.png diff --git a/core/assets-raw/sprites/blocks/rock1.png b/core/assets-raw/sprites/blocks/environment/rock1.png similarity index 100% rename from core/assets-raw/sprites/blocks/rock1.png rename to core/assets-raw/sprites/blocks/environment/rock1.png diff --git a/core/assets-raw/sprites/blocks/rock2.png b/core/assets-raw/sprites/blocks/environment/rock2.png similarity index 100% rename from core/assets-raw/sprites/blocks/rock2.png rename to core/assets-raw/sprites/blocks/environment/rock2.png diff --git a/core/assets-raw/sprites/blocks/rockshadow1.png b/core/assets-raw/sprites/blocks/environment/rockshadow1.png similarity index 100% rename from core/assets-raw/sprites/blocks/rockshadow1.png rename to core/assets-raw/sprites/blocks/environment/rockshadow1.png diff --git a/core/assets-raw/sprites/blocks/rockshadow2.png b/core/assets-raw/sprites/blocks/environment/rockshadow2.png similarity index 100% rename from core/assets-raw/sprites/blocks/rockshadow2.png rename to core/assets-raw/sprites/blocks/environment/rockshadow2.png diff --git a/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-1.png new file mode 100644 index 0000000000..7811175144 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-2.png new file mode 100644 index 0000000000..9b19b6e2e4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/sand-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge.png new file mode 100644 index 0000000000..26a01f04a1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/sand-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/sand-cliff-side.png b/core/assets-raw/sprites/blocks/environment/sand-cliff-side.png new file mode 100644 index 0000000000..5468b77b59 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/sand-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/sand1.png b/core/assets-raw/sprites/blocks/environment/sand1.png similarity index 100% rename from core/assets-raw/sprites/blocks/sand1.png rename to core/assets-raw/sprites/blocks/environment/sand1.png diff --git a/core/assets-raw/sprites/blocks/sand2.png b/core/assets-raw/sprites/blocks/environment/sand2.png similarity index 100% rename from core/assets-raw/sprites/blocks/sand2.png rename to core/assets-raw/sprites/blocks/environment/sand2.png diff --git a/core/assets-raw/sprites/blocks/sand3.png b/core/assets-raw/sprites/blocks/environment/sand3.png similarity index 100% rename from core/assets-raw/sprites/blocks/sand3.png rename to core/assets-raw/sprites/blocks/environment/sand3.png diff --git a/core/assets-raw/sprites/blocks/sandedge.png b/core/assets-raw/sprites/blocks/environment/sandedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/sandedge.png rename to core/assets-raw/sprites/blocks/environment/sandedge.png diff --git a/core/assets-raw/sprites/blocks/shrub.png b/core/assets-raw/sprites/blocks/environment/shrub.png similarity index 100% rename from core/assets-raw/sprites/blocks/shrub.png rename to core/assets-raw/sprites/blocks/environment/shrub.png diff --git a/core/assets-raw/sprites/blocks/shrubshadow.png b/core/assets-raw/sprites/blocks/environment/shrubshadow.png similarity index 100% rename from core/assets-raw/sprites/blocks/shrubshadow.png rename to core/assets-raw/sprites/blocks/environment/shrubshadow.png diff --git a/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-1.png new file mode 100644 index 0000000000..5355ae6578 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-2.png new file mode 100644 index 0000000000..566b5c2989 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/snow-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge.png new file mode 100644 index 0000000000..c99aafe67d Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/snow-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/snow-cliff-side.png b/core/assets-raw/sprites/blocks/environment/snow-cliff-side.png new file mode 100644 index 0000000000..8fb62c768f Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/snow-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/snow1.png b/core/assets-raw/sprites/blocks/environment/snow1.png similarity index 100% rename from core/assets-raw/sprites/blocks/snow1.png rename to core/assets-raw/sprites/blocks/environment/snow1.png diff --git a/core/assets-raw/sprites/blocks/snow2.png b/core/assets-raw/sprites/blocks/environment/snow2.png similarity index 100% rename from core/assets-raw/sprites/blocks/snow2.png rename to core/assets-raw/sprites/blocks/environment/snow2.png diff --git a/core/assets-raw/sprites/blocks/snow3.png b/core/assets-raw/sprites/blocks/environment/snow3.png similarity index 100% rename from core/assets-raw/sprites/blocks/snow3.png rename to core/assets-raw/sprites/blocks/environment/snow3.png diff --git a/core/assets-raw/sprites/blocks/snowedge.png b/core/assets-raw/sprites/blocks/environment/snowedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/snowedge.png rename to core/assets-raw/sprites/blocks/environment/snowedge.png diff --git a/core/assets-raw/sprites/blocks/environment/space.png b/core/assets-raw/sprites/blocks/environment/space.png new file mode 100644 index 0000000000..54eb43362b Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/space.png differ diff --git a/core/assets-raw/sprites/blocks/environment/spaceedge.png b/core/assets-raw/sprites/blocks/environment/spaceedge.png new file mode 100644 index 0000000000..8f0314574a Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/spaceedge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-1.png new file mode 100644 index 0000000000..d7f7bd6fb9 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-2.png new file mode 100644 index 0000000000..327f896766 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/stone-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge.png new file mode 100644 index 0000000000..32d291b922 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/stone-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/stone-cliff-side.png b/core/assets-raw/sprites/blocks/environment/stone-cliff-side.png new file mode 100644 index 0000000000..c71d3aeade Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/stone-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/stone1.png b/core/assets-raw/sprites/blocks/environment/stone1.png similarity index 100% rename from core/assets-raw/sprites/blocks/stone1.png rename to core/assets-raw/sprites/blocks/environment/stone1.png diff --git a/core/assets-raw/sprites/blocks/stone2.png b/core/assets-raw/sprites/blocks/environment/stone2.png similarity index 100% rename from core/assets-raw/sprites/blocks/stone2.png rename to core/assets-raw/sprites/blocks/environment/stone2.png diff --git a/core/assets-raw/sprites/blocks/stone3.png b/core/assets-raw/sprites/blocks/environment/stone3.png similarity index 100% rename from core/assets-raw/sprites/blocks/stone3.png rename to core/assets-raw/sprites/blocks/environment/stone3.png diff --git a/core/assets-raw/sprites/blocks/stoneedge.png b/core/assets-raw/sprites/blocks/environment/stoneedge.png similarity index 100% rename from core/assets-raw/sprites/blocks/stoneedge.png rename to core/assets-raw/sprites/blocks/environment/stoneedge.png diff --git a/core/assets-raw/sprites/blocks/environment/thorium1.png b/core/assets-raw/sprites/blocks/environment/thorium1.png new file mode 100644 index 0000000000..af0c3945cf Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/thorium1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/thorium2.png b/core/assets-raw/sprites/blocks/environment/thorium2.png new file mode 100644 index 0000000000..3770ac936e Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/thorium2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/thorium3.png b/core/assets-raw/sprites/blocks/environment/thorium3.png new file mode 100644 index 0000000000..c81886a2ff Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/thorium3.png differ diff --git a/core/assets-raw/sprites/blocks/environment/titanium1.png b/core/assets-raw/sprites/blocks/environment/titanium1.png new file mode 100644 index 0000000000..d3eb607b41 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/titanium1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/titanium2.png b/core/assets-raw/sprites/blocks/environment/titanium2.png new file mode 100644 index 0000000000..155ec110d4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/titanium2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/titanium3.png b/core/assets-raw/sprites/blocks/environment/titanium3.png new file mode 100644 index 0000000000..4207658ab4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/titanium3.png differ diff --git a/core/assets-raw/sprites/blocks/environment/tungsten1.png b/core/assets-raw/sprites/blocks/environment/tungsten1.png new file mode 100644 index 0000000000..e831275a95 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/tungsten1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/tungsten2.png b/core/assets-raw/sprites/blocks/environment/tungsten2.png new file mode 100644 index 0000000000..fb85738120 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/tungsten2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/tungsten3.png b/core/assets-raw/sprites/blocks/environment/tungsten3.png new file mode 100644 index 0000000000..6c7b42a5b0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/tungsten3.png differ diff --git a/core/assets-raw/sprites/blocks/environment/water-cliff-edge-1.png b/core/assets-raw/sprites/blocks/environment/water-cliff-edge-1.png new file mode 100644 index 0000000000..271d355d2f Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/water-cliff-edge-1.png differ diff --git a/core/assets-raw/sprites/blocks/environment/water-cliff-edge-2.png b/core/assets-raw/sprites/blocks/environment/water-cliff-edge-2.png new file mode 100644 index 0000000000..e843c6cd20 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/water-cliff-edge-2.png differ diff --git a/core/assets-raw/sprites/blocks/environment/water-cliff-edge.png b/core/assets-raw/sprites/blocks/environment/water-cliff-edge.png new file mode 100644 index 0000000000..7962b0c445 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/water-cliff-edge.png differ diff --git a/core/assets-raw/sprites/blocks/environment/water-cliff-side.png b/core/assets-raw/sprites/blocks/environment/water-cliff-side.png new file mode 100644 index 0000000000..e184eb7aa8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/environment/water-cliff-side.png differ diff --git a/core/assets-raw/sprites/blocks/water.png b/core/assets-raw/sprites/blocks/environment/water.png similarity index 100% rename from core/assets-raw/sprites/blocks/water.png rename to core/assets-raw/sprites/blocks/environment/water.png diff --git a/core/assets-raw/sprites/blocks/wateredge.png b/core/assets-raw/sprites/blocks/environment/wateredge.png similarity index 100% rename from core/assets-raw/sprites/blocks/wateredge.png rename to core/assets-raw/sprites/blocks/environment/wateredge.png diff --git a/core/assets-raw/sprites/blocks/extra/block-border.png b/core/assets-raw/sprites/blocks/extra/block-border.png new file mode 100644 index 0000000000..e4f5596ce8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/block-border.png differ diff --git a/core/assets-raw/sprites/blocks/extra/block-elevation.png b/core/assets-raw/sprites/blocks/extra/block-elevation.png new file mode 100644 index 0000000000..06b3214b56 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/block-elevation.png differ diff --git a/core/assets-raw/sprites/blocks/block-middle.png b/core/assets-raw/sprites/blocks/extra/block-middle.png similarity index 100% rename from core/assets-raw/sprites/blocks/block-middle.png rename to core/assets-raw/sprites/blocks/extra/block-middle.png diff --git a/core/assets-raw/sprites/blocks/extra/block-slope.png b/core/assets-raw/sprites/blocks/extra/block-slope.png new file mode 100644 index 0000000000..a9a7ce463d Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/block-slope.png differ diff --git a/core/assets-raw/sprites/blocks/extra/border.png b/core/assets-raw/sprites/blocks/extra/border.png new file mode 100644 index 0000000000..1a5517ac49 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/border.png differ diff --git a/core/assets-raw/sprites/blocks/conduitliquid.png b/core/assets-raw/sprites/blocks/extra/conduit-liquid.png similarity index 100% rename from core/assets-raw/sprites/blocks/conduitliquid.png rename to core/assets-raw/sprites/blocks/extra/conduit-liquid.png diff --git a/core/assets-raw/sprites/blocks/cross.png b/core/assets-raw/sprites/blocks/extra/cross-1.png similarity index 100% rename from core/assets-raw/sprites/blocks/cross.png rename to core/assets-raw/sprites/blocks/extra/cross-1.png diff --git a/core/assets-raw/sprites/blocks/extra/cross-2.png b/core/assets-raw/sprites/blocks/extra/cross-2.png new file mode 100644 index 0000000000..c2e6418df8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/cross-2.png differ diff --git a/core/assets-raw/sprites/blocks/extra/cross-3.png b/core/assets-raw/sprites/blocks/extra/cross-3.png new file mode 100644 index 0000000000..60567dc1bf Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/cross-3.png differ diff --git a/core/assets-raw/sprites/blocks/extra/cross-4.png b/core/assets-raw/sprites/blocks/extra/cross-4.png new file mode 100644 index 0000000000..5c24f39694 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/cross-4.png differ diff --git a/core/assets-raw/sprites/blocks/enemyspawn.png b/core/assets-raw/sprites/blocks/extra/enemyspawn.png similarity index 100% rename from core/assets-raw/sprites/blocks/enemyspawn.png rename to core/assets-raw/sprites/blocks/extra/enemyspawn.png diff --git a/core/assets-raw/sprites/blocks/extra/nuclearreactor-shadow.png b/core/assets-raw/sprites/blocks/extra/nuclearreactor-shadow.png new file mode 100644 index 0000000000..b8cd9b59e6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/nuclearreactor-shadow.png differ diff --git a/core/assets-raw/sprites/blocks/extra/place-arrow.png b/core/assets-raw/sprites/blocks/extra/place-arrow.png new file mode 100644 index 0000000000..3fc6718d29 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/place-arrow.png differ diff --git a/core/assets-raw/sprites/blocks/playerspawn.png b/core/assets-raw/sprites/blocks/extra/playerspawn.png similarity index 100% rename from core/assets-raw/sprites/blocks/playerspawn.png rename to core/assets-raw/sprites/blocks/extra/playerspawn.png diff --git a/core/assets-raw/sprites/blocks/extra/ripples.png b/core/assets-raw/sprites/blocks/extra/ripples.png new file mode 100644 index 0000000000..7e975a5b4c Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/ripples.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-1-0.png b/core/assets-raw/sprites/blocks/extra/rubble-1-0.png new file mode 100644 index 0000000000..50587fa070 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-1-0.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-1-1.png b/core/assets-raw/sprites/blocks/extra/rubble-1-1.png new file mode 100644 index 0000000000..703be71c3c Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-1-1.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-2-0.png b/core/assets-raw/sprites/blocks/extra/rubble-2-0.png new file mode 100644 index 0000000000..5a69b696a0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-2-0.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-2-1.png b/core/assets-raw/sprites/blocks/extra/rubble-2-1.png new file mode 100644 index 0000000000..3638d6ee3e Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-2-1.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-3-0.png b/core/assets-raw/sprites/blocks/extra/rubble-3-0.png new file mode 100644 index 0000000000..4fb1a666db Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-3-0.png differ diff --git a/core/assets-raw/sprites/blocks/extra/rubble-3-1.png b/core/assets-raw/sprites/blocks/extra/rubble-3-1.png new file mode 100644 index 0000000000..4fb1a666db Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/rubble-3-1.png differ diff --git a/core/assets-raw/sprites/blocks/shadow.png b/core/assets-raw/sprites/blocks/extra/shadow-1.png similarity index 62% rename from core/assets-raw/sprites/blocks/shadow.png rename to core/assets-raw/sprites/blocks/extra/shadow-1.png index a4f6243e61..ce366cdac9 100644 Binary files a/core/assets-raw/sprites/blocks/shadow.png and b/core/assets-raw/sprites/blocks/extra/shadow-1.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-2.png b/core/assets-raw/sprites/blocks/extra/shadow-2.png new file mode 100644 index 0000000000..ecec67ced8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-2.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-3.png b/core/assets-raw/sprites/blocks/extra/shadow-3.png new file mode 100644 index 0000000000..023a68f210 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-3.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-4.png b/core/assets-raw/sprites/blocks/extra/shadow-4.png new file mode 100644 index 0000000000..4b55f9be7d Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-4.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-5.png b/core/assets-raw/sprites/blocks/extra/shadow-5.png new file mode 100644 index 0000000000..3d09ca421c Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-5.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-6.png b/core/assets-raw/sprites/blocks/extra/shadow-6.png new file mode 100644 index 0000000000..ed6da05349 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-6.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-round-1.png b/core/assets-raw/sprites/blocks/extra/shadow-round-1.png new file mode 100644 index 0000000000..2910e29275 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-round-1.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-round-2.png b/core/assets-raw/sprites/blocks/extra/shadow-round-2.png new file mode 100644 index 0000000000..f7a191914e Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-round-2.png differ diff --git a/core/assets-raw/sprites/blocks/extra/shadow-rounded-2.png b/core/assets-raw/sprites/blocks/extra/shadow-rounded-2.png new file mode 100644 index 0000000000..5dedff2673 Binary files /dev/null and b/core/assets-raw/sprites/blocks/extra/shadow-rounded-2.png differ diff --git a/core/assets-raw/sprites/blocks/flameturret.png b/core/assets-raw/sprites/blocks/flameturret.png deleted file mode 100644 index 734af9bed1..0000000000 Binary files a/core/assets-raw/sprites/blocks/flameturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/fluxpump.png b/core/assets-raw/sprites/blocks/fluxpump.png deleted file mode 100644 index 3ed76c6a32..0000000000 Binary files a/core/assets-raw/sprites/blocks/fluxpump.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/grassblock1.png b/core/assets-raw/sprites/blocks/grassblock1.png deleted file mode 100644 index cceb8b43d1..0000000000 Binary files a/core/assets-raw/sprites/blocks/grassblock1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/grassblock2.png b/core/assets-raw/sprites/blocks/grassblock2.png deleted file mode 100644 index a66ee2ab2b..0000000000 Binary files a/core/assets-raw/sprites/blocks/grassblock2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/grassedge.png b/core/assets-raw/sprites/blocks/grassedge.png deleted file mode 100644 index 32b6f87c0f..0000000000 Binary files a/core/assets-raw/sprites/blocks/grassedge.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/iron1.png b/core/assets-raw/sprites/blocks/iron1.png deleted file mode 100644 index 7e8c332d80..0000000000 Binary files a/core/assets-raw/sprites/blocks/iron1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/iron2.png b/core/assets-raw/sprites/blocks/iron2.png deleted file mode 100644 index 97bcdf16b6..0000000000 Binary files a/core/assets-raw/sprites/blocks/iron2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/iron3.png b/core/assets-raw/sprites/blocks/iron3.png deleted file mode 100644 index 6110bcfd78..0000000000 Binary files a/core/assets-raw/sprites/blocks/iron3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/irondrill.png b/core/assets-raw/sprites/blocks/irondrill.png deleted file mode 100644 index ef0550b268..0000000000 Binary files a/core/assets-raw/sprites/blocks/irondrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/laserturret.png b/core/assets-raw/sprites/blocks/laserturret.png deleted file mode 100644 index 7452832d41..0000000000 Binary files a/core/assets-raw/sprites/blocks/laserturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/liquid/bridge-conduit-arrow.png b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-arrow.png new file mode 100644 index 0000000000..3c2ab9921e Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-arrow.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/bridge-conduit-bridge.png b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-bridge.png new file mode 100644 index 0000000000..fb5633fc6e Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-bridge.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/bridge-conduit-end.png b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-end.png new file mode 100644 index 0000000000..2392bdb67b Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/bridge-conduit-end.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/bridge-conduit.png b/core/assets-raw/sprites/blocks/liquid/bridge-conduit.png new file mode 100644 index 0000000000..d815787b4f Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/bridge-conduit.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png new file mode 100644 index 0000000000..04eca38d45 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-0.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png new file mode 100644 index 0000000000..dff4e78e7f Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-1.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png new file mode 100644 index 0000000000..5fec0419e0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-2.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png new file mode 100644 index 0000000000..8983671a0d Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-3.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png new file mode 100644 index 0000000000..3a8d3ac7a8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-4.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png new file mode 100644 index 0000000000..5fa2763614 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-5.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png new file mode 100644 index 0000000000..931b4d5b87 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom-6.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-bottom.png b/core/assets-raw/sprites/blocks/liquid/conduit-bottom.png new file mode 100644 index 0000000000..7e3892102f Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-0.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-0.png new file mode 100644 index 0000000000..c3809b0f60 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-0.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-1.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-1.png new file mode 100644 index 0000000000..37ee6de2a6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-1.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-2.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-2.png new file mode 100644 index 0000000000..635972c5bc Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-2.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-3.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-3.png new file mode 100644 index 0000000000..e067a5bb7b Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-3.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-4.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-4.png new file mode 100644 index 0000000000..ad90c44566 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-4.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-5.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-5.png new file mode 100644 index 0000000000..7941306123 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-5.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/conduit-top-6.png b/core/assets-raw/sprites/blocks/liquid/conduit-top-6.png new file mode 100644 index 0000000000..eb0cdfbb25 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/conduit-top-6.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-junction.png b/core/assets-raw/sprites/blocks/liquid/liquid-junction.png new file mode 100644 index 0000000000..29808c9146 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-junction.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-router-bottom.png b/core/assets-raw/sprites/blocks/liquid/liquid-router-bottom.png new file mode 100644 index 0000000000..655303b3c6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-router-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-router-liquid.png b/core/assets-raw/sprites/blocks/liquid/liquid-router-liquid.png new file mode 100644 index 0000000000..08f30e7a73 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-router-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-router-top.png b/core/assets-raw/sprites/blocks/liquid/liquid-router-top.png new file mode 100644 index 0000000000..f89dad02bc Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-router-top.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-router.png b/core/assets-raw/sprites/blocks/liquid/liquid-router.png new file mode 100644 index 0000000000..dd4230684e Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-router.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-tank-bottom.png b/core/assets-raw/sprites/blocks/liquid/liquid-tank-bottom.png new file mode 100644 index 0000000000..082dace989 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-tank-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-tank-liquid.png b/core/assets-raw/sprites/blocks/liquid/liquid-tank-liquid.png new file mode 100644 index 0000000000..5e1e387d8f Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-tank-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-tank-top.png b/core/assets-raw/sprites/blocks/liquid/liquid-tank-top.png new file mode 100644 index 0000000000..ee332f30fc Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/liquid-tank-top.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/mechanical-pump.png b/core/assets-raw/sprites/blocks/liquid/mechanical-pump.png new file mode 100644 index 0000000000..02238bee1a Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/mechanical-pump.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/phase-conduit-arrow.png b/core/assets-raw/sprites/blocks/liquid/phase-conduit-arrow.png new file mode 100644 index 0000000000..aba310f7da Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/phase-conduit-arrow.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/phase-conduit-bridge.png b/core/assets-raw/sprites/blocks/liquid/phase-conduit-bridge.png new file mode 100644 index 0000000000..d6bf45f0cc Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/phase-conduit-bridge.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/phase-conduit-end.png b/core/assets-raw/sprites/blocks/liquid/phase-conduit-end.png new file mode 100644 index 0000000000..be40ddf9a0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/phase-conduit-end.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/phase-conduit.png b/core/assets-raw/sprites/blocks/liquid/phase-conduit.png new file mode 100644 index 0000000000..0be4509752 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/phase-conduit.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-0.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-0.png new file mode 100644 index 0000000000..5cc6fd009a Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-0.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png new file mode 100644 index 0000000000..43017050e7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-1.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png new file mode 100644 index 0000000000..7796332524 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-2.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png new file mode 100644 index 0000000000..4ae0db8bf1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-3.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png new file mode 100644 index 0000000000..b90b2a51b2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-4.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-5.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-5.png new file mode 100644 index 0000000000..74aedc579e Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-5.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png new file mode 100644 index 0000000000..30c4e4bcc2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/pulse-conduit-top-6.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/rotary-pump.png b/core/assets-raw/sprites/blocks/liquid/rotary-pump.png new file mode 100644 index 0000000000..10a877f3ae Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/rotary-pump.png differ diff --git a/core/assets-raw/sprites/blocks/liquid/thermal-pump.png b/core/assets-raw/sprites/blocks/liquid/thermal-pump.png new file mode 100644 index 0000000000..786b6861d5 Binary files /dev/null and b/core/assets-raw/sprites/blocks/liquid/thermal-pump.png differ diff --git a/core/assets-raw/sprites/blocks/liquiditemjunction.png b/core/assets-raw/sprites/blocks/liquiditemjunction.png deleted file mode 100644 index 9df1857781..0000000000 Binary files a/core/assets-raw/sprites/blocks/liquiditemjunction.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/liquidjunction.png b/core/assets-raw/sprites/blocks/liquidjunction.png deleted file mode 100644 index 0a0bfdcfba..0000000000 Binary files a/core/assets-raw/sprites/blocks/liquidjunction.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/liquidrouter.png b/core/assets-raw/sprites/blocks/liquidrouter.png deleted file mode 100644 index 2b542a739f..0000000000 Binary files a/core/assets-raw/sprites/blocks/liquidrouter.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/machineturret.png b/core/assets-raw/sprites/blocks/machineturret.png deleted file mode 100644 index 4757ab8631..0000000000 Binary files a/core/assets-raw/sprites/blocks/machineturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/megarepairturret.png b/core/assets-raw/sprites/blocks/megarepairturret.png deleted file mode 100644 index 7bf6173a93..0000000000 Binary files a/core/assets-raw/sprites/blocks/megarepairturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/mortarturret.png b/core/assets-raw/sprites/blocks/mortarturret.png deleted file mode 100644 index 3435a9f0ca..0000000000 Binary files a/core/assets-raw/sprites/blocks/mortarturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/mossblock.png b/core/assets-raw/sprites/blocks/mossblock.png deleted file mode 100644 index 194f078ee7..0000000000 Binary files a/core/assets-raw/sprites/blocks/mossblock.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/mossstone.png b/core/assets-raw/sprites/blocks/mossstone.png deleted file mode 100644 index f49713af7c..0000000000 Binary files a/core/assets-raw/sprites/blocks/mossstone.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/nuclearreactor-center.png b/core/assets-raw/sprites/blocks/nuclearreactor-center.png deleted file mode 100644 index 5b5a2688cc..0000000000 Binary files a/core/assets-raw/sprites/blocks/nuclearreactor-center.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/nuclearreactor-icon.png b/core/assets-raw/sprites/blocks/nuclearreactor-icon.png deleted file mode 100644 index 2a97a1bc63..0000000000 Binary files a/core/assets-raw/sprites/blocks/nuclearreactor-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/nuclearreactor-lights.png b/core/assets-raw/sprites/blocks/nuclearreactor-lights.png deleted file mode 100644 index 69a28b33df..0000000000 Binary files a/core/assets-raw/sprites/blocks/nuclearreactor-lights.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/nuclearreactor-small.png b/core/assets-raw/sprites/blocks/nuclearreactor-small.png deleted file mode 100644 index 6d9f0bfd35..0000000000 Binary files a/core/assets-raw/sprites/blocks/nuclearreactor-small.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/nuclearreactor.png b/core/assets-raw/sprites/blocks/nuclearreactor.png deleted file mode 100644 index 5e28126f38..0000000000 Binary files a/core/assets-raw/sprites/blocks/nuclearreactor.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/oilrefinery.png b/core/assets-raw/sprites/blocks/oilrefinery.png deleted file mode 100644 index acfe53f47f..0000000000 Binary files a/core/assets-raw/sprites/blocks/oilrefinery.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/omnidrill.png b/core/assets-raw/sprites/blocks/omnidrill.png deleted file mode 100644 index 5ff4798aab..0000000000 Binary files a/core/assets-raw/sprites/blocks/omnidrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/plasmaturret.png b/core/assets-raw/sprites/blocks/plasmaturret.png deleted file mode 100644 index e6e7a1d328..0000000000 Binary files a/core/assets-raw/sprites/blocks/plasmaturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/power/battery-large.png b/core/assets-raw/sprites/blocks/power/battery-large.png new file mode 100644 index 0000000000..7800659d35 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/battery-large.png differ diff --git a/core/assets-raw/sprites/blocks/power/battery.png b/core/assets-raw/sprites/blocks/power/battery.png new file mode 100644 index 0000000000..a61a546646 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/battery.png differ diff --git a/core/assets-raw/sprites/blocks/coalgenerator-top.png b/core/assets-raw/sprites/blocks/power/combustion-generator-top.png similarity index 100% rename from core/assets-raw/sprites/blocks/coalgenerator-top.png rename to core/assets-raw/sprites/blocks/power/combustion-generator-top.png diff --git a/core/assets-raw/sprites/blocks/power/combustion-generator.png b/core/assets-raw/sprites/blocks/power/combustion-generator.png new file mode 100644 index 0000000000..116811b8e1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/combustion-generator.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-bottom.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-bottom.png new file mode 100644 index 0000000000..a7688ed12d Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-light.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-light.png new file mode 100644 index 0000000000..deabd2752f Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-light.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-0.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-0.png new file mode 100644 index 0000000000..13efbdd207 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-0.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-1.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-1.png new file mode 100644 index 0000000000..820e15bde2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-1.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-2.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-2.png new file mode 100644 index 0000000000..a1032085ff Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-2.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-3.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-3.png new file mode 100644 index 0000000000..2b22128700 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-plasma-3.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor-top.png b/core/assets-raw/sprites/blocks/power/fusion-reactor-top.png new file mode 100644 index 0000000000..165f77c739 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor-top.png differ diff --git a/core/assets-raw/sprites/blocks/power/fusion-reactor.png b/core/assets-raw/sprites/blocks/power/fusion-reactor.png new file mode 100644 index 0000000000..11aaf95ad7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/fusion-reactor.png differ diff --git a/core/assets-raw/sprites/blocks/power/nuclear-reactor-center.png b/core/assets-raw/sprites/blocks/power/nuclear-reactor-center.png new file mode 100644 index 0000000000..bebf831aea Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/nuclear-reactor-center.png differ diff --git a/core/assets-raw/sprites/blocks/power/nuclear-reactor-lights.png b/core/assets-raw/sprites/blocks/power/nuclear-reactor-lights.png new file mode 100644 index 0000000000..65f7869f54 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/nuclear-reactor-lights.png differ diff --git a/core/assets-raw/sprites/blocks/power/nuclear-reactor.png b/core/assets-raw/sprites/blocks/power/nuclear-reactor.png new file mode 100644 index 0000000000..4f7561e0bb Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/nuclear-reactor.png differ diff --git a/core/assets-raw/sprites/blocks/power/power-node-large.png b/core/assets-raw/sprites/blocks/power/power-node-large.png new file mode 100644 index 0000000000..4d8d7d5b4e Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/power-node-large.png differ diff --git a/core/assets-raw/sprites/blocks/power/power-node.png b/core/assets-raw/sprites/blocks/power/power-node.png new file mode 100644 index 0000000000..696e84ce7d Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/power-node.png differ diff --git a/core/assets-raw/sprites/blocks/power/powerinfinite.png b/core/assets-raw/sprites/blocks/power/powerinfinite.png new file mode 100644 index 0000000000..ce78153ea0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/powerinfinite.png differ diff --git a/core/assets-raw/sprites/blocks/power/powervoid.png b/core/assets-raw/sprites/blocks/power/powervoid.png new file mode 100644 index 0000000000..d91f656faf Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/powervoid.png differ diff --git a/core/assets-raw/sprites/blocks/rtgenerator-top.png b/core/assets-raw/sprites/blocks/power/rtg-generator-top.png similarity index 100% rename from core/assets-raw/sprites/blocks/rtgenerator-top.png rename to core/assets-raw/sprites/blocks/power/rtg-generator-top.png diff --git a/core/assets-raw/sprites/blocks/power/rtg-generator.png b/core/assets-raw/sprites/blocks/power/rtg-generator.png new file mode 100644 index 0000000000..9e4a43058f Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/rtg-generator.png differ diff --git a/core/assets-raw/sprites/blocks/power/solar-panel-large.png b/core/assets-raw/sprites/blocks/power/solar-panel-large.png new file mode 100644 index 0000000000..a0f3d22ba3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/solar-panel-large.png differ diff --git a/core/assets-raw/sprites/blocks/power/solar-panel.png b/core/assets-raw/sprites/blocks/power/solar-panel.png new file mode 100644 index 0000000000..5aafde0e04 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/solar-panel.png differ diff --git a/core/assets-raw/sprites/blocks/power/thermal-generator.png b/core/assets-raw/sprites/blocks/power/thermal-generator.png new file mode 100644 index 0000000000..388654a481 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/thermal-generator.png differ diff --git a/core/assets-raw/sprites/blocks/power/turbine-generator-top.png b/core/assets-raw/sprites/blocks/power/turbine-generator-top.png new file mode 100644 index 0000000000..4e2f4fa5b4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/turbine-generator-top.png differ diff --git a/core/assets-raw/sprites/blocks/power/turbine-generator.png b/core/assets-raw/sprites/blocks/power/turbine-generator.png new file mode 100644 index 0000000000..4d7dde6b47 Binary files /dev/null and b/core/assets-raw/sprites/blocks/power/turbine-generator.png differ diff --git a/core/assets-raw/sprites/blocks/powerbooster.png b/core/assets-raw/sprites/blocks/powerbooster.png deleted file mode 100644 index 5d0ce18461..0000000000 Binary files a/core/assets-raw/sprites/blocks/powerbooster.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/poweredconveyor.png b/core/assets-raw/sprites/blocks/poweredconveyor.png deleted file mode 100644 index bae08a3d20..0000000000 Binary files a/core/assets-raw/sprites/blocks/poweredconveyor.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/poweredconveyormove.png b/core/assets-raw/sprites/blocks/poweredconveyormove.png deleted file mode 100644 index 60d4002873..0000000000 Binary files a/core/assets-raw/sprites/blocks/poweredconveyormove.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/powerlaser.png b/core/assets-raw/sprites/blocks/powerlaser.png deleted file mode 100644 index a20be88c15..0000000000 Binary files a/core/assets-raw/sprites/blocks/powerlaser.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/powerlasercorner.png b/core/assets-raw/sprites/blocks/powerlasercorner.png deleted file mode 100644 index 52c2858c7d..0000000000 Binary files a/core/assets-raw/sprites/blocks/powerlasercorner.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/powerlaserrouter.png b/core/assets-raw/sprites/blocks/powerlaserrouter.png deleted file mode 100644 index ea7c8122f3..0000000000 Binary files a/core/assets-raw/sprites/blocks/powerlaserrouter.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/production/alloy-fuser.png b/core/assets-raw/sprites/blocks/production/alloy-fuser.png new file mode 100644 index 0000000000..79464317e2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/alloy-fuser.png differ diff --git a/core/assets-raw/sprites/blocks/production/alloy-smelter.png b/core/assets-raw/sprites/blocks/production/alloy-smelter.png new file mode 100644 index 0000000000..7c483b2a4b Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/alloy-smelter.png differ diff --git a/core/assets-raw/sprites/blocks/production/arc-smelter-top.png b/core/assets-raw/sprites/blocks/production/arc-smelter-top.png new file mode 100644 index 0000000000..67ba01fd18 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/arc-smelter-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/arc-smelter.png b/core/assets-raw/sprites/blocks/production/arc-smelter.png new file mode 100644 index 0000000000..5f9212d4b6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/arc-smelter.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor-frame0.png b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame0.png new file mode 100644 index 0000000000..2985c82f70 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame0.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor-frame1.png b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame1.png new file mode 100644 index 0000000000..8dfd11128c Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame1.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor-frame2.png b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame2.png new file mode 100644 index 0000000000..cd65d58676 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor-frame2.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor-liquid.png b/core/assets-raw/sprites/blocks/production/biomattercompressor-liquid.png new file mode 100644 index 0000000000..17f8bd2e4d Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor-top.png b/core/assets-raw/sprites/blocks/production/biomattercompressor-top.png new file mode 100644 index 0000000000..0c97e631b8 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/biomattercompressor.png b/core/assets-raw/sprites/blocks/production/biomattercompressor.png new file mode 100644 index 0000000000..0652d74220 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/biomattercompressor.png differ diff --git a/core/assets-raw/sprites/blocks/production/blast-mixer.png b/core/assets-raw/sprites/blocks/production/blast-mixer.png new file mode 100644 index 0000000000..c20530333f Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/blast-mixer.png differ diff --git a/core/assets-raw/sprites/blocks/production/centrifuge-liquid.png b/core/assets-raw/sprites/blocks/production/centrifuge-liquid.png new file mode 100644 index 0000000000..3cf9d7393d Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/centrifuge-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/production/centrifuge.png b/core/assets-raw/sprites/blocks/production/centrifuge.png new file mode 100644 index 0000000000..af15eab31e Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/centrifuge.png differ diff --git a/core/assets-raw/sprites/blocks/production/cryofluidmixer-bottom.png b/core/assets-raw/sprites/blocks/production/cryofluidmixer-bottom.png new file mode 100644 index 0000000000..51037d3e26 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cryofluidmixer-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/production/cryofluidmixer-liquid.png b/core/assets-raw/sprites/blocks/production/cryofluidmixer-liquid.png new file mode 100644 index 0000000000..47ab49a42f Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cryofluidmixer-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/production/cryofluidmixer-top.png b/core/assets-raw/sprites/blocks/production/cryofluidmixer-top.png new file mode 100644 index 0000000000..64ffa52a5b Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cryofluidmixer-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/cultivator-middle.png b/core/assets-raw/sprites/blocks/production/cultivator-middle.png new file mode 100644 index 0000000000..636a7c9865 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cultivator-middle.png differ diff --git a/core/assets-raw/sprites/blocks/production/cultivator-top.png b/core/assets-raw/sprites/blocks/production/cultivator-top.png new file mode 100644 index 0000000000..c5db5a8ffd Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cultivator-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/cultivator.png b/core/assets-raw/sprites/blocks/production/cultivator.png new file mode 100644 index 0000000000..584451324f Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/cultivator.png differ diff --git a/core/assets-raw/sprites/blocks/production/incinerator.png b/core/assets-raw/sprites/blocks/production/incinerator.png new file mode 100644 index 0000000000..95b2acd3a9 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/incinerator.png differ diff --git a/core/assets-raw/sprites/blocks/production/itemsource.png b/core/assets-raw/sprites/blocks/production/itemsource.png new file mode 100644 index 0000000000..95ed50bce4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/itemsource.png differ diff --git a/core/assets-raw/sprites/blocks/production/itemvoid.png b/core/assets-raw/sprites/blocks/production/itemvoid.png new file mode 100644 index 0000000000..51b216a3ed Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/itemvoid.png differ diff --git a/core/assets-raw/sprites/blocks/lavasmelter.png b/core/assets-raw/sprites/blocks/production/lavasmelter.png similarity index 100% rename from core/assets-raw/sprites/blocks/lavasmelter.png rename to core/assets-raw/sprites/blocks/production/lavasmelter.png diff --git a/core/assets-raw/sprites/blocks/production/liquidsource.png b/core/assets-raw/sprites/blocks/production/liquidsource.png new file mode 100644 index 0000000000..49e08309f2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/liquidsource.png differ diff --git a/core/assets-raw/sprites/blocks/production/melter.png b/core/assets-raw/sprites/blocks/production/melter.png new file mode 100644 index 0000000000..ea1207e0bf Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/melter.png differ diff --git a/core/assets-raw/sprites/blocks/production/oilrefinery.png b/core/assets-raw/sprites/blocks/production/oilrefinery.png new file mode 100644 index 0000000000..445c57ece3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/oilrefinery.png differ diff --git a/core/assets-raw/sprites/blocks/production/phase-weaver-bottom.png b/core/assets-raw/sprites/blocks/production/phase-weaver-bottom.png new file mode 100644 index 0000000000..986861dd19 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/phase-weaver-bottom.png differ diff --git a/core/assets-raw/sprites/blocks/production/phase-weaver-weave.png b/core/assets-raw/sprites/blocks/production/phase-weaver-weave.png new file mode 100644 index 0000000000..660d664472 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/phase-weaver-weave.png differ diff --git a/core/assets-raw/sprites/blocks/production/phase-weaver.png b/core/assets-raw/sprites/blocks/production/phase-weaver.png new file mode 100644 index 0000000000..b03f62e27b Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/phase-weaver.png differ diff --git a/core/assets-raw/sprites/blocks/production/plastanium-compressor-top.png b/core/assets-raw/sprites/blocks/production/plastanium-compressor-top.png new file mode 100644 index 0000000000..2addf06035 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/plastanium-compressor-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/plastanium-compressor.png b/core/assets-raw/sprites/blocks/production/plastanium-compressor.png new file mode 100644 index 0000000000..2f186d27ad Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/plastanium-compressor.png differ diff --git a/core/assets-raw/sprites/blocks/production/poweralloysmelter-top.png b/core/assets-raw/sprites/blocks/production/poweralloysmelter-top.png new file mode 100644 index 0000000000..e3da203391 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/poweralloysmelter-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/pulverizer-rotator.png b/core/assets-raw/sprites/blocks/production/pulverizer-rotator.png new file mode 100644 index 0000000000..f9e5ab3537 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/pulverizer-rotator.png differ diff --git a/core/assets-raw/sprites/blocks/production/pulverizer.png b/core/assets-raw/sprites/blocks/production/pulverizer.png new file mode 100644 index 0000000000..194c4176b4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/pulverizer.png differ diff --git a/core/assets-raw/sprites/blocks/production/pump-liquid.png b/core/assets-raw/sprites/blocks/production/pump-liquid.png new file mode 100644 index 0000000000..4ecbc4988c Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/pump-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/production/pyratite-mixer.png b/core/assets-raw/sprites/blocks/production/pyratite-mixer.png new file mode 100644 index 0000000000..94abd827e4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/pyratite-mixer.png differ diff --git a/core/assets-raw/sprites/blocks/production/separator-liquid.png b/core/assets-raw/sprites/blocks/production/separator-liquid.png new file mode 100644 index 0000000000..a87bf0b14b Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/separator-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/production/separator.png b/core/assets-raw/sprites/blocks/production/separator.png new file mode 100644 index 0000000000..6254945930 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/separator.png differ diff --git a/core/assets-raw/sprites/blocks/production/silicon-smelter-top.png b/core/assets-raw/sprites/blocks/production/silicon-smelter-top.png new file mode 100644 index 0000000000..f86c4d8f35 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/silicon-smelter-top.png differ diff --git a/core/assets-raw/sprites/blocks/production/silicon-smelter.png b/core/assets-raw/sprites/blocks/production/silicon-smelter.png new file mode 100644 index 0000000000..1543289936 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/silicon-smelter.png differ diff --git a/core/assets-raw/sprites/blocks/production/smelter.png b/core/assets-raw/sprites/blocks/production/smelter.png new file mode 100644 index 0000000000..f3ff85fa19 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/smelter.png differ diff --git a/core/assets-raw/sprites/blocks/production/solidifer.png b/core/assets-raw/sprites/blocks/production/solidifer.png new file mode 100644 index 0000000000..1493f92864 Binary files /dev/null and b/core/assets-raw/sprites/blocks/production/solidifer.png differ diff --git a/core/assets-raw/sprites/blocks/pulseconduit.png b/core/assets-raw/sprites/blocks/pulseconduit.png deleted file mode 100644 index 8a634d9746..0000000000 Binary files a/core/assets-raw/sprites/blocks/pulseconduit.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/pulseconduitbottom.png b/core/assets-raw/sprites/blocks/pulseconduitbottom.png deleted file mode 100644 index 304dc71403..0000000000 Binary files a/core/assets-raw/sprites/blocks/pulseconduitbottom.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/pulseconduittop.png b/core/assets-raw/sprites/blocks/pulseconduittop.png deleted file mode 100644 index a2d6cc9c54..0000000000 Binary files a/core/assets-raw/sprites/blocks/pulseconduittop.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/pump.png b/core/assets-raw/sprites/blocks/pump.png deleted file mode 100644 index 8c81843452..0000000000 Binary files a/core/assets-raw/sprites/blocks/pump.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/repairturret.png b/core/assets-raw/sprites/blocks/repairturret.png deleted file mode 100644 index 66c8b6e288..0000000000 Binary files a/core/assets-raw/sprites/blocks/repairturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/rtgenerator.png b/core/assets-raw/sprites/blocks/rtgenerator.png deleted file mode 100644 index b0a625ba7b..0000000000 Binary files a/core/assets-raw/sprites/blocks/rtgenerator.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/sandblock1.png b/core/assets-raw/sprites/blocks/sandblock1.png deleted file mode 100644 index 52b8edb4ba..0000000000 Binary files a/core/assets-raw/sprites/blocks/sandblock1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/sandblock2.png b/core/assets-raw/sprites/blocks/sandblock2.png deleted file mode 100644 index 646fdd4831..0000000000 Binary files a/core/assets-raw/sprites/blocks/sandblock2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/sandblock3.png b/core/assets-raw/sprites/blocks/sandblock3.png deleted file mode 100644 index ca0093a2e6..0000000000 Binary files a/core/assets-raw/sprites/blocks/sandblock3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/shieldgenerator.png b/core/assets-raw/sprites/blocks/shieldgenerator.png deleted file mode 100644 index 4da9e9ccf5..0000000000 Binary files a/core/assets-raw/sprites/blocks/shieldgenerator.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/shotgunturret.png b/core/assets-raw/sprites/blocks/shotgunturret.png deleted file mode 100644 index 752f95fb0c..0000000000 Binary files a/core/assets-raw/sprites/blocks/shotgunturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/smelter-middle.png b/core/assets-raw/sprites/blocks/smelter-middle.png deleted file mode 100644 index caf10170b4..0000000000 Binary files a/core/assets-raw/sprites/blocks/smelter-middle.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/smelter.png b/core/assets-raw/sprites/blocks/smelter.png deleted file mode 100644 index dd197aad34..0000000000 Binary files a/core/assets-raw/sprites/blocks/smelter.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/sniperturret.png b/core/assets-raw/sprites/blocks/sniperturret.png deleted file mode 100644 index 1d1a2a3649..0000000000 Binary files a/core/assets-raw/sprites/blocks/sniperturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/snowblock1.png b/core/assets-raw/sprites/blocks/snowblock1.png deleted file mode 100644 index a3bb781fe6..0000000000 Binary files a/core/assets-raw/sprites/blocks/snowblock1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/snowblock2.png b/core/assets-raw/sprites/blocks/snowblock2.png deleted file mode 100644 index 508e42fe7e..0000000000 Binary files a/core/assets-raw/sprites/blocks/snowblock2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/snowblock3.png b/core/assets-raw/sprites/blocks/snowblock3.png deleted file mode 100644 index 93d1b071e2..0000000000 Binary files a/core/assets-raw/sprites/blocks/snowblock3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/steelconveyor.png b/core/assets-raw/sprites/blocks/steelconveyor.png deleted file mode 100644 index ff6a0abd21..0000000000 Binary files a/core/assets-raw/sprites/blocks/steelconveyor.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/steelconveyormove.png b/core/assets-raw/sprites/blocks/steelconveyormove.png deleted file mode 100644 index 371a4a8720..0000000000 Binary files a/core/assets-raw/sprites/blocks/steelconveyormove.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/steelwall-large-icon.png b/core/assets-raw/sprites/blocks/steelwall-large-icon.png deleted file mode 100644 index a38f508587..0000000000 Binary files a/core/assets-raw/sprites/blocks/steelwall-large-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/steelwall-large.png b/core/assets-raw/sprites/blocks/steelwall-large.png deleted file mode 100644 index 217e30ff5c..0000000000 Binary files a/core/assets-raw/sprites/blocks/steelwall-large.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/steelwall.png b/core/assets-raw/sprites/blocks/steelwall.png deleted file mode 100644 index ab8652474a..0000000000 Binary files a/core/assets-raw/sprites/blocks/steelwall.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stoneblock1.png b/core/assets-raw/sprites/blocks/stoneblock1.png deleted file mode 100644 index 0783b4b26f..0000000000 Binary files a/core/assets-raw/sprites/blocks/stoneblock1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stoneblock2.png b/core/assets-raw/sprites/blocks/stoneblock2.png deleted file mode 100644 index db2b3a59dd..0000000000 Binary files a/core/assets-raw/sprites/blocks/stoneblock2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stoneblock3.png b/core/assets-raw/sprites/blocks/stoneblock3.png deleted file mode 100644 index 67fcf95616..0000000000 Binary files a/core/assets-raw/sprites/blocks/stoneblock3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stonedrill.png b/core/assets-raw/sprites/blocks/stonedrill.png deleted file mode 100644 index 0fd9b107be..0000000000 Binary files a/core/assets-raw/sprites/blocks/stonedrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stoneformer.png b/core/assets-raw/sprites/blocks/stoneformer.png deleted file mode 100644 index baeedc7505..0000000000 Binary files a/core/assets-raw/sprites/blocks/stoneformer.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/stonewall.png b/core/assets-raw/sprites/blocks/stonewall.png deleted file mode 100644 index 4bacdfdee2..0000000000 Binary files a/core/assets-raw/sprites/blocks/stonewall.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/storage/core-open.png b/core/assets-raw/sprites/blocks/storage/core-open.png new file mode 100644 index 0000000000..dbcc4fbebc Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/core-open.png differ diff --git a/core/assets-raw/sprites/blocks/storage/core-top.png b/core/assets-raw/sprites/blocks/storage/core-top.png new file mode 100644 index 0000000000..f1d779149d Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/core-top.png differ diff --git a/core/assets-raw/sprites/blocks/core.png b/core/assets-raw/sprites/blocks/storage/core.png similarity index 100% rename from core/assets-raw/sprites/blocks/core.png rename to core/assets-raw/sprites/blocks/storage/core.png diff --git a/core/assets-raw/sprites/blocks/storage/sortedunloader.png b/core/assets-raw/sprites/blocks/storage/sortedunloader.png new file mode 100644 index 0000000000..1845832275 Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/sortedunloader.png differ diff --git a/core/assets-raw/sprites/blocks/storage/unloader.png b/core/assets-raw/sprites/blocks/storage/unloader.png new file mode 100644 index 0000000000..7de606ee51 Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/unloader.png differ diff --git a/core/assets-raw/sprites/blocks/storage/vault.png b/core/assets-raw/sprites/blocks/storage/vault.png new file mode 100644 index 0000000000..4bf04a80fb Binary files /dev/null and b/core/assets-raw/sprites/blocks/storage/vault.png differ diff --git a/core/assets-raw/sprites/blocks/teleporter-top.png b/core/assets-raw/sprites/blocks/teleporter-top.png deleted file mode 100644 index 5e624107a8..0000000000 Binary files a/core/assets-raw/sprites/blocks/teleporter-top.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/teleporter.png b/core/assets-raw/sprites/blocks/teleporter.png deleted file mode 100644 index 74f3d68a16..0000000000 Binary files a/core/assets-raw/sprites/blocks/teleporter.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/thermalgenerator.png b/core/assets-raw/sprites/blocks/thermalgenerator.png deleted file mode 100644 index 62ce54ca84..0000000000 Binary files a/core/assets-raw/sprites/blocks/thermalgenerator.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titancannon-icon.png b/core/assets-raw/sprites/blocks/titancannon-icon.png deleted file mode 100644 index 718e833971..0000000000 Binary files a/core/assets-raw/sprites/blocks/titancannon-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titancannon-icon_old.png b/core/assets-raw/sprites/blocks/titancannon-icon_old.png deleted file mode 100644 index 9d4cee27fb..0000000000 Binary files a/core/assets-raw/sprites/blocks/titancannon-icon_old.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titancannon.png b/core/assets-raw/sprites/blocks/titancannon.png deleted file mode 100644 index a1e47ed71b..0000000000 Binary files a/core/assets-raw/sprites/blocks/titancannon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titancannon_old.png b/core/assets-raw/sprites/blocks/titancannon_old.png deleted file mode 100644 index fc492458c2..0000000000 Binary files a/core/assets-raw/sprites/blocks/titancannon_old.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titanium1.png b/core/assets-raw/sprites/blocks/titanium1.png deleted file mode 100644 index 5cf051540b..0000000000 Binary files a/core/assets-raw/sprites/blocks/titanium1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titanium2.png b/core/assets-raw/sprites/blocks/titanium2.png deleted file mode 100644 index 9aad0c978f..0000000000 Binary files a/core/assets-raw/sprites/blocks/titanium2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titanium3.png b/core/assets-raw/sprites/blocks/titanium3.png deleted file mode 100644 index f8f1ed80a3..0000000000 Binary files a/core/assets-raw/sprites/blocks/titanium3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumdrill.png b/core/assets-raw/sprites/blocks/titaniumdrill.png deleted file mode 100644 index d2f8fef5fc..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumdrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumpurifier.png b/core/assets-raw/sprites/blocks/titaniumpurifier.png deleted file mode 100644 index 82d2b85848..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumpurifier.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumshieldwall.png b/core/assets-raw/sprites/blocks/titaniumshieldwall.png deleted file mode 100644 index b33dfe5ff5..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumshieldwall.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumwall-large-icon.png b/core/assets-raw/sprites/blocks/titaniumwall-large-icon.png deleted file mode 100644 index 6361f084c5..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumwall-large-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumwall-large.png b/core/assets-raw/sprites/blocks/titaniumwall-large.png deleted file mode 100644 index 807fde31b1..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumwall-large.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/titaniumwall.png b/core/assets-raw/sprites/blocks/titaniumwall.png deleted file mode 100644 index 583c63e4a3..0000000000 Binary files a/core/assets-raw/sprites/blocks/titaniumwall.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/turret.png b/core/assets-raw/sprites/blocks/turret.png deleted file mode 100644 index 3edb816320..0000000000 Binary files a/core/assets-raw/sprites/blocks/turret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-1-top.png b/core/assets-raw/sprites/blocks/turrets/bases/block-1-top.png new file mode 100644 index 0000000000..601189e850 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-1-top.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-1.png b/core/assets-raw/sprites/blocks/turrets/bases/block-1.png new file mode 100644 index 0000000000..9f8b7cfa93 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-1.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-2-top.png b/core/assets-raw/sprites/blocks/turrets/bases/block-2-top.png new file mode 100644 index 0000000000..62f631c40b Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-2-top.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-2.png b/core/assets-raw/sprites/blocks/turrets/bases/block-2.png new file mode 100644 index 0000000000..49bef72447 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-2.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-3-top.png b/core/assets-raw/sprites/blocks/turrets/bases/block-3-top.png new file mode 100644 index 0000000000..c06f2803fc Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-3-top.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-3.png b/core/assets-raw/sprites/blocks/turrets/bases/block-3.png new file mode 100644 index 0000000000..2a1becda62 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-3.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-4-top.png b/core/assets-raw/sprites/blocks/turrets/bases/block-4-top.png new file mode 100644 index 0000000000..d37d0f88bd Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-4-top.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/bases/block-4.png b/core/assets-raw/sprites/blocks/turrets/bases/block-4.png new file mode 100644 index 0000000000..24cae427f5 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/bases/block-4.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/arc-heat.png b/core/assets-raw/sprites/blocks/turrets/turrets/arc-heat.png new file mode 100644 index 0000000000..dd8046dac7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/arc-heat.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/arc.png b/core/assets-raw/sprites/blocks/turrets/turrets/arc.png new file mode 100644 index 0000000000..6c083ce15f Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/arc.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/cyclone.png b/core/assets-raw/sprites/blocks/turrets/turrets/cyclone.png new file mode 100644 index 0000000000..350e7c66f4 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/cyclone.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/duo.png b/core/assets-raw/sprites/blocks/turrets/turrets/duo.png new file mode 100644 index 0000000000..7bae2646f3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/duo.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/fuse.png b/core/assets-raw/sprites/blocks/turrets/turrets/fuse.png new file mode 100644 index 0000000000..2a5edf540a Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/fuse.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/hail-heat.png b/core/assets-raw/sprites/blocks/turrets/turrets/hail-heat.png new file mode 100644 index 0000000000..4bec862cb6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/hail-heat.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/hail.png b/core/assets-raw/sprites/blocks/turrets/turrets/hail.png new file mode 100644 index 0000000000..075325a110 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/hail.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/lancer-heat.png b/core/assets-raw/sprites/blocks/turrets/turrets/lancer-heat.png new file mode 100644 index 0000000000..28edce943f Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/lancer-heat.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/lancer.png b/core/assets-raw/sprites/blocks/turrets/turrets/lancer.png new file mode 100644 index 0000000000..eb1fde9df0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/lancer.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/meltdown.png b/core/assets-raw/sprites/blocks/turrets/turrets/meltdown.png new file mode 100644 index 0000000000..f873d676fe Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/meltdown.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/ripple-heat.png b/core/assets-raw/sprites/blocks/turrets/turrets/ripple-heat.png new file mode 100644 index 0000000000..d58f923fda Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/ripple-heat.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/ripple.png b/core/assets-raw/sprites/blocks/turrets/turrets/ripple.png new file mode 100644 index 0000000000..72062b54b1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/ripple.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/salvo-heat.png b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-heat.png new file mode 100644 index 0000000000..39a14c6daf Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-heat.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-left.png b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-left.png new file mode 100644 index 0000000000..390e9f178b Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-left.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-right.png b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-right.png new file mode 100644 index 0000000000..d647a16e3b Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/salvo-panel-right.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/salvo.png b/core/assets-raw/sprites/blocks/turrets/turrets/salvo.png new file mode 100644 index 0000000000..2264599bdc Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/salvo.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/scatter.png b/core/assets-raw/sprites/blocks/turrets/turrets/scatter.png new file mode 100644 index 0000000000..075325a110 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/scatter.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/scorch-shoot.png b/core/assets-raw/sprites/blocks/turrets/turrets/scorch-shoot.png new file mode 100644 index 0000000000..c03ba1b198 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/scorch-shoot.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/scorch.png b/core/assets-raw/sprites/blocks/turrets/turrets/scorch.png new file mode 100644 index 0000000000..91338f693f Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/scorch.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/spectre.png b/core/assets-raw/sprites/blocks/turrets/turrets/spectre.png new file mode 100644 index 0000000000..056a823abc Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/spectre.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/swarmer.png b/core/assets-raw/sprites/blocks/turrets/turrets/swarmer.png new file mode 100644 index 0000000000..ba80299957 Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/swarmer.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/wave-liquid.png b/core/assets-raw/sprites/blocks/turrets/turrets/wave-liquid.png new file mode 100644 index 0000000000..134e93cd0c Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/wave-liquid.png differ diff --git a/core/assets-raw/sprites/blocks/turrets/turrets/wave.png b/core/assets-raw/sprites/blocks/turrets/turrets/wave.png new file mode 100644 index 0000000000..83c67c389d Binary files /dev/null and b/core/assets-raw/sprites/blocks/turrets/turrets/wave.png differ diff --git a/core/assets-raw/sprites/blocks/units/drone-factory-top-open.png b/core/assets-raw/sprites/blocks/units/drone-factory-top-open.png new file mode 100644 index 0000000000..25db6bb2b2 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/drone-factory-top-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/drone-factory-top.png b/core/assets-raw/sprites/blocks/units/drone-factory-top.png new file mode 100644 index 0000000000..2e6562c82b Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/drone-factory-top.png differ diff --git a/core/assets-raw/sprites/blocks/units/drone-factory.png b/core/assets-raw/sprites/blocks/units/drone-factory.png new file mode 100644 index 0000000000..b703c27481 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/drone-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/drop-point.png b/core/assets-raw/sprites/blocks/units/drop-point.png new file mode 100644 index 0000000000..de57674e8e Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/drop-point.png differ diff --git a/core/assets-raw/sprites/blocks/units/fabricator-factory-top-open.png b/core/assets-raw/sprites/blocks/units/fabricator-factory-top-open.png new file mode 100644 index 0000000000..9e99b15561 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/fabricator-factory-top-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/fabricator-factory-top.png b/core/assets-raw/sprites/blocks/units/fabricator-factory-top.png new file mode 100644 index 0000000000..373223f3cc Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/fabricator-factory-top.png differ diff --git a/core/assets-raw/sprites/blocks/units/fabricator-factory.png b/core/assets-raw/sprites/blocks/units/fabricator-factory.png new file mode 100644 index 0000000000..b703c27481 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/fabricator-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/mech-factory.png b/core/assets-raw/sprites/blocks/units/mech-factory.png new file mode 100644 index 0000000000..5d7b07b3b7 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mech-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory-open.png b/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory-open.png new file mode 100644 index 0000000000..3dcbb76e63 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory.png b/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory.png new file mode 100644 index 0000000000..72edb2c28b Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/dart-ship-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory-open.png b/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory-open.png new file mode 100644 index 0000000000..90889dc05c Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory.png b/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory.png new file mode 100644 index 0000000000..387dd7dc0d Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/delta-mech-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory-open.png b/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory-open.png new file mode 100644 index 0000000000..2e3ed0c350 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory.png b/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory.png new file mode 100644 index 0000000000..786be242a0 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/mechs/javelin-ship-factory.png differ diff --git a/core/assets-raw/sprites/blocks/units/reconstructor-open.png b/core/assets-raw/sprites/blocks/units/reconstructor-open.png new file mode 100644 index 0000000000..32527045b3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/reconstructor-open.png differ diff --git a/core/assets-raw/sprites/blocks/units/reconstructor.png b/core/assets-raw/sprites/blocks/units/reconstructor.png new file mode 100644 index 0000000000..b703c27481 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/reconstructor.png differ diff --git a/core/assets-raw/sprites/blocks/units/repair-point-turret.png b/core/assets-raw/sprites/blocks/units/repair-point-turret.png new file mode 100644 index 0000000000..f574433e19 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/repair-point-turret.png differ diff --git a/core/assets-raw/sprites/blocks/units/repair-point.png b/core/assets-raw/sprites/blocks/units/repair-point.png new file mode 100644 index 0000000000..5a50dd76f3 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/repair-point.png differ diff --git a/core/assets-raw/sprites/blocks/units/resupply-point.png b/core/assets-raw/sprites/blocks/units/resupply-point.png new file mode 100644 index 0000000000..a69238a59e Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/resupply-point.png differ diff --git a/core/assets-raw/sprites/blocks/units/ship-factory.png b/core/assets-raw/sprites/blocks/units/ship-factory.png new file mode 100644 index 0000000000..f662f6ba25 Binary files /dev/null and b/core/assets-raw/sprites/blocks/units/ship-factory.png differ diff --git a/core/assets-raw/sprites/blocks/uranium1.png b/core/assets-raw/sprites/blocks/uranium1.png deleted file mode 100644 index c89c866a93..0000000000 Binary files a/core/assets-raw/sprites/blocks/uranium1.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/uranium2.png b/core/assets-raw/sprites/blocks/uranium2.png deleted file mode 100644 index 20eb054ec3..0000000000 Binary files a/core/assets-raw/sprites/blocks/uranium2.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/uranium3.png b/core/assets-raw/sprites/blocks/uranium3.png deleted file mode 100644 index dfa5ed2cc2..0000000000 Binary files a/core/assets-raw/sprites/blocks/uranium3.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/uraniumdrill.png b/core/assets-raw/sprites/blocks/uraniumdrill.png deleted file mode 100644 index 7849b7cecd..0000000000 Binary files a/core/assets-raw/sprites/blocks/uraniumdrill.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/walls/carbide-wall-large.png b/core/assets-raw/sprites/blocks/walls/carbide-wall-large.png new file mode 100644 index 0000000000..0bfb7563ce Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/carbide-wall-large.png differ diff --git a/core/assets-raw/sprites/blocks/walls/carbide-wall.png b/core/assets-raw/sprites/blocks/walls/carbide-wall.png new file mode 100644 index 0000000000..ba7cabf436 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/carbide-wall.png differ diff --git a/core/assets-raw/sprites/blocks/duriumwall-large.png b/core/assets-raw/sprites/blocks/walls/deflector-wall-large.png similarity index 100% rename from core/assets-raw/sprites/blocks/duriumwall-large.png rename to core/assets-raw/sprites/blocks/walls/deflector-wall-large.png diff --git a/core/assets-raw/sprites/blocks/duriumwall.png b/core/assets-raw/sprites/blocks/walls/deflector-wall.png similarity index 100% rename from core/assets-raw/sprites/blocks/duriumwall.png rename to core/assets-raw/sprites/blocks/walls/deflector-wall.png diff --git a/core/assets-raw/sprites/blocks/walls/door-large-open.png b/core/assets-raw/sprites/blocks/walls/door-large-open.png new file mode 100644 index 0000000000..d4f26329ec Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/door-large-open.png differ diff --git a/core/assets-raw/sprites/blocks/walls/door-large.png b/core/assets-raw/sprites/blocks/walls/door-large.png new file mode 100644 index 0000000000..d0a93cccbe Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/door-large.png differ diff --git a/core/assets-raw/sprites/blocks/walls/door-open.png b/core/assets-raw/sprites/blocks/walls/door-open.png new file mode 100644 index 0000000000..0112c2cf03 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/door-open.png differ diff --git a/core/assets-raw/sprites/blocks/walls/door.png b/core/assets-raw/sprites/blocks/walls/door.png new file mode 100644 index 0000000000..84266f37da Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/door.png differ diff --git a/core/assets-raw/sprites/blocks/walls/phase-wall-large.png b/core/assets-raw/sprites/blocks/walls/phase-wall-large.png new file mode 100644 index 0000000000..7aa89afbd9 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/phase-wall-large.png differ diff --git a/core/assets-raw/sprites/blocks/walls/phase-wall.png b/core/assets-raw/sprites/blocks/walls/phase-wall.png new file mode 100644 index 0000000000..cdb08fd654 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/phase-wall.png differ diff --git a/core/assets-raw/sprites/blocks/walls/thorium-wall-large.png b/core/assets-raw/sprites/blocks/walls/thorium-wall-large.png new file mode 100644 index 0000000000..ab909004d6 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/thorium-wall-large.png differ diff --git a/core/assets-raw/sprites/blocks/walls/thorium-wall.png b/core/assets-raw/sprites/blocks/walls/thorium-wall.png new file mode 100644 index 0000000000..4a4e8d83c1 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/thorium-wall.png differ diff --git a/core/assets-raw/sprites/blocks/walls/tungsten-wall-large.png b/core/assets-raw/sprites/blocks/walls/tungsten-wall-large.png new file mode 100644 index 0000000000..28f37e4207 Binary files /dev/null and b/core/assets-raw/sprites/blocks/walls/tungsten-wall-large.png differ diff --git a/core/assets-raw/sprites/blocks/ironwall.png b/core/assets-raw/sprites/blocks/walls/tungsten-wall.png similarity index 100% rename from core/assets-raw/sprites/blocks/ironwall.png rename to core/assets-raw/sprites/blocks/walls/tungsten-wall.png diff --git a/core/assets-raw/sprites/blocks/waveturret.png b/core/assets-raw/sprites/blocks/waveturret.png deleted file mode 100644 index 7ca8d8a3f8..0000000000 Binary files a/core/assets-raw/sprites/blocks/waveturret.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/weaponfactory-icon.png b/core/assets-raw/sprites/blocks/weaponfactory-icon.png deleted file mode 100644 index 27065c56af..0000000000 Binary files a/core/assets-raw/sprites/blocks/weaponfactory-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/blocks/weaponfactory.png b/core/assets-raw/sprites/blocks/weaponfactory.png deleted file mode 100644 index 0938f64971..0000000000 Binary files a/core/assets-raw/sprites/blocks/weaponfactory.png and /dev/null differ diff --git a/core/assets-raw/sprites/bullet.png b/core/assets-raw/sprites/bullet.png deleted file mode 100644 index 5f1c82cdbc..0000000000 Binary files a/core/assets-raw/sprites/bullet.png and /dev/null differ diff --git a/core/assets-raw/sprites/chainbullet.png b/core/assets-raw/sprites/chainbullet.png deleted file mode 100644 index aaf1228374..0000000000 Binary files a/core/assets-raw/sprites/chainbullet.png and /dev/null differ diff --git a/core/assets-raw/sprites/circle.png b/core/assets-raw/sprites/circle.png deleted file mode 100644 index 154fd0dd83..0000000000 Binary files a/core/assets-raw/sprites/circle.png and /dev/null differ diff --git a/core/assets-raw/sprites/effects/beam-end.png b/core/assets-raw/sprites/effects/beam-end.png new file mode 100644 index 0000000000..14bc5b24e9 Binary files /dev/null and b/core/assets-raw/sprites/effects/beam-end.png differ diff --git a/core/assets-raw/sprites/effects/beam.png b/core/assets-raw/sprites/effects/beam.png new file mode 100644 index 0000000000..d556a4cd07 Binary files /dev/null and b/core/assets-raw/sprites/effects/beam.png differ diff --git a/core/assets-raw/sprites/blank.png b/core/assets-raw/sprites/effects/blank.png similarity index 100% rename from core/assets-raw/sprites/blank.png rename to core/assets-raw/sprites/effects/blank.png diff --git a/core/assets-raw/sprites/effects/bullet-back.png b/core/assets-raw/sprites/effects/bullet-back.png new file mode 100644 index 0000000000..0e18b51a45 Binary files /dev/null and b/core/assets-raw/sprites/effects/bullet-back.png differ diff --git a/core/assets-raw/sprites/effects/bullet.png b/core/assets-raw/sprites/effects/bullet.png new file mode 100644 index 0000000000..716f462489 Binary files /dev/null and b/core/assets-raw/sprites/effects/bullet.png differ diff --git a/core/assets-raw/sprites/effects/casing.png b/core/assets-raw/sprites/effects/casing.png new file mode 100644 index 0000000000..7c46a3ccf7 Binary files /dev/null and b/core/assets-raw/sprites/effects/casing.png differ diff --git a/core/assets-raw/sprites/effects/clear.png b/core/assets-raw/sprites/effects/clear.png new file mode 100644 index 0000000000..4df27225b0 Binary files /dev/null and b/core/assets-raw/sprites/effects/clear.png differ diff --git a/core/assets-raw/sprites/enemyarrow.png b/core/assets-raw/sprites/effects/enemyarrow.png similarity index 100% rename from core/assets-raw/sprites/enemyarrow.png rename to core/assets-raw/sprites/effects/enemyarrow.png diff --git a/core/assets-raw/sprites/effects/error.png b/core/assets-raw/sprites/effects/error.png new file mode 100644 index 0000000000..0da635889b Binary files /dev/null and b/core/assets-raw/sprites/effects/error.png differ diff --git a/core/assets-raw/sprites/laserend.png b/core/assets-raw/sprites/effects/laser-end.png similarity index 100% rename from core/assets-raw/sprites/laserend.png rename to core/assets-raw/sprites/effects/laser-end.png diff --git a/core/assets-raw/sprites/laser.png b/core/assets-raw/sprites/effects/laser.png similarity index 100% rename from core/assets-raw/sprites/laser.png rename to core/assets-raw/sprites/effects/laser.png diff --git a/core/assets-raw/sprites/laserfull.png b/core/assets-raw/sprites/effects/laserfull.png similarity index 100% rename from core/assets-raw/sprites/laserfull.png rename to core/assets-raw/sprites/effects/laserfull.png diff --git a/core/assets-raw/sprites/effects/minelaser-end.png b/core/assets-raw/sprites/effects/minelaser-end.png new file mode 100644 index 0000000000..7bfed103d6 Binary files /dev/null and b/core/assets-raw/sprites/effects/minelaser-end.png differ diff --git a/core/assets-raw/sprites/effects/minelaser.png b/core/assets-raw/sprites/effects/minelaser.png new file mode 100644 index 0000000000..b6c990e21e Binary files /dev/null and b/core/assets-raw/sprites/effects/minelaser.png differ diff --git a/core/assets-raw/sprites/effects/missile-back.png b/core/assets-raw/sprites/effects/missile-back.png new file mode 100644 index 0000000000..2f0676e058 Binary files /dev/null and b/core/assets-raw/sprites/effects/missile-back.png differ diff --git a/core/assets-raw/sprites/effects/missile.png b/core/assets-raw/sprites/effects/missile.png new file mode 100644 index 0000000000..de5f1f0b17 Binary files /dev/null and b/core/assets-raw/sprites/effects/missile.png differ diff --git a/core/assets-raw/sprites/effects/scorch1.png b/core/assets-raw/sprites/effects/scorch1.png new file mode 100644 index 0000000000..70f74ca624 Binary files /dev/null and b/core/assets-raw/sprites/effects/scorch1.png differ diff --git a/core/assets-raw/sprites/effects/scorch2.png b/core/assets-raw/sprites/effects/scorch2.png new file mode 100644 index 0000000000..696f77a9f1 Binary files /dev/null and b/core/assets-raw/sprites/effects/scorch2.png differ diff --git a/core/assets-raw/sprites/effects/scorch3.png b/core/assets-raw/sprites/effects/scorch3.png new file mode 100644 index 0000000000..76725325de Binary files /dev/null and b/core/assets-raw/sprites/effects/scorch3.png differ diff --git a/core/assets-raw/sprites/effects/scorch4.png b/core/assets-raw/sprites/effects/scorch4.png new file mode 100644 index 0000000000..908ac88324 Binary files /dev/null and b/core/assets-raw/sprites/effects/scorch4.png differ diff --git a/core/assets-raw/sprites/effects/scorch5.png b/core/assets-raw/sprites/effects/scorch5.png new file mode 100644 index 0000000000..09bc0c4a01 Binary files /dev/null and b/core/assets-raw/sprites/effects/scorch5.png differ diff --git a/core/assets-raw/sprites/effects/shell-back.png b/core/assets-raw/sprites/effects/shell-back.png new file mode 100644 index 0000000000..2e01159935 Binary files /dev/null and b/core/assets-raw/sprites/effects/shell-back.png differ diff --git a/core/assets-raw/sprites/effects/shell.png b/core/assets-raw/sprites/effects/shell.png new file mode 100644 index 0000000000..ade07f9fc8 Binary files /dev/null and b/core/assets-raw/sprites/effects/shell.png differ diff --git a/core/assets-raw/sprites/shot.png b/core/assets-raw/sprites/effects/shot.png similarity index 100% rename from core/assets-raw/sprites/shot.png rename to core/assets-raw/sprites/effects/shot.png diff --git a/core/assets-raw/sprites/effects/transfer-arrow.png b/core/assets-raw/sprites/effects/transfer-arrow.png new file mode 100644 index 0000000000..3f284bd32f Binary files /dev/null and b/core/assets-raw/sprites/effects/transfer-arrow.png differ diff --git a/core/assets-raw/sprites/effects/transfer-end.png b/core/assets-raw/sprites/effects/transfer-end.png new file mode 100644 index 0000000000..cc5ced07aa Binary files /dev/null and b/core/assets-raw/sprites/effects/transfer-end.png differ diff --git a/core/assets-raw/sprites/effects/transfer.png b/core/assets-raw/sprites/effects/transfer.png new file mode 100644 index 0000000000..e6ed599706 Binary files /dev/null and b/core/assets-raw/sprites/effects/transfer.png differ diff --git a/core/assets-raw/sprites/enemies/blastenemy-t1.png b/core/assets-raw/sprites/enemies/blastenemy-t1.png deleted file mode 100644 index 54d3d77722..0000000000 Binary files a/core/assets-raw/sprites/enemies/blastenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/blastenemy-t2.png b/core/assets-raw/sprites/enemies/blastenemy-t2.png deleted file mode 100644 index d48d5c3447..0000000000 Binary files a/core/assets-raw/sprites/enemies/blastenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/blastenemy-t3.png b/core/assets-raw/sprites/enemies/blastenemy-t3.png deleted file mode 100644 index 18c2172c68..0000000000 Binary files a/core/assets-raw/sprites/enemies/blastenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/empenemy-t1.png b/core/assets-raw/sprites/enemies/empenemy-t1.png deleted file mode 100644 index 0161483346..0000000000 Binary files a/core/assets-raw/sprites/enemies/empenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/empenemy-t2.png b/core/assets-raw/sprites/enemies/empenemy-t2.png deleted file mode 100644 index 2779405f89..0000000000 Binary files a/core/assets-raw/sprites/enemies/empenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/empenemy-t3.png b/core/assets-raw/sprites/enemies/empenemy-t3.png deleted file mode 100644 index c82d2e6943..0000000000 Binary files a/core/assets-raw/sprites/enemies/empenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fastenemy-t1.png b/core/assets-raw/sprites/enemies/fastenemy-t1.png deleted file mode 100644 index dd6eedee5a..0000000000 Binary files a/core/assets-raw/sprites/enemies/fastenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fastenemy-t2.png b/core/assets-raw/sprites/enemies/fastenemy-t2.png deleted file mode 100644 index 525fb577ea..0000000000 Binary files a/core/assets-raw/sprites/enemies/fastenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fastenemy-t3.png b/core/assets-raw/sprites/enemies/fastenemy-t3.png deleted file mode 100644 index fc447b6bd7..0000000000 Binary files a/core/assets-raw/sprites/enemies/fastenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/flamerenemy-t1.png b/core/assets-raw/sprites/enemies/flamerenemy-t1.png deleted file mode 100644 index 4d13b33bea..0000000000 Binary files a/core/assets-raw/sprites/enemies/flamerenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/flamerenemy-t2.png b/core/assets-raw/sprites/enemies/flamerenemy-t2.png deleted file mode 100644 index c4b331c34b..0000000000 Binary files a/core/assets-raw/sprites/enemies/flamerenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/flamerenemy-t3.png b/core/assets-raw/sprites/enemies/flamerenemy-t3.png deleted file mode 100644 index 91bfb2c5d7..0000000000 Binary files a/core/assets-raw/sprites/enemies/flamerenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fortressenemy-t1.png b/core/assets-raw/sprites/enemies/fortressenemy-t1.png deleted file mode 100644 index 08af9a95b3..0000000000 Binary files a/core/assets-raw/sprites/enemies/fortressenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fortressenemy-t2.png b/core/assets-raw/sprites/enemies/fortressenemy-t2.png deleted file mode 100644 index 07eec2cd31..0000000000 Binary files a/core/assets-raw/sprites/enemies/fortressenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/fortressenemy-t3.png b/core/assets-raw/sprites/enemies/fortressenemy-t3.png deleted file mode 100644 index 7beb99e5f7..0000000000 Binary files a/core/assets-raw/sprites/enemies/fortressenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/healerenemy-t1.png b/core/assets-raw/sprites/enemies/healerenemy-t1.png deleted file mode 100644 index a2a019f515..0000000000 Binary files a/core/assets-raw/sprites/enemies/healerenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/healerenemy-t2.png b/core/assets-raw/sprites/enemies/healerenemy-t2.png deleted file mode 100644 index c46199589e..0000000000 Binary files a/core/assets-raw/sprites/enemies/healerenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/healerenemy-t3.png b/core/assets-raw/sprites/enemies/healerenemy-t3.png deleted file mode 100644 index 736533e97e..0000000000 Binary files a/core/assets-raw/sprites/enemies/healerenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/mortarenemy-t1.png b/core/assets-raw/sprites/enemies/mortarenemy-t1.png deleted file mode 100644 index 71d6ace244..0000000000 Binary files a/core/assets-raw/sprites/enemies/mortarenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/mortarenemy-t2.png b/core/assets-raw/sprites/enemies/mortarenemy-t2.png deleted file mode 100644 index 71072a08ea..0000000000 Binary files a/core/assets-raw/sprites/enemies/mortarenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/mortarenemy-t3.png b/core/assets-raw/sprites/enemies/mortarenemy-t3.png deleted file mode 100644 index 74845b4a2b..0000000000 Binary files a/core/assets-raw/sprites/enemies/mortarenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/rapidenemy-t1.png b/core/assets-raw/sprites/enemies/rapidenemy-t1.png deleted file mode 100644 index 4266c7839a..0000000000 Binary files a/core/assets-raw/sprites/enemies/rapidenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/rapidenemy-t2.png b/core/assets-raw/sprites/enemies/rapidenemy-t2.png deleted file mode 100644 index d92b7a1e96..0000000000 Binary files a/core/assets-raw/sprites/enemies/rapidenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/rapidenemy-t3.png b/core/assets-raw/sprites/enemies/rapidenemy-t3.png deleted file mode 100644 index e8fd4c0fe4..0000000000 Binary files a/core/assets-raw/sprites/enemies/rapidenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/standardenemy-t1.png b/core/assets-raw/sprites/enemies/standardenemy-t1.png deleted file mode 100644 index 72d033db76..0000000000 Binary files a/core/assets-raw/sprites/enemies/standardenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/standardenemy-t2.png b/core/assets-raw/sprites/enemies/standardenemy-t2.png deleted file mode 100644 index 642d06cb6c..0000000000 Binary files a/core/assets-raw/sprites/enemies/standardenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/standardenemy-t3.png b/core/assets-raw/sprites/enemies/standardenemy-t3.png deleted file mode 100644 index c02539e6c5..0000000000 Binary files a/core/assets-raw/sprites/enemies/standardenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/tankenemy-t1.png b/core/assets-raw/sprites/enemies/tankenemy-t1.png deleted file mode 100644 index 059bc6e6c7..0000000000 Binary files a/core/assets-raw/sprites/enemies/tankenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/tankenemy-t2.png b/core/assets-raw/sprites/enemies/tankenemy-t2.png deleted file mode 100644 index a959046fc9..0000000000 Binary files a/core/assets-raw/sprites/enemies/tankenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/tankenemy-t3.png b/core/assets-raw/sprites/enemies/tankenemy-t3.png deleted file mode 100644 index 836275cdb3..0000000000 Binary files a/core/assets-raw/sprites/enemies/tankenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/targetenemy-t1.png b/core/assets-raw/sprites/enemies/targetenemy-t1.png deleted file mode 100644 index 72d033db76..0000000000 Binary files a/core/assets-raw/sprites/enemies/targetenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/titanenemy-t1.png b/core/assets-raw/sprites/enemies/titanenemy-t1.png deleted file mode 100644 index 204488c950..0000000000 Binary files a/core/assets-raw/sprites/enemies/titanenemy-t1.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/titanenemy-t2.png b/core/assets-raw/sprites/enemies/titanenemy-t2.png deleted file mode 100644 index 9acc6a5181..0000000000 Binary files a/core/assets-raw/sprites/enemies/titanenemy-t2.png and /dev/null differ diff --git a/core/assets-raw/sprites/enemies/titanenemy-t3.png b/core/assets-raw/sprites/enemies/titanenemy-t3.png deleted file mode 100644 index 162a2c50bd..0000000000 Binary files a/core/assets-raw/sprites/enemies/titanenemy-t3.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-dirium.png b/core/assets-raw/sprites/icon-dirium.png deleted file mode 100644 index df7bfef7ab..0000000000 Binary files a/core/assets-raw/sprites/icon-dirium.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-iron.png b/core/assets-raw/sprites/icon-iron.png deleted file mode 100644 index 46b05fb505..0000000000 Binary files a/core/assets-raw/sprites/icon-iron.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-sand.png b/core/assets-raw/sprites/icon-sand.png deleted file mode 100644 index 6492eb7b9d..0000000000 Binary files a/core/assets-raw/sprites/icon-sand.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-steel.png b/core/assets-raw/sprites/icon-steel.png deleted file mode 100644 index 1f4cd1eda8..0000000000 Binary files a/core/assets-raw/sprites/icon-steel.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-titanium.png b/core/assets-raw/sprites/icon-titanium.png deleted file mode 100644 index 01e75295de..0000000000 Binary files a/core/assets-raw/sprites/icon-titanium.png and /dev/null differ diff --git a/core/assets-raw/sprites/icon-uranium.png b/core/assets-raw/sprites/icon-uranium.png deleted file mode 100644 index 0dd3f67a71..0000000000 Binary files a/core/assets-raw/sprites/icon-uranium.png and /dev/null differ diff --git a/core/assets-raw/sprites/items/item-biomatter.png b/core/assets-raw/sprites/items/item-biomatter.png new file mode 100644 index 0000000000..58aeaaad59 Binary files /dev/null and b/core/assets-raw/sprites/items/item-biomatter.png differ diff --git a/core/assets-raw/sprites/items/item-blast-compound.png b/core/assets-raw/sprites/items/item-blast-compound.png new file mode 100644 index 0000000000..eda970bf5c Binary files /dev/null and b/core/assets-raw/sprites/items/item-blast-compound.png differ diff --git a/core/assets-raw/sprites/items/item-carbide.png b/core/assets-raw/sprites/items/item-carbide.png new file mode 100644 index 0000000000..44e95c280e Binary files /dev/null and b/core/assets-raw/sprites/items/item-carbide.png differ diff --git a/core/assets-raw/sprites/icon-coal.png b/core/assets-raw/sprites/items/item-coal.png similarity index 100% rename from core/assets-raw/sprites/icon-coal.png rename to core/assets-raw/sprites/items/item-coal.png diff --git a/core/assets-raw/sprites/items/item-lead.png b/core/assets-raw/sprites/items/item-lead.png new file mode 100644 index 0000000000..e0bb050697 Binary files /dev/null and b/core/assets-raw/sprites/items/item-lead.png differ diff --git a/core/assets-raw/sprites/items/item-phase-matter.png b/core/assets-raw/sprites/items/item-phase-matter.png new file mode 100644 index 0000000000..fcacd1e8d2 Binary files /dev/null and b/core/assets-raw/sprites/items/item-phase-matter.png differ diff --git a/core/assets-raw/sprites/items/item-plastanium.png b/core/assets-raw/sprites/items/item-plastanium.png new file mode 100644 index 0000000000..81fffc80c0 Binary files /dev/null and b/core/assets-raw/sprites/items/item-plastanium.png differ diff --git a/core/assets-raw/sprites/items/item-pyratite.png b/core/assets-raw/sprites/items/item-pyratite.png new file mode 100644 index 0000000000..81a89836e6 Binary files /dev/null and b/core/assets-raw/sprites/items/item-pyratite.png differ diff --git a/core/assets-raw/sprites/items/item-sand.png b/core/assets-raw/sprites/items/item-sand.png new file mode 100644 index 0000000000..57a5be0aad Binary files /dev/null and b/core/assets-raw/sprites/items/item-sand.png differ diff --git a/core/assets-raw/sprites/items/item-silicon.png b/core/assets-raw/sprites/items/item-silicon.png new file mode 100644 index 0000000000..348ddd4a55 Binary files /dev/null and b/core/assets-raw/sprites/items/item-silicon.png differ diff --git a/core/assets-raw/sprites/icon-stone.png b/core/assets-raw/sprites/items/item-stone.png similarity index 100% rename from core/assets-raw/sprites/icon-stone.png rename to core/assets-raw/sprites/items/item-stone.png diff --git a/core/assets-raw/sprites/items/item-surge-alloy.png b/core/assets-raw/sprites/items/item-surge-alloy.png new file mode 100644 index 0000000000..db7d97a9e7 Binary files /dev/null and b/core/assets-raw/sprites/items/item-surge-alloy.png differ diff --git a/core/assets-raw/sprites/items/item-thorium.png b/core/assets-raw/sprites/items/item-thorium.png new file mode 100644 index 0000000000..3f4bc57bfe Binary files /dev/null and b/core/assets-raw/sprites/items/item-thorium.png differ diff --git a/core/assets-raw/sprites/items/item-titanium.png b/core/assets-raw/sprites/items/item-titanium.png new file mode 100644 index 0000000000..de982e6445 Binary files /dev/null and b/core/assets-raw/sprites/items/item-titanium.png differ diff --git a/core/assets-raw/sprites/items/item-tungsten.png b/core/assets-raw/sprites/items/item-tungsten.png new file mode 100644 index 0000000000..f6af84f404 Binary files /dev/null and b/core/assets-raw/sprites/items/item-tungsten.png differ diff --git a/core/assets-raw/sprites/items/liquid-icon.png b/core/assets-raw/sprites/items/liquid-icon.png new file mode 100644 index 0000000000..642517ce48 Binary files /dev/null and b/core/assets-raw/sprites/items/liquid-icon.png differ diff --git a/core/assets-raw/sprites/mechs/mech-standard-icon.png b/core/assets-raw/sprites/mechs/mech-standard-icon.png deleted file mode 100644 index 4546324c10..0000000000 Binary files a/core/assets-raw/sprites/mechs/mech-standard-icon.png and /dev/null differ diff --git a/core/assets-raw/sprites/mechs/mech-standard.png b/core/assets-raw/sprites/mechs/mech-standard.png deleted file mode 100644 index 1b343a9775..0000000000 Binary files a/core/assets-raw/sprites/mechs/mech-standard.png and /dev/null differ diff --git a/core/assets-raw/sprites/mechs/mechs/alpha-mech-base.png b/core/assets-raw/sprites/mechs/mechs/alpha-mech-base.png new file mode 100644 index 0000000000..50cd656428 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/alpha-mech-base.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/alpha-mech-leg.png b/core/assets-raw/sprites/mechs/mechs/alpha-mech-leg.png new file mode 100644 index 0000000000..7204dda0a7 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/alpha-mech-leg.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/alpha-mech.png b/core/assets-raw/sprites/mechs/mechs/alpha-mech.png new file mode 100644 index 0000000000..8d895faf1e Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/alpha-mech.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/delta-mech-base.png b/core/assets-raw/sprites/mechs/mechs/delta-mech-base.png new file mode 100644 index 0000000000..50cd656428 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/delta-mech-base.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/delta-mech-leg.png b/core/assets-raw/sprites/mechs/mechs/delta-mech-leg.png new file mode 100644 index 0000000000..7181a138d6 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/delta-mech-leg.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/delta-mech.png b/core/assets-raw/sprites/mechs/mechs/delta-mech.png new file mode 100644 index 0000000000..20ddec3a99 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/delta-mech.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/omega-mech-base.png b/core/assets-raw/sprites/mechs/mechs/omega-mech-base.png new file mode 100644 index 0000000000..50cd656428 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/omega-mech-base.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/omega-mech-leg.png b/core/assets-raw/sprites/mechs/mechs/omega-mech-leg.png new file mode 100644 index 0000000000..7204dda0a7 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/omega-mech-leg.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/omega-mech.png b/core/assets-raw/sprites/mechs/mechs/omega-mech.png new file mode 100644 index 0000000000..c982a0893b Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/omega-mech.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/tau-mech-base.png b/core/assets-raw/sprites/mechs/mechs/tau-mech-base.png new file mode 100644 index 0000000000..50cd656428 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/tau-mech-base.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/tau-mech-leg.png b/core/assets-raw/sprites/mechs/mechs/tau-mech-leg.png new file mode 100644 index 0000000000..7204dda0a7 Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/tau-mech-leg.png differ diff --git a/core/assets-raw/sprites/mechs/mechs/tau-mech.png b/core/assets-raw/sprites/mechs/mechs/tau-mech.png new file mode 100644 index 0000000000..c982a0893b Binary files /dev/null and b/core/assets-raw/sprites/mechs/mechs/tau-mech.png differ diff --git a/core/assets-raw/sprites/mechs/ships/dart-ship.png b/core/assets-raw/sprites/mechs/ships/dart-ship.png new file mode 100644 index 0000000000..6e4993776a Binary files /dev/null and b/core/assets-raw/sprites/mechs/ships/dart-ship.png differ diff --git a/core/assets-raw/sprites/mechs/ship-standard.png b/core/assets-raw/sprites/mechs/ships/halberd-ship.png similarity index 100% rename from core/assets-raw/sprites/mechs/ship-standard.png rename to core/assets-raw/sprites/mechs/ships/halberd-ship.png diff --git a/core/assets-raw/sprites/mechs/ships/javelin-ship.png b/core/assets-raw/sprites/mechs/ships/javelin-ship.png new file mode 100644 index 0000000000..2f0f7a7e11 Binary files /dev/null and b/core/assets-raw/sprites/mechs/ships/javelin-ship.png differ diff --git a/core/assets-raw/sprites/mechs/ships/trident-ship.png b/core/assets-raw/sprites/mechs/ships/trident-ship.png new file mode 100644 index 0000000000..d0d0dd52ec Binary files /dev/null and b/core/assets-raw/sprites/mechs/ships/trident-ship.png differ diff --git a/core/assets-raw/sprites/circle2.png b/core/assets-raw/sprites/shapes/circle.png similarity index 100% rename from core/assets-raw/sprites/circle2.png rename to core/assets-raw/sprites/shapes/circle.png diff --git a/core/assets-raw/sprites/shapes/shape-3.png b/core/assets-raw/sprites/shapes/shape-3.png new file mode 100644 index 0000000000..db75ca9349 Binary files /dev/null and b/core/assets-raw/sprites/shapes/shape-3.png differ diff --git a/core/assets-raw/sprites/shapes/shape-4.png b/core/assets-raw/sprites/shapes/shape-4.png new file mode 100644 index 0000000000..427aa33835 Binary files /dev/null and b/core/assets-raw/sprites/shapes/shape-4.png differ diff --git a/core/assets-raw/sprites/shapes/shape-5.png b/core/assets-raw/sprites/shapes/shape-5.png new file mode 100644 index 0000000000..34873c6ccb Binary files /dev/null and b/core/assets-raw/sprites/shapes/shape-5.png differ diff --git a/core/assets-raw/sprites/shapes/shape-6.png b/core/assets-raw/sprites/shapes/shape-6.png new file mode 100644 index 0000000000..70a6bf4c9a Binary files /dev/null and b/core/assets-raw/sprites/shapes/shape-6.png differ diff --git a/core/assets-raw/sprites/shapes/shape-7.png b/core/assets-raw/sprites/shapes/shape-7.png new file mode 100644 index 0000000000..b1d4298b34 Binary files /dev/null and b/core/assets-raw/sprites/shapes/shape-7.png differ diff --git a/core/assets-raw/sprites/shell.png b/core/assets-raw/sprites/shell.png deleted file mode 100644 index 747f735e65..0000000000 Binary files a/core/assets-raw/sprites/shell.png and /dev/null differ diff --git a/core/assets-raw/sprites/shot-long.png b/core/assets-raw/sprites/shot-long.png deleted file mode 100644 index 6d9ebaab45..0000000000 Binary files a/core/assets-raw/sprites/shot-long.png and /dev/null differ diff --git a/core/assets-raw/sprites/titanshell.png b/core/assets-raw/sprites/titanshell.png deleted file mode 100644 index 7f90039999..0000000000 Binary files a/core/assets-raw/sprites/titanshell.png and /dev/null differ diff --git a/core/assets-raw/sprites/ui/icons/icon-arrow-16.png b/core/assets-raw/sprites/ui/icons/icon-arrow-16.png new file mode 100644 index 0000000000..227ea56fdb Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-arrow-16.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-arrow.png b/core/assets-raw/sprites/ui/icons/icon-arrow.png index d8f82bf42e..227ea56fdb 100644 Binary files a/core/assets-raw/sprites/ui/icons/icon-arrow.png and b/core/assets-raw/sprites/ui/icons/icon-arrow.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-break.png b/core/assets-raw/sprites/ui/icons/icon-break.png new file mode 100644 index 0000000000..8f20326533 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-break.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-cancel.png b/core/assets-raw/sprites/ui/icons/icon-cancel.png index bf4c7ed9a0..2e9914525b 100644 Binary files a/core/assets-raw/sprites/ui/icons/icon-cancel.png and b/core/assets-raw/sprites/ui/icons/icon-cancel.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-check.png b/core/assets-raw/sprites/ui/icons/icon-check.png index 67bd7b09cd..eb282e25ee 100644 Binary files a/core/assets-raw/sprites/ui/icons/icon-check.png and b/core/assets-raw/sprites/ui/icons/icon-check.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-close-down.png b/core/assets-raw/sprites/ui/icons/icon-close-down.png deleted file mode 100644 index 204c2eb911..0000000000 Binary files a/core/assets-raw/sprites/ui/icons/icon-close-down.png and /dev/null differ diff --git a/core/assets-raw/sprites/ui/icons/icon-close-over.png b/core/assets-raw/sprites/ui/icons/icon-close-over.png deleted file mode 100644 index e526739fb6..0000000000 Binary files a/core/assets-raw/sprites/ui/icons/icon-close-over.png and /dev/null differ diff --git a/core/assets-raw/sprites/ui/icons/icon-close.png b/core/assets-raw/sprites/ui/icons/icon-close.png deleted file mode 100644 index 02b26e0a25..0000000000 Binary files a/core/assets-raw/sprites/ui/icons/icon-close.png and /dev/null differ diff --git a/core/assets-raw/sprites/ui/icons/icon-egg.png b/core/assets-raw/sprites/ui/icons/icon-egg.png new file mode 100644 index 0000000000..9f00eb61cd Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-egg.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-elevation.png b/core/assets-raw/sprites/ui/icons/icon-elevation.png new file mode 100644 index 0000000000..b484f8093d Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-elevation.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-eraser.png b/core/assets-raw/sprites/ui/icons/icon-eraser.png new file mode 100644 index 0000000000..76b43c4887 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-eraser.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-file-image.png b/core/assets-raw/sprites/ui/icons/icon-file-image.png new file mode 100644 index 0000000000..a16c5170a7 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-file-image.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-file.png b/core/assets-raw/sprites/ui/icons/icon-file.png new file mode 100644 index 0000000000..f91d5a46a7 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-file.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-floppy-16.png b/core/assets-raw/sprites/ui/icons/icon-floppy-16.png new file mode 100644 index 0000000000..7993fad275 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-floppy-16.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-generated.png b/core/assets-raw/sprites/ui/icons/icon-generated.png new file mode 100644 index 0000000000..15a0d6051e Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-generated.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-item.png b/core/assets-raw/sprites/ui/icons/icon-item.png new file mode 100644 index 0000000000..0e99951b81 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-item.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-items-none.png b/core/assets-raw/sprites/ui/icons/icon-items-none.png new file mode 100644 index 0000000000..3bfe694cda Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-items-none.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-liquid.png b/core/assets-raw/sprites/ui/icons/icon-liquid.png new file mode 100644 index 0000000000..8511bad640 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-liquid.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-locked.png b/core/assets-raw/sprites/ui/icons/icon-locked.png new file mode 100644 index 0000000000..d87ab09c83 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-locked.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-logic.png b/core/assets-raw/sprites/ui/icons/icon-logic.png new file mode 100644 index 0000000000..8e299e06d7 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-logic.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-map.png b/core/assets-raw/sprites/ui/icons/icon-map.png new file mode 100644 index 0000000000..a3aa72fe12 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-map.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-menu-large.png b/core/assets-raw/sprites/ui/icons/icon-menu-large.png new file mode 100644 index 0000000000..e21d31376c Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-menu-large.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-missing.png b/core/assets-raw/sprites/ui/icons/icon-missing.png new file mode 100644 index 0000000000..fcae107678 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-missing.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-units.png b/core/assets-raw/sprites/ui/icons/icon-units.png new file mode 100644 index 0000000000..d528133039 Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-units.png differ diff --git a/core/assets-raw/sprites/ui/icons/icon-unlocks.png b/core/assets-raw/sprites/ui/icons/icon-unlocks.png new file mode 100644 index 0000000000..eb9a143a9f Binary files /dev/null and b/core/assets-raw/sprites/ui/icons/icon-unlocks.png differ diff --git a/core/assets-raw/sprites/ui/info-banner.png b/core/assets-raw/sprites/ui/info-banner.png new file mode 100644 index 0000000000..4cb1409caa Binary files /dev/null and b/core/assets-raw/sprites/ui/info-banner.png differ diff --git a/core/assets-raw/sprites/ui/inventory.9.png b/core/assets-raw/sprites/ui/inventory.9.png new file mode 100644 index 0000000000..f8fffa50e1 Binary files /dev/null and b/core/assets-raw/sprites/ui/inventory.9.png differ diff --git a/core/assets-raw/sprites/ui/textarea.9.png b/core/assets-raw/sprites/ui/textarea.9.png new file mode 100644 index 0000000000..e8b1e5df84 Binary files /dev/null and b/core/assets-raw/sprites/ui/textarea.9.png differ diff --git a/core/assets-raw/sprites/units/drone.png b/core/assets-raw/sprites/units/drone.png new file mode 100644 index 0000000000..18367666ce Binary files /dev/null and b/core/assets-raw/sprites/units/drone.png differ diff --git a/core/assets-raw/sprites/units/fabricator.png b/core/assets-raw/sprites/units/fabricator.png new file mode 100644 index 0000000000..10d9e47b98 Binary files /dev/null and b/core/assets-raw/sprites/units/fabricator.png differ diff --git a/core/assets-raw/sprites/units/monsoon.png b/core/assets-raw/sprites/units/monsoon.png new file mode 100644 index 0000000000..2c89a57c48 Binary files /dev/null and b/core/assets-raw/sprites/units/monsoon.png differ diff --git a/core/assets-raw/sprites/units/scout-base.png b/core/assets-raw/sprites/units/scout-base.png new file mode 100644 index 0000000000..50cd656428 Binary files /dev/null and b/core/assets-raw/sprites/units/scout-base.png differ diff --git a/core/assets-raw/sprites/units/scout-leg.png b/core/assets-raw/sprites/units/scout-leg.png new file mode 100644 index 0000000000..7204dda0a7 Binary files /dev/null and b/core/assets-raw/sprites/units/scout-leg.png differ diff --git a/core/assets-raw/sprites/units/scout.png b/core/assets-raw/sprites/units/scout.png new file mode 100644 index 0000000000..25474f4e5a Binary files /dev/null and b/core/assets-raw/sprites/units/scout.png differ diff --git a/core/assets-raw/sprites/units/titan-base.png b/core/assets-raw/sprites/units/titan-base.png new file mode 100644 index 0000000000..fb5830f3ff Binary files /dev/null and b/core/assets-raw/sprites/units/titan-base.png differ diff --git a/core/assets-raw/sprites/units/titan-leg.png b/core/assets-raw/sprites/units/titan-leg.png new file mode 100644 index 0000000000..eb21e7da28 Binary files /dev/null and b/core/assets-raw/sprites/units/titan-leg.png differ diff --git a/core/assets-raw/sprites/units/titan.png b/core/assets-raw/sprites/units/titan.png new file mode 100644 index 0000000000..63dcb6f8e3 Binary files /dev/null and b/core/assets-raw/sprites/units/titan.png differ diff --git a/core/assets-raw/sprites/units/vtol-flame.png b/core/assets-raw/sprites/units/vtol-flame.png new file mode 100644 index 0000000000..b4fdd7a0c2 Binary files /dev/null and b/core/assets-raw/sprites/units/vtol-flame.png differ diff --git a/core/assets-raw/sprites/units/vtol.png b/core/assets-raw/sprites/units/vtol.png new file mode 100644 index 0000000000..d33f701bb9 Binary files /dev/null and b/core/assets-raw/sprites/units/vtol.png differ diff --git a/core/assets-raw/sprites/weapons/beam-equip.png b/core/assets-raw/sprites/weapons/beam-equip.png deleted file mode 100644 index 4509ed3ade..0000000000 Binary files a/core/assets-raw/sprites/weapons/beam-equip.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/beam.png b/core/assets-raw/sprites/weapons/beam.png deleted file mode 100644 index e7bf82c60f..0000000000 Binary files a/core/assets-raw/sprites/weapons/beam.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/blaster-equip.png b/core/assets-raw/sprites/weapons/blaster-equip.png index 6a90610d9f..b5207231b5 100644 Binary files a/core/assets-raw/sprites/weapons/blaster-equip.png and b/core/assets-raw/sprites/weapons/blaster-equip.png differ diff --git a/core/assets-raw/sprites/weapons/blaster.png b/core/assets-raw/sprites/weapons/blaster.png deleted file mode 100644 index b677a58bcd..0000000000 Binary files a/core/assets-raw/sprites/weapons/blaster.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/chain-blaster-equip.png b/core/assets-raw/sprites/weapons/chain-blaster-equip.png new file mode 100644 index 0000000000..37a3cb4e5e Binary files /dev/null and b/core/assets-raw/sprites/weapons/chain-blaster-equip.png differ diff --git a/core/assets-raw/sprites/weapons/clustergun-equip.png b/core/assets-raw/sprites/weapons/clustergun-equip.png deleted file mode 100644 index 8f5c6db8a2..0000000000 Binary files a/core/assets-raw/sprites/weapons/clustergun-equip.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/clustergun.png b/core/assets-raw/sprites/weapons/clustergun.png deleted file mode 100644 index 1f76fcf8be..0000000000 Binary files a/core/assets-raw/sprites/weapons/clustergun.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/flakgun-equip.png b/core/assets-raw/sprites/weapons/flakgun-equip.png new file mode 100644 index 0000000000..02ab411a4c Binary files /dev/null and b/core/assets-raw/sprites/weapons/flakgun-equip.png differ diff --git a/core/assets-raw/sprites/weapons/flamethrower-equip.png b/core/assets-raw/sprites/weapons/flamethrower-equip.png new file mode 100644 index 0000000000..2ff0908ef5 Binary files /dev/null and b/core/assets-raw/sprites/weapons/flamethrower-equip.png differ diff --git a/core/assets-raw/sprites/weapons/missiles-equip.png b/core/assets-raw/sprites/weapons/missiles-equip.png new file mode 100644 index 0000000000..67603de505 Binary files /dev/null and b/core/assets-raw/sprites/weapons/missiles-equip.png differ diff --git a/core/assets-raw/sprites/weapons/shockgun-equip.png b/core/assets-raw/sprites/weapons/shockgun-equip.png index 96a33a55a5..26e4939de8 100644 Binary files a/core/assets-raw/sprites/weapons/shockgun-equip.png and b/core/assets-raw/sprites/weapons/shockgun-equip.png differ diff --git a/core/assets-raw/sprites/weapons/shockgun.png b/core/assets-raw/sprites/weapons/shockgun.png deleted file mode 100644 index 9ed4e1521b..0000000000 Binary files a/core/assets-raw/sprites/weapons/shockgun.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/triblaster-equip.png b/core/assets-raw/sprites/weapons/triblaster-equip.png deleted file mode 100644 index 231869b8e4..0000000000 Binary files a/core/assets-raw/sprites/weapons/triblaster-equip.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/triblaster.png b/core/assets-raw/sprites/weapons/triblaster.png deleted file mode 100644 index 1b6e1db871..0000000000 Binary files a/core/assets-raw/sprites/weapons/triblaster.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/vulcan-equip.png b/core/assets-raw/sprites/weapons/vulcan-equip.png deleted file mode 100644 index 1c9ddf9401..0000000000 Binary files a/core/assets-raw/sprites/weapons/vulcan-equip.png and /dev/null differ diff --git a/core/assets-raw/sprites/weapons/vulcan.png b/core/assets-raw/sprites/weapons/vulcan.png deleted file mode 100644 index a4632649f9..0000000000 Binary files a/core/assets-raw/sprites/weapons/vulcan.png and /dev/null differ diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index fd1153ce6f..3e9976024d 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1,7 +1,6 @@ -text.about=Created by [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginally an entry in the [orange]GDL[] Metal Monstrosity Jam.\n\nCredits:\n- SFX made with [YELLOW]bfxr[]\n- Music made by [GREEN]a beat a day[]\n\nSpecial thanks to:\n- [coral]MitchellFJN[]: extensive playtesting and feedback\n- [sky]Luxray5474[]: wiki work, code contributions\n- [lime]Epowerj[]: code build system, icon\n- All the beta testers on itch.io and Google Play\n +text.about=Created by [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[] text.credits=Credits text.discord=Join the mindustry discord! -text.changes=[SCARLET]Attention!\n[]Some important game mechanics have been changed.\n\n- [accent]Teleporters[] now use power.\n- [accent]Smelteries[] and [accent]crucibles[] now have a maximum item capacity.\n- [accent]Crucibles[] now require coal as fuel. text.link.discord.description=the official Mindustry discord chatroom text.link.github.description=Game source code text.link.dev-builds.description=Unstable development builds @@ -11,25 +10,52 @@ text.link.google-play.description=Google Play store listing text.link.wiki.description=official Mindustry wiki text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. text.gameover=The core was destroyed. text.highscore=[YELLOW]New highscore! text.lasted=You lasted until wave text.level.highscore=High Score: [accent]{0} text.level.delete.title=Confirm Delete -text.level.delete=Are you sure you want to delete\nthe map "[orange]{0}"? +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? text.level.select=Level Select text.level.mode=Gamemode: +text.construction.title=Block Construction Guide +text.construction=\ +You've just selected [accent]block construction mode[].\n\n\ +To begin placing, simply tap a valid location near your ship.\n\ +Once you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\ +\n\ +- [accent]Remove blocks[] from your selection by tapping them.\n\ +- [accent]Shift the selection[] by holding and dragging any block in the selection.\n\ +- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n\ +- [accent]Cancel construction or selection[] by pressing the X at the bottom left. + +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=\ +You've just selected [accent]block deconstruction mode[].\n\n\ +To begin breaking, simply tap a block near your ship.\n\ +Once you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\ +\n\ +- [accent]Remove blocks[] from your selection by tapping them.\n\ +- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n\ +- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks text.savegame=Save Game text.loadgame=Load Game text.joingame=Join Game +text.addplayers=Add/Remove Players text.newgame=New Game text.quit=Quit +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! text.about.button=About text.name=Name: -text.public=Public +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! text.players={0} players online -text.server.player.host={0} (host) text.players.single={0} player online text.server.mismatch=Packet error: possible client/server version mismatch.\nMake sure you and the host have the\nlatest version of Mindustry! text.server.closing=[accent]Closing server... @@ -40,6 +66,10 @@ text.server.kicked.clientOutdated=Outdated client! Update your game! text.server.kicked.serverOutdated=Outdated server! Ask the host to update! text.server.kicked.banned=You are banned on this server. text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. text.server.connected={0} has joined. text.server.disconnected={0} has disconnected. text.nohost=Can't host server on a custom map! @@ -70,8 +100,6 @@ text.server.bans=Bans text.server.bans.none=No banned players found! text.server.admins=Admins text.server.admins.none=No admins found! -text.server.rollback=Rollback -text.server.rollback.numberfield=Rollback Amount: text.server.add=Add Server text.server.delete=Are you sure you want to delete this server? text.server.hostname=Host: {0} @@ -84,7 +112,6 @@ text.confirmban=Are you sure you want to ban this player? text.confirmunban=Are you sure you want to unban this player? text.confirmadmin=Are you sure you want to make this player an admin? text.confirmunadmin=Are you sure you want to remove admin status from this player? -text.joingame.byip=Join by IP... text.joingame.title=Join Game text.joingame.ip=IP: text.disconnect=Disconnected. @@ -96,8 +123,7 @@ text.server.port=Port: text.server.addressinuse=Address already in use! text.server.invalidport=Invalid port number! text.server.error=[crimson]Error hosting server: [orange]{0} -text.tutorial.back=< Prev -text.tutorial.next=Next > +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. text.save.new=New Save text.save.overwrite=Are you sure you want to overwrite\nthis save slot? text.overwrite=Overwrite @@ -107,7 +133,7 @@ text.savefail=Failed to save game! text.save.delete.confirm=Are you sure you want to delete this save? text.save.delete=Delete text.save.export=Export Save -text.save.import.invalid=[orange]This save is invalid!\n\nNote that[scarlet]importing saves with custom maps[orange]\nfrom other devices does not work! +text.save.import.invalid=[orange]This save is invalid! text.save.import.fail=[crimson]Failed to import save: [orange]{0} text.save.export.fail=[crimson]Failed to export save: [orange]{0} text.save.import=Import Save @@ -116,7 +142,7 @@ text.save.rename=Rename text.save.rename.text=New name: text.selectslot=Select a save. text.slot=[accent]Slot {0} -text.save.corrupted=[orange]Save file corrupted or invalid! +text.save.corrupted=[orange]Save file corrupted or invalid!\nIf you have just updated your game, this is probably a change in the save format and [scarlet]not[] a bug. text.empty= text.on=On text.off=Off @@ -142,6 +168,7 @@ text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet conn text.changelog.current=[yellow][[Current version] text.changelog.latest=[orange][[Latest version] text.loading=[accent]Loading... +text.saving=[accent]Saving... text.wave=[orange]Wave {0} text.wave.waiting=Wave in {0} text.waiting=Waiting... @@ -149,36 +176,62 @@ text.enemies={0} Enemies text.enemies.single={0} Enemy text.loadimage=Load Image text.saveimage=Save Image -text.oregen=Ore Generation +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation text.editor.badsize=[orange]Invalid image dimensions![]\nValid map dimensions: {0} -text.editor.errorimageload=Error loading image file:\n[orange]{0} -text.editor.errorimagesave=Error saving image file:\n[orange]{0} +text.editor.errorimageload=Error loading file:\n[orange]{0} +text.editor.errorimagesave=Error saving file:\n[orange]{0} text.editor.generate=Generate text.editor.resize=Resize text.editor.loadmap=Load Map text.editor.savemap=Save Map -text.editor.loadimage=Load Image -text.editor.saveimage=Save Image +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.loadimage=Import Terrain +text.editor.saveimage=Export Terrain text.editor.unsaved=[scarlet]You have unsaved changes![]\nAre you sure you want to exit? -text.editor.brushsize=Brush size: {0} -text.editor.noplayerspawn=This map has no player spawnpoint! -text.editor.manyplayerspawns=Maps cannot have more than one\nplayer spawnpoint! -text.editor.manyenemyspawns=Cannot have more than\n{0} enemy spawnpoints! text.editor.resizemap=Resize Map -text.editor.resizebig=[scarlet]Warning!\n[]Maps larger than 256 units may be laggy and unstable. text.editor.mapname=Map Name: text.editor.overwrite=[accent]Warning!\nThis overwrites an existing map. -text.editor.failoverwrite=[crimson]Cannot overwrite default map! +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? text.editor.selectmap=Select a map to load: text.width=Width: text.height=Height: -text.randomize=Randomize -text.apply=Apply -text.update=Update text.menu=Menu text.play=Play text.load=Load text.save=Save +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms text.language.restart=Please restart your game for the language settings to take effect. text.settings.language=Language text.settings=Settings @@ -187,6 +240,7 @@ text.editor=Editor text.mapeditor=Map Editor text.donate=Donate text.settings.reset=Reset to Defaults +text.settings.rebind=Rebind text.settings.controls=Controls text.settings.game=Game text.settings.sound=Sound @@ -195,69 +249,69 @@ text.upgrades=Upgrades text.purchased=[LIME]Created! text.weapons=Weapons text.paused=Paused -text.respawn=Respawning in +text.yes=Yes +text.no=No text.info.title=[accent]Info text.error.title=[crimson]An error has occured -text.error.crashmessage=[SCARLET]An unexpected error has occured, which would have caused a crash.\n[]Please report the exact circumstances under which this error occured to the developer: \n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=An error has occured -text.mode.break=Break mode: {0} -text.mode.place=Place mode: {0} -placemode.hold.name=line -placemode.areadelete.name=area -placemode.touchdelete.name=touch -placemode.holddelete.name=hold -placemode.none.name=none -placemode.touch.name=touch -placemode.cursor.name=cursor -text.blocks.extrainfo=[accent]extra block info: text.blocks.blockinfo=Block Info -text.blocks.editlogs=Edit Logs -text.block.editlogsnotfound=[red]There are no edit logs for this location. text.blocks.powercapacity=Power Capacity -text.blocks.powershot=Power/shot -text.blocks.powersecond=Power/second -text.blocks.powerdraindamage=Power Drain/damage -text.blocks.shieldradius=Shield Radius -text.blocks.itemspeedsecond=Item Speed/second -text.blocks.range=Range +text.blocks.powershot=Power/Shot +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range text.blocks.size=Size -text.blocks.powerliquid=Power/Liquid -text.blocks.maxliquidsecond=Max liquid/second -text.blocks.liquidcapacity=Liquid capacity -text.blocks.liquidsecond=Liquid/second -text.blocks.damageshot=Damage/shot -text.blocks.ammocapacity=Ammo Capacity -text.blocks.ammo=Ammo -text.blocks.ammoitem=Ammo/item -text.blocks.maxitemssecond=Max items/second -text.blocks.powerrange=Power range -text.blocks.lasertilerange=Laser tile range -text.blocks.capacity=Capacity +text.blocks.liquidcapacity=Liquid Capacity +text.blocks.maxitemssecond=Max Items +text.blocks.powerrange=Power Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity text.blocks.itemcapacity=Item Capacity -text.blocks.maxpowergenerationsecond=Max Power Generation/second -text.blocks.powergenerationsecond=Power Generation/second -text.blocks.generationsecondsitem=Generation Seconds/item -text.blocks.input=Input +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed text.blocks.inputliquid=Input Liquid +text.blocks.inputliquidaux=Aux Liquid text.blocks.inputitem=Input Item -text.blocks.output=Output -text.blocks.secondsitem=Seconds/item -text.blocks.maxpowertransfersecond=Max power transfer/second +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use text.blocks.explosive=Highly explosive! -text.blocks.repairssecond=Repaired/second text.blocks.health=Health text.blocks.inaccuracy=Inaccuracy text.blocks.shots=Shots -text.blocks.shotssecond=Shots/second -text.blocks.fuel=Fuel -text.blocks.fuelduration=Fuel Duration -text.blocks.maxoutputsecond=Max output/second +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time text.blocks.inputcapacity=Input capacity text.blocks.outputcapacity=Output capacity -text.blocks.poweritem=Power/Item -text.placemode=Place Mode -text.breakmode=Break Mode -text.health=health + +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items + +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting setting.difficulty.easy=easy setting.difficulty.normal=normal setting.difficulty.hard=hard @@ -265,7 +319,6 @@ setting.difficulty.insane=insane setting.difficulty.purge=purge setting.difficulty.name=Difficulty: setting.screenshake.name=Screen Shake -setting.smoothcam.name=Smooth Camera setting.indicators.name=Enemy Indicators setting.effects.name=Display Effects setting.sensitivity.name=Controller Sensitivity @@ -276,9 +329,8 @@ setting.multithread.name=Multithreading setting.fps.name=Show FPS setting.vsync.name=VSync setting.lasers.name=Show Power Lasers -setting.previewopacity.name=Placing Preview Opacity setting.healthbars.name=Show Entity Health bars -setting.pixelate.name=Pixelate Screen +setting.minimap.name=Show Minimap setting.musicvol.name=Music Volume setting.mutemusic.name=Mute Music setting.sfxvol.name=SFX Volume @@ -296,50 +348,6 @@ map.grassland.name=grassland map.tundra.name=tundra map.spiral.name=spiral map.tutorial.name=tutorial -tutorial.intro.text=[yellow]Welcome to the tutorial.[] To begin, press 'next'. -tutorial.moveDesktop.text=To move, use the [orange][[WASD][] keys. Hold [orange]shift[] to boost. Hold [orange]CTRL[] while using the [orange]scrollwheel[] to zoom in or out. -tutorial.shoot.text=Use your mouse to aim, hold [orange]left mouse button[] to shoot. Try practicing on the [yellow]target[]. -tutorial.moveAndroid.text=To pan the view, drag one finger across the screen. Pinch and drag to zoom in or out. -tutorial.placeSelect.text=Try selecting a [yellow]conveyor[] from the block menu in the bottom right. -tutorial.placeConveyorDesktop.text=Use the [orange][[scrollwheel][] to rotate the conveyor to face [orange]forwards[], then place it in the [yellow]marked location[] using the [orange][[left mouse button][]. -tutorial.placeConveyorAndroid.text=Use the [orange][[rotate button][] to rotate the conveyor to face [orange]forwards[], drag it into position with one finger, then place it in the [yellow]marked location[] using the [orange][[checkmark][]. -tutorial.placeConveyorAndroidInfo.text=Alternatively, you can press the crosshair icon in the bottom left to switch to [orange][[touch mode][], and place blocks by tapping on the screen. In touch mode, blocks can be rotated with the arrow at the bottom left. Press [yellow]next[] to try it out. -tutorial.placeDrill.text=Now, select and place a [yellow]stone drill[] at the marked location. -tutorial.blockInfo.text=If you want to learn more about a block, you can tap the [orange]question mark[] in the top right to read its description. -tutorial.deselectDesktop.text=You can de-select a block using the [orange][[right mouse button][]. -tutorial.deselectAndroid.text=You can deselect a block by pressing the [orange]X[] button. -tutorial.drillPlaced.text=The drill will now produce [yellow]stone,[] output it onto the conveyor, then move it into the [yellow]core[]. -tutorial.drillInfo.text=Different ores need different drills. Stone requires stone drills, iron requires iron drills, etc. -tutorial.drillPlaced2.text=Moving items into the core puts them in your [yellow]item inventory[], in the top left. Placing blocks uses items from your inventory. -tutorial.moreDrills.text=You can link many drills and conveyors up together, like so. -tutorial.deleteBlock.text=You can delete blocks by clicking the [orange]right mouse button[] on the block you want to delete. Try deleting this conveyor. -tutorial.deleteBlockAndroid.text=You can delete blocks by [orange]selecting the crosshair[] in the [orange]break mode menu[] in the bottom left and tapping a block. Try deleting this conveyor. -tutorial.placeTurret.text=Now, select and place a [yellow]turret[] at the [yellow]marked location[]. -tutorial.placedTurretAmmo.text=This turret will now accept [yellow]ammo[] from the conveyor. You can see how much ammo it has by hovering over it and checking the [green]green bar[]. -tutorial.turretExplanation.text=Turrets will automatically shoot at the nearest enemy in range, as long as they have enough ammo. -tutorial.waves.text=Every [yellow]60[] seconds, a wave of [coral]enemies[] will spawn in specific locations and attempt to destroy the core. -tutorial.coreDestruction.text=Your objective is to [yellow]defend the core[]. If the core is destroyed, you [coral]lose the game[]. -tutorial.pausingDesktop.text=If you ever need to take a break, press the [orange]pause button[] in the top left or [orange]space[] to pause the game. You can still select and place blocks while paused, but cannot move or shoot. -tutorial.pausingAndroid.text=If you ever need to take a break, press the [orange]pause button[] in the top left to pause the game. You can still break and place blocks while paused. -tutorial.purchaseWeapons.text=You can purchase new [yellow]weapons[] for your mech by opening the upgrade menu in the bottom left. -tutorial.switchWeapons.text=Switch weapons by either clicking its icon in the bottom left, or using numbers [orange][[1-9][]. -tutorial.spawnWave.text=Here comes a wave now. Destroy them. -tutorial.pumpDesc.text=In later waves, you might need to use [yellow]pumps[] to distribute liquids for generators or extractors. -tutorial.pumpPlace.text=Pumps work similarly to drills, except that they produce liquids instead of items. Try placing a pump on the [yellow]designated oil[]. -tutorial.conduitUse.text=Now place a [orange]conduit[] leading away from the pump. -tutorial.conduitUse2.text=And a few more... -tutorial.conduitUse3.text=And a few more... -tutorial.generator.text=Now, place a [orange]combustion generator[] block at the end of the conduit. -tutorial.generatorExplain.text=This generator will now create [yellow]power[] from the oil. -tutorial.lasers.text=Power is distributed using [yellow]power lasers[]. Rotate and place one here. -tutorial.laserExplain.text=The generator will now move power into the laser block. An [yellow]opaque[] beam means that it is currently transmitting power, and a [yellow]transparent[] beam means it is not. -tutorial.laserMore.text=You can check how much power a block has by hovering over it and checking the [yellow]yellow bar[] at the top. -tutorial.healingTurret.text=This laser can be used to power a [lime]repair turret[]. Place one here. -tutorial.healingTurretExplain.text=As long as it has power, this turret will [lime]repair nearby blocks.[] When playing, make sure you get one in your base as quickly as possible! -tutorial.smeltery.text=Many blocks require [orange]steel[] to make, which requires a [orange]smelter[] to craft. Place one here. -tutorial.smelterySetup.text=This smelter will now produce [orange]steel[] from the input iron, using coal as fuel. -tutorial.tunnelExplain.text=Also note that the items are going through a [orange]tunnel block[] and emerging on the other side, going through the stone block. Keep in mind that tunnels can only go through up to 2 blocks. -tutorial.end.text=And that concludes the tutorial! Good luck! text.keybind.title=Rebind Keys keybind.move_x.name=move_x keybind.move_y.name=move_y @@ -357,12 +365,6 @@ keybind.player_list.name=player_list keybind.console.name=console keybind.rotate_alt.name=rotate_alt keybind.rotate.name=rotate -keybind.weapon_1.name=weapon_1 -keybind.weapon_2.name=weapon_2 -keybind.weapon_3.name=weapon_3 -keybind.weapon_4.name=weapon_4 -keybind.weapon_5.name=weapon_5 -keybind.weapon_6.name=weapon_6 mode.text.help.title=Description of modes mode.waves.name=waves mode.waves.description=the normal mode. limited resources and automatic incoming waves. @@ -370,189 +372,148 @@ mode.sandbox.name=sandbox mode.sandbox.description=infinite resources and no timer for waves. mode.freebuild.name=freebuild mode.freebuild.description=limited resources and no timer for waves. -upgrade.standard.name=standard -upgrade.standard.description=The standard mech. -upgrade.blaster.name=blaster -upgrade.blaster.description=Shoots a slow, weak bullet. -upgrade.triblaster.name=triblaster -upgrade.triblaster.description=Shoots 3 bullets in a spread. -upgrade.clustergun.name=clustergun -upgrade.clustergun.description=Shoots an inaccurate spread of explosive grenades. -upgrade.beam.name=beam cannon -upgrade.beam.description=Shoots a long-range piercing laser beam. -upgrade.vulcan.name=vulcan -upgrade.vulcan.description=Shoots a barrage of fast bullets. -upgrade.shockgun.name=shockgun -upgrade.shockgun.description=Shoots a devastating blast of charged shrapnel. -item.stone.name=stone -item.iron.name=iron -item.coal.name=coal -item.steel.name=steel -item.titanium.name=titanium -item.dirium.name=dirium -item.uranium.name=uranium -item.sand.name=sand -liquid.water.name=water -liquid.plasma.name=plasma -liquid.lava.name=lava -liquid.oil.name=oil -block.weaponfactory.name=weapon factory -block.weaponfactory.fulldescription=Used to create weapons for the player mech. Click to use. Automatically takes resources from the core. -block.air.name=air -block.blockpart.name=blockpart -block.deepwater.name=deepwater -block.water.name=water -block.lava.name=lava -block.oil.name=oil -block.stone.name=stone -block.blackstone.name=blackstone -block.iron.name=iron -block.coal.name=coal -block.titanium.name=titanium -block.uranium.name=uranium -block.dirt.name=dirt -block.sand.name=sand -block.ice.name=ice -block.snow.name=snow -block.grass.name=grass -block.sandblock.name=sandblock -block.snowblock.name=snowblock -block.stoneblock.name=stoneblock -block.blackstoneblock.name=blackstoneblock -block.grassblock.name=grassblock -block.mossblock.name=mossblock -block.shrub.name=shrub -block.rock.name=rock -block.icerock.name=icerock -block.blackrock.name=blackrock -block.dirtblock.name=dirtblock -block.stonewall.name=stone wall -block.stonewall.fulldescription=A cheap defensive block. Useful for protecting the core and turrets in the first few waves. -block.ironwall.name=iron wall -block.ironwall.fulldescription=A basic defensive block. Provides protection from enemies. -block.steelwall.name=steel wall -block.steelwall.fulldescription=A standard defensive block. adequate protection from enemies. -block.titaniumwall.name=titanium wall -block.titaniumwall.fulldescription=A strong defensive block. Provides protection from enemies. -block.duriumwall.name=dirium wall -block.duriumwall.fulldescription=A very strong defensive block. Provides protection from enemies. -block.compositewall.name=composite wall -block.steelwall-large.name=large steel wall -block.steelwall-large.fulldescription=A standard defensive block. Spans multiple tiles. -block.titaniumwall-large.name=large titanium wall -block.titaniumwall-large.fulldescription=A strong defensive block. Spans multiple tiles. -block.duriumwall-large.name=large dirium wall -block.duriumwall-large.fulldescription=A very strong defensive block. Spans multiple tiles. -block.titaniumshieldwall.name=shielded wall -block.titaniumshieldwall.fulldescription=A strong defensive block, with an extra built-in shield. Requires power. Uses energy to absorb enemy bullets. It is recommended to use power boosters to provide energy to this block. -block.repairturret.name=repair turret -block.repairturret.fulldescription=Repairs nearby damaged blocks in range at a slow rate. Uses small amounts of power. -block.megarepairturret.name=repair turret II -block.megarepairturret.fulldescription=Repairs nearby damaged blocks in range at a decent rate. Uses power. -block.shieldgenerator.name=shield generator -block.shieldgenerator.fulldescription=An advanced defensive block. Shields all the blocks in a radius from attack. Uses power at a slow rate when idle, but drains energy quickly on bullet contact. -block.door.name=door -block.door.fulldescription=A block than can be opened and closed by tapping it. -block.door-large.name=large door -block.door-large.fulldescription=A block than can be opened and closed by tapping it. -block.conduit.name=conduit -block.conduit.fulldescription=Basic liquid transport block. Works like a conveyor, but with liquids. Best used with pumps or other conduits. Can be used as a bridge over liquids for enemies and players. -block.pulseconduit.name=pulse conduit -block.pulseconduit.fulldescription=Advanced liquid transport block. Transports liquids faster and stores more than standard conduits. -block.liquidrouter.name=liquid router -block.liquidrouter.fulldescription=Works similarly to a router. Accepts liquid input from one side and outputs it to the other sides. Useful for splitting liquid from a single conduit into multiple other conduits. -block.conveyor.name=conveyor -block.conveyor.fulldescription=Basic item transport block. Moves items forward and automatically deposits them into turrets or crafters. Rotatable. Can be used as a bridge over liquids for enemies and players. -block.steelconveyor.name=steel conveyor -block.steelconveyor.fulldescription=Advanced item transport block. Moves items faster than standard conveyors. -block.poweredconveyor.name=pulse conveyor -block.poweredconveyor.fulldescription=The ultimate item transport block. Moves items faster than steel conveyors. -block.router.name=router -block.router.fulldescription=Accepts items from one direction and outputs them to 3 other directions. Can also store a certain amount of items.Useful for splitting the materials from one drill into multiple turrets. -block.junction.name=junction -block.junction.fulldescription=Acts as a bridge for two crossing conveyor belts. Useful in situations with two different conveyors carrying different materials to different locations. -block.conveyortunnel.name=conveyor tunnel -block.conveyortunnel.fulldescription=Transports item under blocks. To use, place one tunnel leading into the block to be tunneled under, and one on the other side. Make sure both tunnels face opposite directions, which is towards the blocks they are inputting or outputting to. -block.liquidjunction.name=liquid junction -block.liquidjunction.fulldescription=Acts as a bridge for two crossing conduits. Useful in situations with two different conduits carrying different liquids to different locations. -block.liquiditemjunction.name=liquid-item junction -block.liquiditemjunction.fulldescription=Acts as a bridge for crossing conduits and conveyors. -block.powerbooster.name=power booster -block.powerbooster.fulldescription=Distributes power to all blocks within its radius. -block.powerlaser.name=power laser -block.powerlaser.fulldescription=Creates a laser that transmits power to the block in front of it. Does not generate any power itself. Best used with generators or other lasers. -block.powerlaserrouter.name=laser router -block.powerlaserrouter.fulldescription=Laser that distributes power to three directions at once. Useful in situations where it is required to power multiple blocks from one generator. -block.powerlasercorner.name=laser corner -block.powerlasercorner.fulldescription=Laser that distributes power to two directions at once. Useful in situations where it is required to power multiple blocks from one generator, and a router is imprecise. -block.teleporter.name=teleporter -block.teleporter.fulldescription=Advanced item transport block. Teleporters input items to other teleporters of the same color. Does nothing if no teleporters of the same color exist. If multiple teleporters exist of the same color, a random one is selected. Uses power. Tap to change color. -block.sorter.name=sorter -block.sorter.fulldescription=Sorts item by material type. Material to accept is indicated by the color in the block. All items that match the sort material are outputted forward, everything else is outputted to the left and right. -block.core.name=core -block.pump.name=pump -block.pump.fulldescription=Pumps liquids from a source block- usually water, lava or oil. Outputs liquid into nearby conduits. -block.fluxpump.name=fluxpump -block.fluxpump.fulldescription=An advanced version of the pump. Stores more liquid and pumps liquid faster. -block.smelter.name=smelter -block.smelter.fulldescription=The essential crafting block. When inputted 1 iron and 1 coal as fuel, outputs one steel. It is advised to input iron and coal on different belts to prevent clogging. -block.crucible.name=crucible -block.crucible.fulldescription=An advanced crafting block. When inputted 1 titanium, 1 steel and 1 coal as fuel, outputs one dirium. It is advised to input coal, steel and titanium on different belts to prevent clogging. -block.coalpurifier.name=coal extractor -block.coalpurifier.fulldescription=A basic extractor block. Outputs coal when supplied with large amounts of water and stone. -block.titaniumpurifier.name=titanium extractor -block.titaniumpurifier.fulldescription=A standard extractor block. Outputs titanium when supplied with large amounts of water and iron. -block.oilrefinery.name=oil refinery -block.oilrefinery.fulldescription=Refines large amounts of oil into coal items. Useful for fueling coal-based turrets when coal veins are scarce. -block.stoneformer.name=stone former -block.stoneformer.fulldescription=Soldifies liquid lava into stone. Useful for producing massive amounts of stone for coal purifiers. -block.lavasmelter.name=lava smelter -block.lavasmelter.fulldescription=Uses lava to convert iron to steel. An alternative to smelteries. Useful in situations where coal is scarce. -block.stonedrill.name=stone drill -block.stonedrill.fulldescription=The essential drill. When placed on stone tiles, outputs stone at a slow pace indefinitely. -block.irondrill.name=iron drill -block.irondrill.fulldescription=A basic drill. When placed on iron ore tiles, outputs iron at a slow pace indefinitely. -block.coaldrill.name=coal drill -block.coaldrill.fulldescription=A basic drill. When placed on coal ore tiles, outputs coal at a slow pace indefinitely. -block.uraniumdrill.name=uranium drill -block.uraniumdrill.fulldescription=An advanced drill. When placed on uranium ore tiles, outputs uranium at a slow pace indefinitely. -block.titaniumdrill.name=titanium drill -block.titaniumdrill.fulldescription=An advanced drill. When placed on titanium ore tiles, outputs titanium at a slow pace indefinitely. -block.omnidrill.name=omnidrill -block.omnidrill.fulldescription=The ultimate drill. Will mine any ore it is placed on at a rapid pace. -block.coalgenerator.name=coal generator -block.coalgenerator.fulldescription=The essential generator. Generates power from coal. Outputs power as lasers to its 4 sides. -block.thermalgenerator.name=thermal generator -block.thermalgenerator.fulldescription=Generates power from lava. Outputs power as lasers to its 4 sides. -block.combustiongenerator.name=combustion generator -block.combustiongenerator.fulldescription=Generates power from oil. Outputs power as lasers to its 4 sides. -block.rtgenerator.name=RTG generator -block.rtgenerator.fulldescription=Generates small amounts of power from the radioactive decay of uranium. Outputs power as lasers to its 4 sides. -block.nuclearreactor.name=nuclear reactor -block.nuclearreactor.fulldescription=An advanced version of the RTG Generator, and the ultimate power generator. Generates power from uranium. Requires constant water cooling. Highly volatile; will explode violently if insufficient amounts of coolant are supplied. -block.turret.name=turret -block.turret.fulldescription=A basic, cheap turret. Uses stone for ammo. Has slightly more range than the double-turret. -block.doubleturret.name=double turret -block.doubleturret.fulldescription=A slightly more powerful version of the turret. Uses stone for ammo. Does significantly more damage, but has a lower range. Shoots two bullets. -block.machineturret.name=gattling turret -block.machineturret.fulldescription=A standard all-around turret. Uses iron for ammo. Has a fast fire rate with decent damage. -block.shotgunturret.name=splitter turret -block.shotgunturret.fulldescription=A standard turret. Uses iron for ammo. Shoots a spread of 7 bullets. Lower range, but higher damage output than the gattling turret. -block.flameturret.name=flamer turret -block.flameturret.fulldescription=Advanced close-range turret. Uses coal for ammo. Has very low range, but very high damage. Good for close quarters. Recommended to be used behind walls. -block.sniperturret.name=railgun turret -block.sniperturret.fulldescription=Advanced long-range turret. Uses steel for ammo. Very high damage, but low fire rate. Expensive to use, but can be placed far away from enemy lines due to its range. -block.mortarturret.name=flak turret -block.mortarturret.fulldescription=Advanced low-accuracy splash-damage turret. Uses coal for ammo. Shoots a barrage of bullets that explode into shrapnel. Useful for large crowds of enemies. -block.laserturret.name=laser turret -block.laserturret.fulldescription=Advanced single-target turret. Uses power. Good medium-range all-around turret. Single-target only. Never misses. -block.waveturret.name=tesla turret -block.waveturret.fulldescription=Advanced multi-target turret. Uses power. Medium range. Never misses.Average to low damage, but can hit multiple enemies simultaneously with chain lighting. -block.plasmaturret.name=plasma turret -block.plasmaturret.fulldescription=Highly advanced version of the flamer turret. Uses coal as ammo. Very high damage, low to medium range. -block.chainturret.name=chain turret -block.chainturret.fulldescription=The ultimate rapid-fire turret. Uses uranium as ammo. Shoots large slugs at a high fire rate. Medium range. Spans multiple tiles. Extremely tough. -block.titancannon.name=titan cannon -block.titancannon.fulldescription=The ultimate long-range turret. Uses uranium as ammo. Shoots large splash-damage shells at a medium rate of fire. Long range. Spans multiple tiles. Extremely tough. -block.playerspawn.name=playerspawn -block.enemyspawn.name=enemyspawn \ No newline at end of file + +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks + +item.stone.name=Stone +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.name=Coal +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.name=Titanium +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.name=Sand +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. + +liquid.water.name=Water +liquid.lava.name=Lava +liquid.oil.name=Oil +liquid.cryofluid.name=Cryofluid + +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} + +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} + +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.door.name=Door +block.door-large.name=Large Door +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.conveyor.name=Conveyor +block.titanium-conveyor.name=Titanium Conveyor +block.junction.name=Junction +block.splitter.name=Splitter +block.splitter.description=Outputs items into three different directions once they are recieved. +block.router.name=Router +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.name=Sorter +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.smelter.name=Smelter +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.conduit.name=Conduit +block.pulseconduit.name=Pulse Conduit +block.liquidrouter.name=Liquid Router +block.liquidtank.name=Liquid Tank +block.liquidjunction.name=Liquid Junction +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor diff --git a/core/assets/bundles/bundle_de.properties b/core/assets/bundles/bundle_de.properties index be8fa12cf5..78b1bf4794 100644 --- a/core/assets/bundles/bundle_de.properties +++ b/core/assets/bundles/bundle_de.properties @@ -1,479 +1,495 @@ -text.about = Erstellt von [ROYAL] Anuken. [] \nUrsprünglich ein Eintrag im [orange] GDL [] MM Jam.\n\nCredits: \n- SFX gemacht mit [yellow] bfxr [] - Musik gemacht von [green] RoccoW [] / gefunden auf [lime] FreeMusicArchive.org [] \n\nBesonderer Dank geht an: \n- [coral] MitchellFJN []: Umfangreicher Spieletest und Feedback \n- [sky] Luxray5474 []: Wiki-Arbeit, Code-Beiträge \n- Alle Beta-Tester auf itch.io und Google Play\n -text.discord = Trete dem Mindustry Discord bei! -text.gameover = Der Kern wurde zerstört. -text.highscore = [YELLOW] Neuer Highscore! -text.lasted = Du hast bis zur folgenden Welle überlebt -text.level.highscore = High Score: [accent] {0} -text.level.delete.title = Löschen bestätigen -text.level.delete = Bist du sicher, dass du die Karte \"[orange] {0}\" löschen möchtest? -text.level.select = Level Auswahl -text.level.mode = Spielmodus: -text.savegame = Spiel speichern -text.loadgame = Spiel laden -text.joingame = Spiel beitreten -text.quit = Verlassen -text.about.button = Info -text.name = Name: -text.public = Öffentlich -text.players = {0} Spieler online -text.players.single = {0} Spieler online -text.server.mismatch = Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben! -.server.closing = [accent] Server wird geschlossen... -text.server.kicked.kick = Du wurdest vom Server gekickt! -text.server.kicked.invalidPassword = Falsches Passwort. -text.server.connected = {0} ist beigetreten -text.server.disconnected = {0} hat die Verbindung getrennt. -text.nohost = Server kann nicht auf einer benutzerdefinierten Karte gehostet werden! -text.hostserver = Server hosten -text.host = Host -text.hosting = [accent] Server wird geöffnet... -text.hosts.refresh = Aktualisieren -text.hosts.discovering = Suche nach LAN-Spielen -text.server.refreshing = Server wird aktualisiert -text.hosts.none = [lightgray] Keine LAN Spiele gefunden! -text.host.invalid = [scarlet] Kann keine Verbindung zum Host herstellen. -text.server.add = Server hinzufügen -text.server.delete = Bist du dir sicher das du diesen Server löschen möchtest? -text.server.hostname = Host: {0} -text.server.edit = Server bearbeiten -text.joingame.byip = Über IP beitreten ... -text.joingame.title = Spiel beitreten -text.joingame.ip = IP: -text.disconnect = Verbindung unterbrochen. -text.connecting = [accent] Verbindet... -text.connecting.data = [accent] Weltdaten werden geladen... -text.connectfail = [crimson] Verbindung zum Server konnte nicht hergestellt werden: [orange]{0} -text.server.port = Port: -text.server.invalidport = Falscher Port! -text.server.error = [crimson] Fehler beim Hosten des Servers: [orange] {0} -text.tutorial.back = < Zurück -text.tutorial.next = Weiter > -text.save.new = Neuer Spielstand -text.save.overwrite = Möchten du diesen Spielstand wirklich überschreiben? -text.overwrite = Überschreiben -text.save.none = Keine Spielstände gefunden! -text.saveload = [accent] Speichern ... -text.savefail = Fehler beim Speichern des Spiels! -text.save.delete.confirm = Möchtest du diesen Spielstand wirklich löschen? -text.save.delete = Löschen -text.save.export = Spielstand exportieren -text.save.import.invalid = [orange] Dieser Spielstand ist ungültig! -text.save.import.fail = [crimson] Spielstand konnte nicht importiert werden: [orange] {0} -text.save.export.fail = [crimson] Spielstand konnte nicht exportiert werden: [orange] {0} -text.save.import = Spielstand importieren -text.save.newslot = Name speichern: -text.save.rename = Umbenennen -text.save.rename.text = Neuer Name -text.selectslot = Wähle einen Spielstand -text.slot = [accent] Platz {0} -text.save.corrupted = [orange] Datei beschädigt oder ungültig! -text.empty = -text.on = An -text.off = Aus -text.save.autosave = Automatisches Speichern: {0} -text.save.map = Karte: {0} -text.save.wave = Welle: {0} -text.save.date = Zuletzt gespeichert: {0} -text.confirm = Bestätigen -text.delete = Löschen -text.ok = OK -text.open = Öffnen -text.cancel = Abbruch -text.openlink = Link öffnen -text.back = Zurück -text.quit.confirm = Willst du wirklich aufhören? -text.loading = [accent] Wird geladen ... -text.wave = [orange] Welle {0} -text.wave.waiting = Welle in {0} -text.waiting = Warten... -text.enemies = {0} Feinde -text.enemies.single = {0} Feind -text.loadimage = Bild laden -text.saveimage = Bild speichern -text.editor.badsize = [orange]Ungültige Bildabmessungen! [] Gültige Kartenabmessungen: {0} -text.editor.errorimageload = Fehler beim Laden des Bildes: [orange] {0} -text.editor.errorimagesave = Fehler beim Speichern des Bildes: [orange] {0} -text.editor.generate = Generieren -text.editor.resize = Grösse\nanpassen -text.editor.loadmap = Karte\nladen -text.editor.savemap = Karte\nspeichern -text.editor.loadimage = Bild\nladen -text.editor.saveimage = Bild\nspeichern -text.editor.unsaved = [crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? -text.editor.brushsize = Pinselgrösse: {0} -text.editor.noplayerspawn = Diese Karte hat keinen Spielerspawnpunkt! -text.editor.manyplayerspawns = Maps können nicht mehr als einen Spawnpunkt des Spielers haben! -text.editor.manyenemyspawns = Kann nicht mehr als {0} feindliche Spawnpunkte haben! -text.editor.resizemap = Grösse der Karte ändern -text.editor.resizebig = [crimson] Warnung! [] Karten, die grösser als 256 Einheiten sind, können ruckeln und instabil sein. -text.editor.mapname = Map Name -text.editor.overwrite = [accent] Warnung! Dies überschreibt eine vorhandene Map. -text.editor.failoverwrite = [crimson] Die Standardkarte kann nicht überschrieben werden! -text.editor.selectmap = Wähle eine Map zum Laden: -text.width = Breite: -text.height = Höhe: -text.randomize = Zufällig -text.apply = Anwenden -text.update = Aktualisieren -text.menu = Menü -text.play = Spielen -text.load = Laden -text.save = Speichern -text.settings = Einstellungen -text.tutorial = Tutorial -text.editor = Bearbeiter -text.mapeditor = Karten Bearbeiter -text.donate = Spenden -text.settings.reset = Auf Standard zurücksetzen -text.settings.controls = Steuerung -text.settings.game = Spiel -text.settings.sound = Audio -text.settings.graphics = Grafiken -text.upgrades = Verbesserungen -text.purchased = [LIME] Erstellt! -text.weapons = Waffen -text.paused = Pausiert -text.respawn = Respawn in -text.error.title = [crimson] Ein Fehler ist aufgetreten -text.error.crashmessage = [SCARLET] Es ist ein unerwarteter Fehler aufgetreten, der einen Absturz verursacht hätte. [] Bitte geben Sie die genauen Umstände an, unter denen dieser Fehler passiert ist, für den Entwickler: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = EIn Fehler ist aufgetreten! -text.mode.break = Zerstörungsmodus: {0} -text.mode.place = Platzierungsmodus: {0} -placemode.hold.name = Zeile -placemode.areadelete.name = Gebiet -placemode.touchdelete.name = berühren -placemode.holddelete.name = halten -placemode.none.name = keine -placemode.touch.name = berühren -placemode.cursor.name = Mauszeiger -text.blocks.extrainfo = [accent] Extra Blockinfo: -text.blocks.blockinfo = Blockinfo: -text.blocks.powercapacity = Energiekapazität -text.blocks.powershot = Energie / Schuss -text.blocks.powersecond = Energie / Sekunde -text.blocks.powerdraindamage = Energieabnahme / Schaden -text.blocks.shieldradius = Schildradius -text.blocks.itemspeedsecond = Gegenstands Geschwindigkeit / Sekunde -text.blocks.range = Reichweite -text.blocks.size = Grösse -text.blocks.powerliquid = Energie / Flüssigkeit -text.blocks.maxliquidsecond = Max Flüssigkeit / Sekunde -text.blocks.liquidcapacity = Flüssigkeitskapazität -text.blocks.liquidsecond = Flüssigkeit / Sekunde -text.blocks.damageshot = Schaden / Schuss -text.blocks.ammocapacity = Munitionskapazität -text.blocks.ammo = Munition -text.blocks.ammoitem = Munition / Gegenstand -text.blocks.maxitemssecond = Max Gegenstände / Sekunde -text.blocks.powerrange = Energiereichweite -text.blocks.lasertilerange = Laser Reichweite -text.blocks.capacity = Kapazität -text.blocks.itemcapacity = Gegenstand Kapazität -text.blocks.maxpowergenerationsecond = Max Energieerzeugung / Sekunde -text.blocks.powergenerationsecond = Energieerzeugung / Sekunde -text.blocks.generationsecondsitem = Generation Sekunden / Gegenstand -text.blocks.input = Eingabe -text.blocks.inputliquid = Flüssigkeiten Eingabe -text.blocks.inputitem = Eingabe Gegenstand -text.blocks.output = Ausgabe -text.blocks.secondsitem = Sekunden / Item -text.blocks.maxpowertransfersecond = Max Energieübertragung / Sekunde -text.blocks.explosive = Hochexplosiv! -text.blocks.repairssecond = Reparaturen / Sekunde -text.blocks.health = Lebenspunkte -text.blocks.inaccuracy = Ungenauigkeit -text.blocks.shots = Schüsse -text.blocks.shotssecond = Schüsse / Sekunde -text.blocks.fuel = Treibstoff -text.blocks.fuelduration = Treibstoffdauer -text.blocks.maxoutputsecond = Max Ausgabe / Sekunde -text.blocks.inputcapacity = Eingabekapazität -text.blocks.outputcapacity = Ausgabekapazität -text.blocks.poweritem = Energie / Gegenstand -text.placemode = Platzierungsmodus -text.breakmode = Zerstörungsmodus -text.health = Lebenspunkte -setting.difficulty.easy = Leicht -setting.difficulty.normal = Normal -setting.difficulty.hard = Schwer -setting.difficulty.insane = Unmöglich -setting.difficulty.purge = Auslöschung -setting.difficulty.name = Schwierigkeit -setting.screenshake.name = Bildschirm wackeln -setting.smoothcam.name = Glatte Kamera -setting.indicators.name = Feind Indikatoren -setting.effects.name = Effekte anzeigen -setting.sensitivity.name = Kontroller Empfindlichkeit -setting.saveinterval.name = Autosave Häufigkeit -setting.seconds = {0} Sekunden -setting.fps.name = Zeige FPS -setting.vsync.name = VSync -setting.lasers.name = Zeige Energielaser -setting.healthbars.name = Zeige Objekt Lebensbalken -setting.pixelate.name = Pixel Bildschirm -setting.musicvol.name = Musiklautstärke -setting.mutemusic.name = Musik stummschalten -setting.sfxvol.name = Audioeffekte Lautstärke -setting.mutesound.name = Audioeffekte stummschalten -map.maze.name = Labyrinth -map.fortress.name = Festung -map.sinkhole.name = Sinkloch -map.caves.name = Höhlen -map.volcano.name = Vulkan -map.caldera.name = Lavakessel -map.scorch.name = Flammen -map.desert.name = Wüste -map.island.name = Insel -map.grassland.name = Grasland -map.tundra.name = Kältesteppe -map.spiral.name = Spirale -map.tutorial.name = Tutorial -tutorial.intro.text = [gelb] Willkommen zum Tutorial [] Um zu beginnen, drücke 'weiter'. -tutorial.moveDesktop.text = Verwende zum Verschieben die Tasten [orange] [[WASD] []. Halte [orange] Shift [] gedrückt, um zu erhöhen. Halte [orange] CTRL/STRG [] gedrückt, während du das [orange] Scrollrad [] zum Vergrössern oder Verkleinern verwendest. -tutorial.shoot.text = Ziele mit der Maus, halte die [orange] linke Maustaste [] gedrückt, um zu schiessen. Versuche es mit dem [gelben] Ziel []. -tutorial.moveAndroid.text = Um die Ansicht zu verschieben, ziehe einen Finger über den Bildschirm. Drücke und ziehe, um zu vergrössern oder zu verkleinern. -tutorial.placeSelect.text = Versuche, ein [gelbes] Förderband [] aus dem Blockmenü unten rechts auszuwählen. -tutorial.placeConveyorDesktop.text = Verwende das [orange] [[scrollwheel] [], um das Förderband nach vorne zu bewegen [orange] vorwärts [], und platziere es dann an der [gelben] markierten Stelle [] mit [orange] [[linke Maustaste] []. -tutorial.placeConveyorAndroid.text = Verwende die [orange] [[rotieren-Taste] [], um das Förderband nach vorne [orange] zu drehen [], ziehe es mit einem Finger in Position und platziere es dann an der [gelben] markierten Stelle [] mit der [Orange] [[Häkchen][]. -tutorial.placeConveyorAndroidInfo.text = Alternativ kannst du das Fadenkreuzsymbol unten links drücken, um zum [orange] [[touch mode] [] zu wechseln, und Blöcke durch Tippen auf den Bildschirm platzieren. Im Touch-Modus können Blöcke mit dem Pfeil unten links gedreht werden. Drücke [gelb] neben [], um es auszuprobieren. -tutorial.placeDrill.text = Wähle nun einen [gelben] Steinbohrer [] an der markierten Stelle aus und platziere ihn. -tutorial.blockInfo.text = Wenn du mehr über einen Block erfahren möchtest, tippe oben rechts auf das [orange] Fragezeichen [], um dessen Beschreibung zu lesen. -tutorial.deselectDesktop.text = Du kannst einen Block mit [Orange] [[Rechte Maustaste] [] abwählen. -tutorial.deselectAndroid.text = Du kannst einen Block abwählen, indem du die [orange] X [] -Taste drücken. -tutorial.drillPlaced.text = Der Bohrer erzeugt nun [gelben] Stein, [] gib den Stein nun auf das Förderband aus und bewege ihn dann in den [gelben] Kern []. -tutorial.drillInfo.text = Verschiedene Erze benötigen unterschiedliche Bohrer. Stein erfordert Steinbohrer, Eisen erfordert Eisenbohrer usw. -tutorial.drillPlaced2.text = Wenn du Gegenstände in den Kern verschiebst, steckst du sie in dein [gelbes] Inventar [] oben links. Das Platzieren von Blöcken verwendet Gegenstände aus deinem Inventar. -tutorial.moreDrills.text = Du kannst so viele Bohrer und Förderer miteinander verbinden wie du lust hast. -tutorial.deleteBlock.text = Du kannst Blöcke löschen, indem du mit der [orange] rechte Maustaste [] auf dem Block klickst, den du löschen möchtest. Versuche, dieses Förderband zu löschen. -tutorial.deleteBlockAndroid.text = Du kannst Blöcke löschen, indem du [orange] das Fadenkreuz [] im [orange] Pausenmodusmenü [] links unten auswählst und auf einen Block tippst. Versuche, dieses Fliessband zu löschen. -tutorial.placeTurret.text = Wähle nun einen [gelben] Turm [] an der [gelben] markierten Stelle [] und platziere ihn. -tutorial.placedTurretAmmo.text = Dieser Turm nimmt nun [gelbe] Munition [] vom Förderband an. Du kannst sehen, wie viel Munition es hat, indem du darüber schweben und den [grünen] grünen Balken [] prüfen. -tutorial.turretExplanation.text = Geschütze schiessen automatisch auf den nächsten Feind in Reichweite, solange sie genug Munition haben. -tutorial.waves.text = Jede [yellow] 60 [] Sekunden wird eine Welle von [coral] Feinden [] an einem bestimmten Orten erscheinen und versuchen, den Kern zu zerstören. -tutorial.coreDestruction.text = Dein Ziel ist es, den Kern [yellow] zu verteidigen. Wenn der Kern zerstört wird, verlierst du [coral] das Spiel []. -tutorial.pausingDesktop.text = Wenn du jemals eine Pause machen möchtest, drücke die [orange] Pause-Taste [] oben links oder [orange]space[], um das Spiel anzuhalten. Du kannst auch Blöcke immer noch auswählen und platzieren, während du das Spiel pausiert ist, aber Sie können sich nicht bewegen oder schiessen. -tutorial.pausingAndroid.text = Wenn du jemals eine Pause machen willst, drück einfach die [orange] Pause-Taste [] oben links, um das Spiel anzuhalten. Sie können immer noch Sachen auswählen und Blöcke während der Pause platzieren. -tutorial.purchaseWeapons.text = Du kannst neue [yellow] Waffen [] für deinen Mech kaufen, indem du das Verbesserungs-Menü unten links öffnest. -tutorial.switchWeapons.text = Schalte Waffen, indem du entweder auf das Symbol unten links klickst oder Nummern verwendest [orange] [[1-9] []. -tutorial.spawnWave.text = Hier kommt die erste Welle. Zerstöre sie. -tutorial.pumpDesc.text = In späteren Wellen müsst du möglicherweise [yellow] Pumpen [] verwenden, um Flüssigkeiten für Generatoren oder Extraktoren zu verteilen. -tutorial.pumpPlace.text = Pumpen arbeiten ähnlich wie Bohrer, ausser dass sie anstelle von Gegenständen Flüssigkeiten produzieren. Versuch mal, eine Pumpe auf das [yellow] gekennzeichnete Öl [] zu setzen. -tutorial.conduitUse.text = Stellen Sie nun eine [orange] Leitungsrohr [] von der Pumpe weg. -tutorial.conduitUse2.text = Und noch ein paar mehr ... -tutorial.conduitUse3.text = Und noch ein paar mehr ... -tutorial.generator.text = Stellen Sie nun einen [orange] Verbrennungsgenerator [] Block am Ende des Leitungsrohres auf. -tutorial.generatorExplain.text = Dieser Generator erzeugt nun [yellow] Energie [] aus dem Öl. -tutorial.lasers.text = Die Energie wird mit [yellow] Energielasern [] verteilt. Drehe und platziere einen hier. -tutorial.laserExplain.text = Der Generator wird nun Energie in den Laserblock bewegen. Ein [yellow] undurchsichtiger [] Strahl bedeutet, dass er gerade Leistung überträgt, und ein [yellow] transparenter [] Strahl bedeutet, dass dies nicht der Fall ist. -tutorial.laserMore.text = Du kannst überprüfen, wie viel Energie ein Block hat, indem du darüber schweben und den [yellow] gelben Balken [] oben prüfen. -tutorial.healingTurret.text = Dieser Laser kann verwendet werden, um einen [lime] -Reparaturgeschütz [] anzutreiben. Platziere einen hier. -tutorial.healingTurretExplain.text = Solange er Kraft hat, repariert dieser Turm in der Nähe Blöcke. [] Wenn du spielst, stelle sicher, dass du so schnell wie möglich einen in deiner Basis bekommst! -tutorial.smeltery.text = Viele Blöcke benötigen [orange] Stahl [], um eine [orange] Schmelzer [] herzustellen. Platziere einen hier. -tutorial.smelterySetup.text = Diese Schmelzer wird nun [orange] Stahl [] aus dem Eingangs-Eisen produzieren, wobei Kohle als Brennstoff verwendet wird. -tutorial.end.text = Und damit ist das Tutorial abgeschlossen! Viel Glück! -keybind.move_x.name = bewege_x -keybind.move_y.name = bewege_y -keybind.select.name = wählen -keybind.break.name = Unterbrechung -keybind.shoot.name = Schiess -keybind.zoom_hold.name = zoomen_halten -keybind.zoom.name = zoomen -keybind.menu.name = Menü -keybind.pause.name = Pause -keybind.dash.name = Bindestrich -keybind.rotate_alt.name = drehen_alt -keybind.rotate.name = Drehen -keybind.weapon_1.name = Waffe_1 -keybind.weapon_2.name = Waffe_2 -keybind.weapon_3.name = Waffe_3 -keybind.weapon_4.name = Waffe_4 -keybind.weapon_5.name = Waffe_5 -keybind.weapon_6.name = Waffe_6 -mode.waves.name = Wellen -mode.sandbox.name = Sandkasten -mode.freebuild.name = Freier Bau -upgrade.standard.name = Standard -upgrade.standard.description = Der Standardmech. -upgrade.blaster.name = Blaster -upgrade.blaster.description = Schiesst eine langsame, schwache Kugel. -upgrade.triblaster.name = Dreifach-Blaster -upgrade.triblaster.description = Schiesst 3 Kugeln in einer Ausbreitung. -upgrade.clustergun.name = Klumpen-Waffe -upgrade.clustergun.description = Schiesst eine ungenaue Verbreitung von explosiven Granaten. -upgrade.beam.name = Strahlkanone -upgrade.beam.description = Schiesst einen weitreichenden durchdringenden Laserstrahl. -upgrade.vulcan.name = Vulkan -upgrade.vulcan.description = Schiesst eine Flut von schnellen Kugeln. -upgrade.shockgun.name = Schock-Waffe -upgrade.shockgun.description = Erschiesst eine verheerende Explosion von geladenen Granatsplittern. -item.stone.name = Stein -item.iron.name = Eisen -item.coal.name = Kohle -item.steel.name = Stahl -item.titanium.name = Titan -item.dirium.name = Dirium -item.uranium.name = Uran -item.sand.name = Sand -liquid.water.name = Wasser -liquid.plasma.name = Plasma -liquid.lava.name = Lava -liquid.oil.name = Öl -block.weaponfactory.name = Waffenfabrik -block.air.name = Luft -block.blockpart.name = Blockteil -block.deepwater.name = tiefes Wasser -block.water.name = Wasser -block.lava.name = Lava -block.oil.name = Öl -block.stone.name = Stein -block.blackstone.name = schwarzer Stein -block.iron.name = Eisen -block.coal.name = Kohle -block.titanium.name = Titan -block.uranium.name = Uran -block.dirt.name = Erde -block.sand.name = Sand -block.ice.name = Eis -block.snow.name = Schnee -block.grass.name = Gras -block.sandblock.name = Sandstein -block.snowblock.name = Schneeblock -block.stoneblock.name = Steinblock -block.blackstoneblock.name = Schwarzer Stein -block.grassblock.name = Grasblock -block.mossblock.name = Moosblock -block.shrub.name = Busch -block.rock.name = Felsen -block.icerock.name = Eisfelsen -block.blackrock.name = Schwarzer Felsen -block.dirtblock.name = Erdblock -block.stonewall.name = Steinwand -block.stonewall.fulldescription = Ein billiger Verteidigungsblock. Nützlich zum Schutz des Kerns und der Geschütztürme in den ersten Wellen. -block.ironwall.name = Eisenwand -block.ironwall.fulldescription = Ein grundlegender Verteidigungsblock. Bietet Schutz vor Feinden. -block.steelwall.name = Stahlwand -block.steelwall.fulldescription = Ein Standard-Verteidigungsblock. Bietet angemessen Schutz vor Feinden. -block.titaniumwall.name = Titanwand -block.titaniumwall.fulldescription = Eine starke Abwehrblockade. Bietet Schutz vor Feinden. -block.duriumwall.name = Diriumwand -block.duriumwall.fulldescription = Eine sehr starke Abwehrblockade. Bietet guten Schutz vor Feinden. -block.compositewall.name = Verbundende Wand -block.steelwall-large.name = grosse Stahlwand -block.steelwall-large.fulldescription = Ein Standard-Verteidigungsblock. Mehrere Blöcke gross. -block.titaniumwall-large.name = grosse Titanwand -block.titaniumwall-large.fulldescription = Eine starke Abwehrblockade. Mehrere Blöcke gross. -block.duriumwall-large.name = grosse Diriumwand -block.duriumwall-large.fulldescription = Eine sehr starke Abwehrblockade. Mehrere Blöcke gross. -block.titaniumshieldwall.name = geschützte Wand -block.titaniumshieldwall.fulldescription = Ein starker Abwehrblock mit einem extra eingebauten Schild. Benötigt Energie. Verwendet Energie, um feindliche Kugeln zu absorbieren. Es wird empfohlen, Verstärker zu verwenden, um diesem Block Energie zuzuführen. -block.repairturret.name = Reparaturgeschütz -block.repairturret.fulldescription = Repariert beschädigte Blöcke in der Nähe in einem langsamen Tempo. Nutzt geringe Mengen an Energie. -block.megarepairturret.name = Reparaturgeschütz II -block.megarepairturret.fulldescription = Repariert in der Nähe beschädigte Blöcke in Reichweite zu einem vernünftigen Preis. Verwendet Energie. -block.shieldgenerator.name = Schildgenerator -block.shieldgenerator.fulldescription = Ein fortgeschrittener Verteidigungsblock. Schützt alle Blöcke in einem Radius vor Angriffen. Bei keinem Betrieb langsamer Strom verbrauch, verliert bei Kugelkontakt jedoch schnell Energie. -block.door.name = Tür -block.door.fulldescription = Ein Block, der durch Antippen geöffnet und geschlossen werden kann. -block.door-large.name = grosse Tür -block.door-large.fulldescription = Ein Block der mehrere Felder gross ist und der durch Antippen geöffnet und geschlossen werden kann. -block.conduit.name = Leitungsrohr -block.conduit.fulldescription = Grundlegender Flüssigkeitstransportblock. Funktioniert wie ein Förderband, aber mit Flüssigkeiten. Am besten mit Pumpen oder anderen Leitungen verwenden. Kann als Brücke für Gegner und Spieler verwendet werden. -block.pulseconduit.name = Pulsleitungsrohr -block.pulseconduit.fulldescription = Fortschrittlicher Flüssigkeitstransportblock. Transportiert Flüssigkeiten schneller und speichert mehr als normale Leitungsrohre. -block.liquidrouter.name = flüssigkeiten verteiler -block.liquidrouter.fulldescription = Funktioniert ähnlich wie ein Router. Akzeptiert Flüssigkeit von einer Seite und gibt sie auf die anderen Seiten aus. Nützlich zum Aufspalten von Flüssigkeit aus eines einzelnen Leitungsrohres in mehrere andere Leitungensrohre. -block.conveyor.name = Förderband -block.conveyor.fulldescription = Grundelement Transport Block. Bewegt Gegenstände nach vorne und legt sie automatisch in Türmen oder ähnliches. Drehbar. Kann als Brücke für Gegner und Spieler verwendet werden. -block.steelconveyor.name = Stahlförderband -block.steelconveyor.fulldescription = Erweiterter Transportblock Bewegt Gegenstände schneller als Standardförderer. -block.poweredconveyor.name = Impulsförderband -block.poweredconveyor.fulldescription = Der ultimative Transportblock für Gegenstände. Bewegt Gegenstände noch schneller als Stahlförderer. -block.router.name = Verteiler -block.router.fulldescription = Akzeptiert Objekte aus einer Richtung und gibt sie in 3 andere Richtungen aus. Kann auch eine bestimmte Anzahl von Gegenständen speichern. Geeignet zum Aufteilen der Materialien von einem Bohrer in mehrere Geschütze. -block.junction.name = Kreuzung -block.junction.fulldescription = Fungiert als Brücke für zwei kreuzende Förderbänder. Nützlich in Situationen mit zwei verschiedenen Förderbänder, die unterschiedliche Materialien zu verschiedenen Orten transportieren. -block.conveyortunnel.name = Förderbandtunnel -block.conveyortunnel.fulldescription = Transportiert Artikel unter Blöcken. Verwendung für einen Tunnel, der in den zu untertunnelnden Block führt, und einen auf der anderen Seite. Stellen Sie sicher, dass beide Tunnel in entgegengesetzte Richtungen weisen, das heisst das die Tunnel voneinander weggucken. -block.liquidjunction.name = flüssigkeite Kreuzung -block.liquidjunction.fulldescription = Funktioniert als Brücke für zwei kreuzende Leitungsrohre. Nützlich in Situationen mit zwei verschiedenen Leitungen, die unterschiedliche Flüssigkeiten zu verschiedenen Orten transportieren. -block.liquiditemjunction.name = Flüssigkeit-Gegenstand-Kreuzung -block.liquiditemjunction.fulldescription = Fungiert als Brücke zum Überqueren von Leitungsrohre und Förderbändern. -block.powerbooster.name = Energieverstärker -block.powerbooster.fulldescription = Verteilt die Energie an alle Blöcke innerhalb seines Radius. -block.powerlaser.name = Energielaser -block.powerlaser.fulldescription = Erzeugt einen Laser, der die Kraft auf den Block davor überträgt. Erzeugt selbst keine Energie. Am besten mit Generatoren oder anderen Lasern verwendet. -block.powerlaserrouter.name = Laser Verteiler -block.powerlaserrouter.fulldescription = Laser, der die Kraft in drei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen. -block.powerlasercorner.name = Laser-Ecke -block.powerlasercorner.fulldescription = Laser, der die Kraft in zwei Richtungen gleichzeitig verteilt. Nützlich in Situationen, in denen mehrere Blöcke von einem Generator mit Strom versorgt werden müssen und ein Router ungenau ist. -block.teleporter.name = Teleporter -block.teleporter.fulldescription = Erweiterter Transportblock Teleporter geben Gegenstände in andere Teleporter derselben Farbe ein. Tut nichts, wenn keine Teleporter derselben Farbe existieren. Wenn mehrere Teleporter mit derselben Farbe existieren, wird eine zufällige ausgewählt. Verwendet Energie. Tippen, um die Farbe zu ändern. -block.sorter.name = Sortierer -block.sorter.fulldescription = Sortiert den Gegenstand nach Materialart. Das zu akzeptierende Material wird durch die Farbe im Block angezeigt. Alle Artikel, die dem Sortiermaterial entsprechen, werden vorwärts ausgegeben, alles andere wird nach links und rechts ausgegeben. -block.core.name = Kern -block.pump.name = Pumpe -block.pump.fulldescription = Pumpen Flüssigkeiten aus einem Quellblock - meist Wasser, Lava oder Öl. Gibt Flüssigkeit in benachbarte Leitungsrohre aus. -block.fluxpump.name = flux Pumpe -block.fluxpump.fulldescription = Eine erweiterte Version der Pumpe. Speichert mehr Flüssigkeit und pumpt Flüssigkeit schneller. -block.smelter.name = Schmelzer -block.smelter.fulldescription = Der essentielle Bastelblock. Wenn 1x Eisen und 1x Kohle eingegeben wird, wird 1x Stahl ausgegeben. -block.crucible.name = Tiegel -block.crucible.fulldescription = Ein fortgeschrittener Handwerksblock. Braucht Kohle um zu funktionieren. Wenn 1x Titan und 1x Stahl eingegeben wird, wird 1x Dirium ausgegeben. -block.coalpurifier.name = Kohle-Extraktor -block.coalpurifier.fulldescription = Ein einfacher Extraktorblock. Gibt Kohle aus, wenn der Block mit grossen Mengen Wasser und Stein gefüllt wird. -block.titaniumpurifier.name = Titan-Extraktor -block.titaniumpurifier.fulldescription = Ein Standard-Extraktorblock. Gibt Titan aus wenn er mit grossen Mengen Wasser und Eisen gefüllt wird. -block.oilrefinery.name = Ölraffinerie -block.oilrefinery.fulldescription = Veredelt grosse Mengen Öl zu Kohle. Nützlich für die Betankung von Blöcken die Kohle benutzen wie z.b. Waffentürme, wenn Kohleadern knapp sind. -block.stoneformer.name = Steinformer -block.stoneformer.fulldescription = Verfestigt flüssige Lava zu Stein. Nützlich für die Herstellung von grossen Mengen von Stein für Kohle-Extraktor. -block.lavasmelter.name = Lava-Schmelzer -block.lavasmelter.fulldescription = Verwendet Lava, um Eisen zu Stahl schmelzen. Eine Alternative zum Schmelzer. Nützlich in Situationen, in denen Kohle knapp ist. -block.stonedrill.name = Steinbohrer -block.stonedrill.fulldescription = Der wesentliche Bohrer. Wenn er auf Steinplatten gelegt wird, gibt er Steine ​​mit einer langsamen Geschwindigkeit auf endlosen Zeit aus. -block.irondrill.name = Eisenbohrer -block.irondrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Eisenerz gelegt wird, gibt er Eisen auf endloser Zeit langsam aus. -block.coaldrill.name = Kohlenbohrer -block.coaldrill.fulldescription = Eine grundlegender Bohrer. Wenn es auf Kohleerz platziert wird, gibt er für endloser Zeit langsam Kohle aus. -block.uraniumdrill.name = Uran-Bohrer -block.uraniumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Uranerz platziert wird, gibt er Uran mit einer langsamen Geschwindigkeit auf endloser Zeit ab. -block.titaniumdrill.name = Titan-Bohrer -block.titaniumdrill.fulldescription = Ein fortgeschrittener Bohrer. Wenn es auf Titanerz platziert wird, gibt er Titan langsam aus für undendlich langer Zeit -block.omnidrill.name = Omni-Bohrer -block.omnidrill.fulldescription = Der ultimative Bohrer. Baut in schnellem Tempo jegliches Erz ab. -block.coalgenerator.name = Kohle-Generator -block.coalgenerator.fulldescription = Der wesentliche Generator. Erzeugt Energie aus Kohle. Gibt Energie als Laser an seine 4 Seiten aus. -block.thermalgenerator.name = thermischer Generator -block.thermalgenerator.fulldescription = Erzeugt Energie aus Lava. Gibt Energie als Laser an seine 4 Seiten aus. -block.combustiongenerator.name = Verbrennungsgenerator -block.combustiongenerator.fulldescription = Erzeugt Energie aus Öl. Gibt Energie als Laser an seine 4 Seiten aus. -block.rtgenerator.name = RTG-Generator -block.rtgenerator.fulldescription = Erzeugt geringe Mengen an Energie aus dem radioaktiven Zerfall von Uran. Gibt Energie als Laser an seine 4 Seiten aus. -block.nuclearreactor.name = Kernreaktor -block.nuclearreactor.fulldescription = Eine erweiterte Version des RTG-Generators und der ultimative Energie Generator. Erzeugt Strom aus Uran. Erfordert konstante Wasserkühlung. Sehr heiss; explodiert heftig, wenn zu wenig Kühlmittel zugeführt wird. -block.turret.name = Geschütz -block.turret.fulldescription = Ein einfacher, billiger Turm. Verwendet Stein für Munition. Hat etwas mehr Reichweite als das Doppelgeschütz. -block.doubleturret.name = Doppelgeschütz -block.doubleturret.fulldescription = Eine etwas stärkere Version des Geschützes. Verwendet Stein für Munition. Hat deutlich mehr Schaden, hat aber eine geringere Reichweite. Schiesst zwei Kugeln. -block.machineturret.name = Gatling Geschütz -block.machineturret.fulldescription = Ein Standard-Allround-Turm. Verwendet Eisen für Munition. Hat eine schnelle Feuerrate mit gutem Schaden. -block.shotgunturret.name = Splittergeschütz -block.shotgunturret.fulldescription = Ein Standard-Turm. Verwendet Eisen für Munition. Schiesst 7 Kugeln auf einmal. Geringere Reichweite, aber höhere Schadensleistung als das Gatling Geschütz -block.flameturret.name = Flammenwerfer -block.flameturret.fulldescription = Fortschrittlicher Nahbereichswaffe. Verwendet Kohle für Munition. Hat eine sehr geringe Reichweite, aber sehr hohen Schaden. Gut für Nahkampf. Empfohlen für den Einsatz hinter Mauern. -block.sniperturret.name = Schienenkanone -block.sniperturret.fulldescription = Fortschrittliches Reichweitengeschütz. Verwendet Stahl für Munition. Sehr hoher Schaden, aber niedrige Feuerrate. Teuer zu verwenden, kann aber aufgrund seiner Reichweite weit entfernt von den feindlichen Linien platziert werden. -block.mortarturret.name = Flakgeschütz -block.mortarturret.fulldescription = Fortschrittlicher Flächen-Schaden Turm. Verwendet Kohle für Munition. Sehr langsame Feuerrate und Geschosse, aber sehr hoher Einzelziel- und Flächenschaden. Nützlich gegen grosse Mengen von Feinden. -block.laserturret.name = Laserturm -block.laserturret.fulldescription = Fortschrittlicher Einzelziel-Turm. Verwendet Strom. Guter Allround-Revolver für mittlere Reichweiten. Nur Einzelziel. Verfehlt nie. -block.waveturret.name = Teslakanone -block.waveturret.fulldescription = Erweiterter Mehrfach-Ziele-Turm. Verwendet Strom. Mittlere Reichweite. Verfehlt nie. Im Durchschnitt zu wenig Schaden, aber kann mehrere Feinde gleichzeitig mit Kettenblitz treffen. -block.plasmaturret.name = Plasmageschütz -block.plasmaturret.fulldescription = Hochentwickelte Version des Flammenwerfers. Verwendet Kohle als Munition. Sehr hoher Schaden, niedriger bis mittlerer Reichweite. -block.chainturret.name = Kettengeschütz -block.chainturret.fulldescription = Die ultimative Schnellfeuer Waffe. Verwendet Uran als Munition. Schiesst grosse Kugeln mit hoher Feuerrate. Mittlere Reichweite. Mehrere Felder gross. Hält extrem viel aus. -block.titancannon.name = Titan Kanone -block.titancannon.fulldescription = Die ultimative Langstrecken Kanone. Verwendet Uran als Munition. Schiesst grosse Flächen-Schadenden-Granaten mit einer mittleren Feuerrate ab. Lange Reichweite. Ist mehrere Felder gross. Hält extrem viel aus. -block.playerspawn.name = Spielerspawn -block.enemyspawn.name = Gegnerspawn +text.about=Erstellt von [ROYAL] Anuken. [] \nUrsprünglich ein Eintrag im [orange] GDL [] MM Jam.\n\nCredits: \n- SFX gemacht mit [yellow] bfxr [] - Musik gemacht von [green] RoccoW [] / gefunden auf [lime] FreeMusicArchive.org [] \n\nBesonderer Dank geht an: \n- [coral] MitchellFJN []: Umfangreicher Spieletest und Feedback \n- [sky] Luxray5474 []: Wiki-Arbeit, Code-Beiträge \n- Alle Beta-Tester auf itch.io und Google Play\n +text.discord=Trete dem Mindustry Discord bei! +text.gameover=Der Kern wurde zerstört. +text.highscore=[YELLOW] Neuer Highscore! +text.lasted=Du hast bis zur folgenden Welle überlebt +text.level.highscore=High Score: [accent] {0} +text.level.delete.title=Löschen bestätigen +text.level.select=Level Auswahl +text.level.mode=Spielmodus: +text.savegame=Spiel speichern +text.loadgame=Spiel laden +text.joingame=Spiel beitreten +text.quit=Verlassen +text.about.button=Info +text.name=Name: +text.players={0} Spieler online +text.players.single={0} Spieler online +text.server.mismatch=Paketfehler: Mögliche Client / Server-Version stimmt nicht überein. Stell sicher, dass du und der Host die neueste Version von Mindustry haben! +text.server.kicked.kick=Du wurdest vom Server gekickt! +text.server.kicked.invalidPassword=Falsches Passwort. +text.server.connected={0} ist beigetreten +text.server.disconnected={0} hat die Verbindung getrennt. +text.nohost=Server kann nicht auf einer benutzerdefinierten Karte gehostet werden! +text.hostserver=Server hosten +text.host=Host +text.hosting=[accent] Server wird geöffnet... +text.hosts.refresh=Aktualisieren +text.hosts.discovering=Suche nach LAN-Spielen +text.server.refreshing=Server wird aktualisiert +text.hosts.none=[lightgray] Keine LAN Spiele gefunden! +text.host.invalid=[scarlet] Kann keine Verbindung zum Host herstellen. +text.server.add=Server hinzufügen +text.server.delete=Bist du dir sicher das du diesen Server löschen möchtest? +text.server.hostname=Host: {0} +text.server.edit=Server bearbeiten +text.joingame.title=Spiel beitreten +text.joingame.ip=IP: +text.disconnect=Verbindung unterbrochen. +text.connecting=[accent] Verbindet... +text.connecting.data=[accent] Weltdaten werden geladen... +text.connectfail=[crimson] Verbindung zum Server konnte nicht hergestellt werden: [orange]{0} +text.server.port=Port: +text.server.invalidport=Falscher Port! +text.server.error=[crimson] Fehler beim Hosten des Servers: [orange] {0} +text.save.new=Neuer Spielstand +text.save.overwrite=Möchten du diesen Spielstand wirklich überschreiben? +text.overwrite=Überschreiben +text.save.none=Keine Spielstände gefunden! +text.saveload=[accent] Speichern ... +text.savefail=Fehler beim Speichern des Spiels! +text.save.delete.confirm=Möchtest du diesen Spielstand wirklich löschen? +text.save.delete=Löschen +text.save.export=Spielstand exportieren +text.save.import.invalid=[orange] Dieser Spielstand ist ungültig! +text.save.import.fail=[crimson] Spielstand konnte nicht importiert werden: [orange] {0} +text.save.export.fail=[crimson] Spielstand konnte nicht exportiert werden: [orange] {0} +text.save.import=Spielstand importieren +text.save.newslot=Name speichern: +text.save.rename=Umbenennen +text.save.rename.text=Neuer Name +text.selectslot=Wähle einen Spielstand +text.slot=[accent] Platz {0} +text.save.corrupted=[orange] Datei beschädigt oder ungültig! +text.empty= +text.on=An +text.off=Aus +text.save.autosave=Automatisches Speichern: {0} +text.save.map=Karte: {0} +text.save.wave=Welle: {0} +text.save.date=Zuletzt gespeichert: {0} +text.confirm=Bestätigen +text.delete=Löschen +text.ok=OK +text.open=Öffnen +text.cancel=Abbruch +text.openlink=Link öffnen +text.back=Zurück +text.quit.confirm=Willst du wirklich aufhören? +text.loading=[accent] Wird geladen ... +text.wave=[orange] Welle {0} +text.wave.waiting=Welle in {0} +text.waiting=Warten... +text.enemies={0} Feinde +text.enemies.single={0} Feind +text.loadimage=Bild laden +text.saveimage=Bild speichern +text.editor.badsize=[orange]Ungültige Bildabmessungen! [] Gültige Kartenabmessungen: {0} +text.editor.errorimageload=Fehler beim Laden des Bildes: [orange] {0} +text.editor.errorimagesave=Fehler beim Speichern des Bildes: [orange] {0} +text.editor.generate=Generieren +text.editor.resize=Grösse\nanpassen +text.editor.loadmap=Karte\nladen +text.editor.savemap=Karte\nspeichern +text.editor.loadimage=Bild\nladen +text.editor.saveimage=Bild\nspeichern +text.editor.unsaved=[crimson] Du hast Änderungen nicht gespeichert [] Möchtest du wirklich aufhören? +text.editor.resizemap=Grösse der Karte ändern +text.editor.mapname=Map Name +text.editor.overwrite=[accent] Warnung! Dies überschreibt eine vorhandene Map. +text.editor.selectmap=Wähle eine Map zum Laden: +text.width=Breite: +text.height=Höhe: +text.menu=Menü +text.play=Spielen +text.load=Laden +text.save=Speichern +text.settings=Einstellungen +text.tutorial=Tutorial +text.editor=Bearbeiter +text.mapeditor=Karten Bearbeiter +text.donate=Spenden +text.settings.reset=Auf Standard zurücksetzen +text.settings.controls=Steuerung +text.settings.game=Spiel +text.settings.sound=Audio +text.settings.graphics=Grafiken +text.upgrades=Verbesserungen +text.purchased=[LIME] Erstellt! +text.weapons=Waffen +text.paused=Pausiert +text.error.title=[crimson] Ein Fehler ist aufgetreten +text.error.crashtitle=EIn Fehler ist aufgetreten! +text.blocks.blockinfo=Blockinfo: +text.blocks.powercapacity=Energiekapazität +text.blocks.powershot=Energie / Schuss +text.blocks.size=Grösse +text.blocks.liquidcapacity=Flüssigkeitskapazität +text.blocks.maxitemssecond=Max Gegenstände / Sekunde +text.blocks.powerrange=Energiereichweite +text.blocks.itemcapacity=Gegenstand Kapazität +text.blocks.inputliquid=Flüssigkeiten Eingabe +text.blocks.inputitem=Eingabe Gegenstand +text.blocks.explosive=Hochexplosiv! +text.blocks.health=Lebenspunkte +text.blocks.inaccuracy=Ungenauigkeit +text.blocks.shots=Schüsse +text.blocks.inputcapacity=Eingabekapazität +text.blocks.outputcapacity=Ausgabekapazität +setting.difficulty.easy=Leicht +setting.difficulty.normal=Normal +setting.difficulty.hard=Schwer +setting.difficulty.insane=Unmöglich +setting.difficulty.purge=Auslöschung +setting.difficulty.name=Schwierigkeit +setting.screenshake.name=Bildschirm wackeln +setting.indicators.name=Feind Indikatoren +setting.effects.name=Effekte anzeigen +setting.sensitivity.name=Kontroller Empfindlichkeit +setting.saveinterval.name=Autosave Häufigkeit +setting.seconds={0} Sekunden +setting.fps.name=Zeige FPS +setting.vsync.name=VSync +setting.lasers.name=Zeige Energielaser +setting.healthbars.name=Zeige Objekt Lebensbalken +setting.musicvol.name=Musiklautstärke +setting.mutemusic.name=Musik stummschalten +setting.sfxvol.name=Audioeffekte Lautstärke +setting.mutesound.name=Audioeffekte stummschalten +map.maze.name=Labyrinth +map.fortress.name=Festung +map.sinkhole.name=Sinkloch +map.caves.name=Höhlen +map.volcano.name=Vulkan +map.caldera.name=Lavakessel +map.scorch.name=Flammen +map.desert.name=Wüste +map.island.name=Insel +map.grassland.name=Grasland +map.tundra.name=Kältesteppe +map.spiral.name=Spirale +map.tutorial.name=Tutorial +keybind.move_x.name=bewege_x +keybind.move_y.name=bewege_y +keybind.select.name=wählen +keybind.break.name=Unterbrechung +keybind.shoot.name=Schiess +keybind.zoom_hold.name=zoomen_halten +keybind.zoom.name=zoomen +keybind.menu.name=Menü +keybind.pause.name=Pause +keybind.dash.name=Bindestrich +keybind.rotate_alt.name=drehen_alt +keybind.rotate.name=Drehen +mode.waves.name=Wellen +mode.sandbox.name=Sandkasten +mode.freebuild.name=Freier Bau +item.stone.name=Stein +item.coal.name=Kohle +item.titanium.name=Titan +item.thorium.name=Uran +item.sand.name=Sand +liquid.water.name=Wasser +liquid.lava.name=Lava +liquid.oil.name=Öl +block.door.name=Tür +block.door-large.name=grosse Tür +block.conduit.name=Leitungsrohr +block.pulseconduit.name=Pulsleitungsrohr +block.liquidrouter.name=flüssigkeiten verteiler +block.conveyor.name=Förderband +block.router.name=Verteiler +block.junction.name=Kreuzung +block.liquidjunction.name=flüssigkeite Kreuzung +block.sorter.name=Sortierer +block.smelter.name=Schmelzer +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.closing=[accent]Closing server... +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.clientOutdated=Outdated client! Update your game! +text.server.kicked.serverOutdated=Outdated server! Ask the host to update! +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.server.friendlyfire=Friendly Fire +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.server.addressinuse=Address already in use! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.language.restart=Please restart your game for the language settings to take effect. +text.settings.language=Language +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.info.title=[accent]Info +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_es.properties b/core/assets/bundles/bundle_es.properties index af95e6b530..a8136b7ead 100644 --- a/core/assets/bundles/bundle_es.properties +++ b/core/assets/bundles/bundle_es.properties @@ -1,552 +1,495 @@ -text.about = Creado por [ROYAL]Anuken [] - [SKY] anukendev@gmail.com [] Originalmente una entrada en el [naranja] GDL [] Metal Monstrosity Jam. Créditos: - SFX hecho con [AMARILLO] bfxr [] - Música hecha por [VERDE] RoccoW [] / encontrado en [lime] FreeMusicArchive.org [] Agradecimientos especiales a: - [coral] MitchellFJN []: extensa prueba de juego y comentarios - [cielo] Luxray5474 []: trabajo wiki, contribuciones de código - [lime] Epowerj []: sistema de compilación de código, icono - Todos los probadores beta en itch.io y Google Play\n -text.credits = Créditos -text.discord = ¡Únete al Discord de Mindustry! -text.changes = [SCARLET] ¡Atención! [] Algunas mecánicas importantes del juego han sido cambiadas. - [acento] Los teletransportadores [] ahora usan energía. - [acento]Los talleres de fundición[] y [acento]los crisoles [] ahora tienen una capacidad máxima de artículos. - [acento] Los crisoles[] ahora requieren carbón como combustible. -text.link.discord.description = La sala oficial del discord de Mindustry -text.link.github.description = Código fuente del juego -text.link.dev-builds.description = Estados en desarrollo inestables -text.link.trello.description = Tablero trello oficial para las características planificadas -text.link.itch.io.description = itch.io és la página con descargas para PC y la versión web -text.link.google-play.description = Listado en la tienda de Google Play -text.link.wiki.description = Wiki oficial de Mindustry -text.linkfail = ¡Error al abrir el enlace!\nLa URL ha sido copiada a su portapapeles -text.editor.web = ¡La versión web no es compatible con el editor!\nDescargue el juego para usarlo. -text.multiplayer.web = ¡Esta versión del juego no admite multijugador!\nPara jugar al modo multijugador desde su navegador, use el enlace \"versión de varios jugadores\" en la página itch.io. -text.gameover = El núcleo fue destruido. -text.highscore = [YELLOW]¡Nueva mejor puntuación! -text.lasted = Duró hasta la ronda -text.level.highscore = Puntuación màs alta: [accent] -text.level.delete.title = Confirmar Eliminación -text.level.delete = ¿Seguro que quieres eliminar el mapa \"[ORANGE] \" {0}? -text.level.select = Selección de nivel -text.level.mode = Modo de juego: -text.savegame = Guardar Partida -text.loadgame = Cargar Partida -text.joingame = Unirse a una Partida -text.newgame = Nueva Partida -text.quit = Salir -text.about.button = Acerca de -text.name = Nombre -text.public = Público -text.players = {0} Jugadores en línea -text.server.player.host = {0} ANFITRIÓN -text.players.single = {0} jugador en línea -text.server.mismatch = Error de paquete: posible desajuste de la versión cliente / servidor.\n¡Asegúrate de que tú y el anfitrión tengáis la última versión de Mindustry! -text.server.closing = [accent] Cerrando servidor ... -text.server.kicked.kick = ¡Has sido expulsado del servidor! -text.server.kicked.invalidPassword = ¡Contraseña inválida! -text.server.kicked.clientOutdated = Cliente desactualizado ¡Actualiza tu juego! -text.server.kicked.serverOutdated = Servidor desactualizado ¡Pidele actualizar al anfitrión! -text.server.kicked.banned = Tu entrada está prohibida en este servidor. -text.server.kicked.recentKick = Has sido echado recientemente.\nEspera antes de conectarte de nuevo. -text.server.connected = se ha unido. -text.server.disconnected = se ha desconectado -text.nohost = ¡No se puede alojar el servidor en un mapa personalizado! -text.host.info = El botón [acento] host [] aloja un servidor en los puertos [escarlata] 6567 [] y [escarlata] 6568. [] Cualquiera en el mismo [LIGHT_GRAY] wifi o red local [] debería poder ver su servidor en su servidor lista. Si desea que las personas puedan conectarse desde cualquier lugar mediante IP, se requiere [acento] reenvío de puerto []. [LIGHT_GRAY] Nota: Si alguien tiene problemas para conectarse a su juego LAN, asegúrese de haber permitido a Mindustry el acceso a su red local en la configuración de su firewall. -text.join.info = Aquí puede ingresar un servidor [acento] IP [] para conectarse, o descubrir servidores de [acento] red local [] para conectarse. Tanto el modo multijugador LAN como WAN son compatibles. [LIGHT_GRAY] Nota: no hay una lista de servidores global automática; si desea conectarse con alguien por IP, deberá solicitar al host su IP. -text.hostserver = Hostear servidor -text.host = Hostear -text.hosting = [acento] Abriendo servidor ... -text.hosts.refresh = Refrescar -text.hosts.discovering = Descubriendo juegos en LAN -text.server.refreshing = Servidor refrescante -text.hosts.none = [lightgray] ¡No se encontraron juegos LAN! -text.host.invalid = [escarlata] No se puede conectar al host. -text.server.friendlyfire = Fuego amigo -text.trace = Rastro del jugador -text.trace.playername = Nombre del jugador: [acento] {0} -text.trace.ip = IP: [acento] {0} -text.trace.id = ID único: [acento] {0} -text.trace.android = Cliente de Android: [acento] {0} -text.trace.modclient = Cliente personalizado: [acento] {0} -text.trace.totalblocksbroken = Total de bloques rotos: [acento] {0} -text.trace.structureblocksbroken = Bloques de estructura rotos: [acento] {0} -text.trace.lastblockbroken = Último bloque roto: [acento] {0} -text.trace.totalblocksplaced = Total de bloques colocados: [acento] {0} -text.trace.lastblockplaced = Último bloque colocado: [acento] {0} -text.invalidid = ID de cliente no válido Presente un informe del error. -text.server.bans = Baneos -text.server.bans.none = ¡No se encontraron jugadores baneados! -text.server.admins = Admins -text.server.admins.none = ¡No se encontraron administradores! -text.server.add = Agregar servidor -text.server.delete = ¿Seguro que quieres eliminar este servidor? -text.server.hostname = Anfitrión: {0} -text.server.edit = Editar servidor -text.server.outdated = [crimson] ¡Servidor obsoleto! [] -text.server.outdated.client = [carmesí] Cliente desactualizado! [] -text.server.version = [lightgray] Versión: {0} -text.server.custombuild = [amarillo] Creación personalizada -text.confirmban = ¿Estás seguro de que quieres prohibir este jugador? -text.confirmunban = ¿Estás seguro de que quieres desbloquear a este jugador? -text.confirmadmin = ¿Seguro que quieres que este jugador sea un administrador? -text.confirmunadmin = ¿Seguro que quieres eliminar el estado de administrador de este reproductor? -text.joingame.byip = Unirse por IP ... -text.joingame.title = Unirse a una partida -text.joingame.ip = IP: -text.disconnect = Desconectado. -text.disconnect.data = ¡Fallo al cargar datos mundiales! -text.connecting = [accent] Conectando ... -text.connecting.data = [accent] Cargando información del mapa... -text.connectfail = [crimson] Fallo al conectar al servidor: [orange] -text.server.port = Puerto: -text.server.addressinuse = ¡Dirección ya en uso! -text.server.invalidport = ¡Número de puerto inválido! -text.server.error = [crimson] Error en la creación del servidor: [orange] -text.tutorial.back = < Anterior -text.tutorial.next = Siguiente > -text.save.new = Nuevo Guardado -text.save.overwrite = ¿Seguro que quieres sobrescribir este juego guardado? -text.overwrite = Sobreescribir -text.save.none = ¡No hay juegos guardados! -text.saveload = [accent] Guardando... -text.savefail = ¡Error al guardar el juego! -text.save.delete.confirm = ¿Estás seguro de que deseas eliminar este guardado? -text.save.delete = Borrar -text.save.export = Exportar guardado -text.save.import.invalid = [orange] ¡Este guardado es inválido! -text.save.import.fail = [crimson] Fallo al importar guardado: [orange] {0} -text.save.export.fail = [crimson] Fallo al exportar guardado: [orange] {0} -text.save.import = Importar Guardado -text.save.newslot = Nombre del guardado: -text.save.rename = Renombrar -text.save.rename.text = Nuevo nombre -text.selectslot = Seleccionar una guardado -text.slot = [accent] Casilla {0} -text.save.corrupted = [orange] ¡Arhivo de guardado corrupto o inválido! -text.empty = -text.on = Encendido -text.off = Apagado -text.save.autosave = Guardado automático: {0} -text.save.map = Mapa: {0} -text.save.wave = Horda: {0} -text.save.difficulty = Dificultad: {0} -text.save.date = Guardado por última vez: {0} -text.confirm = Confirmar -text.delete = Eliiminar -text.ok = OK -text.open = Abrir -text.cancel = Cancelar -text.openlink = Abrir enlace -text.copylink = Copiar link -text.back = Atrás -text.quit.confirm = ¿Seguro que quieres salir? -text.changelog.title = Changelog -text.changelog.loading = Obteniendo changelog ... -text.changelog.error.android = [naranja] Tenga en cuenta que el registro de cambios no funciona en Android 4.4 y versiones posteriores. Esto se debe a un error interno de Android. -text.changelog.error = [escarlata] ¡Error al obtener el registro de cambios! Comprueba tu conexión a Internet. -text.changelog.current = [amarillo] [[Versión actual] -text.changelog.latest = [naranja] [[Última versión] -text.loading = [accent] Cargando... -text.wave = [orange] Horda {0} -text.wave.waiting = Horda en {0} -text.waiting = Esperando... -text.enemies = {0} Enemigos -text.enemies.single = {0} Enemigo -text.loadimage = Cargar imagen -text.saveimage = Guardar imagen -text.oregen = Generación de mineral {0} -text.editor.badsize = [orange]¡Dimensiones de imagen inválidas![]\nDimensiones de mapa válidas: {0} -text.editor.errorimageload = Error al cargar el archivo de imagen: [orange] {0} -text.editor.errorimagesave = Error al guardar el archivo de imagen: [orange] {0} -text.editor.generate = Generar -text.editor.resize = Cambiar tamaño -text.editor.loadmap = Cargar mapa -text.editor.savemap = Guardar mapa -text.editor.loadimage = Cargar imagen -text.editor.saveimage = Guardar imagen -text.editor.unsaved = [scarlet] ¡Tienes cambios sin guardar! [] ¿Estás seguro de que quieres salir? -text.editor.brushsize = Tamaño del pincel: {0} -text.editor.noplayerspawn = ¡Este mapa no tiene punto de aparición del jugador! -text.editor.manyplayerspawns = ¡Los mapas no pueden tener más de un punto de spawn de jugador! -text.editor.manyenemyspawns = {0 }¡No puede tener más de puntos de aparición enemiga! -text.editor.resizemap = Cambiar el tamaño del mapa -text.editor.resizebig = [escarlata] ¡Advertencia! [] Los mapas de más de 256 unidades pueden ser inestables. -text.editor.mapname = Nombre del mapa -text.editor.overwrite = [acento] ¡Advertencia!\nEsto sobrescribe un mapa existente. -text.editor.failoverwrite = [carmesí] ¡No se puede sobrescribir el mapa por defecto! -text.editor.selectmap = Seleccione un mapa para cargar: -text.width = Ancho: -text.height = Altura: -text.randomize = Aleatorizar -text.apply = Aplicar -text.update = Refrescar -text.menu = Menú -text.play = Jugar -text.load = Cargar -text.save = Salvar -text.language.restart = Por favor, reinicie su juego para que la configuración de idioma surta efecto. -text.settings.language = Idioma -text.settings = Ajustes -text.tutorial = Tutorial -text.editor = Editor -text.mapeditor = Editor de Mapas -text.donate = Donar -text.settings.reset = Restablecer los valores predeterminados -text.settings.controls = Controles -text.settings.game = Juego -text.settings.sound = Sonido -text.settings.graphics = Gráficos -text.upgrades = Mejoras -text.purchased = [LIME] Creado! -text.weapons = Armas -text.paused = Pausado -text.respawn = Reapareciendo en -text.info.title = [acento] Información -text.error.title = [carmesí] Se ha producido un error -text.error.crashmessage = [SCARLET] Se ha producido un error inesperado, que habría causado un bloqueo. [] Informe las circunstancias exactas bajo las cuales se produjo este error al desarrollador: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Ha ocurrido un error -text.mode.break = Modo de eliminación: -text.mode.place = Modo de colocación: -placemode.hold.name = Línea -placemode.areadelete.name = Àrea -placemode.touchdelete.name = Toque -placemode.holddelete.name = Mantener -placemode.none.name = Ninguno -placemode.touch.name = Toque -placemode.cursor.name = Cursor -text.blocks.extrainfo = [acento] información adicional del bloque: -text.blocks.blockinfo = Información de bloque -text.blocks.powercapacity = Capacidad de energía -text.blocks.powershot = Energía/disparo -text.blocks.powersecond = energía drenada/segundo -text.blocks.powerdraindamage = Energía drenada/daño -text.blocks.shieldradius = Radio del escudo -text.blocks.itemspeedsecond = Velocidad del objeto/segundo -text.blocks.range = Rango -text.blocks.size = Tamaño -text.blocks.powerliquid = Poder/Líquido -text.blocks.maxliquidsecond = máximo líquido/segundo -text.blocks.liquidcapacity = Capacidad de liquido -text.blocks.liquidsecond = Líquido/segundo -text.blocks.damageshot = Daño/ disparo -text.blocks.ammocapacity = Capacidad de munición -text.blocks.ammo = Munición: -text.blocks.ammoitem = Munición/objeto -text.blocks.maxitemssecond = Objetos máximos/segundo -text.blocks.powerrange = Rango de energía -text.blocks.lasertilerange = Rango de poder en casilllas -text.blocks.capacity = Capacidad -text.blocks.itemcapacity = Capacidad de items -text.blocks.maxpowergenerationsecond = Máxima generación de energía/segundo -text.blocks.powergenerationsecond = Generación de energía/segundo -text.blocks.generationsecondsitem = Segundos de generación/Item -text.blocks.input = Ingreso -text.blocks.inputliquid = Entrada de líquidos -text.blocks.inputitem = Entrada de ítems -text.blocks.output = Salida -text.blocks.secondsitem = Segundos/Ítem -text.blocks.maxpowertransfersecond = Máxima transferencia de poder/segundo -text.blocks.explosive = ¡Altamente explosivo! -text.blocks.repairssecond = Reparado / segundo -text.blocks.health = Vida -text.blocks.inaccuracy = Inexactitud -text.blocks.shots = Disparos -text.blocks.shotssecond = Disparos / segundo -text.blocks.fuel = Combustible -text.blocks.fuelduration = Duración del combustible -text.blocks.maxoutputsecond = Máxima salida/segundo -text.blocks.inputcapacity = Capacidad de entrada -text.blocks.outputcapacity = Capacidad de salida -text.blocks.poweritem = Poder/Ítem -text.placemode = Modo colocar -text.breakmode = Modo romper -text.health = Salud -setting.difficulty.easy = Fácil -setting.difficulty.normal = Mormal -setting.difficulty.hard = Difícil -setting.difficulty.insane = Insano -setting.difficulty.purge = Purga -setting.difficulty.name = Dificultad: -setting.screenshake.name = Shake de pantalla -setting.smoothcam.name = Cámara lisa -setting.indicators.name = Indicador del enemigo -setting.effects.name = Mostrar efectos -setting.sensitivity.name = Sensibilidad del controlador -setting.saveinterval.name = Intervalo de autoguardado -setting.seconds = Segundos -setting.fullscreen.name = Pantalla completa -setting.multithread.name = Multithreading -setting.fps.name = Mostrar fps -setting.vsync.name = VSync -setting.lasers.name = Mostrar láseres de poder -setting.previewopacity.name = Colocando Vista Previa Opacidad -setting.healthbars.name = Mostrar barras de vida de enemigos y jugadores -setting.pixelate.name = Pixelear pantalla -setting.musicvol.name = Volumen de la música -setting.mutemusic.name = Apagar música -setting.sfxvol.name = Volumen de los efectos de sonido -setting.mutesound.name = Apagar sonidos -map.maze.name = Laberinto -map.fortress.name = Fortaleza -map.sinkhole.name = Sumidero -map.caves.name = Cuevas -map.volcano.name = Volcán -map.caldera.name = Caldera -map.scorch.name = Desierto volcánico -map.desert.name = Desierto -map.island.name = Isla -map.grassland.name = Pastizal -map.tundra.name = Tundra -map.spiral.name = Espiral -map.tutorial.name = Tutorial -tutorial.intro.text = [amarillo] Bienvenido al tutorial. [] Para comenzar, presione 'siguiente'. -tutorial.moveDesktop.text = Para moverse, use las teclas [naranja] [[WASD] []. Mantenga [naranja] shift [] para impulsar. Mantenga presionada la tecla [naranja] CTRL [] mientras usa la rueda de desplazamiento [naranja] [] para acercar o alejar la imagen. -tutorial.shoot.text = Usa el mouse para apuntar, mantén presionado [naranja] el botón izquierdo del mouse [] para disparar. Intenta practicar en el objetivo [amarillo] []. -tutorial.moveAndroid.text = Para recorrer la vista, arrastre un dedo por la pantalla. Pellizque y arrastre para acercar o alejar. -tutorial.placeSelect.text = Intente seleccionar un transportador [amarillo] [] desde el menú del bloque en la parte inferior derecha. -tutorial.placeConveyorDesktop.text = Utilice [naranja] [[rueda de desplazamiento] [] para girar la cinta transportadora hacia [naranja] hacia adelante [], luego colóquela en la ubicación [amarilla] marcada [] usando [naranja] [[botón izquierdo del mouse] []. -tutorial.placeConveyorAndroid.text = Utilice [naranja] [[girar el botón] [] para girar el transportador hacia [naranja] hacia delante [], arrástrelo con un dedo, luego colóquelo en la ubicación [amarilla] marcada [] usando [naranja] [[marca de verificación][]. -tutorial.placeConveyorAndroidInfo.text = Alternativamente, puede presionar el icono de la cruz en la parte inferior izquierda para cambiar a [naranja] [[modo táctil] [] y colocar bloques tocando en la pantalla. En modo táctil, los bloques se pueden girar con la flecha en la parte inferior izquierda. Presione [amarillo] al lado [] para probarlo. -tutorial.placeDrill.text = Ahora, seleccione y coloque un taladro de piedra [amarillo] [] en la ubicación marcada. -tutorial.blockInfo.text = Si desea obtener más información sobre un bloque, puede tocar el signo de interrogación [naranja] [] en la parte superior derecha para leer su descripción. -tutorial.deselectDesktop.text = Puede deseleccionar un bloque usando el [naranja] [[botón derecho del mouse] []. -tutorial.deselectAndroid.text = Puede deseleccionar un bloque presionando el botón [naranja] X []. -tutorial.drillPlaced.text = El taladro ahora producirá piedra [amarilla], [] la enviará al transportador y luego la moverá al núcleo [amarillo] []. -tutorial.drillInfo.text = Diferentes minerales necesitan diferentes ejercicios. La piedra requiere taladros de piedra, el hierro requiere taladros de hierro, etc. -tutorial.drillPlaced2.text = Mover elementos al núcleo los coloca en su inventario de elementos [amarillo] [], en la esquina superior izquierda. Colocar bloques utiliza elementos de tu inventario. -tutorial.moreDrills.text = Puede vincular muchos taladros y cintas transportadoras juntas, de esa manera. -tutorial.deleteBlock.text = Puede eliminar bloques haciendo clic en el botón [naranja] del botón derecho del mouse [] en el bloque que desea eliminar. Intente eliminar este transportador. -tutorial.deleteBlockAndroid.text = Puede eliminar bloques mediante [naranja] seleccionando la cruz [] en el menú [naranja] del modo de interrupción [] en la parte inferior izquierda y tocando un bloque. Intente eliminar este transportador. -tutorial.placeTurret.text = Ahora, seleccione y coloque una torreta [amarilla] [] en la ubicación marcada [amarilla] []. -tutorial.placedTurretAmmo.text = Esta torre ahora aceptará [amarillo] munición [] del transportador. Puedes ver la cantidad de munición que tiene al pasar el mouse sobre ella y verificar la barra verde [verde] []. -tutorial.turretExplanation.text = Las torretas dispararán automáticamente al enemigo más cercano al alcance, siempre que tengan suficiente munición. -tutorial.waves.text = Cada [amarillo] 60 [] segundos, una ola de [coral] enemigos [] aparecerán en lugares específicos e intentarán destruir el núcleo. -tutorial.coreDestruction.text = Tu objetivo es [amarillo] defender el núcleo []. Si se destruye el núcleo, tú [coral] pierdes el juego []. -tutorial.pausingDesktop.text = Si alguna vez necesita tomar un descanso, presione el botón de pausa [naranja] [] en el espacio superior izquierdo o [naranja] [] para detener el juego. Aún puede seleccionar y colocar bloques mientras está en pausa, pero no puede mover o disparar. -tutorial.pausingAndroid.text = Si alguna vez necesita tomarse un descanso, presione el botón de pausa [naranja] [] en la parte superior izquierda para pausar el juego. Todavía puede romper y colocar bloques mientras está en pausa. -tutorial.purchaseWeapons.text = Puedes comprar nuevas armas [amarillas] [] para tu mech abriendo el menú de actualización en la esquina inferior izquierda. -tutorial.switchWeapons.text = Cambie las armas haciendo clic en su icono en la esquina inferior izquierda o usando números [naranja] [[1-9] []. -tutorial.spawnWave.text = Aquí viene una ola ahora. Destruyelos. -tutorial.pumpDesc.text = En olas posteriores, es posible que deba usar bombas [amarillas] [] para distribuir líquidos para generadores o extractores. -tutorial.pumpPlace.text = Las bombas funcionan de manera similar a los taladros, excepto que producen líquidos en lugar de artículos. Intente colocar una bomba en el aceite designado [amarillo] []. -tutorial.conduitUse.text = Ahora coloque un conducto [naranja] [] que se aleje de la bomba. -tutorial.conduitUse2.text = Y algunos más ... -tutorial.conduitUse3.text = Y algunos más ... -tutorial.generator.text = Ahora, coloque un bloque [naranja] de generador de combustión [] al final del conducto. -tutorial.generatorExplain.text = Este generador ahora creará energía [amarilla] [] del aceite. -tutorial.lasers.text = La potencia se distribuye usando láseres de potencia [amarillos] []. Gira y coloca uno aquí. -tutorial.laserExplain.text = El generador ahora moverá energía al bloque láser. Un haz [amarillo] opaco [] significa que está transmitiendo corriente, y un haz [amarillo] transparente [] significa que no lo está. -tutorial.laserMore.text = Puedes verificar cuánta potencia tiene un bloque al pasar el mouse sobre él y verificar la barra amarilla [amarilla] [] en la parte superior. -tutorial.healingTurret.text = Este láser se puede usar para alimentar una torreta de reparación de [cal] []. Coloca uno aquí. -tutorial.healingTurretExplain.text = Mientras tenga energía, esta torreta [cal] reparará los bloques cercanos. [] ¡Al jugar, asegúrate de obtener uno en tu base lo más rápido posible! -tutorial.smeltery.text = Muchos bloques requieren [naranja] acero [] para fabricar, lo que requiere una fundición [naranja] [] para fabricar. Coloca uno aquí. -tutorial.smelterySetup.text = Esta fundición producirá acero [naranja] [] a partir de la plancha de entrada, utilizando carbón como combustible. -tutorial.tunnelExplain.text = También tenga en cuenta que los artículos pasan por un bloque de túnel [naranja] [] y salen del otro lado, pasando por el bloque de piedra. Tenga en cuenta que los túneles solo pueden atravesar hasta 2 bloques. -tutorial.end.text = ¡Y eso concluye el tutorial! ¡Buena suerte! -text.keybind.title = Vuelva a conectar las llaves -keybind.move_x.name = mover_x -keybind.move_y.name = mover_y -keybind.select.name = Elija -keybind.break.name = Romper -keybind.shoot.name = ¡Dispara! -keybind.zoom_hold.name = Enfoque_Mantener -keybind.zoom.name = Enfoquè -keybind.block_info.name = Bloque_informacion -keybind.menu.name = Menú -keybind.pause.name = Pausa -keybind.dash.name = Deslizar -keybind.chat.name = Chat -keybind.player_list.name = Jugadores_lista -keybind.console.name = Console -keybind.rotate_alt.name = Rotacion_alt -keybind.rotate.name = Girar -keybind.weapon_1.name = Arma_1 -keybind.weapon_2.name = Arma_2 -keybind.weapon_3.name = Arma_3\n -keybind.weapon_4.name = Arma_4 -keybind.weapon_5.name = Arma_5 -keybind.weapon_6.name = Arma6 -mode.text.help.title = Descripción de modos -mode.waves.name = Hordas -mode.waves.description = El modo normal. Recursos limitados y las hordas vendrán automáticamente -mode.sandbox.name = Sandbox -mode.sandbox.description = Recursos infinitos y sin temporizador para las olas. -mode.freebuild.name = Construcción libre -mode.freebuild.description = Recursos limitados y sin tiempo definido para las hordas -upgrade.standard.name = Estandar -upgrade.standard.description = El mech estándar. -upgrade.blaster.name = Blaster -upgrade.blaster.description = Dispara una bala lenta y débil. -upgrade.triblaster.name = Triblaster -upgrade.triblaster.description = Dispara 3 balas en una extensión. -upgrade.clustergun.name = Cleaster Gun -upgrade.clustergun.description = Dispara una propagación inexacta de granadas explosivas. -upgrade.beam.name = Cañòn Beam -upgrade.beam.description = Dispara un rayo láser perforante de largo alcance. -upgrade.vulcan.name = Volcán -upgrade.vulcan.description = Dispara una ráfaga de balas rapidas -upgrade.shockgun.name = Pistola Shock\n -upgrade.shockgun.description = Dispara una ráfaga devastadora de metralla cargada. -item.stone.name = Piedra -item.iron.name = Hierro -item.coal.name = Carbón -item.steel.name = Acero -item.titanium.name = Titanio -item.dirium.name = Dirio -item.uranium.name = Uranio -item.sand.name = Arena -liquid.water.name = Agua -liquid.plasma.name = Plasma -liquid.lava.name = Lava -liquid.oil.name = Aceite -block.weaponfactory.name = Fábrica de armas -block.weaponfactory.fulldescription = Se usa para crear armas para el jugador mech. Haga clic para usar. Automáticamente toma recursos del núcleo. -block.air.name = Aire -block.blockpart.name = Bloque -block.deepwater.name = Aguas profundas -block.water.name = Agua -block.lava.name = Lava -block.oil.name = Aceite -block.stone.name = Piedra -block.blackstone.name = Piedra Negra -block.iron.name = Hierro -block.coal.name = Carbón -block.titanium.name = Titanio -block.uranium.name = Uranio -block.dirt.name = Sucio -block.sand.name = Arena -block.ice.name = Hielo -block.snow.name = Nieve -block.grass.name = Césped -block.sandblock.name = Bloque de arena -block.snowblock.name = Bloque de nieve -block.stoneblock.name = Bloque de piedra -block.blackstoneblock.name = Bloque de piedra negra -block.grassblock.name = Bloque de césped -block.mossblock.name = Bloque de musgo -block.shrub.name = Arbusto -block.rock.name = Piedra -block.icerock.name = Piedra congelada -block.blackrock.name = Roca Negra -block.dirtblock.name = Bloque de suciedad -block.stonewall.name = Pared de piedra -block.stonewall.fulldescription = Un bloque defensivo barato. Útil para proteger el núcleo y las torrecillas en las primeras olas. -block.ironwall.name = Muro de hierro -block.ironwall.fulldescription = Un bloque defensivo básico. Proporciona protección de los enemigos. -block.steelwall.name = Muro de acero -block.steelwall.fulldescription = Un bloque defensivo estándar. protección adecuada de los enemigos. -block.titaniumwall.name = Muro de titanio -block.titaniumwall.fulldescription = Un fuerte bloqueo defensivo. Proporciona protección de los enemigos. -block.duriumwall.name = Muro de Dirio -block.duriumwall.fulldescription = Un bloque defensivo muy fuerte. Proporciona protección de los enemigos. -block.compositewall.name = Muro compuesto -block.steelwall-large.name = Pared de acero grande -block.steelwall-large.fulldescription = Un bloque defensivo estándar. Se extiende por múltiples mosaicos. -block.titaniumwall-large.name = Gran pared de titanio -block.titaniumwall-large.fulldescription = Un fuerte bloqueo defensivo. Se extiende por múltiples mosaicos. -block.duriumwall-large.name = Gran pared de dirio -block.duriumwall-large.fulldescription = Un bloque defensivo muy fuerte. Se extiende por múltiples mosaicos. -block.titaniumshieldwall.name = Muro blindado -block.titaniumshieldwall.fulldescription = Un fuerte bloque defensivo, con un escudo extra incorporado. Requiere poder Usa energía para absorber las balas enemigas. Se recomienda utilizar potenciadores de potencia para proporcionar energía a este bloque. -block.repairturret.name = Torreta de reparación -block.repairturret.fulldescription = Repara los bloques dañados cercanos en el rango a una velocidad lenta. Utiliza pequeñas cantidades de energía. -block.megarepairturret.name = Torreta de reparación II -block.megarepairturret.fulldescription = Repara los bloques dañados cercanos en el rango a una tasa decente. Utiliza el poder -block.shieldgenerator.name = Generador de escudo -block.shieldgenerator.fulldescription = Un bloque defensivo avanzado. Protege todos los bloques en un radio del ataque. Utiliza potencia a un ritmo lento cuando está inactivo, pero consume energía rápidamente al contacto con balas. -block.door.name = Puerta -block.door.fulldescription = Un bloque que se puede abrir y cerrar tocando. -block.door-large.name = Puerta grande -block.door-large.fulldescription = Un bloque que se puede abrir y cerrar tocando. -block.conduit.name = Conducto -block.conduit.fulldescription = Bloque de transporte de líquido básico. Funciona como un transportador, pero con líquidos. Se usa mejor con bombas u otros conductos. Se puede usar como puente sobre líquidos para enemigos y jugadores. -block.pulseconduit.name = Conducto de pulso -block.pulseconduit.fulldescription = Bloque de transporte de líquidos avanzado. Transporta líquidos más rápido y almacena más que los conductos estándar. -block.liquidrouter.name = Enrutador líquido -block.liquidrouter.fulldescription = Funciona de manera similar a un enrutador. Acepta entrada de líquido desde un lado y lo envía a los otros lados. Útil para dividir el líquido de un solo conducto en otros múltiples conductos. -block.conveyor.name = Transportador -block.conveyor.fulldescription = Bloque de transporte de elementos básicos. Mueve los artículos hacia adelante y los deposita automáticamente en torretas o crafters. Giratorio. Se puede usar como puente sobre líquidos para enemigos y jugadores. -block.steelconveyor.name = Transportador de acero -block.steelconveyor.fulldescription = Bloque de transporte de elementos avanzados. Mueve los artículos más rápido que los transportadores estándar. -block.poweredconveyor.name = Transportador de pulso -block.poweredconveyor.fulldescription = El último bloque de transporte de elementos. Mueve los artículos más rápido que los transportadores de acero. -block.router.name = Enrutador -block.router.fulldescription = Acepta elementos de una dirección y los envía a otras 3 direcciones. También puede almacenar una cierta cantidad de artículos. Útil para dividir los materiales de un taladro en múltiples torretas. -block.junction.name = Union -block.junction.fulldescription = Actúa como un puente para cruzar dos cintas transportadoras. Útil en situaciones con dos transportadores diferentes que llevan diferentes materiales a diferentes ubicaciones. -block.conveyortunnel.name = Túnel transportador -block.conveyortunnel.fulldescription = Transporta el artículo debajo de bloques. Para usarlo, coloque un túnel que conduce al bloque por el que se colocará el túnel, y otro del otro lado. Asegúrate de que ambos túneles estén orientados en direcciones opuestas, que se dirigen hacia los bloques a los que están ingresando o enviando. -block.liquidjunction.name = Unión líquida -block.liquidjunction.fulldescription = Actúa como un puente para dos conductos que cruzan. Útil en situaciones con dos conductos diferentes que llevan diferentes líquidos a diferentes lugares. -block.liquiditemjunction.name = Unión líquidos-elementos -block.liquiditemjunction.fulldescription = Actúa como un puente para cruzar conductos y transportadores. -block.powerbooster.name = Ampliación de potencia -block.powerbooster.fulldescription = Distribuye energía a todos los bloques dentro de su radio. -block.powerlaser.name = Láser de potencia -block.powerlaser.fulldescription = Crea un láser que transmite potencia al bloque que está delante de él. No genera ningún poder en sí mismo. Se usa mejor con generadores u otros láseres. -block.powerlaserrouter.name = Enrutador láser -block.powerlaserrouter.fulldescription = Láser que distribuye la potencia en tres direcciones a la vez. Útil en situaciones donde se requiere para alimentar múltiples bloques de un generador. -block.powerlasercorner.name = Laser esquinero -block.powerlasercorner.fulldescription = Láser que distribuye la potencia en dos direcciones a la vez. Útil en situaciones donde se requiere alimentar múltiples bloques desde un generador, y un enrutador es impreciso. -block.teleporter.name = Teletransportador -block.teleporter.fulldescription = Bloque de transporte de elementos avanzados. Los teleportadores ingresan elementos a otros teletransportadores del mismo color. No hace nada si no existen teletransportadores del mismo color. Si existen múltiples teleportadores del mismo color, se selecciona uno aleatorio. Utiliza el poder Toca para cambiar el color. -block.sorter.name = Clasificador -block.sorter.fulldescription = Ordena el artículo por tipo de material. El material a aceptar se indica por el color en el bloque. Todos los elementos que coinciden con el material de clasificación se envían hacia adelante, todo lo demás se envía a la izquierda y a la derecha. -block.core.name = Nùcleo -block.pump.name = Bomba -block.pump.fulldescription = Bombea líquidos de un bloque fuente, generalmente agua, lava o aceite. Emite líquido en los conductos cercanos. -block.fluxpump.name = Bomba de flujo -block.fluxpump.fulldescription = Una versión avanzada de la bomba. Almacena más líquido y bombea líquido más rápido. -block.smelter.name = horno de fundición -block.smelter.fulldescription = El bloque de elaboración esencial. Cuando se ingresa 1 hierro y 1 carbón como combustible, se emite un acero. Se aconseja ingresar hierro y carbón en diferentes bandas para evitar obstrucciones. -block.crucible.name = Crisol -block.crucible.fulldescription = Un bloque de elaboración avanzada. Cuando se ingresa 1 titanio, 1 acero y 1 carbón como combustible, se emite un dirium. Se aconseja ingresar carbón, acero y titanio en diferentes bandas para evitar obstrucciones. -block.coalpurifier.name = Extractor de carbón -block.coalpurifier.fulldescription = Un bloque extractor básico. Salidas de carbón cuando se suministra con grandes cantidades de agua y piedra. -block.titaniumpurifier.name = extractor de titanio -block.titaniumpurifier.fulldescription = Un bloque extractor estándar. Salidas de titanio cuando se suministra con grandes cantidades de agua y hierro. -block.oilrefinery.name = Refinería de petróleo -block.oilrefinery.fulldescription = Refina grandes cantidades de aceite en artículos de carbón. Útil para alimentar torretas a base de carbón cuando las vetas de carbón son escasas. -block.stoneformer.name = Piedra antigua -block.stoneformer.fulldescription = Se vende lava líquida en piedra. Útil para producir cantidades masivas de piedra para purificadores de carbón. -block.lavasmelter.name = Fundición de lava -block.lavasmelter.fulldescription = Utiliza lava para convertir el hierro en acero. Una alternativa a las fundiciones. Útil en situaciones donde el carbón es escaso. -block.stonedrill.name = Perforadora de piedra -block.stonedrill.fulldescription = El taladro esencial. Cuando se coloca en baldosas de piedra, las impresiones de piedra a un ritmo lento de forma indefinida. -block.irondrill.name = Perforadora de hierro -block.irondrill.fulldescription = Un ejercicio básico. Cuando se coloca sobre baldosas de mineral de hierro, emite hierro a un ritmo lento indefinidamente. -block.coaldrill.name = Perforadora de carbón -block.coaldrill.fulldescription = Un ejercicio básico. Cuando se coloca en las baldosas de mineral de carbón, emite carbón a un ritmo lento indefinidamente. -block.uraniumdrill.name = Taladro de uranio -block.uraniumdrill.fulldescription = Un taladro avanzado. Cuando se coloca en placas de mineral de uranio, emite uranio a un ritmo lento de forma indefinida. -block.titaniumdrill.name = Taladro de titanio -block.titaniumdrill.fulldescription = Un taladro avanzado. Cuando se coloca en baldosas de mineral de titanio, emite titanio a un ritmo lento indefinidamente. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = El último ejercicio Va a extraer cualquier mineral que se coloca a un ritmo rápido. -block.coalgenerator.name = Generador de carbón -block.coalgenerator.fulldescription = El generador esencial. Genera energía del carbón Emite energía como láser en sus 4 lados. -block.thermalgenerator.name = Generador térmico -block.thermalgenerator.fulldescription = Genera energía de la lava Emite energía como láser en sus 4 lados. -block.combustiongenerator.name = Generador de combustión -block.combustiongenerator.fulldescription = Genera energía del petróleo. Emite energía como láser en sus 4 lados. -block.rtgenerator.name = Generador de RTG -block.rtgenerator.fulldescription = Genera pequeñas cantidades de energía a partir de la desintegración radiactiva del uranio. Emite energía como láser en sus 4 lados. -block.nuclearreactor.name = Reactor nuclear -block.nuclearreactor.fulldescription = Una versión avanzada del generador RTG y el último generador de energía. Genera energía del uranio. Requiere enfriamiento constante de agua. Altamente volátil; explotará violentamente si se suministran cantidades insuficientes de refrigerante. -block.turret.name = Torre -block.turret.fulldescription = Una torrecilla básica y barata. Utiliza piedra para munición. Tiene un poco más de alcance que la torre doble. -block.doubleturret.name = Doble torreta -block.doubleturret.fulldescription = Una versión un poco más poderosa de la torreta. Utiliza piedra para munición. Hace mucho más daño, pero tiene un rango menor. Dispara dos balas. -block.machineturret.name = Torreta de gatling -block.machineturret.fulldescription = Una torreta versátil estándar. Utiliza hierro para munición. Tiene una velocidad de disparo rápida con un daño decente. -block.shotgunturret.name = Torreta divisoria -block.shotgunturret.fulldescription = Una torreta estándar. Utiliza hierro para munición. Dispara una extensión de 7 balas. Alcance más bajo, pero mayor producción de daños que la torreta de gatling. -block.flameturret.name = Torreta de fuego -block.flameturret.fulldescription = Torreta de corto alcance avanzada. Utiliza carbón para munición. Tiene un rango muy bajo, pero un daño muy alto. Bueno para cuartos cercanos. Recomendado para ser utilizado detrás de las paredes. -block.sniperturret.name = Torreta railgun -block.sniperturret.fulldescription = Torreta de largo alcance avanzada. Utiliza acero para munición. Daño muy alto, pero bajo índice de fuego. Es caro de usar, pero puede colocarse lejos de las líneas enemigas debido a su alcance. -block.mortarturret.name = Torreta antiaérea -block.mortarturret.fulldescription = Torreta avanzada de baja salpicadura de daños por salpicadura. Utiliza carbón para munición. Dispara un aluvión de balas que explotan en metralla. Útil para grandes multitudes de enemigos. -block.laserturret.name = Torreta láser -block.laserturret.fulldescription = Torreta de un solo objetivo avanzado. Utiliza el energia. Buena torre de medio alcance. Objetivo único. Nunca falla -block.waveturret.name = Torreta tesla -block.waveturret.fulldescription = Torreta multi-objetivo avanzada. Utiliza el poder Rango medio. Nunca falla. De Medio a bajo daño, pero puede golpear a varios enemigos simultáneamente con rayos en cadena. -block.plasmaturret.name = Torreta de plasma -block.plasmaturret.fulldescription = Versión altamente avanzada de la torreta de fuego. Utiliza carbón como munición. Daño muy alto, rango bajo a medio. -block.chainturret.name = Torreta de cadena -block.chainturret.fulldescription = La torreta de fuego rápido suprema. Usa uranio como munición. Dispara babosas grandes a una alta cadencia. Rango medio. Se extiende por múltiples bloques. Extremadamente duradero. -block.titancannon.name = Cañón titán -block.titancannon.fulldescription = La torreta de largo alcance suprema. Usa uranio como munición. Dispara grandes proyectiles con daño de área a una cadencia media. De largo alcance. Se extiende por múltiples bloques. Extremadamente duradero. -block.playerspawn.name = Punto de aparición del jugador -block.enemyspawn.name = Generador de enemigos +text.about=Creado por [ROYAL]Anuken [] - [SKY] anukendev@gmail.com [] Originalmente una entrada en el [naranja] GDL [] Metal Monstrosity Jam. Créditos: - SFX hecho con [AMARILLO] bfxr [] - Música hecha por [VERDE] RoccoW [] / encontrado en [lime] FreeMusicArchive.org [] Agradecimientos especiales a: - [coral] MitchellFJN []: extensa prueba de juego y comentarios - [cielo] Luxray5474 []: trabajo wiki, contribuciones de código - [lime] Epowerj []: sistema de compilación de código, icono - Todos los probadores beta en itch.io y Google Play\n +text.credits=Créditos +text.discord=¡Únete al Discord de Mindustry! +text.link.discord.description=La sala oficial del discord de Mindustry +text.link.github.description=Código fuente del juego +text.link.dev-builds.description=Estados en desarrollo inestables +text.link.trello.description=Tablero trello oficial para las características planificadas +text.link.itch.io.description=itch.io és la página con descargas para PC y la versión web +text.link.google-play.description=Listado en la tienda de Google Play +text.link.wiki.description=Wiki oficial de Mindustry +text.linkfail=¡Error al abrir el enlace!\nLa URL ha sido copiada a su portapapeles +text.editor.web=¡La versión web no es compatible con el editor!\nDescargue el juego para usarlo. +text.multiplayer.web=¡Esta versión del juego no admite multijugador!\nPara jugar al modo multijugador desde su navegador, use el enlace "versión de varios jugadores" en la página itch.io. +text.gameover=El núcleo fue destruido. +text.highscore=[YELLOW]¡Nueva mejor puntuación! +text.lasted=Duró hasta la ronda +text.level.highscore=Puntuación màs alta: [accent] +text.level.delete.title=Confirmar Eliminación +text.level.select=Selección de nivel +text.level.mode=Modo de juego: +text.savegame=Guardar Partida +text.loadgame=Cargar Partida +text.joingame=Unirse a una Partida +text.newgame=Nueva Partida +text.quit=Salir +text.about.button=Acerca de +text.name=Nombre +text.players={0} Jugadores en línea +text.players.single={0} jugador en línea +text.server.mismatch=Error de paquete: posible desajuste de la versión cliente / servidor.\n¡Asegúrate de que tú y el anfitrión tengáis la última versión de Mindustry! +text.server.closing=[accent] Cerrando servidor ... +text.server.kicked.kick=¡Has sido expulsado del servidor! +text.server.kicked.invalidPassword=¡Contraseña inválida! +text.server.kicked.clientOutdated=Cliente desactualizado ¡Actualiza tu juego! +text.server.kicked.serverOutdated=Servidor desactualizado ¡Pidele actualizar al anfitrión! +text.server.kicked.banned=Tu entrada está prohibida en este servidor. +text.server.kicked.recentKick=Has sido echado recientemente.\nEspera antes de conectarte de nuevo. +text.server.connected=se ha unido. +text.server.disconnected=se ha desconectado +text.nohost=¡No se puede alojar el servidor en un mapa personalizado! +text.host.info=El botón [acento] host [] aloja un servidor en los puertos [escarlata] 6567 [] y [escarlata] 6568. [] Cualquiera en el mismo [LIGHT_GRAY] wifi o red local [] debería poder ver su servidor en su servidor lista. Si desea que las personas puedan conectarse desde cualquier lugar mediante IP, se requiere [acento] reenvío de puerto []. [LIGHT_GRAY] Nota: Si alguien tiene problemas para conectarse a su juego LAN, asegúrese de haber permitido a Mindustry el acceso a su red local en la configuración de su firewall. +text.join.info=Aquí puede ingresar un servidor [acento] IP [] para conectarse, o descubrir servidores de [acento] red local [] para conectarse. Tanto el modo multijugador LAN como WAN son compatibles. [LIGHT_GRAY] Nota: no hay una lista de servidores global automática; si desea conectarse con alguien por IP, deberá solicitar al host su IP. +text.hostserver=Hostear servidor +text.host=Hostear +text.hosting=[acento] Abriendo servidor ... +text.hosts.refresh=Refrescar +text.hosts.discovering=Descubriendo juegos en LAN +text.server.refreshing=Servidor refrescante +text.hosts.none=[lightgray] ¡No se encontraron juegos LAN! +text.host.invalid=[escarlata] No se puede conectar al host. +text.server.friendlyfire=Fuego amigo +text.trace=Rastro del jugador +text.trace.playername=Nombre del jugador: [acento] {0} +text.trace.ip=IP: [acento] {0} +text.trace.id=ID único: [acento] {0} +text.trace.android=Cliente de Android: [acento] {0} +text.trace.modclient=Cliente personalizado: [acento] {0} +text.trace.totalblocksbroken=Total de bloques rotos: [acento] {0} +text.trace.structureblocksbroken=Bloques de estructura rotos: [acento] {0} +text.trace.lastblockbroken=Último bloque roto: [acento] {0} +text.trace.totalblocksplaced=Total de bloques colocados: [acento] {0} +text.trace.lastblockplaced=Último bloque colocado: [acento] {0} +text.invalidid=ID de cliente no válido Presente un informe del error. +text.server.bans=Baneos +text.server.bans.none=¡No se encontraron jugadores baneados! +text.server.admins=Admins +text.server.admins.none=¡No se encontraron administradores! +text.server.add=Agregar servidor +text.server.delete=¿Seguro que quieres eliminar este servidor? +text.server.hostname=Anfitrión: {0} +text.server.edit=Editar servidor +text.server.outdated=[crimson] ¡Servidor obsoleto! [] +text.server.outdated.client=[carmesí] Cliente desactualizado! [] +text.server.version=[lightgray] Versión: {0} +text.server.custombuild=[amarillo] Creación personalizada +text.confirmban=¿Estás seguro de que quieres prohibir este jugador? +text.confirmunban=¿Estás seguro de que quieres desbloquear a este jugador? +text.confirmadmin=¿Seguro que quieres que este jugador sea un administrador? +text.confirmunadmin=¿Seguro que quieres eliminar el estado de administrador de este reproductor? +text.joingame.title=Unirse a una partida +text.joingame.ip=IP: +text.disconnect=Desconectado. +text.disconnect.data=¡Fallo al cargar datos mundiales! +text.connecting=[accent] Conectando ... +text.connecting.data=[accent] Cargando información del mapa... +text.connectfail=[crimson] Fallo al conectar al servidor: [orange] +text.server.port=Puerto: +text.server.addressinuse=¡Dirección ya en uso! +text.server.invalidport=¡Número de puerto inválido! +text.server.error=[crimson] Error en la creación del servidor: [orange] +text.save.new=Nuevo Guardado +text.save.overwrite=¿Seguro que quieres sobrescribir este juego guardado? +text.overwrite=Sobreescribir +text.save.none=¡No hay juegos guardados! +text.saveload=[accent] Guardando... +text.savefail=¡Error al guardar el juego! +text.save.delete.confirm=¿Estás seguro de que deseas eliminar este guardado? +text.save.delete=Borrar +text.save.export=Exportar guardado +text.save.import.invalid=[orange] ¡Este guardado es inválido! +text.save.import.fail=[crimson] Fallo al importar guardado: [orange] {0} +text.save.export.fail=[crimson] Fallo al exportar guardado: [orange] {0} +text.save.import=Importar Guardado +text.save.newslot=Nombre del guardado: +text.save.rename=Renombrar +text.save.rename.text=Nuevo nombre +text.selectslot=Seleccionar una guardado +text.slot=[accent] Casilla {0} +text.save.corrupted=[orange] ¡Arhivo de guardado corrupto o inválido! +text.empty= +text.on=Encendido +text.off=Apagado +text.save.autosave=Guardado automático: {0} +text.save.map=Mapa: {0} +text.save.wave=Horda: {0} +text.save.difficulty=Dificultad: {0} +text.save.date=Guardado por última vez: {0} +text.confirm=Confirmar +text.delete=Eliiminar +text.ok=OK +text.open=Abrir +text.cancel=Cancelar +text.openlink=Abrir enlace +text.copylink=Copiar link +text.back=Atrás +text.quit.confirm=¿Seguro que quieres salir? +text.changelog.title=Changelog +text.changelog.loading=Obteniendo changelog ... +text.changelog.error.android=[naranja] Tenga en cuenta que el registro de cambios no funciona en Android 4.4 y versiones posteriores. Esto se debe a un error interno de Android. +text.changelog.error=[escarlata] ¡Error al obtener el registro de cambios! Comprueba tu conexión a Internet. +text.changelog.current=[amarillo] [[Versión actual] +text.changelog.latest=[naranja] [[Última versión] +text.loading=[accent] Cargando... +text.wave=[orange] Horda {0} +text.wave.waiting=Horda en {0} +text.waiting=Esperando... +text.enemies={0} Enemigos +text.enemies.single={0} Enemigo +text.loadimage=Cargar imagen +text.saveimage=Guardar imagen +text.editor.badsize=[orange]¡Dimensiones de imagen inválidas![]\nDimensiones de mapa válidas: {0} +text.editor.errorimageload=Error al cargar el archivo de imagen: [orange] {0} +text.editor.errorimagesave=Error al guardar el archivo de imagen: [orange] {0} +text.editor.generate=Generar +text.editor.resize=Cambiar tamaño +text.editor.loadmap=Cargar mapa +text.editor.savemap=Guardar mapa +text.editor.loadimage=Cargar imagen +text.editor.saveimage=Guardar imagen +text.editor.unsaved=[scarlet] ¡Tienes cambios sin guardar! [] ¿Estás seguro de que quieres salir? +text.editor.resizemap=Cambiar el tamaño del mapa +text.editor.mapname=Nombre del mapa +text.editor.overwrite=[acento] ¡Advertencia!\nEsto sobrescribe un mapa existente. +text.editor.selectmap=Seleccione un mapa para cargar: +text.width=Ancho: +text.height=Altura: +text.menu=Menú +text.play=Jugar +text.load=Cargar +text.save=Salvar +text.language.restart=Por favor, reinicie su juego para que la configuración de idioma surta efecto. +text.settings.language=Idioma +text.settings=Ajustes +text.tutorial=Tutorial +text.editor=Editor +text.mapeditor=Editor de Mapas +text.donate=Donar +text.settings.reset=Restablecer los valores predeterminados +text.settings.controls=Controles +text.settings.game=Juego +text.settings.sound=Sonido +text.settings.graphics=Gráficos +text.upgrades=Mejoras +text.purchased=[LIME] Creado! +text.weapons=Armas +text.paused=Pausado +text.info.title=[acento] Información +text.error.title=[carmesí] Se ha producido un error +text.error.crashtitle=Ha ocurrido un error +text.blocks.blockinfo=Información de bloque +text.blocks.powercapacity=Capacidad de energía +text.blocks.powershot=Energía/disparo +text.blocks.size=Tamaño +text.blocks.liquidcapacity=Capacidad de liquido +text.blocks.maxitemssecond=Objetos máximos/segundo +text.blocks.powerrange=Rango de energía +text.blocks.itemcapacity=Capacidad de items +text.blocks.inputliquid=Entrada de líquidos +text.blocks.inputitem=Entrada de ítems +text.blocks.explosive=¡Altamente explosivo! +text.blocks.health=Vida +text.blocks.inaccuracy=Inexactitud +text.blocks.shots=Disparos +text.blocks.inputcapacity=Capacidad de entrada +text.blocks.outputcapacity=Capacidad de salida +setting.difficulty.easy=Fácil +setting.difficulty.normal=Mormal +setting.difficulty.hard=Difícil +setting.difficulty.insane=Insano +setting.difficulty.purge=Purga +setting.difficulty.name=Dificultad: +setting.screenshake.name=Shake de pantalla +setting.indicators.name=Indicador del enemigo +setting.effects.name=Mostrar efectos +setting.sensitivity.name=Sensibilidad del controlador +setting.saveinterval.name=Intervalo de autoguardado +setting.seconds=Segundos +setting.fullscreen.name=Pantalla completa +setting.multithread.name=Multithreading +setting.fps.name=Mostrar fps +setting.vsync.name=VSync +setting.lasers.name=Mostrar láseres de poder +setting.healthbars.name=Mostrar barras de vida de enemigos y jugadores +setting.musicvol.name=Volumen de la música +setting.mutemusic.name=Apagar música +setting.sfxvol.name=Volumen de los efectos de sonido +setting.mutesound.name=Apagar sonidos +map.maze.name=Laberinto +map.fortress.name=Fortaleza +map.sinkhole.name=Sumidero +map.caves.name=Cuevas +map.volcano.name=Volcán +map.caldera.name=Caldera +map.scorch.name=Desierto volcánico +map.desert.name=Desierto +map.island.name=Isla +map.grassland.name=Pastizal +map.tundra.name=Tundra +map.spiral.name=Espiral +map.tutorial.name=Tutorial +text.keybind.title=Vuelva a conectar las llaves +keybind.move_x.name=mover_x +keybind.move_y.name=mover_y +keybind.select.name=Elija +keybind.break.name=Romper +keybind.shoot.name=¡Dispara! +keybind.zoom_hold.name=Enfoque_Mantener +keybind.zoom.name=Enfoquè +keybind.block_info.name=Bloque_informacion +keybind.menu.name=Menú +keybind.pause.name=Pausa +keybind.dash.name=Deslizar +keybind.chat.name=Chat +keybind.player_list.name=Jugadores_lista +keybind.console.name=Console +keybind.rotate_alt.name=Rotacion_alt +keybind.rotate.name=Girar +mode.text.help.title=Descripción de modos +mode.waves.name=Hordas +mode.waves.description=El modo normal. Recursos limitados y las hordas vendrán automáticamente +mode.sandbox.name=Sandbox +mode.sandbox.description=Recursos infinitos y sin temporizador para las olas. +mode.freebuild.name=Construcción libre +mode.freebuild.description=Recursos limitados y sin tiempo definido para las hordas +item.stone.name=Piedra +item.coal.name=Carbón +item.titanium.name=Titanio +item.sand.name=Arena +liquid.water.name=Agua +liquid.lava.name=Lava +liquid.oil.name=Aceite +block.door.name=Puerta +block.door-large.name=Puerta grande +block.conduit.name=Conducto +block.pulseconduit.name=Conducto de pulso +block.liquidrouter.name=Enrutador líquido +block.conveyor.name=Transportador +block.router.name=Enrutador +block.junction.name=Union +block.liquidjunction.name=Unión líquida +block.sorter.name=Clasificador +block.smelter.name=horno de fundición +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_fr.properties b/core/assets/bundles/bundle_fr.properties index 6c8a007520..4d29faf7f7 100644 --- a/core/assets/bundles/bundle_fr.properties +++ b/core/assets/bundles/bundle_fr.properties @@ -1,528 +1,495 @@ -text.about = Créé par [ROYAL]Anuken.[]\nA l'origine une entrée dans le [orange]GDL[] MM Jam.\n\nCrédits: \n- SFX réalisé avec [yellow]bfxr[] \n- Musique faite par [lime]RoccoW[] / trouvé sur [lime]FreeMusicArchive.org[] \n\nRemerciements particuliers à:\n- [coral]MitchellFJN[]: nombreux tests et retours d'expérience \n- [sky]Luxray5474[]: travail wiki, contributions de code \n- [lime]Epowerj[]: système de compilation de code, icône \n- Tous les beta testeurs sont sur itch.io et Google Play\n -text.discord = Rejoignez le discord de Mindustry -text.changes = [SCARLET]Attention![] \nCertains mécanismes importants du jeu ont ete modifies.\n-Les [accent]telporteurs[] utilisent maintenant de l'énergie.\n-Les [accent]fonderies[] et les[accent]crucibles[] ont maintenant une capacité maximale d'articles.\n-Les [accent]crucibles[] exigent maintenant du charbon comme combustible. -text.gameover = Le noyau a été détruit. -text.highscore = [YELLOW]Nouveau meilleur score! -text.lasted = Vous avez duré jusqu'à la vague -text.level.highscore = Meilleur score: [accent]{0} -text.level.delete.title = Confirmer -text.level.delete = Êtes-vous sûr de vouloir supprimer la carte \"[orange] \"? -text.level.select = Sélection de niveau -text.level.mode = Mode de jeu : -text.savegame = Sauvegarder la partie -text.loadgame = Charger la partie -text.joingame = Rejoindre la partie -text.quit = Quitter -text.about.button = À propos -text.name = Nom : -text.public = Publique -text.players = joueurs en ligne -text.server.player.host = Héberger -text.players.single = joueur en ligne -text.server.mismatch = Erreur de paquet: possible incompatibilité de version client/serveur. Assurez-vous que vous et l'hôte avez la dernière version de Mindustry! -text.server.closing = [accent]Fermeture du serveur ... -text.server.kicked.kick = Vous avez été expulsé du serveur! -text.server.kicked.invalidPassword = Mot de passe non valide ! -text.server.kicked.clientOutdated = Client dépassé! Mettez à jour votre jeu! -text.server.kicked.serverOutdated = Serveur dépassé! Demandez à l'hôte de le mettre à jour! -text.server.kicked.banned = Vous êtes banni sur ce serveur. -text.server.connected = a rejoint le serveur -text.server.disconnected = {0} s'est déconnecté. -text.nohost = Impossible d'héberger le serveur sur une carte personnalisée! -text.hostserver = Héberger un serveur -text.host = Héberger -text.hosting = [accent]Ouverture du serveur ... -text.hosts.refresh = Actualiser -text.hosts.discovering = Recherche de parties LAN -text.server.refreshing = Actualisation du serveur -text.hosts.none = [lightgray]Aucun jeu LAN trouvé! -text.host.invalid = [scarlet]Impossible de se connecter à l'hôte. -text.server.friendlyfire = Tir allié -text.trace = suivre le joueur -text.trace.playername = Nom du joueur: [accent] {0} -text.trace.ip = IP: [accent] {0} -text.trace.id = ID unique: [accent] {0} -text.trace.android = Client Android: [accent] {0} -text.trace.modclient = Client personnalisé: [accent] {0} -text.trace.totalblocksbroken = Total des blocs détruits: [accent] {0} -text.trace.structureblocksbroken = Blocs de structure détruits: [accent] {0} -text.trace.lastblockbroken = Dernier bloc détruit: [accent] {0} -text.trace.totalblocksplaced = Nombre total de blocs placés: [accent] {0} -text.trace.lastblockplaced = Dernier bloc placé: [accent] {0} -text.invalidid = ID client invalide! Soumettre un rapport de bug -text.server.bans = Interdictions -text.server.bans.none = Aucun joueur banni trouvé! -text.server.admins = Administrateurs -text.server.admins.none = Aucun administrateur trouvé! -text.server.add = Ajouter un serveur -text.server.delete = Êtes-vous sûr de vouloir supprimer ce serveur? -text.server.hostname = Héberger -text.server.edit = éditer le serveur -text.server.outdated = [crimson]Serveur obsolète![] -text.server.outdated.client = [Crimson]Client obsolète![] -text.server.version = [lightgray]Version: {0} -text.server.custombuild = [jaune]Construction personnalisée -text.confirmban = Êtes-vous sûr de vouloir bannir ce joueur? -text.confirmunban = Êtes-vous sûr de vouloir annuler le ban de ce joueur? -text.confirmadmin = Êtes-vous sûr de vouloir faire de ce joueur un administrateur? -text.confirmunadmin = Êtes-vous sûr de vouloir supprimer le statut d'administrateur de ce joueur? -text.joingame.byip = Rejoindre par IP ... -text.joingame.title = Rejoindre une partie -text.joingame.ip = IP : -text.disconnect = Déconnecté -text.connecting = [accent]Connexion ... -text.connecting.data = [accent] Chargement des données de la partie ... -text.connectfail = [crimson] Échec de la connexion au serveur : [orange] -text.server.port = Port : -text.server.addressinuse = Adresse déjà utilisée! -text.server.invalidport = Numéro de port incorrect. -text.server.error = [crimson]Erreur lors de l'hébergement du serveur: [orange] {0} -text.tutorial.back = < Précédent\n -text.tutorial.next = Suivant> -text.save.new = Nouvelle sauvegarde -text.save.overwrite = Êtes-vous sûr de vouloir remplacer cette sauvegarde? -text.overwrite = Écraser -text.save.none = Aucune sauvegarde trouvée! -text.saveload = [accent]Sauvegarde ... -text.savefail = Échec de la sauvegarde du jeu ! -text.save.delete.confirm = Êtes-vous sûr de vouloir supprimer cette sauvegarde? -text.save.delete = Supprimer -text.save.export = Exporter la sauvegarde -text.save.import.invalid = [orange]Cette sauvegarde est invalide! -text.save.import.fail = [crimson]Echec de l'importation de la sauvegarde: [orange] {0} -text.save.export.fail = [crimson]Échec de l'exportation de la sauvegarde: [orange] {0} -text.save.import = Importer la sauvegarde -text.save.newslot = Enregistrer le nom: -text.save.rename = Renommer -text.save.rename.text = Nouveau nom: -text.selectslot = Sélectionnez une sauvegarde. -text.slot = [accent]Emplacement {0} -text.save.corrupted = [orange]Le fichier enregistrer est corrompu ou invalide! -text.empty = -text.on = Allumer -text.off = Eteint -text.save.autosave = Sauvegarde automatique -text.save.map = Carte -text.save.wave = Vague : -text.save.difficulty = DIFFICULTÉ -text.save.date = Dernière sauvegarde: {0} -text.confirm = Confirmer -text.delete = Supprimer -text.ok = OK -text.open = Ouvrir -text.cancel = Annuler -text.openlink = Lien public -text.back = Retour -text.quit.confirm = Êtes-vous sûr de vouloir quitter? -text.changelog.title = Note de mise à jour -text.changelog.error = [Scarlet] Erreur lors de l'obtention du changement de serveur! Vérifiez votre connexion internet. -text.changelog.current = [jaune] [[Version actuelle] -text.changelog.latest = [orange][[Dernière version] -text.loading = [accent]Chargement ... -text.wave = [orange]Vague {0} -text.wave.waiting = Vague dans {0} -text.waiting = Attente... -text.enemies = ennemis -text.enemies.single = Ennemi -text.loadimage = Charger l'image -text.saveimage = Enregistrer l'image -text.oregen = Génération de minerais -text.editor.badsize = [orange]Dimensions de l'image non valides![] Dimensions de la carte valides: {0} -text.editor.errorimageload = Erreur lors du chargement du fichier image:[orange] {0} -text.editor.errorimagesave = Erreur lors de la sauvegarde du fichier image:[orange] {0} -text.editor.generate = Générer -text.editor.resize = Redimensionner -text.editor.loadmap = Charger la carte -text.editor.savemap = Enregistrer la carte -text.editor.loadimage = Charger l'image -text.editor.saveimage = Enregistrer l'image -text.editor.unsaved = [scarlet] Vous avez des changements non sauvegardés![] Êtes-vous sûr de vouloir quitter? -text.editor.brushsize = Taille de la brosse: -text.editor.noplayerspawn = Cette carte n'a pas de point d'apparition de joueur! -text.editor.manyplayerspawns = Les cartes ne peuvent pas avoir plus d'un point d'apparition de joueur! -text.editor.manyenemyspawns = Il ne peut pas avoir plus de {0} points d'apparition ennemis! -text.editor.resizemap = Redimensionner la carte -text.editor.resizebig = [scarlet]Attention![] Les cartes de plus de 256 unités peuvent laggés et être instables. -text.editor.mapname = Nom de la carte: -text.editor.overwrite = [accent]Attention! Cela écrasera la carte existante. -text.editor.failoverwrite = [Crimson]Impossible d'écraser la carte par défaut! -text.editor.selectmap = Sélectionnez une carte à charger: -text.width = Largeur: -text.height = Hauteur: -text.randomize = Rendre aléatoire -text.apply = Appliquer -text.update = Modifier -text.menu = Menu -text.play = Jouer -text.load = Charger -text.save = Sauvegarder -text.language.restart = Veuillez redémarrer votre jeu pour que les paramètres de langue soient appliqués. -text.settings.language = Langue -text.settings = Réglages -text.tutorial = Tutoriel -text.editor = Éditeur -text.mapeditor = Éditeur de carte -text.donate = Faire un don -text.settings.reset = Valeur par défaut. -text.settings.controls = Contrôles -text.settings.game = Jeu -text.settings.sound = Son -text.settings.graphics = Graphique -text.upgrades = Améliorations -text.purchased = [VERT]Créé! -text.weapons = Armes -text.paused = Pause -text.respawn = Réapparition dans -text.info.title = [accent]Info -text.error.title = [crimson]Une erreur est survenue -text.error.crashmessage = [SCARLET]Une erreur inattendue est survenue, et aurait provoqué un crash.[] Veuillez indiquer les circonstances exactes dans lesquelles cette erreur est survenue au développeur:[ORANGE] anukendev@gmail.com[] -text.error.crashtitle = Une erreur est survenue -text.mode.break = Mode de rupture: {0} -text.mode.place = Placez le mode: {0} -placemode.hold.name = Ligne -placemode.areadelete.name = surface -placemode.touchdelete.name = toucher -placemode.holddelete.name = tenir -placemode.none.name = aucun -placemode.touch.name = toucher -placemode.cursor.name = curseur -text.blocks.extrainfo = [accent] info supplémentaire du bloc: -text.blocks.blockinfo = Bloquer les infos -text.blocks.powercapacity = capacité d'énergie -text.blocks.powershot = Energie/Tir -text.blocks.powersecond = Energie/Seconde -text.blocks.powerdraindamage = Utilisation d'énergie/Dommage -text.blocks.shieldradius = rayon du bouclier -text.blocks.itemspeedsecond = Vitesse d'Article/Seconde -text.blocks.range = Portée -text.blocks.size = Taille -text.blocks.powerliquid = énergie/Liquide -text.blocks.maxliquidsecond = Max Liquide/Seconde -text.blocks.liquidcapacity = Capacité liquide -text.blocks.liquidsecond = Liquide/seconde -text.blocks.damageshot = Dégâts/tir -text.blocks.ammocapacity = Capacité de munitions -text.blocks.ammo = Munition -text.blocks.ammoitem = Munitions/Article -text.blocks.maxitemssecond = Max articles/seconde -text.blocks.powerrange = Gamme de puissance -text.blocks.lasertilerange = portée du laser -text.blocks.capacity = Capacité -text.blocks.itemcapacity = Capacité article -text.blocks.maxpowergenerationsecond = Génération max d'énergie/seconde -text.blocks.powergenerationsecond = Génération d'énergie/seconde -text.blocks.generationsecondsitem = Génération Secondes / item -text.blocks.input = entrée -text.blocks.inputliquid = Entrée de liquide -text.blocks.inputitem = entré d'article -text.blocks.output = Sortie -text.blocks.secondsitem = Secondes/article -text.blocks.maxpowertransfersecond = Transfert max d'énergie/seconde -text.blocks.explosive = Hautement explosif ! -text.blocks.repairssecond = Réparation/seconde -text.blocks.health = Santé -text.blocks.inaccuracy = Inexactitude -text.blocks.shots = tirs -text.blocks.shotssecond = Tirs/seconde -text.blocks.fuel = Carburant -text.blocks.fuelduration = Durée du carburant -text.blocks.maxoutputsecond = Sortie max/seconde -text.blocks.inputcapacity = Capacité d'entrée -text.blocks.outputcapacity = Capacité de sortie -text.blocks.poweritem = Energie/Article -text.placemode = Mode Placement -text.breakmode = Mode destruction -text.health = santé -setting.difficulty.easy = facile -setting.difficulty.normal = normal -setting.difficulty.hard = difficile -setting.difficulty.insane = Extreme -setting.difficulty.purge = Purge -setting.difficulty.name = Difficulté: -setting.screenshake.name = Tremblement d'écran -setting.smoothcam.name = Caméra lisse -setting.indicators.name = Indicateurs ennemis -setting.effects.name = Effets d'affichage -setting.sensitivity.name = Sensibilité de la manette -setting.saveinterval.name = Intervalle des sauvegardes auto -setting.seconds = {0} secondes -setting.fullscreen.name = Plein écran -setting.multithread.name = Multithreading [scarlet] (instable!) -setting.fps.name = Afficher FPS -setting.vsync.name = VSync -setting.lasers.name = Afficher les rayons des lasers -setting.healthbars.name = Afficher les barres de santé des entités -setting.pixelate.name = Pixéliser l'écran -setting.musicvol.name = volume musique -setting.mutemusic.name = Musique muette -setting.sfxvol.name = Volume SFX -setting.mutesound.name = Son muet -map.maze.name = Labyrinthe -map.fortress.name = forteresse -map.sinkhole.name = gouffre -map.caves.name = cavernes -map.volcano.name = volcan -map.caldera.name = chaudron -map.scorch.name = brûlure -map.desert.name = désert -map.island.name = Île -map.grassland.name = prairie -map.tundra.name = toundra -map.spiral.name = spirale -map.tutorial.name = tutoriel -tutorial.intro.text = [yellow]Bienvenue dans le tutoriel[]. Pour commencer, appuyez sur \"suivant\". -tutorial.moveDesktop.text = Pour vous déplacer, utilisez les touches [orange][[WASD][]. Maintenez [orange]shift[] pour accélérer. Maintenez la touche [orange]CTRL[] enfoncée tout en utilisant la [orange]molette[] pour effectuer un zoom avant ou arrière. -tutorial.shoot.text = Utilisez votre souris pour viser, maintenez le [orange]bouton gauche de la souris[] pour tirer. Essayez de pratiquer sur la [yellow]cible[]. -tutorial.moveAndroid.text = Pour déplacer votre vue, faites glisser un doigt sur l'écran. Pincez et faites glisser pour effectuer un zoom avant ou arrière. -tutorial.placeSelect.text = Essayez de sélectionner un [yellow]transporteur[] dans le menu des blocs en bas à droite. -tutorial.placeConveyorDesktop.text = Utilisez la [orange][[molette][] pour faire pivoter le transporteur [orange]vers l'avant[], puis placez-le dans l'emplacement [yellow]marqué[] en utilisant le [orange][[bouton gauche de la souris][]. -tutorial.placeConveyorAndroid.text = Utilisez [orange][[bouton de rotation][] pour faire pivoter le transporteur [orange]vers l'avant[], faites-le glisser avec votre doigt, puis placez-le dans l'emplacement [yellow]marqué[] avec [orange][[coche][]. -tutorial.placeConveyorAndroidInfo.text = Vous pouvez également appuyer sur l'icône du réticule en bas à gauche pour passer en [orange][[mode tactile][] et placer des blocs en appuyant sur l'écran. En mode tactile, les blocs peuvent être pivotés avec la flèche en bas à gauche. Appuyez sur [yellow]suivant[] pour essayer. -tutorial.placeDrill.text = Maintenant, sélectionnez et placez un [yellow]extracteur de pierre[] à l'endroit marqué. -tutorial.blockInfo.text = Si vous voulez en savoir plus sur un bloc, vous pouvez appuyer sur le [orange]point d'interrogation[] en haut à droite pour lire sa description. -tutorial.deselectDesktop.text = Vous pouvez désélectionner un bloc en utilisant le [orange][[bouton droit de la souris][]. -tutorial.deselectAndroid.text = Vous pouvez désélectionner un bloc en appuyant sur le bouton [orange]X[]. -tutorial.drillPlaced.text = L'extracteur produira maintenant de la [yellow]pierre[], qui sera placée sur le transporteur, puis sera emmenée dans le [yellow]noyau[]. -tutorial.drillInfo.text = Différents minerais ont besoin de différents extracteurs. La pierre nécessite des extracteurs de pierre, le fer des extracteurs de fer,...etc... -tutorial.drillPlaced2.text = Le déplacement d'article dans le noyau le place dans votre[yellow]inventaire[], au milieu à droite. Le placement de blocs utilise les éléments de votre inventaire. -tutorial.moreDrills.text = Vous pouvez relier plusieurs extracteurs et transporteurs ensemble, comme ça. -tutorial.deleteBlock.text = Vous pouvez supprimer des blocs en cliquant sur le clique [orange]droit de la souris[] sur le bloc que vous voulez supprimer. Essayez de supprimer ce transporteur. -tutorial.deleteBlockAndroid.text = Vous pouvez supprimer des blocs en [orange]sélectionnant le réticule[] dans [orange] le menu du mode destruction[] en bas à gauche et en appuyant sur un bloc. Essayez de supprimer ce transporteur. -tutorial.placeTurret.text = Maintenant, sélectionnez et placez une [yellow]tourelle[] à l'[yellow]emplacement marqué[]. -tutorial.placedTurretAmmo.text = Cette tourelle accepte maintenant les [yellow]munitions[] du transporteur. Vous pouvez voir combien de munitions elle a en la survolant et en vérifiant sa [green]barre verte[]. -tutorial.turretExplanation.text = Les tourelles tirent automatiquement sur l'ennemi le plus proche, pourvu qu'elles aient suffisamment de munitions. -tutorial.waves.text = Toutes les [yellow]60 secondes[], une [coral]vague d'ennemis[] apparaîtra dans des endroits spécifiques et tentera de détruire votre noyau. -tutorial.coreDestruction.text = Votre objectif est de [yellow]défendre le noyau[]. Si le noyau est détruit, vous [coral]perdez le jeu[]. -tutorial.pausingDesktop.text = Si vous avez besoin de faire une pause, appuyez sur le bouton [orange]pause[] en haut à gauche pour mettre le jeu en pause. Vous pouvez toujours casser et placer des blocs en pause, mais vous ne pouvez pas bouger ou tirer. -tutorial.pausingAndroid.text = Si vous avez besoin de faire une pause, appuyez sur le bouton [orange]pause[] en haut à gauche pour mettre le jeu en pause. Vous pouvez toujours casser et placer des blocs en pause. -tutorial.purchaseWeapons.text = Vous pouvez acheter de nouvelles [yellow]armes[] pour votre robot en ouvrant le menu de mise à niveau en bas à gauche. -tutorial.switchWeapons.text = Changez d'arme en cliquant sur l'icône en bas à droite, ou en utilisant les chiffres [orange][[1-9][]. -tutorial.spawnWave.text = Une vague arrive. Détruisez-les. -tutorial.pumpDesc.text = Dans les vagues plus lointaines, vous devrez peut-être utiliser des [yellow]pompes[] pour distribuer du liquide pour les générateurs ou les extracteurs. -tutorial.pumpPlace.text = Les pompes fonctionnent de la même manière que les extracteurs, sauf qu'elles récoltent du liquides et non du minerai. Essayez de placer une pompe sur [yellow]pétrole[] désigné. -tutorial.conduitUse.text = Maintenant, placez un [orange]conduit[] qui s'éloigne de la pompe. -tutorial.conduitUse2.text = Et un autre ... -tutorial.conduitUse3.text = Et un autre ... -tutorial.generator.text = Maintenant, placez un bloc [orange]de générateur à combustion[] à l'extrémité du conduit. -tutorial.generatorExplain.text = Ce générateur va maintenant créer de l' [yellow]énergie[] à partir de pétrole. -tutorial.lasers.text = L'énergie est distribuée à l'aide de [yellow]lasers d'énergie[]. Tournez et placez-en un ici. -tutorial.laserExplain.text = Le générateur va maintenant déplacer la puissance dans le laser. Un faisceau [yellow]opaque[] signifie qu'il transmet actuellement de la puissance, et un faisceau [yellow]transparent[] signifie que ce n'est pas le cas. -tutorial.laserMore.text = Vous pouvez vérifier la puissance d'un bloc en survolant celui-ci et en vérifiant la [yellow]barre jaune[] en haut. -tutorial.healingTurret.text = Ce laser peut être utilisé pour alimenter une [lime]tourelle de réparation[]. Placez-en une ici. -tutorial.healingTurretExplain.text = Tant qu'elle a de la puissance, cette tourelle [lime]réparera les blocs voisins[].En jouant, assurez-vous d'en avoir une dans votre base le plus rapidement possible! -tutorial.smeltery.text = De nombreux blocs nécessitent de l' [orange]acier[] pour fabriquer, ce qui nécessite une [orange]fonderie[]. Placez-en une ici. -tutorial.smelterySetup.text = Cette fonderie produira maintenant de l' [orange]acier[] à partir du fer, utilisant le charbon comme combustible. -tutorial.tunnelExplain.text = Notez également que les objets traversent un [orange]tunnel[] et émergent de l'autre côté, traversant le bloc de pierre. Gardez à l'esprit que les tunnels ne peuvent traverser que 2 blocs. -tutorial.end.text = Et cela conclut le tutoriel! Bonne chance! -text.keybind.title = Relier le clés -keybind.move_x.name = mouvement x -keybind.move_y.name = mouvement y -keybind.select.name = sélectionner -keybind.break.name = Pause -keybind.shoot.name = tirer -keybind.zoom_hold.name = tenir le zoom -keybind.zoom.name = zoom -keybind.block_info.name = bloc_info -keybind.menu.name = menu -keybind.pause.name = Pause -keybind.dash.name = attaque frontal -keybind.chat.name = chat -keybind.player_list.name = Liste des joueurs -keybind.console.name = console -keybind.rotate_alt.name = tourner_alt -keybind.rotate.name = Tourner -keybind.weapon_1.name = Arme 1 -keybind.weapon_2.name = Arme 2 -keybind.weapon_3.name = Arme 3 -keybind.weapon_4.name = Arme 4 -keybind.weapon_5.name = Arme 5 -keybind.weapon_6.name = Arme 6 -mode.waves.name = Vagues -mode.sandbox.name = bac à sable -mode.freebuild.name = construction libre -upgrade.standard.name = La norme -upgrade.standard.description = Le robot standard. -upgrade.blaster.name = blaster -upgrade.blaster.description = Tire faiblement et lentetement. -upgrade.triblaster.name = Tri-blaster -upgrade.triblaster.description = Tire 3 balles se propageant en V. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Tire une portée de grenades explosives. -upgrade.beam.name = beam cannon -upgrade.beam.description = Tire un rayon laser de perçage à longue portée. -upgrade.vulcan.name = Vulcain -upgrade.vulcan.description = Tire un barrage de balles rapides. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Tire une charge de balles qui explosent en éclats. -item.stone.name = pierre -item.iron.name = fer -item.coal.name = charbon -item.steel.name = Acier -item.titanium.name = Titane -item.dirium.name = dirium -item.uranium.name = uranium -item.sand.name = sable -liquid.water.name = eau -liquid.plasma.name = plasma -liquid.lava.name = lave -liquid.oil.name = pétrole -block.weaponfactory.name = usine d'armes -block.weaponfactory.fulldescription = Utilisé pour créer des armes pour le joueur robot. Cliquez pour utiliser. Extrait automatiquement les ressources du noyau. -block.air.name = air -block.blockpart.name = partie de block -block.deepwater.name = eaux profondes -block.water.name = eau -block.lava.name = lave -block.oil.name = pétrole -block.stone.name = pierre -block.blackstone.name = pierre noire -block.iron.name = fer -block.coal.name = charbon -block.titanium.name = titane -block.uranium.name = uranium -block.dirt.name = terre -block.sand.name = sable -block.ice.name = glace -block.snow.name = neige -block.grass.name = herbe -block.sandblock.name = bloc de sable -block.snowblock.name = bloc de neige -block.stoneblock.name = bloc de pierre -block.blackstoneblock.name = bloc de roche noire -block.grassblock.name = bloc d'herbe -block.mossblock.name = bloc de mousse -block.shrub.name = arbuste -block.rock.name = roche -block.icerock.name = roche de glace -block.blackrock.name = roche noire -block.dirtblock.name = bloc de terre -block.stonewall.name = mur de pierres -block.stonewall.fulldescription = Un bloc défensif bon marché. Utile pour protéger le noyau et les tourelles durant les premières vagues. -block.ironwall.name = mur de fer -block.ironwall.fulldescription = Un bloc défensif de base. Fournit une protection contre les ennemis. -block.steelwall.name = mur en acier -block.steelwall.fulldescription = Un bloc défensif standard. une protection adéquate contre les ennemis. -block.titaniumwall.name = mur de titane -block.titaniumwall.fulldescription = Un bloc défensif très résistant. Fournit une protection contre les ennemis. -block.duriumwall.name = mur de dirium -block.duriumwall.fulldescription = Un bloc défensif extrêmement résistant. Fournit une protection contre les ennemis. -block.compositewall.name = mur composite -block.steelwall-large.name = grand mur d'acier -block.steelwall-large.fulldescription = Un bloc défensif standard, prend plusieurs blocs de largeurs. -block.titaniumwall-large.name = grand mur de titane -block.titaniumwall-large.fulldescription = Un bloc défensif très résistant, prend plusieurs blocs de largeurs. -block.duriumwall-large.name = grand mur de dirium -block.duriumwall-large.fulldescription = Un bloc défensif extrêmement résistant, prend plusieurs blocs de largeurs. -block.titaniumshieldwall.name = mur blindé -block.titaniumshieldwall.fulldescription = Un bloc défensif solide, avec un bouclier intégré. Nécessite de l'énergie. Utilise l'énergie pour absorber les balles ennemies. Il est recommandé d'utiliser un distributeur d'énergie pour fournir de l'énergie à ce bloc. -block.repairturret.name = tourelle de réparation -block.repairturret.fulldescription = Répare les blocs avoisinants endommagés dans un zone circulaire à un rythme lent. Utilise de l'énergie. -block.megarepairturret.name = tourelle de réparation II -block.megarepairturret.fulldescription = Répare les blocs avoisinants endommagés dans un zone circulaire à un rythme régulier. Utilise de l'énergie. -block.shieldgenerator.name = générateur de bouclier -block.shieldgenerator.fulldescription = Un bloc défensif avancé. Protège tous les blocs avoisinants dans une zone circulaire. Utilise l'énergie à un rythme lent, mais draine beaucoup d'énergie au contact d'un tir ennemi. -block.door.name = porte -block.door.fulldescription = Un bloc qui peut être ouvert et fermé en cliquant dessus. -block.door-large.name = grande porte -block.door-large.fulldescription = Un bloc qui peut être ouvert et fermé en cliquant dessus. -block.conduit.name = conduit -block.conduit.fulldescription = Bloc de transport de liquide basique. Fonctionne comme un transporteur, mais avec des liquides. A placé à coté de pompes. Peut être utilisé comme un pont sur les liquides pour les ennemis et les joueurs. -block.pulseconduit.name = conduit à impulsion -block.pulseconduit.fulldescription = Bloc de transport de liquide avancé. Transporte les liquides plus rapidement et stocke plus que les conduits standards. -block.liquidrouter.name = routeur de liquide -block.liquidrouter.fulldescription = Fonctionne de manière similaire à un routeur. Accepte l'entrée de liquide d'un côté et l'envoie vers 3 autres côtés. Utile pour répartir le liquide d'un conduit vers plusieurs autres conduits. -block.conveyor.name = transporteur -block.conveyor.fulldescription = Bloc de transport standard. Déplace les objets vers l'avant et les dépose automatiquement dans les tourelles ou des blocs d'artisanats. Rotatif. Peut être utilisé comme une plateforme sur les liquides, pour les ennemis et les joueurs. -block.steelconveyor.name = transporteur en acier -block.steelconveyor.fulldescription = transporteur d'articles avancé. Déplace les objets plus rapidement que les transporteurs standards. -block.poweredconveyor.name = transporteur à impulsions -block.poweredconveyor.fulldescription = Le transporteur d'articles ultime. Déplace les articles plus rapidement que les convoyeurs en acier. -block.router.name = Routeur -block.router.fulldescription = Accepte les éléments d'une direction et les distribue dans 3 autres directions. Peut également stocker une certaine quantité d'articles. Utile pour diviser cette quantité afin d'approvisionner plusieurs tourelles ou des blocs d'artisanat. -block.junction.name = jonction -block.junction.fulldescription = Agit comme un pont pour deux transporteurs qui se croisent et qui possèdent différents articles. -block.conveyortunnel.name = tunnel de transport -block.conveyortunnel.fulldescription = Transporte des articles sous les blocs. Pour l'utiliser, placez les entre des blocs, au maximum deux. Assurez-vous que les deux tunnels sont orientés dans des directions opposées. -block.liquidjunction.name = jonction à liquide -block.liquidjunction.fulldescription = Agit comme un pont pour deux conduits. Utile dans la situations ou deux conduits se croisent et transportent différents liquides. -block.liquiditemjunction.name = jonction de liquide-article -block.liquiditemjunction.fulldescription = Agit comme un pont pour croiser les conduits et les convoyeurs. -block.powerbooster.name = distributeur d'énergie -block.powerbooster.fulldescription = Distribue la puissance à tous les blocs avoisinants dans une zone circulaire. -block.powerlaser.name = laser d'énergie -block.powerlaser.fulldescription = Crée un laser qui transmet la puissance au bloc en face de lui. Ne génère pas d'énergie par lui-même. Idéal avec des générateurs ou d'autres lasers. -block.powerlaserrouter.name = routeur laser -block.powerlaserrouter.fulldescription = Laser qui distribue la puissance à trois directions à la fois. Utile pour séparer l'énergie afin d'alimenter plusieurs blocs -block.powerlasercorner.name = laser en angle droit -block.powerlasercorner.fulldescription = Laser qui distribue la puissance à deux directions en angle droit. Utile pour séparer l'énergie afin d'alimenter plusieurs blocs. -block.teleporter.name = téléporteur -block.teleporter.fulldescription = Bloc de transport avancé. Les téléporteurs saisissent des articles aux autres téléporteurs de la même couleur. Ne fait rien si aucun téléporteur de la même couleur n'existe. Si plusieurs téléporteurs existent de la même couleur, un téléporteur aléatoire est sélectionné. Utilise de l'énergie. Cliquez pour changer de couleur. -block.sorter.name = trieur -block.sorter.fulldescription = Trie l'article par type de matériau. Le matériau à accepter est indiqué par la couleur du bloc. Tous les éléments qui correspondent au matériau de tri sont sortis vers l'avant, tout le reste est sorti à gauche et à droite. -block.core.name = noyau -block.pump.name = pompe -block.pump.fulldescription = Pompe les liquides provenant d'un bloc source - généralement de l'eau, de la lave ou de l'huile. Transmet le liquide dans les conduits voisins. -block.fluxpump.name = pompe à flux -block.fluxpump.fulldescription = Une version avancée de la pompe. Stocke plus et pompe le liquide plus rapidement que la pompe ordinaire. -block.smelter.name = fonderie -block.smelter.fulldescription = Le bloc d'artisanat essentiel. Produit de l'acier lorsqu'il est approvisionné en fer et charbon. Il est conseillé d'introduire le fer et le charbon par différents transporteurs pour éviter les situations de débordement. -block.crucible.name = Crucible -block.crucible.fulldescription = Un bloc d'artisanat avancé. Produit du diridium lorsqu'il est approvisionné en fer, en titanium et en charbon . Il est conseillé d'introduire ces minerais par différents transporteurs. -block.coalpurifier.name = raffinerie de charbon -block.coalpurifier.fulldescription = Un bloc d'artisanat de base. Produit du charbon lorsqu'il est fourni avec de grandes quantités d'eau et de pierre. -block.titaniumpurifier.name = raffinerie de titane -block.titaniumpurifier.fulldescription = Un bloc d'artisanat avancé. Produit du titane lorsqu'il est fourni avec de grandes quantités d'eau et de fer. -block.oilrefinery.name = raffinerie de pétrole -block.oilrefinery.fulldescription = Un bloc d'artisanat standard. Affine de grandes quantités de pétrole grâce au charbon. Utile pour alimenter les tourelles à charbon lorsque les filons de charbon sont rares. -block.stoneformer.name = raffinerie de pierre -block.stoneformer.fulldescription = Un bloc d'artisanat standard. Il purifie la lave en pierre. Utile pour produire des quantités massives de pierre. -block.lavasmelter.name = fonderie d'acier -block.lavasmelter.fulldescription = Un bloc d'artisanat de base. Utilise la lave pour convertir le fer en acier. Une alternative aux fonderies. Utile dans les situations où le charbon est rare. -block.stonedrill.name = extracteur de pierre -block.stonedrill.fulldescription = L'extracteur essentiel. Lorsqu'il est placé sur de la pierre, il l'extrait à un rythme rapide. -block.irondrill.name = extracteur de fer -block.irondrill.fulldescription = Un extracteur de base. Lorsqu'ils il est placé sur un minerai de charbon, il l'extrait à un rythme régulier. -block.coaldrill.name = extracteur de charbon -block.coaldrill.fulldescription = Un extracteur de base. Lorsqu'il est placé sur un minerai de charbon, il l'extrait à un rythme régulier. -block.uraniumdrill.name = extracteur d'uranium -block.uraniumdrill.fulldescription = Un extracteur avancé .Lorsqu'il est placé sur un minerai d'uranium, il l'extrait lentement. -block.titaniumdrill.name = extracteur de titane -block.titaniumdrill.fulldescription = Un extracteur avancé. Lorsqu'il est placé sur un minerai de titane, il l'extrait lentement. -block.omnidrill.name = omni-extracteur -block.omnidrill.fulldescription = L'extracteur ultime .Minera n'importe quel minerai sur lequel il est placé à un rythme rapide. -block.coalgenerator.name = générateur à charbon -block.coalgenerator.fulldescription = Le générateur essentiel. Génère de l'énergie à partir du charbon. eparpille l'énergie de ses 4 cotés. -block.thermalgenerator.name = générateur thermique -block.thermalgenerator.fulldescription = Génère de l'énergie à partir de la lave. éparpille l'énergie de ses 4 cotés. -block.combustiongenerator.name = générateur à combustion -block.combustiongenerator.fulldescription = Génère de l'énergie à partir du pétrole. éparpille l'énergie de ses 4 cotés. -block.rtgenerator.name = Générateur RTG -block.rtgenerator.fulldescription = Génère de petites quantités d'énergie à partir d'uranium. éparpille l'énergie de ses 4 cotés -block.nuclearreactor.name = réacteur nucléaire -block.nuclearreactor.fulldescription = Une version avancée du générateur RTG ,et le générateur d'énergie ultime. Génère de l'énergie à partir de l'uranium. Nécessite un refroidissement à eau constant. Hautement inflammable; explosera violemment si des quantités insuffisantes de liquide de refroidissement sont fournies. -block.turret.name = tourelle -block.turret.fulldescription = Une tourelle basique et bon marché. Utilise la pierre pour munition. A légèrement plus de portée que la double-tourelle. -block.doubleturret.name = tourelle double -block.doubleturret.fulldescription = Une version légèrement plus puissante de la tourelle. Utilise la pierre comme munition. Fait beaucoup plus de dégâts, mais a une portée inférieure. -block.machineturret.name = tourelle gattling -block.machineturret.fulldescription = Une tourelle complète standard. Utilise le fer pour munition. A une vitesse de tir rapide avec des dégâts décents. -block.shotgunturret.name = tourelle fusil à pompe. -block.shotgunturret.fulldescription = Une tourelle standard. Utilise le fer pour munition. Tire une portée de 7 balles. Portée basse, mais plus de dégâts que la tourelle gattling. -block.flameturret.name = tourelle incendiaire -block.flameturret.fulldescription = Tourelle avancée à courte portée. Utilise du charbon pour munition. A une portée très faible, mais des dégâts très élevés. Bon pour les places étroites. Recommandé pour tirer à travers les murs. -block.sniperturret.name = Tourelle laser -block.sniperturret.fulldescription = Tourelle avancée à longue portée. Utilise l'acier pour munition. Dommages très élevés, mais faible vitesse de tir. Chère, mais peut être placé loin des lignes ennemies en raison de sa portée. -block.mortarturret.name = Tourelle Antiaérienne -block.mortarturret.fulldescription = Tourelle avancée à fragmentation de faible précision. Utilise du charbon pour munition. Tire un barrage de balles qui explose en éclats. Utile pour les grandes foules d'ennemis. -block.laserturret.name = Tourelle Laser -block.laserturret.fulldescription = Tourelle à cible unique avancée. Utilise de l'énergie. Bonne tourelle polyvalente de moyenne portée. Une seule cible seulement. Ne manque jamais. -block.waveturret.name = Tourelle Tesla -block.waveturret.fulldescription = Tourelle multi-cible avancée. Utilise de l'énergie. Portée moyenne. Ne manque jamais sa cible. Peu de dégâts, mais peut frapper plusieurs ennemis simultanément avec sa répétition d'éclair. -block.plasmaturret.name = Tourelle à plasma -block.plasmaturret.fulldescription = Version très avancée de la tourelle incendiaire. Utilise le charbon comme munition. Dommages très élevés, de faible à moyenne portée. -block.chainturret.name = Tourelle à répétition. -block.chainturret.fulldescription = La tourelle ultime à tir rapide. Utilise l'uranium comme munition. Tire de grosses salves à une vitesse de feu élevée. Portée moyenne. Traverse plusieurs carreaux. Extrêmement résistante. -block.titancannon.name = Cannon Titan -block.titancannon.fulldescription = La tourelle à longue portée ultime. Utilise l'uranium comme munition. Tire de gros obus à dégâts de zone à une cadence de tir moyenne. Longue portée. occupe plusieurs blocks. Extrêmement dur. -block.playerspawn.name = point d'apparition joueur -block.enemyspawn.name = Point d'apparition ennemie +text.about=Créé par [ROYAL]Anuken.[]\nA l'origine une entrée dans le [orange]GDL[] MM Jam.\n\nCrédits: \n- SFX réalisé avec [yellow]bfxr[] \n- Musique faite par [lime]RoccoW[] / trouvé sur [lime]FreeMusicArchive.org[] \n\nRemerciements particuliers à:\n- [coral]MitchellFJN[]: nombreux tests et retours d'expérience \n- [sky]Luxray5474[]: travail wiki, contributions de code \n- [lime]Epowerj[]: système de compilation de code, icône \n- Tous les beta testeurs sont sur itch.io et Google Play\n +text.discord=Rejoignez le discord de Mindustry +text.gameover=Le noyau a été détruit. +text.highscore=[YELLOW]Nouveau meilleur score! +text.lasted=Vous avez duré jusqu'à la vague +text.level.highscore=Meilleur score: [accent]{0} +text.level.delete.title=Confirmer +text.level.select=Sélection de niveau +text.level.mode=Mode de jeu : +text.savegame=Sauvegarder la partie +text.loadgame=Charger la partie +text.joingame=Rejoindre la partie +text.quit=Quitter +text.about.button=À propos +text.name=Nom : +text.players=joueurs en ligne +text.players.single=joueur en ligne +text.server.mismatch=Erreur de paquet: possible incompatibilité de version client/serveur. Assurez-vous que vous et l'hôte avez la dernière version de Mindustry! +text.server.closing=[accent]Fermeture du serveur ... +text.server.kicked.kick=Vous avez été expulsé du serveur! +text.server.kicked.invalidPassword=Mot de passe non valide ! +text.server.kicked.clientOutdated=Client dépassé! Mettez à jour votre jeu! +text.server.kicked.serverOutdated=Serveur dépassé! Demandez à l'hôte de le mettre à jour! +text.server.kicked.banned=Vous êtes banni sur ce serveur. +text.server.connected=a rejoint le serveur +text.server.disconnected={0} s'est déconnecté. +text.nohost=Impossible d'héberger le serveur sur une carte personnalisée! +text.hostserver=Héberger un serveur +text.host=Héberger +text.hosting=[accent]Ouverture du serveur ... +text.hosts.refresh=Actualiser +text.hosts.discovering=Recherche de parties LAN +text.server.refreshing=Actualisation du serveur +text.hosts.none=[lightgray]Aucun jeu LAN trouvé! +text.host.invalid=[scarlet]Impossible de se connecter à l'hôte. +text.server.friendlyfire=Tir allié +text.trace=suivre le joueur +text.trace.playername=Nom du joueur: [accent] {0} +text.trace.ip=IP: [accent] {0} +text.trace.id=ID unique: [accent] {0} +text.trace.android=Client Android: [accent] {0} +text.trace.modclient=Client personnalisé: [accent] {0} +text.trace.totalblocksbroken=Total des blocs détruits: [accent] {0} +text.trace.structureblocksbroken=Blocs de structure détruits: [accent] {0} +text.trace.lastblockbroken=Dernier bloc détruit: [accent] {0} +text.trace.totalblocksplaced=Nombre total de blocs placés: [accent] {0} +text.trace.lastblockplaced=Dernier bloc placé: [accent] {0} +text.invalidid=ID client invalide! Soumettre un rapport de bug +text.server.bans=Interdictions +text.server.bans.none=Aucun joueur banni trouvé! +text.server.admins=Administrateurs +text.server.admins.none=Aucun administrateur trouvé! +text.server.add=Ajouter un serveur +text.server.delete=Êtes-vous sûr de vouloir supprimer ce serveur? +text.server.hostname=Héberger +text.server.edit=éditer le serveur +text.server.outdated=[crimson]Serveur obsolète![] +text.server.outdated.client=[Crimson]Client obsolète![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[jaune]Construction personnalisée +text.confirmban=Êtes-vous sûr de vouloir bannir ce joueur? +text.confirmunban=Êtes-vous sûr de vouloir annuler le ban de ce joueur? +text.confirmadmin=Êtes-vous sûr de vouloir faire de ce joueur un administrateur? +text.confirmunadmin=Êtes-vous sûr de vouloir supprimer le statut d'administrateur de ce joueur? +text.joingame.title=Rejoindre une partie +text.joingame.ip=IP : +text.disconnect=Déconnecté +text.connecting=[accent]Connexion ... +text.connecting.data=[accent] Chargement des données de la partie ... +text.connectfail=[crimson] Échec de la connexion au serveur : [orange] +text.server.port=Port : +text.server.addressinuse=Adresse déjà utilisée! +text.server.invalidport=Numéro de port incorrect. +text.server.error=[crimson]Erreur lors de l'hébergement du serveur: [orange] {0} +text.save.new=Nouvelle sauvegarde +text.save.overwrite=Êtes-vous sûr de vouloir remplacer cette sauvegarde? +text.overwrite=Écraser +text.save.none=Aucune sauvegarde trouvée! +text.saveload=[accent]Sauvegarde ... +text.savefail=Échec de la sauvegarde du jeu ! +text.save.delete.confirm=Êtes-vous sûr de vouloir supprimer cette sauvegarde? +text.save.delete=Supprimer +text.save.export=Exporter la sauvegarde +text.save.import.invalid=[orange]Cette sauvegarde est invalide! +text.save.import.fail=[crimson]Echec de l'importation de la sauvegarde: [orange] {0} +text.save.export.fail=[crimson]Échec de l'exportation de la sauvegarde: [orange] {0} +text.save.import=Importer la sauvegarde +text.save.newslot=Enregistrer le nom: +text.save.rename=Renommer +text.save.rename.text=Nouveau nom: +text.selectslot=Sélectionnez une sauvegarde. +text.slot=[accent]Emplacement {0} +text.save.corrupted=[orange]Le fichier enregistrer est corrompu ou invalide! +text.empty= +text.on=Allumer +text.off=Eteint +text.save.autosave=Sauvegarde automatique +text.save.map=Carte +text.save.wave=Vague : +text.save.difficulty=DIFFICULTÉ +text.save.date=Dernière sauvegarde: {0} +text.confirm=Confirmer +text.delete=Supprimer +text.ok=OK +text.open=Ouvrir +text.cancel=Annuler +text.openlink=Lien public +text.back=Retour +text.quit.confirm=Êtes-vous sûr de vouloir quitter? +text.changelog.title=Note de mise à jour +text.changelog.error=[Scarlet] Erreur lors de l'obtention du changement de serveur! Vérifiez votre connexion internet. +text.changelog.current=[jaune] [[Version actuelle] +text.changelog.latest=[orange][[Dernière version] +text.loading=[accent]Chargement ... +text.wave=[orange]Vague {0} +text.wave.waiting=Vague dans {0} +text.waiting=Attente... +text.enemies=ennemis +text.enemies.single=Ennemi +text.loadimage=Charger l'image +text.saveimage=Enregistrer l'image +text.editor.badsize=[orange]Dimensions de l'image non valides![] Dimensions de la carte valides: {0} +text.editor.errorimageload=Erreur lors du chargement du fichier image:[orange] {0} +text.editor.errorimagesave=Erreur lors de la sauvegarde du fichier image:[orange] {0} +text.editor.generate=Générer +text.editor.resize=Redimensionner +text.editor.loadmap=Charger la carte +text.editor.savemap=Enregistrer la carte +text.editor.loadimage=Charger l'image +text.editor.saveimage=Enregistrer l'image +text.editor.unsaved=[scarlet] Vous avez des changements non sauvegardés![] Êtes-vous sûr de vouloir quitter? +text.editor.resizemap=Redimensionner la carte +text.editor.mapname=Nom de la carte: +text.editor.overwrite=[accent]Attention! Cela écrasera la carte existante. +text.editor.selectmap=Sélectionnez une carte à charger: +text.width=Largeur: +text.height=Hauteur: +text.menu=Menu +text.play=Jouer +text.load=Charger +text.save=Sauvegarder +text.language.restart=Veuillez redémarrer votre jeu pour que les paramètres de langue soient appliqués. +text.settings.language=Langue +text.settings=Réglages +text.tutorial=Tutoriel +text.editor=Éditeur +text.mapeditor=Éditeur de carte +text.donate=Faire un don +text.settings.reset=Valeur par défaut. +text.settings.controls=Contrôles +text.settings.game=Jeu +text.settings.sound=Son +text.settings.graphics=Graphique +text.upgrades=Améliorations +text.purchased=[VERT]Créé! +text.weapons=Armes +text.paused=Pause +text.info.title=[accent]Info +text.error.title=[crimson]Une erreur est survenue +text.error.crashtitle=Une erreur est survenue +text.blocks.blockinfo=Bloquer les infos +text.blocks.powercapacity=capacité d'énergie +text.blocks.powershot=Energie/Tir +text.blocks.size=Taille +text.blocks.liquidcapacity=Capacité liquide +text.blocks.maxitemssecond=Max articles/seconde +text.blocks.powerrange=Gamme de puissance +text.blocks.itemcapacity=Capacité article +text.blocks.inputliquid=Entrée de liquide +text.blocks.inputitem=entré d'article +text.blocks.explosive=Hautement explosif ! +text.blocks.health=Santé +text.blocks.inaccuracy=Inexactitude +text.blocks.shots=tirs +text.blocks.inputcapacity=Capacité d'entrée +text.blocks.outputcapacity=Capacité de sortie +setting.difficulty.easy=facile +setting.difficulty.normal=normal +setting.difficulty.hard=difficile +setting.difficulty.insane=Extreme +setting.difficulty.purge=Purge +setting.difficulty.name=Difficulté: +setting.screenshake.name=Tremblement d'écran +setting.indicators.name=Indicateurs ennemis +setting.effects.name=Effets d'affichage +setting.sensitivity.name=Sensibilité de la manette +setting.saveinterval.name=Intervalle des sauvegardes auto +setting.seconds={0} secondes +setting.fullscreen.name=Plein écran +setting.multithread.name=Multithreading [scarlet] (instable!) +setting.fps.name=Afficher FPS +setting.vsync.name=VSync +setting.lasers.name=Afficher les rayons des lasers +setting.healthbars.name=Afficher les barres de santé des entités +setting.musicvol.name=volume musique +setting.mutemusic.name=Musique muette +setting.sfxvol.name=Volume SFX +setting.mutesound.name=Son muet +map.maze.name=Labyrinthe +map.fortress.name=forteresse +map.sinkhole.name=gouffre +map.caves.name=cavernes +map.volcano.name=volcan +map.caldera.name=chaudron +map.scorch.name=brûlure +map.desert.name=désert +map.island.name=Île +map.grassland.name=prairie +map.tundra.name=toundra +map.spiral.name=spirale +map.tutorial.name=tutoriel +text.keybind.title=Relier le clés +keybind.move_x.name=mouvement x +keybind.move_y.name=mouvement y +keybind.select.name=sélectionner +keybind.break.name=Pause +keybind.shoot.name=tirer +keybind.zoom_hold.name=tenir le zoom +keybind.zoom.name=zoom +keybind.block_info.name=bloc_info +keybind.menu.name=menu +keybind.pause.name=Pause +keybind.dash.name=attaque frontal +keybind.chat.name=chat +keybind.player_list.name=Liste des joueurs +keybind.console.name=console +keybind.rotate_alt.name=tourner_alt +keybind.rotate.name=Tourner +mode.waves.name=Vagues +mode.sandbox.name=bac à sable +mode.freebuild.name=construction libre +item.stone.name=pierre +item.coal.name=charbon +item.titanium.name=Titane +item.sand.name=sable +liquid.water.name=eau +liquid.lava.name=lave +liquid.oil.name=pétrole +block.door.name=porte +block.door-large.name=grande porte +block.conduit.name=conduit +block.pulseconduit.name=conduit à impulsion +block.liquidrouter.name=routeur de liquide +block.conveyor.name=transporteur +block.router.name=Routeur +block.junction.name=jonction +block.liquidjunction.name=jonction à liquide +block.sorter.name=trieur +block.smelter.name=fonderie +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.disconnect.data=Failed to load world data! +text.copylink=Copy Link +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_in_ID.properties b/core/assets/bundles/bundle_in_ID.properties index 6c96a7468c..b4c7f1e2a4 100644 --- a/core/assets/bundles/bundle_in_ID.properties +++ b/core/assets/bundles/bundle_in_ID.properties @@ -1,491 +1,495 @@ -text.about = Dibuat oleh [ROYAL]Anuken.[]\nAwalnya masuk di [orange]GDL[] MM Jam.\n\nKredit:\n- SFX dibuat dengan [YELLOW]bfxr[]\n- Musik dibuat oleh [GREEN]RoccoW[] / ditemukan di [lime]FreeMusicArchive.org[]\n\nTerima kasih khusus kepada:\n- [coral]MitchellFJN[]: playtesting dan umpan balik yang luas\n- [sky]Luxray5474[]: pekerjaan wiki, kontribusi kode\n- Semua penguji beta di itch.io dan Google Play\n -text.discord = Bergabunglah dengan Discord Mindustry! -text.gameover = Intinya hancur. -text.highscore = [YELLOW]Rekor baru! -text.lasted = Anda bertahan sampai gelombang -text.level.highscore = Skor Tinggi: [accent]{0} -text.level.delete.title = Konfirmasi Hapus -text.level.delete = Yakin ingin menghapus peta \"[orange]{0}\"? -text.level.select = Pilih Level -text.level.mode = Modus permainan: -text.savegame = Simpan Permainan -text.loadgame = Lanjutkan -text.joingame = Bermain Bersama -text.quit = Keluar -text.about.button = Tentang -text.name = Nama: -text.public = Publik -text.players = {0} pemain online -text.server.player.host = {0} (host) -text.players.single = {0} pemain online -text.server.mismatch = Kesalahan paket: kemungkinan versi client / server tidak sesuai.\nPastikan Anda dan host memiliki versi terbaru Mindustry! -text.server.closing = [accent]Menutup server... -text.server.kicked.kick = Anda telah dikeluarkan dari server! -text.server.kicked.invalidPassword = Kata sandi salah! -text.server.kicked.clientOutdated = Client versi lama! Update game Anda! -text.server.kicked.serverOutdated = Server versi lama! Tanyakan host untuk mengupdate! -text.server.connected = {0} telah bergabung. -text.server.disconnected = {0} telah terputus. -text.nohost = Tidak dapat meng-host server pada peta khusus! -text.hostserver = Host Server -text.host = Host -text.hosting = [accent]Membuka server... -text.hosts.refresh = Segarkan -text.hosts.discovering = Mencari game LAN -text.server.refreshing = Menyegarkan server -text.hosts.none = [lightgray]Tidak ada game LAN yang ditemukan! -text.host.invalid = [scarlet]Tidak dapat terhubung ke host. -text.server.friendlyfire = Tembak Sesama -text.server.add = Tambahkan Server -text.server.delete = Yakin ingin menghapus server ini? -text.server.hostname = Host: {0} -text.server.edit = Sunting Server -text.joingame.byip = Bergabung dengan IP... -text.joingame.title = Bermain Bersama -text.joingame.ip = IP: -text.disconnect = Sambungan terputus. -text.connecting = [accent]Menghubungkan... -text.connecting.data = [accent]Memuat data level... -text.connectfail = [crimson]Gagal terhubung ke server: [orange]{0} -text.server.port = Port: -text.server.addressinuse = Alamat sudah di pakai! -text.server.invalidport = Nomor port salah! -text.server.error = [crimson]Kesalahan server hosting: [orange]{0} -text.tutorial.back = < Sebelumnya -text.tutorial.next = Berikutnya > -text.save.new = Simpan Baru -text.save.overwrite = Yakin ingin mengganti slot simpan ini? -text.overwrite = Ganti -text.save.none = Tidak ada simpanan ditemukan! -text.saveload = [accent]Menyimpan... -text.savefail = Gagal menyimpan game! -text.save.delete.confirm = Yakin ingin menghapus save ini? -text.save.delete = Hapus -text.save.export = Ekspor Simpanan -text.save.import.invalid = [orange]Simpanan ini tidak valid! -text.save.import.fail = [crimson]Gagal mengimpor: [orange]{0} -text.save.export.fail = [crimson]Gagal mengekspor save: [orange]{0} -text.save.import = Impor Simpanan -text.save.newslot = Nama simpanan: -text.save.rename = Ganti nama -text.save.rename.text = Nama baru: -text.selectslot = Pilih simpanan. -text.slot = [accent]Slot{0} -text.save.corrupted = [orange]Simpanan rusak atau tidak valid! -text.empty = -text.on = Hidup -text.off = Mati -text.save.autosave = Simpan otomatis: {0} -text.save.map = Peta: {0} -text.save.wave = Gelombang {0} -text.save.date = Terakhir Disimpan: {0} -text.confirm = Konfirmasi -text.delete = Hapus -text.ok = OK -text.open = Buka -text.cancel = Batal -text.openlink = Buka tautan -text.back = Kembali -text.quit.confirm = Anda yakin ingin berhenti? -text.loading = [accent]Memuat... -text.wave = [orange]Gelombang {0} -text.wave.waiting = Gelombang dimulai {0} -text.waiting = Menunggu... -text.enemies = {0} musuh -text.enemies.single = {0} Musuh -text.loadimage = Buka Gambar -text.saveimage = Simpan Gambar -text.oregen = Generator Bijih -text.editor.badsize = [orange]Dimensi gambar tidak valid![]\nDimensi peta yang valid: {0} -text.editor.errorimageload = Kesalahan saat memuat file gambar:\n[orange]{0} -text.editor.errorimagesave = Kesalahan saat menyimpan file gambar:\n[orange]{0} -text.editor.generate = Hasilkan -text.editor.resize = Ubah ukuran -text.editor.loadmap = Buka Peta -text.editor.savemap = Simpan Peta -text.editor.loadimage = Buka Gambar -text.editor.saveimage = Simpan Gambar -text.editor.unsaved = [scarlet]Anda memiliki perubahan yang belum disimpan![]\nYakin ingin keluar? -text.editor.brushsize = Ukuran sikat: {0} -text.editor.noplayerspawn = Peta ini tidak memiliki spawnpoint pemain! -text.editor.manyplayerspawns = Peta tidak bisa memiliki lebih dari satu\nspawnpoint pemain! -text.editor.manyenemyspawns = Tidak dapat memiliki lebih dari\n{0} spawnpoint musuh! -text.editor.resizemap = Ubah ukuran peta -text.editor.resizebig = [scarlet]Peringatan!\n[]Peta yang lebih besar dari 256 unit mungkin nge-lag dan tidak stabil. -text.editor.mapname = Nama Peta: -text.editor.overwrite = [accent]Peringatan!\nIni akan mengganti peta yang ada. -text.editor.failoverwrite = [crimson]Tidak dapat mengganti peta default! -text.editor.selectmap = Pilih peta yang akan dimuat: -text.width = Lebar: -text.height = Tinggi: -text.randomize = Acak -text.apply = Terapkan -text.update = Perbarui -text.menu = Menu -text.play = Main -text.load = Buka -text.save = Simpan -text.language.restart = Silakan mulai ulang permainan Anda agar pengaturan bahasa mulai berlaku. -text.settings.language = Bahasa -text.settings = Pengaturan -text.tutorial = Tutorial -text.editor = Pengedit -text.mapeditor = Pengedit Peta -text.donate = Sumbangkan -text.settings.reset = Atur ulang ke Default -text.settings.controls = Kontrol -text.settings.game = Permainan -text.settings.sound = Suara -text.settings.graphics = Grafis -text.upgrades = Perbaruan -text.purchased = [LIME]Dibuat! -text.weapons = Senjata -text.paused = Jeda -text.respawn = Respawning dalam -text.info.title = [accent]Info -text.error.title = [crimson]Telah terjadi kesalahan -text.error.crashmessage = [SCARLET]Kesalahan tak terduga telah terjadi, yang menyebabkan kerusakan.\n[]Tolong laporkan keadaan yang tepat dimana kesalahan ini terjadi pada pengembang:\n[ORANGE] anukendev@gmail.com[] -text.error.crashtitle = Telah terjadi kesalahan -text.mode.break = Mode penghancur: {0} -text.mode.place = Mode penaruh: {0} -placemode.hold.name = garis -placemode.areadelete.name = area -placemode.touchdelete.name = sentuh -placemode.holddelete.name = tahan -placemode.none.name = tidak ada -placemode.touch.name = sentuh -placemode.cursor.name = kursor -text.blocks.extrainfo = [accent]info tambahan blok: -text.blocks.blockinfo = Info Blok -text.blocks.powercapacity = Kapasitas Tenaga -text.blocks.powershot = Tenaga/tembakan -text.blocks.powersecond = Tenaga/detik -text.blocks.powerdraindamage = Tenaga Dipakai/damage -text.blocks.shieldradius = Radius Perisai -text.blocks.itemspeedsecond = Kecepatan Barang/detik -text.blocks.range = Jangkauan -text.blocks.size = Ukuran -text.blocks.powerliquid = Tenaga/Cairan -text.blocks.maxliquidsecond = Batas cairan/detik -text.blocks.liquidcapacity = Kapasitas cairan -text.blocks.liquidsecond = Cairan/detik -text.blocks.damageshot = Damage/tembakan -text.blocks.ammocapacity = Kapasitas Amunisi -text.blocks.ammo = Amunisi -text.blocks.ammoitem = Amunisi/barang -text.blocks.maxitemssecond = Batas barang/detik -text.blocks.powerrange = Jangkauan tenaga -text.blocks.lasertilerange = Kotak jangkauan laser -text.blocks.capacity = Kapasitas -text.blocks.itemcapacity = Kapasitas Barang -text.blocks.maxpowergenerationsecond = Batas Penghasil Tenaga/detik -text.blocks.powergenerationsecond = Penghasil Tenaga/detik -text.blocks.generationsecondsitem = Waktu Penghasil (detik)/barang -text.blocks.input = Masukan -text.blocks.inputliquid = Cairan yang Masuk -text.blocks.inputitem = Barang yang Masuk -text.blocks.output = Keluar -text.blocks.secondsitem = Detik/barang -text.blocks.maxpowertransfersecond = Batas transfer tenaga/detik -text.blocks.explosive = Mudah meledak! -text.blocks.repairssecond = Perbaikan/detik -text.blocks.health = Darah -text.blocks.inaccuracy = Ketidaktelitian -text.blocks.shots = Tembakan -text.blocks.shotssecond = Tembakan/detik -text.blocks.fuel = Bahan Bakar -text.blocks.fuelduration = Durasi Bahan Bakar -text.blocks.maxoutputsecond = Batas keluar/detik -text.blocks.inputcapacity = Kapasitas masuk -text.blocks.outputcapacity = Kapasitas keluar -text.blocks.poweritem = Tenaga/barang -text.placemode = Mode Penempatan -text.breakmode = Mode Penghancur -text.health = darah -setting.difficulty.easy = mudah -setting.difficulty.normal = normal -setting.difficulty.hard = sulit -setting.difficulty.insane = sangat susah -setting.difficulty.purge = paling susah -setting.difficulty.name = Kesulitan: -setting.screenshake.name = Layar Bergoyang -setting.smoothcam.name = Kamera Halus -setting.indicators.name = Indikator Musuh -setting.effects.name = Efek Tampilan -setting.sensitivity.name = Sensitivitas Pengendali -setting.saveinterval.name = Waktu Simpan Otomatis -setting.seconds = {0} Detik -setting.fullscreen.name = Layar Penuh -setting.fps.name = Tunjukkan FPS -setting.vsync.name = VSync -setting.lasers.name = Tampilkan Laser Tenaga -setting.healthbars.name = Tampilkan Bar Darah Entitas -setting.pixelate.name = Layar Pixel -setting.musicvol.name = Volume Musik -setting.mutemusic.name = Bisukan Musik -setting.sfxvol.name = Volume Suara -setting.mutesound.name = Bisukan Suara -map.maze.name = labirin -map.fortress.name = benteng -map.sinkhole.name = lubang pembuangan -map.caves.name = gua -map.volcano.name = gunung berapi -map.caldera.name = kaldera -map.scorch.name = penghangusan -map.desert.name = gurun -map.island.name = pulau -map.grassland.name = padang rumput -map.tundra.name = tundra -map.spiral.name = spiral -map.tutorial.name = tutorial -tutorial.intro.text = [yellow]Selamat datang di tutorial.[] Untuk memulai, tekan 'berikutnya'. -tutorial.moveDesktop.text = Untuk bergerak, gunakan tombol [orange][[WASD][]. Tahan tombol [orange]shift[] untuk mempercepat. Tahan [orange]CTRL[] saat menggunakan [orange]scrollwheel[] untuk memperbesar atau memperkecil tampilan. -tutorial.shoot.text = Gunakan mouse anda untuk mengarahkan, tahan [orange]tombol kiri mouse[] untuk menembak. Cobalah menembaki [yellow]target[]. -tutorial.moveAndroid.text = Untuk menggeser tampilan, seret satu jari ke layar. Jepit dan seret untuk memperbesar atau memperkecil tampilan. -tutorial.placeSelect.text = Coba pilih [yellow]konveyor[] dari menu blok di kanan bawah. -tutorial.placeConveyorDesktop.text = Gunakan [orange][[scrollwheel][] untuk memutar konveyor menghadap [orange]ke depan[], lalu letakkan di [yellow]lokasi yang ditandai[] menggunakan [orange][[tombol kiri mouse]][]. -tutorial.placeConveyorAndroid.text = Gunakan [orange][[tombol putar]][] untuk memutar konveyor menghadap [orange]ke depan[], seret ke posisi dengan satu jari, lalu letakkan di [yellow]lokasi yang ditandai[] dengan menggunakan [orange][[tanda centang][]. -tutorial.placeConveyorAndroidInfo.text = Sebagai alternatif, Anda dapat menekan ikon crosshair di kiri bawah untuk beralih ke [orange][[mode sentuh]][], dan letakkan blok dengan mengetuk layar. Dalam mode sentuh, blok bisa diputar dengan panah di kiri bawah. Tekan [yellow]berikutnya[] untuk mencobanya. -tutorial.placeDrill.text = Sekarang, pilih dan tempatkan [yellow]pertambangan battu[] di lokasi yang ditandai. -tutorial.blockInfo.text = Jika Anda ingin mempelajari lebih lanjut tentang blok, Anda dapat menekan [orange]tanda tanya[] di bagian kanan atas untuk membaca deskripsinya. -tutorial.deselectDesktop.text = Anda bisa membatalkan pemilihan blok menggunakan [orange][[tombol mouse kanan][]. -tutorial.deselectAndroid.text = Anda dapat membatalkan pemilihan blok dengan menekan tombol [orange]X (silang)[]. -tutorial.drillPlaced.text = Pertambangannya sekarang akan menghasilkan [yellow]batu[] yang dikeluarkan ke konveyor, lalu memindahkannya ke [yellow]intinya[]. -tutorial.drillInfo.text = Bijih yang berbeda membutuhkan pertambangan yang berbeda. Batu membutuhkan pertambangan batu, besi membutuhkan pertambangan besi, dll. -tutorial.drillPlaced2.text = Memindahkan barang ke dalam inti menempatkannya di [yellow]inventaris barang[] Anda, di kiri atas. Menempatkan blok menggunakan barang dari inventaris Anda. -tutorial.moreDrills.text = Anda bisa menghubungkan banyak pertambangan dan konveyor bersama-sama, seperti biasa. -tutorial.deleteBlock.text = Anda dapat menghapus blok dengan mengeklik [orange]tombol mouse kanan[] di blok yang ingin Anda hapus. Coba hapus konveyor ini. -tutorial.deleteBlockAndroid.text = Anda dapat menghapus blok dengan [orange]memilih crosshair[] di [orange]menu mode penghancur[] di kiri bawah dan mengetuk bloknya. Coba hapus konveyor ini. -tutorial.placeTurret.text = Sekarang, pilih dan tempatkan [yellow]turret[] di [yellow]lokasi yang ditandai[]. -tutorial.placedTurretAmmo.text = Turret ini sekarang akan menerima [yellow]amunisi[] dari konveyor. Anda dapat melihat berapa banyak amunisi yang dimiliki dengan menggeser kursor di bloknya dan memeriksa di [green]bilah hijau[]. -tutorial.turretExplanation.text = Turret secara otomatis akan menembak musuh terdekat dalam jangkauan, selama mereka memiliki cukup amunisi. -tutorial.waves.text = Setiap [yellow]60[] detik, gelombang [coral]musuh[] akan muncul di lokasi tertentu dan berusaha menghancurkan intinya. -tutorial.coreDestruction.text = Tujuan Anda adalah untuk [yellow]mempertahankan intinya[]. Jika intinya hancur, Anda [coral]kalah dalam permainan[]. -tutorial.pausingDesktop.text = Jika Anda perlu istirahat sebentar, tekan [orange]tombol jeda[] di bagian kiri atas atau [orange]tombol spasi[] untuk menghentikan sementara permainan. Anda masih bisa memilih dan menempatkan blok sambil berhenti, tapi tidak bisa bergerak atau menembak. -tutorial.pausingAndroid.text = Jika Anda perlu istirahat sebentar, tekan [orange]tombol jeda[] di kiri atas untuk menjeda permainan. Anda masih bisa menghapus dan menempatkan blok sambil berhenti sebentar. -tutorial.purchaseWeapons.text = Anda bisa membeli [yellow]senjata baru[] untuk robot Anda dengan membuka menu upgrade di kiri bawah. -tutorial.switchWeapons.text = Untuk mengganti senjata, klik ikonnya di kiri bawah, atau gunakan angka [orange][[1-9][]. -tutorial.spawnWave.text = Gelombang sekarang datang. Hancurkan mereka. -tutorial.pumpDesc.text = Pada gelombang selanjutnya, Anda mungkin perlu menggunakan [yellow]pompa[] untuk mendistribusikan cairan untuk generator atau ekstraktor. -tutorial.pumpPlace.text = Pompa bekerja seperti dengan pertambangan, namun mereka menghasilkan cairan dan bukan barang. Cobalah menempatkan pompa pada [yellow]minyak yang ditunjuk[]. -tutorial.conduitUse.text = Sekarang tempatkan [orange]saluran[] yang mengarah jauh dari pompa. -tutorial.conduitUse2.text = Dan beberapa lagi... -tutorial.conduitUse3.text = Dan beberapa lagi... -tutorial.generator.text = Sekarang, tempatkan [orange]blok generator pembakaran[] di ujung saluran. -tutorial.generatorExplain.text = Generator ini sekarang akan menciptakan [yellow]tenaga[] dari minyak. -tutorial.lasers.text = Tenaga didistribusikan menggunakan [yellow]laser tenaga[]. Putar dan tempatkan di sini. -tutorial.laserExplain.text = Generator sekarang akan memindahkan tenaga ke blok laser. Sinar [yellow]terang[] menandakan bahwa saat ini mentransmisikan tenaga, dan sinar [yellow]transparan[] berarti tidak. -tutorial.laserMore.text = Anda dapat memeriksa berapa banyak tenaga yang dimiliki blok dengan memindahkan kursor/mengetuk di atasnya dan memeriksa [yellow]bar kuning[] di bagian atas. -tutorial.healingTurret.text = Laser ini bisa digunakan untuk menyalakan [lime]turret perbaikan[]. Tempatkan satu di sini. -tutorial.healingTurretExplain.text = Selama memiliki tenaga, turret ini akan [lime]memperbaiki blok terdekat[]. Saat bermain, pastikan Anda memasukkannya ke markas Anda secepat mungkin! -tutorial.smeltery.text = Banyak blok yang membutuhkan [orange]baja[] agar dapat dibangun, yang membutuhkan [orange]peleburan[] untuk dibuat. Tempatkan satu di sini. -tutorial.smelterySetup.text = Peleburan ini sekarang akan menghasilkan [orange]baja[] dari besi yang masuk, dengan batubara sebagai bahan bakarnya. -tutorial.tunnelExplain.text = Perhatikan juga bahwa barang-barang itu masuk melalui [orange]blok terowongan[] dan muncul di sisi lain, melewati blok batu. Perlu diingat bahwa terowongan hanya bisa melalui sampai 2 blok. -tutorial.end.text = Dan itu menyimpulkan tutorialnya! Semoga berhasil! -keybind.move_x.name = gerak_x -keybind.move_y.name = gerak_y -keybind.select.name = pilih -keybind.break.name = hapus -keybind.shoot.name = tembak -keybind.zoom_hold.name = perbesar_tahan -keybind.zoom.name = perbesar -keybind.menu.name = menu -keybind.pause.name = jeda -keybind.dash.name = berlari -keybind.rotate_alt.name = putar_alt -keybind.rotate.name = putar -keybind.weapon_1.name = senjata_1 -keybind.weapon_2.name = senjata_2 -keybind.weapon_3.name = senjata_3 -keybind.weapon_4.name = senjata_4 -keybind.weapon_5.name = senjata_5 -keybind.weapon_6.name = senjata_6 -mode.waves.name = gelombang -mode.sandbox.name = sandbox -mode.freebuild.name = freebuild -upgrade.standard.name = standar -upgrade.standard.description = Robot standar. -upgrade.blaster.name = blaster -upgrade.blaster.description = Menembakan sebuah peluru yang lemah dan lambat. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Menembakan 3 peluru secara menyebar. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Menembakan sebuah granat eksplosif yang tidak akurat. -upgrade.beam.name = meriam sinar -upgrade.beam.description = Menembakan sinar laser jarak jauh. -upgrade.vulcan.name = vulcan -upgrade.vulcan.description = Menembakkan rombongan peluru dengan cepat. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Menembakkan ledakan yang menghancurkan dari pecahan peluru yang terisi. -item.stone.name = batu -item.iron.name = besi -item.coal.name = batu bara -item.steel.name = baja -item.titanium.name = titanium -item.dirium.name = dirium -item.uranium.name = uranium -item.sand.name = pasir -liquid.water.name = air -liquid.plasma.name = plasma -liquid.lava.name = lahar -liquid.oil.name = minyak -block.weaponfactory.name = pabrik senjata -block.weaponfactory.fulldescription = Dipakai untuk membuat senjata bagi robot pemain. Klik untuk memakai. Otomatis mengambil sumber daya dari inti. -block.air.name = udara -block.blockpart.name = bagian blok -block.deepwater.name = air dangkal -block.water.name = air -block.lava.name = lahar -block.oil.name = minyak -block.stone.name = batu -block.blackstone.name = batu hitam -block.iron.name = besi -block.coal.name = batu bara -block.titanium.name = titanium -block.uranium.name = uranium -block.dirt.name = tanah -block.sand.name = pasir -block.ice.name = es -block.snow.name = salju -block.grass.name = rumput -block.sandblock.name = blok pasir -block.snowblock.name = blok salju -block.stoneblock.name = blok batu -block.blackstoneblock.name = blok batu hitam -block.grassblock.name = blok rumput -block.mossblock.name = blok lumut -block.shrub.name = belukar -block.rock.name = batu -block.icerock.name = batu es -block.blackrock.name = batu hitam -block.dirtblock.name = blok tanah -block.stonewall.name = dinding batu -block.stonewall.fulldescription = Sebuah blok defensif yang murah. Berguna untuk melindungi inti dan turret di beberapa gelombang pertama. -block.ironwall.name = dinding besi -block.ironwall.fulldescription = Blok defensif dasar. Menyediakan perlindungan dari musuh. -block.steelwall.name = dinding baja -block.steelwall.fulldescription = Sebuah blok defensif standar. Perlindungan yang memadai dari musuh. -block.titaniumwall.name = dinding titanium -block.titaniumwall.fulldescription = Blok pertahanan yang kuat. Menyediakan perlindungan dari musuh. -block.duriumwall.name = dinding dirium -block.duriumwall.fulldescription = Blok pertahanan yang sangat kuat. Menyediakan perlindungan dari musuh. -block.compositewall.name = dinding komposit -block.steelwall-large.name = dinding baja besar -block.steelwall-large.fulldescription = Sebuah blok defensif standar. Membentang beberapa ubin. -block.titaniumwall-large.name = dinding titanium besar -block.titaniumwall-large.fulldescription = Blok pertahanan yang kuat. Membentang beberapa ubin. -block.duriumwall-large.name = dinding dirium yang besar -block.duriumwall-large.fulldescription = Blok pertahanan yang sangat kuat. Membentang beberapa ubin. -block.titaniumshieldwall.name = dinding perisai -block.titaniumshieldwall.fulldescription = Sebuah blok defensif yang kuat, dengan tambahan perisai. Membutuhkan tenaga. Menggunakan energi untuk menyerap peluru musuh. Dianjurkan untuk menggunakan pemercepat tenaga untuk memberi energi pada blok ini. -block.repairturret.name = turret perbaikan -block.repairturret.fulldescription = Memperbaiki blok terdekat yang rusak dengan lambat. Menggunakan sedikit tenaga. -block.megarepairturret.name = perbaikan turret II -block.megarepairturret.fulldescription = Memperbaiki blok yang rusak dengan normal. Menggunakan tenaga. -block.shieldgenerator.name = pembangkit perisai -block.shieldgenerator.fulldescription = Blok defensif yang maju. Mellindungi semua blok dalam radius dari serangan. Menggunakan tenaga dengan lambat saat menganggur, namun menyalurkan energi dengan cepat pada kontak peluru. -block.door.name = pintu -block.door.fulldescription = Blok yang bisa dibuka dan ditutup dengan mengetuknya. -block.door-large.name = pintu besar -block.door-large.fulldescription = Blok yang bisa dibuka dan ditutup dengan mengetuknya. -block.conduit.name = saluran -block.conduit.fulldescription = Blok pengangkut cairan dasar. Bekerja seperti konveyor, tapi dengan cairan. Terbaik digunakan dengan pompa atau saluran lainnya. Bisa digunakan sebagai jembatan di atas cairan untuk musuh dan pemain. -block.pulseconduit.name = saluran cepat -block.pulseconduit.fulldescription = Blok pengangkut cairan tingkat lanjut. Mengangkut cairan lebih cepat dan menyimpan lebih banyak dari pada saluran standar. -block.liquidrouter.name = router cairan -block.liquidrouter.fulldescription = Bekerja seperti router. Menerima masukan cairan dari satu sisi dan mengeluarkannya ke sisi yang lain. Berguna untuk pemisahan cairan dari satu saluran ke beberapa saluran lainnya. -block.conveyor.name = konveyor -block.conveyor.fulldescription = Blok dasar pengangkut barang. Memindahkan barang ke depan dan secara otomatis menyimpannya ke turret, ekstraktor, dan pertambangan. Bisa diputar. Bisa digunakan sebagai jembatan di atas cairan untuk musuh dan pemain. -block.steelconveyor.name = konveyor baja -block.steelconveyor.fulldescription = Blok transportasi barang lanjutan. Memindahkan barang lebih cepat dari konveyor standar. -block.poweredconveyor.name = konveyor cepat -block.poweredconveyor.fulldescription = Blok terbaik untuk pengangkutan barang. Memindahkan barang lebih cepat dari konveyor baja. -block.router.name = router -block.router.fulldescription = Menerima item dari satu arah dan mengeluarkannya ke 3 arah. Bisa juga menyimpan sejumlah barang. Berguna untuk membelah bahan dari satu pertambangan ke beberapa turret. -block.junction.name = persimpangan jalan -block.junction.fulldescription = Bertindak sebagai jembatan untuk dua sabuk persimpangan. Berguna dalam situasi dengan dua konveyor berbeda yang membawa bahan berbeda ke lokasi yang berbeda. -block.conveyortunnel.name = terowongan konveyor -block.conveyortunnel.fulldescription = Memindahkan barang di bawah blok. Untuk menggunakan, tempatkan satu terowongan yang menuju ke terowongan di bawah blok, dan satu di sisi lain. Pastikan kedua terowongan menghadap ke arah yang berlawanan, yaitu menuju blok yang mereka masukkan atau keluarkan. -block.liquidjunction.name = persimpangan cairan -block.liquidjunction.fulldescription = Bertindak sebagai jembatan untuk dua saluran persimpangan. Berguna dalam situasi dengan dua saluran berbeda yang membawa cairan berbeda ke lokasi yang berbeda. -block.liquiditemjunction.name = persimpangan barang-cairan -block.liquiditemjunction.fulldescription = Bertindak sebagai jembatan untuk menyilang saluran dan konveyor. -block.powerbooster.name = pemercepat tenaga -block.powerbooster.fulldescription = Mendistribusikan tenaga ke semua blok dalam radiusnya. -block.powerlaser.name = laser tenaga -block.powerlaser.fulldescription = Membuat laser yang mentransmisikan daya ke blok di depannya. Tidak menghasilkan tenaga itu sendiri. Terbaik digunakan dengan generator atau laser lainnya. -block.powerlaserrouter.name = router laser -block.powerlaserrouter.fulldescription = Laser yang mendistribusikan tenaga ke tiga arah sekaligus. Berguna dalam situasi di mana diperlukan tenaga ke beberapa blok dari satu generator. -block.powerlasercorner.name = sudut laser -block.powerlasercorner.fulldescription = Laser yang mendistribusikan tenaga ke dua arah sekaligus. Berguna dalam situasi di mana diperlukan tenaga ke beberapa blok dari satu generator, dan arah router kurang tepat. -block.teleporter.name = teleporter -block.teleporter.fulldescription = Blok transportasi barang lanjutan. Teleporter memasukkan barang ke teleporter lain dengan warna yang sama. Tidak ada apa-apa jika tidak ada teleporter dengan warna yang sama. Jika beberapa teleporter memiliki warna yang sama, teleporter dipilih secara acak. Menggunakan tenaga. Ketuk/klik untuk mengubah warna. -block.sorter.name = penyortir -block.sorter.fulldescription = Menyortir barang menurut jenis bahannya. Bahan yang diterima ditandai dengan warna di blok. Semua item yang sesuai dengan jenis bahan dilepaskan ke depan, segala sesuatu yang lain dikeluarkan ke kiri dan kanan. -block.core.name = inti -block.pump.name = pompa -block.pump.fulldescription = Memompa cairan dari sumber blok- biasanya air, lahar atau minyak. Mengeluarkan cairan ke saluran terdekat. -block.fluxpump.name = pompa flux -block.fluxpump.fulldescription = Sebuah versi lanjutan dari pompa. Menyimpan lebih banyak cairan dan memompa cairan lebih cepat. -block.smelter.name = peleburan -block.smelter.fulldescription = Blok kerajinan esensial. Saat dimasukkan 1 besi dan 1 batu bara sebagai bahan bakar, akan mengeluarkan satu baja. Disarankan untuk memasukkan besi dan batu bara ke sabuk yang berbeda untuk mencegah penyumbatan. -block.crucible.name = peleburan dirium -block.crucible.fulldescription = Sebuah blok kerajinan yang maju. Saat dimasukkan 1 titanium, 1 baja dan 1 batu bara sebagai bahan bakar, mengeluarkan satu dirium. Disarankan untuk memasukkan batubara, baja dan titanium pada sabuk yang berbeda untuk mencegah penyumbatan. -block.coalpurifier.name = ekstraktor batubara -block.coalpurifier.fulldescription = Blok ekstraktor dasar. mengeluarkan batu bara saat dipasok dengan air dan batu dalam skala yang besar. -block.titaniumpurifier.name = ekstraktor titanium -block.titaniumpurifier.fulldescription = Blok ekstraktor standar. mengeluarkan titanium bila dipasok dengan air dan besi dalam skala yang besar. -block.oilrefinery.name = penyulingan minyak -block.oilrefinery.fulldescription = Menyuling sejumlah minyak menjadi batubara. Berguna untuk memasok turret berbasis batubara saat penambangan batubara langka. -block.stoneformer.name = pembentuk batu -block.stoneformer.fulldescription = Mengubah lahar ke dalam batu. Berguna untuk menghasilkan batu dalam jumlah besar untuk pemurni batu bara. -block.lavasmelter.name = peleburan lava -block.lavasmelter.fulldescription = Menggunakan lahar untuk mengubah besi menjadi baja. Sebuah alternatif untuk peleburan batubara. Berguna dalam situasi di mana pertambangan batubara langka. -block.stonedrill.name = pertambangan batu -block.stonedrill.fulldescription = Pertambangan penting. Saat diletakkan di atas ubin batu, akan menghasilkan batu pada kecepatan yang lambat tanpa batas waktu. -block.irondrill.name = pertambangan besi -block.irondrill.fulldescription = Pertambangan dasar. Saat diletakkan di atas ubin bijih besi, akan mengeluarkan besi pada kecepatan yang lambat tanpa batas waktu. -block.coaldrill.name = pertambangan batubara -block.coaldrill.fulldescription = Pertambangan dasar. Saat ditempatkan di ubin bijih batubara, akan mengeluarkan batu bara pada kecepatan yang lambat tanpa batas waktu. -block.uraniumdrill.name = pertambangan uranium -block.uraniumdrill.fulldescription = Sebuah pertambangan yang canggih. Saat ditempatkan di ubin bijih uranium, akan mengeluarkan uranium pada kecepatan lambat tanpa batas waktu. -block.titaniumdrill.name = pertambangan titanium -block.titaniumdrill.fulldescription = Sebuah pertambangan yang canggih. Saat ditempatkan pada ubin bijih titanium, akan mengeluarkan titanium pada kecepatan lambat tanpa batas waktu. -block.omnidrill.name = pertambangan super -block.omnidrill.fulldescription = Pertambangan yang terbaik. Akan saya tambang bijih apapun itu ditempatkan pada kecepatan tinggi. -block.coalgenerator.name = pembangkit tenaga batubara -block.coalgenerator.fulldescription = Generator penting. Menghasilkan tenaga dari batu bara. Keluarkan tenaga sebagai laser ke 4 sisinya. -block.thermalgenerator.name = pembangkit tenaga panas -block.thermalgenerator.fulldescription = Menghasilkan tenaga dari lahar. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.combustiongenerator.name = pembangkit tenaga minyak -block.combustiongenerator.fulldescription = Menghasilkan tenaga dari minyak. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.rtgenerator.name = pembangkit tenaga radioaktif -block.rtgenerator.fulldescription = Menghasilkan sedikit tenaga dari peluruhan radioaktif uranium. Mengeluarkan tenaga sebagai laser ke 4 sisi. -block.nuclearreactor.name = reaktor nuklir -block.nuclearreactor.fulldescription = Versi lanjutan Pembangkit Tenaga Radioaktif, dan generator tenaga tertinggi. Menghasilkan tenaga dari uranium. Membutuhkan pendinginan air konstan. Sangat mudah menguap; akan meledak dengan hebat jika tidak cukup jumlah pendingin yang diberikan. -block.turret.name = turret -block.turret.fulldescription = Sebuah menara dasar yang murah. Menggunakan batu untuk amunisi. Memiliki jangkauan yang sedikit lebih banyak daripada turret ganda. -block.doubleturret.name = turret ganda -block.doubleturret.fulldescription = Versi turret standar yang sedikit lebih bertenaga. Menggunakan batu untuk amunisi. Memberikan damage secara signifikan lebih banyak, namun memiliki jangkauan yang lebih rendah. Menembak dua peluru. -block.machineturret.name = turret cepat -block.machineturret.fulldescription = Sebuah menara standar. Menggunakan besi untuk amunisi. Memiliki tembakan yang cepat dengan damage yang layak. -block.shotgunturret.name = turret split -block.shotgunturret.fulldescription = Sebuah turret standar. Menggunakan besi untuk amunisi. Menembakkan 7 peluru. Jaraknya pendek, namun damage-nya lebih tinggi daripada turret cepat. -block.flameturret.name = turret api -block.flameturret.fulldescription = Turret jarak dekat lanjutan. Menggunakan batubara untuk amunisi. Memiliki jangkauan yang pendek, namun sangat tinggi damage-nya. Bagus untuk jarak dekat. Dianjurkan untuk digunakan dibalik dinding. -block.sniperturret.name = turret railgun -block.sniperturret.fulldescription = Turret jarak jauh lanjutan. Menggunakan baja untuk amunisi. Kerusakan yang sangat tinggi, namun menembak dengan lambat. Mahal untuk digunakan, tapi bisa ditempatkan jauh dari garis musuh karena jangkauannya. -block.mortarturret.name = turret flak -block.mortarturret.fulldescription = Turret dengan akurasi pendek dan damage eksplosif. Menggunakan batubara untuk amunisi. Menembakkan peluru yang meledak lalu menjadi pecahan peluru. Berguna untuk kerumunan musuh yang besar. -block.laserturret.name = turret laser -block.laserturret.fulldescription = Turret satu target. Menggunakan tenaga. Memiliki jarak sedang yang bagus. Target tunggal saja. Tidak pernah meleset. -block.waveturret.name = turret tesla -block.waveturret.fulldescription = Turret target banyak. Menggunakan tenaga. Jaraknya sedang. Tidak pernah meleset. Rata-rata damage-nya kecil, namun bisa menembak beberapa musuh bersamaan dengan petir berantai. -block.plasmaturret.name = turret plasma -block.plasmaturret.fulldescription = Versi yang sangat maju dari turret api. Menggunakan batubara sebagai amunisi. Damage yang sangat tinggi, jaraknya pendek sampai sedang. -block.chainturret.name = turret berantai -block.chainturret.fulldescription = Menara api yang menembak dengan cepat. Menggunakan uranium sebagai amunisi. Menembak peluru besar dengan kecepatan tinggi. Jaraknya sedang. Membentang beberapa ubin. Sangat tangguh. -block.titancannon.name = meriam titan -block.titancannon.fulldescription = Turret jarak jauh terakhir. Menggunakan uranium sebagai amunisi. Menembakkan peluru yang meledak dengan cipratan besar dengan kecepatan sedang. Jarak jauh. Membentang beberapa ubin. Sangat tangguh. -block.playerspawn.name = spawn pemain -block.enemyspawn.name = spawn musuh \ No newline at end of file +text.about=Dibuat oleh [ROYAL]Anuken.[]\nAwalnya masuk di [orange]GDL[] MM Jam.\n\nKredit:\n- SFX dibuat dengan [YELLOW]bfxr[]\n- Musik dibuat oleh [GREEN]RoccoW[] / ditemukan di [lime]FreeMusicArchive.org[]\n\nTerima kasih khusus kepada:\n- [coral]MitchellFJN[]: playtesting dan umpan balik yang luas\n- [sky]Luxray5474[]: pekerjaan wiki, kontribusi kode\n- Semua penguji beta di itch.io dan Google Play\n +text.discord=Bergabunglah dengan Discord Mindustry! +text.gameover=Intinya hancur. +text.highscore=[YELLOW]Rekor baru! +text.lasted=Anda bertahan sampai gelombang +text.level.highscore=Skor Tinggi: [accent]{0} +text.level.delete.title=Konfirmasi Hapus +text.level.select=Pilih Level +text.level.mode=Modus permainan: +text.savegame=Simpan Permainan +text.loadgame=Lanjutkan +text.joingame=Bermain Bersama +text.quit=Keluar +text.about.button=Tentang +text.name=Nama: +text.players={0} pemain online +text.players.single={0} pemain online +text.server.mismatch=Kesalahan paket: kemungkinan versi client / server tidak sesuai.\nPastikan Anda dan host memiliki versi terbaru Mindustry! +text.server.closing=[accent]Menutup server... +text.server.kicked.kick=Anda telah dikeluarkan dari server! +text.server.kicked.invalidPassword=Kata sandi salah! +text.server.kicked.clientOutdated=Client versi lama! Update game Anda! +text.server.kicked.serverOutdated=Server versi lama! Tanyakan host untuk mengupdate! +text.server.connected={0} telah bergabung. +text.server.disconnected={0} telah terputus. +text.nohost=Tidak dapat meng-host server pada peta khusus! +text.hostserver=Host Server +text.host=Host +text.hosting=[accent]Membuka server... +text.hosts.refresh=Segarkan +text.hosts.discovering=Mencari game LAN +text.server.refreshing=Menyegarkan server +text.hosts.none=[lightgray]Tidak ada game LAN yang ditemukan! +text.host.invalid=[scarlet]Tidak dapat terhubung ke host. +text.server.friendlyfire=Tembak Sesama +text.server.add=Tambahkan Server +text.server.delete=Yakin ingin menghapus server ini? +text.server.hostname=Host: {0} +text.server.edit=Sunting Server +text.joingame.title=Bermain Bersama +text.joingame.ip=IP: +text.disconnect=Sambungan terputus. +text.connecting=[accent]Menghubungkan... +text.connecting.data=[accent]Memuat data level... +text.connectfail=[crimson]Gagal terhubung ke server: [orange]{0} +text.server.port=Port: +text.server.addressinuse=Alamat sudah di pakai! +text.server.invalidport=Nomor port salah! +text.server.error=[crimson]Kesalahan server hosting: [orange]{0} +text.save.new=Simpan Baru +text.save.overwrite=Yakin ingin mengganti slot simpan ini? +text.overwrite=Ganti +text.save.none=Tidak ada simpanan ditemukan! +text.saveload=[accent]Menyimpan... +text.savefail=Gagal menyimpan game! +text.save.delete.confirm=Yakin ingin menghapus save ini? +text.save.delete=Hapus +text.save.export=Ekspor Simpanan +text.save.import.invalid=[orange]Simpanan ini tidak valid! +text.save.import.fail=[crimson]Gagal mengimpor: [orange]{0} +text.save.export.fail=[crimson]Gagal mengekspor save: [orange]{0} +text.save.import=Impor Simpanan +text.save.newslot=Nama simpanan: +text.save.rename=Ganti nama +text.save.rename.text=Nama baru: +text.selectslot=Pilih simpanan. +text.slot=[accent]Slot{0} +text.save.corrupted=[orange]Simpanan rusak atau tidak valid! +text.empty= +text.on=Hidup +text.off=Mati +text.save.autosave=Simpan otomatis: {0} +text.save.map=Peta: {0} +text.save.wave=Gelombang {0} +text.save.date=Terakhir Disimpan: {0} +text.confirm=Konfirmasi +text.delete=Hapus +text.ok=OK +text.open=Buka +text.cancel=Batal +text.openlink=Buka tautan +text.back=Kembali +text.quit.confirm=Anda yakin ingin berhenti? +text.loading=[accent]Memuat... +text.wave=[orange]Gelombang {0} +text.wave.waiting=Gelombang dimulai {0} +text.waiting=Menunggu... +text.enemies={0} musuh +text.enemies.single={0} Musuh +text.loadimage=Buka Gambar +text.saveimage=Simpan Gambar +text.editor.badsize=[orange]Dimensi gambar tidak valid![]\nDimensi peta yang valid: {0} +text.editor.errorimageload=Kesalahan saat memuat file gambar:\n[orange]{0} +text.editor.errorimagesave=Kesalahan saat menyimpan file gambar:\n[orange]{0} +text.editor.generate=Hasilkan +text.editor.resize=Ubah ukuran +text.editor.loadmap=Buka Peta +text.editor.savemap=Simpan Peta +text.editor.loadimage=Buka Gambar +text.editor.saveimage=Simpan Gambar +text.editor.unsaved=[scarlet]Anda memiliki perubahan yang belum disimpan![]\nYakin ingin keluar? +text.editor.resizemap=Ubah ukuran peta +text.editor.mapname=Nama Peta: +text.editor.overwrite=[accent]Peringatan!\nIni akan mengganti peta yang ada. +text.editor.selectmap=Pilih peta yang akan dimuat: +text.width=Lebar: +text.height=Tinggi: +text.menu=Menu +text.play=Main +text.load=Buka +text.save=Simpan +text.language.restart=Silakan mulai ulang permainan Anda agar pengaturan bahasa mulai berlaku. +text.settings.language=Bahasa +text.settings=Pengaturan +text.tutorial=Tutorial +text.editor=Pengedit +text.mapeditor=Pengedit Peta +text.donate=Sumbangkan +text.settings.reset=Atur ulang ke Default +text.settings.controls=Kontrol +text.settings.game=Permainan +text.settings.sound=Suara +text.settings.graphics=Grafis +text.upgrades=Perbaruan +text.purchased=[LIME]Dibuat! +text.weapons=Senjata +text.paused=Jeda +text.info.title=[accent]Info +text.error.title=[crimson]Telah terjadi kesalahan +text.error.crashtitle=Telah terjadi kesalahan +text.blocks.blockinfo=Info Blok +text.blocks.powercapacity=Kapasitas Tenaga +text.blocks.powershot=Tenaga/tembakan +text.blocks.size=Ukuran +text.blocks.liquidcapacity=Kapasitas cairan +text.blocks.maxitemssecond=Batas barang/detik +text.blocks.powerrange=Jangkauan tenaga +text.blocks.itemcapacity=Kapasitas Barang +text.blocks.inputliquid=Cairan yang Masuk +text.blocks.inputitem=Barang yang Masuk +text.blocks.explosive=Mudah meledak! +text.blocks.health=Darah +text.blocks.inaccuracy=Ketidaktelitian +text.blocks.shots=Tembakan +text.blocks.inputcapacity=Kapasitas masuk +text.blocks.outputcapacity=Kapasitas keluar +setting.difficulty.easy=mudah +setting.difficulty.normal=normal +setting.difficulty.hard=sulit +setting.difficulty.insane=sangat susah +setting.difficulty.purge=paling susah +setting.difficulty.name=Kesulitan: +setting.screenshake.name=Layar Bergoyang +setting.indicators.name=Indikator Musuh +setting.effects.name=Efek Tampilan +setting.sensitivity.name=Sensitivitas Pengendali +setting.saveinterval.name=Waktu Simpan Otomatis +setting.seconds={0} Detik +setting.fullscreen.name=Layar Penuh +setting.fps.name=Tunjukkan FPS +setting.vsync.name=VSync +setting.lasers.name=Tampilkan Laser Tenaga +setting.healthbars.name=Tampilkan Bar Darah Entitas +setting.musicvol.name=Volume Musik +setting.mutemusic.name=Bisukan Musik +setting.sfxvol.name=Volume Suara +setting.mutesound.name=Bisukan Suara +map.maze.name=labirin +map.fortress.name=benteng +map.sinkhole.name=lubang pembuangan +map.caves.name=gua +map.volcano.name=gunung berapi +map.caldera.name=kaldera +map.scorch.name=penghangusan +map.desert.name=gurun +map.island.name=pulau +map.grassland.name=padang rumput +map.tundra.name=tundra +map.spiral.name=spiral +map.tutorial.name=tutorial +keybind.move_x.name=gerak_x +keybind.move_y.name=gerak_y +keybind.select.name=pilih +keybind.break.name=hapus +keybind.shoot.name=tembak +keybind.zoom_hold.name=perbesar_tahan +keybind.zoom.name=perbesar +keybind.menu.name=menu +keybind.pause.name=jeda +keybind.dash.name=berlari +keybind.rotate_alt.name=putar_alt +keybind.rotate.name=putar +mode.waves.name=gelombang +mode.sandbox.name=sandbox +mode.freebuild.name=freebuild +item.stone.name=batu +item.coal.name=batu bara +item.titanium.name=titanium +item.thorium.name=thorium +item.sand.name=pasir +liquid.water.name=air +liquid.lava.name=lahar +liquid.oil.name=minyak +block.door.name=pintu +block.door-large.name=pintu besar +block.conduit.name=saluran +block.pulseconduit.name=saluran cepat +block.liquidrouter.name=router cairan +block.conveyor.name=konveyor +block.router.name=router +block.junction.name=persimpangan jalan +block.liquidjunction.name=persimpangan cairan +block.sorter.name=penyortir +block.smelter.name=peleburan +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.multithread.name=Multithreading +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_ita.properties b/core/assets/bundles/bundle_ita.properties index 02b83f8158..4b02d88130 100644 --- a/core/assets/bundles/bundle_ita.properties +++ b/core/assets/bundles/bundle_ita.properties @@ -1,551 +1,495 @@ -text.about = Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginariamente era una voce nel [orange]GDL[] Metal Monstrosity Jam.\n\n Crediti:\n - SFX realizzato con [YELLOW]bfxr [] \n - Musica creata da [GREEN]RoccoW[] / trovata su [lime]FreeMusicArchive.org[]\n\n Un ringraziamento speciale a:\n - [coral]MitchellFJN []: esteso test del gioco e feedback\n - [sky]Luxray5474 []: lavorazione della wiki, contributi col codice\n - [lime]Epowerj []: sistema di costruzione del codice, icone\n - Tutti i beta tester su itch.io e Google Play\n -text.credits = Crediti -text.discord = Unisciti sul server discord di mindustry! -text.changes = [SCARLET]Attenzione!\n[]Alcune importanti meccaniche di gioco sono state modificate.\n\n - [accent]I teletrasporti[] ora usano la corrente.\n - [accent]Le fornaci[] e [accent]i crogioli[] ora hanno una capacità massima di oggetti. \n- [accent]I crogioli[] ora richiedono il carbone come combustibile. -text.link.discord.description = la chatroom ufficiale del server discord di Mindustry -text.link.github.description = Codice sorgente del gioco -text.link.dev-builds.description = Build di sviluppo versioni instabili -text.link.trello.description = Scheda ufficiale trello per funzionalità pianificate -text.link.itch.io.description = pagina di itch.io con download per PC e versione web -text.link.google-play.description = Elenco di Google Play Store -text.link.wiki.description = wiki ufficiale di Mindustry -text.linkfail = Impossibile aprire il link! L'URL è stato copiato nella tua bacheca. -text.editor.web = La versione web non supporta l'editor! Scarica il gioco per usarlo. -text.multiplayer.web = Questa versione del gioco non supporta il multiplayer! Per giocare in multiplayer dal tuo browser, usa il link \"versione web multiplayer\" nella pagina itch.io. -text.gameover = Il nucleo è stato distrutto. -text.highscore = [YELLOW]Nuovo record! -text.lasted = Sei durato fino all'onda -text.level.highscore = Migliore: [accent]{0} -text.level.delete.title = Conferma Eliminazione -text.level.delete = Sei sicuro di voler eliminare la mappa \"[arancione]{0}\"? -text.level.select = Selezione del livello -text.level.mode = Modalità di gioco: -text.savegame = Salva -text.loadgame = Carica -text.joingame = Gioca MP -text.newgame = Nuovo gioco -text.quit = Esci -text.about.button = Informazioni -text.name = Nome: -text.public = Pubblico -text.players = {0} giocatori online -text.server.player.host = {0} (host) -text.players.single = {0} giocatori online -text.server.mismatch = Errore nel pacchetto: possibile discrepanza nella versione client / server. Assicurati che tu e l'host abbiate l'ultima versione di Mindustry! -text.server.closing = [accent]Chiusura server ... -text.server.kicked.kick = Sei stato cacciato dal server! -text.server.kicked.invalidPassword = 10468 = Password non valida. -text.server.kicked.clientOutdated = Versione del client obsoleta! Aggiorna il tuo gioco! -text.server.kicked.serverOutdated = Server obsoleto! Chiedi all'host di aggiornare! -text.server.kicked.banned = Sei stato bannato su questo server. -text.server.kicked.recentKick = Sei stato cacciato di recente. Attendi prima di connetterti di nuovo. -text.server.connected = {0} si è connesso -text.server.disconnected = {0} si è disconnesso -text.nohost = Impossibile hostare il server con una mappa personalizzata! -text.host.info = Il pulsante [accent]hos [] ospita un server sulle porte [scarlet]6567[] e [scarlet]656.[] Chiunque sulla stessa [LIGHT_GRAY]connessione wifi o rete locale[] dovrebbe essere in grado di vedere il proprio server nel proprio elenco server.\n\n Se vuoi che le persone siano in grado di connettersi ovunque tramite IP, è richiesto il [accent]port forwarding[]. \n\n[LIGHT_GRAY]Nota: se qualcuno sta riscontrando problemi durante la connessione al gioco LAN, assicurati di aver consentito a Mindustry di accedere alla rete locale nelle impostazioni del firewall. -text.join.info = Qui è possibile inserire un [accent]IP del server[] a cui connettersi, o scoprire [accento]un server sulla rete locale[] disponibile.\n Sono supportati sia il multiplayer LAN che WAN. \n\n[LIGHT_GRAY]Nota: non esiste un elenco di server globali automatici; se si desidera connettersi a qualcuno tramite IP, è necessario chiedere all'host il proprio IP. -text.hostserver = Server host -text.host = Host -text.hosting = [accento] Apertura del server ... -text.hosts.refresh = Aggiorna -text.hosts.discovering = Scoperta partite LAN -text.server.refreshing = Aggiornamento server -text.hosts.none = [lightgray]Nessuna partita LAN trovata! -text.host.invalid = [scarlet]Impossibile connettersi all'host. -text.server.friendlyfire = Fuoco amico -text.trace = Trace Player -text.trace.playername = Nome del giocatore: [accent]{0} -text.trace.ip = IP: [accent]{0} -text.trace.id = ID univoco: [accent]{0} -text.trace.android = Client Android: [accent] {0} -text.trace.modclient = Cliente personalizzato: [accent]{0} -text.trace.totalblocksbroken = Totale blocchi interrotti: [accent]{0} -text.trace.structureblocksbroken = Blocchi strutturali distrutti: [accent]{0} -text.trace.lastblockbroken = Ultimo blocco distrutto: [accent]{0} -text.trace.totalblocksplaced = Totale blocchi posizionati: [accent]{0} -text.trace.lastblockplaced = Ultimo blocco inserito: [accent]{0} -text.invalidid = ID cliente non valido! Invia una segnalazione di bug. -text.server.bans = Lista Ban -text.server.bans.none = Nessun giocatore bandito trovato! -text.server.admins = Amministratori -text.server.admins.none = Nessun amministratore trovato! -text.server.add = Aggiungi server -text.server.delete = Sei sicuro di voler eliminare questo server? -text.server.hostname = Host: {0} -text.server.edit = Modifica server -text.server.outdated = [crimson]Server obsoleto![] -text.server.outdated.client = [crimson]Client obsoleto![] -text.server.version = [lightgray]Versione: {0} -text.server.custombuild = [yellow] Costruzione personalizzata -text.confirmban = Sei sicuro di voler bandire questo giocatore? -text.confirmunban = Sei sicuro di voler sbloccare questo giocatore? -text.confirmadmin = Sei sicuro di voler rendere questo giocatore un amministratore? -text.confirmunadmin = Sei sicuro di voler rimuovere lo stato di amministratore da questo player? -text.joingame.byip = Unisciti a IP ... -text.joingame.title = Unisciti alla Partita -text.joingame.ip = IP: -text.disconnect = Disconnesso. -text.disconnect.data = Errore nel caricamento i dati del mondo! -text.connecting = [accent]Connessione in corso ... -text.connecting.data = [accent]Caricamento dei dati del mondo ... -text.connectfail = [crimson] Impossibile connettersi al server: [orange] {0} -text.server.port = Porta: -text.server.addressinuse = Indirizzo già in uso! -text.server.invalidport = Numero di porta non valido! -text.server.error = [crimson]Errore nell'hosting del server: [orange] {0} -text.tutorial.back = < Prec -text.tutorial.next = Succ > -text.save.new = Nuovo Salvataggio -text.save.overwrite = Sei sicuro di voler sovrascrivere questo salvataggio? -text.overwrite = Sostituisci -text.save.none = Nessun salvataggio trovato! -text.saveload = [Accent]Salvataggio ... -text.savefail = Salvataggio del gioco non riuscito! -text.save.delete.confirm = Sei sicuro di voler eliminare questo salvataggio? -text.save.delete = Elimina -text.save.export = Esporta Salva -text.save.import.invalid = [orange]Questo salvataggio non è valido! -text.save.import.fail = [crimson]Impossibile importare salvataggio: [orange]{0} -text.save.export.fail = [crimson]Impossibile esportare il salvataggio: [orange]{0} -text.save.import = Importa Salvataggio -text.save.newslot = Salva nome: -text.save.rename = Rinomina -text.save.rename.text = Nuovo nome: -text.selectslot = Seleziona un salvataggio. -text.slot = [accent]Slot {0} -text.save.corrupted = [orang]File di salvataggio danneggiato o non valido! -text.empty = -text.on = Acceso -text.off = Spento -text.save.autosave = Salvataggio automatico: {0} -text.save.map = mappa -text.save.wave = Ondata: -text.save.difficulty = Difficolta: {0} -text.save.date = Ultimo salvataggio: {0} -text.confirm = Conferma -text.delete = Elimina -text.ok = OK -text.open = Apri -text.cancel = Annulla -text.openlink = Apri Link -text.copylink = Copia link -text.back = Indietro -text.quit.confirm = Sei sicuro di voler uscire? -text.changelog.title = Registro modifiche -text.changelog.loading = Ottenere il registro delle modifiche ... -text.changelog.error.android = [orange]Nota che il log delle modifiche non funziona su Android 4.4 e versioni precedenti! Ciò è dovuto a un bug interno di Android. -text.changelog.error = [scarlet]Errore durante il recupero del changelog! Controlla la tua connessione Internet. -text.changelog.current = [yellow][[Current version] -text.changelog.latest = [orange][[Latest version] -text.loading = [accent]Caricamento in corso ... -text.wave = [orange]Onda {0} -text.wave.waiting = Onda in {0} -text.waiting = In attesa... -text.enemies = {0} Nemici -text.enemies.single = {0} Nemico -text.loadimage = Carica immagine -text.saveimage = Salva Immagine -text.oregen = Generazione dei minerali -text.editor.badsize = [orange]Dimensioni dell'immagine non valide![]\n Dimensioni della mappa valide: {0} -text.editor.errorimageload = Errore durante il caricamento del file immagine:\n [orange]{0} -text.editor.errorimagesave = Errore durante il salvataggio del file immagine:\n [orange]{0} -text.editor.generate = Genera -text.editor.resize = Zomma o \nRiduci -text.editor.loadmap = Carica\nmappa -text.editor.savemap = Salva\nla mappa -text.editor.loadimage = Carica\nimmagine -text.editor.saveimage = Salva\nImmagine -text.editor.unsaved = [scarlet]Hai modifiche non salvate![]\nSei sicuro di voler uscire? -text.editor.brushsize = Dimensione del pennello: {0} -text.editor.noplayerspawn = Questa mappa non ha lo spawnpoint del giocatore! -text.editor.manyplayerspawns = Le mappe non possono avere più di un punto di spawn di un giocatore! -text.editor.manyenemyspawns = Non puoi avere più di {0} spawn nemici! -text.editor.resizemap = Ridimensiona la mappa -text.editor.resizebig = [Scarlet]Attenzione!\n[]Le mappe più grandi di 256 unità potrebbero causare del lag oltre ad essere instabili. -text.editor.mapname = Nome Mappa: -text.editor.overwrite = [Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. -text.editor.failoverwrite = [crimson]Impossibile sovrascrivere la mappa di default! -text.editor.selectmap = Seleziona una mappa da caricare: -text.width = Larghezza: -text.height = Altezza: -text.randomize = Randomizza -text.apply = Applicare -text.update = Aggiorna -text.menu = Menu -text.play = Gioca -text.load = Carica -text.save = Salva -text.language.restart = Riavvia il gioco affinché il cambiamento della lingua abbia effetto. -text.settings.language = Lingua -text.settings = Impostazioni -text.tutorial = Lezioni -text.editor = Editor -text.mapeditor = Editor delle mappe -text.donate = Dona -text.settings.reset = Resetta Alle Impostazioni Predefinite -text.settings.controls = Controlli -text.settings.game = Gioco -text.settings.sound = Suono -text.settings.graphics = Grafica -text.upgrades = Miglioramenti -text.purchased = [LIME]Creato! -text.weapons = Armi -text.paused = In pausa -text.respawn = Rinascita in -text.info.title = [Accent]Informazioni -text.error.title = [crimson]Si è verificato un errore -text.error.crashmessage = [SCARLET]Si è verificato un errore imprevisto che ha causato un arresto anomalo.[] Si prega di segnalare le circostanze esatte in cui questo errore si è verificato allo sviluppatore:\n[ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Si è verificato un errore -text.mode.break = Modalità di interruzione: {0} -text.mode.place = Modalità luogo: {0} -placemode.hold.name = linea -placemode.areadelete.name = area -placemode.touchdelete.name = toccare -placemode.holddelete.name = trattieni -placemode.none.name = nessuno -placemode.touch.name = toccare -placemode.cursor.name = cursore -text.blocks.extrainfo = [accent]informazioni extra sui blocchi: -text.blocks.blockinfo = Informazioni sul blocco -text.blocks.powercapacity = Capacità energetica -text.blocks.powershot = Danno/Colpo -text.blocks.powersecond = Energia/Secondo -text.blocks.powerdraindamage = Consumo/Danno -text.blocks.shieldradius = Raggio dello scudo -text.blocks.itemspeedsecond = Velocita Oggetti/Secondo -text.blocks.range = Gamma -text.blocks.size = Grandezza -text.blocks.powerliquid = Energia/Liquido -text.blocks.maxliquidsecond = Max liquido/Secondo -text.blocks.liquidcapacity = Capacità del liquido -text.blocks.liquidsecond = Liquido/Secondo -text.blocks.damageshot = Danni colpo -text.blocks.ammocapacity = Capacità del caricatore -text.blocks.ammo = Munizioni -text.blocks.ammoitem = Munizioni/Oggetto -text.blocks.maxitemssecond = Oggetti massimi/secondo -text.blocks.powerrange = Raggio Energia -text.blocks.lasertilerange = Raggio piastrelle laser -text.blocks.capacity = Capacità -text.blocks.itemcapacity = Capacità oggetto -text.blocks.maxpowergenerationsecond = Massima Energia Generata/secondo -text.blocks.powergenerationsecond = Energia generata/secondo -text.blocks.generationsecondsitem = Generazione secondi/oggetto -text.blocks.input = Ingresso -text.blocks.inputliquid = Ingresso del liquido -text.blocks.inputitem = Ingresso Oggetto -text.blocks.output = Uscita -text.blocks.secondsitem = Secondi/item -text.blocks.maxpowertransfersecond = Massimo trasferimento di potenza/secondo -text.blocks.explosive = Altamente esplosivo! -text.blocks.repairssecond = Ripara/secondo -text.blocks.health = Salute -text.blocks.inaccuracy = inesattezza -text.blocks.shots = Colpi -text.blocks.shotssecond = Colpi/secondo -text.blocks.fuel = Carburante -text.blocks.fuelduration = Durata del carburante -text.blocks.maxoutputsecond = Uscita max/secondo -text.blocks.inputcapacity = Capacità di ingresso -text.blocks.outputcapacity = Capacità di uscita -text.blocks.poweritem = Energia/Oggetto -text.placemode = Place Mode -text.breakmode = Modalità di interruzione -text.health = Salutee -setting.difficulty.easy = facile -setting.difficulty.normal = medio -setting.difficulty.hard = difficile -setting.difficulty.insane = Folle -setting.difficulty.purge = Epurazione -setting.difficulty.name = Difficoltà: -setting.screenshake.name = Screen Shake -setting.smoothcam.name = Smooth Camera -setting.indicators.name = Indicatori nemici -setting.effects.name = Visualizza effetti -setting.sensitivity.name = Sensibilità del controllore. -setting.saveinterval.name = Intervallo di salvataggio automatico -setting.seconds = {0} Secondi -setting.fullscreen.name = Schermo Intero -setting.multithread.name = multithreading -setting.fps.name = Mostra FPS -setting.vsync.name = Sincronizzazione Verticale -setting.lasers.name = Mostra Energia Dei Laser -setting.healthbars.name = Mostra barra della salute delle entità -setting.pixelate.name = Schermo Pixelate -setting.musicvol.name = Volume Musica -setting.mutemusic.name = Musica muta -setting.sfxvol.name = Volume SFX -setting.mutesound.name = Suono muto -map.maze.name = labirinto -map.fortress.name = fortezza -map.sinkhole.name = dolina -map.caves.name = grotte -map.volcano.name = vulcano -map.caldera.name = caldera -map.scorch.name = bruciatura -map.desert.name = Deserto -map.island.name = Isola -map.grassland.name = Prateria -map.tundra.name = Tundra -map.spiral.name = spirale -map.tutorial.name = Tutorial -tutorial.intro.text = [yellow]Benvenuti nel tutorial.[] Per iniziare, premere 'succ'. -tutorial.moveDesktop.text = Per spostarsi, utilizza i tasti [orange][[WASD][] . Tenere premuto [orange]shift []per correre. Tenere premuto [orange]CTRL[] mentre si utilizza la [orange]rotella del mouse[] per ingrandire o ridurre lo zoom. -tutorial.shoot.text = Usa il mouse per mirare, tieni premuto [orange]tasto sinistro del mouse[] per sparare. Fai uun po' di pratica con quest' [yellow]obiettivo[]. -tutorial.moveAndroid.text = Per spostare la vista, trascina un dito sullo schermo. Pizzica e trascina per ingrandire o ridurre. -tutorial.placeSelect.text = Prova a selezionare un [yellow]nastro trasportatore[] dal menu dei blocchi in basso a destra. -tutorial.placeConveyorDesktop.text = Utilizza la [orange]rotellina di scorrimento[] per ruotare il nastro trasportatore in modo che sia rivolto verso [orange]in avanti[], quindi posizionarlo nella [yellow]posizione contrassegnata[] utilizzando il [orange]tasto sinistro del mouse[]. -tutorial.placeConveyorAndroid.text = Utilizzare il pulsante [orange]tasto di rotazione[] per ruotare il trasportatore in modo che sia rivolto [orange]in avanti[], trascinalo in posizione con un dito, quindi posizionalo nella [yellow]posizione contrassegnata[] utilizzando [orange]segno di spunta[] -tutorial.placeConveyorAndroidInfo.text = In alternativa, puoi premere l'icona mirino in basso a sinistra per passare alla [orange] touch mode[] e posiziona i blocchi toccando sullo schermo. In modalità touch, i blocchi possono essere ruotati con la freccia in basso a sinistra. Premi [yellow]avanti[] per provarlo. -tutorial.placeDrill.text = Ora, seleziona e posiziona un [yellow]trapano per pietra[] nella posizione contrassegnata. -tutorial.blockInfo.text = Se vuoi saperne di più su un blocco, puoi toccare il [orange]punto interrogativo[] in alto a destra per leggere la sua descrizione. -tutorial.deselectDesktop.text = Puoi deselezionare un blocco usando [orange]tasto destro del mouse[]. -tutorial.deselectAndroid.text = È possibile deselezionare un blocco premendo il tasto [orange]X[]. -tutorial.drillPlaced.text = Il trapano ora produrrà [yellow]pietra,[] la manderà sul nastro trasportatore, quindi la sposterà nel [yellow]nucleo[]. -tutorial.drillInfo.text = I minerali differenti hanno bisogno di trapani diversi. La pietra richiede il trapano di pietra, il ferro richiede il trapano di ferro, ecc. -tutorial.drillPlaced2.text = Spostando gli oggetti nel nucleo li metti nell' [yellow]inventario[], in alto a sinistra. Piazzare i blocchi usa gli oggetti dal tuo inventario. -tutorial.moreDrills.text = Puoi collegare molti trapani e trasportatori insieme, in questo modo. -tutorial.deleteBlock.text = È possibile eliminare i blocchi facendo clic sul [orange]pulsante destro del mouse[] sul blocco che si desidera eliminare. Prova a eliminare questo trasportatore. -tutorial.deleteBlockAndroid.text = È possibile eliminare i blocchi [orange]selezionandoli col mirino[] nel menu della [orange]modalità pausa[] in basso a sinistra e toccando un blocco. Prova a eliminare questo trasportatore. -tutorial.placeTurret.text = Ora, seleziona e posiziona una [yellow]torretta[] nella [yellow]posizione contrassegnata[]. -tutorial.placedTurretAmmo.text = Questa torretta ora accetta [yellow]munizioni[] dal trasportatore. Puoi vedere quante munizioni ha al passaggio del mouse [green]barra verde[]. -tutorial.turretExplanation.text = Le torrette spareranno automaticamente al nemico più vicino nel raggio d'azione, a patto che abbiano munizioni sufficienti. -tutorial.waves.text = Ogni [yellow]60[] secondi, un'ondata di [coral]nemici[] si genera in posizioni specifiche e tenta di distruggere il nucleo. -tutorial.coreDestruction.text = Il tuo obiettivo è difendere [yellow]il nucleo[]. Se il nucleo viene distrutto, tu [coral]perdi la partita[]. -tutorial.pausingDesktop.text = Se hai bisogno di fare una pausa, premi il [orange]pulsante di pausa[] in alto a sinistra per mettere in pausa il gioco. Puoi ancora selezionare e posizionare i blocchi mentre sei in pausa, ma non puoi muoverti o sparare -tutorial.pausingAndroid.text = Se hai bisogno di fare una pausa, premi il [orange]pulsante di pausa[] in alto a sinistra per mettere in pausa il gioco. Puoi ancora rompere e posizionare i blocchi mentre sei in pausa. -tutorial.purchaseWeapons.text = Puoi acquistare nuove [yellow]armi[] per il tuo mech aprendo il menu di aggiornamenti in basso a sinistra. -tutorial.switchWeapons.text = Cambia le armi facendo clic sulla sua icona in basso a sinistra o usando i numeri [orange][[1-9][]. -tutorial.spawnWave.text = Ecco un'ondata ora. Distruggili. -tutorial.pumpDesc.text = Nelle onde successive, potrebbe essere necessario utilizzare le [yellow]pompe[] per distribuire i liquidi per i generatori o gli estrattori. -tutorial.pumpPlace.text = Le pompe funzionano in modo simile ai trapani, tranne per il fatto che producono liquidi anziché oggetti. Prova a posizionare una pompa sull' [yellow]petrolio evidenziato[]. -tutorial.conduitUse.text = Ora posiziona una [orange]conduttura[] -tutorial.conduitUse2.text = E alcuni altri ... -tutorial.conduitUse3.text = E alcuni altri ... -tutorial.generator.text = Ora, posizionare un [orange]generatore a combustione[] all'estremità del condotto. -tutorial.generatorExplain.text = Questo generatore ora creerà [yellow]corrente[] dall'petrolio. -tutorial.lasers.text = La potenza è distribuita usando i [yellow]laser energetici[]. Ruota e posizionane uno qui. -tutorial.laserExplain.text = Il generatore ora trasferirà l'energia nel blocco laser. Un raggio [yellow]opaco[] indica che sta trasmettendo corrente e un raggio [yellow]trasparente[] significa che non la sta strasmettendo. -tutorial.laserMore.text = Puoi controllare quanta energia ha un blocco passandoci sopra e controllando la barra [yellow]gialla[] in alto. -tutorial.healingTurret.text = Questo laser può essere utilizzato per alimentare una [lime]torretta di riparazione[]. Mettine una qui. -tutorial.healingTurretExplain.text = Finché ha energia, questa torretta [lime]riparerà i blocchi vicini.[] Durante la riproduzione del gioco, assicurati di averne una nella tua base il più rapidamente possibile! -tutorial.smeltery.text = Molti blocchi richiedono [orange]acciaio[] da produrre, che richiede una [orange] fonderia[] per la produzione. Mettine una qui. -tutorial.smelterySetup.text = Questa fonderia produrrà ora [orange]acciaio[] dal ferro in ingresso, usando il carbone come combustibile. -tutorial.tunnelExplain.text = Si noti inoltre che gli oggetti passano attraverso un[orange]tunnel[] e emergono dall'altra parte, passando attraverso il blocco di pietra. Tieni presente che i tunnel possono attraversare fino a 2 blocchi. -tutorial.end.text = E questo conclude il tutorial! In bocca al lupo! -text.keybind.title = Configurazione Tasti -keybind.move_x.name = move_x -keybind.move_y.name = move_y -keybind.select.name = seleziona -keybind.break.name = rompere -keybind.shoot.name = sparare -keybind.zoom_hold.name = zoom_hold -keybind.zoom.name = zoom -keybind.block_info.name = Informazioni blocco -keybind.menu.name = menu -keybind.pause.name = pausa -keybind.dash.name = corsa -keybind.chat.name = Chat -keybind.player_list.name = lista_giocatori -keybind.console.name = console -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Ruotare -keybind.weapon_1.name = arma_1 -keybind.weapon_2.name = arma_2 -keybind.weapon_3.name = arma_3 -keybind.weapon_4.name = arma_4 -keybind.weapon_5.name = arma_5 -keybind.weapon_6.name = arma_6 -mode.text.help.title = Descrizione delle modalità -mode.waves.name = onde -mode.waves.description = modalità normale. risorse limitate e onde in entrata automatiche. -mode.sandbox.name = Sandbox -mode.sandbox.description = risorse infinite e nessun timer per le onde. -mode.freebuild.name = freebuild -mode.freebuild.description = risorse limitate e nessun timer per le onde. -upgrade.standard.name = Standard -upgrade.standard.description = Il mech standard. -upgrade.blaster.name = blaster -upgrade.blaster.description = Spara un proiettile lento, debole. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Spara 3 proiettili a diffusione. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Spara delle imprecise granate esplosive. -upgrade.beam.name = cannone a raggi -upgrade.beam.description = Spara un raggio laser penetrante a lungo raggio. -upgrade.vulcan.name = Vulcano -upgrade.vulcan.description = Spara una raffica di proiettili veloci. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Spara a una devastante esplosione di shrapnel carichi. -item.stone.name = pietra -item.iron.name = ferro -item.coal.name = carbone -item.steel.name = acciaio -item.titanium.name = titanio -item.dirium.name = diridio -item.uranium.name = uranio -item.sand.name = sabbia -liquid.water.name = acqua -liquid.plasma.name = Plasma -liquid.lava.name = lava -liquid.oil.name = petrolio -block.weaponfactory.name = fabbrica d'armi -block.weaponfactory.fulldescription = Utilizzata per creare armi per il giocatore mech. Clicca per usare. Prende automaticamente le risorse dal core. -block.air.name = aria -block.blockpart.name = blockpart -block.deepwater.name = acque profonde -block.water.name = acqua -block.lava.name = lava -block.oil.name = petrolio -block.stone.name = pietra -block.blackstone.name = pietra nera -block.iron.name = ferro -block.coal.name = carbone -block.titanium.name = titanio -block.uranium.name = uranio -block.dirt.name = terra -block.sand.name = sabbia -block.ice.name = ghiaccio -block.snow.name = neve -block.grass.name = Erba -block.sandblock.name = blocco di sabbia -block.snowblock.name = blocco di neve -block.stoneblock.name = blocco di pietra -block.blackstoneblock.name = blocco di pietra nera -block.grassblock.name = blocco d'erba -block.mossblock.name = blocco di muschio -block.shrub.name = arbusto -block.rock.name = roccia -block.icerock.name = giaccio -block.blackrock.name = roccia nera -block.dirtblock.name = blocco di terra -block.stonewall.name = muro di pietra -block.stonewall.fulldescription = Un blocco difensivo poco costoso. Utile per proteggere il nucleo e le torrette nelle prime ondate. -block.ironwall.name = muro di ferro -block.ironwall.fulldescription = Un blocco difensivo di base. Fornisce protezione dai nemici. -block.steelwall.name = muro d'acciaio -block.steelwall.fulldescription = Un blocco difensivo standard. protezione adeguata dai nemici. -block.titaniumwall.name = muro di titanio -block.titaniumwall.fulldescription = Un forte blocco difensivo. Fornisce protezione dai nemici. -block.duriumwall.name = muro di diridio -block.duriumwall.fulldescription = Un blocco difensivo molto forte. Fornisce protezione dai nemici. -block.compositewall.name = muro composito -block.steelwall-large.name = grande muro di acciaio -block.steelwall-large.fulldescription = Un blocco difensivo standard. Si estende su più tessere. -block.titaniumwall-large.name = grande muro di titanio -block.titaniumwall-large.fulldescription = Un forte blocco difensivo. Si estende su più tessere. -block.duriumwall-large.name = grande muro di diridio -block.duriumwall-large.fulldescription = Un blocco difensivo molto forte. Si estende su più tessere. -block.titaniumshieldwall.name = muro schermato -block.titaniumshieldwall.fulldescription = Un forte blocco difensivo, con uno scudo incorporato extra. Richiede energia. Utilizza l'energia per assorbire i proiettili nemici. Si consiglia di utilizzare i booster di energia per fornire energia a questo blocco. -block.repairturret.name = torretta di riparazione -block.repairturret.fulldescription = Ripara i blocchi danneggiati vicini nel raggio di azione a un ritmo lento. Utilizza piccole quantità di energia. -block.megarepairturret.name = torretta di riparazione II -block.megarepairturret.fulldescription = Ripara i blocchi vicini danneggiati nel raggio di portata a ritmo moderato. Usa il potere. -block.shieldgenerator.name = generatore di scudi -block.shieldgenerator.fulldescription = Un blocco difensivo avanzato. Fa da scudo per tutti i blocchi in un raggio dalla posizione. Utilizza l'energia a una velocità ridotta quando è inattivo, ma scarica rapidamente energia sul contatto con i proiettili. -block.door.name = porta -block.door.fulldescription = Un blocco che può essere aperto e chiuso toccandolo. -block.door-large.name = grande porta -block.door-large.fulldescription = Un blocco che può essere aperto e chiuso toccandolo. -block.conduit.name = Condotto -block.conduit.fulldescription = Blocco di trasporto liquido di base. Funziona come un trasportatore, ma con liquidi. Ideale per pompe o altri condotti. Può essere usato come un ponte sui liquidi per nemici e giocatori. -block.pulseconduit.name = condotto di impulso -block.pulseconduit.fulldescription = Blocco di trasporto di liquidi avanzato. Trasporta i liquidi più velocemente e immagazzina più dei condotti standard. -block.liquidrouter.name = router liquido -block.liquidrouter.fulldescription = Funziona in modo simile a un router. Accetta input liquidi da un lato e li invia agli altri lati. Utile per separare il liquido da un singolo condotto in più condotti. -block.conveyor.name = trasportatore -block.conveyor.fulldescription = Blocco di trasporto basico. Sposta gli oggetti in avanti e li deposita automaticamente in torrette o crafters. Ruotabile. Può essere usato come un ponte sui liquidi per nemici e giocatori. -block.steelconveyor.name = trasportatore d'acciaio -block.steelconveyor.fulldescription = Blocco avanzato di trasporto. Sposta gli oggetti più velocemente rispetto ai trasportatori standard. -block.poweredconveyor.name = trasportatore di impulsi -block.poweredconveyor.fulldescription = Il blocco di trasporto di ultima generazione. Sposta gli oggetti più velocemente dei trasportatori in acciaio. -block.router.name = router -block.router.fulldescription = Accetta elementi da una direzione e li invia a 3 altre direzioni. Può anche memorizzare una certa quantità di oggetti. Utile per dividere i materiali da un trapano a più torrette. -block.junction.name = giunzione -block.junction.fulldescription = Funziona come un ponte per due nastri trasportatori che la attraversono. Utile in situazioni con due diversi trasportatori che trasportano materiali diversi in luoghi diversi. -block.conveyortunnel.name = tunnel di trasporto -block.conveyortunnel.fulldescription = Trasporta oggetti sotto blocchi. Per utilizzare, posizionare un tunnel che conduce nel blocco da scavare sotto il tunnel e uno sull'altro lato. Assicurarsi che entrambe le gallerie siano rivolte in direzioni opposte, cioè verso i blocchi in cui vengono immesse o in uscita. -block.liquidjunction.name = giunzione liquida -block.liquidjunction.fulldescription = Funziona come un ponte per due condotti di attraversamento. Utile in situazioni con due condotti diversi che trasportano liquidi diversi in luoghi diversi. -block.liquiditemjunction.name = giunzione di oggetti liquidi -block.liquiditemjunction.fulldescription = Funziona come un ponte per attraversare condutture e trasportatori. -block.powerbooster.name = power booster -block.powerbooster.fulldescription = Distribuisce l'energia a tutti i blocchi entro il suo raggio. -block.powerlaser.name = laser energetico -block.powerlaser.fulldescription = Crea un laser che trasmette energia al blocco di fronte ad esso. Non genera alcuna energia. Ideale per generatori o altri laser. -block.powerlaserrouter.name = router laser -block.powerlaserrouter.fulldescription = Laser che distribuisce la potenza in tre direzioni contemporaneamente. Utile in situazioni in cui è necessario alimentare più blocchi da un generatore. -block.powerlasercorner.name = angolo laser -block.powerlasercorner.fulldescription = Laser che distribuisce la potenza in due direzioni contemporaneamente. Utile in situazioni in cui è necessario alimentare più blocchi da un generatore e un router è impreciso. -block.teleporter.name = teletrasporto -block.teleporter.fulldescription = Blocco avanzato di trasporto dell'elemento. I teletrasportatori immettono gli oggetti ad altri teletrasportatori dello stesso colore. Non fa nulla se non esistono teletrasportatori dello stesso colore. Se esistono più teletrasporti dello stesso colore, ne viene selezionato uno casuale. Usa l'energia. Tocca per cambiare colore. -block.sorter.name = sorter -block.sorter.fulldescription = Ordina l'oggetto per tipo di materiale. Il materiale da accettare è indicato dal colore nel blocco. Tutti gli articoli che corrispondono al materiale di ordinamento vengono emessi in avanti, tutto il resto viene emesso a sinistra e a destra. -block.core.name = Centro -block.pump.name = pompa -block.pump.fulldescription = Pompa di liquidi da un blocco sorgente - di solito acqua, lava o petrolio. Emette liquido nei condotti nelle vicinanze. -block.fluxpump.name = pompaflux -block.fluxpump.fulldescription = Una versione avanzata della pompa. Memorizza più liquido e pompa il liquido più velocemente. -block.smelter.name = fonderia -block.smelter.fulldescription = Il blocco di lavorazione essenziale. Quando immesso 1 ferro e 1 carbone come combustibile, emette un acciaio. Si consiglia di inserire ferro e carbone su diverse cinghie per evitare l'intasamento. -block.crucible.name = crogiuolo -block.crucible.fulldescription = Un blocco di lavorazione avanzato. Immettendo 1 titanio, 1 acciaio e 1 carbone come combustibile, emette un diridio. Si consiglia di inserire carbone, acciaio e titanio su nastri diversi per evitare l'intasamento. -block.coalpurifier.name = estrattore di carbone -block.coalpurifier.fulldescription = Un blocco estrattore di base. Emette carbone quando viene fornito con grandi quantità di acqua e pietra. -block.titaniumpurifier.name = estrattore di titanio -block.titaniumpurifier.fulldescription = Un blocco estrattore standard. Produce il titanio quando viene fornito con grandi quantità di acqua e ferro. -block.oilrefinery.name = raffineria d'petrolio -block.oilrefinery.fulldescription = Affina grandi quantità di petrolio in oggetti di carbone. Utile per alimentare torrette a base di carbone quando le vene del carbone scarseggiano. -block.stoneformer.name = forma pietre -block.stoneformer.fulldescription = Solidifica la lava producendo pietra. Utile per produrre enormi quantità di pietra per depuratori di carbone. -block.lavasmelter.name = fonderia a lava -block.lavasmelter.fulldescription = Usa la lava per convertire il ferro in acciaio. Un'alternativa alle smelterie. Utile in situazioni in cui il carbone è scarso. -block.stonedrill.name = trapano di pietra -block.stonedrill.fulldescription = Il trapano essenziale. Se posizionato su delle piastrelle di pietra, emette una pietra a un ritmo lento indefinitamente. -block.irondrill.name = trapano di ferro -block.irondrill.fulldescription = Un trapano di base. Quando viene posizionato su delle piastrelle con il minerale ferro, emette il ferro a un ritmo lento indefinitamente. -block.coaldrill.name = trivella di carbone -block.coaldrill.fulldescription = Un trapano di base. Se posizionato su delle piastrelle di carbone, produce a tempo indeterminato il carbone a un ritmo lento. -block.uraniumdrill.name = trapano all'uranio -block.uraniumdrill.fulldescription = Un trapano avanzato. Se posizionato su delel piastrelle con dell'uranio, emette l'uranio a un ritmo lento indefinitamente. -block.titaniumdrill.name = trapano in titanio -block.titaniumdrill.fulldescription = Un trapano avanzato. Se posizionato su delle piastrelle di titanio, emette il titanio a un ritmo lento indefinitamente. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = L'ultimo trapano. Trapanerà qualsiasi minerale su cui è posizionato ad un ritmo rapido. -block.coalgenerator.name = generatore di carbone -block.coalgenerator.fulldescription = Il generatore essenziale. Genera potenza dal carbone. Emette potenza come laser sui suoi 4 lati. -block.thermalgenerator.name = generatore termico -block.thermalgenerator.fulldescription = Genera energia dalla lava. Emette energia come laser sui suoi 4 lati. -block.combustiongenerator.name = generatore a combustione -block.combustiongenerator.fulldescription = Genera energia dall'petrolio. Emette energia come laser sui suoi 4 lati. -block.rtgenerator.name = Generatore RTG -block.rtgenerator.fulldescription = Genera piccole quantità di energia dal decadimento radioattivo dell'uranio. Emette potenza come laser sui suoi 4 lati. -block.nuclearreactor.name = reattore nucleare -block.nuclearreactor.fulldescription = Una versione avanzata del Generatore RTG e il massimo generatore di energia. Genera potenza dall'uranio. Richiede un raffreddamento costante dell'acqua. Altamente volatile; esploderà violentemente se vengono fornite quantità insufficienti di refrigerante. -block.turret.name = torretta -block.turret.fulldescription = Una torretta semplice ed economica. Usa la pietra per le munizioni. Ha un raggio leggermente superiore rispetto alla doppia torretta. -block.doubleturret.name = doppia torretta -block.doubleturret.fulldescription = Una versione leggermente più potente della torretta. Usa la pietra per le munizioni. Fa molto più danni, ma ha un raggio più basso. Spara due proiettili. -block.machineturret.name = torretta di gattling -block.machineturret.fulldescription = Una torretta standard a tutto tondo. Usa il ferro per le munizioni. Ha una velocità di fuoco veloce con danni decenti. -block.shotgunturret.name = torretta di splitter -block.shotgunturret.fulldescription = Una torretta standard. Usa il ferro per le munizioni. Spara una diffusione di 7 proiettili. Gittata inferiore, ma maggiore danno inflitto rispetto alla torretta gattling. -block.flameturret.name = lanciafiamme -block.flameturret.fulldescription = Torretta avanzata a distanza ravvicinata. Usa carbone per munizioni. Ha una portata molto bassa, ma un danno molto alto. Buono per luoghi chiusi. Consigliato per essere usato dietro i muri. -block.sniperturret.name = torretta ellettromagnetica -block.sniperturret.fulldescription = Torretta avanzata a lungo raggio. Utilizza l'acciaio come munizioni. Danno molto alto, ma bassa velocità di fuoco. Costoso da usare, ma può essere posizionato lontano dalle linee nemiche a causa della sua portata. -block.mortarturret.name = torretta di sfogo -block.mortarturret.fulldescription = Torretta a getto d'acqua avanzato a bassa precisione. Usa carbone per munizioni. Spara una raffica di proiettili che esplodono in shrapnel. Utile per grandi folle di nemici. -block.laserturret.name = torretta laser -block.laserturret.fulldescription = Avanzata torretta a bersaglio singolo. Usa l'energia Buona torretta a medio raggio a tutto tondo. Ingaggio singolo. Non manca mai il bersaglio. -block.waveturret.name = Torretta tesla -block.waveturret.fulldescription = Torretta multi-target avanzata. Usa l'energia. Gamma media Non manca mai. Danno basso, ma può colpire più nemici contemporaneamente con dei fulmini a catena. -block.plasmaturret.name = torretta a plasma -block.plasmaturret.fulldescription = Versione altamente avanzata del lanciafiamme. Usa il carbone come munizione. Danno molto alto, da basso a medio raggio. -block.chainturret.name = torretta a catena -block.chainturret.fulldescription = L'ultima torretta a fuoco rapido. Usa l'uranio come munizione. Spara grossi proiettili ad un alto tasso di fuoco. Gamma media Si estende su più tessere. Estremamente duro. -block.titancannon.name = cannone di titano -block.titancannon.fulldescription = L'ultima torretta a lungo raggio. Usa l'uranio come munizione. Spara grossi proiettili di schizzi a una velocità media di fuoco. Lungo raggio. Si estende su più tessere. Estremamente duro. -block.playerspawn.name = spawngiocatore -block.enemyspawn.name = spawnnemico +text.about=Creato da [ROYAL]Anuken[] - [SKY]anukendev@gmail.com[]\nOriginariamente era una voce nel [orange]GDL[] Metal Monstrosity Jam.\n\n Crediti:\n - SFX realizzato con [YELLOW]bfxr [] \n - Musica creata da [GREEN]RoccoW[] / trovata su [lime]FreeMusicArchive.org[]\n\n Un ringraziamento speciale a:\n - [coral]MitchellFJN []: esteso test del gioco e feedback\n - [sky]Luxray5474 []: lavorazione della wiki, contributi col codice\n - [lime]Epowerj []: sistema di costruzione del codice, icone\n - Tutti i beta tester su itch.io e Google Play\n +text.credits=Crediti +text.discord=Unisciti sul server discord di mindustry! +text.link.discord.description=la chatroom ufficiale del server discord di Mindustry +text.link.github.description=Codice sorgente del gioco +text.link.dev-builds.description=Build di sviluppo versioni instabili +text.link.trello.description=Scheda ufficiale trello per funzionalità pianificate +text.link.itch.io.description=pagina di itch.io con download per PC e versione web +text.link.google-play.description=Elenco di Google Play Store +text.link.wiki.description=wiki ufficiale di Mindustry +text.linkfail=Impossibile aprire il link! L'URL è stato copiato nella tua bacheca. +text.editor.web=La versione web non supporta l'editor! Scarica il gioco per usarlo. +text.multiplayer.web=Questa versione del gioco non supporta il multiplayer! Per giocare in multiplayer dal tuo browser, usa il link "versione web multiplayer" nella pagina itch.io. +text.gameover=Il nucleo è stato distrutto. +text.highscore=[YELLOW]Nuovo record! +text.lasted=Sei durato fino all'onda +text.level.highscore=Migliore: [accent]{0} +text.level.delete.title=Conferma Eliminazione +text.level.select=Selezione del livello +text.level.mode=Modalità di gioco: +text.savegame=Salva +text.loadgame=Carica +text.joingame=Gioca MP +text.newgame=Nuovo gioco +text.quit=Esci +text.about.button=Informazioni +text.name=Nome: +text.players={0} giocatori online +text.players.single={0} giocatori online +text.server.mismatch=Errore nel pacchetto: possibile discrepanza nella versione client / server. Assicurati che tu e l'host abbiate l'ultima versione di Mindustry! +text.server.closing=[accent]Chiusura server ... +text.server.kicked.kick=Sei stato cacciato dal server! +text.server.kicked.invalidPassword=10468 = Password non valida. +text.server.kicked.clientOutdated=Versione del client obsoleta! Aggiorna il tuo gioco! +text.server.kicked.serverOutdated=Server obsoleto! Chiedi all'host di aggiornare! +text.server.kicked.banned=Sei stato bannato su questo server. +text.server.kicked.recentKick=Sei stato cacciato di recente. Attendi prima di connetterti di nuovo. +text.server.connected={0} si è connesso +text.server.disconnected={0} si è disconnesso +text.nohost=Impossibile hostare il server con una mappa personalizzata! +text.host.info=Il pulsante [accent]hos [] ospita un server sulle porte [scarlet]6567[] e [scarlet]656.[] Chiunque sulla stessa [LIGHT_GRAY]connessione wifi o rete locale[] dovrebbe essere in grado di vedere il proprio server nel proprio elenco server.\n\n Se vuoi che le persone siano in grado di connettersi ovunque tramite IP, è richiesto il [accent]port forwarding[]. \n\n[LIGHT_GRAY]Nota: se qualcuno sta riscontrando problemi durante la connessione al gioco LAN, assicurati di aver consentito a Mindustry di accedere alla rete locale nelle impostazioni del firewall. +text.join.info=Qui è possibile inserire un [accent]IP del server[] a cui connettersi, o scoprire [accento]un server sulla rete locale[] disponibile.\n Sono supportati sia il multiplayer LAN che WAN. \n\n[LIGHT_GRAY]Nota: non esiste un elenco di server globali automatici; se si desidera connettersi a qualcuno tramite IP, è necessario chiedere all'host il proprio IP. +text.hostserver=Server host +text.host=Host +text.hosting=[accento] Apertura del server ... +text.hosts.refresh=Aggiorna +text.hosts.discovering=Scoperta partite LAN +text.server.refreshing=Aggiornamento server +text.hosts.none=[lightgray]Nessuna partita LAN trovata! +text.host.invalid=[scarlet]Impossibile connettersi all'host. +text.server.friendlyfire=Fuoco amico +text.trace=Trace Player +text.trace.playername=Nome del giocatore: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=ID univoco: [accent]{0} +text.trace.android=Client Android: [accent] {0} +text.trace.modclient=Cliente personalizzato: [accent]{0} +text.trace.totalblocksbroken=Totale blocchi interrotti: [accent]{0} +text.trace.structureblocksbroken=Blocchi strutturali distrutti: [accent]{0} +text.trace.lastblockbroken=Ultimo blocco distrutto: [accent]{0} +text.trace.totalblocksplaced=Totale blocchi posizionati: [accent]{0} +text.trace.lastblockplaced=Ultimo blocco inserito: [accent]{0} +text.invalidid=ID cliente non valido! Invia una segnalazione di bug. +text.server.bans=Lista Ban +text.server.bans.none=Nessun giocatore bandito trovato! +text.server.admins=Amministratori +text.server.admins.none=Nessun amministratore trovato! +text.server.add=Aggiungi server +text.server.delete=Sei sicuro di voler eliminare questo server? +text.server.hostname=Host: {0} +text.server.edit=Modifica server +text.server.outdated=[crimson]Server obsoleto![] +text.server.outdated.client=[crimson]Client obsoleto![] +text.server.version=[lightgray]Versione: {0} +text.server.custombuild=[yellow] Costruzione personalizzata +text.confirmban=Sei sicuro di voler bandire questo giocatore? +text.confirmunban=Sei sicuro di voler sbloccare questo giocatore? +text.confirmadmin=Sei sicuro di voler rendere questo giocatore un amministratore? +text.confirmunadmin=Sei sicuro di voler rimuovere lo stato di amministratore da questo player? +text.joingame.title=Unisciti alla Partita +text.joingame.ip=IP: +text.disconnect=Disconnesso. +text.disconnect.data=Errore nel caricamento i dati del mondo! +text.connecting=[accent]Connessione in corso ... +text.connecting.data=[accent]Caricamento dei dati del mondo ... +text.connectfail=[crimson] Impossibile connettersi al server: [orange] {0} +text.server.port=Porta: +text.server.addressinuse=Indirizzo già in uso! +text.server.invalidport=Numero di porta non valido! +text.server.error=[crimson]Errore nell'hosting del server: [orange] {0} +text.save.new=Nuovo Salvataggio +text.save.overwrite=Sei sicuro di voler sovrascrivere questo salvataggio? +text.overwrite=Sostituisci +text.save.none=Nessun salvataggio trovato! +text.saveload=[Accent]Salvataggio ... +text.savefail=Salvataggio del gioco non riuscito! +text.save.delete.confirm=Sei sicuro di voler eliminare questo salvataggio? +text.save.delete=Elimina +text.save.export=Esporta Salva +text.save.import.invalid=[orange]Questo salvataggio non è valido! +text.save.import.fail=[crimson]Impossibile importare salvataggio: [orange]{0} +text.save.export.fail=[crimson]Impossibile esportare il salvataggio: [orange]{0} +text.save.import=Importa Salvataggio +text.save.newslot=Salva nome: +text.save.rename=Rinomina +text.save.rename.text=Nuovo nome: +text.selectslot=Seleziona un salvataggio. +text.slot=[accent]Slot {0} +text.save.corrupted=[orang]File di salvataggio danneggiato o non valido! +text.empty= +text.on=Acceso +text.off=Spento +text.save.autosave=Salvataggio automatico: {0} +text.save.map=mappa +text.save.wave=Ondata: +text.save.difficulty=Difficolta: {0} +text.save.date=Ultimo salvataggio: {0} +text.confirm=Conferma +text.delete=Elimina +text.ok=OK +text.open=Apri +text.cancel=Annulla +text.openlink=Apri Link +text.copylink=Copia link +text.back=Indietro +text.quit.confirm=Sei sicuro di voler uscire? +text.changelog.title=Registro modifiche +text.changelog.loading=Ottenere il registro delle modifiche ... +text.changelog.error.android=[orange]Nota che il log delle modifiche non funziona su Android 4.4 e versioni precedenti! Ciò è dovuto a un bug interno di Android. +text.changelog.error=[scarlet]Errore durante il recupero del changelog! Controlla la tua connessione Internet. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.loading=[accent]Caricamento in corso ... +text.wave=[orange]Onda {0} +text.wave.waiting=Onda in {0} +text.waiting=In attesa... +text.enemies={0} Nemici +text.enemies.single={0} Nemico +text.loadimage=Carica immagine +text.saveimage=Salva Immagine +text.editor.badsize=[orange]Dimensioni dell'immagine non valide![]\n Dimensioni della mappa valide: {0} +text.editor.errorimageload=Errore durante il caricamento del file immagine:\n [orange]{0} +text.editor.errorimagesave=Errore durante il salvataggio del file immagine:\n [orange]{0} +text.editor.generate=Genera +text.editor.resize=Zomma o \nRiduci +text.editor.loadmap=Carica\nmappa +text.editor.savemap=Salva\nla mappa +text.editor.loadimage=Carica\nimmagine +text.editor.saveimage=Salva\nImmagine +text.editor.unsaved=[scarlet]Hai modifiche non salvate![]\nSei sicuro di voler uscire? +text.editor.resizemap=Ridimensiona la mappa +text.editor.mapname=Nome Mappa: +text.editor.overwrite=[Accent]Attenzione!\nQuesto sovrascrive una mappa esistente. +text.editor.selectmap=Seleziona una mappa da caricare: +text.width=Larghezza: +text.height=Altezza: +text.menu=Menu +text.play=Gioca +text.load=Carica +text.save=Salva +text.language.restart=Riavvia il gioco affinché il cambiamento della lingua abbia effetto. +text.settings.language=Lingua +text.settings=Impostazioni +text.tutorial=Lezioni +text.editor=Editor +text.mapeditor=Editor delle mappe +text.donate=Dona +text.settings.reset=Resetta Alle Impostazioni Predefinite +text.settings.controls=Controlli +text.settings.game=Gioco +text.settings.sound=Suono +text.settings.graphics=Grafica +text.upgrades=Miglioramenti +text.purchased=[LIME]Creato! +text.weapons=Armi +text.paused=In pausa +text.info.title=[Accent]Informazioni +text.error.title=[crimson]Si è verificato un errore +text.error.crashtitle=Si è verificato un errore +text.blocks.blockinfo=Informazioni sul blocco +text.blocks.powercapacity=Capacità energetica +text.blocks.powershot=Danno/Colpo +text.blocks.size=Grandezza +text.blocks.liquidcapacity=Capacità del liquido +text.blocks.maxitemssecond=Oggetti massimi/secondo +text.blocks.powerrange=Raggio Energia +text.blocks.itemcapacity=Capacità oggetto +text.blocks.inputliquid=Ingresso del liquido +text.blocks.inputitem=Ingresso Oggetto +text.blocks.explosive=Altamente esplosivo! +text.blocks.health=Salute +text.blocks.inaccuracy=inesattezza +text.blocks.shots=Colpi +text.blocks.inputcapacity=Capacità di ingresso +text.blocks.outputcapacity=Capacità di uscita +setting.difficulty.easy=facile +setting.difficulty.normal=medio +setting.difficulty.hard=difficile +setting.difficulty.insane=Folle +setting.difficulty.purge=Epurazione +setting.difficulty.name=Difficoltà: +setting.screenshake.name=Screen Shake +setting.indicators.name=Indicatori nemici +setting.effects.name=Visualizza effetti +setting.sensitivity.name=Sensibilità del controllore. +setting.saveinterval.name=Intervallo di salvataggio automatico +setting.seconds={0} Secondi +setting.fullscreen.name=Schermo Intero +setting.multithread.name=multithreading +setting.fps.name=Mostra FPS +setting.vsync.name=Sincronizzazione Verticale +setting.lasers.name=Mostra Energia Dei Laser +setting.healthbars.name=Mostra barra della salute delle entità +setting.musicvol.name=Volume Musica +setting.mutemusic.name=Musica muta +setting.sfxvol.name=Volume SFX +setting.mutesound.name=Suono muto +map.maze.name=labirinto +map.fortress.name=fortezza +map.sinkhole.name=dolina +map.caves.name=grotte +map.volcano.name=vulcano +map.caldera.name=caldera +map.scorch.name=bruciatura +map.desert.name=Deserto +map.island.name=Isola +map.grassland.name=Prateria +map.tundra.name=Tundra +map.spiral.name=spirale +map.tutorial.name=Tutorial +text.keybind.title=Configurazione Tasti +keybind.move_x.name=move_x +keybind.move_y.name=move_y +keybind.select.name=seleziona +keybind.break.name=rompere +keybind.shoot.name=sparare +keybind.zoom_hold.name=zoom_hold +keybind.zoom.name=zoom +keybind.block_info.name=Informazioni blocco +keybind.menu.name=menu +keybind.pause.name=pausa +keybind.dash.name=corsa +keybind.chat.name=Chat +keybind.player_list.name=lista_giocatori +keybind.console.name=console +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Ruotare +mode.text.help.title=Descrizione delle modalità +mode.waves.name=onde +mode.waves.description=modalità normale. risorse limitate e onde in entrata automatiche. +mode.sandbox.name=Sandbox +mode.sandbox.description=risorse infinite e nessun timer per le onde. +mode.freebuild.name=freebuild +mode.freebuild.description=risorse limitate e nessun timer per le onde. +item.stone.name=pietra +item.coal.name=carbone +item.titanium.name=titanio +item.sand.name=sabbia +liquid.water.name=acqua +liquid.lava.name=lava +liquid.oil.name=petrolio +block.door.name=porta +block.door-large.name=grande porta +block.conduit.name=Condotto +block.pulseconduit.name=condotto di impulso +block.liquidrouter.name=router liquido +block.conveyor.name=trasportatore +block.router.name=router +block.junction.name=giunzione +block.liquidjunction.name=giunzione liquida +block.sorter.name=sorter +block.smelter.name=fonderia +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_pl.properties b/core/assets/bundles/bundle_pl.properties index 08131a3ddc..ef022067b0 100644 --- a/core/assets/bundles/bundle_pl.properties +++ b/core/assets/bundles/bundle_pl.properties @@ -1,488 +1,495 @@ -text.about = Stworzony przez [ROYAL] Anuken. []\nPierwotnie wpis w [orange] GDL [] MM Jam.\n\nNapisy:\n- SFX wykonane z pomocą [YELLOW] bfxr []\n- Muzyka wykonana przez [GREEN] RoccoW [] / znaleziona na [lime] FreeMusicArchive.org []\n\nSpecjalne podziękowania dla:\n- [coral] MitchellFJN []: obszerne testowanie i feedback\n- [niebo] Luxray5474 []: prace związane z wiki, pomoc z kodem\n- Wszystkich beta testerów na itch.io i Google Play\n -text.discord = Odwiedź nasz serwer Discord -text.gameover = Rdzeń został zniszczony. -text.highscore = [YELLOW] Nowy rekord! -text.lasted = Wytrwałeś do fali -text.level.highscore = Rekord: [accent]{0} -text.level.delete.title = Potwierdź kasowanie -text.level.delete = Czy na pewno chcesz usunąć mapę \"[orange]{0}\"? -text.level.select = Wybrany poziom -text.level.mode = Tryb gry: -text.savegame = Zapisz Grę -text.loadgame = Wczytaj grę -text.joingame = Gra wieloosobowa -text.quit = Wyjdź -text.about.button = O grze -text.name = Nazwa: -text.public = Publiczny -text.players = {0} graczy online -text.server.player.host = {0} (host) -text.players.single = {0} gracz online -text.server.mismatch = Błąd pakietu: możliwa niezgodność wersji klienta/serwera.\nUpewnij się, że Ty i host macie najnowszą wersję Mindustry! -text.server.closing = [accent] Zamykanie serwera ... -text.server.kicked.kick = Zostałeś wyrzucony z serwera! -text.server.kicked.invalidPassword = Nieprawidłowe hasło! -text.server.kicked.clientOutdated = Nieaktualna gra! Zaktualizują ją! -text.server.kicked.serverOutdated = Nieaktualna gra! Zaktualizują ją! -text.server.connected = {0} dołączył do gry . -text.server.disconnected = {0} został rozłączony. -text.nohost = Nie można hostować serwera na mapie niestandardowej! -text.hostserver = Serwer hosta -text.host = Host -text.hosting = [accent] Otwieranie serwera ... -text.hosts.refresh = Odśwież -text.hosts.discovering = Wyszukiwanie gier w sieci LAN -text.server.refreshing = Odświeżanie serwera -text.hosts.none = [lightgray] Brak serwerów w sieci LAN! -text.host.invalid = [scarlet] Nie można połączyć się z hostem. -text.server.friendlyfire = Bratobójczy ogień -text.server.add = Dodaj serwer -text.server.delete = Czy na pewno chcesz usunąć ten serwer? -text.server.hostname = Host: {0} -text.server.edit = Edytuj serwer -text.joingame.byip = Dołącz przez IP... -text.joingame.title = Dołącz do gry -text.joingame.ip = IP: -text.disconnect = Rozłączony. -text.connecting = [accent]Łączenie ... -text.connecting.data = [accent]Ładowanie danych świata... -text.connectfail = [crimson]Nie można połączyć się z serwerem: [orange] {0} -text.server.port = Port: -text.server.addressinuse = Adres jest już w użyciu! -text.server.invalidport = Nieprawidłowy numer portu. -text.server.error = [crimson] Błąd hostowania serwera: [orange] {0} -text.tutorial.back = < Cofnij -text.tutorial.next = Dalej > -text.save.new = Nowy zapis -text.save.overwrite = Czy na pewno chcesz nadpisać zapis gry? -text.overwrite = Nadpisz -text.save.none = Nie znaleziono zapisów gry! -text.saveload = [akcent]Zapisywanie... -text.savefail = Nie udało się zapisać gry! -text.save.delete.confirm = Czy na pewno chcesz usunąć ten zapis gry? -text.save.delete = Usuń -text.save.export = Eksportuj -text.save.import.invalid = [orange]Zapis gry jest niepoprawny! -text.save.import.fail = [crimson]Nie udało się zaimportować zapisu: [orange] {0} -text.save.export.fail = [crimson]Nie można wyeksportować zapisu: [orange] {0} -text.save.import = Importuj -text.save.newslot = Zapisz nazwę: -text.save.rename = Zmień nazwę -text.save.rename.text = Zmień nazwę -text.selectslot = Wybierz zapis. -text.slot = [accent]Slot {0} -text.save.corrupted = [orange]Zapis gry jest uszkodzony lub nieprawidłowy! -text.empty = -text.on = Włączone -text.off = Wyłączone -text.save.autosave = Zapisywanie automatyczne -text.save.map = Mapa: {0} -text.save.wave = Fala: {0} -text.save.date = Ostatnio zapisano: {0} -text.confirm = Potwierdź -text.delete = Usuń -text.ok = Ok -text.open = Otwórz -text.cancel = Anuluj -text.openlink = Otwórz link -text.back = Wróć -text.quit.confirm = Czy na pewno chcesz wyjść? -text.loading = [accent]Ładowanie ... -text.wave = [orange]Fala {0} -text.wave.waiting = Fala w {0} -text.waiting = Oczekiwanie... -text.enemies = {0} wrogów -text.enemies.single = {0} wróg -text.loadimage = Załaduj obraz -text.saveimage = Zapisz obraz -text.editor.badsize = [orange]Nieprawidłowe wymiary obrazu![]\nWymiary poprawne: {0} -text.editor.errorimageload = Błąd podczas ładowania pliku obrazu: [orange]{0} -text.editor.errorimagesave = Błąd podczas zapisywania pliku obrazu: [orange]{0} -text.editor.generate = Generuj -text.editor.resize = Zmień rozmiar -text.editor.loadmap = Załaduj mapę -text.editor.savemap = Zapisz mapę -text.editor.loadimage = Załaduj obraz -text.editor.saveimage = Zapisz obraz -text.editor.unsaved = [scarlet]Masz niezapisane zmiany![]\nCzy na pewno chcesz wyjść? -text.editor.brushsize = Rozmiar pędzla: {0} -text.editor.noplayerspawn = Ta mapa nie ma ustawionego spawnu gracza! -text.editor.manyplayerspawns = Mapy nie mogą mieć więcej niż jeden punkt spawnu gracza! -text.editor.manyenemyspawns = Nie może mieć więcej niż {0} punktów spawnu wroga! -text.editor.resizemap = Zmień rozmiar mapy -text.editor.resizebig = [scarlet]Uwaga![]\nMapy większe niż 256 jednostek mogą przycinać i być niestabilne. -text.editor.mapname = Nazwa mapy: -text.editor.overwrite = [accent]Uwaga!\nSpowoduje to nadpisanie istniejącej mapy. -text.editor.failoverwrite = [crimson]Nie można nadpisać mapy podstawowej! -text.editor.selectmap = Wybierz mapę do załadowania: -text.width = Szerokość: -text.height = Wysokość: -text.randomize = Wylosuj -text.apply = Zastosuj -text.update = Zaktualizuj -text.menu = Menu -text.play = Graj -text.load = Wczytaj -text.save = Zapisz -text.language.restart = Uruchom grę ponownie aby ustawiony język zaczął funkcjonować. -text.settings.language = Język -text.settings = Ustawienia -text.tutorial = Poradnik -text.editor = Edytor -text.mapeditor = Edytor map -text.donate = Wspomóż nas -text.settings.reset = Przywróć domyślne -text.settings.controls = Sterowanie -text.settings.game = Gra -text.settings.sound = Dźwięk -text.settings.graphics = Grafika -text.upgrades = Ulepszenia -text.purchased = [LIME]Stworzono! -text.weapons = Bronie -text.paused = Wstrzymano -text.respawn = Odrodzenie za -text.info.title = [accent]Informacje -text.error.title = [crimson]Wystąpił błąd -text.error.crashmessage = [SCARLET]Wystąpił nieoczekiwany błąd, który spowodowałby awarię.[]\nProszę, powiadom dewelopera gry o tym błędzie, pisząc jak do niego doszło: [ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Wystąpił błąd -text.mode.break = Tryb przerw: {0} -text.mode.place = Tryb układania: {0} -placemode.hold.name = linia -placemode.areadelete.name = obszar -placemode.touchdelete.name = Dotyk -placemode.holddelete.name = Przytrzymać -placemode.none.name = żaden -placemode.touch.name = Dotyk -placemode.cursor.name = kursor -text.blocks.extrainfo = [accent]Dodatkowe informacje o bloku: -text.blocks.blockinfo = Informacje o bloku -text.blocks.powercapacity = Moc znamionowa -text.blocks.powershot = moc / strzał -text.blocks.powersecond = moc / sekunda -text.blocks.powerdraindamage = siła ataku / obrażenia -text.blocks.shieldradius = Promień osłony -text.blocks.itemspeedsecond = prędkość ​​/ sekundy -text.blocks.range = Zakres -text.blocks.size = Rozmiar -text.blocks.powerliquid = moc / ciecz -text.blocks.maxliquidsecond = maksymalna ilość cieczy / sekunda -text.blocks.liquidcapacity = Pojemność cieczy -text.blocks.liquidsecond = ciecz / sekunda -text.blocks.damageshot = obrażenia / strzał -text.blocks.ammocapacity = Pojemność amunicji -text.blocks.ammo = Amunicja: -text.blocks.ammoitem = amunicja / przedmiot -text.blocks.maxitemssecond = Maksymalna liczba przedmiotów / Sekunda -text.blocks.powerrange = Zakres mocy -text.blocks.lasertilerange = Zasięg lasera -text.blocks.capacity = Wydajność -text.blocks.itemcapacity = Pojemność przedmiotów -text.blocks.maxpowergenerationsecond = maksymalne generowanie energii / sekunda -text.blocks.powergenerationsecond = wytwarzanie energii / sekunda -text.blocks.generationsecondsitem = sekunda / przedmiot -text.blocks.input = Wkład -text.blocks.inputliquid = Potrzebna ciecz -text.blocks.inputitem = Potrzebne przedmioty -text.blocks.output = Wyjście -text.blocks.secondsitem = sekundy / przedmiot -text.blocks.maxpowertransfersecond = maksymalny transfer mocy / sekundę -text.blocks.explosive = Wysoce wybuchowy! -text.blocks.repairssecond = naprawa / sekunda -text.blocks.health = Zdrowie -text.blocks.inaccuracy = Niedokładność -text.blocks.shots = Strzały -text.blocks.shotssecond = Strzały / Sekunda -text.blocks.fuel = Paliwo -text.blocks.fuelduration = Wydajność paliwa -text.blocks.maxoutputsecond = maksymalne wyjście / sekunda -text.blocks.inputcapacity = Pojemność wejściowa -text.blocks.outputcapacity = Wydajność wyjściowa -text.blocks.poweritem = moc / przedmiot -text.placemode = Tryb miejsca -text.breakmode = Tryb przerwania -text.health = Zdrowie: -setting.difficulty.easy = łatwy -setting.difficulty.normal = normalny -setting.difficulty.hard = trudny -setting.difficulty.insane = szalony -setting.difficulty.purge = Czystka -setting.difficulty.name = Poziom trudności -setting.screenshake.name = Trzęsienie się ekranu -setting.smoothcam.name = Płynna kamera -setting.indicators.name = Wskaźniki wroga -setting.effects.name = Wyświetlanie efektów -setting.sensitivity.name = Czułość kontrolera -setting.saveinterval.name = Interwał automatycznego zapisywania -setting.seconds = Sekundy -setting.fps.name = Widoczny licznik FPS -setting.vsync.name = Synchronizacja pionowa -setting.lasers.name = Pokaż lasery zasilające -setting.healthbars.name = Pokaż paski zdrowia jednostki -setting.pixelate.name = Rozpikselizowany obraz -setting.musicvol.name = Głośność muzyki -setting.mutemusic.name = Wycisz muzykę -setting.sfxvol.name = Głośność dźwięków -setting.mutesound.name = Wycisz dźwięki -map.maze.name = labirynt -map.fortress.name = twierdza -map.sinkhole.name = wgłębienie -map.caves.name = jaskinie -map.volcano.name = wulkan -map.caldera.name = kaldera -map.scorch.name = opalacz -map.desert.name = pustynia -map.island.name = wyspa -map.grassland.name = łąka -map.tundra.name = tundra -map.spiral.name = spirala -map.tutorial.name = Poradnik -tutorial.intro.text = [yellow]Witamy w poradniku do gry.[]\nAby rozpocząć, naciśnij \"Dalej\". -tutorial.moveDesktop.text = Aby się poruszać, użyj klawiszy [orange]W A S[] oraz [orange]D[]. Przytrzymaj [orange]shift[], aby przyśpieszyć.\nPrzytrzymując [orange]ctrl[] podczas kręcenia [orange]rolką myszy[] możesz zmieniać poziom przybliżenia mapy. -tutorial.shoot.text = Do celowania używasz kursora myszy.\n[orange]Lewy przycisk myszy[] służy do strzelania. Poćwicz na [yellow]celu[]. -tutorial.moveAndroid.text = Aby przesunąć widok, przeciągnij jednym palcem po ekranie. Ściśnij i przeciągnij, aby powiększyć lub pomniejszyć. -tutorial.placeSelect.text = Wybierz [yellow]przenośnik[] z menu blokowego w prawym dolnym rogu. -tutorial.placeConveyorDesktop.text = Użyj [orange]rolki myszy[] aby obrócić przenośnik [orange]do przodu[], a następnie umieść go w [yellow]oznaczonym miejscu[] za pomocą [orange]lewego przycisku myszy[]. -tutorial.placeConveyorAndroid.text = Użyj [orange]przycisk obracania[], aby obrócić przenośnik [orange]do przodu[]. Jednym palcem przeciągnij go na [yellow]wskazaną pozycję[], a następnie umieść go za pomocą [orange]znacznika wyboru[]. -tutorial.placeConveyorAndroidInfo.text = Możesz też nacisnąć ikonę celownika w lewym dolnym rogu, aby przełączyć na [pomarańczowy] [[tryb dotykowy] [] i umieścić bloki, dotykając ekranu. W trybie dotykowym bloki można obracać strzałką w lewym dolnym rogu. Naciśnij [żółty] następny [], aby go wypróbować. -tutorial.placeDrill.text = Teraz wybierz i umieść [yellow]wiertło do kamienia[] w oznaczonym miejscu. -tutorial.blockInfo.text = Jeśli chcesz się dowiedzieć więcej na temat wybranego bloku, kliknij [orange]znak zapytania[] w prawym górnym rogu, aby przeczytać jego opis. -tutorial.deselectDesktop.text = Możesz anulować wybór bloku za pomocą [orange]prawego przycisku myszy[]. -tutorial.deselectAndroid.text = Możesz odznaczyć blok, naciskając przycisk [orange]X[]. -tutorial.drillPlaced.text = Wiertło będzie teraz produkować [yellow]kamień[] i podawać go na przenośnik, który przeniesie go do [yellow]rdzenia[] -tutorial.drillInfo.text = Różne rudy wymagają różnych wierteł. Kamień wymaga wiertła do kamieniu, żelazo wymaga wiertła do żelaza, itd. -tutorial.drillPlaced2.text = Przeniesienie przedmiotów do rdzenia powoduje umieszczenie ich w [yellow]inwentarzu przedmiotów[] w lewym górnym rogu. Stawianie bloków te przedmioty zużywa. -tutorial.moreDrills.text = Możesz połączyć wiertła i przenośników w przedstawiony sposób: -tutorial.deleteBlock.text = Możesz usuwać bloki, klikając [orange]prawy przycisk myszy[] na bloku, który chcesz usunąć.\nSpróbuj usunąć [yellow]oznaczony[] przenośnik. -tutorial.deleteBlockAndroid.text = Możesz usuwać bloki za pomocą [orange]krzyżyka[] w [orange]menu przerywania[] w lewym dolnym rogu i stukając w blok.\nSpróbuj usunąć [yellow]oznaczony[] przenośnik. -tutorial.placeTurret.text = Teraz wybierz i umieść [yellow]działko[] w [yellow]zaznaczonym miejscu[]. -tutorial.placedTurretAmmo.text = Działko przyjmie teraz [yellow]amunicję[] z przenośnika. Możesz zobaczyć, ile ma amunicji, najeżdżając na działko kursorem i sprawdzając [green]zielony pasek[]. -tutorial.turretExplanation.text = Wieżyczki będą strzelać automatycznie do najbliższego wroga w zasięgu, o ile będą miały wystarczającą ilość amunicji. -tutorial.waves.text = Co każde [yellow]60[] sekund, fala [coral]wrogów[] pojawi się w określonym miejscu na mapie i spróbuje zniszczyć rdzeń. -tutorial.coreDestruction.text = Twoim celem jest [yellow]obrona rdzenia[]. Zniszczenie rdzenia jest równoznaczne z [coral]przegraną[]. -tutorial.pausingDesktop.text = Jeśli chcesz zrobić przerwę, naciśnij [orange]spację[] lub [orange]przycisk pauzy[] w lewym górnym rogu. Nadal możesz wybierać i wstawiać klocki podczas pauzy, ale nie możesz niszczyć i strzelać. -tutorial.pausingAndroid.text = Jeśli chcesz zrobić przerwę, naciśnij [orange]przycisk pauzy[]. Nadal możesz stawiać i niszczyć bloki. -tutorial.purchaseWeapons.text = Możesz kupić nowe [żółte] bronie [] dla swojego mecha, otwierając menu aktualizacji w lewym dolnym rogu. -tutorial.switchWeapons.text = Zmień broń, klikając jej ikonę w lewym dolnym rogu lub używając cyfr [orange][1-9[]. -tutorial.spawnWave.text = Fala wrogów nadchodzi! Zniszcz ich. -tutorial.pumpDesc.text = W późniejszych falach może być konieczne użycie [yellow]pompy[] do rozprowadzania cieczy dla generatorów lub ekstraktorów. -tutorial.pumpPlace.text = Pompy działają podobnie do wierteł, z tym wyjątkiem, że wytwarzają ciecze zamiast ród.\nSpróbuj umieścić pompę na [yellow]oleju[]. -tutorial.conduitUse.text = Teraz umieść [orange]rurę[] wychodzącą od pompy. -tutorial.conduitUse2.text = I tak jeszcze jedną... -tutorial.conduitUse3.text = I jeszcze jedeną... -tutorial.generator.text = Teraz umieść [orange]generator spalinowy[] na końcu kanału. -tutorial.generatorExplain.text = Generator ten będzie teraz wytwarzać [yellow]energię[] z oleju. -tutorial.lasers.text = Energia jest dystrybuowana za pomocą [yellow]przekaźników[]. Umieść takowy przekaźnik na [yellow]wskazanej pozycji[]. -tutorial.laserExplain.text = Generator przeniesie teraz moc do przekaźnika. Wiązka [orange]nieprzeźroczysta[] oznacza, że ​​aktualnie transmituje moc. Wiązka [yellow]przeźroczysta[] wskazuje na brak przekazywanej energii. -tutorial.laserMore.text = Możesz sprawdzić ile energii przechowuje blok najeżdżając na niego kursorem i sprawdzając [yellow]żółty pasek[]. -tutorial.healingTurret.text = Energia może być używany do zasilania [lime]wież naprawczych[]. Umieść takową [yellow]tutaj[]. -tutorial.healingTurretExplain.text = Dopóki dostarczana jest do niej energia będzie [lime]naprawiać pobliskie bloki[]. Podczas gry ustawiłeś ją niedaleko swojej bazy tak szybko, jak to tylko możliwe! -tutorial.smeltery.text = Wiele bloków wymaga do pracy [orange]stali[], którą można wytwarzać w [orange]hucie[]. A skoro tak, to postaw ją teraz. -tutorial.smelterySetup.text = Huta wytworzy teraz z żelaza [orange]stal[], wykorzystując węgiel jako paliwo. -tutorial.tunnelExplain.text = Zwróć też uwagę, że przedmioty przechodzą przez [orange]tunel[] i wynurzają się po drugiej stronie, przechodząc przez kamienny blok. Pamiętaj, że tunele mogą przechodzić tylko do 2 bloków. -tutorial.end.text = I na tym poradnik się kończy!\nPozostaje tylko życzyć Ci [orange]powodzenia[]! -keybind.move_x.name = Poruszanie w poziomie -keybind.move_y.name = Poruszanie w pionie -keybind.select.name = Wybieranie -keybind.break.name = Niszczenie -keybind.shoot.name = Strzelanie -keybind.zoom_hold.name = Inicjator przybliżania -keybind.zoom.name = Przybliżanie -keybind.menu.name = menu -keybind.pause.name = pauza -keybind.dash.name = przyśpieszenie -keybind.rotate_alt.name = Obracanie (1) -keybind.rotate.name = Obracanie (2) -keybind.weapon_1.name = Broń 1 -keybind.weapon_2.name = Broń 2 -keybind.weapon_3.name = Broń 3 -keybind.weapon_4.name = Broń 4 -keybind.weapon_5.name = Broń 5 -keybind.weapon_6.name = Broń 6 -mode.waves.name = Fale -mode.sandbox.name = sandbox -mode.freebuild.name = budowanie -upgrade.standard.name = Standardowy -upgrade.standard.description = Standardowy mech. -upgrade.blaster.name = blaster -upgrade.blaster.description = Wystrzeliwuje powolną, słabą kulę. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Strzela 3 pociskami w rozprzestrzenianiu. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Wystrzeliwuje w różnych kierunkach kilka granatów. -upgrade.beam.name = Działko laserowe -upgrade.beam.description = Strzela laserem o dalekim zasięgu. -upgrade.vulcan.name = wulkan -upgrade.vulcan.description = Wystrzeliwuje grad szybkich pocisków. -upgrade.shockgun.name = Działko elektryczne -upgrade.shockgun.description = Wystrzeliwuje niszczycielski podmuch naładowanych odłamków. -item.stone.name = kamień -item.iron.name = żelazo -item.coal.name = węgiel -item.steel.name = stal -item.titanium.name = tytan -item.dirium.name = dirium -item.uranium.name = uran -item.sand.name = piasek -liquid.water.name = woda -liquid.plasma.name = plazma -liquid.lava.name = lawa -liquid.oil.name = olej -block.weaponfactory.name = fabryka broni -block.air.name = Powietrze. -block.blockpart.name = Kawałek bloku -block.deepwater.name = głęboka woda -block.water.name = woda -block.lava.name = lawa -block.oil.name = olej -block.stone.name = kamień -block.blackstone.name = czarny kamień -block.iron.name = żelazo -block.coal.name = węgiel -block.titanium.name = tytan -block.uranium.name = uran -block.dirt.name = ziemia -block.sand.name = piasek -block.ice.name = lód -block.snow.name = śnieg -block.grass.name = trawa -block.sandblock.name = blok z piasku -block.snowblock.name = blok ze śniegu -block.stoneblock.name = blok z kamienia -block.blackstoneblock.name = blok z czarnego kamienia -block.grassblock.name = blok z trawy -block.mossblock.name = porośnięty blok -block.shrub.name = krzew -block.rock.name = kamyk -block.icerock.name = kamyk lodowy -block.blackrock.name = czarny kamyk -block.dirtblock.name = Brudny blok -block.stonewall.name = Kamienna ściana -block.stonewall.fulldescription = Tani blok obronny. Przydatny do ochrony rdzenia i wież w pierwszych kilku falach. -block.ironwall.name = Żelazna ściana -block.ironwall.fulldescription = Podstawowy blok obronny. Zapewnia ochronę przed wrogami. -block.steelwall.name = stalowa ściana -block.steelwall.fulldescription = Standardowy blok obronny. odpowiednia ochrona przed wrogami. -block.titaniumwall.name = tytanowa ściana -block.titaniumwall.fulldescription = Silny blok obronny. Zapewnia ochronę przed wrogami. -block.duriumwall.name = ściana z dirium -block.duriumwall.fulldescription = Bardzo silny blok obronny. Zapewnia ochronę przed wrogami. -block.compositewall.name = ściana kompozytowa -block.steelwall-large.name = duża stalowa ściana -block.steelwall-large.fulldescription = Standardowy blok obronny. Rozpiętość wielu płytek. -block.titaniumwall-large.name = duża tytanowa ściana -block.titaniumwall-large.fulldescription = Silny blok obronny. Rozpiętość wielu płytek. -block.duriumwall-large.name = duża ściana z dirium -block.duriumwall-large.fulldescription = Bardzo silny blok obronny. Rozpiętość wielu płytek. -block.titaniumshieldwall.name = Ściana z polem obronnym -block.titaniumshieldwall.fulldescription = Silny blok obronny z dodatkową wbudowaną tarczą. Wymaga zasilania. Używa energii do pochłaniania pocisków wroga. W celu dostarczenia zasilania zaleca się stosowanie wzmacniaczy energii. -block.repairturret.name = Wieża naprawcza -block.repairturret.fulldescription = Naprawia pobliskie uszkodzone bloki w niedużej prędkości. Wykorzystuje niewielkie ilości energii. -block.megarepairturret.name = Wieża naprawcza II -block.megarepairturret.fulldescription = Naprawia pobliskie uszkodzone bloki z przyzwoitą prędkośą. Do działania wykorzystuje energię. -block.shieldgenerator.name = Generator tarczy -block.shieldgenerator.fulldescription = Zaawansowany blok obronny. Osłania wszystkie bloki w promieniu przed atakiem wroga. Zużywa energię z niewielką prędkością gdy jest w stanie bezczynności, ale z kolei gdy jest w użyciu, energię pobiera bardzo szybko. -block.door.name = drzwi -block.door.fulldescription = Blok, który można otworzyć i zamknąć poprzez dotknięcie -block.door-large.name = duże drzwi -block.door-large.fulldescription = Blok, który można otworzyć i zamknąć poprzez dotknięcie -block.conduit.name = Rura -block.conduit.fulldescription = Podstawowy blok transportu cieczy. Działa jak przenośnik, tylko że z płynami. Najlepiej stosować z pompami bądź razem innymi rurami.\nMoże być używany jako pomost nad płynami dla wrogów i graczy. -block.pulseconduit.name = Rura impulsowa -block.pulseconduit.fulldescription = Zaawansowany blok transportu cieczy. Transportuje ciecze szybciej i przechowuje więcej niż rury standardowe. -block.liquidrouter.name = Rozdzielacz płynów -block.liquidrouter.fulldescription = Działa podobnie do zwykłego rozdzielacza. Akceptuje wejście cieczy z jednej strony i przekazuje ją na pozostałe strony. Przydatny do rozdzielania cieczy z jednej rury do wielu. -block.conveyor.name = Przenośnik -block.conveyor.fulldescription = Podstawowy blok transportu przedmiotów. Przenosi przedmioty do przodu i automatycznie umieszcza je w blokach. Może być używany jako pomost nad płynami dla wrogów i graczy. -block.steelconveyor.name = Przenośnik stalowy -block.steelconveyor.fulldescription = Zaawansowany blok transportu przedmiotów. Przenosi elementy szybciej niż standardowe przenośniki. -block.poweredconveyor.name = przenośnik impulsowy -block.poweredconveyor.fulldescription = Najszybszy blok transportowy przedmiotów. -block.router.name = Rozdzielacz -block.router.fulldescription = Przyjmuje przedmioty z jednego kierunku i przekazuje je w 3 innych kierunkach. Może również przechowywać pewną liczbę przedmiotów. Przydatny do dzielenia materiałów z jednego wiertła na wiele dział. -block.junction.name = węzeł -block.junction.fulldescription = Działa jako pomost dla dwóch skrzyżowanych przenośników taśmowych. Przydatny w sytuacjach, gdy dwa różne przenośniki przenoszą różne materiały do ​​różnych lokalizacji. -block.conveyortunnel.name = tunel przenośnikowy -block.conveyortunnel.fulldescription = Transportuje przedmiot pod blokami. Aby użyć, umieść jeden tunel prowadzący do bloku, który ma zostać tunelowany, i jeden po drugiej stronie. Upewnij się, że oba tunele są skierowane w przeciwnych kierunkach, czyli w wejście do bloku podającego, a wyjście do bloku odbierającego. -block.liquidjunction.name = Węzeł dla płynów -block.liquidjunction.fulldescription = Skrzyżowanie dla rurociągów. Przydatne w sytuacji, w której transportujemy różne ciecze w rurach, które muszą się przeciąć. -block.liquiditemjunction.name = Węzeł rur i przenośników -block.liquiditemjunction.fulldescription = Skrzyżowanie przenośników taśmowych oraz rur. -block.powerbooster.name = Wzmacniacz energii -block.powerbooster.fulldescription = Dystrybuuje moc do wszystkich bloków w swoim promieniu. -block.powerlaser.name = Przekaźnik -block.powerlaser.fulldescription = Tworzy laser, który przesyła moc do bloku przed nim. Nie generuje energii. Najlepiej stosować z generatorami lub innymi przekaźnikami. -block.powerlaserrouter.name = Duży rozdzielacz energii -block.powerlaserrouter.fulldescription = Przekaźnik, który dystrybuuje energię w trzech kierunkach jednocześnie. Przydatne w sytuacjach, w których wymagane jest zasilanie wielu bloków z jednego generatora. -block.powerlasercorner.name = Rozdzielacz energii -block.powerlasercorner.fulldescription = Przekaźnik, który dystrybuuje energię w dwóch kierunkach jednocześnie. Przydatny w sytuacjach, gdy wymagane jest zasilanie wielu bloków z jednego generatora, a router jest nieprecyzyjny. -block.teleporter.name = teleporter -block.teleporter.fulldescription = Zaawansowany blok transportu przedmiotów. Teleporty przenoszą przedmioty do innych teleportów tego samego koloru. Jeżeli nie istnieją teleporty z tym samym kolorem, to nie niczego nigdzie nie przenosi. Jeśli istnieje wiele teleportów tego samego koloru, wybierany jest losowy. Zużywa energię. Stuknij aby zmienić kolor. -block.sorter.name = Sortownik -block.sorter.fulldescription = Sortuje przedmioty według rodzaju materiału. Materiał do zaakceptowania jest oznaczony w bloku. Wszystkie pasujące przedmioty są wysyłane do przodu, wszystko inne jest wyprowadzane na lewo i na prawo. -block.core.name = Rdzeń -block.pump.name = pompa -block.pump.fulldescription = Pompuje ciecze z bloku źródłowego - zwykle wody, lawy lub oleju. Wyprowadza ciecz do pobliskich rur. -block.fluxpump.name = pompa strumieniowa -block.fluxpump.fulldescription = Zaawansowana wersja pompy. Przechowuje więcej cieczy i szybciej pompuje. -block.smelter.name = huta -block.smelter.fulldescription = Niezbędny blok rzemieślniczy. Po wprowadzeniu 1 żelaza i 1 węgla jako paliwa, wytwarza 1 stal. Zaleca się wprowadzanie żelaza i węgla na różne pasy, aby zapobiec zatkaniu. -block.crucible.name = tygiel -block.crucible.fulldescription = Zaawansowany blok rzemieślniczy. Po wprowadzeniu 1 tytanu, 1 stali i 1 węgla jako paliwa, otrzymuje 1 dirium. Zaleca się wprowadzanie węgla, stali i tytanu z różnych taśm, aby zapobiec zatkaniu. -block.coalpurifier.name = ekstraktor węgla -block.coalpurifier.fulldescription = Wyprowadza węgiel, gdy jest dostarczana duża ilością wody i kamienia. -block.titaniumpurifier.name = ekstraktor tytanu -block.titaniumpurifier.fulldescription = Wyprowadza tytan, gdy jest dostarczana duża ilości wody i żelaza. -block.oilrefinery.name = Rafineria ropy -block.oilrefinery.fulldescription = Rafinuje duże ilości oleju do postaci węgla. Przydatne do napędzania działek napędzanych węglem, gry nie ma w pobliżu wystarcząjacej ilości rud węgla. -block.stoneformer.name = Wytwarzacz kamienia -block.stoneformer.fulldescription = Schładza lawę do postaci kamień. Przydatny do produkcji ogromnych ilości kamienia do oczyszczania węgla. -block.lavasmelter.name = Huta lawowa -block.lavasmelter.fulldescription = Używa lawy, by przekształcić żelazo w stal. Alternatywa dla zwykłych hut. Przydatny w sytuacjach, gdy brakuje węgla. -block.stonedrill.name = wiertło do kamienia -block.stonedrill.fulldescription = Niezbędne wiertło. Po umieszczeniu na kamiennym podłożu, powoli i w nieskończoność wydobywa kamień. -block.irondrill.name = wiertło do żelaza -block.irondrill.fulldescription = Po umieszczeniu na rudzie żelaza, powoli i w nieskończoność wydobywa żelazo. -block.coaldrill.name = wiertło do węgla -block.coaldrill.fulldescription = Po umieszczeniu na rudzie węgla, powoli i w nieskończoność wydobywa węgiel. -block.uraniumdrill.name = wiertło do uranu -block.uraniumdrill.fulldescription = Wiertło zaawansowane. Po umieszczeniu na rudzie uranu, wydobywa uran w wolnym tempie przez czas nieokreślony. -block.titaniumdrill.name = wiertło do tytanu -block.titaniumdrill.fulldescription = Wiertło zaawansowane. Po umieszczeniu na rudzie tytanu, wydobywa tytan w wolnym tempie przez czas nieokreślony. -block.omnidrill.name = omnidril -block.omnidrill.fulldescription = Wiertło wielofunkcyjne. W szybkim tempie wydobywa każdą rudę. -block.coalgenerator.name = generator na węgiel -block.coalgenerator.fulldescription = Niezbędny generator. Generuje energię z węgla na wszystkie strony. -block.thermalgenerator.name = generator termiczny -block.thermalgenerator.fulldescription = Generuje energię z lawy. Generuje energię z węgla na wszystkie strony. -block.combustiongenerator.name = generator spalinowy -block.combustiongenerator.fulldescription = Generuje moc z oleju. Generuje energię z węgla na wszystkie strony. -block.rtgenerator.name = Generator RTG -block.rtgenerator.fulldescription = Generuje niewielkie ilości energii z rozpadu promieniotwórczego uranu. Generuje energię z węgla na wszystkie strony. -block.nuclearreactor.name = reaktor jądrowy -block.nuclearreactor.fulldescription = Zaawansowana wersja generatora RTG i zarazem najlepsze źródło energii. Generuje ją z uranu. Wymaga stałego chłodzenia wodą. Wybucha niemal natychmiast w momencie, gdy dostarczona zostanie niewystarczająca ilość płynu chłodzącego. -block.turret.name = działko -block.turret.fulldescription = Podstawowa, nieduża wieżyczka. Używa kamienia jako amunicji. Ma nieco większy zasięg niż działko podwójne. -block.doubleturret.name = działko podwójne -block.doubleturret.fulldescription = Nieco mocniejsza wersja działka. Używa kamienia jako amunicji. Znacznie więcej obrażeń, ale ma mniejszy zasięg. Wystrzeliwuje dwie kule. -block.machineturret.name = działko szybkostrzelne -block.machineturret.fulldescription = Standardowa, wszechstronna wieża. Używa żelaza jako amunicji. Strzela dość szybko i ma przyzwoite uszkodzenia. -block.shotgunturret.name = działko odłamkowe -block.shotgunturret.fulldescription = Standardowa wieża. Używa żelaza do amunicji. Wystrzeliwuje 7 pocisków. Niższy zasięg, ale większe obrażenia niż działko szybkostrzelne. -block.flameturret.name = miotacz ognia -block.flameturret.fulldescription = Zaawansowana wieżyczka bliskiego zasięgu. Używa węgla jako amunicji. Ma bardzo niski zasięg, ale bardzo duże obrażenia. Dobra na krótkie dystanse. Zalecana do stosowania za ścianami. -block.sniperturret.name = karabin -block.sniperturret.fulldescription = Zaawansowana wieżyczka dalekiego zasięgu. Używa stali jako amunicji. Ma bardzo duże obrażenia, ale niski współczynnik ognia. Kosztowne w użyciu, ale można je umieścić z dala od linii wroga ze względu na jego zasięg. -block.mortarturret.name = Miotacz odłamków -block.mortarturret.fulldescription = Zaawansowana, bardzo dokładne działko rozpryskowe. Używa węgla jako amunicji. Wystrzeliwuje grad eksplodujących pocisków. Przydatny dla dużych hord wrogów. -block.laserturret.name = działo laserowe -block.laserturret.fulldescription = Zaawansowana wieża z jednym celem. Używa energii. Dobra wieża o średnim zasięgu. Atakuje tylko 1 cel i nigdy nie pudłuje. -block.waveturret.name = Działo Tesli -block.waveturret.fulldescription = Zaawansowana wieża celującai. Używa mocy. Średni zasięg. Nigdy nie trafia. Stosuje niskie obrażenia, ale może trafić wielu wrogów jednocześnie z oświetleniem łańcuszkiem. -block.plasmaturret.name = Działo plazmowe -block.plasmaturret.fulldescription = Wysoce zaawansowana wersja miotacza ognia. Używa węgla jako amunicji. Zadaje bardzo duże obrażenia i posiada średni zasięg. -block.chainturret.name = Działo uranowe -block.chainturret.fulldescription = Najlepsze działo szybkiego ognia. Używa uranu jako amunicji. Wystrzeliwuje duże spirale z dużą szybkością. Posiada średni zasięg. -block.titancannon.name = Potężne działo uranowe -block.titancannon.fulldescription = Najlepsze działo dalekiego zasięgu. Używa uranu jako amunicji. Wystrzeliwuje duże pociski z odłamkami. Ma średnią szybkostrzelność i duży promień rażenia. -block.playerspawn.name = Spawn gracza -block.enemyspawn.name = Spawn wroga +text.about=Stworzony przez [ROYAL] Anuken. []\nPierwotnie wpis w [orange] GDL [] MM Jam.\n\nNapisy:\n- SFX wykonane z pomocą [YELLOW] bfxr []\n- Muzyka wykonana przez [GREEN] RoccoW [] / znaleziona na [lime] FreeMusicArchive.org []\n\nSpecjalne podziękowania dla:\n- [coral] MitchellFJN []: obszerne testowanie i feedback\n- [niebo] Luxray5474 []: prace związane z wiki, pomoc z kodem\n- Wszystkich beta testerów na itch.io i Google Play\n +text.discord=Odwiedź nasz serwer Discord +text.gameover=Rdzeń został zniszczony. +text.highscore=[YELLOW] Nowy rekord! +text.lasted=Wytrwałeś do fali +text.level.highscore=Rekord: [accent]{0} +text.level.delete.title=Potwierdź kasowanie +text.level.select=Wybrany poziom +text.level.mode=Tryb gry: +text.savegame=Zapisz Grę +text.loadgame=Wczytaj grę +text.joingame=Gra wieloosobowa +text.quit=Wyjdź +text.about.button=O grze +text.name=Nazwa: +text.players={0} graczy online +text.players.single={0} gracz online +text.server.mismatch=Błąd pakietu: możliwa niezgodność wersji klienta/serwera.\nUpewnij się, że Ty i host macie najnowszą wersję Mindustry! +text.server.closing=[accent] Zamykanie serwera ... +text.server.kicked.kick=Zostałeś wyrzucony z serwera! +text.server.kicked.invalidPassword=Nieprawidłowe hasło! +text.server.kicked.clientOutdated=Nieaktualna gra! Zaktualizują ją! +text.server.kicked.serverOutdated=Nieaktualna gra! Zaktualizują ją! +text.server.connected={0} dołączył do gry . +text.server.disconnected={0} został rozłączony. +text.nohost=Nie można hostować serwera na mapie niestandardowej! +text.hostserver=Serwer hosta +text.host=Host +text.hosting=[accent] Otwieranie serwera ... +text.hosts.refresh=Odśwież +text.hosts.discovering=Wyszukiwanie gier w sieci LAN +text.server.refreshing=Odświeżanie serwera +text.hosts.none=[lightgray] Brak serwerów w sieci LAN! +text.host.invalid=[scarlet] Nie można połączyć się z hostem. +text.server.friendlyfire=Bratobójczy ogień +text.server.add=Dodaj serwer +text.server.delete=Czy na pewno chcesz usunąć ten serwer? +text.server.hostname=Host: {0} +text.server.edit=Edytuj serwer +text.joingame.title=Dołącz do gry +text.joingame.ip=IP: +text.disconnect=Rozłączony. +text.connecting=[accent]Łączenie ... +text.connecting.data=[accent]Ładowanie danych świata... +text.connectfail=[crimson]Nie można połączyć się z serwerem: [orange] {0} +text.server.port=Port: +text.server.addressinuse=Adres jest już w użyciu! +text.server.invalidport=Nieprawidłowy numer portu. +text.server.error=[crimson] Błąd hostowania serwera: [orange] {0} +text.save.new=Nowy zapis +text.save.overwrite=Czy na pewno chcesz nadpisać zapis gry? +text.overwrite=Nadpisz +text.save.none=Nie znaleziono zapisów gry! +text.saveload=[akcent]Zapisywanie... +text.savefail=Nie udało się zapisać gry! +text.save.delete.confirm=Czy na pewno chcesz usunąć ten zapis gry? +text.save.delete=Usuń +text.save.export=Eksportuj +text.save.import.invalid=[orange]Zapis gry jest niepoprawny! +text.save.import.fail=[crimson]Nie udało się zaimportować zapisu: [orange] {0} +text.save.export.fail=[crimson]Nie można wyeksportować zapisu: [orange] {0} +text.save.import=Importuj +text.save.newslot=Zapisz nazwę: +text.save.rename=Zmień nazwę +text.save.rename.text=Zmień nazwę +text.selectslot=Wybierz zapis. +text.slot=[accent]Slot {0} +text.save.corrupted=[orange]Zapis gry jest uszkodzony lub nieprawidłowy! +text.empty= +text.on=Włączone +text.off=Wyłączone +text.save.autosave=Zapisywanie automatyczne +text.save.map=Mapa: {0} +text.save.wave=Fala: {0} +text.save.date=Ostatnio zapisano: {0} +text.confirm=Potwierdź +text.delete=Usuń +text.ok=Ok +text.open=Otwórz +text.cancel=Anuluj +text.openlink=Otwórz link +text.back=Wróć +text.quit.confirm=Czy na pewno chcesz wyjść? +text.loading=[accent]Ładowanie ... +text.wave=[orange]Fala {0} +text.wave.waiting=Fala w {0} +text.waiting=Oczekiwanie... +text.enemies={0} wrogów +text.enemies.single={0} wróg +text.loadimage=Załaduj obraz +text.saveimage=Zapisz obraz +text.editor.badsize=[orange]Nieprawidłowe wymiary obrazu![]\nWymiary poprawne: {0} +text.editor.errorimageload=Błąd podczas ładowania pliku obrazu: [orange]{0} +text.editor.errorimagesave=Błąd podczas zapisywania pliku obrazu: [orange]{0} +text.editor.generate=Generuj +text.editor.resize=Zmień rozmiar +text.editor.loadmap=Załaduj mapę +text.editor.savemap=Zapisz mapę +text.editor.loadimage=Załaduj obraz +text.editor.saveimage=Zapisz obraz +text.editor.unsaved=[scarlet]Masz niezapisane zmiany![]\nCzy na pewno chcesz wyjść? +text.editor.resizemap=Zmień rozmiar mapy +text.editor.mapname=Nazwa mapy: +text.editor.overwrite=[accent]Uwaga!\nSpowoduje to nadpisanie istniejącej mapy. +text.editor.selectmap=Wybierz mapę do załadowania: +text.width=Szerokość: +text.height=Wysokość: +text.menu=Menu +text.play=Graj +text.load=Wczytaj +text.save=Zapisz +text.language.restart=Uruchom grę ponownie aby ustawiony język zaczął funkcjonować. +text.settings.language=Język +text.settings=Ustawienia +text.tutorial=Poradnik +text.editor=Edytor +text.mapeditor=Edytor map +text.donate=Wspomóż nas +text.settings.reset=Przywróć domyślne +text.settings.controls=Sterowanie +text.settings.game=Gra +text.settings.sound=Dźwięk +text.settings.graphics=Grafika +text.upgrades=Ulepszenia +text.purchased=[LIME]Stworzono! +text.weapons=Bronie +text.paused=Wstrzymano +text.info.title=[accent]Informacje +text.error.title=[crimson]Wystąpił błąd +text.error.crashtitle=Wystąpił błąd +text.blocks.blockinfo=Informacje o bloku +text.blocks.powercapacity=Moc znamionowa +text.blocks.powershot=moc / strzał +text.blocks.size=Rozmiar +text.blocks.liquidcapacity=Pojemność cieczy +text.blocks.maxitemssecond=Maksymalna liczba przedmiotów / Sekunda +text.blocks.powerrange=Zakres mocy +text.blocks.itemcapacity=Pojemność przedmiotów +text.blocks.inputliquid=Potrzebna ciecz +text.blocks.inputitem=Potrzebne przedmioty +text.blocks.explosive=Wysoce wybuchowy! +text.blocks.health=Zdrowie +text.blocks.inaccuracy=Niedokładność +text.blocks.shots=Strzały +text.blocks.inputcapacity=Pojemność wejściowa +text.blocks.outputcapacity=Wydajność wyjściowa +setting.difficulty.easy=łatwy +setting.difficulty.normal=normalny +setting.difficulty.hard=trudny +setting.difficulty.insane=szalony +setting.difficulty.purge=Czystka +setting.difficulty.name=Poziom trudności +setting.screenshake.name=Trzęsienie się ekranu +setting.indicators.name=Wskaźniki wroga +setting.effects.name=Wyświetlanie efektów +setting.sensitivity.name=Czułość kontrolera +setting.saveinterval.name=Interwał automatycznego zapisywania +setting.seconds=Sekundy +setting.fps.name=Widoczny licznik FPS +setting.vsync.name=Synchronizacja pionowa +setting.lasers.name=Pokaż lasery zasilające +setting.healthbars.name=Pokaż paski zdrowia jednostki +setting.musicvol.name=Głośność muzyki +setting.mutemusic.name=Wycisz muzykę +setting.sfxvol.name=Głośność dźwięków +setting.mutesound.name=Wycisz dźwięki +map.maze.name=labirynt +map.fortress.name=twierdza +map.sinkhole.name=wgłębienie +map.caves.name=jaskinie +map.volcano.name=wulkan +map.caldera.name=kaldera +map.scorch.name=opalacz +map.desert.name=pustynia +map.island.name=wyspa +map.grassland.name=łąka +map.tundra.name=tundra +map.spiral.name=spirala +map.tutorial.name=Poradnik +keybind.move_x.name=Poruszanie w poziomie +keybind.move_y.name=Poruszanie w pionie +keybind.select.name=Wybieranie +keybind.break.name=Niszczenie +keybind.shoot.name=Strzelanie +keybind.zoom_hold.name=Inicjator przybliżania +keybind.zoom.name=Przybliżanie +keybind.menu.name=menu +keybind.pause.name=pauza +keybind.dash.name=przyśpieszenie +keybind.rotate_alt.name=Obracanie (1) +keybind.rotate.name=Obracanie (2) +mode.waves.name=Fale +mode.sandbox.name=sandbox +mode.freebuild.name=budowanie +item.stone.name=kamień +item.coal.name=węgiel +item.titanium.name=tytan +item.thorium.name=uran +item.sand.name=piasek +liquid.water.name=woda +liquid.lava.name=lawa +liquid.oil.name=olej +block.door.name=drzwi +block.door-large.name=duże drzwi +block.conduit.name=Rura +block.pulseconduit.name=Rura impulsowa +block.liquidrouter.name=Rozdzielacz płynów +block.conveyor.name=Przenośnik +block.router.name=Rozdzielacz +block.junction.name=węzeł +block.liquidjunction.name=Węzeł dla płynów +block.sorter.name=Sortownik +block.smelter.name=huta +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_pt_BR.properties b/core/assets/bundles/bundle_pt_BR.properties index 664b1e25a1..3b54e1282d 100644 --- a/core/assets/bundles/bundle_pt_BR.properties +++ b/core/assets/bundles/bundle_pt_BR.properties @@ -3,9 +3,8 @@ text.discord=Junte-se ao Discord do Mindustry! (Lá nós falamos em inglês) text.gameover=O núcleo foi destruído. text.highscore=[YELLOW]Novo recorde! text.lasted=Você durou até a horda -text.level.highscore= Melhor\npontuação: [accent] {0} +text.level.highscore=Melhor\npontuação: [accent] {0} text.level.delete.title=Confirmar exclusão -text.level.delete=Você tem certeza que quer excluir\no mapa "[orange]{0}"? text.level.select=Seleção de Fase text.level.mode=Modo de Jogo: text.savegame=Salvar Jogo @@ -33,7 +32,6 @@ text.loading=[accent]Carregando... text.wave=[orange]Horda {0} text.wave.waiting=Horda em {0} text.waiting=Aguardando... -text.countdown=Horda em {0} text.enemies={0} Inimigos restantes text.enemies.single={0} Inimigo restante text.loadimage=Carregar\nImagem @@ -48,21 +46,12 @@ text.editor.savemap=Salvar\n Mapa text.editor.loadimage=Carregar\n Imagem text.editor.saveimage=Salvar\nImagem text.editor.unsaved=[scarlet]Você tem alterações não salvas![]\nTem certeza que quer sair? -text.editor.brushsize=Tamanho do pincel: {0} -text.editor.noplayerspawn=Este mapa não tem ponto de spawn para o jogador! -text.editor.manyplayerspawns=Mapas não podem ter mais de um\nponto de spawn para jogador! -text.editor.manyenemyspawns=Não pode haver mais de\n{0} pontos de spawn para inimigos! text.editor.resizemap=Redimensionar Mapa -text.editor.resizebig=[scarlet]Aviso!\n[]Mapas maiores que 256 unidades podem ser 'lentos' e instáveis text.editor.mapname=Nome do Mapa: text.editor.overwrite=[accent]Aviso!\nIsso sobrescreve um mapa existente. -text.editor.failoverwrite=[crimson]Não é possível salvar sobre o mapa padrão! text.editor.selectmap=Selecione uma mapa para carregar: text.width=Largura: text.height=Altura: -text.randomize=Aleatório -text.apply=Aplicar -text.update=Atualizar text.menu=Menu text.play=Jogar text.load=Carregar @@ -81,58 +70,27 @@ text.upgrades=Melhorias text.purchased=[LIME]Comprado! text.weapons=Arsenal text.paused=Pausado -text.respawn=Reaparecendo em text.error.title=[crimson]Um erro ocorreu -text.error.crashmessage=[SCARLET]Um erro inesperado aconteceu, que pode ter causado o jogo a fechar. []Por favor, informe as exatas circunstâncias em que o erro ocorreu ao desenvolvidor:\n[ORANGE]anukendev@gmail.com[] text.error.crashtitle=Um erro ocorreu. -text.blocks.extrainfo=[accent]Informação extra: text.blocks.blockinfo=Informação do Bloco text.blocks.powercapacity=Capacidade de Energia text.blocks.powershot=Energia/tiro -text.blocks.powersecond=Energia/segundo -text.blocks.powerdraindamage=Energia/dano -text.blocks.shieldradius=Raio do Escudo -text.blocks.itemspeedsecond=Itens/segundo -text.blocks.range=Alcance text.blocks.size=Tamanho -text.blocks.powerliquid=Energia/Líquido -text.blocks.maxliquidsecond=Entrada Máx. Líquido/segundo text.blocks.liquidcapacity=Capacidade de Líquido -text.blocks.liquidsecond=Líquido/segundo -text.blocks.damageshot=Dano/tiro -text.blocks.ammocapacity=Munição Máxima -text.blocks.ammo=Munição -text.blocks.ammoitem=Munição/item text.blocks.maxitemssecond=Máximo de itens/segundo text.blocks.powerrange=Alcance da Energia -text.blocks.lasertilerange=Alcance do Laser (em células) -text.blocks.capacity=Capacidade text.blocks.itemcapacity=Capacidade de Itens -text.blocks.powergenerationsecond=Geração de Energia/segundo -text.blocks.generationsecondsitem=Tempo de geração/item -text.blocks.input=Entrada text.blocks.inputliquid=Líquido de entrada text.blocks.inputitem=Item de entrada -text.blocks.output=Saída -text.blocks.secondsitem=Segundos/item -text.blocks.maxpowertransfersecond=Transferência máxima de Energia/segundo text.blocks.explosive=Altamente Explosivo! -text.blocks.repairssecond=Reparo/segundo text.blocks.health=Saúde text.blocks.inaccuracy=Imprecisão text.blocks.shots=Tiros -text.blocks.shotssecond=Taxa de tiro -text.placemode=Modo construção -text.breakmode=Modo remoção -text.health=Saúde setting.difficulty.easy=Fácil setting.difficulty.normal=Normal setting.difficulty.hard=Difícil setting.difficulty.name=Dificuldade setting.screenshake.name=Balanço da Tela -#Tremor da tela? -setting.smoothcam.name=Câmera suave -#Suavizar Câmera? setting.indicators.name=Indicadores de Inimigos setting.effects.name=Particulas setting.sensitivity.name=Sensibilidade do Controle @@ -140,7 +98,6 @@ setting.fps.name=Mostrar FPS setting.vsync.name=VSync setting.lasers.name=Mostrar lasers setting.healthbars.name=Mostrar barra de saúde de entidades -setting.pixelate.name=Pixelar Tela setting.musicvol.name=Volume da Música setting.mutemusic.name=Desligar Musica setting.sfxvol.name=Volume de Efeitos @@ -158,54 +115,10 @@ map.grassland.name=grassland map.tundra.name=tundra map.spiral.name=spiral map.tutorial.name=tutorial -tutorial.intro.text=[yellow]Bem-vindo ao tutorial.[] Para começar aperte 'próximo'. -tutorial.moveDesktop.text=Para mover, use as teclas [orange][[WASD][]. Segure [orange]shift[] para mover rápido. Segure [orange]CTRL[] enquanto usa a [orange]roda do mouse[] para aumentar ou diminuir o zoom. -tutorial.shootInternal.text=Use o mouse para mirar, segure [orange]botão esquerdo do mouse[] para atirar. Tente praticar no [yellow]alvo[]. -tutorial.moveAndroid.text=Para arrastar a visão, passe um dedo pela tela. Pince com os dedos para aumentar ou diminuir o zoom. -tutorial.placeSelect.text=Tente selecionar uma [yellow]esteira[] do menu de blocos no canto inferior direito. -tutorial.placeConveyorDesktop.text=Use a [orange][[roda do mouse][] para girar a esteira até que aponte [orange]para frente[], então coloque-a no [yellow]local marcado[] usando o [orange][[botão esquerdo do mouse][]. -tutorial.placeConveyorAndroid.text=Use o [orange][[botão de girar][] para girar a esteira para que aponte [orange]para frente[], arraste-a para a posição e então coloque-a na [yellow]posição marcada[] usando o [orange][[botão de confirmação][]. -tutorial.placeConveyorAndroidInfo.text=Você também pode apertar no ícone com uma cruz no canto inferior esquerdo para alterar para o [orange][[modo de toque][], e colocar blocos apertando na tela. No modo de toque, blocos podem ser girados com a seta no canto inferior esquerdo. Aperte [yellow]próximo[] para tentar. -tutorial.placeDrill.text=Agora selecione e coloque uma [yellow]broca de pedra[] no local marcado. -tutorial.blockInfo.text=Se quiser saber mais sobre os blocos, você pode apertar o [orange]símbolo de interrogação[] no canto superior direito para ler mais. -tutorial.deselectDesktop.text=Você pode cancelar a seleção de um bloco usando o [orange][botão direito do mouse[]. -tutorial.deselectAndroid.text=ocê pode cancelar a seleção de um bloco apertando o botão [orange]X[]. -tutorial.drillPlaced.text=A broca produzirá [yellow]pedra[], direcionando o produzido para a esteira a qual moverá a pedra para o [yellow]núcleo[]. -tutorial.drillInfo.text=Minérios diferentes precisam de diferentes brocas. Pedra precisam de brocas de pedra, Ferro de brocas de ferro, etc. -tutorial.drillPlaced2.text=Itens movidos para o núcleo são colocados em seu [yellow]inventário[], no canto superior esquerdo. Colocar blocos gasta os recursos do inventário. -tutorial.moreDrills.text=Você pode conectar várias brocas e esteiras, veja. -tutorial.deleteBlock.text=Você pode excluir blocos clickando com o [orange]botão direito do mouse[] no bloco que quiser destruir. Tente excluir esta esteira. -tutorial.deleteBlockAndroid.text=Você pode excluir blocos [orange]apertando na cruz[] no [orange]menu modo de quebra[] no canto inferior esquerdo e então apertando no bloco desejado. Tente excluir esta esteira. -tutorial.placeTurret.text=Agora, selecione e construa uma [yellow]torre[] no [yellow]local marcado[]. -tutorial.placedTurretAmmo.text=Esta torre aceitará [yellow]munição[] da esteira. Você pode ver quanta munição elas tem passando o mouse sobre elas e verificando a [green]barra verde[]. -tutorial.turretExplanation.text=As torres irão atirar no inimigo mais próximo que estiver ao alcance, contanto que tenham munição suficiente. -tutorial.waves.text=A cada [yellow]60[] segundos, uma horda de [coral]inimigos[] irá aparecer em locais específicos e tentará destruir o núcleo. -tutorial.coreDestruction.text=Seu objetivo é [yellow]defender o núcleo[]. Se o núcleo for destruído, vecê [coral]perde o jogo[]. -tutorial.pausingDesktop.text=Se você precisar parar por alguns instantes, aperte o [orange]botão de pausa[] no canto superior esquerdo ou [orange]barra de espaço[] para pausar o jogo. Você pode colocar blocos enquanto o jogo esta pausado, porém não poderá se mover ou atirar. -tutorial.pausingAndroid.text=Se você precisar parar por alguns instantes, aperte o [orange]botão de pausa[] no canto superior esquerdo ou [orange]barra de espaço[] para pausar o jogo. Você pode colocar blocos enquanto o jogo esta pausado. -tutorial.purchaseWeapons.text=Você pode comprar novas [yellow]armas[] para seu mecha, basta abrir o menu de melhorias no canto inferior esquerdo. -tutorial.switchWeapons.text=Alterne entre suas armas clickando em seu ícone ou usando as teclas numéricas [orange][[1-9][]. -tutorial.spawnWave.text=Uma horda esta vindo. Destrúa-os. -tutorial.pumpDesc.text=Em hordas mais avançadas, você talvez precise de [yellow]bombas[] para distribuir líquidos para geradores ou extratores. -tutorial.pumpPlace.text=Bombas trabalham de forma semelhante às brocas, porém elas produzem líquidos ao envés de minérios. Tente colocar uma bomba na [yellow]célula de petróleo designada[]. -tutorial.conduitUse.text=Agora coloque um [orange]cano[] levando para longe da bomba. -tutorial.conduitUse2.text=E mais alguns... -tutorial.conduitUse3.text=E mais alguns... -tutorial.generator.text=Agora coloque um [orange]gerador a combustão[] no final do cano. -tutorial.generatorExplain.text=Este gerador irá produzir [yellow]energia[] do petróleo. -tutorial.lasers.text=Energia é distribuida usando [yellow]lasers[]. Gire e coloque um aqui. -tutorial.laserExplain.text=O gerador irá mover energia para o bloco do laser. Um feixe [yellow]opaco[] significa que a energia está sendo transmitida, e um feixe [yellow]transparente[] significa que não. -tutorial.laserMore.text=Você pode verificar quanta energia um bloco tem ao passar o mouse sobre eles e verificando a [yellow]barra amarela[] no topo. -tutorial.healingTurret.text=Este laser pode ser usado para energizar uma [lime]torre de reparo[]. Coloque uma aqui. -tutorial.healingTurretExplain.text=Enquanto tiver energia, esta torre irá [lime]reparar blocos próximos.[] Quando jogar, tenha certeza de construir uma dessas próximas do núcleo o mais rápido possível! -tutorial.smeltery.text=Muitos blocos precisam de [orange]aço[] para serem construídos, o que requer uma [orange]fundidora[] para ser feito. Coloque uma aqui. -tutorial.smelterySetup.text=Esta fundidora irá produzir [orange]aço[] quando receber carvão e ferro. -tutorial.end.text=E este é o fim do Tutorial! Boa Sorte! keybind.move_x.name=move_x keybind.move_y.name=move_y keybind.select.name=selecionar keybind.break.name=quebrar -keybind.shootInternal.name=atirar keybind.zoom_hold.name=segurar_zoom keybind.zoom.name=zoom keybind.menu.name=menu @@ -213,259 +126,370 @@ keybind.pause.name=pausar keybind.dash.name=Correr keybind.rotate_alt.name=girar_alt* keybind.rotate.name=girar -keybind.weapon_1.name=Arma 1 -keybind.weapon_2.name=Arma 2 -keybind.weapon_3.name=Arma 3 -keybind.weapon_4.name=Arma 4 -keybind.weapon_5.name=Arma 5 -keybind.weapon_6.name=Arma 6 mode.waves.name=hordas mode.sandbox.name=sandbox -#CAIXINHA DE AREIA mode.freebuild.name=construção \nlivre -weapon.blaster.name=Blaster -weapon.blaster.description=Atira um projétil lento e fraco. -weapon.triblaster.name=Blaster Triplo -weapon.triblaster.description=Atira 3 balas que se espalham. -weapon.multigun.name=Escopeta -weapon.multigun.description=Atira balas com baixa precisão e uma\n alta taxa de disparo. -weapon.flamer.name=Lança-Chamas -weapon.railgun.name=Rifle Sniper -weapon.flamer.description=É um lança-chamas. O que mais ele faria? -weapon.railgun.description=Atira um projétil de longo alcance. -weapon.mortar.name=Morteiro -weapon.mortar.description=Atira um projétil lento, porém devastador. item.stone.name=Pedra -item.iron.name=Ferro item.coal.name=Carvão -item.steel.name=Aço item.titanium.name=Titânio -item.dirium.name=Dírio -item.uranium.name=Urânio +item.thorium.name=Urânio liquid.water.name=Água -liquid.plasma.name=Plasma liquid.lava.name=Lava liquid.oil.name=Petróleo -block.air.name=Ar -block.blockpart.name=blockpart -#que? -block.deepwater.name=Água Profunda -block.water.name=Água -block.lava.name=Lava -block.oil.name=Petróleo -block.stone.name=Pedra -block.blackstone.name=Pedra Escura -block.iron.name=Ferro -block.coal.name=Carvão -block.titanium.name=Titânio -block.uranium.name=Urânio -block.dirt.name=Terra -block.sand.name=Areia -block.ice.name=Gelo -block.snow.name=Neve -block.grass.name=Grama -block.sandblock.name=Bloco de Areia -block.snowblock.name=Bloco de Neve -block.stoneblock.name=Rocha -block.blackstoneblock.name=Rocha Escura -block.grassblock.name=Bloco de Grama -block.mossblock.name=Musgo -block.shrub.name=Arbusto -block.rock.name=Rocha -block.icerock.name=Rocha de Gelo -block.blackrock.name=Rocha Escura -block.dirtblock.name=Bloco de Terra -block.stonewall.name=Parede de Pedra -block.stonewall.fulldescription=Um bloco defensivo barato. Útil para proteger o núcleo e torres nas primeiras hordas. -block.ironwall.name=Parede de Ferro -block.ironwall.fulldescription=Um bloco defensivo básico. Fornece proteção contra inimigos. -block.steelwall.name=Parede de aço -block.steelwall.fulldescription=Um bloco defensivo padrão. Fornece proteção contra inimigos. -block.titaniumwall.name=Parede de Titânio -block.titaniumwall.fulldescription=Um bloco defensivo forte. Fornece proteção contra inimigos. -block.duriumwall.name=Parede de Dírio -block.duriumwall.fulldescription=Um bloco defensivo muito forte. Fornece proteção contra inimigos. -block.compositewall.name=Parede de Composto -block.compositewall.fulldescription= Um bloco defensivo extremamente forte. Fornece a melhor proteção contra inimigos. -block.steelwall-large.name=Parede Grande de Aço -block.steelwall-large.fulldescription=Um bloco defensivo padrão. Ocupa multiplas células. -block.titaniumwall-large.name=Parede Grande de Titânio -block.titaniumwall-large.fulldescription=Um bloco defensivo forte. Ocupa multiplas células. -block.duriumwall-large.name=Parede Grande de Dírio -block.duriumwall-large.fulldescription=Um bloco defensivo muito forte. Ocupa multiplas células. -block.titaniumshieldwall.name=Parede com Escudo -block.titaniumshieldwall.fulldescription=Um bloco defensivo forte, com um escudo de energia imbutido. Usa energia passivamente e para absorver projéteis inimigos. É recomendado usar distribuidores de energia para abastecer este bloco. -#A strong defensive block, with an extra built-in shield. Requires power. Uses energy to absorb enemy bullets. It is recommended to use power boosters to provide energy to this block. -block.repairturret.name=Torre de Reparo -block.repairturret.fulldescription=Lentamente repara blocos danificados dentro do seu alcance. Consome um pouco de energia. -#Repairs nearby damaged blocks in range at a slow rate. Uses small amounts of power. -block.repairturret.description=[powerinfo]Consome Energia.[white]\nRepara blocos próximos. -block.megarepairturret.name=Torre de Reparo II -block.megarepairturret.fulldescription=Repara blocos danificados dentro do seu alcance. Consome um pouco de energia. -block.megarepairturret.description=[powerinfo]Consome Energia.[white]\nRepara blocos próximos. -block.shieldgenerator.name=Gerador de Escudo -block.shieldgenerator.fulldescription= Um bloco defensivo avançado. Protege todos os blocos em um raio. Lentamente usa energia quando parado, mas rapidamente drena em contato com projéteis. -#An advanced defensive block. Shields all the blocks in a radius from attack. Uses power at a slow rate when idle, but drains energy quickly on bullet contact. block.door.name=Porta -block.door.fulldescription=Um bloco que pode ser aberto e fechado ao tocar nele. -block.door.description=Abre e Fecha.\n[interact]Toque para alternar o estado. block.door-large.name=Porta Grande -block.door-large.fulldescription=Um bloco que pode ser aberto e fechado ao tocar nele. -block.door-large.description=Abre e Fecha.\n[interact]Toque para alternar o estado. block.conduit.name=Cano -block.conduit.fulldescription=Bloco de transporte de líquido básico. Funciona como uma esteira, mas com líquidos. Pode ser usado como uma ponte para inimigos e jogadores. -#Basic liquid transport block. Works like a conveyor, but with liquids. Best used with pumps or other conduits. Can be used as a bridge over liquids for enemies and players. block.pulseconduit.name=Cano de impulso -block.pulseconduit.fulldescription=Bloco de transporte de líquido avançado. Transporta líquidos mais rapidamente e armazena mais que canos normais. -#Advanced liquid transport block. Transports liquids faster and stores more than standard conduits. block.liquidrouter.name=Roteador de líquido -block.liquidrouter.fulldescription=Aceita líquido de uma direção e o redireciona para as outras 3 direções. Útil para dividir o líquido entre vários canos. -#Works similarly to a router. Accepts liquid input from one side and outputs it to the other sides. Useful for splitting liquid from a single conduit into multiple other conduits. -block.liquidrouter.description=Divide líquidos em 3 direções. block.conveyor.name=Esteira -block.conveyor.fulldescription=Bloco de transporte básico. Movimenta itens para frente e automaticamente os deposita em torres ou blocos de fabricação. Pode ser girado. Pode ser usado como uma ponte para inimigos e jogadores. -#Basic item transport block. Moves items forward and automatically deposits them into turrets or crafters. Rotatable. Can be used as a bridge over liquids for enemies and players. -block.steelconveyor.name=Esteira de aço -block.steelconveyor.fulldescription=Bloco de transporte avançado. Movimenta itens mais rapidamente que esteiras normais. -#Advanced item transport block. Moves items faster than standard conveyors. -block.poweredconveyor.name=Esteira de Impulso -block.poweredconveyor.fulldescription=O Bloco supremo de transporte. Movimenta itens mais rapidamente que esteiras de aço. -#The ultimate item transport block. Moves items faster than steel conveyors. block.router.name=Roteador -block.router.fulldescription=Aceita itens de uma direção e os redireciona para as outras 3 direções. Pode guardar uma certa quantidade de itens. Útil para dividir materiais entre várias torres. block.router.description=Divide materiais em 3 direções. block.junction.name=Junção -block.junction.fulldescription=Funciona como uma ponte para 2 linhas de esteiras que se cruzam. Útil em situações onde duas esteiras carregam diferentes materiais para diferentes locais. -block.junction.description=Funciona como uma junção para as esteiras. -block.conveyortunnel.name=Túnel de esteira -block.conveyortunnel.fulldescription=Transporta itens por baixo de blocos. Para usar coloque um túnel apontado para o bloco que deseja passar por baixo, e outro apontado para o primeiro túnel. -block.conveyortunnel.description=Transporta intes por baixo de blocos. block.liquidjunction.name=Junção de líquido -block.liquidjunction.fulldescription=Funciona como uma ponte para 2 canos que se cruzam. Útil em situações onde 2 canos diferentes carregam diferentes líquidos para diferentes locais. -block.liquiditemjunction.name=liquid-item junction -block.liquiditemjunction.fulldescription=Acts as a bridge for crossing conduits and conveyors. -block.liquiditemjunction.description=Serves as a junction for items and liquids. -block.powerbooster.name=Distribuidor de energia -block.powerbooster.fulldescription=Distribui energia para todos os blocos dentro de seu raio. -block.powerbooster.description=Distribui energia em um raio. -block.powerlaser.name=Laser -#Laser de energia? -block.powerlaser.fulldescription=Cria um laser que transmite energia para o bloco à sua frente. Melhor usado com geradores ou outros lasers. Não gera energia. -block.powerlaser.description=Transmite energia. -block.powerlaserrouter.name=laser duplo -block.powerlaserrouter.fulldescription=Divide a entrada de energia em 3 lasers. Útil em situações onde é necessário conectar muitos blocos a partir de um gerador. -block.powerlaserrouter.description=Divide a entrada de energia em 3 lasers. -block.powerlasercorner.name=laser triplo -#*Essa nomeação ficou escrota -block.powerlasercorner.fulldescription=Laser que distribui energia para duas direções ao mesmo tempo. Útil em situações onde é necessário conectar muitos blocos a partir de um gerador. -block.powerlasercorner.description=Divide a entrada de energia em 2 lasers. -block.teleporter.name=Teleportador -block.teleporter.fulldescription=Bloco avançado de transporte de itens. Teleportadores transferem itens para outros teleportadores da mesma cor. Não faz nada se não houverem outros da mesma cor. Se houverem múltiplos da mesma cor, um aleatório será selecionado. Toque nas flechas para mudar de cor. -block.teleporter.description=[interact]Tap block to config[] block.sorter.name=Ordenador -block.sorter.fulldescription=Separa itens pelo tipo de material. O material a ser aceito é indicado pela cor do bloco. Todos os itens que correspondem ao material a ser separado são direcionados para frente, todo o resto é direcionado para os lados. block.sorter.description=[interact]Aperte no bloco para configurar[] -block.core.name=núcleo -block.pump.name=bomba -block.pump.fulldescription=Bombeia líquidos de um bloco, geralmente água, lava ou petróleo. Os líquidos são bombeados para canos próximos. -block.pump.description=Bombeia líquidos para canos próximos. -block.fluxpump.name=Bomba de fluxo -block.fluxpump.fulldescription=Uma versão avançada da bomba comum. Guarda mais líquido e bombeia mais rápido. -block.fluxpump.description=Bombeia líquidos para canos próximos. block.smelter.name=Fornalha -block.smelter.fulldescription=O bloco de produção essencial. Quando recebe 1 carvão e\n1 ferro produz 1 aço -block.smelter.description=Converte carvão + ferro em aço. -block.crucible.name=Usina de fundição -block.crucible.fulldescription=Um bloco de produção avançado. Quando recebe 1 titânio e 1 aço produz 1 dírio. -block.crucible.description=Converte aço + titânio em dírio. -block.coalpurifier.name=Extrator de carvão -block.coalpurifier.fulldescription=Um bloco extrator básico. Produz carvão quando fornecido com grandes quantidades de água e pedra. -block.coalpurifier.description=Converte pedra + água em carvão. -block.titaniumpurifier.name=Extrator de titânio -block.titaniumpurifier.fulldescription=Um bloco extrator padrão. Produz titânio quando fornecido com grandes quantidas de água e ferro. -block.titaniumpurifier.description=Converte água e ferro em titânio. -block.oilrefinery.name=Refinaria de Petróleo -block.oilrefinery.fulldescription=Refina grande quantidades de petróleo para produzir carvão. Útil para abastecer torres que utilizam carvão quando jazidas de carvão são escassas. -block.oilrefinery.description=Converte petróleo em carvão. -block.stoneformer.name=Formador de Pedra -block.stoneformer.fulldescription=Solidifica lava para formar pedra. Útil para produzir grandes quantidades de pedra para extratores de carvão. -block.stoneformer.description=Converte lava em pedra. -block.lavasmelter.name=Fornalha à Lava -block.lavasmelter.fulldescription=Usa lava para converter ferro em aço. Uma alternativa para a fundidora. Útil em situações onde não há carvão por perto. -block.lavasmelter.description=Converte ferro + lava em aço. -block.stonedrill.name=Broca de pedra -block.stonedrill.fulldescription=A broca essencial. Quando colocada em uma jazida de pedra gera pedra indefinidamente. -block.stonedrill.description=Gera 1 pedra a cada 4 segundos. -#Mines 1 stone every 4 seconds. -block.irondrill.name=Broca de Ferro -block.irondrill.fulldescription=Uma broca básica. Quando colocada sobre uma jazida de ferro, lentamente gera ferro. -#A basic drill. When placed on iron ore tiles, outputs iron at a slow pace indefinitely. -block.irondrill.description=Gera 1 ferro a cada 5 segundos. -block.coaldrill.name=Broca de Carvão -block.coaldrill.fulldescription=Uma broca básica. Quando colocada sobre uma jazida de carvão, lentamente gera carvão. -block.coaldrill.description=Gera 1 carvão a cada 5 segundos. -block.uraniumdrill.name=Broca de Urânio -block.uraniumdrill.fulldescription=Uma broca avançada. Quando colocada sobre uma jazida de urânio, lentamente gera urânio. -block.uraniumdrill.description=Gera 1 Urânio a cada 7 segundos. -block.titaniumdrill.name=Broca de Titânio -block.titaniumdrill.fulldescription=Uma broca avançada. Quando colocada sobre uma jazida de titânio, lentamente gera titânio. -block.titaniumdrill.description=Gera 1 Titânio a cada 5 segundos. -block.omnidrill.name=Omnibroca -block.omnidrill.fulldescription=A broca suprema. Rapidamente extrai qualquer minério em que é colocada. -#The ultimate drill. Will mine any ore it is placed on at a rapid pace. -block.omnidrill.description=Gera 1 de qualquer recurso a cada 3 segundos. -block.coalgenerator.name=Gerador à Carvão -#Crase ou não? -block.coalgenerator.fulldescription=O gerador essencial. Gera energia a partir de carvão. Distribui energia em forma de laser para os 4 lados. -block.coalgenerator.description=Gera energia a partir de carvão. -block.thermalgenerator.name=Gerador Térmico -block.thermalgenerator.fulldescription=Gera energia a partir de lava. Distribui energia em forma de laser para os 4 lados. -block.thermalgenerator.description=Gera energia a partir de lava. -block.combustiongenerator.name=Gerador à Combustão -block.combustiongenerator.fulldescription=Gera energia a partir de petróleo. Distribui energia em forma de laser para os 4 lados. -block.combustiongenerator.description=Gera energia a partir de petróleo. -block.rtgenerator.name=Gerador RTG -block.rtgenerator.fulldescription=Gera pouca quantidade de energia a partir do decaimento radioativo do urânio. Distribui energia em forma de laser para os 4 lados. -block.rtgenerator.description=Gera energia a partir de Urânio. -block.nuclearreactor.name=Reator Nuclear -block.nuclearreactor.fulldescription=Uma versão avançada do gerador RTG. Gera energia a partir de Urânio. Requer constante resfriamento à água. Altamente volátil; explodirá violentamente se não for suprido com quantiddades suficientes de água. -block.turret.name=Torre Comum -block.turret.fulldescription=Uma torre básica e barata. Usa pedra como munição. Tem alcance um pouco maior que a torre dupla. -block.turret.description=[turretinfo]Munição: pedra -block.doubleturret.name=Torre Dupla -block.doubleturret.fulldescription=Uma versão um pouco mais poderosa do que a torre comum. Usa pedra como munição. Causa um dano maior, porém tem menor alcance. Atira dois projéteis. -block.doubleturret.description=[turretinfo]Munição: pedra -block.machineturret.name=Torre Automática -block.machineturret.fulldescription=Uma torre padrão completa. Usa ferro como munição. Tem alta taxa de disparo e dano decente. -block.machineturret.description=[turretinfo]Munição: ferro -block.shotgunturret.name=Torre Splitter -#Splitter turret -block.shotgunturret.fulldescription=Uma torre padrão. Usa ferro como munição. Atira 7 balas em forma de cone. Pouco alcance, porém maior dano do que a Torre Dupla. -block.shotgunturret.description=[turretinfo]Munição: ferro -block.flameturret.name=Torre lança-\nchamas -block.flameturret.fulldescription=Torre avançada de baixo alcance. Usa carvão. Pouco alcance mas alto dano. Boa para trechos estreitos. Recomenda-se usá-la atŕas de paredes. -block.flameturret.description=[turretinfo]Munição: carvão -block.sniperturret.name=Torre Sniper -#Torre Railgun? -block.sniperturret.fulldescription=Torre avançada de longo alcance. Usa aço como munição. Dano altíssimo, porém baixa taxa de disparo. Cara para usar, porém pode ser colocada longe das linhas inimigas dado seu alcance. -block.sniperturret.description=[turretinfo]Munição: aço -block.mortarturret.name=Torre Flak -block.mortarturret.fulldescription=Torre avançada de dano em área. Usa carvão. Taxa de disparo e balas lentas, mas alto dano em alvo único ou distribuído. -block.mortarturret.description=[turretinfo]Munição: carvão -block.laserturret.name=Torre laser -block.laserturret.fulldescription=Torre de alvo único avançada. Usa energia. Boa torre de alcance médio e uso geral. Alvo único apenas. Nunca erra. -block.laserturret.description=[turretinfo]Usa Energia -block.waveturret.name=Torre Tesla -block.waveturret.fulldescription=Torre de múltiplos alvos avançada. Usa Energia. Alcance médio. Nunca erra. Dano médio-baixo, porém pode acertar vários inimigos simultaneamente com raios conectados. -block.waveturret.description=[turretinfo]Usa Energia -block.plasmaturret.name=Torre de Plasma -block.plasmaturret.fulldescription=Versão altamente avançada da Torre lança-chamas. Usa carvão. Dano altíssimo e alcance médio-baixo. -block.plasmaturret.description=[turretinfo]Munição: carvão -block.chainturret.name=Canhão automático -block.chainturret.fulldescription=A torre de tiro rápido mais avançada. Usa Urânio como munição. Atira grandes projéteis rapidamente. Alcance médio. Ocupa várias células. Extremamente resistente. -block.chainturret.description=[turretinfo]Munição: Urânio -block.titancannon.name=Canhão Titã -block.titancannon.fulldescription=A torre de longo alcance mais avançada. Usa Urânio como munição. Atira várias balas de dano em área à uma taxa de disparo média. Alto alcance. Ocupa várias células. Extremamente resistente. -block.titancannon.description=[turretinfo]Munição: Urânio -block.playerspawn.name=playerspawn -block.enemyspawn.name=enemyspawn +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.joingame=Join Game +text.addplayers=Add/Remove Players +text.newgame=New Game +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.about.button=About +text.name=Name: +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.players={0} players online +text.players.single={0} player online +text.server.mismatch=Packet error: possible client/server version mismatch.\nMake sure you and the host have the\nlatest version of Mindustry! +text.server.closing=[accent]Closing server... +text.server.kicked.kick=You have been kicked from the server! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.invalidPassword=Invalid password! +text.server.kicked.clientOutdated=Outdated client! Update your game! +text.server.kicked.serverOutdated=Outdated server! Ask the host to update! +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.server.connected={0} has joined. +text.server.disconnected={0} has disconnected. +text.nohost=Can't host server on a custom map! +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.hostserver=Host Server +text.host=Host +text.hosting=[accent]Opening server... +text.hosts.refresh=Refresh +text.hosts.discovering=Discovering LAN games +text.server.refreshing=Refreshing server +text.hosts.none=[lightgray]No LAN games found! +text.host.invalid=[scarlet]Can't connect to host. +text.server.friendlyfire=Friendly Fire +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.add=Add Server +text.server.delete=Are you sure you want to delete this server? +text.server.hostname=Host: {0} +text.server.edit=Edit Server +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.joingame.title=Join Game +text.joingame.ip=IP: +text.disconnect=Disconnected. +text.disconnect.data=Failed to load world data! +text.connecting=[accent]Connecting... +text.connecting.data=[accent]Loading world data... +text.connectfail=[crimson]Failed to connect to server: [orange]{0} +text.server.port=Port: +text.server.addressinuse=Address already in use! +text.server.invalidport=Invalid port number! +text.server.error=[crimson]Error hosting server: [orange]{0} +text.save.new=New Save +text.save.none=No saves found! +text.save.delete.confirm=Are you sure you want to delete this save? +text.save.delete=Delete +text.save.export=Export Save +text.save.import.invalid=[orange]This save is invalid! +text.save.import.fail=[crimson]Failed to import save: [orange]{0} +text.save.export.fail=[crimson]Failed to export save: [orange]{0} +text.save.import=Import Save +text.save.newslot=Save name: +text.save.rename=Rename +text.save.rename.text=New name: +text.on=On +text.off=Off +text.save.autosave=Autosave: {0} +text.save.map=Map: {0} +text.save.difficulty=Difficulty: {0} +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.language.restart=Please restart your game for the language settings to take effect. +text.settings.language=Language +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.info.title=[accent]Info +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.blocks.inputcapacity=Input capacity +text.blocks.outputcapacity=Output capacity +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.difficulty.insane=insane +setting.difficulty.purge=purge +setting.saveinterval.name=Autosave Interval +setting.seconds={0} Seconds +setting.fullscreen.name=Fullscreen +setting.multithread.name=Multithreading +setting.minimap.name=Show Minimap +text.keybind.title=Rebind Keys +keybind.shoot.name=shoot +keybind.block_info.name=block_info +keybind.chat.name=chat +keybind.player_list.name=player_list +keybind.console.name=console +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.name=Sand +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_ru.properties b/core/assets/bundles/bundle_ru.properties index dda5282ad3..81593c7e58 100644 --- a/core/assets/bundles/bundle_ru.properties +++ b/core/assets/bundles/bundle_ru.properties @@ -1,555 +1,495 @@ -text.about = Создатель [ROYAL] Anuken. [] \nИзначально игра была создана для участия в [orange] GDL [] MM Jam. \n\nАвторы: \n- Звуковые эффекты, сделаны с помощью [YELLOW] bfxr [] \n- Музыка, создана [GREEN] RoccoW [] / найденная на [lime] FreeMusicArchive.org [] \n\nОсобая благодарность: \n- [coral] MitchellFJN []: в тестировании и отзывах \n- [sky] Luxray5474 []: работа в вики, помощь в разработке \n- Все бета-тестеры на itch.io и Google Play\n\nИгра переведена полностью на русский язык [GREEN]krocotavus[] и [GREEN]lexa1549. Дополнил перевод [GREEN]Prosta4ok_ua[]\n -text.credits = Авторы -text.discord = Присоединяйтесь к нашему Discord чату! -text.changes=[SCARLET] Внимание!\n[]Изменена некоторая важная игровая механика.\n\n-[accent]Телепортеры[]теперь используют силу.\n-[accent]Печи[]и[accent]тигли[]теперь имеют максимальная емкость элемента.\n-[accent]Тигли[]теперь требует угля в качестве топлива. -text.link.discord.description = официальный discord-сервер Mindustry -text.link.github.description = Исходный код игры -text.link.dev-builds.description = Нестабильные разработки -text.link.trello.description = Официальная доска trello для запланированных функций -text.link.itch.io.description = itch.io страница с загрузкой ПК и веб-версией -text.link.google-play.description = Google Play список магазинов -text.link.wiki.description = официальная вики Mindustry -text.linkfail = Не удалось открыть ссылку!\nURL-адрес был скопирован в буфер обмена. -text.editor.web = Веб-версия не поддерживает редактор!\nЗагрузите игру, чтобы использовать ее. -text.multiplayer.web = Эта версия игры не поддерживает многопользовательскую игру! \n Чтобы играть в мультиплеер из своего браузера, используйте ссылку «Многопользовательская веб-версия» на странице itch.io. -text.gameover = Ядро было уничтожено. -text.highscore = [YELLOW]Новый рекорд! -text.lasted = Вы продержались до волны -text.level.highscore = Рекорд: [accent]{0} -text.level.delete.title = Подтвердите удаление -text.level.delete = Вы уверены,что хотите удалить эту карту \"[orange]\"{0}? -text.level.select = Выбор уровня -text.level.mode = Режим игры: -text.savegame = Сохранить игру -text.loadgame = Загрузить игру -text.joingame = Присоединиться -text.newgame= Новая игра -text.quit = Выход -text.about.button = Об игре -text.name = Название: -text.public = Общие -text.players = Игроков на сервере: {0} -text.server.player.host={0} (хост) -text.players.single = {0} игрок на сервере -text.server.mismatch = Ошибка пакета: возможное несоответствие версии клиента / сервера. Убедитесь, что у вас и у создателя сервера установлена ​​последняя версия Mindustry! -text.server.closing = [accent]Закрытие сервера... -text.server.kicked.kick = Вас выгнали с сервера! -text.server.kicked.fastShoot = Вы стреляете слишком быстро. -text.server.kicked.invalidPassword = Неверный пароль. -text.server.kicked.clientOutdated = Устаревший клиент! Обновите игру! -text.server.kicked.serverOutdated = Устаревший сервер! Попросите хост обновить! -text.server.kicked.banned = Вы заблокированы на этом сервере. +text.about=Создатель [ROYAL] Anuken. [] \nИзначально игра была создана для участия в [orange] GDL [] MM Jam. \n\nАвторы: \n- Звуковые эффекты, сделаны с помощью [YELLOW] bfxr [] \n- Музыка, создана [GREEN] RoccoW [] / найденная на [lime] FreeMusicArchive.org [] \n\nОсобая благодарность: \n- [coral] MitchellFJN []: в тестировании и отзывах \n- [sky] Luxray5474 []: работа в вики, помощь в разработке \n- Все бета-тестеры на itch.io и Google Play\n\nИгра переведена полностью на русский язык [GREEN]krocotavus[] и [GREEN]lexa1549. Дополнил перевод [GREEN]Prosta4ok_ua[]\n +text.credits=Авторы +text.discord=Присоединяйтесь к нашему Discord чату! +text.link.discord.description=официальный discord-сервер Mindustry +text.link.github.description=Исходный код игры +text.link.dev-builds.description=Нестабильные разработки +text.link.trello.description=Официальная доска trello для запланированных функций +text.link.itch.io.description=itch.io страница с загрузкой ПК и веб-версией +text.link.google-play.description=Google Play список магазинов +text.link.wiki.description=официальная вики Mindustry +text.linkfail=Не удалось открыть ссылку!\nURL-адрес был скопирован в буфер обмена. +text.editor.web=Веб-версия не поддерживает редактор!\nЗагрузите игру, чтобы использовать ее. +text.multiplayer.web=Эта версия игры не поддерживает многопользовательскую игру! \n Чтобы играть в мультиплеер из своего браузера, используйте ссылку «Многопользовательская веб-версия» на странице itch.io. +text.gameover=Ядро было уничтожено. +text.highscore=[YELLOW]Новый рекорд! +text.lasted=Вы продержались до волны +text.level.highscore=Рекорд: [accent]{0} +text.level.delete.title=Подтвердите удаление +text.level.select=Выбор уровня +text.level.mode=Режим игры: +text.savegame=Сохранить игру +text.loadgame=Загрузить игру +text.joingame=Присоединиться +text.newgame=Новая игра +text.quit=Выход +text.about.button=Об игре +text.name=Название: +text.players=Игроков на сервере: {0} +text.players.single={0} игрок на сервере +text.server.mismatch=Ошибка пакета: возможное несоответствие версии клиента / сервера. Убедитесь, что у вас и у создателя сервера установлена ​​последняя версия Mindustry! +text.server.closing=[accent]Закрытие сервера... +text.server.kicked.kick=Вас выгнали с сервера! +text.server.kicked.fastShoot=Вы стреляете слишком быстро. +text.server.kicked.invalidPassword=Неверный пароль. +text.server.kicked.clientOutdated=Устаревший клиент! Обновите игру! +text.server.kicked.serverOutdated=Устаревший сервер! Попросите хост обновить! +text.server.kicked.banned=Вы заблокированы на этом сервере. text.server.kicked.recentKick=Вы недавно были кикнуты.\n Подождите немного перед следующим подключением -text.server.connected = {0} присоединился -text.server.disconnected = {0} отключился. -text.nohost = Не удается запустить сервер на пользовательской карте! +text.server.connected={0} присоединился +text.server.disconnected={0} отключился. +text.nohost=Не удается запустить сервер на пользовательской карте! text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. -text.hostserver = Запустить сервер -text.host = Сервер -text.hosting = [accent]Открытие сервера... -text.hosts.refresh = Обновить -text.hosts.discovering = Поиск локальных игр -text.server.refreshing = Обновление сервера -text.hosts.none = [lightgray]Локальных игр не обнаружено! -text.host.invalid = [scarlet] Не удается подключиться к хосту. -text.server.friendlyfire = Дружественный огонь -text.trace = Слежка за игроком -text.trace.playername = Имя игрока: [accent]{0} -text.trace.ip = IP: [accent]{0} -text.trace.id = Уникальный идентификатор: [accent]{0} -text.trace.android = Клиент Android: [accent]{0} -text.trace.modclient = Пользовательский клиент: [accent]{0} -text.trace.totalblocksbroken = Всего разбитых блоков: [accent]{0} - -e.structureblocksbroken = Структурных блоков сломанных: [accent]{0} -text.trace.lastblockbroken = Последний сломанный блок:[accent]{0} -text.trace.totalblocksplaced = Всего размещено блоков: [accent]{0} -text.trace.lastblockplaced = Последний размещенный блок: [accent]{0} -text.invalidid = Недопустимый идентификатор клиента! Отправьте отчет об ошибке. -text.server.bans = Блокировки -text.server.bans.none = Никаких заблокированных игроков не найдено! -text.server.admins = Администраторы -text.server.admins.none = Администраторов не найдено! -text.server.add = Добавить сервер -text.server.delete = Вы действительно хотите удалить этот сервер? -text.server.hostname = Хост: {0} -text.server.edit = Редактировать сервер -text.server.outdated = [crimson]Устаревший сервер![] -text.server.outdated.client = [crimson]Устаревший клиент![] -text.server.version = [lightgray]Версия: {0} +text.hostserver=Запустить сервер +text.host=Сервер +text.hosting=[accent]Открытие сервера... +text.hosts.refresh=Обновить +text.hosts.discovering=Поиск локальных игр +text.server.refreshing=Обновление сервера +text.hosts.none=[lightgray]Локальных игр не обнаружено! +text.host.invalid=[scarlet] Не удается подключиться к хосту. +text.server.friendlyfire=Дружественный огонь +text.trace=Слежка за игроком +text.trace.playername=Имя игрока: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Уникальный идентификатор: [accent]{0} +text.trace.android=Клиент Android: [accent]{0} +text.trace.modclient=Пользовательский клиент: [accent]{0} +text.trace.totalblocksbroken=Всего разбитых блоков: [accent]{0} +text.trace.lastblockbroken=Последний сломанный блок:[accent]{0} +text.trace.totalblocksplaced=Всего размещено блоков: [accent]{0} +text.trace.lastblockplaced=Последний размещенный блок: [accent]{0} +text.invalidid=Недопустимый идентификатор клиента! Отправьте отчет об ошибке. +text.server.bans=Блокировки +text.server.bans.none=Никаких заблокированных игроков не найдено! +text.server.admins=Администраторы +text.server.admins.none=Администраторов не найдено! +text.server.add=Добавить сервер +text.server.delete=Вы действительно хотите удалить этот сервер? +text.server.hostname=Хост: {0} +text.server.edit=Редактировать сервер +text.server.outdated=[crimson]Устаревший сервер![] +text.server.outdated.client=[crimson]Устаревший клиент![] +text.server.version=[lightgray]Версия: {0} text.server.custombuild=[yellow]Пользовательская сборка -text.confirmban = Вы действительно хотите заблокировать этого игрока? -text.confirmunban = Вы действительно хотите разблокировать этого игрока? -text.confirmadmin = Вы уверены, что хотите сделать этого игрока администратором? -text.confirmunadmin = Вы действительно хотите удалить статус администратора с этого игрока? -text.joingame.byip = Присоединиться по IP ... -text.joingame.title = Присоединиться к игре -text.joingame.ip = IP: -text.disconnect = Отключён\n -text.disconnect.data = Не удалось загрузить данные мира! -text.connecting = [accent]Подключение... -text.connecting.data = [accent]Загрузка данных мира... -text.connectfail = [crimson]Не удалось подключиться к серверу: [orange] {0} -text.server.port = Порт: +text.confirmban=Вы действительно хотите заблокировать этого игрока? +text.confirmunban=Вы действительно хотите разблокировать этого игрока? +text.confirmadmin=Вы уверены, что хотите сделать этого игрока администратором? +text.confirmunadmin=Вы действительно хотите удалить статус администратора с этого игрока? +text.joingame.title=Присоединиться к игре +text.joingame.ip=IP: +text.disconnect=Отключён\n +text.disconnect.data=Не удалось загрузить данные мира! +text.connecting=[accent]Подключение... +text.connecting.data=[accent]Загрузка данных мира... +text.connectfail=[crimson]Не удалось подключиться к серверу: [orange] {0} +text.server.port=Порт: text.server.addressinuse=Адрес уже используется! -text.server.invalidport = Неверный номер порта! -text.server.error = [crimson]Ошибка создания сервера: [orange] {0} -text.tutorial.back = <назад -text.tutorial.next = далее> -text.save.new = Новое сохранение -text.save.overwrite = Вы уверены,что хотите перезаписать этот слот для сохранения? -text.overwrite = Перезаписать -text.save.none = Сохранения не найдены! -text.saveload = [accent]Сохранение... -text.savefail = Не удалось сохранить игру! -text.save.delete.confirm = Вы уверены,что хотите удалить это сохранение? -text.save.delete = Удалить -text.save.export = Отправить сохранение -text.save.import.invalid = [orange]Это сохранение недействительно! -text.save.import.fail = [crimson]Не удалось импортировать сохранение: [orange] {0} -text.save.export.fail = [crimson]Не удалось отправить сохранение: [orange] {0} -text.save.import = Импортировать сохранение -text.save.newslot = Имя сохранения: -text.save.rename = Переименовывать -text.save.rename.text = Новое название: -text.selectslot = Выберите сохранение. -text.slot = [accent]Слот {0} -text.save.corrupted = [orange]Файл сохранения поврежден или имеет недействительный формат! -text.empty = <Пусто> -text.on = Вкл -text.off = Выкл -text.save.autosave = Автосохранение: {0} -text.save.map = Карта: {0} -text.save.wave = Волна: {0} -text.save.difficulty = Сложность: {0} -text.save.date = Последнее сохранение: {0} -text.confirm = Подтвердить -text.delete = Удалить -text.ok = ОК -text.open = Открыть -text.cancel = Отмена -text.openlink = Открыть ссылку -text.copylink = Скопировать ссылку -text.back = Назад -text.quit.confirm = Вы уверены, что хотите выйти? -text.changelog.title = Список изменений -text.changelog.loading = Получение изменений ... -text.changelog.error.android = [orange]Обратите внимание, что журнал изменений иногда не работает на Android 4.4 и ниже!\nЭто связано с внутренней ошибкой Android. -text.changelog.error.ios = [orange]В настоящее время журнал изменений не поддерживается iOS. -text.changelog.error = [scarlet]Ошибка при получении изменений!\nПроверьте подключение к Интернету. +text.server.invalidport=Неверный номер порта! +text.server.error=[crimson]Ошибка создания сервера: [orange] {0} +text.save.new=Новое сохранение +text.save.overwrite=Вы уверены,что хотите перезаписать этот слот для сохранения? +text.overwrite=Перезаписать +text.save.none=Сохранения не найдены! +text.saveload=[accent]Сохранение... +text.savefail=Не удалось сохранить игру! +text.save.delete.confirm=Вы уверены,что хотите удалить это сохранение? +text.save.delete=Удалить +text.save.export=Отправить сохранение +text.save.import.invalid=[orange]Это сохранение недействительно! +text.save.import.fail=[crimson]Не удалось импортировать сохранение: [orange] {0} +text.save.export.fail=[crimson]Не удалось отправить сохранение: [orange] {0} +text.save.import=Импортировать сохранение +text.save.newslot=Имя сохранения: +text.save.rename=Переименовывать +text.save.rename.text=Новое название: +text.selectslot=Выберите сохранение. +text.slot=[accent]Слот {0} +text.save.corrupted=[orange]Файл сохранения поврежден или имеет недействительный формат! +text.empty=<Пусто> +text.on=Вкл +text.off=Выкл +text.save.autosave=Автосохранение: {0} +text.save.map=Карта: {0} +text.save.wave=Волна: {0} +text.save.difficulty=Сложность: {0} +text.save.date=Последнее сохранение: {0} +text.confirm=Подтвердить +text.delete=Удалить +text.ok=ОК +text.open=Открыть +text.cancel=Отмена +text.openlink=Открыть ссылку +text.copylink=Скопировать ссылку +text.back=Назад +text.quit.confirm=Вы уверены, что хотите выйти? +text.changelog.title=Список изменений +text.changelog.loading=Получение изменений ... +text.changelog.error.android=[orange]Обратите внимание, что журнал изменений иногда не работает на Android 4.4 и ниже!\nЭто связано с внутренней ошибкой Android. +text.changelog.error.ios=[orange]В настоящее время журнал изменений не поддерживается iOS. +text.changelog.error=[scarlet]Ошибка при получении изменений!\nПроверьте подключение к Интернету. text.changelog.current=[yellow][[Текущая версия] text.changelog.latest=[orange][[Последняя версия] -text.loading = [accent] Загрузка... -text.wave = [orange]Волна {0} -text.wave.waiting = Волна через {0} -text.waiting = Ожидание... -text.enemies = {0} Противников -text.enemies.single = {0} Противник -text.loadimage = Загрузить изображение -text.saveimage = Сохранить изображение -text.oregen = Генерация руд -text.editor.badsize = [orange]Недопустимый формат изображения! [] \nДопустимый формат карты: {0} -text.editor.errorimageload = Ошибка загрузки изображения: [orange] {0} -text.editor.errorimagesave = Ошибка сохранения изображения: [orange] {0} -text.editor.generate = Создать -text.editor.resize = Изменить \nразмер -text.editor.loadmap = Загрузить\nкарту -text.editor.savemap = Сохранить\nкарту -text.editor.loadimage = Загрузить \nизображение -text.editor.saveimage = Сохранить \nизображение -text.editor.unsaved = [scarlet]У вас есть не сохраненные изменения![] \nВы уверены,что хотите выйти? -text.editor.brushsize = Размер кисти: {0} -text.editor.noplayerspawn = На этой карте нет точки появления игрока! -text.editor.manyplayerspawns = На карте не может быть больше одной точки появления игрока! -text.editor.manyenemyspawns = Не может быть больше {0} вражеских точек появления! -text.editor.resizemap = Изменить размер карты -text.editor.resizebig = [scarlet]Внимание! \n[]Карты размером больше 256 единиц могут быть не стабильны и тормозить. -text.editor.mapname = Название карты: -text.editor.overwrite = [accent]Внимание! \nЭто перезапишет уже существующую карту. -text.editor.failoverwrite = [crimson]Невозможно перезаписать стандартную карту! -text.editor.selectmap = Выберите карту для загрузки: -text.width = Ширина: -text.height = Высота: -text.randomize = Рандомизировать -text.apply = Применить -text.update = Обновить -text.menu = Меню -text.play = Играть -text.load = Загрузить -text.save = Сохранить -text.language.restart = Перезагрузите игру, чтобы настройки языка вступили в силу. -text.settings.language = Язык -text.settings = Настройки -text.tutorial = Обучение -text.editor = Редактор -text.mapeditor = Редактор карт -text.donate = Донат -text.settings.reset = Сбросить по умолчанию -text.settings.controls = Управление -text.settings.game = Игра -text.settings.sound = Звук -text.settings.graphics = Графика -text.upgrades = Улучшения -text.purchased = [LIME]Создан! -text.weapons = Оружие -text.paused = Пауза -text.respawn = Возрождение через -text.info.title = [accent]Информация -text.error.title = [crimson]Произошла ошибка -text.error.crashmessage = [SCARLET]Произошла непредвиденная ошибка,которая могла вызвать сбой.[]Пожалуйста, сообщите точные обстоятельства разработчику,при которых эта ошибка возникла : [ORANGE]anukendev@gmail.com[] -text.error.crashtitle = Произошла ошибка -text.mode.break = Режим сноса: {0} -text.mode.place = Режим размещения: {0} -placemode.hold.name = линия -placemode.areadelete.name = зона -placemode.touchdelete.name = касание -placemode.holddelete.name = удержание -placemode.none.name = ничего -placemode.touch.name = Касание -placemode.cursor.name = курсор -text.blocks.extrainfo = [accent]дополнительная информация о блоке: -text.blocks.blockinfo = Информация о блоке -text.blocks.powercapacity = Вместимость энергии -text.blocks.powershot = Энергия / выстрел -text.blocks.powersecond = Энергия / в секунду -text.blocks.powerdraindamage = Поглощение энергии / урон -text.blocks.shieldradius = Радиус щита -text.blocks.itemspeedsecond = Скорость предметов / в секунду -text.blocks.range = Дальность -text.blocks.size = Размер -text.blocks.powerliquid = Энергия / Жидкость -text.blocks.maxliquidsecond = Макс. Жидкость / в секунду -text.blocks.liquidcapacity = Вместимость жидкости -text.blocks.liquidsecond = Жидкость / в секунду -text.blocks.damageshot = Урон / выстрел -text.blocks.ammocapacity = Вместимость боеприпасов -text.blocks.ammo = боеприпасы -text.blocks.ammoitem = боеприпасы / предмет -text.blocks.maxitemssecond = Макс. Количество предметов / в секунду -text.blocks.powerrange = Диапазон мощности энергии -text.blocks.lasertilerange = Дальность лазера -text.blocks.capacity = Вместимость -text.blocks.itemcapacity = Вместимость предметов -text.blocks.maxpowergenerationsecond = Макс. выработка энергии / в секунду -text.blocks.powergenerationsecond = Выработка энергии / в секунду -text.blocks.generationsecondsitem = Выработка секунд / предмет -text.blocks.input = Прием -text.blocks.inputliquid = Прием жидкости -text.blocks.inputitem = Прием предмета -text.blocks.output = Вывод -text.blocks.secondsitem = Секунды / предмет -text.blocks.maxpowertransfersecond = Максимальная передача энергии / секунда -text.blocks.explosive = Взрывоопасно! -text.blocks.repairssecond = Ремонт / в секунду -text.blocks.health = Здоровье -text.blocks.inaccuracy = Разброс -text.blocks.shots = Выстрелы -text.blocks.shotssecond = Выстрелов / в секунду -text.blocks.fuel = топливо -text.blocks.fuelduration = Продолжительность действия топлива -text.blocks.maxoutputsecond = Макс. Вывод / в секунду -text.blocks.inputcapacity = Вместимость ввода -text.blocks.outputcapacity = Вместимость вывода -text.blocks.poweritem = Энергия / предмет -text.placemode = Режим размещения -text.breakmode = Режим сноса -text.health = здоровье -setting.difficulty.easy = легко -setting.difficulty.normal = нормально -setting.difficulty.hard = тяжело -setting.difficulty.insane = нереально -setting.difficulty.purge = зачистка -setting.difficulty.name = Сложность -setting.screenshake.name = Дрожание экрана -setting.smoothcam.name = Плавная камера -setting.indicators.name = Индикаторы противников -setting.effects.name = Эффекты на экране -setting.sensitivity.name = Чувствительность контроллера -setting.saveinterval.name = Интервал автосохранения -setting.seconds = {0} Секунд -setting.fullscreen.name = Полноэкранный -setting.multithread.name = Многопоточность -setting.fps.name = Показать FPS -setting.vsync.name = Верт. синхронизация -setting.lasers.name = Показывать энергетические лазеры -setting.previewopacity.name = Прозрачность объкта при предв. просм. -setting.healthbars.name = Показать полоски здоровья объекта -setting.pixelate.name = Пикселизация экрана -setting.musicvol.name = Громкость музыки -setting.mutemusic.name = Заглушить музыку -setting.sfxvol.name = Громкость звуковых эффектов -setting.mutesound.name = Заглушить звук -map.maze.name = лабиринт -map.fortress.name = крепость -map.sinkhole.name = раковина -map.caves.name = пещеры -map.volcano.name = вулкан -map.caldera.name = кальдера -map.scorch.name = горение -map.desert.name = пустыня -map.island.name = остров -map.grassland.name = луг -map.tundra.name = тундра -map.spiral.name = спираль -map.tutorial.name = обучение -tutorial.intro.text = [yellow]Добро пожаловать в обучение.[] Чтобы начать нажмите «далее». -tutorial.moveDesktop.text = Для перемещения используйте [orange][[WASD][] клавиши. Удерживайте [orange]shift[] для ускорения. Удерживайте [orange]CTRL[] при использовании [orange]​​колесика мыши[] для увеличения или уменьшения масштаба. -tutorial.shoot.text = Используйте мышь для прицеливания, удерживайте [orange]левую кнопку мыши[],чтобы выстрелить. Попробуйте на [yellow]цели[]. -tutorial.moveAndroid.text = Чтобы изменить вид, перетащите палец по экрану. Зажмите и разведите двумя пальцами,чтобы увеличить или уменьшить степень приближения. -tutorial.placeSelect.text = Попробуйте выбрать [yellow]конвейер[] из меню блоков внизу справа. -tutorial.placeConveyorDesktop.text = Используйте [orange][[колёсико мыши][],чтобы повернуть конвейер лицом [orange]вперед[], поместите его в [yellow]отмеченное место[],используя [orange][[левую кнопку мыши]]. -tutorial.placeConveyorAndroid.text = Используйте [orange][[кнопку поворота][], чтобы повернуть конвейер лицом [orange]вперед[], перетащите его на место одним пальцем, затем поместите его в [yellow]отмеченное место[],используя [orange][[галочку][]. -tutorial.placeConveyorAndroidInfo.text = Кроме того, вы можете нажать на значок перекрестия в левом нижнем углу, чтобы перейти в [orange][[режим касания][] и поместить блоки, нажав на экран. В режиме касания блоки можно поворачивать стрелкой в ​​левом нижнем углу. Нажмите [yellow]далее[],чтобы попробовать. -tutorial.placeDrill.text = Теперь, выберите и поместите [yellow]каменный бур[] в отмеченное место. -tutorial.blockInfo.text = Если вы хотите узнать больше о блоке, вы можете нажать на [orange]знак вопроса[] в правом верхнем углу, чтобы прочитать его описание. -tutorial.deselectDesktop.text = Вы можете отменить выбор блока, используя [orange][[правую кнопку мыши][]. -tutorial.deselectAndroid.text = Вы можете отменить выбор блока, нажав кнопку [orange]X[]. -tutorial.drillPlaced.text = Бур теперь производит [yellow]камень,[] выведет его на конвейер, а затем переместит его в [yellow]ядро​[] -tutorial.drillInfo.text = Разным рудам нужны разные буры. Камень требует каменный бур, железо требует железный бур и т.д. -tutorial.drillPlaced2.text = Перемещение предметов в ядро ​​помещает их в ваш [yellow]инвентарь для предметов[], в левом верхнем углу. Размещение блоков использует предметы из вашего инвентаря. -tutorial.moreDrills.text = Вы можете соединить много буров и конвейеров вместе, вот так. -tutorial.deleteBlock.text = Вы можете удалить блоки, щелкнув [orange]правой кнопкой мыши[] на блоке, который вы хотите удалить. Попробуйте удалить этот конвейер. -tutorial.deleteBlockAndroid.text = Вы можете удалить блоки [orange]выбрав перекрестие []в меню режима сноса[orange][] в левом нижнем углу и нажать на блок. Попробуйте удалить этот конвейер. -tutorial.placeTurret.text = Теперь выберите и поместите [yellow]турель[] в [yellow]отмеченную позицию[]. -tutorial.placedTurretAmmo.text = Эта турель теперь принимает [yellow]боеприпасы[] от конвейера. Вы можете видеть, сколько патронов у него есть, посмотрев на зеленую полосу над ней [green][]. -tutorial.turretExplanation.text = Турели автоматически стреляют в ближайшего противника в радиусе действия, если у них достаточно боеприпасов. -tutorial.waves.text = Каждые [yellow]60[] секунд, волна [coral]противников[] будет появляться в определенных местах и будет ​​пытаться уничтожить ядро. -tutorial.coreDestruction.text = Ваша цель - [yellow]защитить ядро​​[]. Если ядро будет ​​уничтожено, то вы [cotal]проиграете[] -tutorial.pausingDesktop.text = Если вам нужен будет перерыв, нажмите кнопку [orange]пауза[] в верхнем левом углу или [orange]пробел[],чтобы приостановить игру. Вы можете выбирать и размещать блоки во время паузы, но не можете перемещаться или стрелять. -tutorial.pausingAndroid.text = Если вам нужен будет перерыв, нажмите кнопку [orange]пауза[] в левом верхнем углу, чтобы приостановить игру. Вы можете по-прежнему размещать выбранные блоки во время паузы. -tutorial.purchaseWeapons.text = Вы можете приобрести новое [yellow]оружие[] для своего меха, открыв меню обновлений в левом нижнем углу. -tutorial.switchWeapons.text = Переключаться между оружием можно, щелкнув на его значок в левом нижнем углу или используя цифры [orange][[1-9][]. -tutorial.spawnWave.text = Сейчас прибудет волна. Уничтожьте их. -tutorial.pumpDesc.text = В более поздних волнах, вы должны использовать [yellow]насосы[] для распределения жидкостей для генераторов или экстракторов. -tutorial.pumpPlace.text = Насосы работают аналогично бурам, за исключением того, что они производят жидкости вместо предметов. Попробуйте поместить насос на [yellow]обозначенную нефть[]. -tutorial.conduitUse.text = Теперь поместите [orange]трубопровод[], ведущий от насоса. -tutorial.conduitUse2.text = И еще немного ... -tutorial.conduitUse3.text = И еще немного ... -tutorial.generator.text = Теперь поместите блок[orange]генератор сжигания[] в конец трубопровода. -tutorial.generatorExplain.text = Этот генератор теперь создаст [yellow]энергию[] из нефти. -tutorial.lasers.text = Энергия распределяется с использованием [yellow]электрических лазеров[]. Поверните и поместите его здесь. -tutorial.laserExplain.text = Теперь генератор передаст энергию в лазерный блок. [yellow]Непрозрачный[] луч означает, что он в настоящее время передает электричество, а [yellow]прозрачный[] луч означает, что он не передает электричество. -tutorial.laserMore.text = Вы можете проверить,какой заряд у блока, наблюдая за [yellow]желтой полосой[] над ним. -tutorial.healingTurret.text = Этот лазер можно использовать для питания [lime]ремонтной турели[]. Поместите её сюда. -tutorial.healingTurretExplain.text = Пока она имеет заряд, эта турель будет [lime]ремонтировать соседние блоки.[] Когда вы играете, убедитесь, что вы имеете такую на своей базе как можно быстрее. -tutorial.smeltery.text = Для многих блоков требуется [orange]сталь[], для этого требуется [orange]плавильный завод[]. Поместите его сюда. -tutorial.smelterySetup.text = Этот завод теперь производит [orange]сталь[] из поступающего железа, используя уголь в качестве топлива. -tutorial.tunnelExplain.text = Также обратите внимание, что предметы проходят через [orange] туннельный блок [] и появляются на другой стороне, проходя через каменный блок. Имейте в виду, что туннели могут проходить только до двух блоков. -tutorial.end.text = На этом обучение закончено! Удачи! -text.keybind.title = Переназначить клавиши -keybind.move_x.name = движение_x -keybind.move_y.name = движение_y -keybind.select.name = выбрать -keybind.break.name = Разрушить -keybind.shoot.name = стрельба -keybind.zoom_hold.name = удержание_зума -keybind.zoom.name = Приблизить -keybind.block_info.name = инфо_о_блоке -keybind.menu.name = Меню -keybind.pause.name = Пауза -keybind.dash.name = Рывок -keybind.chat.name = Чат -keybind.player_list.name = список_игроков -keybind.console.name = консоль -keybind.rotate_alt.name = вращать_alt -keybind.rotate.name = вращать -keybind.weapon_1.name = Оружие_1 -keybind.weapon_2.name = Оружие_2 -keybind.weapon_3.name = Оружие_3 -keybind.weapon_4.name = Оружие_4 -keybind.weapon_5.name = Оружие_5 -keybind.weapon_6.name = Оружие_6 +text.loading=[accent] Загрузка... +text.wave=[orange]Волна {0} +text.wave.waiting=Волна через {0} +text.waiting=Ожидание... +text.enemies={0} Противников +text.enemies.single={0} Противник +text.loadimage=Загрузить изображение +text.saveimage=Сохранить изображение +text.editor.badsize=[orange]Недопустимый формат изображения! [] \nДопустимый формат карты: {0} +text.editor.errorimageload=Ошибка загрузки изображения: [orange] {0} +text.editor.errorimagesave=Ошибка сохранения изображения: [orange] {0} +text.editor.generate=Создать +text.editor.resize=Изменить \nразмер +text.editor.loadmap=Загрузить\nкарту +text.editor.savemap=Сохранить\nкарту +text.editor.loadimage=Загрузить \nизображение +text.editor.saveimage=Сохранить \nизображение +text.editor.unsaved=[scarlet]У вас есть не сохраненные изменения![] \nВы уверены,что хотите выйти? +text.editor.resizemap=Изменить размер карты +text.editor.mapname=Название карты: +text.editor.overwrite=[accent]Внимание! \nЭто перезапишет уже существующую карту. +text.editor.selectmap=Выберите карту для загрузки: +text.width=Ширина: +text.height=Высота: +text.menu=Меню +text.play=Играть +text.load=Загрузить +text.save=Сохранить +text.language.restart=Перезагрузите игру, чтобы настройки языка вступили в силу. +text.settings.language=Язык +text.settings=Настройки +text.tutorial=Обучение +text.editor=Редактор +text.mapeditor=Редактор карт +text.donate=Донат +text.settings.reset=Сбросить по умолчанию +text.settings.controls=Управление +text.settings.game=Игра +text.settings.sound=Звук +text.settings.graphics=Графика +text.upgrades=Улучшения +text.purchased=[LIME]Создан! +text.weapons=Оружие +text.paused=Пауза +text.info.title=[accent]Информация +text.error.title=[crimson]Произошла ошибка +text.error.crashtitle=Произошла ошибка +text.blocks.blockinfo=Информация о блоке +text.blocks.powercapacity=Вместимость энергии +text.blocks.powershot=Энергия / выстрел +text.blocks.size=Размер +text.blocks.liquidcapacity=Вместимость жидкости +text.blocks.maxitemssecond=Макс. Количество предметов / в секунду +text.blocks.powerrange=Диапазон мощности энергии +text.blocks.itemcapacity=Вместимость предметов +text.blocks.inputliquid=Прием жидкости +text.blocks.inputitem=Прием предмета +text.blocks.explosive=Взрывоопасно! +text.blocks.health=Здоровье +text.blocks.inaccuracy=Разброс +text.blocks.shots=Выстрелы +text.blocks.inputcapacity=Вместимость ввода +text.blocks.outputcapacity=Вместимость вывода +setting.difficulty.easy=легко +setting.difficulty.normal=нормально +setting.difficulty.hard=тяжело +setting.difficulty.insane=нереально +setting.difficulty.purge=зачистка +setting.difficulty.name=Сложность +setting.screenshake.name=Дрожание экрана +setting.indicators.name=Индикаторы противников +setting.effects.name=Эффекты на экране +setting.sensitivity.name=Чувствительность контроллера +setting.saveinterval.name=Интервал автосохранения +setting.seconds={0} Секунд +setting.fullscreen.name=Полноэкранный +setting.multithread.name=Многопоточность +setting.fps.name=Показать FPS +setting.vsync.name=Верт. синхронизация +setting.lasers.name=Показывать энергетические лазеры +setting.healthbars.name=Показать полоски здоровья объекта +setting.musicvol.name=Громкость музыки +setting.mutemusic.name=Заглушить музыку +setting.sfxvol.name=Громкость звуковых эффектов +setting.mutesound.name=Заглушить звук +map.maze.name=лабиринт +map.fortress.name=крепость +map.sinkhole.name=раковина +map.caves.name=пещеры +map.volcano.name=вулкан +map.caldera.name=кальдера +map.scorch.name=горение +map.desert.name=пустыня +map.island.name=остров +map.grassland.name=луг +map.tundra.name=тундра +map.spiral.name=спираль +map.tutorial.name=обучение +text.keybind.title=Переназначить клавиши +keybind.move_x.name=движение_x +keybind.move_y.name=движение_y +keybind.select.name=выбрать +keybind.break.name=Разрушить +keybind.shoot.name=стрельба +keybind.zoom_hold.name=удержание_зума +keybind.zoom.name=Приблизить +keybind.block_info.name=инфо_о_блоке +keybind.menu.name=Меню +keybind.pause.name=Пауза +keybind.dash.name=Рывок +keybind.chat.name=Чат +keybind.player_list.name=список_игроков +keybind.console.name=консоль +keybind.rotate_alt.name=вращать_alt +keybind.rotate.name=вращать mode.text.help.title=Описание режимов -mode.waves.name = волны -mode.waves.description = в нормальном режиме. ограниченные ресурсы и автоматические наступающие волны. -mode.sandbox.name = песочница -mode.sandbox.description = бесконечные ресурсы и нет таймера для волн. -mode.freebuild.name = свободная\nстройка +mode.waves.name=волны +mode.waves.description=в нормальном режиме. ограниченные ресурсы и автоматические наступающие волны. +mode.sandbox.name=песочница +mode.sandbox.description=бесконечные ресурсы и нет таймера для волн. +mode.freebuild.name=свободная\nстройка mode.freebuild.description=ограниченные ресурсы и нет таймера для волн. -upgrade.standard.name = стандарт -upgrade.standard.description = Стандартный мех. -upgrade.blaster.name = Бластер -upgrade.blaster.description = Стреляет медленной, слабой пулей. -upgrade.triblaster.name = трибластер -upgrade.triblaster.description = Стреляет 3 пулями в разброс. -upgrade.clustergun.name = Кластерная пушка -upgrade.clustergun.description = Стреляет неточным распространением взрывных гранат. -upgrade.beam.name = Лучевая пушка -upgrade.beam.description = Стреляет пробивающим лазерным луч высокой дальности. -upgrade.vulcan.name = вулкан -upgrade.vulcan.description = Стреляет шквалом быстрых пуль. -upgrade.shockgun.name = шоковая пушка -upgrade.shockgun.description = Стреляет взрывным зарядом заряженной шрапнели. -item.stone.name = камень -item.iron.name = железо -item.coal.name = Уголь -item.steel.name = сталь -item.titanium.name = титан -item.dirium.name = дириум -item.uranium.name = уран -item.sand.name = песок -liquid.water.name = Вода -liquid.plasma.name = Плазма -liquid.lava.name = лава -liquid.oil.name = Нефть -block.weaponfactory.name = оружейный завод -block.weaponfactory.fulldescription=Используется для создания оружия для игрока. Нажмите для использования. Автоматически извлекает ресурсы из ядра. -block.air.name = воздух -block.blockpart.name = часть блока -block.deepwater.name = глубоководье -block.water.name = вода -block.lava.name = лава -block.oil.name = нефть -block.stone.name = Камень -block.blackstone.name = черный камень -block.iron.name = железо -block.coal.name = уголь -block.titanium.name = титан -block.uranium.name = уран -block.dirt.name = земля -block.sand.name = песок -block.ice.name = лед -block.snow.name = снег -block.grass.name = трава -block.sandblock.name = блок песка -block.snowblock.name = блок снега -block.stoneblock.name = блок камня -block.blackstoneblock.name = блок черного камня -block.grassblock.name = блок травы -block.mossblock.name = блок мха -block.shrub.name = кустарник -block.rock.name = валун -block.icerock.name = замерзший валун -block.blackrock.name = черный валун -block.dirtblock.name = блок земли -block.stonewall.name = каменная стена -block.stonewall.fulldescription = Дешевый оборонительный блок. Полезен для защиты ядра и турелей в первых волнах. -block.ironwall.name = железная стена -block.ironwall.fulldescription = Основной защитный блок. Обеспечивает защиту от противников. -block.steelwall.name = стальная стена -block.steelwall.fulldescription = Стандартный защитный блок. адекватная защита от противников. -block.titaniumwall.name = титановая стена -block.titaniumwall.fulldescription = Сильный защитный блок. Обеспечивает защиту от противников. -block.duriumwall.name = стена из дириума -block.duriumwall.fulldescription = Очень прочный защитный блок. Обеспечивает защиту от противников. -block.compositewall.name = композитная стена -block.steelwall-large.name = большая стальная стена -block.steelwall-large.fulldescription = Стандартный защитный блок. Охватывает несколько клеток. -block.titaniumwall-large.name = большая титановая стена -block.titaniumwall-large.fulldescription = Сильный защитный блок. Охватывает несколько клеток. -block.duriumwall-large.name = большая стена из дириума -block.duriumwall-large.fulldescription = Очень сильный защитный блок. Охватывает несколько клеток. -block.titaniumshieldwall.name = экранированная стена -block.titaniumshieldwall.fulldescription = Прочный защитный блок с дополнительным встроенным щитом. Требует энергию. Использует энергию для поглощения вражеских пуль. Для обеспечения энергией этого блока рекомендуется использовать усилители энергии. -block.repairturret.name = ремонтная турель -block.repairturret.fulldescription = Ремонтирует близлежащие поврежденные блоки в радиусе действия. Использует небольшое количество энергии. -block.megarepairturret.name = ремонтная турель II -block.megarepairturret.fulldescription = Ремонтирует близлежащие поврежденные блоки в радиусе действия с приличной скоростью. Использует энергию. -block.shieldgenerator.name = Генератор щита -block.shieldgenerator.fulldescription = Передовой защитный блок. Защищает все блоки в радиусе от атаки. Использует мало энергии когда бездействует, но быстро разряжается при контакте с пулями. -block.door.name = дверь -block.door.fulldescription = Блок, который можно открыть и закрыть, нажав на него. -block.door-large.name = большая дверь -block.door-large.fulldescription = Блок, который можно открыть и закрыть, нажав на него. -block.conduit.name = трубопровод -block.conduit.fulldescription = Основной блок транспортировки жидкости. Работает как конвейер, но с жидкостями. Лучше всего использовать насосы или другие трубопроводы. Может использоваться как мост через жидкости для противников и игроков. -block.pulseconduit.name = импульсный трубопровод -block.pulseconduit.fulldescription = Передовой блок транспортировки жидкости. Перемещает жидкости быстрее и хранит больше, чем стандартные трубопроводы. -block.liquidrouter.name = Маршрутизатор житкостей -block.liquidrouter.fulldescription = Работает аналогично маршрутизатору. Принимает жидкость с одной стороны и выводит ее на другие стороны. Полезно для разделения жидкости из одного трубопровода на несколько других трубопроводов. -block.conveyor.name = конвейер -block.conveyor.fulldescription = Основной транспортный блок. Перемещает предметы вперед и автоматически перекладывает их в турели или в крафтеры. Могут вращаться . Может использоваться как мост через жидкости для противников и игроков. -block.steelconveyor.name = стальной конвейер -block.steelconveyor.fulldescription = Передовой транспортный блок предметов. Перемещает предметы быстрее, чем стандартные конвейеры. -block.poweredconveyor.name = импульсный конвейер -block.poweredconveyor.fulldescription = Лучший транспортный блок. Перемещает предметы быстрее, чем стальные конвейеры. -block.router.name = Маршрутизатор -block.router.fulldescription = Принимает предметы с одного направления и выводит их в 3 других направлениях. Может также хранить определенное количество предметов. Используется для разделения материалов с одного бура на несколько турелей -block.junction.name = Перекресток -block.junction.fulldescription = Действует как мост для двух конвейерных лент. Полезно в ситуациях с двумя различными конвейерами, несущими разные материалы в разные места. -block.conveyortunnel.name = конвейерный туннель -block.conveyortunnel.fulldescription = Перемещает предмет под блоками. Чтобы использовать, поместите один туннель, ведущий в блок, который должен быть туннелирован, и один на другой стороне. Убедитесь, что оба туннеля обращены к противоположным направлениям, которые относятся к блокам, которые они принимают или выводят. -block.liquidjunction.name = Перекресток для жидкостей -block.liquidjunction.fulldescription = Действует как мост для двух пересекающихся трубопроводов. Полезно в ситуациях с двумя различными каналами, перемещающими различные жидкости в разные места. -block.liquiditemjunction.name = Распределитель жидкостей и предметов -block.liquiditemjunction.fulldescription = Действует как мост для пересекающихся трубопроводов и конвейеров. -block.powerbooster.name = усилитель энергии -block.powerbooster.fulldescription = Распределяет электричество всем блокам в пределах своего радиуса. -block.powerlaser.name = Энергетический лазер -block.powerlaser.fulldescription = Создает лазер, который передает питание блоку перед ним. Не генерирует никакой энергии. Лучше всего использовать с генераторами или другими лазерами. -block.powerlaserrouter.name = лазерный маршрутизатор -block.powerlaserrouter.fulldescription = Лазер, который одновременно передает электричество в три направления. Полезно в тех ситуациях, когда требуется питание нескольким блокам от одного генератора. -block.powerlasercorner.name = лазерный угол -block.powerlasercorner.fulldescription = Лазер, распределяющий энергию сразу на два направления. Полезно в тех ситуациях, когда требуется питание нескольким блокам от одного генератора, а маршрутизатор не годится. -block.teleporter.name = телепорт -block.teleporter.fulldescription = Улучшенный транспортный блок предметов. Телепортеры передают предметы в другие телепорты одного цвета. Ничего не происходит, если нет телепортеров одного цвета. Если несколько телепортеров имеют один и тот же цвет, выбирается случайный. Использует энергия. Нажмите, чтобы изменить цвет. -block.sorter.name = сортировщик -block.sorter.fulldescription = Сортирует предмет по типу материала. Материал для приема указывается цветом в блоке. Все предметы, соответствующие материалу сортировки, выводятся вперед, все остальное выводится влево и вправо. -block.core.name = Ядро -block.pump.name = Насос -block.pump.fulldescription = Качают жидкости из блока источнка - обычно вода, лава или нефть. Выводит жидкость в соседние трубопроводы. -block.fluxpump.name = Флюсовый насос -block.fluxpump.fulldescription = Передовая версия насоса. Хранит больше жидкости и качает быстрее. -block.smelter.name = Плавильный завод -block.smelter.fulldescription = Основной блок крафтер. При вводе 1 х железа и 1 х угля выдается одна сталь. -block.crucible.name = Тигель -block.crucible.fulldescription = Продвинутый блок крафтер. При вводе 1х титана и 1х стали выдается один дириум. -block.coalpurifier.name = Экстрактор угля -block.coalpurifier.fulldescription = Стандартный экстрактор. Выдает уголь, когда подается большое количество воды и камня. -block.titaniumpurifier.name = Экстрактор титана -block.titaniumpurifier.fulldescription = Стандартный экстрактор. Выдает титан при подаче большого количества воды и железа. -block.oilrefinery.name = Нефтеперерабатывающий Завод -block.oilrefinery.fulldescription = Перерабатывает большое количество нефти в уголь. Полезно для заправки турелей использующих уголь, когда на карте дефицит угля. -block.stoneformer.name = Формировщик камня -block.stoneformer.fulldescription = Охлаждает жидкую лаву, делая из него камень. Полезен для производства большого количества камней для ​угольного очистителя -block.lavasmelter.name = лавовый плавильный завод -block.lavasmelter.fulldescription = Использует лаву для переработки железа в сталь. Альтернатива плавильным заводам. Полезно в ситуациях, когда угля мало. -block.stonedrill.name = каменный бур -block.stonedrill.fulldescription = Важный бур. Когда он помещается на каменную клетку, медленно, бесконечно добывает камень. -block.irondrill.name = Железный бур -block.irondrill.fulldescription = Основной бур. При размещении на клетке с железной рудой, выдает железо медленным темпом на неопределенный срок. -block.coaldrill.name = угольный бур -block.coaldrill.fulldescription = Основной бур. При размещении на клетке с угольной рудой происходит медленный темп добычи угля на неопределенный срок. -block.uraniumdrill.name = урановый бур -block.uraniumdrill.fulldescription = Передовой бур. При размещении на клетке с урановой рудой выдает уран медленным темпом на неопределенный срок. -block.titaniumdrill.name = титановый бур -block.titaniumdrill.fulldescription = Продвинутый бур. При размещении на клетках с титановой рудой выводится титан медленным темпом на неопределенный срок. -block.omnidrill.name = Адаптивный бур -block.omnidrill.fulldescription = Идеальный бур. Будет добывать любую руду на которой стоит с безумным темпом\n -block.coalgenerator.name = угольный генератор -block.coalgenerator.fulldescription = Важный генератор. Генерирует энергию из угля. Выводит энергию в качестве лазеров на 4 стороны. -block.thermalgenerator.name = термальный генератор -block.thermalgenerator.fulldescription = Генерирует энергию из лавы. Выводит электричество в качестве лазеров на 4 стороны. -block.combustiongenerator.name = генератор внутреннего сгорания -block.combustiongenerator.fulldescription = Генерирует энергию из нефти. Выводит энергию в качестве лазеров на 4 стороны. -block.rtgenerator.name = Генератор RTG -block.rtgenerator.fulldescription = Генерирует небольшое количество энергии из распада радиоактивного урана. Выводит энергию в качестве лазеров на 4 стороны. -block.nuclearreactor.name = ядерный реактор -block.nuclearreactor.fulldescription = Передовая версия генератора RTG и идеальный источник энергии. Генерирует энергию из урана. Требуется постоянное водяное охлаждение.Крайне взрывоопасен; сильно взорвётся при подаче недостаточного количества хладагента. -block.turret.name = Турель -block.turret.fulldescription = Базовая, дешевая турель. Использует камень для боеприпасов. Имеет немного больше диапазон, чем двойная турель. -block.doubleturret.name = двойная турель -block.doubleturret.fulldescription = Немного более мощная версия турели. Использует камень для боеприпасов. Значительно больший урон, но имеет более низкий диапазон. Выстреливает двумя пулями. -block.machineturret.name = Турель Гатлинга -block.machineturret.fulldescription = Стандартная универсальная турель. Использует железо для боеприпасов. Обладает быстрой скоростью выстрелов с приличным уроном. -block.shotgunturret.name = разветвленная турель\n -block.shotgunturret.fulldescription = Стандартная турель. Использует железо для боеприпасов. Стреляет в разброс 7 пулями. Маленький диапазон, но более высокий уровень урона по сравнению с турелью Гатлинга. -block.flameturret.name = Огнемётная турель\n -block.flameturret.fulldescription = Продвинутая турель для защиты на близком расстоянии. Использует уголь для боеприпасов. Имеет очень маленький диапазон, но очень высокий урон. Хорошо подходит на близких расстояниях. Рекомендуется использовать со стенами. -block.sniperturret.name = Турель-рельсотрон -block.sniperturret.fulldescription = Продвинутая дальнобойная турель. Использует сталь для боеприпасов. Очень высокий урон, но низкая скорость стрельбы. Дорога в использовании, но может быть помещена далеко от вражеских линий из-за её дальности. -block.mortarturret.name = Зенитная турель -block.mortarturret.fulldescription = Продвинутая турель с уроном по зоне. Использует уголь для боеприпасов. Очень низкая скорость стрельбы и пуль, но очень высокий урон по одной цели и зоне. Полезен для больших толп врагов. -block.laserturret.name = лазерная турель -block.laserturret.fulldescription = Продвинутая турель. Использует энергию. Хорошая , универсальная турель средней дальности. Атакует только одну цель. Никогда не промахивается. -block.waveturret.name = Тесла-турель -block.waveturret.fulldescription = Продвинутая многоцелевая турель. Использует энергию. Средняя дальность. Никогда не промахивается. В среднем, может нанести небольшой урон, но он может поразить нескольких противников одновременно с помощью цепной молнии. -block.plasmaturret.name = плазменная турель -block.plasmaturret.fulldescription = Высокотехнологичная версия огнеметной турели. Использует уголь в качестве боеприпасов. Очень высокий урон, дальность между маленькой и средней. -block.chainturret.name = Пулемётная турель -block.chainturret.fulldescription = Самая лучшая, скорострельная турель. Использует уран для боеприпасов. Стреляет большими снарядами с высокой скорострельностью. Средняя дальность. Охватывает несколько клеток. Чрезвычайно прочная. -block.titancannon.name = Пушка-титан -block.titancannon.fulldescription = Самая лучшая, дальнобойная турель. Использует уран как боеприпасы. Стреляет большими снарядами с уроном по зоне со средней скоростью стрельбы. Большая дальность. Охватывает несколько клеток. Чрезвычайно прочная. -block.playerspawn.name = Точка появления игрока -block.enemyspawn.name = Точка появления врага +item.stone.name=камень +item.coal.name=Уголь +item.titanium.name=титан +item.thorium.name=уран +item.sand.name=песок +liquid.water.name=Вода +liquid.lava.name=лава +liquid.oil.name=Нефть +block.door.name=дверь +block.door-large.name=большая дверь +block.conduit.name=трубопровод +block.pulseconduit.name=импульсный трубопровод +block.liquidrouter.name=Маршрутизатор житкостей +block.conveyor.name=конвейер +block.router.name=Маршрутизатор +block.junction.name=Перекресток +block.liquidjunction.name=Перекресток для жидкостей +block.sorter.name=сортировщик +block.smelter.name=Плавильный завод +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_tk.properties b/core/assets/bundles/bundle_tk.properties index 814f36c6e0..b8006f0edb 100644 --- a/core/assets/bundles/bundle_tk.properties +++ b/core/assets/bundles/bundle_tk.properties @@ -1,551 +1,495 @@ -text.about = [ROYAL] Anuken tarafından oluşturuldu [] - [SKY] anukendev@gmail.com [] Aslen [turuncu] GDL [] Metal Monstrosity Jam. Kredi: - [SARI] ile yapılan SFX bfxr [] - [YEŞİL] RoccoW tarafından yapılan müzik [] / [kireç] bulunan FreeMusicArchive.org [] Özel teşekkürler: - [mercan] MitchellFJN []: Kapsamlı oyun testi ve geri bildirim - [sky] Luxray5474 []: wiki çalışması, kod katkıları - [kireç] Epowerj []: kod sistemi yapılandırması, icon - itch.io ve Google Play'deki tüm beta test kullanıcıları\n -text.credits = Yapımcılar -text.discord = Mindustry Discord'una katılın! -text.changes = [SCARLET] Dikkat! [] Bazı önemli oyun mekanikleri değişti. - [Aksanın] teletaşıyıcı [] şimdi artık gücü kullanıyor. - [accent] dökümcü [] ve [accent] pota [] artık bir maksimum ürün kapasitesine sahip. - [Aksan] pota [] artık yakıt olarak kömür gerektiriyor. -text.link.discord.description = Resmi Mindustry Discord iletişim kanalı -text.link.github.description = Oyunun kaynak kodu -text.link.dev-builds.description = Geliştirme altında olan sürüm -text.link.trello.description = Planlanan özellikler için resmi Trello Bülteni -text.link.itch.io.description = PC yüklemeleri ve web sürümü ile itch.io sayfası -text.link.google-play.description = Google Play mağaza sayfası -text.link.wiki.description = Resmi Mindustry Wikipedi'si -text.linkfail = Bağlantı açılamadı! URL, yazı tahtanıza kopyalandı. -text.editor.web = Web sürümü editörü desteklemiyor! Editörü kullanmak için oyunu indirin. -text.multiplayer.web = Oyunun bu sürümü çok oyunculuyu desteklemiyor! Tarayıcınızdan çok oyunculu oynamak için, itch.io sayfasındaki \"çok oyunculu web sürümü\" bağlantısını kullanın. -text.gameover = Çekirdek yok edildi. -text.highscore = [SARI] Yeni yüksek puan! -text.lasted = Dalgaya kadar sürdün -text.level.highscore = Yüksek Puan: [accent] {0} -text.level.delete.title = Silmeyi onaylayın -text.level.delete = \"[Orange] {0} \" Haritayı silmek istediğinizden emin misiniz? -text.level.select = Seviye Seç -text.level.mode = Oyun Modu -text.savegame = Oyunu Kaydet -text.loadgame = Oyunu yükle -text.joingame = Oyuna katıl -text.newgame = Yeni Oyun -text.quit = Çık -text.about.button = Hakkında -text.name = Adı: -text.public = Herkese açık -text.players = 1090 oyuncu çevrimiçi -text.server.player.host = Sunucu -text.players.single = {0} Oyuncu Çevrimiçi -text.server.mismatch = Paket hatası: olası istemci / sunucu sürümü uyuşmazlığı. Siz ve ev sahibi Mindustry'nin en son sürümüne sahip olduğunuzdan emin olun! -text.server.closing = [accent] Sunucu kapatılıyor ... -text.server.kicked.kick = Sunucudan kovuldun! -text.server.kicked.invalidPassword = Geçersiz şifre! -text.server.kicked.clientOutdated = Oyun sürümünüz geçerli değil. Oyununu güncelleyin! -text.server.kicked.serverOutdated = Eski sunucu! Ev sahibinden güncellemesini isteyin! -text.server.kicked.banned = Bu sunucudan yasaklandınız. -text.server.kicked.recentKick = Son zamanlarda tekmelendin. Tekrar bağlanmadan önce bekleyin. -text.server.connected = {0} katıldı. -text.server.disconnected = {0} bağlantısı kesildi. -text.nohost = Özel bir haritada sunucuyu barındıramıyor! -text.host.info = [Vurgu] ana bilgisayarı [] düğmesi, [657] [65] [65] ve [65] [6568] bağlantı noktalarında bir sunucuyu barındırır. [] Aynı [LIGHT_GRAY] wifi veya yerel ağ [] üzerindeki herkes sunucunuzu sunucularında görebilir. liste. Kişilerin IP tarafından herhangi bir yerden bağlanabilmesini istiyorsanız [vurgu] bağlantı noktası iletme [] gereklidir. [LIGHT_GRAY] Not: Birisi LAN oyununuza bağlanırken sorun yaşıyorsa, güvenlik duvarı ayarlarınızda Mindustry'e yerel ağınıza erişebildiğinizden emin olun. -text.join.info = Burada, bağlanmak için yerel ağ [] sunucularına bağlanmak ya da [aksan] sunucularını bulmak için bir [vurgu] sunucunun IP [] girebilirsiniz. Hem LAN hem de WAN çok oyunculu desteklenir. [LIGHT_GRAY] Not: Otomatik bir global sunucu listesi yoktur; Birisine IP ile bağlanmak isterseniz, ana bilgisayardan kendi IP adreslerini sormanız gerekir. -text.hostserver = Oyunu Sun -text.host = evsahibi -text.hosting = [accent] Sunucu açılıyor ... -text.hosts.refresh = Yenile -text.hosts.discovering = LAN oyunlarını keşfetme -text.server.refreshing = Canlandırıcı sunucu -text.hosts.none = [lightgray] Hayır LAN oyunları bulundu! -text.host.invalid = [scarlet] Ana bilgisayara bağlanılamıyor. -text.server.friendlyfire = Dost ateşi -text.trace = Oyuncuyu Takip Et -text.trace.playername = Oyuncu adı: [accent] {0} -text.trace.ip = IP: [vurgu] {0} -text.trace.id = Benzersiz kimlik: [accent] {0} -text.trace.android = Android : [accent] {0} -text.trace.modclient = Özel Alıcı: [accent] {0} -text.trace.totalblocksbroken = Toplam kırık blok: [accent] {0} -text.trace.structureblocksbroken = Kırılan yapı blokları: [accent] {0} -text.trace.lastblockbroken = Kırılan son blok: [accent] {0} -text.trace.totalblocksplaced = Toplam blok yerleştirildi: [accent] {0} -text.trace.lastblockplaced = Konulan son blok: [accent] {0} -text.invalidid = Geçersiz alıcı kimliği! Bir hata raporu gönderin. -text.server.bans = yasaklar -text.server.bans.none = Yasaklanmış oyuncu bulunamadı! -text.server.admins = Yöneticiler -text.server.admins.none = Yönetici bulunamadı! -text.server.add = Sunucu ekle -text.server.delete = Bu sunucuyu silmek istediğinizden emin misiniz? -text.server.hostname = Sun -text.server.edit = Sunucuyu Düzenle -text.server.outdated = [crimson] Eski Sunucu! -text.server.outdated.client = [crimson] Eski Alıcı! -text.server.version = [lightgray] Sürüm: {0} -text.server.custombuild = [sarı] Özel Yapım -text.confirmban = Bu oyuncuyu yasaklamak istediğinizden emin misiniz? -text.confirmunban = Bu oyuncunun yasağını kaldırmak istediğinden emin misin? -text.confirmadmin = Bu oyuncunun yönetici yapmak istediğinden emin misin? -text.confirmunadmin = Bu oyuncudan yönetici durumunu kaldırmak istediğinizden emin misiniz? -text.joingame.byip = IP ile Katılın ... -text.joingame.title = Oyuna katılmak -text.joingame.ip = IP: -text.disconnect = Bağlantı Kesildi -text.disconnect.data = Dünya verileri yüklenemedi! -text.connecting = [Vurgu] bağlanıyor ... -text.connecting.data = [accent] Dünya verileri yükleniyor ... -text.connectfail = [crimson] Sunucuya bağlanılamadı: [orange] {0} -text.server.port = Liman -text.server.addressinuse = Adres çoktan kullanımda! -text.server.invalidport = Bağlantı noktası numarası geçersiz. -text.server.error = [crimson] Sunucu barındırma hatası: [orange] {0} -text.tutorial.back = <Önceki -text.tutorial.next = İleri > -text.save.new = 6349,Yeni Kayıt -text.save.overwrite = Bu kayıt yuvasının üzerine yazmak istediğinizden emin misiniz? -text.overwrite = Üzerine Yaz -text.save.none = Hiçbir kayıt bulunamadı! -text.saveload = [Vurgu] Kaydediliyor ... -text.savefail = Oyun kaydedilemedi! -text.save.delete.confirm = Bu kaydı silmek istediğinizden emin misiniz? -text.save.delete = Sil -text.save.export = Dışa Aktar -text.save.import.invalid = [turuncu] Bu kayıt geçersiz! -text.save.import.fail = [crimson] Kayıt oyuna aktarılamadı : [orange] {0} -text.save.export.fail = [crimson] Kayıt dışa aktarılamadı: [orange] {0} -text.save.import = İçe Aktar -text.save.newslot = İsmi kaydet: -text.save.rename = Yeniden Adlandır -text.save.rename.text = Yeni İsim: -text.selectslot = Bir kayıt seçin. -text.slot = [accent] Yuva {0} -text.save.corrupted = [orange] Kayıt dosyası bozuk veya geçersiz! -text.empty = -text.on = Açık -text.off = Kapalı -text.save.autosave = Otomatik kaydetme: {0} -text.save.map = harita -text.save.wave = Dalga -text.save.difficulty = zorluk -text.save.date = Son Kaydedilen: {0} -text.confirm = Onayla -text.delete = Sil -text.ok = Tamam -text.open = Açık -text.cancel = İptal -text.openlink = Linki aç -text.copylink = Bağlantıyı kopyala -text.back = Geri -text.quit.confirm = Çıkmak istediğinden emin misin? -text.changelog.title = Değişiklik listesi -text.changelog.loading = Değişiklik listesi yükleniyor -text.changelog.error.android = [turuncu] Android'da olan hata nedeniyle değişiklik listesi görüntülenemiyor. -text.changelog.error = [scarlet] Değişiklik listesi alma hatası! İnternet bağlantınızı kontrol edin. -text.changelog.current = [sarı] [[Güncel versiyon] -text.changelog.latest = [turuncu] [[Son sürüm] -text.loading = [Vurgu] Yükleniyor ... -text.wave = [turuncu] Dalga {0} -text.wave.waiting = {0} içinde dalga -text.waiting = Bekleniyor -text.enemies = {0} Düşmanlar -text.enemies.single = {0} Düşman -text.loadimage = Resmi yükle -text.saveimage = Resmi Kaydet -text.oregen = Maden Üretimi -text.editor.badsize = [orange] Resim boyutları geçersiz! [] Geçerli harita boyutları: {0} -text.editor.errorimageload = Resim dosyası yüklenirken hata oluştu: [orange] {0} -text.editor.errorimagesave = Resim dosyası kaydedilirken hata oluştu: [orange] {0} -text.editor.generate = Üretmek -text.editor.resize = Yeniden Boyutlandırma -text.editor.loadmap = Harita Yükle -text.editor.savemap = Harita Kaydet -text.editor.loadimage = Resmi yükle -text.editor.saveimage = Resmi Kaydet -text.editor.unsaved = [scarlet] Kaydedilmemiş değişiklikleriniz var! [] Çıkmak istediğinizden emin misiniz? -text.editor.brushsize = Fırça boyutu: {0} -text.editor.noplayerspawn = Bu haritanın oyuncu spawnpoint'i yok! -text.editor.manyplayerspawns = Haritalar, birden fazla oyuncu spawnpoint'e sahip olamaz! -text.editor.manyenemyspawns = {0} düşman spawnpoint {0}'den daha fazlası olamaz! -text.editor.resizemap = Haritayı Yeniden Boyutlandır -text.editor.resizebig = [Kızıl] Uyarı! [] 256'dan büyük haritalar yavaş ve dengesiz olabilir. -text.editor.mapname = Harita Adı -text.editor.overwrite = [Vurgu] Uyarı! Bu mevcut bir haritanın üzerine yazar. -text.editor.failoverwrite = [crimson] Varsayılan haritanın üzerine yazılamıyor! -text.editor.selectmap = Yüklenecek bir harita seçin: -text.width = Genişliği: -text.height = Boy: -text.randomize = Rasgele seçmek -text.apply = Uygula -text.update = Güncelle -text.menu = Menü -text.play = Oyna -text.load = Yükle -text.save = Kaydet -text.language.restart = Lütfen dil ayarlarının etkili olması için oyununuzu yeniden başlatın. -text.settings.language = Dil -text.settings = Ayarlar -text.tutorial = Eğitim -text.editor = Editör -text.mapeditor = Harita Editörü -text.donate = Bağışlamak -text.settings.reset = Varsayılanlara Dön -text.settings.controls = kontroller -text.settings.game = Oyun -text.settings.sound = Ses -text.settings.graphics = Grafik -text.upgrades = Geliştirmeler -text.purchased = [KİREÇ] Yap၊ld၊ -text.weapons = Silahlar -text.paused = Duraklatıldı -text.respawn = Saniye içinde yeniden doğacaksınız. -text.info.title = [Vurgu] Bilgi -text.error.title = [crimson] Bir hata oluştu -text.error.crashmessage = [SCARLET] Bir kilitlenme meydana getiren beklenmeyen bir hata oluştu. [] Lütfen geliştiriciye bu hatanın gerçekleştiği koşulları bildirin: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Bir hata oluştu -text.mode.break = Ara verme modu: {0} -text.mode.place = Döşeme modu: {0} -placemode.hold.name = hat -placemode.areadelete.name = alan -placemode.touchdelete.name = dokun -placemode.holddelete.name = tut -placemode.none.name = Yok -placemode.touch.name = dokun -placemode.cursor.name = İmleç -text.blocks.extrainfo = [accent] fazladan blok bilgisi: -text.blocks.blockinfo = Blok Bilgisi -text.blocks.powercapacity = Güç kapasitesi -text.blocks.powershot = Güç / atış -text.blocks.powersecond = Güç / saniye -text.blocks.powerdraindamage = Güç tahliye / hasar -text.blocks.shieldradius = Kalkan Yarıçapı -text.blocks.itemspeedsecond = Ürün Hız / saniye -text.blocks.range = Menzil -text.blocks.size = Boyut -text.blocks.powerliquid = Güç / Sıvı -text.blocks.maxliquidsecond = Maksimum sıvı / saniye -text.blocks.liquidcapacity = Sıvı kapasitesi -text.blocks.liquidsecond = Sıvı / saniye -text.blocks.damageshot = Zarar / atış -text.blocks.ammocapacity = Mermi kapasitesi -text.blocks.ammo = Cephane: -text.blocks.ammoitem = Cephane / öğe -text.blocks.maxitemssecond = Maksimum öğe / saniye -text.blocks.powerrange = Güç aralığı -text.blocks.lasertilerange = Lazer karo aralığı -text.blocks.capacity = Kapasite -text.blocks.itemcapacity = Ürün kapasitesi -text.blocks.maxpowergenerationsecond = Maksimum Güç Üretimi / saniye -text.blocks.powergenerationsecond = Güç Üretimi / saniye -text.blocks.generationsecondsitem = Nesil Saniye / öğe -text.blocks.input = giriş -text.blocks.inputliquid = Giriş sıvı -text.blocks.inputitem = Giriş öğesi -text.blocks.output = Çıktı -text.blocks.secondsitem = Saniye / öğe -text.blocks.maxpowertransfersecond = Maksimum güç aktarımı / saniye -text.blocks.explosive = Çok patlayıcı! -text.blocks.repairssecond = Tamir / saniye -text.blocks.health = Can -text.blocks.inaccuracy = yanlışlık -text.blocks.shots = atışlar -text.blocks.shotssecond = Çekim / saniye -text.blocks.fuel = Yakıt -text.blocks.fuelduration = Yakıt Süresi -text.blocks.maxoutputsecond = Maksimum çıkış / saniye -text.blocks.inputcapacity = Giriş kapasitesi -text.blocks.outputcapacity = Çıkış kapasitesi -text.blocks.poweritem = Güç / Ürün -text.placemode = Yer Modu -text.breakmode = Mola modu -text.health = sağlık -setting.difficulty.easy = kolay -setting.difficulty.normal = orta -setting.difficulty.hard = zor -setting.difficulty.insane = deli -setting.difficulty.purge = tasfiye -setting.difficulty.name = Zorluk: -setting.screenshake.name = Ekran Sallamak -setting.smoothcam.name = Pürüzsüz kamera -setting.indicators.name = Düşman Göstergeleri -setting.effects.name = Görüntü Efektleri -setting.sensitivity.name = Denetleyici hassasiyeti -setting.saveinterval.name = Otomatik Kaydetme Aralığı -setting.seconds = saniye -setting.fullscreen.name = Tam ekran -setting.multithread.name = Çok iş parçacığı -setting.fps.name = Saniyede ... Kare göstermek -setting.vsync.name = VSync -setting.lasers.name = Güç Lazerleri Göster -setting.healthbars.name = Varlık Sağlık çubuklarını göster -setting.pixelate.name = Piksel Ekran -setting.musicvol.name = Müzik sesi -setting.mutemusic.name = Müziği Kapat -setting.sfxvol.name = SFX Hacmi -setting.mutesound.name = Sesi kapat -map.maze.name = Labirent -map.fortress.name = Kale -map.sinkhole.name = düden -map.caves.name = mağaralar -map.volcano.name = volkan -map.caldera.name = kaldera -map.scorch.name = alazlamak -map.desert.name = çöl -map.island.name = ada -map.grassland.name = Çayır -map.tundra.name = tundra -map.spiral.name = sarmal -map.tutorial.name = Eğitim -tutorial.intro.text = [sarı] Eğiticiye hoşgeldiniz. [] Başlamak için 'ileri' ye basın. -tutorial.moveDesktop.text = Taşımak için [turuncu] [[WASD] [] tuşlarını kullanın. Destek için [turuncu] shift [] tuşunu basılı tutun. Yakınlaştırmak veya uzaklaştırmak için [turuncu] kaydırma tekerini [] kullanırken [turuncu] CTRL [] tuşunu basılı tutun. -tutorial.shoot.text = Hedeflemek için farenizi kullanın, [turuncu] sol fare tuşunu [] vurun. [Sarı] hedef [] üzerinde çalışmayı deneyin. -tutorial.moveAndroid.text = Görünümü kaydırmak için, bir parmağınızı ekran boyunca sürükleyin. Yakınlaştırmak veya uzaklaştırmak için sıkıştırın ve sürükleyin. -tutorial.placeSelect.text = Sağ alttaki blok menüsünden [sarı] bir konveyör [] seçmeyi deneyin. -tutorial.placeConveyorDesktop.text = [Turuncu] [[scrollwheel] [] tuşunu kullanarak konveyörü [turuncu] ileriye [] getirin ve [turuncu] [[sol fare tuşu] [] düğmesini kullanarak [sarı] işaretli konuma [] yerleştirin. -tutorial.placeConveyorAndroid.text = [Turuncu] [[döndürme düğmesi] [] düğmesini kullanarak konveyörü [turuncu] ileriye [] doğru döndürün, bir parmağınızla konumuna sürükleyin, ardından [turuncu] kullanarak [sarı] işaretli konuma [] yerleştirin [[onay işareti][]. -tutorial.placeConveyorAndroidInfo.text = Alternatif olarak, [turuncu] [[dokunma modu] [] moduna geçmek için sol alt taraftaki artı simgesini ve ekrana dokunarak blokları yerleştirebilirsiniz. Dokunmatik modda, bloklar soldaki ok ile döndürülebilir. Denemek için [sarı] sonraki [] tuşuna basın. -tutorial.placeDrill.text = Şimdi, işaretlenmiş konuma bir [sarı] taş matkap [] seçin ve yerleştirin. -tutorial.blockInfo.text = Bir blok hakkında daha fazla bilgi edinmek isterseniz, açıklamayı okumak için sağ üstteki [turuncu] soru işaretine [] dokunabilirsiniz. -tutorial.deselectDesktop.text = [Turuncu] [[sağ fare tuşu] [] kullanarak bir bloğu kaldırabilirsiniz. -tutorial.deselectAndroid.text = [Turuncu] X [] düğmesine basarak bir bloğun seçimini kaldırabilirsiniz. -tutorial.drillPlaced.text = Matkap şimdi [sarı] taş üretecek, [] konveyör üzerine çıkacak, daha sonra [sarı] çekirdeğe [] hareket ettirilecektir. -tutorial.drillInfo.text = Farklı cevherlerin farklı matkaplara ihtiyacı vardır. Taş taş matkaplar gerektirir, demir demir matkaplar gerektirir, vb. -tutorial.drillPlaced2.text = Öğeleri çekirdeğe taşımak, onları sol üstteki [sarı] öğe envanterinize [] yerleştirir. Yerleştirme blokları, envanterinizdeki öğeleri kullanır. -tutorial.moreDrills.text = Birçok matkap ve konveyörü birbirine bağlayabilirsiniz. -tutorial.deleteBlock.text = Silmek istediğiniz blokta [turuncu] sağ fare düğmesine [] tıklayarak blokları silebilirsiniz. Bu konveyörü silmeyi deneyin. -tutorial.deleteBlockAndroid.text = Alt soldaki [turuncu] kesme modu menüsünde [] artı işaretini [] seçerek ve bir bloka dokunarak blokları [turuncu] ile silebilirsiniz. Bu konveyörü silmeyi deneyin. -tutorial.placeTurret.text = Şimdi, [sarı] işaretli konuma [] [sarı] bir taret [] seçin ve yerleştirin. -tutorial.placedTurretAmmo.text = Bu taret artık konveyör [sarı] mermiyi [] kabul edecektir. Üzerinde gezdirerek ne kadar cephane olduğunu ve yeşil renkli [[yeşil] çubuğunu [] kontrol ederek görebilirsiniz. -tutorial.turretExplanation.text = Taretler, yeterli mermiye sahip oldukları sürece otomatik olarak en yakın düşmana ateş ederler. -tutorial.waves.text = Her [sarı] 60 [] saniyede, [mercan] düşmanlardan oluşan bir dalga [] belirli yerlerde doğacak ve çekirdeği yok etmeye çalışacaktır. -tutorial.coreDestruction.text = Hedefiniz [sarı] çekirdeği [] savunmaktır. Çekirdek yok edilirse, [mercan] oyunu kaybedersiniz []. -tutorial.pausingDesktop.text = Bir ara vermeniz gerekiyorsa, oyunu duraklatmak için sol üstteki [turuncu] duraklat [] düğmesine veya [turuncu] boşluk [] tuşuna basın. Duraklatılırken blokları seçebilir ve yerleştirebilirsiniz, ancak hareket edemez veya ateş edemezsiniz. -tutorial.pausingAndroid.text = Bir ara vermeniz gerekirse, oyunu duraklatmak için sol üstteki [turuncu] duraklatma düğmesine [] basın. Duraklatılırken hala blokları kırıp yerleştirebilirsiniz. -tutorial.purchaseWeapons.text = Alt soldaki yükseltme menüsünü açarak, makineniz için yeni [sarı] silahlar [] satın alabilirsiniz. -tutorial.switchWeapons.text = Silahları, sol alt taraftaki simgesini tıklayarak veya sayıları [turuncu] [[1-9] [] kullanarak değiştirebilirsiniz. -tutorial.spawnWave.text = İşte şimdi bir dalga geliyor. Onları yok et. -tutorial.pumpDesc.text = Daha sonraki dalgalarda, jeneratörler veya aspiratörler için sıvı dağıtmak için [sarı] pompaları [] kullanmanız gerekebilir. -tutorial.pumpPlace.text = Pompalar, matkaplar yerine benzer şekilde çalışırlar; [Sarı] belirlenmiş yağa [] bir pompa yerleştirmeyi deneyin. -tutorial.conduitUse.text = Şimdi pompadan önde giden bir [turuncu] kablo kanalı [] yerleştirin. -tutorial.conduitUse2.text = Ve birkaç tane daha ... -tutorial.conduitUse3.text = Ve birkaç tane daha ... -tutorial.generator.text = Şimdi, kanalın ucunda bir [turuncu] yanma jeneratörü [] bloğu yerleştirin. -tutorial.generatorExplain.text = Bu jeneratör şimdi yağdan [sarı] güç [] oluşturacaktır. -tutorial.lasers.text = Güç [sarı] güç lazerleri [] kullanılarak dağıtılır. Döndür ve buraya bir tane yerleştir. -tutorial.laserExplain.text = Jeneratör şimdi gücü lazer bloğuna taşıyacaktır. Bir [sarı] opak [] ışını, şu anda gücü iletmekte olduğu anlamına gelir ve [sarı] saydam [] ışını, bunun olmadığı anlamına gelir. -tutorial.laserMore.text = Bir bloğun üzerine geldiğinde ne kadar gç olduğunu ve üst taraftaki [sarı] sarı çubuğu [] kontrol ederek kontrol edebilirsiniz. -tutorial.healingTurret.text = Bu lazer bir [kireç] onarım tareti [] için kullanılabilir. Bir tane buraya yerleştirin. -tutorial.healingTurretExplain.text = Gücü olduğu sürece, bu taret yakındaki blokları tamir eder. [] en yakın zamanda bu bloku temin edin! -tutorial.smeltery.text = Pek çok blok, [turuncu] yapılabilmesi için çelik gerektirir ve bu da [turuncu] bir dökümcünün [] yapılmasını gerektirir. Bir tane buraya yerleştirin. -tutorial.smelterySetup.text = Bu dökümcü kömürü yakıt olarak kullanarak, demirden [turuncu] çelik [] üretecek. -tutorial.tunnelExplain.text = Ayrıca, eşyaların bir [turuncu] tünel bloğundan [] geçtiğini ve taş bloktan geçerek diğer tarafta ortaya çıktığını unutmayın. Tünellerin yalnızca 2 bloğa kadar gidebileceğini unutmayın. -tutorial.end.text = Ve bu dersi bitirir! İyi şanslar! -text.keybind.title = Tuşları yeniden ayarla -keybind.move_x.name = sağ / sol -keybind.move_y.name = yukarı / aşağı -keybind.select.name = seçmek -keybind.break.name = kırmak -keybind.shoot.name = ateş etme -keybind.zoom_hold.name = tut ve büyüt -keybind.zoom.name = Yakınlaştır -keybind.block_info.name = blok bilgisi -keybind.menu.name = menü -keybind.pause.name = duraklatma -keybind.dash.name = tire -keybind.chat.name = Sohbet -keybind.player_list.name = oyuncu listesi -keybind.console.name = KONTROL MASASI -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Döndür -keybind.weapon_1.name = weapon_1 -keybind.weapon_2.name = weapon_2 -keybind.weapon_3.name = weapon_3 -keybind.weapon_4.name = weapon_4 -keybind.weapon_5.name = weapon_5 -keybind.weapon_6.name = weapon_6 -mode.text.help.title = Modların açıklaması -mode.waves.name = dalgalar -mode.waves.description = normal mod. sınırlı kaynaklar ve otomatik gelen dalgalar. -mode.sandbox.name = Limitsiz Oynama -mode.sandbox.description = sonsuz kaynaklar ve dalgalar için zamanlayıcı yok. -mode.freebuild.name = Özgür Oynama -mode.freebuild.description = sınırlı kaynaklar ve dalgalar için zamanlayıcı yok. -upgrade.standard.name = standart -upgrade.standard.description = Standart mech. -upgrade.blaster.name = blaster -upgrade.blaster.description = Yavaş, zayıf bir mermi ateş eder. -upgrade.triblaster.name = triblaster -upgrade.triblaster.description = Bir yayında 3 mermi ateş eder. -upgrade.clustergun.name = clustergun -upgrade.clustergun.description = Yayılan bombalar ateş eder. -upgrade.beam.name = lazer -upgrade.beam.description = Uzun menzilli bir delici lazer ışını atar. -upgrade.vulcan.name = Vulkan -upgrade.vulcan.description = Hızlı mermiler ateş eder. -upgrade.shockgun.name = shockgun -upgrade.shockgun.description = Yıkıcı ve patlayıcı mermiler savurarak ateş eder. -item.stone.name = taş -item.iron.name = Demir -item.coal.name = kömür -item.steel.name = çelik -item.titanium.name = titanyum -item.dirium.name = dirium -item.uranium.name = uranyum -item.sand.name = kum -liquid.water.name = su -liquid.plasma.name = plazma -liquid.lava.name = lav -liquid.oil.name = petrol -block.weaponfactory.name = silah fabrikası -block.weaponfactory.fulldescription = Oyuncu mech için silah oluşturmak için kullanılır. Kullanmak için tıklayın. Kaynaklarını otomatik olarak çekirdekten alır. -block.air.name = hava -block.blockpart.name = blokparçası -block.deepwater.name = derin su -block.water.name = su -block.lava.name = lav -block.oil.name = petrol -block.stone.name = taş -block.blackstone.name = siyah taş -block.iron.name = Demir -block.coal.name = kömür -block.titanium.name = titanyum -block.uranium.name = uranyum -block.dirt.name = toprak -block.sand.name = kum -block.ice.name = buz -block.snow.name = kar -block.grass.name = Otlar -block.sandblock.name = kumbloku -block.snowblock.name = karbloku -block.stoneblock.name = taşbloku -block.blackstoneblock.name = blackstoneblock -block.grassblock.name = grassblock -block.mossblock.name = mossblock -block.shrub.name = çalı -block.rock.name = Kaya -block.icerock.name = ICEROCK -block.blackrock.name = Siyah Kaya -block.dirtblock.name = dirtblock -block.stonewall.name = taş duvar -block.stonewall.fulldescription = Ucuz bir savunma bloğu. İlk birkaç dalgada çekirdeği ve tareti korumak için kullanışlıdır. -block.ironwall.name = Demir duvar -block.ironwall.fulldescription = Temel bir savunma bloğu. Düşmanlardan korunma sağlar. Taş duvardan daha korunaklıdır. -block.steelwall.name = Çelik duvar -block.steelwall.fulldescription = Standart bir savunma bloğu. düşmanlardan korunma sağlar -block.titaniumwall.name = titanyum duvar -block.titaniumwall.fulldescription = Güçlü bir savunma bloğu. Düşmanlardan korunma sağlar. -block.duriumwall.name = dirium duvar -block.duriumwall.fulldescription = Çok güçlü bir savunma bloğu. Düşmanlardan korunma sağlar. -block.compositewall.name = kompozit duvar -block.steelwall-large.name = büyük çelik duvar -block.steelwall-large.fulldescription = Standart bir savunma bloğu. Birden fazla fayansa yayılır. -block.titaniumwall-large.name = büyük titanyum duvar -block.titaniumwall-large.fulldescription = Güçlü bir savunma bloğu. Birden fazla fayans yayılır. -block.duriumwall-large.name = büyük dirsek duvarı -block.duriumwall-large.fulldescription = Çok güçlü bir savunma bloğu. Birden fazla fayans yayılır. -block.titaniumshieldwall.name = korumalı duvar -block.titaniumshieldwall.fulldescription = Ekstra yerleşik bir kalkan ile güçlü bir savunma bloğu. Düşman mermilerini emmek için enerji kullanır. Bu bloğa enerji sağlamak için güç arttırıcıların kullanılması tavsiye edilir. -block.repairturret.name = onarım tareti -block.repairturret.fulldescription = Yakındaki hasarlı blokları yavaş bir hızda tamir eder. Küçük menzili vardır. Az miktarlarda güç kullanır. -block.megarepairturret.name = onarım tareti II -block.megarepairturret.fulldescription = Yakındaki hasarlı blokları tamir eder. Uygun menzillidir. Gücü kullanır. -block.shieldgenerator.name = kalkan üreteci -block.shieldgenerator.fulldescription = Gelişmiş bir savunma bloğu. Bir yarıçaptaki tüm blokları saldırıya karşı korur. Boştayken gücü yavaş bir hızda kullanır, ancak mermi temasında enerjiyi hızla boşaltır. -block.door.name = kapı -block.door.fulldescription = Dokunarak açılıp kapatılabilen bir blok. -block.door-large.name = büyük kapı -block.door-large.fulldescription = Dokunarak açılıp kapatılabilen bir blok. -block.conduit.name = sıvı borusu -block.conduit.fulldescription = Temel sıvı taşıma bloğu. Bir konveyör gibi çalışır, ancak sıvılar ile. pompa veya diğer borular ile kullanılır. Düşmanlar ve oyuncular için sıvılar üzerinde bir köprü olarak kullanılabilir. -block.pulseconduit.name = hızlı sıvı borusu -block.pulseconduit.fulldescription = Gelişmiş sıvı taşıma bloku. Sıvıları daha hızlı taşır ve standart sıvı taşıma borularından daha fazla sıvı depolar. -block.liquidrouter.name = sıvı yönlendirici -block.liquidrouter.fulldescription = Bir yönlendiriciye benzer şekilde çalışır. Bir taraftan sıvı girişi kabul eder ve diğer tarafa gönderir. Tek bir borudan diğer birçok boruyla sıvı paylaşmak için kullanışlıdır. -block.conveyor.name = konveyör -block.conveyor.fulldescription = En temel madde taşıma bloğu. Öğeleri konulduğu yöne göre maddeleri ileriye taşır ve bunları otomatik olarak taretlere ya da üretici bloklara getirir. konulmadan önce Döndürülebilirler, ancak konulduktan sonra Döndürülemezler. Düşmanlar ve oyuncular için sıvılar üzerinde bir köprü olarak kullanılabilir. -block.steelconveyor.name = çelik konveyör -block.steelconveyor.fulldescription = Gelişmiş madde taşıma bloğu. Öğeleri standart konveyörlerden daha hızlı taşır. -block.poweredconveyor.name = hızlı konveyör -block.poweredconveyor.fulldescription = Nihai ürün taşıma bloğu. Öğeleri çelik konveyörlerden daha hızlı taşır. -block.router.name = yönlendirici -block.router.fulldescription = Öğeleri bir yönden kabul eder ve 3 farklı yöne gönderir. Malzemelerin belirli bir miktarını da depolayabilir. Malzemelerin bir matkaptan çoklu taretlere ayrılması için uygundur. -block.junction.name = Kavşak noktası -block.junction.fulldescription = İki çapraz şekilde geçmeye çalışan konveyör bandı için köprü görevi görür. Farklı yerlere farklı malzemeler taşıyan konveyör olduğu durumlarda kullanışlıdır. -block.conveyortunnel.name = konveyör tüneli -block.conveyortunnel.fulldescription = Maddeleri blokların altından geçirmek için kullanılır. Kullanmak için, altına tünel yapılacak bloğun bir tarafta giriş tüneli ve diğer tarafa çıkış tüneli yerleştirin. Her iki tünelin de giriş veya çıkış yapan bloklara doğru zıt yönlere baktığından emin olun. -block.liquidjunction.name = sıvı bağlantı -block.liquidjunction.fulldescription = İki çaprazdan geçen boru için köprü görevi görür. Farklı yerlere farklı sıvılar taşıyan kanalların olduğu durumlarda kullanışlıdır. -block.liquiditemjunction.name = sıvı madde kavşağı -block.liquiditemjunction.fulldescription = Kanalları ve konveyörleri yan yana geçirmek için bir köprü görevi görür. -block.powerbooster.name = güç yükseltici -block.powerbooster.fulldescription = Gücü kendi yarıçapı içindeki tüm bloklara dağıtır. -block.powerlaser.name = güç lazeri -block.powerlaser.fulldescription = Önündeki bloğa güç ileten bir lazer oluşturur. Herhangi bir güç üretmez. En iyi jeneratörler veya diğer lazerler ile kullanılır. -block.powerlaserrouter.name = lazer yönlendirici -block.powerlaserrouter.fulldescription = Bir kerede gücü üç yöne dağıtan lazer. Bir jeneratörden birçok bloka güç verilmesi gereken durumlarda kullanışlıdır. -block.powerlasercorner.name = lazer köşesi -block.powerlasercorner.fulldescription = Bir kerede gücü iki yöne dağıtan lazer. Bir jeneratörden birçok bloka güç verilmesi gereken durumlarda ve bir yönlendiricinin kesin olmadığı durumlarda kullanışlıdır. -block.teleporter.name = teletaşıyıcı -block.teleporter.fulldescription = Gelişmiş madde taşıma bloğu. tele-taşıyıcı, öğeleri aynı renkte olan bir teletaşıyıcıya yönlendirir. Aynı renkte teletaşıyıcı yoksa, hiçbir şey yapmaz. Aynı renkten birden çok tele-yazıcı varsa, rastgele biri seçilir. Gücü kullanır. Rengi değiştirmek için dokunun. Not: Sadece madde ileten teletaşıyıcılar gücü kullanır. -block.sorter.name = ayrıştırıcı -block.sorter.fulldescription = Malzemeleri türüne göre ayrıştırır. Kabul edilecek malzeme bloktaki renkle gösterilir. Doğru materyal ile eşleşen tüm öğeler ileriye doğru çıkar, diğer her şey sol ve sağ taraflardan çıkar. -block.core.name = çekirdek -block.pump.name = pompa -block.pump.fulldescription = Kaynak bloğundan su, lav veya yağ gibi sıvıları pompalar. Yakındaki kanallara sıvıyı aktarır. -block.fluxpump.name = fluxpump -block.fluxpump.fulldescription = Pompanın gelişmiş bir versiyonu. Sıvıyı daha hızlı pompalar ve daha fazla sıvı depolar. -block.smelter.name = dökümcü -block.smelter.fulldescription = Temel üretim bloğu. 1 demir ve 1 kömür yakıt olarak verildiğinde, demir çıkarır. Tıkanmayı önlemek için farklı konveyörlerden demir ve kömürün kullanılması tavsiye edilir. -block.crucible.name = pota -block.crucible.fulldescription = Gelişmiş bir üretim bloğu. 1 titanyum, 1 çelik ve 1 kömür yakıt olarak girildiğinde, dirium çıkarır. Tıkanmayı önlemek için farklı konveyörlerden kömür, çelik ve titanyum kullanılması tavsiye edilir. -block.coalpurifier.name = kömür çıkarıcı -block.coalpurifier.fulldescription = Temel bir ekstraktör bloğu. Çok miktarda su ve taş ile birlikte tedarik edildiğinde kömür çıkarır. -block.titaniumpurifier.name = titanyum çıkarıcı -block.titaniumpurifier.fulldescription = Standart bir ekstraktör bloğu. Çok miktarda su ve demir ile birlikte verildiğinde titanyum çıkarır. -block.oilrefinery.name = yağ rafinerisi -block.oilrefinery.fulldescription = Büyük miktarda yağı kömür parçalarına ayırır. Kömür damarları kıt olduğunda kömür bazlı taretlerin yakıtı için kullanışlıdır. -block.stoneformer.name = taş biçimlendiricisi -block.stoneformer.fulldescription = Lavı taş haline getirir. Muazzam miktarda taş üretmek için kullanışlıdır. -block.lavasmelter.name = lav dökümcüsü -block.lavasmelter.fulldescription = Demiri çeliğe dönüştürmek için lav kullanır. Dökümcüler için bir alternatif. Kömürün az olduğu durumlarda kullanışlıdır -block.stonedrill.name = taş matkap -block.stonedrill.fulldescription = Temel bir matkap. Taş karolara yerleştirildiğinde, süresiz olarak yavaş bir hızda taş çıkarırç -block.irondrill.name = demir matkap -block.irondrill.fulldescription = Temel bir matkap. Demir cevheri çinileri üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde demir çıkarıTemel bir matkap. Demir cevheri çinileri üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde demir çıkarır.\n. -block.coaldrill.name = kömür matkap -block.coaldrill.fulldescription = Temel bir matkap. Kömür madeninin üzerine yerleştirildiğinde, süresiz olarak yavaş bir şekilde kömür çıkarır. -block.uraniumdrill.name = uranyum matkap -block.uraniumdrill.fulldescription = Gelişmiş bir matkap. Uranyum cevheri üzerine yerleştirildiğinde, uranyumu süresiz olarak yavaş bir hızda çıkarır. -block.titaniumdrill.name = titanyum matkap -block.titaniumdrill.fulldescription = Gelişmiş bir matkap. Titanyum cevherinin üzerine yerleştirildiğinde, sonsuza yavaş bir tempoda titanyum çıkar. -block.omnidrill.name = omnidrill -block.omnidrill.fulldescription = En büyük matkap. Herhangi bir cevherin uzerine yerlestitldiginde hızlı bir hızda cevher çıkarır -block.coalgenerator.name = kömür jeneratörü -block.coalgenerator.fulldescription = Gerekli jeneratör. Kömürden güç üretir. 4 tarafına lazer olarak güç verir. -block.thermalgenerator.name = termik jeneratör -block.thermalgenerator.fulldescription = Lavdan güç üretir. 4 tarafına lazer olarak güç verir. -block.combustiongenerator.name = yanma jeneratörü -block.combustiongenerator.fulldescription = Yağdan güç üretir. 4 tarafına lazer olarak güç verir. -block.rtgenerator.name = RTG jeneratörü -block.rtgenerator.fulldescription = Uranyumun radyoaktif bozunmasından az miktarda güç üretir. 4 tarafına lazer olarak güç verir. -block.nuclearreactor.name = nükleer reaktör -block.nuclearreactor.fulldescription = RTG Jeneratörünün gelişmiş bir versiyonu ve en iyi güç jeneratörüdür. Uranyumdan güç üretir. Su ile soğutulması gerekir. Son derece tehlikelidir; yetersiz miktarda su ile beslenmediğinde şiddetli patlayabilir. -block.turret.name = taret -block.turret.fulldescription = Basit, ucuz bir kule. Cephane için taş kullanır. Çift taretten biraz daha büyük menzillidir. -block.doubleturret.name = çift ​​taret -block.doubleturret.fulldescription = Taretin biraz daha güçlü bir versiyonu. Cephane için taş kullanır. standart tarete nazaran daha fazla hasar verir, ancak daha düşük bir menzile sahiptir. İki mermi ile ateş eder. -block.machineturret.name = gattling tareti -block.machineturret.fulldescription = Demir atan bir taret. Cephane için demir kullanır. İyi bir hasar ile ateş oranına sahiptir. -block.shotgunturret.name = splitter tareti -block.shotgunturret.fulldescription = Standart bir kule. Cephane için demir kullanır. tek atışta 7 mermi yayılır. Düşük menzillidir, ancak Gatling taretinden daha yüksek hasar verir. -block.flameturret.name = alev tareti -block.flameturret.fulldescription = Gelişmiş yakın menzilli taret. Cephane için kömür kullanır. Çok düşük bir menzile sahiptir, ancak çok yüksek hasar verir. Duvarların arkasında kullanılması tavsiye edilir. -block.sniperturret.name = çelik tareti -block.sniperturret.fulldescription = Gelişmiş uzun menzilli taret. Cephane için çelik kullanır. Yüksek hasar verir, ancak düşük ateş hızı vardır. Kullanımı pahalı, ancak yüksek menzili nedeniyle düşmanı uzak mesafelerden vurabilir. -block.mortarturret.name = flak tareti -block.mortarturret.fulldescription = Gelişmiş sıçrama hasarlı tareti. Cephane için kömür kullanır. Patlayan mermi şarapnel saysinde birden fazla düşman vurabilir. büyük düşman topluluklarını yok etmek için kullanışlıdır. -block.laserturret.name = lazer tareti -block.laserturret.fulldescription = Gelişmiş tek hedefli taret. Gücü kullanır. orta menzilli taret. Sadece tek hedefli. Asla ıskalamaz. -block.waveturret.name = tesla tareti -block.waveturret.fulldescription = Gelişmiş birçok hedefli taret. Gücü kullanır. Orta menzilli. Asla ıskalamaz. Düşük ile orta seviyede hasar verir, ancak aynı anda birden fazla düşmana vurabilir. -block.plasmaturret.name = plazma tareti -block.plasmaturret.fulldescription = Alev taretinin gelişmiş versiyonu. kömürü cephane olarak kullanır. Çok yüksek hasar, düşük ila orta menzil. -block.chainturret.name = zincir tareti -block.chainturret.fulldescription = En iyi hızlı ateş tareti. Uranyumu cephane olarak kullanır. Yüksek ateş hızı vardır. Orta menzillidir. Birden fazla blok boyunca yayılır. Son derece dayanıklıdır. -block.titancannon.name = titan topu -block.titancannon.fulldescription = En iyi uzak menzilli taret. Uranyumu cephane olarak kullanır. Orta ateş hızındadır ve top ateşinin sıçrama hasarı büyüktür. Uzun mesafe. Birden fazla blok boyunca yayılır. Son derece dayanıklıdır. -block.playerspawn.name = oyuncudoğuşu -block.enemyspawn.name = dϋşmandoğuşu +text.about=[ROYAL] Anuken tarafından oluşturuldu [] - [SKY] anukendev@gmail.com [] Aslen [turuncu] GDL [] Metal Monstrosity Jam. Kredi: - [SARI] ile yapılan SFX bfxr [] - [YEŞİL] RoccoW tarafından yapılan müzik [] / [kireç] bulunan FreeMusicArchive.org [] Özel teşekkürler: - [mercan] MitchellFJN []: Kapsamlı oyun testi ve geri bildirim - [sky] Luxray5474 []: wiki çalışması, kod katkıları - [kireç] Epowerj []: kod sistemi yapılandırması, icon - itch.io ve Google Play'deki tüm beta test kullanıcıları\n +text.credits=Yapımcılar +text.discord=Mindustry Discord'una katılın! +text.link.discord.description=Resmi Mindustry Discord iletişim kanalı +text.link.github.description=Oyunun kaynak kodu +text.link.dev-builds.description=Geliştirme altında olan sürüm +text.link.trello.description=Planlanan özellikler için resmi Trello Bülteni +text.link.itch.io.description=PC yüklemeleri ve web sürümü ile itch.io sayfası +text.link.google-play.description=Google Play mağaza sayfası +text.link.wiki.description=Resmi Mindustry Wikipedi'si +text.linkfail=Bağlantı açılamadı! URL, yazı tahtanıza kopyalandı. +text.editor.web=Web sürümü editörü desteklemiyor! Editörü kullanmak için oyunu indirin. +text.multiplayer.web=Oyunun bu sürümü çok oyunculuyu desteklemiyor! Tarayıcınızdan çok oyunculu oynamak için, itch.io sayfasındaki "çok oyunculu web sürümü" bağlantısını kullanın. +text.gameover=Çekirdek yok edildi. +text.highscore=[SARI] Yeni yüksek puan! +text.lasted=Dalgaya kadar sürdün +text.level.highscore=Yüksek Puan: [accent] {0} +text.level.delete.title=Silmeyi onaylayın +text.level.select=Seviye Seç +text.level.mode=Oyun Modu +text.savegame=Oyunu Kaydet +text.loadgame=Oyunu yükle +text.joingame=Oyuna katıl +text.newgame=Yeni Oyun +text.quit=Çık +text.about.button=Hakkında +text.name=Adı: +text.players=1090 oyuncu çevrimiçi +text.players.single={0} Oyuncu Çevrimiçi +text.server.mismatch=Paket hatası: olası istemci / sunucu sürümü uyuşmazlığı. Siz ve ev sahibi Mindustry'nin en son sürümüne sahip olduğunuzdan emin olun! +text.server.closing=[accent] Sunucu kapatılıyor ... +text.server.kicked.kick=Sunucudan kovuldun! +text.server.kicked.invalidPassword=Geçersiz şifre! +text.server.kicked.clientOutdated=Oyun sürümünüz geçerli değil. Oyununu güncelleyin! +text.server.kicked.serverOutdated=Eski sunucu! Ev sahibinden güncellemesini isteyin! +text.server.kicked.banned=Bu sunucudan yasaklandınız. +text.server.kicked.recentKick=Son zamanlarda tekmelendin. Tekrar bağlanmadan önce bekleyin. +text.server.connected={0} katıldı. +text.server.disconnected={0} bağlantısı kesildi. +text.nohost=Özel bir haritada sunucuyu barındıramıyor! +text.host.info=[Vurgu] ana bilgisayarı [] düğmesi, [657] [65] [65] ve [65] [6568] bağlantı noktalarında bir sunucuyu barındırır. [] Aynı [LIGHT_GRAY] wifi veya yerel ağ [] üzerindeki herkes sunucunuzu sunucularında görebilir. liste. Kişilerin IP tarafından herhangi bir yerden bağlanabilmesini istiyorsanız [vurgu] bağlantı noktası iletme [] gereklidir. [LIGHT_GRAY] Not: Birisi LAN oyununuza bağlanırken sorun yaşıyorsa, güvenlik duvarı ayarlarınızda Mindustry'e yerel ağınıza erişebildiğinizden emin olun. +text.join.info=Burada, bağlanmak için yerel ağ [] sunucularına bağlanmak ya da [aksan] sunucularını bulmak için bir [vurgu] sunucunun IP [] girebilirsiniz. Hem LAN hem de WAN çok oyunculu desteklenir. [LIGHT_GRAY] Not: Otomatik bir global sunucu listesi yoktur; Birisine IP ile bağlanmak isterseniz, ana bilgisayardan kendi IP adreslerini sormanız gerekir. +text.hostserver=Oyunu Sun +text.host=evsahibi +text.hosting=[accent] Sunucu açılıyor ... +text.hosts.refresh=Yenile +text.hosts.discovering=LAN oyunlarını keşfetme +text.server.refreshing=Canlandırıcı sunucu +text.hosts.none=[lightgray] Hayır LAN oyunları bulundu! +text.host.invalid=[scarlet] Ana bilgisayara bağlanılamıyor. +text.server.friendlyfire=Dost ateşi +text.trace=Oyuncuyu Takip Et +text.trace.playername=Oyuncu adı: [accent] {0} +text.trace.ip=IP: [vurgu] {0} +text.trace.id=Benzersiz kimlik: [accent] {0} +text.trace.android=Android : [accent] {0} +text.trace.modclient=Özel Alıcı: [accent] {0} +text.trace.totalblocksbroken=Toplam kırık blok: [accent] {0} +text.trace.structureblocksbroken=Kırılan yapı blokları: [accent] {0} +text.trace.lastblockbroken=Kırılan son blok: [accent] {0} +text.trace.totalblocksplaced=Toplam blok yerleştirildi: [accent] {0} +text.trace.lastblockplaced=Konulan son blok: [accent] {0} +text.invalidid=Geçersiz alıcı kimliği! Bir hata raporu gönderin. +text.server.bans=yasaklar +text.server.bans.none=Yasaklanmış oyuncu bulunamadı! +text.server.admins=Yöneticiler +text.server.admins.none=Yönetici bulunamadı! +text.server.add=Sunucu ekle +text.server.delete=Bu sunucuyu silmek istediğinizden emin misiniz? +text.server.hostname=Sun +text.server.edit=Sunucuyu Düzenle +text.server.outdated=[crimson] Eski Sunucu! +text.server.outdated.client=[crimson] Eski Alıcı! +text.server.version=[lightgray] Sürüm: {0} +text.server.custombuild=[sarı] Özel Yapım +text.confirmban=Bu oyuncuyu yasaklamak istediğinizden emin misiniz? +text.confirmunban=Bu oyuncunun yasağını kaldırmak istediğinden emin misin? +text.confirmadmin=Bu oyuncunun yönetici yapmak istediğinden emin misin? +text.confirmunadmin=Bu oyuncudan yönetici durumunu kaldırmak istediğinizden emin misiniz? +text.joingame.title=Oyuna katılmak +text.joingame.ip=IP: +text.disconnect=Bağlantı Kesildi +text.disconnect.data=Dünya verileri yüklenemedi! +text.connecting=[Vurgu] bağlanıyor ... +text.connecting.data=[accent] Dünya verileri yükleniyor ... +text.connectfail=[crimson] Sunucuya bağlanılamadı: [orange] {0} +text.server.port=Liman +text.server.addressinuse=Adres çoktan kullanımda! +text.server.invalidport=Bağlantı noktası numarası geçersiz. +text.server.error=[crimson] Sunucu barındırma hatası: [orange] {0} +text.save.new=6349,Yeni Kayıt +text.save.overwrite=Bu kayıt yuvasının üzerine yazmak istediğinizden emin misiniz? +text.overwrite=Üzerine Yaz +text.save.none=Hiçbir kayıt bulunamadı! +text.saveload=[Vurgu] Kaydediliyor ... +text.savefail=Oyun kaydedilemedi! +text.save.delete.confirm=Bu kaydı silmek istediğinizden emin misiniz? +text.save.delete=Sil +text.save.export=Dışa Aktar +text.save.import.invalid=[turuncu] Bu kayıt geçersiz! +text.save.import.fail=[crimson] Kayıt oyuna aktarılamadı : [orange] {0} +text.save.export.fail=[crimson] Kayıt dışa aktarılamadı: [orange] {0} +text.save.import=İçe Aktar +text.save.newslot=İsmi kaydet: +text.save.rename=Yeniden Adlandır +text.save.rename.text=Yeni İsim: +text.selectslot=Bir kayıt seçin. +text.slot=[accent] Yuva {0} +text.save.corrupted=[orange] Kayıt dosyası bozuk veya geçersiz! +text.empty= +text.on=Açık +text.off=Kapalı +text.save.autosave=Otomatik kaydetme: {0} +text.save.map=harita +text.save.wave=Dalga +text.save.difficulty=zorluk +text.save.date=Son Kaydedilen: {0} +text.confirm=Onayla +text.delete=Sil +text.ok=Tamam +text.open=Açık +text.cancel=İptal +text.openlink=Linki aç +text.copylink=Bağlantıyı kopyala +text.back=Geri +text.quit.confirm=Çıkmak istediğinden emin misin? +text.changelog.title=Değişiklik listesi +text.changelog.loading=Değişiklik listesi yükleniyor +text.changelog.error.android=[turuncu] Android'da olan hata nedeniyle değişiklik listesi görüntülenemiyor. +text.changelog.error=[scarlet] Değişiklik listesi alma hatası! İnternet bağlantınızı kontrol edin. +text.changelog.current=[sarı] [[Güncel versiyon] +text.changelog.latest=[turuncu] [[Son sürüm] +text.loading=[Vurgu] Yükleniyor ... +text.wave=[turuncu] Dalga {0} +text.wave.waiting={0} içinde dalga +text.waiting=Bekleniyor +text.enemies={0} Düşmanlar +text.enemies.single={0} Düşman +text.loadimage=Resmi yükle +text.saveimage=Resmi Kaydet +text.editor.badsize=[orange] Resim boyutları geçersiz! [] Geçerli harita boyutları: {0} +text.editor.errorimageload=Resim dosyası yüklenirken hata oluştu: [orange] {0} +text.editor.errorimagesave=Resim dosyası kaydedilirken hata oluştu: [orange] {0} +text.editor.generate=Üretmek +text.editor.resize=Yeniden Boyutlandırma +text.editor.loadmap=Harita Yükle +text.editor.savemap=Harita Kaydet +text.editor.loadimage=Resmi yükle +text.editor.saveimage=Resmi Kaydet +text.editor.unsaved=[scarlet] Kaydedilmemiş değişiklikleriniz var! [] Çıkmak istediğinizden emin misiniz? +text.editor.resizemap=Haritayı Yeniden Boyutlandır +text.editor.mapname=Harita Adı +text.editor.overwrite=[Vurgu] Uyarı! Bu mevcut bir haritanın üzerine yazar. +text.editor.selectmap=Yüklenecek bir harita seçin: +text.width=Genişliği: +text.height=Boy: +text.menu=Menü +text.play=Oyna +text.load=Yükle +text.save=Kaydet +text.language.restart=Lütfen dil ayarlarının etkili olması için oyununuzu yeniden başlatın. +text.settings.language=Dil +text.settings=Ayarlar +text.tutorial=Eğitim +text.editor=Editör +text.mapeditor=Harita Editörü +text.donate=Bağışlamak +text.settings.reset=Varsayılanlara Dön +text.settings.controls=kontroller +text.settings.game=Oyun +text.settings.sound=Ses +text.settings.graphics=Grafik +text.upgrades=Geliştirmeler +text.purchased=[KİREÇ] Yap၊ld၊ +text.weapons=Silahlar +text.paused=Duraklatıldı +text.info.title=[Vurgu] Bilgi +text.error.title=[crimson] Bir hata oluştu +text.error.crashtitle=Bir hata oluştu +text.blocks.blockinfo=Blok Bilgisi +text.blocks.powercapacity=Güç kapasitesi +text.blocks.powershot=Güç / atış +text.blocks.size=Boyut +text.blocks.liquidcapacity=Sıvı kapasitesi +text.blocks.maxitemssecond=Maksimum öğe / saniye +text.blocks.powerrange=Güç aralığı +text.blocks.itemcapacity=Ürün kapasitesi +text.blocks.inputliquid=Giriş sıvı +text.blocks.inputitem=Giriş öğesi +text.blocks.explosive=Çok patlayıcı! +text.blocks.health=Can +text.blocks.inaccuracy=yanlışlık +text.blocks.shots=atışlar +text.blocks.inputcapacity=Giriş kapasitesi +text.blocks.outputcapacity=Çıkış kapasitesi +setting.difficulty.easy=kolay +setting.difficulty.normal=orta +setting.difficulty.hard=zor +setting.difficulty.insane=deli +setting.difficulty.purge=tasfiye +setting.difficulty.name=Zorluk: +setting.screenshake.name=Ekran Sallamak +setting.indicators.name=Düşman Göstergeleri +setting.effects.name=Görüntü Efektleri +setting.sensitivity.name=Denetleyici hassasiyeti +setting.saveinterval.name=Otomatik Kaydetme Aralığı +setting.seconds=saniye +setting.fullscreen.name=Tam ekran +setting.multithread.name=Çok iş parçacığı +setting.fps.name=Saniyede ... Kare göstermek +setting.vsync.name=VSync +setting.lasers.name=Güç Lazerleri Göster +setting.healthbars.name=Varlık Sağlık çubuklarını göster +setting.musicvol.name=Müzik sesi +setting.mutemusic.name=Müziği Kapat +setting.sfxvol.name=SFX Hacmi +setting.mutesound.name=Sesi kapat +map.maze.name=Labirent +map.fortress.name=Kale +map.sinkhole.name=düden +map.caves.name=mağaralar +map.volcano.name=volkan +map.caldera.name=kaldera +map.scorch.name=alazlamak +map.desert.name=çöl +map.island.name=ada +map.grassland.name=Çayır +map.tundra.name=tundra +map.spiral.name=sarmal +map.tutorial.name=Eğitim +text.keybind.title=Tuşları yeniden ayarla +keybind.move_x.name=sağ / sol +keybind.move_y.name=yukarı / aşağı +keybind.select.name=seçmek +keybind.break.name=kırmak +keybind.shoot.name=ateş etme +keybind.zoom_hold.name=tut ve büyüt +keybind.zoom.name=Yakınlaştır +keybind.block_info.name=blok bilgisi +keybind.menu.name=menü +keybind.pause.name=duraklatma +keybind.dash.name=tire +keybind.chat.name=Sohbet +keybind.player_list.name=oyuncu listesi +keybind.console.name=KONTROL MASASI +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Döndür +mode.text.help.title=Modların açıklaması +mode.waves.name=dalgalar +mode.waves.description=normal mod. sınırlı kaynaklar ve otomatik gelen dalgalar. +mode.sandbox.name=Limitsiz Oynama +mode.sandbox.description=sonsuz kaynaklar ve dalgalar için zamanlayıcı yok. +mode.freebuild.name=Özgür Oynama +mode.freebuild.description=sınırlı kaynaklar ve dalgalar için zamanlayıcı yok. +item.stone.name=taş +item.coal.name=kömür +item.titanium.name=titanyum +item.sand.name=kum +liquid.water.name=su +liquid.lava.name=lav +liquid.oil.name=petrol +block.door.name=kapı +block.door-large.name=büyük kapı +block.conduit.name=sıvı borusu +block.pulseconduit.name=hızlı sıvı borusu +block.liquidrouter.name=sıvı yönlendirici +block.conveyor.name=konveyör +block.router.name=yönlendirici +block.junction.name=Kavşak noktası +block.liquidjunction.name=sıvı bağlantı +block.sorter.name=ayrıştırıcı +block.smelter.name=dökümcü +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.name=Thorium +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/bundles/bundle_uk_UA.properties b/core/assets/bundles/bundle_uk_UA.properties index 0630b6e4bc..7a85ec0af1 100644 --- a/core/assets/bundles/bundle_uk_UA.properties +++ b/core/assets/bundles/bundle_uk_UA.properties @@ -1,500 +1,495 @@ -text.about = Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n -text.discord = Приєднуйтесь до нашого Discord! -text.changes = [SCARLET] Увага! \n[] Деякі важливі механіки гри були змінені.\n- [accent] Телепорти [] тепер використовують електроенергію. \n- [accent] Домінна піч [] та [accent] Тиглі [] тепер мають ліміт. \n- [accent] Тиглі [] зараз вимагають вугілля як паливо. -text.gameover = Ядро було зруйновано. -text.highscore = [YELLOW] Новий рекорд! -text.lasted = Ви тримались до хвилі -text.level.highscore = Рекорд: [accent] {0} -text.level.delete.title = Підтвердьте видалення -text.level.delete = Ви впевнені, що хочете видалити карту \"[orange] {0} \"? -text.level.select = Вибір рівня -text.level.mode = Ігровий режим -text.savegame = Зберегти гру -text.loadgame = Завантажити гру -text.joingame = Приєднатися\nдо гри +text.about=Створено [ROYAL] Anuken. []\nСпочатку запис у [orange] GDL [] MM Jam.\nТворці:\n- SFX зроблено з [YELLOW] bfxr []\n- Музика зроблена [GREEN] RoccoW [] / Знайдено на [lime] FreeMusicArchive.org [] \nОсоблива подяка:\n- [coral] MitchellFJN []: екстенсивне тестування та відгуки\n- [sky] Luxray5474 []: робота з вікі, вклади коду\n- Всі бета-тестери на itch.io та Google Play\n +text.discord=Приєднуйтесь до нашого Discord! +text.gameover=Ядро було зруйновано. +text.highscore=[YELLOW] Новий рекорд! +text.lasted=Ви тримались до хвилі +text.level.highscore=Рекорд: [accent] {0} +text.level.delete.title=Підтвердьте видалення +text.level.select=Вибір рівня +text.level.mode=Ігровий режим +text.savegame=Зберегти гру +text.loadgame=Завантажити гру +text.joingame=Приєднатися\nдо гри text.newgame=Нова гра -text.quit = Вийти -text.about.button = Про -text.name = Назва: -text.public = Публічний -text.players = {0} гравців онлайн -text.server.player.host = {0} (host) -text.players.single = {0} гравців онлайн -text.server.mismatch = Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! -text.server.closing = [accent] Закриття сервера ... -text.server.kicked.kick = Ви були вигнані з сервера! -text.server.kicked.invalidPassword = Невірний пароль! -text.server.kicked.clientOutdated = Застарілий клієнт! Оновіть свою гру! -text.server.kicked.serverOutdated = Застарілий сервер! Попросіть хост оновити! -text.server.connected = {0} приєднався. -text.server.disconnected = {0} від'єднано. -text.nohost = Неможливо розмістити сервер на власній карті! -text.hostserver = Хост-сервер -text.host = Хост -text.hosting = [accent] Відкриття сервера ... -text.hosts.refresh = Оновити -text.hosts.discovering = Знайомство з мережевими іграми -text.server.refreshing = Оновити сервери -text.hosts.none = [lightgray] Ніяких ігор у мережі не знайдено! -text.host.invalid = [scarlet] Неможливо підключитися до хосту. -text.server.friendlyfire = Дружній вогонь -text.server.add = Додати сервер -text.server.delete = Ви впевнені, що хочете видалити цей сервер? -text.server.hostname = Хост: {0} -text.server.edit = Редагувати сервер -text.joingame.byip = [] Приєднатися по IP ...[] -text.joingame.title = Приєднатися до гри -text.joingame.ip = IP -text.disconnect = Роз'єднано -text.connecting = [accent] Підключення ... -text.connecting.data = [accent] Завантаження світових даних ... -text.connectfail = [crimson] Не вдалося підключитися до сервера: [orange] {0} -text.server.port = Порт -text.server.addressinuse = Адреса вже використовується! -text.server.invalidport = Недійсний номер порту. -text.server.error = [crimson] Помилка хостингу сервера: [orange] {0} -text.tutorial.back = < Попер. -text.tutorial.next = Далі > -text.save.new = Нове збереження -text.save.overwrite = Ви впевнені, що хочете перезаписати цей слот для збереження? -text.overwrite = Перезаписати -text.save.none = Не знайдено жодних збережень! -text.saveload = [accent] Збереження ... -text.savefail = Не вдалося зберегти гру! -text.save.delete.confirm = Ви впевнені, що хочете видалити це збереження? -text.save.delete = Видалити -text.save.export = Експорт збереження -text.save.import.invalid = [orange] Це збереження недійсне! -text.save.import.fail = [crimson] Не вдалося імпортувати збереження: [orange] {0} -text.save.export.fail = [crimson] Не вдалося експортувати збереження: [orange] {0} -text.save.import = Імпортувати збереження -text.save.newslot = Назва збереження: -text.save.rename = Переіменувати -text.save.rename.text = Нова назва: -text.selectslot = Виберіть збереження. -text.slot = [accent] слот {0} -text.save.corrupted = [orange] Збережений файл пошкоджений або він невірний! -text.empty = <порожньо> -text.on = Увімкнути -text.off = Вимкнути -text.save.autosave = Автозбереження: {0} -text.save.map = Карта -text.save.wave = Хвиля {0} -text.save.difficulty = Складність -text.save.date = Останнє збережено: {0} -text.confirm = Підтвердити -text.delete = Видалити -text.ok = ОК -text.open = Відкрити -text.cancel = Скасувати -text.openlink = Відкрити посилання -text.back = Назад -text.quit.confirm = Ти впевнений що хочеш піти? -text.loading = [accent] Завантаження ... -text.wave = [orange] хвиля {0} -text.wave.waiting = Хвиля через {0} -text.waiting = Очікування… -text.enemies = {0} Вороги -text.enemies.single = Противник -text.loadimage = Завантажити зображення -text.saveimage = Зберегти зображення -text.oregen = Генерація руд -text.editor.badsize = [orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0} -text.editor.errorimageload = Помилка завантаження файлу зображень: [orange] {0} -text.editor.errorimagesave = Помилка збереження файлу зображення: [orange] {0} -text.editor.generate = Генератор -text.editor.resize = Змінити розмір -text.editor.loadmap = // Завантажити карту -text.editor.savemap = Зберегти карту -text.editor.loadimage = Завантажити зображення -text.editor.saveimage = Зберегти зображення -text.editor.unsaved = [scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? -text.editor.brushsize = Розмір пензля: {0} -text.editor.noplayerspawn = Ця карта не має ігрового поля для гравця! -text.editor.manyplayerspawns = Карти не можуть мати більше одного ігрового поля для гравців! -text.editor.manyenemyspawns = Не може бути більше ніж {0} ворожих точок! -text.editor.resizemap = Змінити розмір карти -text.editor.resizebig = [scarlet] Попередження! [] Карти, розмір яких перевищує 256 одиниць, можуть виснути і можуть бути нестабільними. -text.editor.mapname = Назва карти: -text.editor.overwrite = [accent] Попередження! Це перезаписує існуючу карту. -text.editor.failoverwrite = [crimson] Неможливо перезаписати карту за замовчуванням! -text.editor.selectmap = Виберіть карту для завантаження: -text.width = Ширина -text.height = Висота -text.randomize = Рандомізувати -text.apply = Застосувати -text.update = Оновити -text.menu = Меню -text.play = Відтворити -text.load = Завантаження -text.save = Зберегти -text.language.restart = Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. -text.settings.language = Мова -text.settings = Налаштування -text.tutorial = Навчальний\nпосібник -text.editor = Редактор -text.mapeditor = Редактор карт -text.donate = Підтримати проект -text.settings.reset = Скинути до стандартних -text.settings.controls = Елементи управління -text.settings.game = Гра -text.settings.sound = Звук -text.settings.graphics = Графіка -text.upgrades = Оновлення -text.purchased = [LIME] Створено! -text.weapons = Зброя -text.paused = Пауза -text.respawn = Відновлення за -text.info.title = [accent] інформація -text.error.title = [crimson] Виникла помилка -text.error.crashmessage = [SCARLET] Виникла несподівана помилка, що призвела до збою. [] Будь ласка, повідомте про конкретні обставини, розробнику: [ORANGE] anukendev@gmail.com [] -text.error.crashtitle = Виникла помилка -text.mode.break = Режим зносу: {0} -text.mode.place = Режим будівництва: {0} -placemode.hold.name = Лінія -placemode.areadelete.name = Площа -placemode.touchdelete.name = Дотик -placemode.holddelete.name = Утримування. -placemode.none.name = (None) -placemode.touch.name = Дотик -placemode.cursor.name = курсор -text.blocks.extrainfo = [accent] додатковий інформаційний блок: -text.blocks.blockinfo = Блокування інформації -text.blocks.powercapacity = Потужність -text.blocks.powershot = Потужність / постріл -text.blocks.powersecond = Потужність / секунда -text.blocks.powerdraindamage = Потужність дренажу / пошкодження -text.blocks.shieldradius = Радіус щита -text.blocks.itemspeedsecond = Швидкість / секунда -text.blocks.range = Радіус -text.blocks.size = Розмір -text.blocks.powerliquid = Потужність / Рідина -text.blocks.maxliquidsecond = Макс. Рідина / секунда -text.blocks.liquidcapacity = Ємкість рідини -text.blocks.liquidsecond = Рідина / секунда -text.blocks.damageshot = Пошкодження / постріл -text.blocks.ammocapacity = Місткість боєприпасів -text.blocks.ammo = Набої -text.blocks.ammoitem = Боєприпаси / предмет -text.blocks.maxitemssecond = Макс. Елементи / секунду -text.blocks.powerrange = Радіус потужності -text.blocks.lasertilerange = Радіус лазерних плиток -text.blocks.capacity = Ємкість -text.blocks.itemcapacity = Ємкість предмету -text.blocks.maxpowergenerationsecond = Максимальна потужність / секунда -text.blocks.powergenerationsecond = Потужність / секунда -text.blocks.generationsecondsitem = Генерація за секунду / предмет -text.blocks.input = Ввід -text.blocks.inputliquid = Ввід речовини -text.blocks.inputitem = Вхідний матеріал -text.blocks.output = Вивід -text.blocks.secondsitem = Секунда / предмет -text.blocks.maxpowertransfersecond = Максимальна передача потужності / секунда -text.blocks.explosive = Вибухонебезпечний! -text.blocks.repairssecond = Ремонт / секунда -text.blocks.health = Здоров'я -text.blocks.inaccuracy = Неточність -text.blocks.shots = Постріли -text.blocks.shotssecond = Постріли / секунду -text.blocks.fuel = Паливо: -text.blocks.fuelduration = Тривалість палива -text.blocks.maxoutputsecond = Макс. Вихід / секунду -text.blocks.inputcapacity = Вхідна ємність -text.blocks.outputcapacity = Випускна ємність -text.blocks.poweritem = Потужність / виріб -text.placemode = Місцевий режим -text.breakmode = Перерваний режим -text.health = Здоров'я -setting.difficulty.easy = Легкий -setting.difficulty.normal = Нормальний -setting.difficulty.hard = Важкий -setting.difficulty.insane = Божевільний -setting.difficulty.purge = Очистити -setting.difficulty.name = Складність -setting.screenshake.name = Тряска екрана -setting.smoothcam.name = Гладка камера -setting.indicators.name = Індикатори ворога -setting.effects.name = Ефекти відображення -setting.sensitivity.name = Чутливість контролера -setting.saveinterval.name = Інтервал автозбереження -setting.seconds = {0} секунд -setting.fullscreen.name = Повноекранний -setting.multithread.name = Багатопотоковий [scarlet] (нестабільний!) -setting.fps.name = Показати FPS -setting.vsync.name = VSunc -setting.lasers.name = Показати енергетичні лазери -setting.healthbars.name = Показати здоров'я -setting.pixelate.name = Пікселяція екрану -setting.musicvol.name = Гучність музики -setting.mutemusic.name = Вимкнути музику -setting.sfxvol.name = Гучність ефектів -setting.mutesound.name = Вимкнути звук -map.maze.name = Лабіринт -map.fortress.name = Фортеця -map.sinkhole.name = Свердловина -map.caves.name = Печери -map.volcano.name = Вулкан -map.caldera.name = Кальдера -map.scorch.name = Мертва земля -map.desert.name = Пустеля -map.island.name = Острів -map.grassland.name = Пасовища -map.tundra.name = Тундра -map.spiral.name = Спіраль -map.tutorial.name = Навчання -tutorial.intro.text = [yellow] Ласкаво просимо до підручника. [] Для початку натисніть \"далі\". -tutorial.moveDesktop.text = Для переміщення використовуйте клавіші [orange] ​​[[WASD] []. Утримуйте [orange] SHIFT[], для прискорення. Утримуйте [orange] CTRL [], використовуючи [orange] колесо прокручування [] для збільшення або зменшення. -tutorial.shoot.text = Використовуйте мишу, щоб націлитись, утримуйте [orange] ліву кнопку миші [], щоб стріляти. Попрактикуйтесь на [yellow] мішені []. -tutorial.moveAndroid.text = Щоб перетягнути панораму, перетягніть один палець по екрану. Використовуйте два пальця, щоб збільшити чи зменшити маштаб. -tutorial.placeSelect.text = Спробуйте вибрати [yellow] конвеєр [] у меню блоку внизу справа. -tutorial.placeConveyorDesktop.text = Використовуйте [orange] [[колесико миші] [], щоб повернути конвеєр [orange] вперед [], а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[ліву кнопку миші] []. -tutorial.placeConveyorAndroid.text = Використовуйте [orange] [[кнопку оберту] [], щоб обернути конвеєр [оранжевий] вперед [], перетягуйте його одним пальцем, а потім помістіть його в [yellow] позначене місце [], використовуючи [orange] [[галочка][]. -tutorial.placeConveyorAndroidInfo.text = Крім того, ви можете натиснути піктограму перехрестя внизу ліворуч, щоб переключитися на [orange] [[сенсорний режим]] [], і помістити блоки, натиснувши на екран. У сенсорному режимі блоки можна повертати зі стрілкою внизу ліворуч. Натисніть [yellow] наступний [], щоб спробувати. -tutorial.placeDrill.text = Тепер виберіть та розмістіть [yellow] кам'яне свердло [] у зазначеному місці. -tutorial.blockInfo.text = Якщо ви хочете дізнатись більше про блок, ви можете торкнутися [orange] знак питання [] у верхньому правому куті, щоб прочитати його опис. -tutorial.deselectDesktop.text = Ви можете вимкнути блок, використовуючи [orange] [[клацання правою кнопкою миші] []. -tutorial.deselectAndroid.text = Ви можете скасувати вибір блоку, натиснувши кнопку [orange] X []. -tutorial.drillPlaced.text = Дриль тепер видобуває [yellow] камінь, [] та виведе його на конвеєр, а потім переміщає його в [yellow] ядро []. -tutorial.drillInfo.text = Різні руди потребують різних дрилі. Камінь вимагає кам'яні свердла, залізо вимагає залізні свердла та ін -tutorial.drillPlaced2.text = Переміщення елементів у ядро ​​вказує їх у ваш [yellow] предметний інвентар [] у верхньому лівому куті. Розміщення блоків використовує предмети з вашого інвентарю. -tutorial.moreDrills.text = Ви можете пов'язати багато свердлів і конвеєрів разом в одну гілку конвеєра. -tutorial.deleteBlock.text = Ви можете видалити блоки, натиснувши правою клавішею [orange] правою кнопкою миші [] по блоці, який ви хочете видалити. Спробуйте видалити цей конвеєр. -tutorial.deleteBlockAndroid.text = Ви можете видалити блоки за допомогою [orange], перехрестя [] в меню [mode] зламу [orange] у нижньому лівому куті та натиснувши на блок. Спробуйте видалити цей конвеєр. -tutorial.placeTurret.text = Тепер виділіть та розмістіть [yellow] турель [] у [yellow] позначеному місці []. -tutorial.placedTurretAmmo.text = Ця турель тепер приймає [yellow] боєприпас [] з конвеєра. Ви можете побачити, скільки боєприпасів вона має, натискаючи на неї і перевіряючи [green] зелену полоску []. -tutorial.turretExplanation.text = Турелі будуть автоматично стріляти у найближчого ворога, якщо вони мають достатню кількість боєприпасів. -tutorial.waves.text = Кожні [yellow] 60 [] секунд, хвиля [coral] ворогів [] буде виникати в певних місцях і намагатися знищити ядро. -tutorial.coreDestruction.text = Ваша мета полягає в тому, щоб [yellow] захищати ядро []. Якщо ядро ​​знищено, ви [coral] програєте[]. -tutorial.pausingDesktop.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] паузи [] у верхньому лівому куті або на кнопку [orange] пропуск [], щоб призупинити гру. Ви можете вибрати і розмістити блоки під час призупинення, але не можете переміщатися чи стріляти. -tutorial.pausingAndroid.text = Якщо вам коли-небудь потрібно зробити перерву, натисніть кнопку [orange] пауза [] у верхньому лівому куті, щоб призупинити гру. Ти можеш ще знищувати та будувати блоки під час призупинення. -tutorial.purchaseWeapons.text = Ви можете придбати нову [yellow] зброю [] для вашого механізму, відкривши меню оновлення в лівому нижньому кутку. -tutorial.switchWeapons.text = Перемикати зброю будь-яким натисканням його піктограми внизу ліворуч або за допомогою цифр [orange] [[1-9] []. -tutorial.spawnWave.text = Ось хвиля зараз. Знищи їх -tutorial.pumpDesc.text = У пізніших хвилях, можливо, доведеться використовувати [yellow] насоси [] для розподілу рідин для генераторів або екстракторів. -tutorial.pumpPlace.text = Насоси працюють аналогічно свердлам, за винятком того, що вони виробляють рідини замість предметів. Спробуйте встановити насос на [yellow] призначене мастило []. -tutorial.conduitUse.text = Тепер покладіть [orange] трубопровід [], віддаляючись від насоса. -tutorial.conduitUse2.text = І ще кілька ... -tutorial.conduitUse3.text = І ще кілька ... -tutorial.generator.text = Тепер, помістіть блок [orange] ​​базовий генератор енергії [] в кінці каналу. -tutorial.generatorExplain.text = Цей генератор тепер створить [yellow] енергію [] від масла. -tutorial.lasers.text = Потужність розподіляється за допомогою [yellow] лазерів потужності []. Поверніть і помістіть його тут. -tutorial.laserExplain.text = Тепер генератор переведе енергію в лазерний блок. Промінь [yellow] непрозорий [] означає, що в даний час він передає потужність, а промінь [yellow] прозорий [] означає, що це не так. -tutorial.laserMore.text = Ви можете перевірити, скільки енергії в блоку, наведіть курсор миші на нього і перевірте [yellow] жовту стрічку [] у верхній частині екрана. -tutorial.healingTurret.text = Цей лазер може бути використаний для живлення турелі для ремонту [lime] []. Помістіть одну тут. -tutorial.healingTurretExplain.text = Поки вона має енергію, ця турель може [lime] відремонтувати блоки. [] Під час гри постарайтеся збудувати одну таку чим швидше! -tutorial.smeltery.text = Для багатьох блоків потрібна [orange] сталь [], для цього потрібна[orange] доминна піч [] . Місце тут. -tutorial.smelterySetup.text = Ця піч буде тепер виробляти [orange] сталь [] із вхідного заліза, використовуючи вугілля як паливо. -tutorial.tunnelExplain.text = Також зауважте, що елементи проходять через [yellow] тунельний блок [] і з'являються з іншого боку, проходячи через кам'яний блок. Майте на увазі, що тунелі можуть проходити лише до 2 блоків. -tutorial.end.text = Ви завершили підручник! Удачі! -text.keybind.title = Ключ перемотки -keybind.move_x.name = move_x -keybind.move_y.name = move_y -keybind.select.name = Вибрати -keybind.break.name = {0}break{/0}{1}; {/1} -keybind.shoot.name = Постріл -keybind.zoom_hold.name = zoom_hold -keybind.zoom.name = Збільшити -keybind.block_info.name = Інформація про блок -keybind.menu.name = Меню -keybind.pause.name = Пауза -keybind.dash.name = Тире -keybind.chat.name = Чат -keybind.player_list.name = Список гравців -keybind.console.name = // Консоль 1 -keybind.rotate_alt.name = rotate_alt -keybind.rotate.name = Повернути -keybind.weapon_1.name = Зброя! -keybind.weapon_2.name = Зброя! -keybind.weapon_3.name = Зброя! -keybind.weapon_4.name = Зброя! -keybind.weapon_5.name = Зброя! -keybind.weapon_6.name = Зброя! -mode.waves.name = Хвилі -mode.sandbox.name = Пісочниця -mode.freebuild.name = Вільний режим -upgrade.standard.name = Стандартний -upgrade.standard.description = Стандартний механ. -upgrade.blaster.name = Бластер -upgrade.blaster.description = Стріляє повільно, слабкі кулі. -upgrade.triblaster.name = Трипластер -upgrade.triblaster.description = Вистрілює 3 кулі в розповсюдженні. -upgrade.clustergun.name = Касетна гармата -upgrade.clustergun.description = Вистрілює неточними вибуховими гранатами. -upgrade.beam.name = Пушечна гармата -upgrade.beam.description = Вистрілює далекобійним,пробірний лазерний промінь. -upgrade.vulcan.name = Вулкан -upgrade.vulcan.description = Вистрілює шквал швидких куль. -upgrade.shockgun.name = Шок-пушка -upgrade.shockgun.description = Стріляє руйнівним вибухом заряженої шрапнелі. -item.stone.name = Камінь -item.iron.name = Залізо -item.coal.name = Вугівалля -item.steel.name = Сталь -item.titanium.name = Титан -item.dirium.name = Дириум -item.uranium.name = Уран -item.sand.name = Пісок -liquid.water.name = Вода -liquid.plasma.name = Плазма -liquid.lava.name = Лава -liquid.oil.name = Нафта -block.weaponfactory.name = Фабрика зброї -block.weaponfactory.fulldescription = Використовується для створення зброї для гравця mech. Натисніть, щоб використати. Автоматично приймає ресурси з основного ядра. -block.air.name = Повітря -block.blockpart.name = Блокчастина -block.deepwater.name = Глибока вода -block.water.name = Вода -block.lava.name = Лава -block.oil.name = Нафта -block.stone.name = Камінь -block.blackstone.name = Чорний камінь -block.iron.name = Залізо -block.coal.name = Вугілля -block.titanium.name = Титан -block.uranium.name = Уран -block.dirt.name = Бруд -block.sand.name = Пісок -block.ice.name = Лід -block.snow.name = Сніг -block.grass.name = Трава -block.sandblock.name = Блок піску -block.snowblock.name = Блок снігу -block.stoneblock.name = Блок камню -block.blackstoneblock.name = Блок чорного камню -block.grassblock.name = Блок бруду -block.mossblock.name = Моссблок -block.shrub.name = Чагарник -block.rock.name = Камень -block.icerock.name = Ледяний камень -block.blackrock.name = Чорний камінь -block.dirtblock.name = Блок землі -block.stonewall.name = Кам'яна стіна -block.stonewall.fulldescription = Недорогий захисний блок. Корисно для захисту ядра та турелі в перші кілька хвиль. -block.ironwall.name = Залізна стіна -block.ironwall.fulldescription = Основний захисний блок. Забезпечує захист від ворогів. -block.steelwall.name = Сталева стіна -block.steelwall.fulldescription = Стандартний захисний блок. адекватний захист від ворогів. -block.titaniumwall.name = Титанова стіна -block.titaniumwall.fulldescription = Сильний захисний блок. Забезпечує захист від ворогів. -block.duriumwall.name = Діріумова стіна -block.duriumwall.fulldescription = Дуже сильний захисний блок. Забезпечує захист від ворогів. -block.compositewall.name = Композитна стіна -block.steelwall-large.name = Велика сталева стіна -block.steelwall-large.fulldescription = Стандартний захисний блок. Поєднує в собі кілька блоків. -block.titaniumwall-large.name = Велика титанова стіна -block.titaniumwall-large.fulldescription = Сильний захисний блок. Поєднує в собі кілька блоків. -block.duriumwall-large.name = Велика дирмітова стіна -block.duriumwall-large.fulldescription = Дуже сильний захисний блок.Поєднує в собі кілька блоків. -block.titaniumshieldwall.name = Стіна з щитом -block.titaniumshieldwall.fulldescription = Сильний захисний блок з додатковим вбудованим щитом. Потрібна енергія. Використовує енергію для поглинання ворожих куль. Рекомендується використовувати силові пристосування для забезпечення енергії цього блоку. -block.repairturret.name = Ремонтна турель -block.repairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Повільний темп. Використовує невелику кількість енергії. -block.megarepairturret.name = Ремонтна турель II -block.megarepairturret.fulldescription = Ремонтує недалекі пошкодженні блоки.Збільшений радіус та швидший темп ремонту . Використовує багато енергії. -block.shieldgenerator.name = Генератор щиту -block.shieldgenerator.fulldescription = Передовий захисний блок. Захищає всі блоки в радіусі від нападу. Не вкористовує енергію при бездіяльності, але швидко витрачає енергію на захист від куль. -block.door.name = Двері -block.door.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. -block.door-large.name = Великі двері -block.door-large.fulldescription = Блок, який можна відкрити та закрити, торкнувшись його. -block.conduit.name = Трубопровід -block.conduit.fulldescription = Основний транспортний блок. Працює як конвеєр, але з рідинами. Найкраще використовується з насосами або іншими трубопроводами. Може використовуватися як міст через рідини для ворогів та гравців. -block.pulseconduit.name = Імпульсний канал -block.pulseconduit.fulldescription = Покращенний блок перевезення рідин. Транспортує рідини швидше і зберігає більше стандартних каналів. -block.liquidrouter.name = маршрутизатор для рідини -block.liquidrouter.fulldescription = Працює аналогічно маршрутизатору. Приймає рідину ввід з одного боку і виводить його на інші сторони. Корисний для розщеплення рідини з одного каналу на кілька інших трубопроводів. -block.conveyor.name = Конвеєр -block.conveyor.fulldescription = Базовий транспортний блок. Переміщує предмети вперед і автоматично вкладає їх у турелі або ремісники. Поворотний Може використовуватися як міст через рідину для ворогів та гравців. -block.steelconveyor.name = Сталевий конвеєр -block.steelconveyor.fulldescription = Розширений блок транспортування предметів. Переміщення елементів швидше, ніж стандартні конвеєри. -block.poweredconveyor.name = Імпульсний конвеєр -block.poweredconveyor.fulldescription = Кінцевий транспортний блок. Переміщення елементів швидше, ніж сталеві конвеєри. -block.router.name = Маршрутизатор -block.router.fulldescription = Приймає елементи з одного напрямку і виводить їх на 3 інших напрямках. Можна також зберігати певну кількість предметів. Використовується для розщеплення матеріалів з одного свердла на декілька башточок. -block.junction.name = Міст -block.junction.fulldescription = Виступає як міст для двох перехресних конвеєрних стрічок. Корисне у ситуаціях з двома різними конвеєрами, що несуть різні матеріали в різних місцях. -block.conveyortunnel.name = Конвеєрний тунель -block.conveyortunnel.fulldescription = Транспортує предмети під блоками. Щоб використати, помістіть один тунель, що веде у блок, щоб бути підсвіченим, а один - з іншого боку. Переконайтеся, що обидва тунелі стикаються з протилежними напрямками, тобто до блоків, які вони вводять або виводять. -block.liquidjunction.name = Міст для рідини -block.liquidjunction.fulldescription = Діє як міст для двох перехресних трубопроводів. Корисно в ситуаціях з двома різними трубами, що несуть різні рідини в різних місцях. -block.liquiditemjunction.name = Перехрестя рідкого пункту -block.liquiditemjunction.fulldescription = Виступає як міст для перетину трубопроводів і конвеєрів. -block.powerbooster.name = Підсилювач потужності -block.powerbooster.fulldescription = Поширює енергію на всі блоки в межах його радіуса. -block.powerlaser.name = Енергетичний лазер -block.powerlaser.fulldescription = Створює лазер, який передає енергію блоку перед ним. Не створює жодної сили сама. Найкраще використовується з генераторами або іншими лазерами. -block.powerlaserrouter.name = Лазерний маршрутизатор -block.powerlaserrouter.fulldescription = Лазер, який розподіляє енергію у три напрямки одночасно. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора. -block.powerlasercorner.name = Лазерний кут -block.powerlasercorner.fulldescription = Лазер, який розподіляє енергію одночасно на два напрямки. Корисно в ситуаціях, коли потрібно живити кілька блоків від одного генератора, а маршрутизатор неточний. -block.teleporter.name = Телепорт -block.teleporter.fulldescription = Продвинутий блок транспортування предметів.Щоб телепортувати предмети з одного місця в інше потрібно збудувати 2 телепорти і назначити на них одинаковий колір. Використовує енергію. Натисніть, щоб змінити колір. -block.sorter.name = Сортувальник -block.sorter.fulldescription = Сортує предмети за типом матеріалу. Матеріал для прийняття позначається кольором у блоці. Всі елементи, що відповідають матеріалу сортування, виводяться вперед, а все інше виводить ліворуч і праворуч. -block.core.name = Ядро -block.pump.name = Насос -block.pump.fulldescription = Насоси рідини з вихідного блоку - зазвичай вода, лава чи олія. Виводить рідину в сусідні трубопроводи. -block.fluxpump.name = Флюсовий насос -block.fluxpump.fulldescription = Розширений варіант насоса. Зберігає більше рідини та перекачує швидше. -block.smelter.name = Плавильня -block.smelter.fulldescription = Основний ремісничий блок. Коли вводиться 1 залізо та 1 вугілля в якості палива, виводить одну сталь. Рекомендується вводити залізо та вугілля на різних поясах, щоб запобігти засміченню. -block.crucible.name = Тигель -block.crucible.fulldescription = Розширений блок обробки. При введенні 1 титану, 1 сталі та 1 вугілля в якості пального, виводить один дирний. Рекомендується вводити вугілля, сталь та титан на різних поясах, щоб запобігти засміченню. -block.coalpurifier.name = вугільний екстрактор -block.coalpurifier.fulldescription = Основний екстрактор. Виходить вугілля при постачанні великої кількості води та каменю. -block.titaniumpurifier.name = Титановий екстрактор -block.titaniumpurifier.fulldescription = Стандартний блок екстрактора. Виходить титан при постачанні великої кількості води та заліза. -block.oilrefinery.name = Нафтопереробний завод -block.oilrefinery.fulldescription = Очищує велику кількість нафти і перетворює на вугілля. Корисний для заправки вугільних башточок, коли вугільні родовища є дефіцитними. -block.stoneformer.name = Кам'янний екстрактор -block.stoneformer.fulldescription = Здавлюється рідка лава в камінь. Корисно для виготовлення великої кількості каменю для очищувачів вугілля. -block.lavasmelter.name = Лавовий завод -block.lavasmelter.fulldescription = Використовує лаву для перетворення залізо на сталь. Альтернатива плавильні. Корисно в ситуаціях, коли вугілля є дефіцитним. -block.stonedrill.name = Кам'янна свердловина -block.stonedrill.fulldescription = Основна свердловина.Розміщюється на кам'яній плитці виводить камінь повільними темпами. -block.irondrill.name = Залізна свердловина -block.irondrill.fulldescription = Базова свердловина.Розміщюється на родовищі залізної руди, випускає залізо в повільному темпі. -block.coaldrill.name = Вугільна свердловина -block.coaldrill.fulldescription = Базова свердловина.Розміщюється на родовищі вугільної руди ,видобуває вугілля повільними темпами. -block.uraniumdrill.name = Уранова свердловина -block.uraniumdrill.fulldescription = Продвинута свердловина. Розміщюється на родовищі уранової руди.Видобуток урану відбувається повільними темпами. -block.titaniumdrill.name = Титанова свердловина -block.titaniumdrill.fulldescription = Продвинута свердловина.Розміщюється на родовищі титанової руди, Видобуток титану відбувається повільним темпом. -block.omnidrill.name = Убер свердловина -block.omnidrill.fulldescription = Кінцева свердловина.Дуже швидко видобуває будь-який вид руди. -block.coalgenerator.name = Вугільний генератор -block.coalgenerator.fulldescription = Основний генератор. Генерує енергію з вугілля. Виводиться потужність лазерів на 4 сторони. -block.thermalgenerator.name = Теплогенератор -block.thermalgenerator.fulldescription = Генерує енергію від лави. Виводиться потужність лазерів на 4 сторони. -block.combustiongenerator.name = Генератор горіння -block.combustiongenerator.fulldescription = Генерує енергію з нафти. Виводиться потужність лазерів на 4 сторони. -block.rtgenerator.name = RTG генератор -block.rtgenerator.fulldescription = Генерує невелику кількість енергії з радіоактивного розпаду урану. Виводиться потужність лазерів на 4 сторони. -block.nuclearreactor.name = Ядерний реактор -block.nuclearreactor.fulldescription = Розширений варіант RTG Generator і кінцевий генератор електроенергії. Генерує енергію з урану. Потребує постійного водяного охолодження.Сильно вибухне якщо не буде постачання води у великії кількості -block.turret.name = Турель -block.turret.fulldescription = Базова, дешева турель. Використовує камінь для боєприпасів. Має трохи більше діапазону, ніж подвійна турель. -block.doubleturret.name = Подвійна турель -block.doubleturret.fulldescription = Дещо потужна версія турель. Використовує камінь для боєприпасів. Значно більший урон, але менший діапазон. Вистрілює дві кулі. -block.machineturret.name = Кулеметна турель -block.machineturret.fulldescription = Стандартна всеосяжна турель. Використовує залізо для боєприпасів. Має швидку швидкість пострілу і гідну шкоду. -block.shotgunturret.name = Розріджуюча турель -block.shotgunturret.fulldescription = Стандартна турель. Використовує залізо для боєприпасів. Вистрілює 7 куль навколо себе.Наносить значних ушкоджень,звісно якщо поцілить :) -block.flameturret.name = Вогнемет -block.flameturret.fulldescription = Продвинута турель ближнього діапазону. Використовує вугілля для боєприпасів. Має дуже низький радіус, але дуже високий збиток. Добре для близьких дистанцій. Рекомендується використовувати за стінами. -block.sniperturret.name = Лазерна турель. -block.sniperturret.fulldescription = Продвинута далекобійна турель. Використовує сталь для боєприпасів. Дуже високий збиток, але низький рівень урону. Дорогі для використання, але можуть бути розташовані далеко від ліній ворога через його радіус. -block.mortarturret.name = Флак турель -block.mortarturret.fulldescription = Продвинута,неточна турель. Використовує вугілля для боєприпасів. Стріляє кулями, що вибухають у шрапнеллв. Корисне для великих натовпів ворогів. -block.laserturret.name = Лазерна турель -block.laserturret.fulldescription = Продвинута однопушечна турель. Використовує енергію. Хороша на середніх дистанціях. Ніколи не пропускає. -block.waveturret.name = Тесла -block.waveturret.fulldescription = Передова багатоцільова турель. Використовує енергію. Середній радіус. Ніколи не пропускає. Активно знижує, але може вражати декількох ворогів одночасно з ланцюговим освітленням. -block.plasmaturret.name = Плазмова турель -block.plasmaturret.fulldescription = Дуже продвинута версія Вогнеметної турелі. Використовує вугілля як боєприпаси. Дуже високий урон, від близької до середньої дистанції. -block.chainturret.name = Уранова турель -block.chainturret.fulldescription = Остаточна швидкістна вежа. Використовує уран як боєприпаси. Вистрілює великі кулі при високій швидкості вогню. Середній радіус. Промінь кілька плиток. Надзвичайно жорсткий. -block.titancannon.name = Титанова гармата -block.titancannon.fulldescription = Найбільш далекобійна турель. Використовує уран як боєприпаси. Вистрілює великі снаряди. Далекобійний. Промінь кілька плиток. Надзвичайно жорсткий. -block.playerspawn.name = Спавн Гравця -block.enemyspawn.name = Спавн ворогів +text.quit=Вийти +text.about.button=Про +text.name=Назва: +text.players={0} гравців онлайн +text.players.single={0} гравців онлайн +text.server.mismatch=Пакетна помилка: невідповідність версії версії клієнта / сервера. Переконайтеся, що ви та хост мають останню версію Mindustry! +text.server.closing=[accent] Закриття сервера ... +text.server.kicked.kick=Ви були вигнані з сервера! +text.server.kicked.invalidPassword=Невірний пароль! +text.server.kicked.clientOutdated=Застарілий клієнт! Оновіть свою гру! +text.server.kicked.serverOutdated=Застарілий сервер! Попросіть хост оновити! +text.server.connected={0} приєднався. +text.server.disconnected={0} від'єднано. +text.nohost=Неможливо розмістити сервер на власній карті! +text.hostserver=Хост-сервер +text.host=Хост +text.hosting=[accent] Відкриття сервера ... +text.hosts.refresh=Оновити +text.hosts.discovering=Знайомство з мережевими іграми +text.server.refreshing=Оновити сервери +text.hosts.none=[lightgray] Ніяких ігор у мережі не знайдено! +text.host.invalid=[scarlet] Неможливо підключитися до хосту. +text.server.friendlyfire=Дружній вогонь +text.server.add=Додати сервер +text.server.delete=Ви впевнені, що хочете видалити цей сервер? +text.server.hostname=Хост: {0} +text.server.edit=Редагувати сервер +text.joingame.title=Приєднатися до гри +text.joingame.ip=IP +text.disconnect=Роз'єднано +text.connecting=[accent] Підключення ... +text.connecting.data=[accent] Завантаження світових даних ... +text.connectfail=[crimson] Не вдалося підключитися до сервера: [orange] {0} +text.server.port=Порт +text.server.addressinuse=Адреса вже використовується! +text.server.invalidport=Недійсний номер порту. +text.server.error=[crimson] Помилка хостингу сервера: [orange] {0} +text.save.new=Нове збереження +text.save.overwrite=Ви впевнені, що хочете перезаписати цей слот для збереження? +text.overwrite=Перезаписати +text.save.none=Не знайдено жодних збережень! +text.saveload=[accent] Збереження ... +text.savefail=Не вдалося зберегти гру! +text.save.delete.confirm=Ви впевнені, що хочете видалити це збереження? +text.save.delete=Видалити +text.save.export=Експорт збереження +text.save.import.invalid=[orange] Це збереження недійсне! +text.save.import.fail=[crimson] Не вдалося імпортувати збереження: [orange] {0} +text.save.export.fail=[crimson] Не вдалося експортувати збереження: [orange] {0} +text.save.import=Імпортувати збереження +text.save.newslot=Назва збереження: +text.save.rename=Переіменувати +text.save.rename.text=Нова назва: +text.selectslot=Виберіть збереження. +text.slot=[accent] слот {0} +text.save.corrupted=[orange] Збережений файл пошкоджений або він невірний! +text.empty=<порожньо> +text.on=Увімкнути +text.off=Вимкнути +text.save.autosave=Автозбереження: {0} +text.save.map=Карта +text.save.wave=Хвиля {0} +text.save.difficulty=Складність +text.save.date=Останнє збережено: {0} +text.confirm=Підтвердити +text.delete=Видалити +text.ok=ОК +text.open=Відкрити +text.cancel=Скасувати +text.openlink=Відкрити посилання +text.back=Назад +text.quit.confirm=Ти впевнений що хочеш піти? +text.loading=[accent] Завантаження ... +text.wave=[orange] хвиля {0} +text.wave.waiting=Хвиля через {0} +text.waiting=Очікування… +text.enemies={0} Вороги +text.enemies.single=Противник +text.loadimage=Завантажити зображення +text.saveimage=Зберегти зображення +text.editor.badsize=[orange] Недійсні розміри зображення! [] Дійсні розміри карти: {0} +text.editor.errorimageload=Помилка завантаження файлу зображень: [orange] {0} +text.editor.errorimagesave=Помилка збереження файлу зображення: [orange] {0} +text.editor.generate=Генератор +text.editor.resize=Змінити розмір +text.editor.loadmap=// Завантажити карту +text.editor.savemap=Зберегти карту +text.editor.loadimage=Завантажити зображення +text.editor.saveimage=Зберегти зображення +text.editor.unsaved=[scarlet] У вас є незбережені зміни! [] Ви впевнені, що хочете вийти? +text.editor.resizemap=Змінити розмір карти +text.editor.mapname=Назва карти: +text.editor.overwrite=[accent] Попередження! Це перезаписує існуючу карту. +text.editor.selectmap=Виберіть карту для завантаження: +text.width=Ширина +text.height=Висота +text.menu=Меню +text.play=Відтворити +text.load=Завантаження +text.save=Зберегти +text.language.restart=Будь ласка, перезапустіть свою гру, щоб налаштування мови набули чинності. +text.settings.language=Мова +text.settings=Налаштування +text.tutorial=Навчальний\nпосібник +text.editor=Редактор +text.mapeditor=Редактор карт +text.donate=Підтримати проект +text.settings.reset=Скинути до стандартних +text.settings.controls=Елементи управління +text.settings.game=Гра +text.settings.sound=Звук +text.settings.graphics=Графіка +text.upgrades=Оновлення +text.purchased=[LIME] Створено! +text.weapons=Зброя +text.paused=Пауза +text.info.title=[accent] інформація +text.error.title=[crimson] Виникла помилка +text.error.crashtitle=Виникла помилка +text.blocks.blockinfo=Блокування інформації +text.blocks.powercapacity=Потужність +text.blocks.powershot=Потужність / постріл +text.blocks.size=Розмір +text.blocks.liquidcapacity=Ємкість рідини +text.blocks.maxitemssecond=Макс. Елементи / секунду +text.blocks.powerrange=Радіус потужності +text.blocks.itemcapacity=Ємкість предмету +text.blocks.inputliquid=Ввід речовини +text.blocks.inputitem=Вхідний матеріал +text.blocks.explosive=Вибухонебезпечний! +text.blocks.health=Здоров'я +text.blocks.inaccuracy=Неточність +text.blocks.shots=Постріли +text.blocks.inputcapacity=Вхідна ємність +text.blocks.outputcapacity=Випускна ємність +setting.difficulty.easy=Легкий +setting.difficulty.normal=Нормальний +setting.difficulty.hard=Важкий +setting.difficulty.insane=Божевільний +setting.difficulty.purge=Очистити +setting.difficulty.name=Складність +setting.screenshake.name=Тряска екрана +setting.indicators.name=Індикатори ворога +setting.effects.name=Ефекти відображення +setting.sensitivity.name=Чутливість контролера +setting.saveinterval.name=Інтервал автозбереження +setting.seconds={0} секунд +setting.fullscreen.name=Повноекранний +setting.multithread.name=Багатопотоковий [scarlet] (нестабільний!) +setting.fps.name=Показати FPS +setting.vsync.name=VSunc +setting.lasers.name=Показати енергетичні лазери +setting.healthbars.name=Показати здоров'я +setting.musicvol.name=Гучність музики +setting.mutemusic.name=Вимкнути музику +setting.sfxvol.name=Гучність ефектів +setting.mutesound.name=Вимкнути звук +map.maze.name=Лабіринт +map.fortress.name=Фортеця +map.sinkhole.name=Свердловина +map.caves.name=Печери +map.volcano.name=Вулкан +map.caldera.name=Кальдера +map.scorch.name=Мертва земля +map.desert.name=Пустеля +map.island.name=Острів +map.grassland.name=Пасовища +map.tundra.name=Тундра +map.spiral.name=Спіраль +map.tutorial.name=Навчання +text.keybind.title=Ключ перемотки +keybind.move_x.name=move_x +keybind.move_y.name=move_y +keybind.select.name=Вибрати +keybind.break.name={0}break{/0}{1}; {/1} +keybind.shoot.name=Постріл +keybind.zoom_hold.name=zoom_hold +keybind.zoom.name=Збільшити +keybind.block_info.name=Інформація про блок +keybind.menu.name=Меню +keybind.pause.name=Пауза +keybind.dash.name=Тире +keybind.chat.name=Чат +keybind.player_list.name=Список гравців +keybind.console.name=// Консоль 1 +keybind.rotate_alt.name=rotate_alt +keybind.rotate.name=Повернути +mode.waves.name=Хвилі +mode.sandbox.name=Пісочниця +mode.freebuild.name=Вільний режим +item.stone.name=Камінь +item.coal.name=Вугівалля +item.titanium.name=Титан +item.thorium.name=Уран +item.sand.name=Пісок +liquid.water.name=Вода +liquid.lava.name=Лава +liquid.oil.name=Нафта +block.door.name=Двері +block.door-large.name=Великі двері +block.conduit.name=Трубопровід +block.pulseconduit.name=Імпульсний канал +block.liquidrouter.name=маршрутизатор для рідини +block.conveyor.name=Конвеєр +block.router.name=Маршрутизатор +block.junction.name=Міст +block.liquidjunction.name=Міст для рідини +block.sorter.name=Сортувальник +block.smelter.name=Плавильня +text.credits=Credits +text.link.discord.description=the official Mindustry discord chatroom +text.link.github.description=Game source code +text.link.dev-builds.description=Unstable development builds +text.link.trello.description=Official trello board for planned features +text.link.itch.io.description=itch.io page with PC downloads and web version +text.link.google-play.description=Google Play store listing +text.link.wiki.description=official Mindustry wiki +text.linkfail=Failed to open link!\nThe URL has been copied to your cliboard. +text.editor.web=The web version does not support the editor!\nDownload the game to use it. +text.web.unsupported=The web version does not support this feature! Download the game to use it. +text.multiplayer.web=This version of the game does not support multiplayer!\nTo play multiplayer from your browser, use the "multiplayer web version" link at the itch.io page. +text.host.web=The web version does not support hosting games! Download the game to use this feature. +text.map.delete=Are you sure you want to delete the map "[orange]{0}[]"? +text.construction.title=Block Construction Guide +text.construction=You've just selected [accent]block construction mode[].\n\nTo begin placing, simply tap a valid location near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Shift the selection[] by holding and dragging any block in the selection.\n- [accent]Place blocks in a line[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel construction or selection[] by pressing the X at the bottom left. +text.deconstruction.title=Block Deconstruction Guide +text.deconstruction=You've just selected [accent]block deconstruction mode[].\n\nTo begin breaking, simply tap a block near your ship.\nOnce you have selected some blocks, press the checkbox to confirm, and your ship will begin de-constructing them.\n\n- [accent]Remove blocks[] from your selection by tapping them.\n- [accent]Remove blocks in an area[] by tapping and holding an empty spot, then dragging in a direction.\n- [accent]Cancel deconstruction or selection[] by pressing the X at the bottom left. +text.showagain=Don't show again next session +text.unlocks=Unlocks +text.addplayers=Add/Remove Players +text.maps=Maps +text.maps.none=[LIGHT_GRAY]No maps found! +text.unlocked=New Block Unlocked! +text.unlocked.plural=New Blocks Unlocked! +text.server.kicked.fastShoot=You are shooting too quickly. +text.server.kicked.banned=You are banned on this server. +text.server.kicked.recentKick=You have been kicked recently.\nWait before connecting again. +text.server.kicked.nameInUse=There is someone with that name\nalready on this server. +text.server.kicked.nameEmpty=Your name must contain at least one character or number. +text.server.kicked.idInUse=You are already on this server! Connecting with two accounts is not permitted. +text.server.kicked.customClient=This server does not support custom builds. Download an official version. +text.host.info=The [accent]host[] button hosts a server on ports [scarlet]6567[] and [scarlet]6568.[]\nAnybody on the same [LIGHT_GRAY]wifi or local network[] should be able to see your server in their server list.\n\nIf you want people to be able to connect from anywhere by IP, [accent]port forwarding[] is required.\n\n[LIGHT_GRAY]Note: If someone is experiencing trouble connecting to your LAN game, make sure you have allowed Mindustry access to your local network in your firewall settings. +text.join.info=Here, you can enter a [accent]server IP[] to connect to, or discover [accent]local network[] servers to connect to.\nBoth LAN and WAN multiplayer is supported.\n\n[LIGHT_GRAY]Note: There is no automatic global server list; if you want to connect to someone by IP, you would need to ask the host for their IP. +text.trace=Trace Player +text.trace.playername=Player name: [accent]{0} +text.trace.ip=IP: [accent]{0} +text.trace.id=Unique ID: [accent]{0} +text.trace.android=Android Client: [accent]{0} +text.trace.modclient=Custom Client: [accent]{0} +text.trace.totalblocksbroken=Total blocks broken: [accent]{0} +text.trace.structureblocksbroken=Structure blocks broken: [accent]{0} +text.trace.lastblockbroken=Last block broken: [accent]{0} +text.trace.totalblocksplaced=Total blocks placed: [accent]{0} +text.trace.lastblockplaced=Last block placed: [accent]{0} +text.invalidid=Invalid client ID! Submit a bug report. +text.server.bans=Bans +text.server.bans.none=No banned players found! +text.server.admins=Admins +text.server.admins.none=No admins found! +text.server.outdated=[crimson]Outdated Server![] +text.server.outdated.client=[crimson]Outdated Client![] +text.server.version=[lightgray]Version: {0} +text.server.custombuild=[yellow]Custom Build +text.confirmban=Are you sure you want to ban this player? +text.confirmunban=Are you sure you want to unban this player? +text.confirmadmin=Are you sure you want to make this player an admin? +text.confirmunadmin=Are you sure you want to remove admin status from this player? +text.disconnect.data=Failed to load world data! +text.copylink=Copy Link +text.changelog.title=Changelog +text.changelog.loading=Getting changelog... +text.changelog.error.android=[orange]Note that the changelog sometimes does not work on Android 4.4 and below!\nThis is due to an internal Android bug. +text.changelog.error.ios=[orange]The changelog is currently not supported in iOS. +text.changelog.error=[scarlet]Error getting changelog!\nCheck your internet connection. +text.changelog.current=[yellow][[Current version] +text.changelog.latest=[orange][[Latest version] +text.saving=[accent]Saving... +text.unknown=Unknown +text.custom=Custom +text.builtin=Built-In +text.map.delete.confirm=Are you sure you want to delete this map? This action cannot be undone! +text.map.random=[accent]Random Map +text.map.nospawn=This map does not have any cores for the player to spawn in! Add a [ROYAL]blue[] core to this map in the editor. +text.editor.slope=\\ +text.editor.openin=Open In Editor +text.editor.oregen=Ore Generation +text.editor.oregen.info=Ore Generation: +text.editor.mapinfo=Map Info +text.editor.author=Author: +text.editor.description=Description: +text.editor.name=Name: +text.editor.teams=Teams +text.editor.elevation=Elevation +text.editor.saved=Saved! +text.editor.save.noname=Your map does not have a name! Set one in the 'map info' menu. +text.editor.save.overwrite=Your map overwrites a built-in map! Pick a different name in the 'map info' menu. +text.editor.import.exists=[scarlet]Unable to import:[] a built-in map named '{0}' already exists! +text.editor.import=Import... +text.editor.importmap=Import Map +text.editor.importmap.description=Import an already existing map +text.editor.importfile=Import File +text.editor.importfile.description=Import an external map file +text.editor.importimage=Import Terrain Image +text.editor.importimage.description=Import an external map image file +text.editor.export=Export... +text.editor.exportfile=Export File +text.editor.exportfile.description=Export a map file +text.editor.exportimage=Export Terrain Image +text.editor.exportimage.description=Export a map image file +text.editor.overwrite.confirm=[scarlet]Warning![] A map with this name already exists. Are you sure you want to overwrite it? +text.fps=FPS: {0} +text.tps=TPS: {0} +text.ping=Ping: {0}ms +text.settings.rebind=Rebind +text.yes=Yes +text.no=No +text.blocks.targetsair=Targets Air +text.blocks.itemspeed=Units Moved +text.blocks.shootrange=Range +text.blocks.poweruse=Power Use +text.blocks.inputitemcapacity=Input Item Capacity +text.blocks.outputitemcapacity=Input Item Capacity +text.blocks.maxpowergeneration=Max Power Generation +text.blocks.powertransferspeed=Power Transfer +text.blocks.craftspeed=Production Speed +text.blocks.inputliquidaux=Aux Liquid +text.blocks.inputitems=Input Items +text.blocks.outputitem=Output Item +text.blocks.drilltier=Drillables +text.blocks.drillspeed=Base Drill Speed +text.blocks.liquidoutput=Liquid Output +text.blocks.liquiduse=Liquid Use +text.blocks.coolant=Coolant +text.blocks.coolantuse=Coolant Use +text.blocks.inputliquidfuel=Fuel Liquid +text.blocks.liquidfueluse=Liquid Fuel Use +text.blocks.reload=Reload +text.blocks.inputfuel=Fuel +text.blocks.fuelburntime=Fuel Burn Time +text.unit.blocks=blocks +text.unit.powersecond=power units/second +text.unit.liquidsecond=liquid units/second +text.unit.itemssecond=items/second +text.unit.pixelssecond=pixels/second +text.unit.liquidunits=liquid units +text.unit.powerunits=power units +text.unit.degrees=degrees +text.unit.seconds=seconds +text.unit.none= +text.unit.items=items +text.category.general=General +text.category.power=Power +text.category.liquids=Liquids +text.category.items=Items +text.category.crafting=Crafting +text.category.shooting=Shooting +setting.minimap.name=Show Minimap +mode.text.help.title=Description of modes +mode.waves.description=the normal mode. limited resources and automatic incoming waves. +mode.sandbox.description=infinite resources and no timer for waves. +mode.freebuild.description=limited resources and no timer for waves. +content.item.name=Items +content.liquid.name=Liquids +content.unit-type.name=Units +content.recipe.name=Blocks +item.stone.description=A common raw material. Used for separating and refining into other materials, or melting into lava. +item.tungsten.name=Tungsten +item.tungsten.description=A common, but very useful structure material. Used in drills and heat-resistant blocks such as generators and smelteries. +item.lead.name=Lead +item.lead.description=A basic starter material. Used extensively in electronics and liquid transportation blocks. +item.coal.description=A common and readily available fuel. +item.carbide.name=Carbide +item.carbide.description=A tough alloy made with tungsten and carbon. Used in advanced transportation blocks and high-tier drills. +item.titanium.description=A rare super-light metal used extensively in liquid transportation, drills and aircraft. +item.thorium.description=A dense, radioactive metal used as structural support and nuclear fuel. +item.silicon.name=Silicon +item.silcion.description=An extremely useful semiconductor, with applications in solar panels and many complex electronics. +item.plastanium.name=Plastanium +item.plastanium.description=A light, ductile material used in advanced aircraft and fragmentation ammunition. +item.phase-matter.name=Phase Matter +item.surge-alloy.name=Surge Alloy +item.biomatter.name=Biomatter +item.biomatter.description=A clump of organic mush; used for conversion into oil or as a basic fuel. +item.sand.description=A common material that is used extensively in smelting, both in alloying and as a flux. +item.blast-compound.name=Blast Compound +item.blast-compound.description=A volatile compound used in bombs and explosives. While it can burned as fuel, this is not advised. +item.pyratite.name=Pyratite +item.pyratite.description=An extremely flammable substance used in incendiary weapons. +liquid.cryofluid.name=Cryofluid +text.item.explosiveness=[LIGHT_GRAY]Explosiveness: {0} +text.item.flammability=[LIGHT_GRAY]Flammability: {0} +text.item.radioactivity=[LIGHT_GRAY]Radioactivity: {0} +text.item.fluxiness=[LIGHT_GRAY]Flux Power: {0} +text.item.hardness=[LIGHT_GRAY]Hardness: {0} +text.liquid.heatcapacity=[LIGHT_GRAY]Heat Capacity: {0} +text.liquid.viscosity=[LIGHT_GRAY]Viscosity: {0} +text.liquid.temperature=[LIGHT_GRAY]Temperature: {0} +block.tungsten-wall.name=Tungsten Wall +block.tungsten-wall-large.name=Large Tungsten Wall +block.carbide-wall.name=Carbide Wall +block.carbide-wall-large.name=Large Carbide Wall +block.thorium-wall.name=Thorium Wall +block.thorium-wall-large.name=Large Thorium Wall +block.duo.name=Duo +block.scorch.name=Scorch +block.hail.name=Hail +block.lancer.name=Lancer +block.titanium-conveyor.name=Titanium Conveyor +block.splitter.name=Splitter +block.splitter.description=Outputs items into two opposite directions immediately after they are recieved. +block.router.description=Splits items into all 4 directions. Can store items as a buffer. +block.distributor.name=Distributor +block.distributor.description=A splitter that can split items into 8 directions. +block.sorter.description=Sorts items. If an item matches the selection, it is allowed to pass. Otherwise, the item is outputted to the left and right. +block.overflow-gate.name=Overflow Gate +block.overflow-gate.description=A combination splitter and router that only outputs to the left and right if the front path is blocked. +block.bridgeconveyor.name=Bridge Conveyor +block.bridgeconveyor.description=A conveyor that can go over other blocks, for up to two total blocks. +block.arc-smelter.name=Arc Smelter +block.silicon-smelter.name=Silicon Smelter +block.phase-weaver.name=Phase Weaver +block.pulverizer.name=Pulverizer +block.cryofluidmixer.name=Cryofluid Mixer +block.melter.name=Melter +block.incinerator.name=Incinerator +block.biomattercompressor.name=Biomatter Compressor +block.separator.name=Separator +block.centrifuge.name=Centrifuge +block.power-node.name=Power Node +block.power-node-large.name=Large Power Node +block.battery.name=Battery +block.battery-large.name=Large Battery +block.combustion-generator.name=Combustion Generator +block.turbine-generator.name=Turbine Generator +block.tungsten-drill.name=Tungsten Drill +block.carbide-drill.name=Carbide Drill +block.laser-drill.name=Laser Drill +block.water-extractor.name=Water Extractor +block.cultivator.name=Cultivator +block.dart-ship-factory.name=Dart Ship Factory +block.delta-mech-factory.name=Delta Mech Factory +block.dronefactory.name=Drone Factory +block.repairpoint.name=Repair Point +block.resupplypoint.name=Resupply Point +block.liquidtank.name=Liquid Tank +block.bridgeconduit.name=Bridge Conduit +block.mechanical-pump.name=Mechanical Pump +block.itemsource.name=Item Source +block.itemvoid.name=Item Void +block.liquidsource.name=Liquid Source +block.powervoid.name=Power Void +block.powerinfinite.name=Power Infinite +block.unloader.name=Unloader +block.sortedunloader.name=Sorted Unloader +block.vault.name=Vault +block.wave.name=Wave +block.swarmer.name=Swarmer +block.salvo.name=Salvo +block.ripple.name=Ripple +block.phase-conveyor.name=Phase Conveyor +block.bridge-conveyor.name=Bridge Conveyor +block.plastanium-compressor.name=Plastanium Compressor +block.pyratite-mixer.name=Pyratite Mixer +block.blast-mixer.name=Blast Mixer +block.solidifer.name=Solidifer +block.solar-panel.name=Solar Panel +block.solar-panel-large.name=Large Solar Panel +block.oil-extractor.name=Oil Extractor +block.javelin-ship-factory.name=Javelin Ship factory +block.drone-factory.name=Drone Factory +block.fabricator-factory.name=Fabricator Factory +block.repair-point.name=Repair Point +block.resupply-point.name=Resupply Point +block.pulse-conduit.name=Pulse Conduit +block.phase-conduit.name=Phase Conduit +block.liquid-router.name=Liquid Router +block.liquid-tank.name=Liquid Tank +block.liquid-junction.name=Liquid Junction +block.bridge-conduit.name=Bridge Conduit +block.rotary-pump.name=Rotary Pump +block.nuclear-reactor.name=Nuclear Reactor +text.save.old=This save is for an older version of the game, and can no longer be used.\n\n[LIGHT_GRAY]Save backwards compatibility will be implemented in the full 4.0 release. diff --git a/core/assets/cursors/drill.png b/core/assets/cursors/drill.png new file mode 100644 index 0000000000..e524b82762 Binary files /dev/null and b/core/assets/cursors/drill.png differ diff --git a/core/assets/cursors/unload.png b/core/assets/cursors/unload.png new file mode 100644 index 0000000000..2340e6d94e Binary files /dev/null and b/core/assets/cursors/unload.png differ diff --git a/core/assets/maps/arena.png b/core/assets/maps/arena.png deleted file mode 100644 index 9755ab4f8a..0000000000 Binary files a/core/assets/maps/arena.png and /dev/null differ diff --git a/core/assets/maps/blankmap.png b/core/assets/maps/blankmap.png deleted file mode 100644 index 52073f2a27..0000000000 Binary files a/core/assets/maps/blankmap.png and /dev/null differ diff --git a/core/assets/maps/caldera.png b/core/assets/maps/caldera.png deleted file mode 100644 index c10907aadb..0000000000 Binary files a/core/assets/maps/caldera.png and /dev/null differ diff --git a/core/assets/maps/canyon.png b/core/assets/maps/canyon.png deleted file mode 100644 index 7240a5a5f5..0000000000 Binary files a/core/assets/maps/canyon.png and /dev/null differ diff --git a/core/assets/maps/caves.png b/core/assets/maps/caves.png deleted file mode 100644 index 1fc4865796..0000000000 Binary files a/core/assets/maps/caves.png and /dev/null differ diff --git a/core/assets/maps/delta.png b/core/assets/maps/delta.png deleted file mode 100644 index c04d38eb49..0000000000 Binary files a/core/assets/maps/delta.png and /dev/null differ diff --git a/core/assets/maps/desert.png b/core/assets/maps/desert.png deleted file mode 100644 index 99488439a3..0000000000 Binary files a/core/assets/maps/desert.png and /dev/null differ diff --git a/core/assets/maps/fortress.png b/core/assets/maps/fortress.png deleted file mode 100644 index 6b977c13de..0000000000 Binary files a/core/assets/maps/fortress.png and /dev/null differ diff --git a/core/assets/maps/grassland.png b/core/assets/maps/grassland.png deleted file mode 100644 index 1c7e8060fe..0000000000 Binary files a/core/assets/maps/grassland.png and /dev/null differ diff --git a/core/assets/maps/island.png b/core/assets/maps/island.png deleted file mode 100644 index 1e5d911afc..0000000000 Binary files a/core/assets/maps/island.png and /dev/null differ diff --git a/core/assets/maps/maps.json b/core/assets/maps/maps.json deleted file mode 100644 index 6150de5f22..0000000000 --- a/core/assets/maps/maps.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "maps": [ - { - "id": 0, - "name": "maze", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 1, - "name": "fortress", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 2, - "name": "sinkhole", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 3, - "name": "caves", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 4, - "name": "volcano", - "visible": true, - "flipBase": true, - "backgroundColor": "646464" - }, - { - "id": 5, - "name": "caldera", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 6, - "name": "scorch", - "visible": true, - "flipBase": false, - "backgroundColor": "e5d8bb" - }, - { - "id": 7, - "name": "desert", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 8, - "name": "island", - "visible": true, - "flipBase": false, - "backgroundColor": "e5d8bb" - }, - { - "id": 9, - "name": "grassland", - "visible": true, - "flipBase": false, - "backgroundColor": "5ab464" - }, - { - "id": 10, - "name": "tundra", - "visible": true, - "flipBase": false, - "backgroundColor": "646464" - }, - { - "id": 11, - "name": "spiral", - "visible": true, - "flipBase": false, - "backgroundColor": "f7feff" - }, - { - "id": 12, - "name": "tutorial", - "visible": false, - "flipBase": false, - "backgroundColor": "646464" - } - ] -} diff --git a/core/assets/maps/maze.png b/core/assets/maps/maze.png deleted file mode 100644 index 018f94a1e0..0000000000 Binary files a/core/assets/maps/maze.png and /dev/null differ diff --git a/core/assets/maps/oilrush.png b/core/assets/maps/oilrush.png deleted file mode 100644 index 43d4e2f851..0000000000 Binary files a/core/assets/maps/oilrush.png and /dev/null differ diff --git a/core/assets/maps/pit.png b/core/assets/maps/pit.png deleted file mode 100644 index 58cc2fcc29..0000000000 Binary files a/core/assets/maps/pit.png and /dev/null differ diff --git a/core/assets/maps/scorch.png b/core/assets/maps/scorch.png deleted file mode 100644 index 6ad8e95514..0000000000 Binary files a/core/assets/maps/scorch.png and /dev/null differ diff --git a/core/assets/maps/sinkhole.png b/core/assets/maps/sinkhole.png deleted file mode 100644 index ebaffda376..0000000000 Binary files a/core/assets/maps/sinkhole.png and /dev/null differ diff --git a/core/assets/maps/spiral.png b/core/assets/maps/spiral.png deleted file mode 100644 index dfe43fcbb4..0000000000 Binary files a/core/assets/maps/spiral.png and /dev/null differ diff --git a/core/assets/maps/test.mmap b/core/assets/maps/test.mmap new file mode 100644 index 0000000000..ab8f0627af Binary files /dev/null and b/core/assets/maps/test.mmap differ diff --git a/core/assets/maps/test1.png b/core/assets/maps/test1.png deleted file mode 100644 index 88ab432825..0000000000 Binary files a/core/assets/maps/test1.png and /dev/null differ diff --git a/core/assets/maps/test2.png b/core/assets/maps/test2.png deleted file mode 100644 index 0596dd6a00..0000000000 Binary files a/core/assets/maps/test2.png and /dev/null differ diff --git a/core/assets/maps/test3.png b/core/assets/maps/test3.png deleted file mode 100644 index 8209a06331..0000000000 Binary files a/core/assets/maps/test3.png and /dev/null differ diff --git a/core/assets/maps/tundra.png b/core/assets/maps/tundra.png deleted file mode 100644 index b64cc2b19f..0000000000 Binary files a/core/assets/maps/tundra.png and /dev/null differ diff --git a/core/assets/maps/tutorial.png b/core/assets/maps/tutorial.png deleted file mode 100644 index 88b8a8af04..0000000000 Binary files a/core/assets/maps/tutorial.png and /dev/null differ diff --git a/core/assets/maps/volcano.png b/core/assets/maps/volcano.png deleted file mode 100644 index dc4f6ba866..0000000000 Binary files a/core/assets/maps/volcano.png and /dev/null differ diff --git a/core/assets/shaders/blockbuild.fragment b/core/assets/shaders/blockbuild.fragment new file mode 100644 index 0000000000..f79b58b3c0 --- /dev/null +++ b/core/assets/shaders/blockbuild.fragment @@ -0,0 +1,63 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform float u_progress; +uniform float u_time; + +varying vec4 v_color; +varying vec2 v_texCoord; + + +bool id(vec2 coords, vec4 base){ + vec4 target = texture2D(u_texture, coords); + return target.a < 0.1 || (coords.x < u_uv.x || coords.y < u_uv.y || coords.x > u_uv2.x || coords.y > u_uv2.y); +} + +bool cont(vec2 T, vec2 v){ + float step = 1.0; + vec4 base = texture2D(u_texture, T); + return base.a > 0.1 && + (id(T + vec2(0, step) * v, base) || id(T + vec2(0, -step) * v, base) || + id(T + vec2(step, 0) * v, base) || id(T + vec2(-step, 0) * v, base)); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + + vec2 t = v_texCoord.xy; + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + vec2 coords = (v_texCoord-u_uv) / v; + float value = coords.x + coords.y; + + vec4 color = texture2D(u_texture, t); + + vec2 center = ((u_uv + u_uv2)/2.0 - u_uv) /v; + float dst = (abs(center.x - coords.x) + abs(center.y - coords.y))/2.0; + float chance = 1.0; + + if(u_progress > 0.8){ + chance = 1.0-(u_progress-0.8)*5.0; + } + + if((mod(u_time / 1.5 + value, 20.0) < 5.0 && cont(t, v)) && rand(coords) < chance){ + gl_FragColor = u_color; + }else if(dst > (1.0-u_progress) * (center.x)){ + gl_FragColor = color; + }else if((dst + 1.0 > (1.0-u_progress) * (center.x)) && color.a > 0.1 && rand(coords) < chance){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } +} diff --git a/core/assets/shaders/blockpreview.fragment b/core/assets/shaders/blockpreview.fragment new file mode 100644 index 0000000000..0f571d632b --- /dev/null +++ b/core/assets/shaders/blockpreview.fragment @@ -0,0 +1,40 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; + +varying vec4 v_color; +varying vec2 v_texCoord; + +bool id(vec2 coords, vec4 base){ + vec4 target = texture2D(u_texture, coords); + return target.a < 0.1 || (coords.x < u_uv.x || coords.y < u_uv.y || coords.x > u_uv2.x || coords.y > u_uv2.y); +} + +bool cont(vec2 T, vec2 v){ + float step = 1.0; + vec4 base = texture2D(u_texture, T); + return base.a > 0.1 && + (id(T + vec2(0, step) * v, base) || id(T + vec2(0, -step) * v, base) || + id(T + vec2(step, 0) * v, base) || id(T + vec2(-step, 0) * v, base)); +} + +void main() { + vec2 t = v_texCoord.xy; + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + vec2 coord = t / v; + vec4 c = texture2D(u_texture, t); + + if(cont(t, v) ){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } +} diff --git a/core/assets/shaders/build.fragment b/core/assets/shaders/build.fragment new file mode 100644 index 0000000000..4977e782b1 --- /dev/null +++ b/core/assets/shaders/build.fragment @@ -0,0 +1,53 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform float u_time; +uniform float u_progress; +uniform vec4 u_color; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform vec2 u_texsize; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +bool id(vec4 v){ + return v.a > 0.1; +} + +void main() { + vec2 coords = (v_texCoord.xy - u_uv) / (u_uv2 - u_uv); + vec2 t = v_texCoord.xy; + + vec4 c = texture2D(u_texture, v_texCoord.xy); + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + float step = 1.0; + + bool outline = texture2D(u_texture, t).a < 0.1 && + (id(texture2D(u_texture, t + vec2(0, step) * v)) || id(texture2D(u_texture, t + vec2(0, -step) * v)) || + id(texture2D(u_texture, t + vec2(step, 0) * v)) || id(texture2D(u_texture, t + vec2(-step, 0) * v))); + + if(1.0-abs(coords.x - 0.5)*2.0 < 1.0-u_progress){ + c = vec4(0.0); + } + + if(c.a > 0.01 || outline){ + float f = abs(sin(coords.x*2.0 + u_time)); + if(f > 0.9 || (f > 0.7 && rand(t) > 0.6)) + f = 1.0; + else + f = 0.0; + c = mix(c, u_color, f * u_color.a); + } + + gl_FragColor = c * v_color; +} diff --git a/core/assets/shaders/fog.fragment b/core/assets/shaders/fog.fragment new file mode 100644 index 0000000000..8955cc9665 --- /dev/null +++ b/core/assets/shaders/fog.fragment @@ -0,0 +1,22 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +const float round = 0.23; + +varying vec4 v_color; +varying vec2 v_texCoord; + +void main() { + vec4 color = texture2D(u_texture, v_texCoord.xy); + color.a = 1.0 - color.r; + color.rgb = vec3(0.0); + color.a = float(int(color.a / round)) * round; + if(color.a >= 1.0 - round){ + color.a = 1.0; + } + gl_FragColor = color * v_color; +} diff --git a/core/assets/shaders/fullmix.fragment b/core/assets/shaders/fullmix.fragment new file mode 100644 index 0000000000..00ff3997a2 --- /dev/null +++ b/core/assets/shaders/fullmix.fragment @@ -0,0 +1,19 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; +uniform vec4 u_color; + +varying vec4 v_color; +varying vec2 v_texCoord; + +void main() { + + vec4 c = texture2D(u_texture, v_texCoord.xy); + + c = mix(c, vec4(v_color.r, v_color.g, v_color.b, c.a), v_color.a); + + gl_FragColor = c * vec4(v_color.rgb, 1.0); +} diff --git a/core/assets/shaders/inline-blocks.fragment b/core/assets/shaders/inline-blocks.fragment new file mode 100644 index 0000000000..e6b3c94b21 --- /dev/null +++ b/core/assets/shaders/inline-blocks.fragment @@ -0,0 +1,96 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform float u_progress; +uniform float u_time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +const float chunk = 4.0; +const float start = 0.7; +const float end = 0.9; + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float round(float f){ + return float(int(f / chunk)) * chunk; +} + +bool id(vec2 coords, vec4 base, float basediff){ + vec4 target = texture2D(u_texture, coords); + return target.a < 0.1 || (coords.x < u_uv.x || coords.y < u_uv.y || coords.x > u_uv2.x || coords.y > u_uv2.y); +} + +bool cont(vec2 T, vec2 v, float basediff){ + float step = 1.0; + vec4 base = texture2D(u_texture, T); + return base.a > 0.1 && + (id(T + vec2(0, step) * v, base, basediff) || id(T + vec2(0, -step) * v, base, basediff) || + id(T + vec2(step, 0) * v, base, basediff) || id(T + vec2(-step, 0) * v, base, basediff)); +} + +bool complete(vec2 coords){ + vec2 rc = vec2(round(coords.x), round(coords.y)); + float r = clamp(rand(rc) + u_progress, 0.0, 1.0); + float fr = (r-start)*(1.0/(end-start)); + + vec2 next = rc + chunk/2.0; + float rdst = max(abs(coords.x - next.x), abs(coords.y - next.y)); + return rdst / (chunk/2.0) < fr; +} + +void main() { + + vec2 t = v_texCoord.xy; + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + + bool any = false; + + vec2 coords = (v_texCoord-u_uv) / v; + + + float value = coords.x + coords.y; + + vec4 color = texture2D(u_texture, t); + vec2 rc = vec2(round(coords.x), round(coords.y)); + vec2 center = ((u_uv + u_uv2)/2.0 - u_uv) /v; + + float r = clamp(rand(rc) + u_progress, 0.0, 1.0); + + const float scl = 10.0; + float dst = (abs(center.x - coords.x) + abs(center.y - coords.y)) / 2.0; + + if(dst - 1.0 < u_progress * (center.x) && dst> u_progress * (center.x) && color.a > 0.1){ + gl_FragColor = u_color; + }else if(r > end){ + gl_FragColor = color; + }else if((cont(t, v, 100.0) && mod(u_time / 1.5 + value, 20.0) < 5.0 && color.a > 0.1) || + (complete(coords) && (!complete(coords + vec2(1.0, 0.0)) || !complete(coords + vec2(-1.0, 0.0)) || !complete(coords + vec2(0.0, 1.0)) + || !complete(coords + vec2(0.0, -1.0))))){ + gl_FragColor = u_color; + }else if(r > start && color.a > 0.1){ + float fr = (r-start)*(1.0/(end-start)); + + vec2 next = rc + chunk/2.0; + float rdst = max(abs(coords.x - next.x), abs(coords.y - next.y)); + if(rdst / (chunk/2.0) < fr){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } + }else{ + gl_FragColor = vec4(0.0); + } +} diff --git a/core/assets/shaders/inline-contour.fragment b/core/assets/shaders/inline-contour.fragment new file mode 100644 index 0000000000..b7b201d373 --- /dev/null +++ b/core/assets/shaders/inline-contour.fragment @@ -0,0 +1,63 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform float u_progress; +uniform float u_time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float diff(vec4 target, vec4 base){ + return (max(target.a / base.a, max(target.r / base.r, max(target.g / base.g, target.b / base.b))) - + min (target.a / base.a, min(target.r / base.r, min(target.g / base.g, target.b / base.b)))) * 4.0; +} + +bool id(vec2 coords, vec4 base, float basediff){ + vec4 target = texture2D(u_texture, coords); + return (diff(target, base)) > basediff - basediff*u_progress + || (basediff < 5.0 && (coords.x < u_uv.x || coords.y < u_uv.y || coords.x > u_uv2.x || coords.y > u_uv2.y)); +} + +bool cont(vec2 T, vec2 v, float basediff){ + float step = 1.0; + vec4 base = texture2D(u_texture, T); + return base.a > 0.1 && + (id(T + vec2(0, step) * v, base, basediff) || id(T + vec2(0, -step) * v, base, basediff) || + id(T + vec2(step, 0) * v, base, basediff) || id(T + vec2(-step, 0) * v, base, basediff)); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + + vec2 t = v_texCoord.xy; + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + vec2 coords = (v_texCoord-u_uv) / v; + float value = coords.x + coords.y; + + vec4 color = texture2D(u_texture, t); + + vec2 center = ((u_uv + u_uv2)/2.0 - u_uv) /v; + float dst = (abs(center.x - coords.x) + abs(center.y - coords.y)) / 2.0; + + if(dst - 1.0 < u_progress * (center.x) && dst> u_progress * (center.x) && color.a > 0.1){ + gl_FragColor = u_color; + }else if(cont(t, v, 6.0)){ + gl_FragColor = color; + }else if(cont(t, v, 3.0) && color.a > 0.1){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } +} diff --git a/core/assets/shaders/inline-noise.fragment b/core/assets/shaders/inline-noise.fragment new file mode 100644 index 0000000000..f7167d1dcf --- /dev/null +++ b/core/assets/shaders/inline-noise.fragment @@ -0,0 +1,111 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform float u_progress; +uniform float u_time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} +vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} + +float snoise(vec3 v){ + const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + +// First corner + vec3 i = floor(v + dot(v, C.yyy) ); + vec3 x0 = v - i + dot(i, C.xxx) ; + +// Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min( g.xyz, l.zxy ); + vec3 i2 = max( g.xyz, l.zxy ); + + // x0 = x0 - 0. + 0.0 * C + vec3 x1 = x0 - i1 + 1.0 * C.xxx; + vec3 x2 = x0 - i2 + 2.0 * C.xxx; + vec3 x3 = x0 - 1. + 3.0 * C.xxx; + +// Permutations + i = mod(i, 289.0 ); + vec4 p = permute( permute( permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + +// Gradients +// ( N*N points uniformly over a square, mapped onto an octahedron.) + float n_ = 1.0/7.0; // N=7 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) + + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4( x.xy, y.xy ); + vec4 b1 = vec4( x.zw, y.zw ); + + vec4 s0 = floor(b0)*2.0 + 1.0; + vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; + + vec3 p0 = vec3(a0.xy,h.x); + vec3 p1 = vec3(a0.zw,h.y); + vec3 p2 = vec3(a1.xy,h.z); + vec3 p3 = vec3(a1.zw,h.w); + +//Normalise gradients + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + +// Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), + dot(p2,x2), dot(p3,x3) ) ); +} + +void main() { + + vec2 t = v_texCoord.xy; + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + + vec2 coords = (v_texCoord-u_uv) / v; + + const float scl = 10.0; + float result = (snoise(vec3(coords.x / scl, coords.y / scl, u_time/400000.0)) + 1.0)/2.0; + + vec4 color = texture2D(u_texture, t); + + if(result < u_progress){ + gl_FragColor = color; + }else if(result < u_progress*2.0 && color.a > 0.1){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } + +} diff --git a/core/assets/shaders/inline.fragment b/core/assets/shaders/inline.fragment new file mode 100644 index 0000000000..d5d2f06f5b --- /dev/null +++ b/core/assets/shaders/inline.fragment @@ -0,0 +1,184 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec4 u_color; +uniform vec2 u_texsize; +uniform vec2 u_uv; +uniform vec2 u_uv2; +uniform float u_progress; +uniform float u_time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +const float chunk = 4.0; + +bool id(vec2 coords, vec4 base, float basediff){ + vec4 target = texture2D(u_texture, coords); + return target.a < 0.1 || (coords.x < u_uv.x || coords.y < u_uv.y || coords.x > u_uv2.x || coords.y > u_uv2.y); +} + +bool cont(vec2 T, vec2 v, float basediff){ + float step = 1.0; + vec4 base = texture2D(u_texture, T); + return base.a > 0.1 && + (id(T + vec2(0, step) * v, base, basediff) || id(T + vec2(0, -step) * v, base, basediff) || + id(T + vec2(step, 0) * v, base, basediff) || id(T + vec2(-step, 0) * v, base, basediff)); +} + +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float round(float f){ + return float(int(f / chunk)) * chunk; +} + +vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);} +vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;} + +float snoise(vec3 v){ + const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + +// First corner + vec3 i = floor(v + dot(v, C.yyy) ); + vec3 x0 = v - i + dot(i, C.xxx) ; + +// Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min( g.xyz, l.zxy ); + vec3 i2 = max( g.xyz, l.zxy ); + + // x0 = x0 - 0. + 0.0 * C + vec3 x1 = x0 - i1 + 1.0 * C.xxx; + vec3 x2 = x0 - i2 + 2.0 * C.xxx; + vec3 x3 = x0 - 1. + 3.0 * C.xxx; + +// Permutations + i = mod(i, 289.0 ); + vec4 p = permute( permute( permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + +// Gradients +// ( N*N points uniformly over a square, mapped onto an octahedron.) + float n_ = 1.0/7.0; // N=7 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z *ns.z); // mod(p,N*N) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) + + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4( x.xy, y.xy ); + vec4 b1 = vec4( x.zw, y.zw ); + + vec4 s0 = floor(b0)*2.0 + 1.0; + vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; + + vec3 p0 = vec3(a0.xy,h.x); + vec3 p1 = vec3(a0.zw,h.y); + vec3 p2 = vec3(a1.xy,h.z); + vec3 p3 = vec3(a1.zw,h.w); + +//Normalise gradients + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + +// Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), + dot(p2,x2), dot(p3,x3) ) ); +} + +void main() { + + vec2 t = v_texCoord.xy; + + vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); + + bool any = false; + + vec2 coords = (v_texCoord-u_uv) / v; + + /* + const float scl = 10.0; + float result = (snoise(vec3(coords.x / scl, coords.y / scl, u_time/400000.0)) + 1.0)/2.0; + + vec4 color = texture2D(u_texture, t); + + if(result < u_progress){ + gl_FragColor = color; + }else if(result < u_progress*2.0 && color.a > 0.1){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + }*/ + + + float value = coords.x + coords.y; + + vec4 color = texture2D(u_texture, t); + vec2 rc = vec2(round(coords.x), round(coords.y)); + vec2 center = ((u_uv + u_uv2)/2.0 - u_uv) /v; + + float r = clamp(rand(rc) + u_progress, 0.0, 1.0); + + const float start = 0.7; + const float end = 0.9; + + const float scl = 10.0; + float result = snoise(vec3(coords.x / scl, coords.y / scl, u_time/400.0))*2.0; + float dst = (abs(center.x - coords.x) + abs(center.y - coords.y)) / 2.0; + + if(dst - 1.0 < u_progress * (center.x) && dst> u_progress * (center.x) && color.a > 0.1){ + gl_FragColor = u_color; + }else if(r > end){ + gl_FragColor = color; + }else if(cont(t, v, 100.0) && mod(u_time / 1.5 + value, 20.0) < 5.0 && color.a > 0.1){ + gl_FragColor = u_color; + }else if(r > start && color.a > 0.1){ + float fr = (r-start)*(1.0/(end-start)); + + vec2 next = rc + chunk/2.0; + float rdst = max(abs(coords.x - next.x), abs(coords.y - next.y)) + result; + if(rdst / (chunk/2.0) < fr){ + gl_FragColor = u_color; + }else{ + gl_FragColor = vec4(0.0); + } + }else{ + gl_FragColor = vec4(0.0); + } +/* + if(mod(u_time / 1.5 + value, 30.0) < 15.0 && color.a > 0.1){ + gl_FragColor = u_color; + }else if(cont(t, v, 3.0)){ + gl_FragColor = color; + }else if(cont(t, v, 1.5)){ + gl_FragColor = u_color; + //}else if(mix(rand(vec2(dst)) * u_progress, 1.0, u_progress) > 0.5){ + // gl_FragColor = texture2D(u_texture, T); + }else{ + gl_FragColor = vec4(0.0); + }*/ +} diff --git a/core/assets/shaders/lava.fragment b/core/assets/shaders/lava.fragment new file mode 100644 index 0000000000..d7f3b3235e --- /dev/null +++ b/core/assets/shaders/lava.fragment @@ -0,0 +1,91 @@ +#ifdef GL_ES +precision highp float; +precision mediump int; +#endif + +#define s2 vec4(255.0, 165.0, 0.0, 255.0) / 255.0 +#define s1 vec4(255.0, 121.0, 62.0, 255.0) / 255.0 + +uniform sampler2D u_texture; + +uniform vec2 camerapos; +uniform vec2 screensize; +uniform float time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float round(float num, float f){ + return float(int(num / f)) * f; +} + +vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); } + +float snoise(vec2 v){ + const vec4 C = vec4(0.211324865405187, 0.366025403784439, + -0.577350269189626, 0.024390243902439); + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod(i, 289.0); + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), + dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +void main() { + + vec2 c = v_texCoord.xy; + vec4 color = texture2D(u_texture, c); + + vec2 v = vec2(1.0/screensize.x, 1.0/screensize.y); + ivec2 icoords = ivec2(int(c.x / v.x + camerapos.x), int(c.y / v.y + camerapos.y)); + vec2 coords = vec2(float(icoords.x), float(icoords.y)); + + float stime = time / 5.0; + + float mscl = 30.0; + float mth = 5.0; + + //if there's something actually there + if(color.r > 0.01){ + vec4 old = color; + color = texture2D(u_texture, c + vec2(sin(stime/3.0 + coords.y/0.75) * v.x, 0.0)) * vec4(0.9, 0.9, 1, 1.0); + color.a = 1.0; + + if(color.r < 0.01){ + color = old; + } + + const float bs = 1.0; + + float n1 = snoise(coords / (20.0 * bs) + vec2(time) / 250.0); + float n2 = snoise((coords + vec2(632.0)) / (9.0 * bs) + vec2(0.0, -time) / 220.0); + float n3 = snoise((coords + vec2(2233.0)) / (15.0 * bs) + vec2(time, 0.0) / 290.0); + + float r = (n1 + n2 + n3) / 3.0; + + if(r < -0.5){ + color = s2; + }else if(r < -0.2){ + color = s1; + } + } + + gl_FragColor = color; +} diff --git a/core/assets/shaders/mix.fragment b/core/assets/shaders/mix.fragment new file mode 100644 index 0000000000..4a9ea9c64b --- /dev/null +++ b/core/assets/shaders/mix.fragment @@ -0,0 +1,19 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D u_texture; +uniform vec4 u_color; + +varying vec4 v_color; +varying vec2 v_texCoord; + +void main() { + + vec4 c = texture2D(u_texture, v_texCoord.xy); + + c = mix(c, vec4(u_color.r, u_color.g, u_color.b, c.a), v_color.a); + + gl_FragColor = c * vec4(v_color.rgb, 1.0); +} diff --git a/core/assets/shaders/oil.fragment b/core/assets/shaders/oil.fragment new file mode 100644 index 0000000000..52701e09a2 --- /dev/null +++ b/core/assets/shaders/oil.fragment @@ -0,0 +1,86 @@ +#ifdef GL_ES +precision highp float; +precision mediump int; +#endif + +#define s1 vec4(63.0, 63.0, 63.0, 255.0) / 255.0 + +uniform sampler2D u_texture; + +uniform vec2 camerapos; +uniform vec2 screensize; +uniform float time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float round(float num, float f){ + return float(int(num / f)) * f; +} + +vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); } + +float snoise(vec2 v){ + const vec4 C = vec4(0.211324865405187, 0.366025403784439, + -0.577350269189626, 0.024390243902439); + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod(i, 289.0); + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), + dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +void main() { + + vec2 c = v_texCoord.xy; + vec4 color = texture2D(u_texture, c); + + vec2 v = vec2(1.0/screensize.x, 1.0/screensize.y); + ivec2 icoords = ivec2(int(c.x / v.x + camerapos.x), int(c.y / v.y + camerapos.y)); + vec2 coords = vec2(float(icoords.x), float(icoords.y)); + + float stime = time / 5.0; + + float mscl = 30.0; + float mth = 5.0; + + //if there's something actually there + if(color.r > 0.01){ + vec4 old = color; + color = texture2D(u_texture, c + vec2(sin(stime/3.0 + coords.y/0.75) * v.x, 0.0)) * vec4(0.9, 0.9, 1, 1.0); + color.a = 1.0; + + if(color.r < 0.01){ + color = old; + } + + float n1 = snoise(coords / 22.0 + vec2(-time) / 540.0); + float n2 = snoise((coords + vec2(632.0)) / 8.0 + vec2(0.0, time) / 510.0); + + float r = (n1 + n2) / 2.0; + + if(r < -0.3 && r > -0.6){ + color *= 1.4; + color.a = 1.0; + } + } + + gl_FragColor = color; +} diff --git a/core/assets/shaders/outline.fragment b/core/assets/shaders/outline.fragment index 6a43f1d4a6..46f6f6872d 100644 --- a/core/assets/shaders/outline.fragment +++ b/core/assets/shaders/outline.fragment @@ -3,42 +3,25 @@ precision mediump float; precision mediump int; #endif +#define SPACE 1.0 + uniform sampler2D u_texture; uniform vec4 u_color; uniform vec2 u_texsize; -uniform float u_lighten; varying vec4 v_color; varying vec2 v_texCoord; -bool id(vec4 v){ - return v.a > 0.1 && !(v.r < 0.01 && v.g < 0.01 && v.b < 0.01); -} - void main() { - - vec2 T = v_texCoord.xy; - vec2 v = vec2(1.0/u_texsize.x, 1.0/u_texsize.y); - bool any = false; + vec4 c = texture2D(u_texture, v_texCoord.xy); - float step = 1.0; - - vec4 c = texture2D(u_texture, T); - - if(texture2D(u_texture, T).a < 0.1 && - (id(texture2D(u_texture, T + vec2(0, step) * v)) || id(texture2D(u_texture, T + vec2(0, -step) * v)) || - id(texture2D(u_texture, T + vec2(step, 0) * v)) || id(texture2D(u_texture, T + vec2(-step, 0) * v)))) - any = true; - - if(any){ - gl_FragColor = u_color; - }else{ - if((c.r < 0.01 && c.g < 0.01 && c.b < 0.01)){ - c = vec4(0.0); - } - gl_FragColor = mix(c, vec4(1.0, 1.0, 1.0, c.a), u_lighten) * v_color; - } + gl_FragColor = mix(c * v_color, u_color, + (1.0-step(0.1, texture2D(u_texture, v_texCoord.xy).a)) * + step(0.1, texture2D(u_texture, v_texCoord.xy + vec2(0, SPACE) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(0, -SPACE) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(SPACE, 0) * v).a + + texture2D(u_texture, v_texCoord.xy + vec2(-SPACE, 0) * v).a)); } diff --git a/core/assets/shaders/space.fragment b/core/assets/shaders/space.fragment new file mode 100644 index 0000000000..f2522b4fca --- /dev/null +++ b/core/assets/shaders/space.fragment @@ -0,0 +1,77 @@ +#ifdef GL_ES +precision highp float; +precision mediump int; +#endif + +#define s1 vec4(63.0, 63.0, 63.0, 255.0) / 255.0 + +uniform sampler2D u_texture; + +uniform vec2 u_center; +uniform vec2 camerapos; +uniform vec2 screensize; +uniform float time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float round(float num, float f){ + return float(int(num / f)) * f; +} + +vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); } + +float snoise(vec2 v){ + const vec4 C = vec4(0.211324865405187, 0.366025403784439, + -0.577350269189626, 0.024390243902439); + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod(i, 289.0); + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), + dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +void main() { + + vec2 c = v_texCoord.xy; + vec4 color = texture2D(u_texture, c); + + vec2 v = vec2(1.0/screensize.x, 1.0/screensize.y); + ivec2 icoords = ivec2(int(c.x / v.x + camerapos.x), int(c.y / v.y + camerapos.y)); + vec2 coords = vec2(float(icoords.x), float(icoords.y)); + + float stime = time / 5.0; + + float mscl = 30.0; + float mth = 5.0; + + //if there's something actually there + if(color.a > 0.01){ + vec2 diff = camerapos + screensize/2.0 - u_center; + + float dst = mod((distance(coords, camerapos + screensize/2.0 - diff/5.0) + time * 2.0 + snoise(coords / 100.0) * 10.0), 150.0); + + if(dst < 40.0){ + color = vec4(vec3(0.2), 1.0); + } + } + + gl_FragColor = color; +} diff --git a/core/assets/shaders/space2.fragment b/core/assets/shaders/space2.fragment new file mode 100644 index 0000000000..5b9ea44853 --- /dev/null +++ b/core/assets/shaders/space2.fragment @@ -0,0 +1,88 @@ +#ifdef GL_ES +precision highp float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec2 u_center; +uniform vec2 camerapos; +uniform vec2 screensize; +uniform float time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +const float tau = 6.28318530717958647692; +const float tscl = 0.4; + +// Gamma correction +#define GAMMA (2.2) + +vec3 ToLinear(vec3 col ){ + // simulate a monitor, converting colour values into light values + return pow( col, vec3(GAMMA) ); +} + +vec3 ToGamma(vec3 col ){ + // convert back into colour values, so the correct light will come out of the monitor + return pow( col, vec3(1.0/GAMMA) ); +} + +float srand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +vec4 Noise(ivec2 x ){ + return vec4(srand((vec2(x)+0.5)/256.0)); +} + +void main(){ + vec4 resultc = texture2D(u_texture, v_texCoord.xy); + + if(resultc.a > 0.0){ + + vec2 coords = v_texCoord.xy*screensize; + + vec3 ray; + ray.xy = 2.0*(coords-screensize.xy*.5)/screensize.x; + ray.z = 1.0; + + float literallyzero = 0.000000000001; + + float offset = time/60.0*.5 * tscl + camerapos.x * u_center.x * resultc.r * literallyzero; + float speed2 = 0.3; + float speed = speed2+.1; + offset += .1; + offset *= 2.0; + + vec3 col = vec3(0.0); + + vec3 stp = ray/max(abs(ray.x),abs(ray.y)); + + vec3 pos = 2.0*stp+.5; + for ( int i=0; i < 20; i++){ + float z = Noise(ivec2(pos.xy)).x; + z = fract(z-offset); + float d = 50.0*z-pos.z; + float w = pow(max(0.0,1.0-8.0*length(fract(pos.xy)-.5)),2.0); + vec3 c = max(vec3(0),vec3(1.0-abs(d+speed2*.5)/speed,1.0-abs(d)/speed,1.0-abs(d-speed2*.5)/speed)); + col += 1.5*(1.0-z)*c*w; + pos += stp; + } + + vec3 color = ToGamma(col); + + if(color.r > 0.3 && color.b > 0.3){ + color = vec3(240.0, 245.0, 255.0) / 255.0; + }else{ + color = vec3(0.0); + } + + gl_FragColor = vec4(color,1.0); + }else{ + gl_FragColor = vec4(0.0); + } + + +} diff --git a/core/assets/shaders/water.fragment b/core/assets/shaders/water.fragment new file mode 100644 index 0000000000..d8f26bebb1 --- /dev/null +++ b/core/assets/shaders/water.fragment @@ -0,0 +1,90 @@ +#ifdef GL_ES +precision highp float; +precision mediump int; +#endif + +uniform sampler2D u_texture; + +uniform vec2 camerapos; +uniform vec2 screensize; +uniform float time; + +varying vec4 v_color; +varying vec2 v_texCoord; + +float round(float num, float f){ + return float(int(num / f)) * f; +} + +vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); } + +float snoise(vec2 v){ + const vec4 C = vec4(0.211324865405187, 0.366025403784439, + -0.577350269189626, 0.024390243902439); + vec2 i = floor(v + dot(v, C.yy) ); + vec2 x0 = v - i + dot(i, C.xx); + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod(i, 289.0); + vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + + i.x + vec3(0.0, i1.x, 1.0 )); + vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), + dot(x12.zw,x12.zw)), 0.0); + m = m*m ; + m = m*m ; + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +void main() { + + vec2 c = v_texCoord.xy; + vec4 color = texture2D(u_texture, c) * v_color; + + vec2 v = vec2(1.0/screensize.x, 1.0/screensize.y); + ivec2 icoords = ivec2(int(c.x / v.x + camerapos.x), int(c.y / v.y + camerapos.y)); + vec2 coords = vec2(float(icoords.x), float(icoords.y)); + + float stime = time / 5.0; + + float mscl = 30.0; + float mth = 5.0; + + //if there's something actually there + if(color.r > 0.01){ + vec4 old = color; + color = texture2D(u_texture, c + vec2(sin(stime/3.0 + coords.y/0.75) * v.x, 0.0)) * vec4(0.9, 0.9, 1, 1.0); + color.a = 1.0; + + if(color.r < 0.01){ + color = old; + } + + float n1 = snoise(coords / 40.0 + vec2(time) / 200.0); + float n2 = snoise((coords + vec2(632.0)) / 25.0 + vec2(0.0, -time) / 190.0); + + float r = (n1 + n2) * 3.0; + + if(mod(float(int(coords.x + coords.y*1.1 + sin(stime / 8.0 + coords.x/5.0 - coords.y/100.0)*2.0)) + + sin(stime / 20.0 + coords.y/3.0) * 1.0 + + sin(stime / 10.0 + coords.y/2.0) * 2.0 + + sin(stime / 7.0 + coords.y/1.0) * 0.5 + + sin(coords.x + coords.y) + + sin(stime / 20.0 + coords.x/4.0) * 1.0, mscl) + r < mth){ + + color *= 1.2; + color.a = 1.0; + } + } + + gl_FragColor = color; +} diff --git a/core/assets/sprites/sprites.atlas b/core/assets/sprites/sprites.atlas index 1b4eb5e605..be782dff2f 100644 --- a/core/assets/sprites/sprites.atlas +++ b/core/assets/sprites/sprites.atlas @@ -11,1661 +11,4391 @@ background orig: 421, 316 offset: 0, 0 index: -1 -blank +bridge-conveyor-arrow rotate: false - xy: 791, 491 - size: 1, 1 - orig: 1, 1 - offset: 0, 0 - index: -1 -blackrock1 - rotate: false - xy: 79, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackrockshadow1 - rotate: false - xy: 1014, 487 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstone1 - rotate: false - xy: 615, 278 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstone2 - rotate: false - xy: 615, 268 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstone3 - rotate: false - xy: 768, 436 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstoneblock1 - rotate: false - xy: 780, 449 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstoneblock2 - rotate: false - xy: 772, 426 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstoneblock3 - rotate: false - xy: 778, 436 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -blackstoneedge - rotate: false - xy: 551, 273 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -block - rotate: false - xy: 85, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -block-2x2 - rotate: false - xy: 473, 227 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -block-3x3 - rotate: false - xy: 424, 198 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -block-middle - rotate: false - xy: 190, 112 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -chainturret - rotate: false - xy: 342, 157 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -chainturret-icon - rotate: false - xy: 57, 48 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -chainturret-icon_old - rotate: false - xy: 651, 382 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -chainturret_old - rotate: false - xy: 362, 177 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -coal1 - rotate: false - xy: 153, 70 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coal2 - rotate: false - xy: 85, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coal3 - rotate: false - xy: 89, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coaldrill - rotate: false - xy: 790, 449 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coalgenerator - rotate: false - xy: 788, 439 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coalgenerator-top - rotate: false - xy: 800, 452 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -coalpurifier - rotate: false - xy: 810, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -combustiongenerator - rotate: false - xy: 820, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -compositewall - rotate: false - xy: 830, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conduit - rotate: false - xy: 840, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conduitbottom - rotate: false - xy: 850, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conduitliquid - rotate: false - xy: 860, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conduittop - rotate: false - xy: 95, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conveyor - rotate: false - xy: 99, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conveyormove - rotate: false - xy: 95, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -conveyortunnel - rotate: false - xy: 870, 453 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -core - rotate: false - xy: 264, 169 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -cross - rotate: false - xy: 880, 455 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -crucible - rotate: false - xy: 105, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -deepwater - rotate: false - xy: 109, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -dirt1 - rotate: false - xy: 105, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -dirt2 - rotate: false - xy: 115, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -dirt3 - rotate: false - xy: 119, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -dirtedge - rotate: false - xy: 722, 442 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -door - rotate: false - xy: 115, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -door-large - rotate: false - xy: 491, 227 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -door-large-icon - rotate: false - xy: 125, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -door-large-open - rotate: false - xy: 380, 177 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -door-open - rotate: false - xy: 129, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -doubleturret - rotate: false - xy: 675, 382 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -duriumwall - rotate: false - xy: 125, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -duriumwall-large - rotate: false - xy: 828, 495 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -duriumwall-large-icon - rotate: false - xy: 135, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -enemyspawn - rotate: false - xy: 145, 58 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -flameturret - rotate: false - xy: 669, 370 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -fluxpump - rotate: false - xy: 139, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grass1 - rotate: false - xy: 135, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grass2 - rotate: false - xy: 155, 60 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grass3 - rotate: false - xy: 149, 48 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grassblock1 - rotate: false - xy: 145, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grassblock2 - rotate: false - xy: 159, 50 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -grassedge - rotate: false - xy: 194, 124 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -ice1 - rotate: false - xy: 155, 38 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -ice2 - rotate: false - xy: 432, 172 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -ice3 - rotate: false - xy: 442, 172 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -iceedge - rotate: false - xy: 328, 129 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -icerock1 - rotate: false - xy: 792, 429 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icerock2 - rotate: false - xy: 798, 439 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icerockshadow1 - rotate: false - xy: 802, 429 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -rockshadow1 - rotate: false - xy: 802, 429 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icerockshadow2 - rotate: false - xy: 450, 214 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -rockshadow2 - rotate: false - xy: 450, 214 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -iron1 - rotate: false - xy: 462, 174 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -iron2 - rotate: false - xy: 792, 419 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -iron3 - rotate: false - xy: 802, 419 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -irondrill - rotate: false - xy: 470, 215 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -ironwall - rotate: false - xy: 470, 205 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -junction - rotate: false - xy: 470, 195 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -laserturret - rotate: false - xy: 687, 310 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -lava - rotate: false - xy: 468, 185 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -lavaedge - rotate: false - xy: 874, 465 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -lavasmelter - rotate: false - xy: 480, 217 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -liquiditemjunction - rotate: false - xy: 480, 207 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -liquidjunction - rotate: false - xy: 490, 217 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -liquidrouter - rotate: false - xy: 480, 197 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -machineturret - rotate: false - xy: 687, 298 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -megarepairturret - rotate: false - xy: 683, 414 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -mortarturret - rotate: false - xy: 683, 402 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -mossblock - rotate: false - xy: 490, 207 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -mossstone - rotate: false - xy: 490, 207 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -nuclearreactor - rotate: false - xy: 264, 143 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -nuclearreactor-center - rotate: false - xy: 290, 169 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -nuclearreactor-icon - rotate: false - xy: 490, 197 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -nuclearreactor-lights - rotate: false - xy: 290, 143 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -nuclearreactor-small - rotate: false - xy: 87, 76 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -oil - rotate: false - xy: 472, 175 + xy: 333, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -oiledge +bridge-conveyor-bridge rotate: false - xy: 918, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -oilrefinery - rotate: false - xy: 478, 185 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -omnidrill - rotate: false - xy: 488, 187 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -plasmaturret - rotate: false - xy: 141, 68 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -playerspawn - rotate: false - xy: 482, 175 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -powerbooster - rotate: false - xy: 492, 177 + xy: 343, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -poweredconveyor +bridge-conveyor-end rotate: false - xy: 498, 187 + xy: 353, 25 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -poweredconveyormove +phase-conveyor-arrow rotate: false - xy: 502, 177 + xy: 627, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -powerlaser - rotate: false - xy: 358, 147 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -powerlasercorner - rotate: false - xy: 358, 137 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -powerlaserrouter - rotate: false - xy: 358, 127 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -pulseconduit - rotate: false - xy: 688, 438 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -pulseconduitbottom - rotate: false - xy: 688, 428 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -pulseconduittop - rotate: false - xy: 657, 291 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -pump - rotate: false - xy: 567, 262 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -repairturret - rotate: false - xy: 603, 276 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -rock1 +phase-conveyor-bridge rotate: false - xy: 577, 262 + xy: 637, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -rock2 +phase-conveyor-end rotate: false - xy: 587, 262 + xy: 627, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 router rotate: false - xy: 569, 252 + xy: 647, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -rtgenerator +warp-gate-top rotate: false - xy: 569, 242 + xy: 702, 257 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +blast-drill + rotate: false + xy: 925, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +blast-drill-rim + rotate: false + xy: 951, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +blast-drill-rotator + rotate: false + xy: 713, 319 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +blast-drill-top + rotate: false + xy: 739, 328 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +carbide-drill + rotate: false + xy: 343, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -rtgenerator-top +carbide-drill-rotator rotate: false - xy: 579, 252 + xy: 353, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -sand1 +carbide-drill-top rotate: false - xy: 569, 232 + xy: 363, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +laser-drill + rotate: false + xy: 202, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +laser-drill-rotator + rotate: false + xy: 184, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +laser-drill-top + rotate: false + xy: 220, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +oil-extractor + rotate: false + xy: 639, 232 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +oil-extractor-liquid + rotate: false + xy: 587, 206 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +oil-extractor-rotator + rotate: false + xy: 613, 206 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +oil-extractor-top + rotate: false + xy: 639, 206 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +plasma-drill + rotate: false + xy: 889, 445 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +plasma-drill-rim + rotate: false + xy: 923, 445 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +plasma-drill-rotator + rotate: false + xy: 957, 445 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +plasma-drill-top + rotate: false + xy: 991, 437 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +tungsten-drill + rotate: false + xy: 741, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +tungsten-drill-rotator + rotate: false + xy: 747, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +tungsten-drill-top + rotate: false + xy: 747, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +water-extractor + rotate: false + xy: 184, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +water-extractor-liquid + rotate: false + xy: 202, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +water-extractor-rotator + rotate: false + xy: 220, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +water-extractor-top + rotate: false + xy: 238, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +blackrock1 + rotate: false + xy: 692, 425 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-blackrock + rotate: false + xy: 692, 425 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +blackrockshadow1 + rotate: false + xy: 171, 107 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +blackstone1 + rotate: false + xy: 411, 106 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-blackstone + rotate: false + xy: 411, 106 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +blackstone2 + rotate: false + xy: 512, 72 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +blackstone3 + rotate: false + xy: 957, 359 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +blackstoneedge + rotate: false + xy: 977, 431 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +coal1 + rotate: false + xy: 353, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +coal2 + rotate: false + xy: 363, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +coal3 + rotate: false + xy: 373, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +dirt2 + rotate: false + xy: 967, 356 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +dirt3 + rotate: false + xy: 977, 356 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +dirtedge + rotate: false + xy: 1011, 359 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +grass-cliff-edge + rotate: false + xy: 997, 345 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass-cliff-edge-1 + rotate: false + xy: 1007, 349 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass-cliff-edge-2 + rotate: false + xy: 1007, 339 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass-cliff-side + rotate: false + xy: 409, 48 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass2 + rotate: false + xy: 997, 355 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass3 + rotate: false + xy: 987, 345 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grassedge + rotate: false + xy: 209, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +ice-cliff-edge + rotate: false + xy: 971, 336 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice-cliff-edge-1 + rotate: false + xy: 413, 28 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice-cliff-edge-2 + rotate: false + xy: 413, 18 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice-cliff-side + rotate: false + xy: 413, 8 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice2 + rotate: false + xy: 409, 38 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice3 + rotate: false + xy: 961, 336 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +iceedge + rotate: false + xy: 237, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +icerock2 + rotate: false + xy: 981, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +icerockshadow1 + rotate: false + xy: 991, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +rockshadow1 + rotate: false + xy: 991, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +icerockshadow2 + rotate: false + xy: 701, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +rockshadow2 + rotate: false + xy: 701, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +lavaedge + rotate: false + xy: 265, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +lead1 + rotate: false + xy: 567, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +lead2 + rotate: false + xy: 555, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +lead3 + rotate: false + xy: 565, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor2 + rotate: false + xy: 527, 131 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor3 + rotate: false + xy: 542, 151 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor4 + rotate: false + xy: 540, 141 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor5 + rotate: false + xy: 527, 121 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor6 + rotate: false + xy: 537, 131 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalflooredge + rotate: false + xy: 847, 302 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +oiledge + rotate: false + xy: 892, 369 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +rock2 + rotate: false + xy: 637, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sand-cliff-edge + rotate: false + xy: 657, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sand-cliff-edge-1 + rotate: false + xy: 657, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sand-cliff-edge-2 + rotate: false + xy: 657, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sand-cliff-side + rotate: false + xy: 657, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand2 rotate: false - xy: 579, 242 + xy: 662, 148 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sand3 rotate: false - xy: 569, 222 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -sandblock1 - rotate: false - xy: 579, 232 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -sandblock2 - rotate: false - xy: 569, 212 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -sandblock3 - rotate: false - xy: 579, 222 + xy: 660, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 sandedge rotate: false - xy: 932, 481 + xy: 920, 369 size: 12, 12 orig: 12, 12 offset: 0, 0 index: -1 -shadow - rotate: false - xy: 625, 273 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -shieldgenerator - rotate: false - xy: 589, 252 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -shotgunturret - rotate: false - xy: 637, 273 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -shrub - rotate: false - xy: 597, 262 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 shrubshadow rotate: false - xy: 599, 252 + xy: 672, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -smelter +snow-cliff-edge rotate: false - xy: 599, 242 + xy: 667, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -smelter-middle +snow-cliff-edge-1 rotate: false - xy: 599, 232 + xy: 667, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -sniperturret +snow-cliff-edge-2 rotate: false - xy: 649, 273 - size: 10, 10 - orig: 10, 10 + xy: 667, 98 + size: 8, 8 + orig: 8, 8 offset: 0, 0 index: -1 -snow1 +snow-cliff-side rotate: false - xy: 599, 222 + xy: 670, 138 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow2 rotate: false - xy: 599, 212 + xy: 682, 149 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snow3 rotate: false - xy: 569, 202 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -snowblock1 - rotate: false - xy: 579, 202 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -snowblock2 - rotate: false - xy: 589, 202 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -snowblock3 - rotate: false - xy: 599, 202 + xy: 667, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 snowedge rotate: false - xy: 960, 481 + xy: 879, 353 size: 12, 12 orig: 12, 12 offset: 0, 0 index: -1 -sorter +spaceedge rotate: false - xy: 65, 16 + xy: 879, 339 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +stone-cliff-edge + rotate: false + xy: 677, 118 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -steelconveyor +stone-cliff-edge-1 rotate: false - xy: 75, 16 + xy: 677, 108 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -steelconveyormove +stone-cliff-edge-2 rotate: false - xy: 69, 6 + xy: 677, 98 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -steelwall +stone-cliff-side rotate: false - xy: 79, 6 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -steelwall-large - rotate: false - xy: 105, 94 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -steelwall-large-icon - rotate: false - xy: 83, 26 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -stone1 - rotate: false - xy: 85, 16 + xy: 889, 317 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone2 rotate: false - xy: 93, 28 + xy: 680, 139 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stone3 rotate: false - xy: 103, 28 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -stoneblock1 - rotate: false - xy: 113, 28 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -stoneblock2 - rotate: false - xy: 123, 28 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -stoneblock3 - rotate: false - xy: 133, 28 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -stonedrill - rotate: false - xy: 143, 28 + xy: 677, 128 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 stoneedge rotate: false - xy: 674, 426 + xy: 893, 355 size: 12, 12 orig: 12, 12 offset: 0, 0 index: -1 -stoneformer +thorium1 rotate: false - xy: 153, 28 + xy: 736, 241 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -stonewall +thorium2 rotate: false - xy: 95, 18 + xy: 736, 231 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -teleporter +thorium3 rotate: false - xy: 105, 18 + xy: 736, 221 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -teleporter-top - rotate: false - xy: 115, 18 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -thermalgenerator - rotate: false - xy: 125, 18 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -titancannon - rotate: false - xy: 316, 169 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 -titancannon-icon - rotate: false - xy: 625, 261 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -titancannon-icon_old - rotate: false - xy: 637, 261 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -titancannon_old - rotate: false - xy: 316, 143 - size: 24, 24 - orig: 24, 24 - offset: 0, 0 - index: -1 titanium1 rotate: false - xy: 135, 18 + xy: 736, 211 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium2 rotate: false - xy: 145, 18 + xy: 731, 201 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 titanium3 rotate: false - xy: 155, 18 + xy: 731, 191 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -titaniumdrill +tungsten1 rotate: false - xy: 89, 6 + xy: 741, 191 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -titaniumpurifier +tungsten2 rotate: false - xy: 99, 8 + xy: 737, 181 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -titaniumshieldwall +tungsten3 rotate: false - xy: 109, 8 + xy: 737, 171 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -titaniumwall +water-cliff-edge rotate: false - xy: 119, 8 + xy: 751, 161 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -titaniumwall-large +water-cliff-edge-1 rotate: false - xy: 141, 112 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -titaniumwall-large-icon - rotate: false - xy: 129, 8 + xy: 873, 305 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -turret +water-cliff-edge-2 rotate: false - xy: 649, 261 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -uranium1 - rotate: false - xy: 163, 28 + xy: 883, 305 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -uranium2 +water-cliff-side rotate: false - xy: 165, 18 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -uranium3 - rotate: false - xy: 169, 8 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -uraniumdrill - rotate: false - xy: 190, 102 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -water - rotate: false - xy: 220, 109 + xy: 871, 295 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 wateredge rotate: false - xy: 722, 428 + xy: 907, 341 size: 12, 12 orig: 12, 12 offset: 0, 0 index: -1 -waveturret +block-border rotate: false - xy: 768, 446 - size: 10, 10 - orig: 10, 10 - offset: 0, 0 - index: -1 -weaponfactory - rotate: false - xy: 123, 76 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -weaponfactory-icon - rotate: false - xy: 230, 109 + xy: 931, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -bullet +block-elevation rotate: false - xy: 557, 199 + xy: 941, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -chainbullet +combustion-generator-top rotate: false - xy: 49, 1 - size: 8, 7 - orig: 8, 7 - offset: 0, 0 - index: -1 -circle - rotate: false - xy: 791, 494 - size: 17, 17 - orig: 17, 17 - offset: 0, 0 - index: -1 -circle2 - rotate: false - xy: 424, 310 - size: 201, 201 - orig: 201, 201 - offset: 0, 0 - index: -1 -blastenemy-t1 - rotate: false - xy: 1008, 497 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -blastenemy-t2 - rotate: false - xy: 141, 96 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -blastenemy-t3 - rotate: false - xy: 159, 114 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -empenemy-t1 - rotate: false - xy: 141, 80 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -empenemy-t2 - rotate: false - xy: 342, 141 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -empenemy-t3 - rotate: false - xy: 69, 58 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -fastenemy-t1 - rotate: false - xy: 551, 257 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -fastenemy-t2 - rotate: false - xy: 509, 229 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -fastenemy-t3 - rotate: false - xy: 651, 410 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -flamerenemy-t1 - rotate: false - xy: 651, 394 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -flamerenemy-t2 - rotate: false - xy: 57, 32 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -flamerenemy-t3 - rotate: false - xy: 653, 366 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -fortressenemy-t1 - rotate: false - xy: 769, 491 - size: 20, 20 - orig: 20, 20 - offset: 0, 0 - index: -1 -fortressenemy-t2 - rotate: false - xy: 27, 4 - size: 20, 20 - orig: 20, 20 - offset: 0, 0 - index: -1 -fortressenemy-t3 - rotate: false - xy: 603, 288 - size: 20, 20 - orig: 20, 20 - offset: 0, 0 - index: -1 -healerenemy-t1 - rotate: false - xy: 653, 350 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -healerenemy-t2 - rotate: false - xy: 653, 334 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -healerenemy-t3 - rotate: false - xy: 653, 318 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -mortarenemy-t1 - rotate: false - xy: 810, 479 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -mortarenemy-t2 - rotate: false - xy: 794, 462 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -mortarenemy-t3 - rotate: false - xy: 810, 463 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -rapidenemy-t1 - rotate: false - xy: 826, 479 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -rapidenemy-t2 - rotate: false - xy: 826, 463 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -rapidenemy-t3 - rotate: false - xy: 842, 479 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -standardenemy-t1 - rotate: false - xy: 842, 463 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -targetenemy-t1 - rotate: false - xy: 842, 463 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -standardenemy-t2 - rotate: false - xy: 974, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -standardenemy-t3 - rotate: false - xy: 988, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -tankenemy-t1 - rotate: false - xy: 858, 479 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -tankenemy-t2 - rotate: false - xy: 858, 463 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -tankenemy-t3 - rotate: false - xy: 874, 479 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -titanenemy-t1 - rotate: false - xy: 123, 112 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -titanenemy-t2 - rotate: false - xy: 105, 76 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -titanenemy-t3 - rotate: false - xy: 123, 94 - size: 16, 16 - orig: 16, 16 - offset: 0, 0 - index: -1 -enemyarrow - rotate: false - xy: 59, 1 - size: 8, 7 - orig: 8, 7 - offset: 0, 0 - index: -1 -icon-coal - rotate: false - xy: 450, 204 + xy: 941, 335 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -icon-dirium +block-middle rotate: false - xy: 460, 215 + xy: 313, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -icon-iron +pump-liquid rotate: false - xy: 460, 205 + xy: 313, 5 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 -icon-sand +block-slope rotate: false - xy: 450, 194 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icon-steel - rotate: false - xy: 460, 195 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icon-stone - rotate: false - xy: 448, 184 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icon-titanium - rotate: false - xy: 452, 174 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -icon-uranium - rotate: false - xy: 458, 184 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -laser - rotate: false - xy: 175, 116 - size: 1, 12 - orig: 1, 12 - offset: 0, 0 - index: -1 -laserend - rotate: false - xy: 453, 225 - size: 18, 18 - orig: 18, 18 - offset: 0, 0 - index: -1 -laserfull - rotate: false - xy: 342, 175 - size: 18, 18 - orig: 18, 18 - offset: 0, 0 - index: -1 -mech-standard - rotate: false - xy: 890, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -mech-standard-icon - rotate: false - xy: 904, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -ship-standard - rotate: false - xy: 946, 481 - size: 12, 12 - orig: 12, 12 - offset: 0, 0 - index: -1 -shell - rotate: false - xy: 579, 212 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -shot - rotate: false - xy: 589, 222 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -shot-long - rotate: false - xy: 589, 212 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -titanshell - rotate: false - xy: 139, 8 + xy: 323, 15 size: 8, 8 orig: 8, 8 offset: 0, 0 index: -1 border rotate: false - xy: 1, 44 + xy: 333, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-liquid + rotate: false + xy: 393, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +cross-1 + rotate: false + xy: 512, 62 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +cross-2 + rotate: false + xy: 357, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cross-3 + rotate: false + xy: 385, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +cross-4 + rotate: false + xy: 316, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +enemyspawn + rotate: false + xy: 987, 355 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +nuclearreactor-shadow + rotate: false + xy: 765, 356 + size: 26, 26 + orig: 26, 26 + offset: 0, 0 + index: -1 +place-arrow + rotate: false + xy: 637, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +playerspawn + rotate: false + xy: 647, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ripples + rotate: false + xy: 416, 58 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +rubble-1-0 + rotate: false + xy: 256, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +rubble-1-1 + rotate: false + xy: 292, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +rubble-2-0 + rotate: false + xy: 274, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +rubble-2-1 + rotate: false + xy: 310, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +rubble-3-0 + rotate: false + xy: 691, 231 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +rubble-3-1 + rotate: false + xy: 691, 231 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +shadow-1 + rotate: false + xy: 452, 56 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +shadow-2 + rotate: false + xy: 447, 176 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +shadow-3 + rotate: false + xy: 845, 417 + size: 26, 26 + orig: 26, 26 + offset: 0, 0 + index: -1 +shadow-4 + rotate: false + xy: 37, 6 + size: 34, 34 + orig: 34, 34 + offset: 0, 0 + index: -1 +shadow-5 + rotate: false + xy: 775, 446 + size: 42, 42 + orig: 42, 42 + offset: 0, 0 + index: -1 +shadow-6 + rotate: false + xy: 627, 373 + size: 50, 50 + orig: 50, 50 + offset: 0, 0 + index: -1 +shadow-round-1 + rotate: false + xy: 464, 56 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +shadow-round-2 + rotate: false + xy: 467, 182 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +shadow-rounded-2 + rotate: false + xy: 161, 3 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +bridge-conduit-arrow + rotate: false + xy: 323, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +bridge-conduit-bridge + rotate: false + xy: 333, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +bridge-conduit-end + rotate: false + xy: 343, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom + rotate: false + xy: 363, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-0 + rotate: false + xy: 373, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-1 + rotate: false + xy: 383, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-2 + rotate: false + xy: 373, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-3 + rotate: false + xy: 383, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-4 + rotate: false + xy: 393, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-5 + rotate: false + xy: 383, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-bottom-6 + rotate: false + xy: 393, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-0 + rotate: false + xy: 403, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-1 + rotate: false + xy: 403, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-2 + rotate: false + xy: 403, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-3 + rotate: false + xy: 517, 131 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-4 + rotate: false + xy: 517, 121 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-5 + rotate: false + xy: 517, 111 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conduit-top-6 + rotate: false + xy: 517, 101 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-router + rotate: false + xy: 522, 151 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-router-bottom + rotate: false + xy: 520, 141 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-router-liquid + rotate: false + xy: 532, 151 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-router-top + rotate: false + xy: 530, 141 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-tank-bottom + rotate: false + xy: 624, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +liquid-tank-liquid + rotate: false + xy: 650, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +liquid-tank-top + rotate: false + xy: 676, 257 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +phase-conduit-arrow + rotate: false + xy: 617, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-conduit-bridge + rotate: false + xy: 627, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-conduit-end + rotate: false + xy: 617, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-0 + rotate: false + xy: 637, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-1 + rotate: false + xy: 647, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-2 + rotate: false + xy: 647, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-3 + rotate: false + xy: 567, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-4 + rotate: false + xy: 577, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-5 + rotate: false + xy: 587, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulse-conduit-top-6 + rotate: false + xy: 597, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +battery + rotate: false + xy: 115, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-battery + rotate: false + xy: 115, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +battery-large + rotate: false + xy: 899, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-battery-large + rotate: false + xy: 899, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +fusion-reactor + rotate: false + xy: 350, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-bottom + rotate: false + xy: 384, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-light + rotate: false + xy: 860, 479 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-plasma-0 + rotate: false + xy: 894, 479 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-plasma-1 + rotate: false + xy: 928, 479 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-plasma-2 + rotate: false + xy: 962, 479 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-plasma-3 + rotate: false + xy: 489, 209 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +fusion-reactor-top + rotate: false + xy: 523, 209 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +nuclear-reactor-center + rotate: false + xy: 587, 232 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +nuclear-reactor-lights + rotate: false + xy: 613, 232 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +rtg-generator-top + rotate: false + xy: 662, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +turbine-generator-top + rotate: false + xy: 364, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +alloy-fuser + rotate: false + xy: 873, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-alloy-fuser + rotate: false + xy: 873, 419 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +alloy-smelter + rotate: false + xy: 125, 1 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-alloy-smelter + rotate: false + xy: 125, 1 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +arc-smelter + rotate: false + xy: 143, 1 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-arc-smelter + rotate: false + xy: 143, 1 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +arc-smelter-top + rotate: false + xy: 507, 191 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +silicon-smelter-top + rotate: false + xy: 507, 191 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor + rotate: false + xy: 525, 191 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor-frame0 + rotate: false + xy: 543, 191 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor-frame1 + rotate: false + xy: 561, 191 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor-frame2 + rotate: false + xy: 713, 301 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor-liquid + rotate: false + xy: 710, 283 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +biomattercompressor-top + rotate: false + xy: 739, 310 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +blast-mixer + rotate: false + xy: 977, 413 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-blast-mixer + rotate: false + xy: 977, 413 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +centrifuge-liquid + rotate: false + xy: 321, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cryofluidmixer-bottom + rotate: false + xy: 375, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cryofluidmixer-liquid + rotate: false + xy: 393, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cryofluidmixer-top + rotate: false + xy: 411, 116 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cultivator + rotate: false + xy: 429, 122 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cultivator-middle + rotate: false + xy: 447, 122 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +cultivator-top + rotate: false + xy: 465, 122 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +lavasmelter + rotate: false + xy: 545, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +oilrefinery + rotate: false + xy: 552, 151 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-weaver + rotate: false + xy: 202, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +phase-weaver-bottom + rotate: false + xy: 238, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +phase-weaver-weave + rotate: false + xy: 220, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +plastanium-compressor-top + rotate: false + xy: 256, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +poweralloysmelter-top + rotate: false + xy: 238, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +pulverizer + rotate: false + xy: 607, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +pulverizer-rotator + rotate: false + xy: 617, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +separator-liquid + rotate: false + xy: 672, 159 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +core-open + rotate: false + xy: 359, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-1 + rotate: false + xy: 957, 349 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-1-top + rotate: false + xy: 921, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-2 + rotate: false + xy: 995, 419 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-2-top + rotate: false + xy: 995, 401 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-3 + rotate: false + xy: 765, 330 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-3-top + rotate: false + xy: 586, 284 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-4 + rotate: false + xy: 180, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +block-4-top + rotate: false + xy: 214, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +arc + rotate: false + xy: 1013, 425 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +arc-heat + rotate: false + xy: 1013, 413 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +cyclone + rotate: false + xy: 572, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +mass-driver-turret + rotate: false + xy: 572, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +duo + rotate: false + xy: 977, 346 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +fuse + rotate: false + xy: 598, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +spectre + rotate: false + xy: 598, 258 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +hail + rotate: false + xy: 933, 357 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +scatter + rotate: false + xy: 933, 357 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +hail-heat + rotate: false + xy: 933, 345 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +lancer + rotate: false + xy: 166, 71 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +lancer-heat + rotate: false + xy: 184, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +meltdown + rotate: false + xy: 855, 445 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +ripple + rotate: false + xy: 665, 231 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +ripple-heat + rotate: false + xy: 665, 205 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +salvo + rotate: false + xy: 292, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +salvo-heat + rotate: false + xy: 328, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +salvo-panel-left + rotate: false + xy: 310, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +salvo-panel-right + rotate: false + xy: 346, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +scorch + rotate: false + xy: 428, 56 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +scorch-shoot + rotate: false + xy: 440, 56 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +swarmer + rotate: false + xy: 328, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +wave + rotate: false + xy: 256, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +wave-liquid + rotate: false + xy: 274, 63 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +drone-factory-top + rotate: false + xy: 831, 380 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +drone-factory-top-open + rotate: false + xy: 849, 381 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +fabricator-factory-top + rotate: false + xy: 828, 362 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +fabricator-factory-top-open + rotate: false + xy: 829, 344 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +mech-factory + rotate: false + xy: 793, 362 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +dart-ship-factory-open + rotate: false + xy: 483, 122 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +delta-mech-factory-open + rotate: false + xy: 831, 398 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +javelin-ship-factory-open + rotate: false + xy: 166, 89 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +reconstructor-open + rotate: false + xy: 274, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +repair-point-turret + rotate: false + xy: 627, 98 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ship-factory + rotate: false + xy: 691, 205 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +door-large-open + rotate: false + xy: 849, 399 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +door-open + rotate: false + xy: 967, 346 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +beam + rotate: false + xy: 1022, 499 + size: 1, 12 + orig: 1, 12 + offset: 0, 0 + index: -1 +beam-end + rotate: false + xy: 131, 107 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +blank + rotate: false + xy: 572, 168 + size: 1, 1 + orig: 1, 1 + offset: 0, 0 + index: -1 +bullet + rotate: false + xy: 692, 435 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +bullet-back + rotate: false + xy: 400, 106 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +casing + rotate: false + xy: 991, 431 + size: 2, 4 + orig: 2, 4 + offset: 0, 0 + index: -1 +clear + rotate: false + xy: 567, 138 + size: 1, 1 + orig: 1, 1 + offset: 0, 0 + index: -1 +enemyarrow + rotate: false + xy: 845, 445 + size: 8, 7 + orig: 8, 7 + offset: 0, 0 + index: -1 +error + rotate: false + xy: 717, 189 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +laser + rotate: false + xy: 1022, 485 + size: 1, 12 + orig: 1, 12 + offset: 0, 0 + index: -1 +laser-end + rotate: false + xy: 157, 129 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +laserfull + rotate: false + xy: 791, 388 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +minelaser + rotate: false + xy: 1022, 471 + size: 1, 12 + orig: 1, 12 + offset: 0, 0 + index: -1 +minelaser-end + rotate: false + xy: 811, 388 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +missile + rotate: false + xy: 481, 158 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +missile-back + rotate: false + xy: 476, 57 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +scorch1 + rotate: false + xy: 717, 230 + size: 7, 25 + orig: 7, 25 + offset: 0, 0 + index: -1 +scorch2 + rotate: false + xy: 819, 361 + size: 7, 25 + orig: 7, 25 + offset: 0, 0 + index: -1 +scorch3 + rotate: false + xy: 717, 203 + size: 7, 25 + orig: 7, 25 + offset: 0, 0 + index: -1 +scorch4 + rotate: false + xy: 418, 168 + size: 7, 25 + orig: 7, 25 + offset: 0, 0 + index: -1 +scorch5 + rotate: false + xy: 411, 134 + size: 7, 25 + orig: 7, 25 + offset: 0, 0 + index: -1 +shell + rotate: false + xy: 487, 57 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +shell-back + rotate: false + xy: 974, 366 + size: 9, 9 + orig: 9, 9 + offset: 0, 0 + index: -1 +shot + rotate: false + xy: 682, 159 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +transfer + rotate: false + xy: 805, 330 + size: 1, 12 + orig: 1, 12 + offset: 0, 0 + index: -1 +transfer-arrow + rotate: false + xy: 741, 201 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +transfer-end + rotate: false + xy: 487, 189 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +block-icon-arc + rotate: false + xy: 151, 107 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +block-icon-biomattercompressor + rotate: false + xy: 427, 158 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-blast-drill + rotate: false + xy: 612, 284 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-bridge-conduit + rotate: false + xy: 951, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +bridge-conduit + rotate: false + xy: 951, 335 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-bridge-conveyor + rotate: false + xy: 728, 291 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +bridge-conveyor + rotate: false + xy: 728, 291 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-carbide-drill + rotate: false + xy: 728, 281 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-carbide-wall + rotate: false + xy: 728, 271 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +carbide-wall + rotate: false + xy: 728, 271 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-carbide-wall-large + rotate: false + xy: 445, 158 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +carbide-wall-large + rotate: false + xy: 445, 158 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-centrifuge + rotate: false + xy: 420, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +centrifuge + rotate: false + xy: 420, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-combustion-generator + rotate: false + xy: 728, 261 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +combustion-generator + rotate: false + xy: 728, 261 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-conduit + rotate: false + xy: 728, 251 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-conveyor + rotate: false + xy: 726, 241 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +conveyor + rotate: false + xy: 726, 241 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-core + rotate: false + xy: 638, 284 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +core + rotate: false + xy: 638, 284 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-cryofluidmixer + rotate: false + xy: 438, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-cultivator + rotate: false + xy: 757, 310 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-cyclone + rotate: false + xy: 685, 309 + size: 26, 26 + orig: 26, 26 + offset: 0, 0 + index: -1 +block-icon-dart-ship-factory + rotate: false + xy: 775, 312 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +dart-ship-factory + rotate: false + xy: 775, 312 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-deepwater + rotate: false + xy: 726, 231 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +deepwater + rotate: false + xy: 726, 231 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-deflector-wall + rotate: false + xy: 726, 221 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +deflector-wall + rotate: false + xy: 726, 221 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-wall + rotate: false + xy: 726, 221 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-phase-wall + rotate: false + xy: 726, 221 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-deflector-wall-large + rotate: false + xy: 487, 171 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +deflector-wall-large + rotate: false + xy: 487, 171 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +phase-wall-large + rotate: false + xy: 487, 171 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-phase-wall-large + rotate: false + xy: 487, 171 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-delta-mech-factory + rotate: false + xy: 579, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +delta-mech-factory + rotate: false + xy: 579, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-dirt + rotate: false + xy: 726, 211 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +dirt1 + rotate: false + xy: 726, 211 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-distributor + rotate: false + xy: 597, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +distributor + rotate: false + xy: 597, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-door + rotate: false + xy: 863, 309 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +door + rotate: false + xy: 863, 309 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-door-large + rotate: false + xy: 615, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +door-large + rotate: false + xy: 615, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-drone-factory + rotate: false + xy: 633, 188 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-drop-point + rotate: false + xy: 861, 299 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +drop-point + rotate: false + xy: 861, 299 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-duo + rotate: false + xy: 1013, 401 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +block-icon-fabricator-factory + rotate: false + xy: 665, 187 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-fuse + rotate: false + xy: 763, 384 + size: 26, 26 + orig: 26, 26 + offset: 0, 0 + index: -1 +block-icon-fusion-reactor + rotate: false + xy: 248, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +block-icon-grass + rotate: false + xy: 289, 53 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +grass1 + rotate: false + xy: 289, 53 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-hail + rotate: false + xy: 962, 371 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +block-icon-ice + rotate: false + xy: 289, 43 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ice1 + rotate: false + xy: 289, 43 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-icerock + rotate: false + xy: 289, 33 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +icerock1 + rotate: false + xy: 289, 33 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-incinerator + rotate: false + xy: 299, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +incinerator + rotate: false + xy: 299, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-itemsource + rotate: false + xy: 309, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +itemsource + rotate: false + xy: 309, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-itemvoid + rotate: false + xy: 299, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +itemvoid + rotate: false + xy: 299, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-javelin-ship-factory + rotate: false + xy: 683, 187 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +javelin-ship-factory + rotate: false + xy: 683, 187 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-junction + rotate: false + xy: 319, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +junction + rotate: false + xy: 319, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-lancer + rotate: false + xy: 466, 202 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +block-icon-laser-drill + rotate: false + xy: 456, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-lava + rotate: false + xy: 299, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +lava + rotate: false + xy: 299, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-liquid-junction + rotate: false + xy: 309, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-junction + rotate: false + xy: 309, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-liquid-router + rotate: false + xy: 329, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-liquid-tank + rotate: false + xy: 684, 283 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-liquidsource + rotate: false + xy: 309, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquidsource + rotate: false + xy: 309, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-mass-driver + rotate: false + xy: 177, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +mass-driver + rotate: false + xy: 177, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +core-top + rotate: false + xy: 177, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-mechanical-pump + rotate: false + xy: 319, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +mechanical-pump + rotate: false + xy: 319, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-meltdown + rotate: false + xy: 1, 6 + size: 34, 34 + orig: 34, 34 + offset: 0, 0 + index: -1 +block-icon-melter + rotate: false + xy: 339, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +melter + rotate: false + xy: 339, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-metalfloor + rotate: false + xy: 319, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +metalfloor1 + rotate: false + xy: 319, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-nuclear-reactor + rotate: false + xy: 203, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +nuclear-reactor + rotate: false + xy: 203, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-oil + rotate: false + xy: 329, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +oil + rotate: false + xy: 329, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-oil-extractor + rotate: false + xy: 229, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-overflow-gate + rotate: false + xy: 349, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +overflow-gate + rotate: false + xy: 349, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-phase-conduit + rotate: false + xy: 329, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-conduit + rotate: false + xy: 329, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-phase-conveyor + rotate: false + xy: 339, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +phase-conveyor + rotate: false + xy: 339, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-phase-weaver + rotate: false + xy: 463, 158 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-plasma-drill + rotate: false + xy: 282, 161 + size: 32, 32 + orig: 32, 32 + offset: 0, 0 + index: -1 +block-icon-plastanium-compressor + rotate: false + xy: 474, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +plastanium-compressor + rotate: false + xy: 474, 140 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-power-node + rotate: false + xy: 359, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +power-node + rotate: false + xy: 359, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-power-node-large + rotate: false + xy: 793, 344 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +power-node-large + rotate: false + xy: 793, 344 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-powerinfinite + rotate: false + xy: 339, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +powerinfinite + rotate: false + xy: 339, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-powervoid + rotate: false + xy: 349, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +powervoid + rotate: false + xy: 349, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-pulse-conduit + rotate: false + xy: 369, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-pulverizer + rotate: false + xy: 349, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-pyratite-mixer + rotate: false + xy: 793, 312 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +pyratite-mixer + rotate: false + xy: 793, 312 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-reconstructor + rotate: false + xy: 811, 343 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +drone-factory + rotate: false + xy: 811, 343 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +fabricator-factory + rotate: false + xy: 811, 343 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +reconstructor + rotate: false + xy: 811, 343 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-repair-point + rotate: false + xy: 359, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +repair-point + rotate: false + xy: 359, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-resupply-point + rotate: false + xy: 379, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +resupply-point + rotate: false + xy: 379, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-ripple + rotate: false + xy: 737, 354 + size: 26, 26 + orig: 26, 26 + offset: 0, 0 + index: -1 +block-icon-rock + rotate: false + xy: 359, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +rock1 + rotate: false + xy: 359, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-rotary-pump + rotate: false + xy: 177, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +rotary-pump + rotate: false + xy: 177, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-rtg-generator + rotate: false + xy: 369, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +rtg-generator + rotate: false + xy: 369, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-salvo + rotate: false + xy: 566, 290 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +block-icon-sand + rotate: false + xy: 389, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sand1 + rotate: false + xy: 389, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-scorch + rotate: false + xy: 921, 357 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +block-icon-separator + rotate: false + xy: 369, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +separator + rotate: false + xy: 369, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-shrub + rotate: false + xy: 379, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +shrub + rotate: false + xy: 379, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-silicon-smelter + rotate: false + xy: 195, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +silicon-smelter + rotate: false + xy: 195, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-smelter + rotate: false + xy: 379, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +smelter + rotate: false + xy: 379, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-snow + rotate: false + xy: 389, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +snow1 + rotate: false + xy: 389, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-solar-panel + rotate: false + xy: 389, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +solar-panel + rotate: false + xy: 389, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-solar-panel-large + rotate: false + xy: 255, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +solar-panel-large + rotate: false + xy: 255, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-solidifer + rotate: false + xy: 399, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +solidifer + rotate: false + xy: 399, 55 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-sortedunloader + rotate: false + xy: 399, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sortedunloader + rotate: false + xy: 399, 45 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-sorter + rotate: false + xy: 399, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +sorter + rotate: false + xy: 399, 35 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-space + rotate: false + xy: 293, 23 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +space + rotate: false + xy: 293, 23 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-spectre + rotate: false + xy: 819, 454 + size: 34, 34 + orig: 34, 34 + offset: 0, 0 + index: -1 +block-icon-splitter + rotate: false + xy: 293, 13 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +splitter + rotate: false + xy: 293, 13 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-stone + rotate: false + xy: 293, 3 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +stone1 + rotate: false + xy: 293, 3 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-swarmer + rotate: false + xy: 157, 149 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +block-icon-thermal-generator + rotate: false + xy: 213, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +thermal-generator + rotate: false + xy: 213, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-thermal-pump + rotate: false + xy: 231, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +thermal-pump + rotate: false + xy: 231, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-thorium-wall + rotate: false + xy: 303, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +thorium-wall + rotate: false + xy: 303, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-thorium-wall-large + rotate: false + xy: 249, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +thorium-wall-large + rotate: false + xy: 249, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-titanium-conveyor + rotate: false + xy: 303, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +titanium-conveyor + rotate: false + xy: 303, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-tungsten-drill + rotate: false + xy: 313, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-tungsten-wall + rotate: false + xy: 303, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +tungsten-wall + rotate: false + xy: 303, 5 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-tungsten-wall-large + rotate: false + xy: 267, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +tungsten-wall-large + rotate: false + xy: 267, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-turbine-generator + rotate: false + xy: 285, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +turbine-generator + rotate: false + xy: 285, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-unloader + rotate: false + xy: 313, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +unloader + rotate: false + xy: 313, 15 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-vault + rotate: false + xy: 281, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +vault + rotate: false + xy: 281, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-warp-gate + rotate: false + xy: 307, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +warp-gate + rotate: false + xy: 307, 135 + size: 24, 24 + orig: 24, 24 + offset: 0, 0 + index: -1 +block-icon-water + rotate: false + xy: 323, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +water + rotate: false + xy: 323, 25 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +block-icon-water-extractor + rotate: false + xy: 303, 117 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +block-icon-wave + rotate: false + xy: 664, 290 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +liquid-icon-cryofluid + rotate: false + xy: 532, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-icon-lava + rotate: false + xy: 542, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-icon-oil + rotate: false + xy: 552, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-icon-water + rotate: false + xy: 562, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +mech-icon-alpha-mech + rotate: false + xy: 279, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-dart-ship + rotate: false + xy: 492, 141 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-delta-mech + rotate: false + xy: 506, 141 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-halberd-ship + rotate: false + xy: 508, 155 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-trident-ship + rotate: false + xy: 508, 155 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-omega-mech + rotate: false + xy: 863, 319 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-tau-mech + rotate: false + xy: 863, 319 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +ore-coal-grass1 + rotate: false + xy: 550, 141 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-grass2 + rotate: false + xy: 527, 111 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-grass3 + rotate: false + xy: 537, 121 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-ice1 + rotate: false + xy: 547, 131 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-ice2 + rotate: false + xy: 562, 151 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-ice3 + rotate: false + xy: 560, 141 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-sand1 + rotate: false + xy: 527, 101 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-sand2 + rotate: false + xy: 537, 111 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-sand3 + rotate: false + xy: 547, 121 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-snow1 + rotate: false + xy: 557, 131 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-snow2 + rotate: false + xy: 537, 101 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-snow3 + rotate: false + xy: 547, 111 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-stone1 + rotate: false + xy: 557, 121 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-stone2 + rotate: false + xy: 547, 101 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-coal-stone3 + rotate: false + xy: 557, 111 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-grass1 + rotate: false + xy: 557, 101 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-grass2 + rotate: false + xy: 577, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-grass3 + rotate: false + xy: 587, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-ice1 + rotate: false + xy: 597, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-ice2 + rotate: false + xy: 607, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-ice3 + rotate: false + xy: 617, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-sand1 + rotate: false + xy: 627, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-sand2 + rotate: false + xy: 637, 178 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-sand3 + rotate: false + xy: 575, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-snow1 + rotate: false + xy: 585, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-snow2 + rotate: false + xy: 595, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-snow3 + rotate: false + xy: 605, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-stone1 + rotate: false + xy: 615, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-stone2 + rotate: false + xy: 625, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-lead-stone3 + rotate: false + xy: 635, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-grass1 + rotate: false + xy: 572, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-grass2 + rotate: false + xy: 582, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-grass3 + rotate: false + xy: 592, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-ice1 + rotate: false + xy: 602, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-ice2 + rotate: false + xy: 612, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-ice3 + rotate: false + xy: 622, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-sand1 + rotate: false + xy: 632, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-sand2 + rotate: false + xy: 572, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-sand3 + rotate: false + xy: 582, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-snow1 + rotate: false + xy: 592, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-snow2 + rotate: false + xy: 602, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-snow3 + rotate: false + xy: 612, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-stone1 + rotate: false + xy: 622, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-stone2 + rotate: false + xy: 632, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-thorium-stone3 + rotate: false + xy: 645, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-grass1 + rotate: false + xy: 655, 168 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-grass2 + rotate: false + xy: 642, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-grass3 + rotate: false + xy: 642, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-ice1 + rotate: false + xy: 652, 158 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-ice2 + rotate: false + xy: 652, 148 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-ice3 + rotate: false + xy: 570, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-sand1 + rotate: false + xy: 580, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-sand2 + rotate: false + xy: 590, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-sand3 + rotate: false + xy: 600, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-snow1 + rotate: false + xy: 610, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-snow2 + rotate: false + xy: 620, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-snow3 + rotate: false + xy: 630, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-stone1 + rotate: false + xy: 640, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-stone2 + rotate: false + xy: 650, 138 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-titanium-stone3 + rotate: false + xy: 567, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-grass1 + rotate: false + xy: 567, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-grass2 + rotate: false + xy: 577, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-grass3 + rotate: false + xy: 567, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-ice1 + rotate: false + xy: 577, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-ice2 + rotate: false + xy: 587, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-ice3 + rotate: false + xy: 577, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-sand1 + rotate: false + xy: 587, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-sand2 + rotate: false + xy: 597, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-sand3 + rotate: false + xy: 587, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-snow1 + rotate: false + xy: 597, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-snow2 + rotate: false + xy: 607, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-snow3 + rotate: false + xy: 597, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-stone1 + rotate: false + xy: 607, 118 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-stone2 + rotate: false + xy: 617, 128 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +ore-tungsten-stone3 + rotate: false + xy: 607, 108 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +unit-icon-scout + rotate: false + xy: 893, 341 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +unit-icon-titan + rotate: false + xy: 382, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +unit-icon-vtol + rotate: false + xy: 907, 355 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +vtol + rotate: false + xy: 907, 355 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +item-biomatter + rotate: false + xy: 721, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-blast-compound + rotate: false + xy: 731, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-carbide + rotate: false + xy: 775, 302 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-coal + rotate: false + xy: 785, 302 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-lead + rotate: false + xy: 795, 302 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-phase-matter + rotate: false + xy: 507, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-plastanium + rotate: false + xy: 505, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-pyratite + rotate: false + xy: 517, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-sand + rotate: false + xy: 527, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-silicon + rotate: false + xy: 515, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-stone + rotate: false + xy: 537, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-surge-alloy + rotate: false + xy: 525, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-thorium + rotate: false + xy: 547, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-titanium + rotate: false + xy: 535, 171 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +item-tungsten + rotate: false + xy: 557, 181 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +liquid-icon + rotate: false + xy: 522, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 +alpha-mech + rotate: false + xy: 73, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +alpha-mech-base + rotate: false + xy: 87, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +delta-mech-base + rotate: false + xy: 87, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +omega-mech-base + rotate: false + xy: 87, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +tau-mech-base + rotate: false + xy: 87, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +scout-base + rotate: false + xy: 87, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +alpha-mech-leg + rotate: false + xy: 101, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +omega-mech-leg + rotate: false + xy: 101, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +tau-mech-leg + rotate: false + xy: 101, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +scout-leg + rotate: false + xy: 101, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +delta-mech + rotate: false + xy: 1011, 387 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +delta-mech-leg + rotate: false + xy: 1011, 373 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +omega-mech + rotate: false + xy: 906, 369 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +tau-mech + rotate: false + xy: 906, 369 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +dart-ship + rotate: false + xy: 791, 330 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +halberd-ship + rotate: false + xy: 223, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +trident-ship + rotate: false + xy: 223, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +javelin-ship + rotate: false + xy: 251, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +mech-icon-javelin-ship + rotate: false + xy: 251, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +circle + rotate: false + xy: 424, 310 + size: 201, 201 + orig: 201, 201 + offset: 0, 0 + index: -1 +shape-3 + rotate: false + xy: 1, 107 + size: 63, 63 + orig: 63, 63 + offset: 0, 0 + index: -1 +shape-4 + rotate: false + xy: 424, 222 + size: 63, 63 + orig: 63, 63 + offset: 0, 0 + index: -1 +shape-5 + rotate: false + xy: 627, 425 + size: 63, 63 + orig: 63, 63 + offset: 0, 0 + index: -1 +shape-6 + rotate: false + xy: 1, 42 + size: 63, 63 + orig: 63, 63 + offset: 0, 0 + index: -1 +shape-7 + rotate: false + xy: 66, 107 + size: 63, 63 + orig: 63, 63 + offset: 0, 0 + index: -1 +border + rotate: false + xy: 996, 471 size: 24, 40 split: 5, 5, 5, 10 orig: 24, 40 @@ -1673,15 +4403,23 @@ border index: -1 button rotate: false - xy: 680, 448 + xy: 99, 15 size: 24, 40 split: 10, 10, 6, 10 orig: 24, 40 offset: 0, 0 index: -1 +textarea + rotate: false + xy: 99, 15 + size: 24, 40 + split: 10, 10, 9, 11 + orig: 24, 40 + offset: 0, 0 + index: -1 button-down rotate: false - xy: 43, 86 + xy: 131, 127 size: 24, 40 split: 10, 10, 6, 10 orig: 24, 40 @@ -1689,7 +4427,7 @@ button-down index: -1 button-over rotate: false - xy: 43, 86 + xy: 131, 127 size: 24, 40 split: 10, 10, 6, 10 orig: 24, 40 @@ -1697,7 +4435,7 @@ button-over index: -1 button-map rotate: false - xy: 495, 245 + xy: 73, 15 size: 24, 40 split: 10, 10, 5, 10 orig: 24, 40 @@ -1705,7 +4443,7 @@ button-map index: -1 button-map-down rotate: false - xy: 126, 130 + xy: 685, 337 size: 24, 40 split: 10, 10, 5, 10 orig: 24, 40 @@ -1713,7 +4451,7 @@ button-map-down index: -1 button-map-over rotate: false - xy: 126, 130 + xy: 685, 337 size: 24, 40 split: 10, 10, 5, 10 orig: 24, 40 @@ -1721,7 +4459,7 @@ button-map-over index: -1 button-select rotate: false - xy: 627, 317 + xy: 333, 135 size: 24, 24 split: 4, 4, 4, 4 orig: 24, 24 @@ -1729,672 +4467,799 @@ button-select index: -1 check-off rotate: false - xy: 521, 253 + xy: 557, 209 size: 28, 32 orig: 28, 32 offset: 0, 0 index: -1 check-on rotate: false - xy: 706, 456 + xy: 703, 412 size: 28, 32 orig: 28, 32 offset: 0, 0 index: -1 check-on-over rotate: false - xy: 27, 26 + xy: 733, 412 size: 28, 32 orig: 28, 32 offset: 0, 0 index: -1 check-over rotate: false - xy: 178, 138 + xy: 763, 412 size: 28, 32 orig: 28, 32 offset: 0, 0 index: -1 clear rotate: false - xy: 663, 382 + xy: 921, 345 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 cursor rotate: false - xy: 57, 26 + xy: 679, 373 size: 4, 4 orig: 4, 4 offset: 0, 0 index: -1 discord-banner rotate: false - xy: 1, 128 + xy: 489, 243 size: 81, 42 orig: 81, 42 offset: 0, 0 index: -1 discord-banner-over rotate: false - xy: 1, 128 + xy: 489, 243 size: 81, 42 orig: 81, 42 offset: 0, 0 index: -1 controller-cursor rotate: false - xy: 810, 495 + xy: 339, 117 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-about rotate: false - xy: 706, 440 + xy: 292, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-add rotate: false - xy: 736, 436 + xy: 308, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-admin rotate: false - xy: 752, 436 + xy: 324, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-admin-small rotate: false - xy: 256, 127 + xy: 731, 311 size: 6, 6 orig: 6, 6 offset: 0, 0 index: -1 icon-areaDelete rotate: false - xy: 669, 358 + xy: 945, 357 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-arrow rotate: false - xy: 178, 122 - size: 14, 14 - orig: 14, 14 + xy: 429, 104 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +icon-arrow-16 + rotate: false + xy: 429, 104 + size: 16, 16 + orig: 16, 16 offset: 0, 0 index: -1 icon-arrow-down rotate: false - xy: 669, 346 + xy: 945, 345 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-arrow-left rotate: false - xy: 669, 334 + xy: 879, 327 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-arrow-right rotate: false - xy: 669, 322 + xy: 877, 315 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-arrow-up rotate: false - xy: 736, 424 + xy: 416, 94 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-back rotate: false - xy: 398, 177 + xy: 447, 104 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-ban rotate: false - xy: 208, 119 + xy: 340, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-break + rotate: false + xy: 465, 104 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-cancel rotate: false - xy: 224, 119 - size: 14, 14 - orig: 14, 14 + xy: 483, 104 + size: 16, 16 + orig: 16, 16 offset: 0, 0 index: -1 icon-chat rotate: false - xy: 748, 424 + xy: 428, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-check rotate: false - xy: 240, 119 - size: 14, 14 - orig: 14, 14 - offset: 0, 0 - index: -1 -icon-close - rotate: false - xy: 1, 86 - size: 40, 40 - orig: 40, 40 - offset: 0, 0 - index: -1 -icon-close-down - rotate: false - xy: 84, 130 - size: 40, 40 - orig: 40, 40 - offset: 0, 0 - index: -1 -icon-close-over - rotate: false - xy: 453, 245 - size: 40, 40 - orig: 40, 40 + xy: 867, 399 + size: 16, 16 + orig: 16, 16 offset: 0, 0 index: -1 icon-crafting rotate: false - xy: 760, 424 + xy: 440, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-cursor rotate: false - xy: 575, 272 + xy: 452, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-defense rotate: false - xy: 587, 272 + xy: 464, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-dev-builds rotate: false - xy: 49, 10 + xy: 356, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-discord rotate: false - xy: 416, 179 + xy: 372, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-distribution rotate: false - xy: 73, 36 + xy: 476, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-donate rotate: false - xy: 432, 182 + xy: 388, 65 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-dots rotate: false - xy: 264, 127 + xy: 177, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-editor rotate: false - xy: 280, 127 + xy: 177, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-egg + rotate: false + xy: 193, 47 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +icon-elevation + rotate: false + xy: 867, 381 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +icon-eraser + rotate: false + xy: 885, 401 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-exit rotate: false - xy: 296, 127 + xy: 193, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-file + rotate: false + xy: 885, 383 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +icon-file-image + rotate: false + xy: 903, 401 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-file-text rotate: false - xy: 312, 127 + xy: 209, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-fill rotate: false - xy: 846, 495 + xy: 903, 383 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-floppy rotate: false - xy: 342, 125 + xy: 209, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-floppy-16 + rotate: false + xy: 921, 401 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 icon-folder rotate: false - xy: 525, 237 + xy: 225, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-folder-parent rotate: false - xy: 541, 237 + xy: 225, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-generated + rotate: false + xy: 66, 57 + size: 48, 48 + orig: 48, 48 + offset: 0, 0 + index: -1 icon-github rotate: false - xy: 256, 111 + xy: 241, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-google-play rotate: false - xy: 272, 111 + xy: 241, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-grid rotate: false - xy: 864, 495 + xy: 921, 383 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-hold rotate: false - xy: 178, 110 + xy: 488, 92 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-holdDelete rotate: false - xy: 557, 245 + xy: 416, 82 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-home rotate: false - xy: 288, 111 + xy: 257, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-host rotate: false - xy: 304, 111 + xy: 257, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-info rotate: false - xy: 557, 233 + xy: 404, 78 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-itch.io rotate: false - xy: 525, 221 + xy: 273, 47 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-item + rotate: false + xy: 404, 66 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +icon-items-none + rotate: false + xy: 711, 161 + size: 8, 8 + orig: 8, 8 + offset: 0, 0 + index: -1 icon-line rotate: false - xy: 882, 495 + xy: 939, 401 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-link rotate: false - xy: 541, 221 + xy: 273, 31 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +icon-liquid + rotate: false + xy: 416, 70 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 icon-load rotate: false - xy: 509, 213 + xy: 181, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-load-image rotate: false - xy: 900, 495 + xy: 939, 383 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-load-map rotate: false - xy: 918, 495 + xy: 957, 401 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-loading rotate: false - xy: 936, 495 + xy: 957, 383 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 +icon-locked + rotate: false + xy: 975, 395 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +icon-logic + rotate: false + xy: 428, 80 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +icon-map + rotate: false + xy: 197, 15 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 icon-menu rotate: false - xy: 557, 221 + xy: 440, 80 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +icon-menu-large + rotate: false + xy: 975, 377 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +icon-missing + rotate: false + xy: 452, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-none rotate: false - xy: 557, 209 + xy: 464, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-pause rotate: false - xy: 352, 113 + xy: 476, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-pencil rotate: false - xy: 954, 495 + xy: 993, 383 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-pencil-small rotate: false - xy: 320, 111 + xy: 213, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-pick rotate: false - xy: 972, 495 + xy: 993, 365 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-play rotate: false - xy: 1002, 483 + xy: 488, 80 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-play-2 rotate: false - xy: 525, 205 + xy: 229, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-players rotate: false - xy: 681, 370 + xy: 500, 81 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-power rotate: false - xy: 681, 358 + xy: 428, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-production rotate: false - xy: 681, 346 + xy: 440, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-quit rotate: false - xy: 541, 205 + xy: 245, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-redo rotate: false - xy: 990, 495 + xy: 665, 169 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-refresh rotate: false - xy: 336, 109 + xy: 261, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rename rotate: false - xy: 627, 301 + xy: 277, 15 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-resize rotate: false - xy: 656, 426 + xy: 683, 169 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-rotate rotate: false - xy: 625, 285 + xy: 400, 90 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-arrow rotate: false - xy: 643, 301 + xy: 492, 155 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-left rotate: false - xy: 641, 285 + xy: 501, 125 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-rotate-right rotate: false - xy: 659, 302 + xy: 501, 109 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save rotate: false - xy: 667, 410 + xy: 501, 93 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-save-image rotate: false - xy: 69, 110 + xy: 701, 171 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-save-map rotate: false - xy: 69, 92 + xy: 719, 171 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-settings rotate: false - xy: 681, 334 + xy: 452, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-terrain rotate: false - xy: 69, 74 + xy: 811, 325 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-tools rotate: false - xy: 667, 394 + xy: 846, 364 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-touch rotate: false - xy: 681, 322 + xy: 464, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-touchDelete rotate: false - xy: 675, 310 + xy: 476, 68 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-trash rotate: false - xy: 762, 474 + xy: 862, 365 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-trash-16 rotate: false - xy: 87, 112 + xy: 829, 326 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-trello rotate: false - xy: 762, 458 + xy: 847, 348 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-tutorial rotate: false - xy: 778, 475 + xy: 847, 332 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-undo rotate: false - xy: 87, 94 + xy: 811, 307 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 +icon-units + rotate: false + xy: 488, 68 + size: 10, 10 + orig: 10, 10 + offset: 0, 0 + index: -1 +icon-unlocks + rotate: false + xy: 847, 316 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 icon-weapon rotate: false - xy: 675, 298 + xy: 500, 69 size: 10, 10 orig: 10, 10 offset: 0, 0 index: -1 icon-wiki rotate: false - xy: 778, 459 + xy: 863, 349 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 icon-zoom rotate: false - xy: 105, 112 + xy: 829, 308 size: 16, 16 orig: 16, 16 offset: 0, 0 index: -1 icon-zoom-small rotate: false - xy: 794, 478 + xy: 863, 333 size: 14, 14 orig: 14, 14 offset: 0, 0 index: -1 +info-banner + rotate: false + xy: 692, 446 + size: 81, 42 + orig: 81, 42 + offset: 0, 0 + index: -1 +inventory + rotate: false + xy: 125, 19 + size: 24, 40 + split: 10, 10, 10, 14 + orig: 24, 40 + offset: 0, 0 + index: -1 logotext rotate: false - xy: 143, 172 + xy: 769, 490 size: 89, 21 orig: 89, 21 offset: 0, 0 index: -1 pane rotate: false - xy: 736, 452 + xy: 819, 416 size: 24, 36 split: 10, 10, 5, 5 orig: 24, 36 @@ -2402,7 +5267,7 @@ pane index: -1 pane-button rotate: false - xy: 627, 343 + xy: 793, 408 size: 24, 36 split: 10, 10, 5, 5 orig: 24, 36 @@ -2410,7 +5275,7 @@ pane-button index: -1 scroll rotate: false - xy: 208, 135 + xy: 711, 345 size: 24, 35 split: 10, 10, 6, 5 orig: 24, 35 @@ -2418,7 +5283,7 @@ scroll index: -1 scroll-horizontal rotate: false - xy: 566, 284 + xy: 143, 169 size: 35, 24 split: 6, 5, 10, 10 orig: 35, 24 @@ -2426,7 +5291,7 @@ scroll-horizontal index: -1 scroll-knob-horizontal rotate: false - xy: 27, 60 + xy: 424, 196 size: 40, 24 split: 10, 6, 0, 24 pad: -1, -1, 10, 10 @@ -2435,7 +5300,7 @@ scroll-knob-horizontal index: -1 scroll-knob-vertical rotate: false - xy: 152, 130 + xy: 151, 23 size: 24, 40 split: 10, 10, 6, 10 orig: 24, 40 @@ -2443,7 +5308,7 @@ scroll-knob-vertical index: -1 scroll-knob-vertical-black rotate: false - xy: 1, 2 + xy: 140, 65 size: 24, 40 split: 10, 10, 6, 10 orig: 24, 40 @@ -2451,42 +5316,42 @@ scroll-knob-vertical-black index: -1 selection rotate: false - xy: 424, 195 + xy: 703, 379 size: 1, 1 orig: 1, 1 offset: 0, 0 index: -1 slider rotate: false - xy: 599, 274 + xy: 181, 107 size: 1, 8 orig: 1, 8 offset: 0, 0 index: -1 slider-knob rotate: false - xy: 627, 381 + xy: 116, 61 size: 22, 44 orig: 22, 44 offset: 0, 0 index: -1 slider-knob-down rotate: false - xy: 656, 444 + xy: 679, 379 size: 22, 44 orig: 22, 44 offset: 0, 0 index: -1 slider-knob-over rotate: false - xy: 656, 444 + xy: 679, 379 size: 22, 44 orig: 22, 44 offset: 0, 0 index: -1 slider-vertical rotate: false - xy: 27, 1 + xy: 131, 169 size: 8, 1 orig: 8, 1 offset: 0, 0 @@ -2514,7 +5379,7 @@ text-sides-over index: -1 textfield rotate: false - xy: 234, 135 + xy: 733, 382 size: 28, 28 split: 6, 6, 6, 6 orig: 28, 28 @@ -2522,7 +5387,7 @@ textfield index: -1 textfield-over rotate: false - xy: 234, 165 + xy: 703, 382 size: 28, 28 split: 2, 2, 2, 2 orig: 28, 28 @@ -2530,14 +5395,14 @@ textfield-over index: -1 white rotate: false - xy: 69, 1 + xy: 855, 485 size: 3, 3 orig: 3, 3 offset: 0, 0 index: -1 window rotate: false - xy: 627, 427 + xy: 656, 310 size: 27, 61 split: 8, 8, 44, 11 orig: 27, 61 @@ -2545,93 +5410,128 @@ window index: -1 window-empty rotate: false - xy: 424, 224 + xy: 627, 310 size: 27, 61 split: 8, 8, 44, 11 orig: 27, 61 offset: 0, 0 index: -1 -beam +drone rotate: false - xy: 69, 48 - size: 8, 8 - orig: 8, 8 + xy: 651, 178 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 -beam-equip +unit-icon-drone rotate: false - xy: 565, 274 - size: 8, 8 - orig: 8, 8 + xy: 651, 178 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 -blaster +fabricator rotate: false - xy: 782, 426 - size: 8, 8 - orig: 8, 8 + xy: 701, 189 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +unit-icon-fabricator + rotate: false + xy: 701, 189 + size: 14, 14 + orig: 14, 14 + offset: 0, 0 + index: -1 +monsoon + rotate: false + xy: 427, 176 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +unit-icon-monsoon + rotate: false + xy: 427, 176 + size: 18, 18 + orig: 18, 18 + offset: 0, 0 + index: -1 +scout + rotate: false + xy: 934, 369 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +titan + rotate: false + xy: 364, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +titan-base + rotate: false + xy: 346, 81 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +titan-leg + rotate: false + xy: 382, 99 + size: 16, 16 + orig: 16, 16 + offset: 0, 0 + index: -1 +vtol-flame + rotate: false + xy: 512, 82 + size: 9, 9 + orig: 9, 9 offset: 0, 0 index: -1 blaster-equip rotate: false - xy: 73, 26 - size: 8, 8 - orig: 8, 8 + xy: 572, 244 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 -clustergun +chain-blaster-equip rotate: false - xy: 352, 103 - size: 8, 8 - orig: 8, 8 + xy: 651, 192 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 -clustergun-equip +flakgun-equip rotate: false - xy: 1014, 477 - size: 8, 8 - orig: 8, 8 + xy: 181, 1 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 -shockgun +flamethrower-equip rotate: false - xy: 589, 242 - size: 8, 8 - orig: 8, 8 + xy: 195, 1 + size: 12, 12 + orig: 12, 12 + offset: 0, 0 + index: -1 +missiles-equip + rotate: false + xy: 878, 367 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 shockgun-equip rotate: false - xy: 589, 232 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -triblaster - rotate: false - xy: 149, 8 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -triblaster-equip - rotate: false - xy: 159, 8 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -vulcan - rotate: false - xy: 200, 109 - size: 8, 8 - orig: 8, 8 - offset: 0, 0 - index: -1 -vulcan-equip - rotate: false - xy: 210, 109 - size: 8, 8 - orig: 8, 8 + xy: 948, 369 + size: 12, 12 + orig: 12, 12 offset: 0, 0 index: -1 diff --git a/core/assets/sprites/sprites.png b/core/assets/sprites/sprites.png index 077a888d44..367ec17fe6 100644 Binary files a/core/assets/sprites/sprites.png and b/core/assets/sprites/sprites.png differ diff --git a/core/assets/ui/uiskin.json b/core/assets/ui/uiskin.json index 8dd34cae0f..ea7082cef6 100644 --- a/core/assets/ui/uiskin.json +++ b/core/assets/ui/uiskin.json @@ -46,9 +46,9 @@ io.anuke.ucore.scene.Skin$TintedDrawable: { invis: {name: white, color: {r: 0, g: 0, b: 0, a: 0} } loadDim: {name: white, color: {r: 0, g: 0, b: 0, a: 0.8} }, chatfield: {name: white, color: {r: 0, g: 0, b: 0, a: 0.2}}, - clear: {name: white, color: {r: 0.1, g: 0.1, b: 0.1, a: 0.75}}, - clear-over: {name: white, color: {r: 1, g: 1, b: 1, a: 0.2} }, - clear-down: {name: white, color: {r: 1, g: 1, b: 1, a: 0.4} } + clear: {name: white, color: {r: 0.1, g: 0.1, b: 0.1, a: 0.75}}, + clear-over: {name: white, color: {r: 1, g: 1, b: 1, a: 0.2} }, + clear-down: {name: white, color: {r: 1, g: 1, b: 1, a: 0.4} } }, io.anuke.ucore.scene.ui.Button$ButtonStyle: { default: {down: button-down, up: button }, @@ -58,6 +58,7 @@ io.anuke.ucore.scene.ui.Button$ButtonStyle: { io.anuke.ucore.scene.ui.TextButton$TextButtonStyle: { default: {over: button-over, disabled: button, font: default-font, fontColor: white, disabledFontColor: grey, down: button-down, up: button, transition: 0 }, discord: {over: discord-banner-over, font: default-font, fontColor: white, up: discord-banner}, + info: {font: default-font, fontColor: white, up: info-banner}, clear: {down: clear-down, up: clear, over: clear-over, font: default-font, fontColor: white, disabledFontColor: grey }, empty: {font: default-font}, toggle: {font: default-font, fontColor: white, checked: button-down, down: button-down, up: button, over: button-over, disabled: button, disabledFontColor: grey } @@ -71,17 +72,13 @@ io.anuke.ucore.scene.ui.ImageButton$ImageButtonStyle: { toggle: {checked: button-down, down: button-down, up: button, imageDisabledColor: lightgray, imageUpColor: white }, togglemap: {down: button-map-down, up: button-map }, select: {checked: button-select, up: clear }, - close-window: {up: button, imageUp: icon-close, imageOver: icon-close-over, imageDown: icon-close-down, disabled: button } -}, -io.anuke.ucore.scene.ui.ImageTextButton$ImageTextButtonStyle: { - default: {down: button-down, up: button, over: button-over, disabled: button, font: default-font, fontColor: white, disabledFontColor: grey }, - toggle: {checked: button-down, down: button-down, up: button, font: default-font, fontColor: white, over: button-over, disabled: button, disabledFontColor: grey } + clear: {down: clear-down, up: clear, over: clear-over}, }, io.anuke.ucore.scene.ui.ScrollPane$ScrollPaneStyle: { - default: {background: border, vScroll: scroll, vScrollKnob: scroll-knob-vertical}, + default: {background: border, vScroll: scroll, vScrollKnob: scroll-knob-vertical-black}, horizontal: {background: border, vScroll: scroll, vScrollKnob: scroll-knob-vertical, hScroll: scroll-horizontal, hScrollKnob: scroll-knob-horizontal}, - volume: {background: button-map, vScroll: scroll, vScrollKnob: scroll-knob-vertical}, - clear: {vScroll: scroll, vScrollKnob: scroll-knob-vertical}, + volume: {background: button-map, vScroll: scroll, vScrollKnob: scroll-knob-vertical-black}, + clear: {vScroll: scroll, vScrollKnob: scroll-knob-vertical-black}, clear-black: {vScroll: scroll, vScrollKnob: scroll-knob-vertical-black} }, io.anuke.ucore.scene.ui.Window$WindowStyle: { @@ -104,11 +101,10 @@ io.anuke.ucore.scene.ui.Label$LabelStyle: { }, io.anuke.ucore.scene.ui.TextField$TextFieldStyle: { default: {font: default-font-chat, fontColor: white, disabledFontColor: grey, selection: selection, background: button, cursor: cursor, messageFont: default-font, messageFontColor: grey } + textarea: {font: default-font-chat, fontColor: white, disabledFontColor: grey, selection: selection, background: textarea, cursor: cursor, messageFont: default-font, messageFontColor: grey } } + io.anuke.ucore.scene.ui.CheckBox$CheckBoxStyle: { default: {checkboxOn: check-on, checkboxOff: check-off, checkboxOnOver: check-on-over, checkboxOver: check-over, font: default-font, fontColor: white, disabledFontColor: grey } -}, -io.anuke.ucore.scene.ui.List$ListStyle: { - default: {fontColorUnselected: white, fontColorSelected: white, font: default-font } } } diff --git a/core/build.gradle b/core/build.gradle index bd295c0c39..549dddef1e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -3,25 +3,6 @@ sourceCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' sourceSets.main.java.srcDirs = ["src/"] -import com.badlogic.gdx.tools.texturepacker.TexturePacker - -task packSprites(){ - doLast { - TexturePacker.process("core/assets-raw/sprites/", "core/assets/sprites/", "sprites.atlas") - } -} - -task packUI(){ - doLast { - TexturePacker.process("core/assets-raw/ui/", "core/assets/ui/", "uiskin.atlas") - } -} - -task pack(){ - dependsOn 'packSprites' - dependsOn 'packUI' -} - eclipse.project { name = appName + "-core" -} \ No newline at end of file +} diff --git a/core/src/Mindustry.gwt.xml b/core/src/Mindustry.gwt.xml index c964b6179d..95e2e371aa 100644 --- a/core/src/Mindustry.gwt.xml +++ b/core/src/Mindustry.gwt.xml @@ -1,19 +1,22 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/Mindustry.java b/core/src/io/anuke/mindustry/Mindustry.java index 22fcea6b65..804baca4e4 100644 --- a/core/src/io/anuke/mindustry/Mindustry.java +++ b/core/src/io/anuke/mindustry/Mindustry.java @@ -1,42 +1,42 @@ package io.anuke.mindustry; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Pixmap.Filter; -import com.badlogic.gdx.graphics.PixmapIO; import io.anuke.mindustry.core.*; -import io.anuke.mindustry.io.BlockLoader; import io.anuke.mindustry.io.BundleLoader; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.modules.ModuleCore; import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.*; -public class Mindustry extends ModuleCore { +public class Mindustry extends ModuleCore{ - @Override - public void init(){ - debug = Platform.instance.isDebug(); + @Override + public void init(){ + Timers.mark(); - Log.setUseColors(false); - BundleLoader.load(); - BlockLoader.load(); + Vars.init(); - module(logic = new Logic()); - module(world = new World()); - module(control = new Control()); - module(renderer = new Renderer()); - module(ui = new UI()); - module(netServer = new NetServer()); - module(netClient = new NetClient()); - module(netCommon = new NetCommon()); - } + debug = Platform.instance.isDebug(); - @Override - public void render(){ - super.render(); - threads.handleRender(); - } + Log.setUseColors(false); + BundleLoader.load(); + ContentLoader.load(); + + module(logic = new Logic()); + module(world = new World()); + module(control = new Control()); + module(renderer = new Renderer()); + module(ui = new UI()); + module(netServer = new NetServer()); + module(netClient = new NetClient()); + + Log.info("Time to load [total]: {0}", Timers.elapsed()); + } + + @Override + public void render(){ + super.render(); + threads.handleRender(); + } } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 14353fe324..18cb9c3ea7 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -4,154 +4,175 @@ import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.IntMap; import io.anuke.mindustry.core.*; -import io.anuke.mindustry.entities.Bullet; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.effect.Puddle; import io.anuke.mindustry.entities.effect.Shield; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.net.EditLog; -import io.anuke.mindustry.net.ClientDebug; -import io.anuke.mindustry.net.ServerDebug; -import io.anuke.ucore.UCore; -import io.anuke.ucore.entities.EffectEntity; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.Version; +import io.anuke.mindustry.net.Net; import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.EffectEntity; +import io.anuke.ucore.entities.trait.DrawTrait; import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.OS; +import io.anuke.ucore.util.Translator; + import java.util.Locale; public class Vars{ - - public static final boolean testMobile = false; - //shorthand for whether or not this is running on android - public static final boolean mobile = (Gdx.app.getType() == ApplicationType.Android) || - Gdx.app.getType() == ApplicationType.iOS || testMobile; - public static final boolean ios = Gdx.app.getType() == ApplicationType.iOS; - public static final boolean android = Gdx.app.getType() == ApplicationType.Android; - //shorthand for whether or not this is running on GWT - public static final boolean gwt = (Gdx.app.getType() == ApplicationType.WebGL); - //whether to send block state change events to players - public static final boolean syncBlockState = false; - //how far away from the player blocks can be placed - public static final float placerange = 66; - //respawn time in frames - public static final float respawnduration = 60*4; - //time between waves in frames (on normal mode) - public static final float wavespace = 60*60*(mobile ? 1 : 1); - //waves can last no longer than 3 minutes, otherwise the next one spawns - public static final float maxwavespace = 60*60*4f; - //advance time the pathfinding starts at - public static final float aheadPathfinding = 60*15; - //how far away from spawn points the player can't place blocks - public static final float enemyspawnspace = 65; - //discord group URL - public static final String discordURL = "https://discord.gg/BKADYds"; - - public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; - public static final String macAppDir = UCore.getProperty("user.home") + "/Library/Application Support/"; - //directory for user-created map data - public static final FileHandle customMapDirectory = gwt ? null : UCore.isAssets() ? - Gdx.files.local("../../desktop/mindustry-maps") : - OS.isMac ? (Gdx.files.absolute(macAppDir).child("maps/")) : - Gdx.files.local("mindustry-maps/"); - //save file directory - public static final FileHandle saveDirectory = gwt ? null : UCore.isAssets() ? - Gdx.files.local("../../desktop/mindustry-saves") : - OS.isMac ? (Gdx.files.absolute(macAppDir).child("saves/")) : - Gdx.files.local("mindustry-saves/"); - //scale of the font - public static float fontscale = Math.max(Unit.dp.scl(1f)/2f, 0.5f); - //camera zoom displayed on startup - public static final int baseCameraScale = Math.round(Unit.dp.scl(4)); - //how much the zoom changes every zoom button press (unused?) - public static final int zoomScale = Math.round(Unit.dp.scl(1)); - //if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available - public static boolean debug = false; - public static boolean debugNet = true; - public static boolean console = false; - //whether the player can clip through walls - public static boolean noclip = false; - //whether to draw chunk borders - public static boolean debugChunks = false; - //whether turrets have infinite ammo (only with debug) - public static boolean infiniteAmmo = true; - //whether to show paths of enemies - public static boolean showPaths = false; - //if false, player is always hidden - public static boolean showPlayer = true; - //whether to hide ui, only on debug - public static boolean showUI = true; + //respawn time in frames + public static final float respawnduration = 60 * 4; + //time between waves in frames (on normal mode) + public static final float wavespace = 60 * 60 * 2f; + //waves can last no longer than 3 minutes, otherwise the next one spawns + public static final float maxwavespace = 60 * 60 * 4f; + //set ridiculously high for now + public static final float coreBuildRange = 800999f; + //discord group URL + public static final String discordURL = "https://discord.gg/BKADYds"; + public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases"; + public static final int maxTextLength = 150; + public static final int maxNameLength = 40; + public static final int maxCharNameLength = 20; + public static final int saveSlots = 64; + public static final float itemSize = 5f; + public static final int tilesize = 8; + public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"), + new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), + new Locale("ita"), new Locale("es"), new Locale("zh","TW")}; + public static final Color[] playerColors = { + Color.valueOf("82759a"), + Color.valueOf("c0c1c5"), + Color.valueOf("fff0e7"), + Color.valueOf("7d2953"), + Color.valueOf("ff074e"), + Color.valueOf("ff072a"), + Color.valueOf("ff76a6"), + Color.valueOf("a95238"), + Color.valueOf("ffa108"), + Color.valueOf("feeb2c"), + Color.valueOf("ffcaa8"), + Color.valueOf("008551"), + Color.valueOf("00e339"), + Color.valueOf("423c7b"), + Color.valueOf("4b5ef1"), + Color.valueOf("2cabfe"), + }; + //server port + public static final int port = 6567; + public static final int webPort = 6568; + public static boolean testMobile; + //shorthand for whether or not this is running on android or ios + public static boolean mobile; + public static boolean ios; + public static boolean android; + //shorthand for whether or not this is running on GWT + public static boolean gwt; + //directory for user-created map data + public static FileHandle customMapDirectory; + //save file directory + public static FileHandle saveDirectory; + public static String mapExtension = "mmap"; + public static String saveExtension = "msav"; + //scale of the font + public static float fontScale; + //camera zoom displayed on startup + public static int baseCameraScale; + //if true, player speed will be increased, massive amounts of resources will be given on start, and other debug options will be available + public static boolean debug = false; + public static boolean console = false; + //whether the player can clip through walls + public static boolean noclip = false; + //whether turrets have infinite ammo (only with debug) + public static boolean infiniteAmmo = true; + //whether to show paths of enemies + public static boolean showPaths = false; + //if false, player is always hidden + public static boolean showPlayer = true; + //whether to hide ui, only on debug + public static boolean showUI = true; //whether to show block debug public static boolean showBlockDebug = false; + public static boolean showFog = true; + public static boolean headless = false; + public static float controllerMin = 0.25f; + public static float baseControllerSpeed = 11f; + //only if smoothCamera + public static boolean snapCamera = true; + public static GameState state; + public static ThreadHandler threads; - public static boolean headless = false; + public static Control control; + public static Logic logic; + public static Renderer renderer; + public static UI ui; + public static World world; + public static NetServer netServer; + public static NetClient netClient; - public static float controllerMin = 0.25f; + public static Player[] players = {}; - public static float baseControllerSpeed = 11f; + public static EntityGroup playerGroup; + public static EntityGroup tileGroup; + public static EntityGroup bulletGroup; + public static EntityGroup shieldGroup; + public static EntityGroup effectGroup; + public static EntityGroup groundEffectGroup; + public static EntityGroup itemGroup; - public static final int saveSlots = 64; - //amount of drops that are left when breaking a block - public static final float breakDropAmount = 0.5f; - - public static Array currentEditLogs = new Array<>(); - - //only if smoothCamera - public static boolean snapCamera = true; - - public static final int tilesize = 8; + public static EntityGroup puddleGroup; + public static EntityGroup fireGroup; + public static EntityGroup[] unitGroups; - public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"), - new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), new Locale("ita"), new Locale("es") , new Locale("zh","TW")}; + public static final Translator[] tmptr = new Translator[]{new Translator(), new Translator(), new Translator(), new Translator()}; - public static final Color[] playerColors = { - Color.valueOf("82759a"), - Color.valueOf("c0c1c5"), - Color.valueOf("fff0e7"), - Color.valueOf("7d2953"), - Color.valueOf("ff074e"), - Color.valueOf("ff072a"), - Color.valueOf("ff76a6"), - Color.valueOf("a95238"), - Color.valueOf("ffa108"), - Color.valueOf("feeb2c"), - Color.valueOf("ffcaa8"), - Color.valueOf("008551"), - Color.valueOf("00e339"), - Color.valueOf("423c7b"), - Color.valueOf("4b5ef1"), - Color.valueOf("2cabfe"), - }; + public static void init(){ + Version.init(); - //server port - public static final int port = 6567; - public static final int webPort = 6568; + playerGroup = Entities.addGroup(Player.class).enableMapping(); + tileGroup = Entities.addGroup(TileEntity.class, false); + bulletGroup = Entities.addGroup(Bullet.class).enableMapping(); + shieldGroup = Entities.addGroup(Shield.class, false); + effectGroup = Entities.addGroup(EffectEntity.class, false); + groundEffectGroup = Entities.addGroup(DrawTrait.class, false); + puddleGroup = Entities.addGroup(Puddle.class, false).enableMapping(); + itemGroup = Entities.addGroup(ItemDrop.class).enableMapping(); + fireGroup = Entities.addGroup(Fire.class, false).enableMapping(); + unitGroups = new EntityGroup[Team.all.length]; - public static final GameState state = new GameState(); - public static final ThreadHandler threads = new ThreadHandler(Platform.instance.getThreadProvider()); + for(Team team : Team.all){ + unitGroups[team.ordinal()] = Entities.addGroup(BaseUnit.class).enableMapping(); + } - public static final ServerDebug serverDebug = new ServerDebug(); - public static final ClientDebug clientDebug = new ClientDebug(); + for(EntityGroup group : Entities.getAllGroups()){ + group.setRemoveListener(entity -> { + if(entity instanceof SyncTrait && Net.client()){ + netClient.addRemovedEntity((entity).getID()); + } + }); + } - public static Control control; - public static Logic logic; - public static Renderer renderer; - public static UI ui; - public static World world; - public static NetCommon netCommon; - public static NetServer netServer; - public static NetClient netClient; - - public static Player player; + threads = new ThreadHandler(Platform.instance.getThreadProvider()); - public static final EntityGroup playerGroup = Entities.addGroup(Player.class).enableMapping(); - public static final EntityGroup enemyGroup = Entities.addGroup(Enemy.class).enableMapping(); - public static final EntityGroup tileGroup = Entities.addGroup(TileEntity.class, false); - public static final EntityGroup bulletGroup = Entities.addGroup(Bullet.class); - public static final EntityGroup shieldGroup = Entities.addGroup(Shield.class, false); - public static final EntityGroup effectGroup = Entities.addGroup(EffectEntity.class, false); + mobile = Gdx.app.getType() == ApplicationType.Android || Gdx.app.getType() == ApplicationType.iOS || testMobile; + ios = Gdx.app.getType() == ApplicationType.iOS; + android = Gdx.app.getType() == ApplicationType.Android; + gwt = Gdx.app.getType() == ApplicationType.WebGL; + + if(!gwt){ + customMapDirectory = OS.getAppDataDirectory("Mindustry").child("maps/"); + saveDirectory = OS.getAppDataDirectory("Mindustry").child("saves/"); + } + + fontScale = Math.max(Unit.dp.scl(1f) / 2f, 0.5f); + baseCameraScale = Math.round(Unit.dp.scl(4)); + } } diff --git a/core/src/io/anuke/mindustry/ai/BlockIndexer.java b/core/src/io/anuke/mindustry/ai/BlockIndexer.java new file mode 100644 index 0000000000..ae21cd4e13 --- /dev/null +++ b/core/src/io/anuke/mindustry/ai/BlockIndexer.java @@ -0,0 +1,328 @@ +package io.anuke.mindustry.ai; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Bits; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.game.EventType.TileChangeEvent; +import io.anuke.mindustry.game.EventType.WorldLoadEvent; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.entities.trait.Entity; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.EnumSet; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.*; + +//TODO consider using quadtrees for finding specific types of blocks within an area +//TODO maybe use Arrays instead of ObjectSets? + +/** + * Class used for indexing special target blocks for AI. + */ +public class BlockIndexer{ + /** + * Size of one ore quadrant. + */ + private final static int oreQuadrantSize = 20; + /** + * Size of one structure quadrant. + */ + private final static int structQuadrantSize = 12; + + /** + * Set of all ores that are being scanned. + */ + private final ObjectSet scanOres = ObjectSet.with(Items.tungsten, Items.coal, Items.lead, Items.thorium, Items.titanium); + private final ObjectSet itemSet = new ObjectSet<>(); + /** + * Stores all ore quadtrants on the map. + */ + private ObjectMap> ores; + /** + * Tags all quadrants. + */ + private Bits[] structQuadrants; + + /** + * Maps teams to a map of flagged tiles by type. + */ + private ObjectMap> enemyMap = new ObjectMap<>(); + /** + * Maps teams to a map of flagged tiles by type. + */ + private ObjectMap> allyMap = new ObjectMap<>(); + /** + * Empty map for invalid teams. + */ + private ObjectMap> emptyMap = new ObjectMap<>(); + /** + * Maps tile positions to their last known tile index data. + */ + private IntMap typeMap = new IntMap<>(); + /** + * Empty array used for returning. + */ + private ObjectSet emptyArray = new ObjectSet<>(); + + public BlockIndexer(){ + Events.on(TileChangeEvent.class, tile -> { + if(typeMap.get(tile.packedPosition()) != null){ + TileIndex index = typeMap.get(tile.packedPosition()); + for(BlockFlag flag : index.flags){ + getMap(index.team).get(flag).remove(tile); + } + } + process(tile); + updateQuadrant(tile); + }); + + Events.on(WorldLoadEvent.class, () -> { + enemyMap.clear(); + allyMap.clear(); + typeMap.clear(); + ores = null; + + //create bitset for each team type that contains each quadrant + structQuadrants = new Bits[Team.all.length]; + for(int i = 0; i < Team.all.length; i++){ + structQuadrants[i] = new Bits(Mathf.ceil(world.width() / (float) structQuadrantSize) * Mathf.ceil(world.height() / (float) structQuadrantSize)); + } + + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + process(world.tile(x, y)); + } + } + + for(int x = 0; x < quadWidth(); x++){ + for(int y = 0; y < quadHeight(); y++){ + updateQuadrant(world.tile(x * structQuadrantSize, y * structQuadrantSize)); + } + } + + scanOres(); + }); + } + + /** + * Get all allied blocks with a flag. + */ + public ObjectSet getAllied(Team team, BlockFlag type){ + return (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray); + } + + /** + * Get all enemy blocks with a flag. + */ + public ObjectSet getEnemy(Team team, BlockFlag type){ + return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray); + } + + public TileEntity findTile(Team team, float x, float y, float range, Predicate pred){ + Entity closest = null; + float dst = 0; + + for(int rx = Math.max((int) ((x - range) / tilesize / structQuadrantSize), 0); rx <= (int) ((x + range) / tilesize / structQuadrantSize) && rx < quadWidth(); rx++){ + for(int ry = Math.max((int) ((y - range) / tilesize / structQuadrantSize), 0); ry <= (int) ((y + range) / tilesize / structQuadrantSize) && ry < quadHeight(); ry++){ + + if(!getQuad(team, rx, ry)) continue; + + for(int tx = rx * structQuadrantSize; tx < (rx + 1) * structQuadrantSize && tx < world.width(); tx++){ + for(int ty = ry * structQuadrantSize; ty < (ry + 1) * structQuadrantSize && ty < world.height(); ty++){ + Tile other = world.tile(tx, ty); + + if(other == null || other.entity == null || !pred.test(other)) continue; + + TileEntity e = other.entity; + + float ndst = Vector2.dst(x, y, e.x, e.y); + if(ndst < range && (closest == null || ndst < dst)){ + dst = ndst; + closest = e; + } + } + } + } + } + + return (TileEntity) closest; + } + + /** + * Returns a set of tiles that have ores of the specified type nearby. + * While each tile in the set is not guaranteed to have an ore directly on it, + * each tile will at least have an ore within {@link #oreQuadrantSize} / 2 blocks of it. + * Only specific ore types are scanned. See {@link #scanOres}. + */ + public ObjectSet getOrePositions(Item item){ + return ores.get(item, emptyArray); + } + + /** + * Find the closest ore block relative to a position. + */ + public Tile findClosestOre(float xp, float yp, Item item){ + Tile tile = Geometry.findClosest(xp, yp, world.indexer().getOrePositions(item)); + + if(tile == null) return null; + + for(int x = Math.max(0, tile.x - oreQuadrantSize / 2); x < tile.x + oreQuadrantSize / 2 && x < world.width(); x++){ + for(int y = Math.max(0, tile.y - oreQuadrantSize / 2); y < tile.y + oreQuadrantSize / 2 && y < world.height(); y++){ + Tile res = world.tile(x, y); + if(res.block() == Blocks.air && res.floor().drops != null && res.floor().drops.item == item){ + return res; + } + } + } + + return null; + } + + private void process(Tile tile){ + if(tile.block().flags != null && + tile.getTeam() != Team.none){ + ObjectMap> map = getMap(tile.getTeam()); + + for(BlockFlag flag : tile.block().flags){ + + ObjectSet arr = map.get(flag); + if(arr == null){ + arr = new ObjectSet<>(); + map.put(flag, arr); + } + + arr.add(tile); + + map.put(flag, arr); + } + typeMap.put(tile.packedPosition(), new TileIndex(tile.block().flags, tile.getTeam())); + } + + if(ores == null) return; + + int quadrantX = tile.x / oreQuadrantSize; + int quadrantY = tile.y / oreQuadrantSize; + itemSet.clear(); + + Tile rounded = world.tile(Mathf.clamp(quadrantX * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(quadrantY * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1)); + + //find all items that this quadrant contains + for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ + for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ + Tile result = world.tile(x, y); + if(result.block().drops == null || !scanOres.contains(result.block().drops.item)) continue; + + itemSet.add(result.block().drops.item); + } + } + + //update quadrant at this position + for(Item item : scanOres){ + ObjectSet set = ores.get(item); + + //update quadrant status depending on whether the item is in it + if(!itemSet.contains(item)){ + set.remove(rounded); + }else{ + set.add(rounded); + } + } + } + + private void updateQuadrant(Tile tile){ + //this quadrant is now 'dirty', re-scan the whole thing + int quadrantX = tile.x / structQuadrantSize; + int quadrantY = tile.y / structQuadrantSize; + int index = quadrantX + quadrantY * quadWidth(); + //Log.info("Updating quadrant: {0} {1}", quadrantX, quadrantY); + + for(TeamData data : state.teams.getTeams()){ + + //fast-set this quadrant to 'occupied' if the tile just placed is already of this team + if(tile.getTeam() == data.team && tile.entity != null){ + structQuadrants[data.team.ordinal()].set(index); + continue; //no need to process futher + } + + structQuadrants[data.team.ordinal()].clear(index); + + outer: + for(int x = quadrantX * structQuadrantSize; x < world.width() && x < (quadrantX + 1) * structQuadrantSize; x++){ + for(int y = quadrantY * structQuadrantSize; y < world.height() && y < (quadrantY + 1) * structQuadrantSize; y++){ + Tile result = world.tile(x, y); + //when a targetable block is found, mark this quadrant as occupied and stop searching + if(result.entity != null && result.getTeam() == data.team){ + structQuadrants[data.team.ordinal()].set(index); + break outer; + } + } + } + } + } + + private boolean getQuad(Team team, int quadrantX, int quadrantY){ + int index = quadrantX + quadrantY * Mathf.ceil(world.width() / (float) structQuadrantSize); + return structQuadrants[team.ordinal()].get(index); + } + + private int quadWidth(){ + return Mathf.ceil(world.width() / (float) structQuadrantSize); + } + + private int quadHeight(){ + return Mathf.ceil(world.height() / (float) structQuadrantSize); + } + + private ObjectMap> getMap(Team team){ + if(!state.teams.has(team)) return emptyMap; + return state.teams.get(team).ally ? allyMap : enemyMap; + } + + private void scanOres(){ + ores = new ObjectMap<>(); + + //initialize ore map with empty sets + for(Item item : scanOres){ + ores.put(item, new ObjectSet<>()); + } + + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + int qx = (x / oreQuadrantSize); + int qy = (y / oreQuadrantSize); + + Tile tile = world.tile(x, y); + + //add position of quadrant to list when an ore is found + if(tile.floor().drops != null && scanOres.contains(tile.floor().drops.item) && tile.block() == Blocks.air){ + ores.get(tile.floor().drops.item).add(world.tile( + //make sure to clamp quadrant middle position, since it might go off bounds + Mathf.clamp(qx * oreQuadrantSize + oreQuadrantSize / 2, 0, world.width() - 1), + Mathf.clamp(qy * oreQuadrantSize + oreQuadrantSize / 2, 0, world.height() - 1))); + } + } + } + } + + private class TileIndex{ + public final EnumSet flags; + public final Team team; + + public TileIndex(EnumSet flags, Team team){ + this.flags = flags; + this.team = team; + } + } +} diff --git a/core/src/io/anuke/mindustry/ai/Heuristics.java b/core/src/io/anuke/mindustry/ai/Heuristics.java deleted file mode 100644 index 3a2e7885cb..0000000000 --- a/core/src/io/anuke/mindustry/ai/Heuristics.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.Heuristic; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.function.Predicate; - -import static io.anuke.mindustry.Vars.tilesize; - -public class Heuristics { - /**How many times more it costs to go through a destructible block than an empty block.*/ - static final float solidMultiplier = 5f; - /**How many times more it costs to go through a tile that touches a solid block.*/ - static final float occludedMultiplier = 5f; - - /**Calculates the fastest path. No priorities, just avoids solid blocks.*/ - public static class FastestHeuristic implements Heuristic { - - @Override - public float estimate(Tile node, Tile other){ - //Get Manhattan distance cost - float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); - - //If either one of the tiles is a breakable solid block (that is, it's player-made), - //increase the cost by the tilesize times the solid block multiplier - //Also add the block health, so blocks with more health cost more to traverse - if(node.breakable() && node.block().solid) cost += tilesize* solidMultiplier + node.block().health; - if(other.breakable() && other.block().solid) cost += tilesize* solidMultiplier + other.block().health; - - //if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls - if(node.occluded) cost += tilesize*occludedMultiplier; - - return cost; - } - } - - /**Calculates the fastest and most destructive path based on a block predicate.*/ - public static class DestrutiveHeuristic implements Heuristic { - /**Should return whether a block if "free", e.g. whether it's an important target*/ - private final Predicate frees; - - public DestrutiveHeuristic(Predicate frees){ - this.frees = frees; - } - - @Override - public float estimate(Tile node, Tile other){ - //Get Manhattan distance cost - float cost = Math.abs(node.worldx() - other.worldx()) + Math.abs(node.worldy() - other.worldy()); - - //If either one of the tiles is a breakable solid block (that is, it's player-made), - //increase the cost by the tilesize times the solid block multiplier - //Also add the block health, so blocks with more health cost more to traverse - if(node.breakable() && node.block().solid) cost += tilesize* solidMultiplier + node.block().health; - if(other.breakable() && other.block().solid) cost += tilesize* solidMultiplier + other.block().health; - - //if this block has solid blocks near it, increase the cost, as we don't want enemies hugging walls - if(node.occluded) cost += tilesize*occludedMultiplier; - - if(other.getLinked() != null) other = other.getLinked(); - if(node.getLinked() != null) node = node.getLinked(); - - //check if it's free - if(frees.test(other.block()) || frees.test(node.block())) cost = 0; - - return cost; - } - } -} diff --git a/core/src/io/anuke/mindustry/ai/OptimizedGraph.java b/core/src/io/anuke/mindustry/ai/OptimizedGraph.java deleted file mode 100644 index 02bd9e5ebd..0000000000 --- a/core/src/io/anuke/mindustry/ai/OptimizedGraph.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.anuke.mindustry.ai; - -/**An interface for an indexed graph that doesn't use allocations for connections.*/ -public interface OptimizedGraph{ - /**This is used in the same way as getConnections(), but does not use Connection objects.*/ - N[] connectionsOf(N node); - - /** Returns the unique index of the given node. - * @param node the node whose index will be returned - * @return the unique index of the given node. */ - int getIndex (N node); -} diff --git a/core/src/io/anuke/mindustry/ai/OptimizedPathFinder.java b/core/src/io/anuke/mindustry/ai/OptimizedPathFinder.java deleted file mode 100644 index 6898ee1215..0000000000 --- a/core/src/io/anuke/mindustry/ai/OptimizedPathFinder.java +++ /dev/null @@ -1,268 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.*; -import com.badlogic.gdx.utils.BinaryHeap; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.TimeUtils; - -/**An IndexedAStarPathfinder that uses an OptimizedGraph, and therefore has less allocations.*/ -public class OptimizedPathFinder implements PathFinder { - OptimizedGraph graph; - IntMap> records = new IntMap<>(); - BinaryHeap> openList; - NodeRecord current; - - /** - * The unique ID for each search run. Used to mark nodes. - */ - private int searchId; - - private static final byte UNVISITED = 0; - private static final byte OPEN = 1; - private static final byte CLOSED = 2; - - @SuppressWarnings("unchecked") - public OptimizedPathFinder(OptimizedGraph graph) { - this.graph = graph; - this.openList = new BinaryHeap<>(); - } - - @Override - public boolean searchConnectionPath(N startNode, N endNode, Heuristic heuristic, GraphPath> outPath) { - return false; - } - - @Override - public boolean searchNodePath(N startNode, N endNode, Heuristic heuristic, GraphPath outPath) { - - // Perform AStar - boolean found = search(startNode, endNode, heuristic); - - if (found) { - // Create a path made of nodes - generateNodePath(startNode, outPath); - } - - return found; - } - - protected boolean search(N startNode, N endNode, Heuristic heuristic) { - - initSearch(startNode, endNode, heuristic); - - // Iterate through processing each node - do { - // Retrieve the node with smallest estimated total cost from the open list - current = openList.pop(); - current.category = CLOSED; - - // Terminate if we reached the goal node - if (current.node == endNode) return true; - - visitChildren(endNode, heuristic); - - } while (openList.size > 0); - - // We've run out of nodes without finding the goal, so there's no solution - return false; - } - - @Override - public boolean search(PathFinderRequest request, long timeToRun) { - - long lastTime = TimeUtils.nanoTime(); - - // We have to initialize the search if the status has just changed - if (request.statusChanged) { - initSearch(request.startNode, request.endNode, request.heuristic); - request.statusChanged = false; - } - - // Iterate through processing each node - do { - - // Check the available time - long currentTime = TimeUtils.nanoTime(); - timeToRun -= currentTime - lastTime; - if (timeToRun <= PathFinderQueue.TIME_TOLERANCE) return false; - - // Retrieve the node with smallest estimated total cost from the open list - current = openList.pop(); - current.category = CLOSED; - - // Terminate if we reached the goal node; we've found a path. - if (current.node == request.endNode) { - request.pathFound = true; - - generateNodePath(request.startNode, request.resultPath); - - return true; - } - - // Visit current node's children - visitChildren(request.endNode, request.heuristic); - - // Store the current time - lastTime = currentTime; - - } while (openList.size > 0); - - // The open list is empty and we've not found a path. - request.pathFound = false; - return true; - } - - protected void initSearch(N startNode, N endNode, Heuristic heuristic) { - - // Increment the search id - if (++searchId < 0) searchId = 1; - - // Initialize the open list - openList.clear(); - - // Initialize the record for the start node and add it to the open list - NodeRecord startRecord = getNodeRecord(startNode); - startRecord.node = startNode; - //startRecord.connection = null; - startRecord.costSoFar = 0; - addToOpenList(startRecord, heuristic.estimate(startNode, endNode)); - - current = null; - } - - protected void visitChildren(N endNode, Heuristic heuristic) { - // Get current node's outgoing connections - //Array> connections = graph.getConnections(current.node); - N[] conn = graph.connectionsOf(current.node); - - // Loop through each connection in turn - for (int i = 0; i < conn.length; i++) { - - //Connection connection = connections.get(i) - - // Get the cost estimate for the node - N node = conn[i]; - - if(node == null) continue; - - float addCost = heuristic.estimate(current.node, node); - - float nodeCost = current.costSoFar + addCost; - - float nodeHeuristic; - NodeRecord nodeRecord = getNodeRecord(node); - if (nodeRecord.category == CLOSED) { // The node is closed - - // If we didn't find a shorter route, skip - if (nodeRecord.costSoFar <= nodeCost) continue; - - // We can use the node's old cost values to calculate its heuristic - // without calling the possibly expensive heuristic function - nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar; - } else if (nodeRecord.category == OPEN) { // The node is open - - // If our route is no better, then skip - if (nodeRecord.costSoFar <= nodeCost) continue; - - // Remove it from the open list (it will be re-added with the new cost) - openList.remove(nodeRecord); - - // We can use the node's old cost values to calculate its heuristic - // without calling the possibly expensive heuristic function - nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar; - } else { // the node is unvisited - - // We'll need to calculate the heuristic value using the function, - // since we don't have a node record with a previously calculated value - nodeHeuristic = heuristic.estimate(node, endNode); - } - - // Update node record's cost and connection - nodeRecord.costSoFar = nodeCost; - nodeRecord.from = current.node; //TODO ??? - - // Add it to the open list with the estimated total cost - addToOpenList(nodeRecord, nodeCost + nodeHeuristic); - } - - } - - protected void generateNodePath(N startNode, GraphPath outPath) { - - // Work back along the path, accumulating nodes - // outPath.clear(); - while (current.from != null) { - outPath.add(current.node); - current = records.get(graph.getIndex(current.from)); - } - outPath.add(startNode); - - // Reverse the path - outPath.reverse(); - } - - protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) { - openList.add(nodeRecord, estimatedTotalCost); - nodeRecord.category = OPEN; - } - - protected NodeRecord getNodeRecord(N node) { - if(!records.containsKey(graph.getIndex(node))){ - NodeRecord record = new NodeRecord<>(); - record.node = node; - record.searchId = searchId; - records.put(graph.getIndex(node), record); - return record; - }else{ - return records.get(graph.getIndex(node)); - } - } - - /** - * This nested class is used to keep track of the information we need for each node during the search. - * - * @param Type of node - * @author davebaol - */ - static class NodeRecord extends BinaryHeap.Node { - /** - * The reference to the node. - */ - N node; - N from; - - /** - * The incoming connection to the node - */ - //Connection connection; - - /** - * The actual cost from the start node. - */ - float costSoFar; - - /** - * The node category: {@link #UNVISITED}, {@link #OPEN} or {@link #CLOSED}. - */ - byte category; - - /** - * ID of the current search. - */ - int searchId; - - /** - * Creates a {@code NodeRecord}. - */ - public NodeRecord() { - super(0); - } - - /** - * Returns the estimated total cost. - */ - public float getEstimatedTotalCost() { - return getValue(); - } - } -} diff --git a/core/src/io/anuke/mindustry/ai/Pathfind.java b/core/src/io/anuke/mindustry/ai/Pathfind.java deleted file mode 100644 index 1d65921692..0000000000 --- a/core/src/io/anuke/mindustry/ai/Pathfind.java +++ /dev/null @@ -1,248 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.PathFinderRequest; -import com.badlogic.gdx.ai.pfa.PathSmoother; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.game.SpawnPoint; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Log; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.*; - -public class Pathfind{ - /**Maximum time taken per frame on pathfinding for a single path.*/ - private static final long maxTime = 1000000 * 5; - - /**Tile graph, for determining conenctions between two tiles*/ - TileGraph graph = new TileGraph(); - /**Smoother that removes extra nodes from a path.*/ - PathSmoother smoother = new PathSmoother(new Raycaster()); - /**temporary vector2 for calculations*/ - Vector2 vector = new Vector2(); - - Vector2 v1 = new Vector2(); - Vector2 v2 = new Vector2(); - Vector2 v3 = new Vector2(); - - /**Finds the position on the path an enemy should move to. - * If the path is not yet calculated, this returns the enemy's position (i. e. "don't move") - * @param enemy The enemy to find a path for - * @return The position the enemy should move to.*/ - public Vector2 find(Enemy enemy){ - //TODO fix -1/-2 node usage - if(enemy.node == -1 || enemy.node == -2){ - findNode(enemy); - } - - if(enemy.node == -2){ - enemy.node = -1; - } - - if(enemy.node < 0 || world.getSpawns().get(enemy.lane).pathTiles == null){ - return vector.set(enemy.x, enemy.y); - } - - Tile[] path = world.getSpawns().get(enemy.lane).pathTiles; - - if(enemy.node >= path.length){ - enemy.node = -1; - return vector.set(enemy.x, enemy.y); - } - - if(enemy.node <= -1){ - return vector.set(enemy.x, enemy.y); - } - - //TODO documentation on what this does - Tile prev = path[enemy.node - 1]; - - Tile target = path[enemy.node]; - - //a bridge has been broken, re-path - if(!world.passable(target.x, target.y)){ - remakePath(); - return vector.set(enemy.x, enemy.y); - } - - float projectLen = Vector2.dst(prev.worldx(), prev.worldy(), target.worldx(), target.worldy()) / 6f; - - Vector2 projection = projectPoint(prev.worldx(), prev.worldy(), - target.worldx(), target.worldy(), enemy.x, enemy.y); - - boolean canProject = true; - - if(projectLen < 8 || !onLine(projection, prev.worldx(), prev.worldy(), target.worldx(), target.worldy())){ - canProject = false; - }else{ - projection.add(v1.set(projectLen, 0).rotate(Angles.angle(prev.worldx(), prev.worldy(), - target.worldx(), target.worldy()))); - } - - float dst = Vector2.dst(enemy.x, enemy.y, target.worldx(), target.worldy()); - float nlinedist = enemy.node >= path.length - 1 ? 9999 : - pointLineDist(path[enemy.node].worldx(), path[enemy.node].worldy(), - path[enemy.node + 1].worldx(), path[enemy.node + 1].worldy(), enemy.x, enemy.y); - - if(dst < 8 || nlinedist < 8){ - if(enemy.node <= path.length-2) - enemy.node ++; - - target = path[enemy.node]; - } - - if(canProject && projection.dst(enemy.x, enemy.y) < Vector2.dst(target.x, target.y, enemy.x, enemy.y)){ - vector.set(projection); - }else{ - vector.set(target.worldx(), target.worldy()); - } - - //near the core, stop - if(enemy.node == path.length - 1){ - vector.set(target.worldx(), target.worldy()); - } - - return vector; - - } - - /**Re-calculate paths for all enemies. Runs when a path changes while moving.*/ - private void remakePath(){ - for(int i = 0; i < enemyGroup.size(); i ++){ - Enemy enemy = enemyGroup.all().get(i); - enemy.node = -1; - } - - resetPaths(); - } - - /**Update the pathfinders and continue calculating the path if it hasn't been calculated yet. - * This method is run each frame.*/ - public void update(){ - - //go through each spawnpoint, and if it's not found a path yet, update it - for(int i = 0; i < world.getSpawns().size; i ++){ - SpawnPoint point = world.getSpawns().get(i); - if(point.request == null || point.finder == null){ - continue; - } - - if(!point.request.pathFound){ - try{ - if(point.finder.search(point.request, maxTime)){ - smoother.smoothPath(point.path); - point.pathTiles = point.path.nodes.toArray(Tile.class); - point.finder = null; - } - }catch (ArrayIndexOutOfBoundsException e){ - //no path - point.request.pathFound = true; - } - } - } - - } - - //1300-1500ms, usually 1400 unoptimized on Caldera - /**Benchmark pathfinding speed. Debugging stuff.*/ - public void benchmark(){ - SpawnPoint point = world.getSpawns().first(); - int amount = 100; - - //warmup - for(int i = 0; i < 100; i ++){ - point.finder.searchNodePath(point.start, world.getCore(), state.difficulty.heuristic, point.path); - point.path.clear(); - } - - Timers.mark(); - for(int i = 0; i < amount; i ++){ - point.finder.searchNodePath(point.start, world.getCore(), state.difficulty.heuristic, point.path); - point.path.clear(); - } - Log.info("Time elapsed: {0}ms\nAverage MS per path: {1}", Timers.elapsed(), Timers.elapsed()/amount); - } - - /**Reset and clear the paths.*/ - public void resetPaths(){ - for(int i = 0; i < world.getSpawns().size; i ++){ - resetPathFor(world.getSpawns().get(i)); - } - } - - private void resetPathFor(SpawnPoint point){ - point.finder = new OptimizedPathFinder<>(graph); - - point.path.clear(); - - point.pathTiles = null; - - point.request = new PathFinderRequest<>(point.start, world.getCore(), state.difficulty.heuristic, point.path); - point.request.statusChanged = true; //IMPORTANT! - } - - /**For an enemy that was just loaded from a save, find the node in the path it should be following.*/ - void findNode(Enemy enemy){ - if(enemy.lane >= world.getSpawns().size || enemy.lane < 0){ - enemy.lane = 0; - } - - if(world.getSpawns().get(enemy.lane).pathTiles == null){ - return; - } - - Tile[] path = world.getSpawns().get(enemy.lane).pathTiles; - - int closest = findClosest(path, enemy.x, enemy.y); - - closest = Mathf.clamp(closest, 1, path.length-1); - if(closest == -1){ - return; - } - - enemy.node = closest; - } - - /**Finds the closest tile to a position, in an array of tiles.*/ - private int findClosest(Tile[] tiles, float x, float y){ - int cindex = -2; - float dst = Float.MAX_VALUE; - - for(int i = 0; i < tiles.length - 1; i ++){ - Tile tile = tiles[i]; - Tile next = tiles[i + 1]; - float d = pointLineDist(tile.worldx(), tile.worldy(), next.worldx(), next.worldy(), x, y); - if(d < dst){ - dst = d; - cindex = i; - } - } - - return cindex + 1; - } - - /**Returns whether a point is on a line.*/ - private boolean onLine(Vector2 vector, float x1, float y1, float x2, float y2){ - return MathUtils.isEqual(vector.dst(x1, y1) + vector.dst(x2, y2), Vector2.dst(x1, y1, x2, y2), 0.01f); - } - - /**Returns distance from a point to a line segment.*/ - private float pointLineDist(float x, float y, float x2, float y2, float px, float py){ - float l2 = Vector2.dst2(x, y, x2, y2); - float t = Math.max(0, Math.min(1, Vector2.dot(px - x, py - y, x2 - x, y2 - y) / l2)); - Vector2 projection = v1.set(x, y).add(v2.set(x2, y2).sub(x, y).scl(t)); // Projection falls on the segment - return projection.dst(px, py); - } - - //TODO documentation - private Vector2 projectPoint(float x1, float y1, float x2, float y2, float pointx, float pointy){ - float px = x2-x1, py = y2-y1, dAB = px*px + py*py; - float u = ((pointx - x1) * px + (pointy - y1) * py) / dAB; - float x = x1 + u * px, y = y1 + u * py; - return v3.set(x, y); //this is D - } -} diff --git a/core/src/io/anuke/mindustry/ai/Pathfinder.java b/core/src/io/anuke/mindustry/ai/Pathfinder.java new file mode 100644 index 0000000000..f3290674be --- /dev/null +++ b/core/src/io/anuke/mindustry/ai/Pathfinder.java @@ -0,0 +1,197 @@ +package io.anuke.mindustry.ai; + +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.ObjectSet.ObjectSetIterator; +import com.badlogic.gdx.utils.Queue; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.mindustry.game.EventType.TileChangeEvent; +import io.anuke.mindustry.game.EventType.WorldLoadEvent; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Log; + +import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.world; + +public class Pathfinder{ + private long maxUpdate = TimeUtils.millisToNanos(4); + private PathData[] paths; + private IntArray blocked = new IntArray(); + + public Pathfinder(){ + Events.on(WorldLoadEvent.class, this::clear); + Events.on(TileChangeEvent.class, tile -> { + + for(TeamData data : state.teams.getTeams()){ + if(data.team != tile.getTeam() && paths[data.team.ordinal()].weights[tile.x][tile.y] >= Float.MAX_VALUE){ + update(tile, data.team); + } + } + + update(tile, tile.getTeam()); + }); + } + + public void update(){ + ObjectSetIterator iterator = new ObjectSetIterator<>(state.teams.getTeams()); + + for(TeamData team : iterator){ + updateFrontier(team.team, maxUpdate); + } + } + + public Tile getTargetTile(Team team, Tile tile){ + float[][] values = paths[team.ordinal()].weights; + + if(values == null) return tile; + + float value = values[tile.x][tile.y]; + + Tile target = null; + float tl = 0f; + for(GridPoint2 point : Geometry.d8){ + int dx = tile.x + point.x, dy = tile.y + point.y; + + Tile other = world.tile(dx, dy); + if(other == null) continue; + + if(values[dx][dy] < value && (target == null || values[dx][dy] < tl) && + !other.solid() && + !(point.x != 0 && point.y != 0 && (world.solid(tile.x + point.x, tile.y) || world.solid(tile.x, tile.y + point.y)))){ //diagonal corner trap + target = other; + tl = values[dx][dy]; + } + } + + if(target == null || tl == Float.MAX_VALUE) return tile; + + return target; + } + + public float getDebugValue(int x, int y){ + return paths[Team.red.ordinal()].weights[x][y]; + } + + public float getValueforTeam(Team team, int x, int y){ + return paths == null ? 0 : paths[team.ordinal()].weights[x][y]; + } + + private boolean passable(Tile tile, Team team){ + return (tile.getWallID() == 0 && !tile.floor().isLiquid && tile.cliffs == 0 && !tile.floor().solid && !(tile.floor().isLiquid && (tile.floor().damageTaken > 0 || tile.floor().drownTime > 0))) + || (tile.breakable() && (tile.getTeam() != team)) || !tile.solid(); + } + + private void update(Tile tile, Team team){ + if(paths[team.ordinal()] != null){ + PathData path = paths[team.ordinal()]; + + if(!passable(tile, team)){ + path.weights[tile.x][tile.y] = Float.MAX_VALUE; + } + + path.search++; + + if(path.lastSearchTime + 1000 / 60 * 3 > TimeUtils.millis()){ + path.frontier.clear(); + } + + path.lastSearchTime = TimeUtils.millis(); + + ObjectSet set = world.indexer().getEnemy(team, BlockFlag.target); + for(Tile other : set){ + path.weights[other.x][other.y] = 0; + path.searches[other.x][other.y] = path.search; + path.frontier.addFirst(other); + } + } + } + + private void createFor(Team team){ + PathData path = new PathData(); + path.search++; + path.frontier.ensureCapacity((world.width() + world.height()) * 3); + + paths[team.ordinal()] = path; + + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + Tile tile = world.tile(x, y); + + if(tile.block().flags != null && state.teams.areEnemies(tile.getTeam(), team) + && tile.block().flags.contains(BlockFlag.target)){ + path.frontier.addFirst(tile); + path.weights[x][y] = 0; + path.searches[x][y] = path.search; + }else{ + path.weights[x][y] = Float.MAX_VALUE; + } + } + } + + updateFrontier(team, -1); + } + + private void updateFrontier(Team team, long nsToRun){ + PathData path = paths[team.ordinal()]; + + long start = TimeUtils.nanoTime(); + + while(path.frontier.size > 0 && (nsToRun < 0 || TimeUtils.timeSinceNanos(start) <= nsToRun)){ + Tile tile = path.frontier.removeLast(); + float cost = path.weights[tile.x][tile.y]; + + if(cost < Float.MAX_VALUE){ + for(GridPoint2 point : Geometry.d4){ + + int dx = tile.x + point.x, dy = tile.y + point.y; + Tile other = world.tile(dx, dy); + + if(other != null && (path.weights[dx][dy] > cost + 1 || path.searches[dx][dy] < path.search) + && passable(other, team)){ + path.frontier.addFirst(world.tile(dx, dy)); + path.weights[dx][dy] = cost + other.cost / 2f; + path.searches[dx][dy] = path.search; + } + } + } + } + } + + private void clear(){ + Timers.mark(); + + paths = new PathData[Team.all.length]; + blocked.clear(); + + for(TeamData data : state.teams.getTeams()){ + PathData path = new PathData(); + paths[data.team.ordinal()] = path; + + createFor(data.team); + } + + state.spawner.checkAllQuadrants(); + + Log.info("Elapsed calculation time: {0}", Timers.elapsed()); + } + + class PathData{ + float[][] weights; + int[][] searches; + int search = 0; + long lastSearchTime; + Queue frontier = new Queue<>(); + + PathData(){ + weights = new float[world.width()][world.height()]; + searches = new int[world.width()][world.height()]; + } + } +} diff --git a/core/src/io/anuke/mindustry/ai/Raycaster.java b/core/src/io/anuke/mindustry/ai/Raycaster.java deleted file mode 100644 index 6ce48805e0..0000000000 --- a/core/src/io/anuke/mindustry/ai/Raycaster.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.utils.Collision; -import com.badlogic.gdx.ai.utils.Ray; -import com.badlogic.gdx.ai.utils.RaycastCollisionDetector; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.util.Geometry; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.tilesize; -import static io.anuke.mindustry.Vars.world; - -public class Raycaster implements RaycastCollisionDetector{ - private boolean found = false; - - @Override - public boolean collides(Ray ray){ - found = false; - - Geometry.iterateLine(0f, ray.start.x, ray.start.y, ray.end.x, ray.end.y, tilesize, (x, y)->{ - if(solid(x, y)){ - found = true; - return; - } - }); - - return found; - } - - @Override - public boolean findCollision(Collision collision, Ray ray){ - Vector2 v = vectorCast(ray.start.x, ray.start.y, ray.end.x, ray.end.y); - if(v == null) return false; - collision.point = v; - collision.normal = v.nor(); - return true; - } - - Vector2 vectorCast(float x0f, float y0f, float x1f, float y1f){ - int x0 = (int)x0f; - int y0 = (int)y0f; - int x1 = (int)x1f; - int y1 = (int)y1f; - int dx = Math.abs(x1 - x0); - int dy = Math.abs(y1 - y0); - - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; - - int err = dx - dy; - int e2; - while(true){ - - if(solid(x0, y0)){ - return new Vector2(x0, y0); - } - if(x0 == x1 && y0 == y1) break; - - e2 = 2 * err; - if(e2 > -dy){ - err = err - dy; - x0 = x0 + sx; - } - - if(e2 < dx){ - err = err + dx; - y0 = y0 + sy; - } - } - return null; - } - - private boolean solid(float x, float y){ - Tile tile = world.tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize)); - - if(tile == null || tile.solid()) return true; - - for(int i = 0; i < 4; i ++){ - Tile near = tile.getNearby(i); - if(near == null || near.solid()) return true; - } - - return false; - } - -} diff --git a/core/src/io/anuke/mindustry/ai/SmoothGraphPath.java b/core/src/io/anuke/mindustry/ai/SmoothGraphPath.java deleted file mode 100644 index 39b1b75ec2..0000000000 --- a/core/src/io/anuke/mindustry/ai/SmoothGraphPath.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.anuke.mindustry.ai; - -import com.badlogic.gdx.ai.pfa.DefaultGraphPath; -import com.badlogic.gdx.ai.pfa.SmoothableGraphPath; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.world.Tile; - -public class SmoothGraphPath extends DefaultGraphPath implements SmoothableGraphPath{ - private Vector2 vector = new Vector2(); - - @Override - public Vector2 getNodePosition(int index){ - Tile tile = nodes.get(index); - return vector.set(tile.worldx(), tile.worldy()); - } - - @Override - public void swapNodes(int index1, int index2){ - nodes.swap(index1, index2); - } - - @Override - public void truncatePath(int newLength){ - nodes.truncate(newLength); - } - - @Override - public void add (Tile node) { - nodes.add(node); - } - -} diff --git a/core/src/io/anuke/mindustry/ai/TileGraph.java b/core/src/io/anuke/mindustry/ai/TileGraph.java deleted file mode 100644 index c4026d6ec7..0000000000 --- a/core/src/io/anuke/mindustry/ai/TileGraph.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.anuke.mindustry.ai; - -import io.anuke.mindustry.world.Tile; - -/**Tilegraph that ignores player-made tiles.*/ -public class TileGraph implements OptimizedGraph { - private Tile[] tiles = new Tile[4]; - - /**Used for the OptimizedPathFinder implementation.*/ - @Override - public Tile[] connectionsOf(Tile node){ - Tile[] nodes = node.getNearby(tiles); - for(int i = 0; i < 4; i ++){ - if(nodes[i] != null && !nodes[i].passable()){ - nodes[i] = null; - } - } - return nodes; - } - - @Override - public int getIndex(Tile node){ - return node.packedPosition(); - } -} diff --git a/core/src/io/anuke/mindustry/ai/WaveSpawner.java b/core/src/io/anuke/mindustry/ai/WaveSpawner.java new file mode 100644 index 0000000000..96635c2576 --- /dev/null +++ b/core/src/io/anuke/mindustry/ai/WaveSpawner.java @@ -0,0 +1,229 @@ +package io.anuke.mindustry.ai; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Bits; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.entities.units.Squad; +import io.anuke.mindustry.game.EventType.WorldLoadEvent; +import io.anuke.mindustry.game.SpawnGroup; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.WaveCreator; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.util.Mathf; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.*; + +public class WaveSpawner{ + private static final int quadsize = 4; + + private Bits quadrants; + + private Array groups; + + private Array flySpawns = new Array<>(); + private Array groundSpawns = new Array<>(); + + public WaveSpawner(){ + Events.on(WorldLoadEvent.class, this::reset); + } + + public void write(DataOutput output) throws IOException{ + output.writeShort(flySpawns.size); + for(FlyerSpawn spawn : flySpawns){ + output.writeFloat(spawn.angle); + } + + output.writeShort(groundSpawns.size); + for(GroundSpawn spawn : groundSpawns){ + output.writeShort((short) spawn.x); + output.writeShort((short) spawn.y); + } + } + + public void read(DataInput input) throws IOException{ + short flya = input.readShort(); + + for(int i = 0; i < flya; i++){ + FlyerSpawn spawn = new FlyerSpawn(); + spawn.angle = input.readFloat(); + flySpawns.add(spawn); + } + + short grounda = input.readShort(); + for(int i = 0; i < grounda; i++){ + GroundSpawn spawn = new GroundSpawn(); + spawn.x = input.readShort(); + spawn.y = input.readShort(); + groundSpawns.add(spawn); + } + } + + public void spawnEnemies(){ + int flyGroups = 0; + int groundGroups = 0; + + //count total subgroups spawned by flying/group types + for(SpawnGroup group : groups){ + int amount = group.getGroupsSpawned(state.wave); + if(group.type.isFlying){ + flyGroups += amount; + }else{ + groundGroups += amount; + } + } + + int addGround = groundGroups - groundSpawns.size, addFly = flyGroups - flySpawns.size; + + //add extra groups if the total exceeds it + for(int i = 0; i < addGround; i++){ + GroundSpawn spawn = new GroundSpawn(); + findLocation(spawn); + groundSpawns.add(spawn); + } + + for(int i = 0; i < addFly; i++){ + FlyerSpawn spawn = new FlyerSpawn(); + findLocation(spawn); + flySpawns.add(spawn); + } + + //store index of last used fly/ground spawn locations + int flyCount = 0, groundCount = 0; + + for(SpawnGroup group : groups){ + int groups = group.getGroupsSpawned(state.wave); + int spawned = group.getUnitsSpawned(state.wave); + + for(int i = 0; i < groups; i++){ + Squad squad = new Squad(); + float spawnX, spawnY; + float spread; + + if(group.type.isFlying){ + FlyerSpawn spawn = flySpawns.get(flyCount); + //TODO verify flyer spawn + + float margin = 40f; //how far away from the edge flying units spawn + spawnX = world.width() * tilesize / 2f + Mathf.sqrwavex(spawn.angle) * (world.width() / 2f * tilesize + margin); + spawnY = world.height() * tilesize / 2f + Mathf.sqrwavey(spawn.angle) * (world.height() / 2f * tilesize + margin); + spread = margin / 1.5f; + + flyCount++; + }else{ + GroundSpawn spawn = groundSpawns.get(groundCount); + checkQuadrant(spawn.x, spawn.y); + if(!getQuad(spawn.x, spawn.y)){ + findLocation(spawn); + } + + spawnX = spawn.x * quadsize * tilesize + quadsize * tilesize / 2f; + spawnY = spawn.y * quadsize * tilesize + quadsize * tilesize / 2f; + spread = quadsize * tilesize / 3f; + + groundCount++; + } + + for(int j = 0; j < spawned; j++){ + BaseUnit unit = group.createUnit(Team.red); + unit.setWave(); + unit.setSquad(squad); + unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread)); + unit.add(); + } + } + } + } + + public void checkAllQuadrants(){ + for(int x = 0; x < quadWidth(); x++){ + for(int y = 0; y < quadHeight(); y++){ + checkQuadrant(x, y); + } + } + } + + private void checkQuadrant(int quadx, int quady){ + setQuad(quadx, quady, true); + + outer: + for(int x = quadx * quadsize; x < world.width() && x < (quadx + 1) * quadsize; x++){ + for(int y = quady * quadsize; y < world.height() && y < (quady + 1) * quadsize; y++){ + Tile tile = world.tile(x, y); + + if(tile == null || tile.solid() || world.pathfinder().getValueforTeam(Team.red, x, y) == Float.MAX_VALUE){ + setQuad(quadx, quady, false); + break outer; + } + } + } + } + + private void reset(){ + flySpawns.clear(); + groundSpawns.clear(); + quadrants = new Bits(quadWidth() * quadHeight()); + + if(groups == null){ + groups = WaveCreator.getSpawns(); + } + } + + private boolean getQuad(int quadx, int quady){ + return quadrants.get(quadx + quady * quadWidth()); + } + + private void setQuad(int quadx, int quady, boolean valid){ + if(valid){ + quadrants.set(quadx + quady * quadWidth()); + }else{ + quadrants.clear(quadx + quady * quadWidth()); + } + } + + //TODO instead of randomly scattering locations around the map, find spawns close to each other + private void findLocation(GroundSpawn spawn){ + spawn.x = -1; + spawn.y = -1; + + int shellWidth = quadWidth() * 2 + quadHeight() * 2 * 6; + shellWidth = Math.min(quadWidth() * quadHeight() / 4, shellWidth); + + Mathf.traverseSpiral(quadWidth(), quadHeight(), Mathf.random(shellWidth), (x, y) -> { + if(getQuad(x, y)){ + spawn.x = x; + spawn.y = y; + return true; + } + + return false; + }); + } + + //TODO instead of randomly scattering locations around the map, find spawns close to each other + private void findLocation(FlyerSpawn spawn){ + spawn.angle = Mathf.random(360f); + } + + private int quadWidth(){ + return Mathf.ceil(world.width() / (float) quadsize); + } + + private int quadHeight(){ + return Mathf.ceil(world.height() / (float) quadsize); + } + + private class FlyerSpawn{ + //square angle + float angle; + } + + private class GroundSpawn{ + //quadrant spawn coordinates + int x, y; + } +} diff --git a/core/src/io/anuke/mindustry/content/AmmoTypes.java b/core/src/io/anuke/mindustry/content/AmmoTypes.java new file mode 100644 index 0000000000..1c784a4736 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/AmmoTypes.java @@ -0,0 +1,204 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.bullets.*; +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.content.fx.ShootFx; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.AmmoType; +import io.anuke.mindustry.type.ContentList; + +public class AmmoTypes implements ContentList{ + public static AmmoType bulletTungsten, bulletLead, bulletCarbide, bulletThorium, bulletSilicon, bulletPyratite, + shotgunTungsten, bombExplosive, bombIncendiary, bombOil, shellCarbide, flamerThermite, weaponMissile, + flakLead, flakExplosive, flakPlastic, flakSurge, missileExplosive, missileIncindiary, missileSurge, + artilleryCarbide, artilleryPlastic, artilleryHoming, artilleryIncindiary, artilleryExplosive, + basicFlame, lancerLaser, lightning, spectreLaser, meltdownLaser, fuseShotgun, oil, water, lava, cryofluid; + + @Override + public void load(){ + + //weapon specific + + shotgunTungsten = new AmmoType(Items.tungsten, WeaponBullets.tungstenShotgun, 2){{ + shootEffect = ShootFx.shootBig; + smokeEffect = ShootFx.shootBigSmoke; + recoil = 1f; + }}; + + shellCarbide = new AmmoType(Items.carbide, WeaponBullets.shellCarbide, 2){{ + shootEffect = ShootFx.shootBig; + smokeEffect = ShootFx.shootBigSmoke; + }}; + + bombExplosive = new AmmoType(Items.blastCompound, WeaponBullets.bombExplosive, 3){{ + shootEffect = Fx.none; + smokeEffect = Fx.none; + }}; + + bombIncendiary = new AmmoType(Items.pyratite, WeaponBullets.bombIncendiary, 3){{ + shootEffect = Fx.none; + smokeEffect = Fx.none; + }}; + + bombOil = new AmmoType(Items.coal, WeaponBullets.bombOil, 3){{ + shootEffect = Fx.none; + smokeEffect = Fx.none; + }}; + + flamerThermite = new AmmoType(Items.pyratite, TurretBullets.basicFlame, 3){{ + shootEffect = ShootFx.shootSmallFlame; + }}; + + weaponMissile = new AmmoType(Items.carbide, MissileBullets.javelin, 2){{ + shootEffect = BulletFx.hitBulletSmall; + smokeEffect = Fx.none; + reloadMultiplier = 1.2f; + }}; + + //bullets + + bulletLead = new AmmoType(Items.lead, StandardBullets.lead, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + reloadMultiplier = 1.6f; + inaccuracy = 5f; + }}; + + bulletTungsten = new AmmoType(Items.tungsten, StandardBullets.tungsten, 2){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + reloadMultiplier = 0.8f; + }}; + + bulletCarbide = new AmmoType(Items.carbide, StandardBullets.carbide, 2){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + reloadMultiplier = 0.6f; + }}; + + bulletThorium = new AmmoType(Items.thorium, StandardBullets.thorium, 2){{ + shootEffect = ShootFx.shootBig; + smokeEffect = ShootFx.shootBigSmoke; + }}; + + bulletSilicon = new AmmoType(Items.silicon, StandardBullets.homing, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + reloadMultiplier = 1.4f; + }}; + + bulletPyratite = new AmmoType(Items.pyratite, StandardBullets.tracer, 3){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + inaccuracy = 3f; + }}; + + //flak + + flakLead = new AmmoType(Items.lead, FlakBullets.lead, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + }}; + + flakExplosive = new AmmoType(Items.blastCompound, FlakBullets.explosive, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + }}; + + flakPlastic = new AmmoType(Items.plastanium, FlakBullets.plastic, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + }}; + + flakSurge = new AmmoType(Items.surgealloy, FlakBullets.surge, 5){{ + shootEffect = ShootFx.shootSmall; + smokeEffect = ShootFx.shootSmallSmoke; + }}; + + //missiles + + missileExplosive = new AmmoType(Items.blastCompound, MissileBullets.explosive, 1){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 1.2f; + }}; + + missileIncindiary = new AmmoType(Items.pyratite, MissileBullets.incindiary, 1){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 1.0f; + }}; + + missileSurge = new AmmoType(Items.surgealloy, MissileBullets.surge, 1){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + }}; + + //artillery + + artilleryCarbide = new AmmoType(Items.carbide, ArtilleryBullets.carbide, 2){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + }}; + + artilleryPlastic = new AmmoType(Items.plastanium, ArtilleryBullets.plastic, 2){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 1.4f; + }}; + + artilleryHoming = new AmmoType(Items.silicon, ArtilleryBullets.homing, 1){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 0.9f; + }}; + + artilleryIncindiary = new AmmoType(Items.pyratite, ArtilleryBullets.incindiary, 2){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 1.2f; + }}; + + artilleryExplosive = new AmmoType(Items.blastCompound, ArtilleryBullets.explosive, 1){{ + shootEffect = ShootFx.shootBig2; + smokeEffect = ShootFx.shootBigSmoke2; + reloadMultiplier = 1.6f; + }}; + + //flame + + basicFlame = new AmmoType(Liquids.oil, TurretBullets.basicFlame, 0.3f){{ + shootEffect = ShootFx.shootSmallFlame; + }}; + + //power + + lancerLaser = new AmmoType(TurretBullets.lancerLaser); + + lightning = new AmmoType(TurretBullets.lightning); + + spectreLaser = new AmmoType(TurretBullets.lancerLaser); + + meltdownLaser = new AmmoType(TurretBullets.lancerLaser); + + fuseShotgun = new AmmoType(Items.tungsten, TurretBullets.fuseShot, 0.1f); + + //liquid + + oil = new AmmoType(Liquids.oil, TurretBullets.oilShot, 0.3f); + + water = new AmmoType(Liquids.water, TurretBullets.waterShot, 0.3f); + + lava = new AmmoType(Liquids.lava, TurretBullets.lavaShot, 0.3f); + + cryofluid = new AmmoType(Liquids.cryofluid, TurretBullets.cryoShot, 0.3f); + + } + + @Override + public Array getAll(){ + return AmmoType.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/Items.java b/core/src/io/anuke/mindustry/content/Items.java new file mode 100644 index 0000000000..d9de2c5513 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/Items.java @@ -0,0 +1,102 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemType; + +public class Items implements ContentList{ + public static Item stone, tungsten, lead, coal, carbide, titanium, thorium, silicon, plastanium, phasematter, surgealloy, + biomatter, sand, blastCompound, pyratite; + + @Override + public void load(){ + + stone = new Item("stone", Color.valueOf("777777")){{ + hardness = 3; + }}; + + tungsten = new Item("tungsten", Color.valueOf("a0b0c8")){{ + type = ItemType.material; + hardness = 1; + cost = 0.75f; + }}; + + lead = new Item("lead", Color.valueOf("8e85a2")){{ + type = ItemType.material; + hardness = 1; + cost = 0.6f; + }}; + + coal = new Item("coal", Color.valueOf("272727")){{ + explosiveness = 0.2f; + flammability = 0.5f; + hardness = 2; + }}; + + carbide = new Item("carbide", Color.valueOf("e2e2e2")){{ + type = ItemType.material; + }}; + + titanium = new Item("titanium", Color.valueOf("8da1e3")){{ + type = ItemType.material; + hardness = 3; + cost = 1.1f; + }}; + + thorium = new Item("thorium", Color.valueOf("f9a3c7")){{ + type = ItemType.material; + explosiveness = 0.1f; + hardness = 4; + radioactivity = 0.5f; + cost = 1.2f; + }}; + + silicon = new Item("silicon", Color.valueOf("53565c")){{ + type = ItemType.material; + cost = 0.9f; + }}; + + plastanium = new Item("plastanium", Color.valueOf("e9ead3")){{ + type = ItemType.material; + flammability = 0.1f; + explosiveness = 0.1f; + cost = 1.5f; + }}; + + phasematter = new Item("phase-matter", Color.valueOf("f4ba6e")){{ + type = ItemType.material; + cost = 1.5f; + }}; + + surgealloy = new Item("surge-alloy", Color.valueOf("b4d5c7")){{ + type = ItemType.material; + }}; + + biomatter = new Item("biomatter", Color.valueOf("648b55")){{ + flammability = 0.4f; + fluxiness = 0.2f; + }}; + + sand = new Item("sand", Color.valueOf("e3d39e")){{ + fluxiness = 0.5f; + }}; + + blastCompound = new Item("blast-compound", Color.valueOf("ff795e")){{ + flammability = 0.2f; + explosiveness = 0.6f; + }}; + + pyratite = new Item("pyratite", Color.valueOf("ffaa5f")){{ + flammability = 0.7f; + explosiveness = 0.2f; + }}; + } + + @Override + public Array getAll(){ + return Item.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/Liquids.java b/core/src/io/anuke/mindustry/content/Liquids.java new file mode 100644 index 0000000000..e1311d4a78 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/Liquids.java @@ -0,0 +1,56 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Liquid; + +public class Liquids implements ContentList{ + public static Liquid water, lava, oil, cryofluid; + + @Override + public void load(){ + + water = new Liquid("water", Color.valueOf("486acd")){ + { + heatCapacity = 0.4f; + tier = 0; + effect = StatusEffects.wet; + } + }; + + lava = new Liquid("lava", Color.valueOf("e37341")){ + { + temperature = 0.8f; + viscosity = 0.8f; + tier = 2; + effect = StatusEffects.melting; + } + }; + + oil = new Liquid("oil", Color.valueOf("313131")){ + { + viscosity = 0.7f; + flammability = 0.6f; + explosiveness = 0.6f; + tier = 1; + effect = StatusEffects.tarred; + } + }; + + cryofluid = new Liquid("cryofluid", Color.SKY){ + { + heatCapacity = 0.9f; + temperature = 0.25f; + tier = 1; + effect = StatusEffects.freezing; + } + }; + } + + @Override + public Array getAll(){ + return Liquid.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/Mechs.java b/core/src/io/anuke/mindustry/content/Mechs.java new file mode 100644 index 0000000000..c946060560 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/Mechs.java @@ -0,0 +1,93 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Mech; +import io.anuke.mindustry.type.Upgrade; + +public class Mechs implements ContentList{ + public static Mech alpha, delta, tau, omega, dart, javelin, trident, halberd; + + /** + * These are not new mechs, just re-assignments for convenience. + */ + public static Mech starterDesktop, starterMobile; + + @Override + public void load(){ + + alpha = new Mech("alpha-mech", false){{ + drillPower = 1; + speed = 0.5f; + weapon = Weapons.blaster; + trailColor = Palette.lightTrail; + }}; + + delta = new Mech("delta-mech", false){{ + drillPower = -1; + speed = 0.63f; + boostSpeed = 0.86f; + itemCapacity = 15; + armor = 30f; + weaponOffsetX = -1; + weaponOffsetY = -1; + weapon = Weapons.shockgun; + ammoCapacity = 50; + trailColor = Color.valueOf("d3ddff"); + }}; + + tau = new Mech("tau-mech", false){{ + drillPower = 2; + speed = 0.5f; + }}; + + omega = new Mech("omega-mech", false){{ + drillPower = 1; + speed = 0.4f; + }}; + + dart = new Mech("dart-ship", true){{ + drillPower = 1; + speed = 0.4f; + maxSpeed = 3f; + drag = 0.1f; + weaponOffsetX = -1; + weaponOffsetY = -1; + trailColor = Palette.lightTrail; + }}; + + javelin = new Mech("javelin-ship", true){{ + drillPower = -1; + speed = 0.4f; + maxSpeed = 3.6f; + drag = 0.09f; + weapon = Weapons.missiles; + trailColor = Color.valueOf("d3ddff"); + }}; + + trident = new Mech("trident-ship", true){{ + drillPower = 1; + speed = 0.4f; + maxSpeed = 3f; + drag = 0.1f; + }}; + + halberd = new Mech("halberd-ship", true){{ + drillPower = 2; + speed = 0.4f; + maxSpeed = 3f; + drag = 0.1f; + }}; + + starterDesktop = alpha; + starterMobile = dart; + } + + @Override + public Array getAll(){ + return Upgrade.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/Recipes.java b/core/src/io/anuke/mindustry/content/Recipes.java new file mode 100644 index 0000000000..9bdae7e9ef --- /dev/null +++ b/core/src/io/anuke/mindustry/content/Recipes.java @@ -0,0 +1,287 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.blocks.*; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.type.Recipe; + +import static io.anuke.mindustry.type.Category.*; + +public class Recipes implements ContentList{ + + @Override + public void load(){ + //WALLS + new Recipe(defense, DefenseBlocks.tungstenWall, new ItemStack(Items.tungsten, 12)); + new Recipe(defense, DefenseBlocks.tungstenWallLarge, new ItemStack(Items.tungsten, 12 * 4)); + + new Recipe(defense, DefenseBlocks.carbideWall, new ItemStack(Items.carbide, 12)); + new Recipe(defense, DefenseBlocks.carbideWallLarge, new ItemStack(Items.carbide, 12 * 4)); + + new Recipe(defense, DefenseBlocks.thoriumWall, new ItemStack(Items.thorium, 12)); + new Recipe(defense, DefenseBlocks.thoriumWallLarge, new ItemStack(Items.thorium, 12 * 4)); + + new Recipe(defense, DefenseBlocks.door, new ItemStack(Items.carbide, 12), new ItemStack(Items.silicon, 8)); + new Recipe(defense, DefenseBlocks.doorLarge, new ItemStack(Items.carbide, 12 * 4), new ItemStack(Items.silicon, 8 * 4)); + + //TURRETS + new Recipe(weapon, TurretBlocks.duo, new ItemStack(Items.tungsten, 40)); + new Recipe(weapon, TurretBlocks.scorch, new ItemStack(Items.tungsten, 50), new ItemStack(Items.carbide, 20)); + new Recipe(weapon, TurretBlocks.hail, new ItemStack(Items.tungsten, 60), new ItemStack(Items.carbide, 35)); + + new Recipe(weapon, TurretBlocks.lancer, new ItemStack(Items.tungsten, 50), new ItemStack(Items.lead, 100), new ItemStack(Items.silicon, 90)); + new Recipe(weapon, TurretBlocks.wave, new ItemStack(Items.carbide, 60), new ItemStack(Items.titanium, 70), new ItemStack(Items.lead, 150)); + new Recipe(weapon, TurretBlocks.swarmer, new ItemStack(Items.carbide, 70), new ItemStack(Items.titanium, 70), new ItemStack(Items.plastanium, 90), new ItemStack(Items.silicon, 60)); + new Recipe(weapon, TurretBlocks.salvo, new ItemStack(Items.tungsten, 210), new ItemStack(Items.carbide, 190), new ItemStack(Items.thorium, 130)); + new Recipe(weapon, TurretBlocks.ripple, new ItemStack(Items.tungsten, 300), new ItemStack(Items.carbide, 220), new ItemStack(Items.thorium, 120)); + + //DISTRIBUTION + new Recipe(distribution, DistributionBlocks.conveyor, new ItemStack(Items.lead, 1)); + new Recipe(distribution, DistributionBlocks.titaniumconveyor, new ItemStack(Items.lead, 2), new ItemStack(Items.titanium, 1)); + new Recipe(distribution, DistributionBlocks.phaseConveyor, new ItemStack(Items.phasematter, 10), new ItemStack(Items.silicon, 15), new ItemStack(Items.lead, 20), new ItemStack(Items.carbide, 20)); + + //starter lead transporation + new Recipe(distribution, DistributionBlocks.junction, new ItemStack(Items.lead, 2)); + new Recipe(distribution, DistributionBlocks.splitter, new ItemStack(Items.lead, 6)); + + //advanced carbide transporation + //new Recipe(distribution, DistributionBlocks.splitter, new ItemStack(Items.carbide, 2), new ItemStack(Items.tungsten, 2)); + new Recipe(distribution, DistributionBlocks.distributor, new ItemStack(Items.carbide, 8), new ItemStack(Items.tungsten, 8)); + new Recipe(distribution, DistributionBlocks.sorter, new ItemStack(Items.carbide, 4), new ItemStack(Items.tungsten, 4)); + new Recipe(distribution, DistributionBlocks.overflowGate, new ItemStack(Items.carbide, 4), new ItemStack(Items.tungsten, 8)); + new Recipe(distribution, DistributionBlocks.bridgeConveyor, new ItemStack(Items.carbide, 8), new ItemStack(Items.tungsten, 8)); + + //CRAFTING + + //smelting + new Recipe(crafting, CraftingBlocks.smelter, new ItemStack(Items.tungsten, 70)); + new Recipe(crafting, CraftingBlocks.arcsmelter, new ItemStack(Items.tungsten, 90), new ItemStack(Items.carbide, 60), new ItemStack(Items.lead, 50)); + new Recipe(crafting, CraftingBlocks.siliconsmelter, new ItemStack(Items.tungsten, 60), new ItemStack(Items.lead, 50)); + + //advanced fabrication + new Recipe(crafting, CraftingBlocks.plastaniumCompressor, new ItemStack(Items.silicon, 160), new ItemStack(Items.lead, 230), new ItemStack(Items.carbide, 120), new ItemStack(Items.titanium, 160)); + new Recipe(crafting, CraftingBlocks.phaseWeaver, new ItemStack(Items.silicon, 260), new ItemStack(Items.lead, 240), new ItemStack(Items.thorium, 150)); + + //TODO implement alloy smelter + //new Recipe(crafting, CraftingBlocks.alloySmelter, new ItemStack(Items.silicon, 160), new ItemStack(Items.lead, 160), new ItemStack(Items.thorium, 140)); + + //misc + new Recipe(crafting, CraftingBlocks.pulverizer, new ItemStack(Items.tungsten, 60), new ItemStack(Items.lead, 50)); + new Recipe(crafting, CraftingBlocks.pyratiteMixer, new ItemStack(Items.tungsten, 100), new ItemStack(Items.lead, 50)); + new Recipe(crafting, CraftingBlocks.blastMixer, new ItemStack(Items.lead, 60), new ItemStack(Items.carbide, 40)); + new Recipe(crafting, CraftingBlocks.cryofluidmixer, new ItemStack(Items.lead, 130), new ItemStack(Items.silicon, 80), new ItemStack(Items.thorium, 90)); + + new Recipe(crafting, CraftingBlocks.solidifier, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 20)); + new Recipe(crafting, CraftingBlocks.melter, new ItemStack(Items.tungsten, 60), new ItemStack(Items.lead, 70), new ItemStack(Items.carbide, 90)); + + new Recipe(crafting, CraftingBlocks.incinerator, new ItemStack(Items.carbide, 10), new ItemStack(Items.lead, 30)); + + //processing + new Recipe(crafting, CraftingBlocks.biomatterCompressor, new ItemStack(Items.lead, 70), new ItemStack(Items.silicon, 60)); + new Recipe(crafting, CraftingBlocks.separator, new ItemStack(Items.tungsten, 60), new ItemStack(Items.carbide, 50)); + new Recipe(crafting, CraftingBlocks.centrifuge, new ItemStack(Items.tungsten, 130), new ItemStack(Items.carbide, 130), new ItemStack(Items.silicon, 60), new ItemStack(Items.titanium, 50)); + + //POWER + new Recipe(power, PowerBlocks.powerNode, new ItemStack(Items.tungsten, 2), new ItemStack(Items.lead, 6)) + .setDependencies(PowerBlocks.combustionGenerator); + new Recipe(power, PowerBlocks.powerNodeLarge, new ItemStack(Items.carbide, 10), new ItemStack(Items.lead, 20), new ItemStack(Items.silicon, 6)) + .setDependencies(PowerBlocks.powerNode); + new Recipe(power, PowerBlocks.battery, new ItemStack(Items.tungsten, 8), new ItemStack(Items.lead, 30), new ItemStack(Items.silicon, 4)) + .setDependencies(PowerBlocks.powerNode); + new Recipe(power, PowerBlocks.batteryLarge, new ItemStack(Items.carbide, 40), new ItemStack(Items.lead, 80), new ItemStack(Items.silicon, 30)) + .setDependencies(PowerBlocks.powerNode); + + //generators - combustion + new Recipe(power, PowerBlocks.combustionGenerator, new ItemStack(Items.tungsten, 50), new ItemStack(Items.lead, 30)); + new Recipe(power, PowerBlocks.turbineGenerator, new ItemStack(Items.tungsten, 70), new ItemStack(Items.carbide, 50), new ItemStack(Items.lead, 80), new ItemStack(Items.silicon, 60)); + + //generators - solar + new Recipe(power, PowerBlocks.solarPanel, new ItemStack(Items.lead, 20), new ItemStack(Items.silicon, 30)); + new Recipe(power, PowerBlocks.largeSolarPanel, new ItemStack(Items.lead, 200), new ItemStack(Items.silicon, 290), new ItemStack(Items.phasematter, 30)); + + //generators - other + new Recipe(power, PowerBlocks.nuclearReactor, new ItemStack(Items.lead, 600), new ItemStack(Items.silicon, 400), new ItemStack(Items.carbide, 300), new ItemStack(Items.thorium, 300)); + + //new Recipe(distribution, StorageBlocks.core, new ItemStack(Items.carbide, 50)); + new Recipe(distribution, StorageBlocks.unloader, new ItemStack(Items.carbide, 40), new ItemStack(Items.silicon, 50)); + new Recipe(distribution, StorageBlocks.sortedunloader, new ItemStack(Items.carbide, 40), new ItemStack(Items.silicon, 70)); + new Recipe(distribution, StorageBlocks.vault, new ItemStack(Items.carbide, 500), new ItemStack(Items.thorium, 350)); + + //DRILLS, PRODUCERS + new Recipe(production, ProductionBlocks.tungstenDrill, new ItemStack(Items.tungsten, 25)); + new Recipe(production, ProductionBlocks.carbideDrill, new ItemStack(Items.tungsten, 50), new ItemStack(Items.carbide, 60)); + new Recipe(production, ProductionBlocks.laserdrill, new ItemStack(Items.tungsten, 90), new ItemStack(Items.carbide, 110), new ItemStack(Items.silicon, 70), new ItemStack(Items.titanium, 80)); + + new Recipe(production, ProductionBlocks.waterextractor, new ItemStack(Items.tungsten, 50), new ItemStack(Items.carbide, 50), new ItemStack(Items.lead, 40)); + new Recipe(production, ProductionBlocks.cultivator, new ItemStack(Items.tungsten, 20), new ItemStack(Items.lead, 50), new ItemStack(Items.silicon, 20)); + new Recipe(production, ProductionBlocks.oilextractor, new ItemStack(Items.tungsten, 300), new ItemStack(Items.carbide, 350), new ItemStack(Items.lead, 230), new ItemStack(Items.thorium, 230), new ItemStack(Items.silicon, 150)); + + //UNITS + + //bodies + + new Recipe(units, UpgradeBlocks.dartFactory, new ItemStack(Items.lead, 150), new ItemStack(Items.silicon, 200), new ItemStack(Items.titanium, 240)) + .setDesktop(); //dart is desktop only, because it's the starter mobile ship + + new Recipe(units, UpgradeBlocks.javelinFactory, new ItemStack(Items.lead, 200), new ItemStack(Items.silicon, 250), new ItemStack(Items.titanium, 300), new ItemStack(Items.plastanium, 200)); + + new Recipe(units, UpgradeBlocks.deltaFactory, new ItemStack(Items.carbide, 160), new ItemStack(Items.silicon, 220), new ItemStack(Items.titanium, 250)) + .setDesktop(); + + //new Recipe(units, UpgradeBlocks.deltaFactory, new ItemStack(Items.tungsten, 30), new ItemStack(Items.lead, 50), new ItemStack(Items.silicon, 30)); + + //actual unit related stuff + new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.tungsten, 50), new ItemStack(Items.lead, 90), new ItemStack(Items.silicon, 130)); + new Recipe(units, UnitBlocks.fabricatorFactory, new ItemStack(Items.carbide, 70), new ItemStack(Items.thorium, 100), new ItemStack(Items.lead, 150), new ItemStack(Items.silicon, 300)); + + new Recipe(units, UnitBlocks.repairPoint, new ItemStack(Items.lead, 30), new ItemStack(Items.tungsten, 30), new ItemStack(Items.silicon, 30)); + new Recipe(units, UnitBlocks.resupplyPoint, new ItemStack(Items.lead, 30), new ItemStack(Items.tungsten, 30), new ItemStack(Items.silicon, 30)); + + //LIQUIDS + new Recipe(liquid, LiquidBlocks.conduit, new ItemStack(Items.lead, 1)) + .setDependencies(CraftingBlocks.smelter); + new Recipe(liquid, LiquidBlocks.pulseConduit, new ItemStack(Items.titanium, 1), new ItemStack(Items.lead, 1)); + new Recipe(liquid, LiquidBlocks.phaseConduit, new ItemStack(Items.phasematter, 10), new ItemStack(Items.silicon, 15), new ItemStack(Items.lead, 20), new ItemStack(Items.titanium, 20)); + + + new Recipe(liquid, LiquidBlocks.liquidRouter, new ItemStack(Items.carbide, 4), new ItemStack(Items.lead, 4)); + new Recipe(liquid, LiquidBlocks.liquidtank, new ItemStack(Items.titanium, 50), new ItemStack(Items.lead, 50), new ItemStack(Items.carbide, 20)); + new Recipe(liquid, LiquidBlocks.liquidJunction, new ItemStack(Items.carbide, 4), new ItemStack(Items.lead, 4)); + new Recipe(liquid, LiquidBlocks.bridgeConduit, new ItemStack(Items.carbide, 8), new ItemStack(Items.lead, 8)); + + new Recipe(liquid, LiquidBlocks.mechanicalPump, new ItemStack(Items.tungsten, 30), new ItemStack(Items.lead, 20)) + .setDependencies(CraftingBlocks.smelter); + new Recipe(liquid, LiquidBlocks.rotaryPump, new ItemStack(Items.tungsten, 140), new ItemStack(Items.lead, 100), new ItemStack(Items.silicon, 40), new ItemStack(Items.titanium, 70)); + + //DEBUG + new Recipe(units, DebugBlocks.itemSource, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.itemVoid, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.liquidSource, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.powerVoid, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.powerInfinite, new ItemStack(Items.carbide, 10), new ItemStack(Items.surgealloy, 5)).setDebug(); + //new Recipe(liquid, LiquidBlocks.thermalPump, new ItemStack(Items.carbide, 10), new ItemStack(Items.surgealloy, 5)); + + /* + new Recipe(production, ProductionBlocks.nucleardrill, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.plasmadrill, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.cultivator, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.waterextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.oilextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40));*/ + + + //new Recipe(distribution, DistributionBlocks.massDriver, new ItemStack(Items.carbide, 1)); + + + /* + + new Recipe(defense, DefenseBlocks.steelwall, new ItemStack(Items.carbide, 12)); + new Recipe(defense, DefenseBlocks.titaniumwall, new ItemStack(Items.titanium, 12)); + new Recipe(defense, DefenseBlocks.diriumwall, new ItemStack(Items.surgealloy, 12)); + new Recipe(defense, DefenseBlocks.steelwalllarge, new ItemStack(Items.carbide, 12 * 4)); + new Recipe(defense, DefenseBlocks.titaniumwalllarge, new ItemStack(Items.titanium, 12 * 4)); + new Recipe(defense, DefenseBlocks.diriumwall, new ItemStack(Items.surgealloy, 12 * 4)); + new Recipe(defense, DefenseBlocks.door, new ItemStack(Items.carbide, 3), new ItemStack(Items.tungsten, 3 * 4)); + new Recipe(defense, DefenseBlocks.largedoor, new ItemStack(Items.carbide, 3 * 4), new ItemStack(Items.tungsten, 3 * 4 * 4)); + new Recipe(defense, DefenseBlocks.deflectorwall, new ItemStack(Items.titanium, 1)); + new Recipe(defense, DefenseBlocks.deflectorwalllarge, new ItemStack(Items.titanium, 1)); + new Recipe(defense, DefenseBlocks.phasewall, new ItemStack(Items.titanium, 1)); + new Recipe(defense, DefenseBlocks.phasewalllarge, new ItemStack(Items.titanium, 1)); + + new Recipe(weapon, TurretBlocks.wave, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.lancer, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.arc, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.swarmer, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.ripple, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.fuse, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.ripple, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.cyclone, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.spectre, new ItemStack(Items.tungsten, 1)); + new Recipe(weapon, TurretBlocks.meltdown, new ItemStack(Items.tungsten, 1)); + + new Recipe(crafting, CraftingBlocks.alloysmelter, new ItemStack(Items.titanium, 50), new ItemStack(Items.carbide, 50)); + new Recipe(crafting, CraftingBlocks.alloyfuser, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 30)); + + new Recipe(crafting, CraftingBlocks.phaseWeaver, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 30)); + new Recipe(crafting, CraftingBlocks.separator, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 30)); + new Recipe(crafting, CraftingBlocks.centrifuge, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 30)); + new Recipe(crafting, CraftingBlocks.siliconsmelter, new ItemStack(Items.carbide, 30), new ItemStack(Items.tungsten, 30)); + new Recipe(crafting, CraftingBlocks.oilRefinery, new ItemStack(Items.carbide, 15), new ItemStack(Items.tungsten, 15)); + new Recipe(crafting, CraftingBlocks.biomatterCompressor, new ItemStack(Items.carbide, 15), new ItemStack(Items.tungsten, 15)); + new Recipe(crafting, CraftingBlocks.plastaniumCompressor, new ItemStack(Items.carbide, 30), new ItemStack(Items.titanium, 15)); + new Recipe(crafting, CraftingBlocks.cryofluidmixer, new ItemStack(Items.carbide, 30), new ItemStack(Items.titanium, 15)); + new Recipe(crafting, CraftingBlocks.pulverizer, new ItemStack(Items.carbide, 10), new ItemStack(Items.tungsten, 10)); + new Recipe(crafting, CraftingBlocks.stoneFormer, new ItemStack(Items.carbide, 10), new ItemStack(Items.tungsten, 10)); + new Recipe(crafting, CraftingBlocks.melter, new ItemStack(Items.carbide, 30), new ItemStack(Items.titanium, 15)); + new Recipe(crafting, CraftingBlocks.incinerator, new ItemStack(Items.carbide, 60), new ItemStack(Items.tungsten, 60)); + + new Recipe(production, ProductionBlocks.tungstenDrill, new ItemStack(Items.tungsten, 25)); + new Recipe(production, ProductionBlocks.reinforcedDrill, new ItemStack(Items.tungsten, 25)); + new Recipe(production, ProductionBlocks.carbideDrill, new ItemStack(Items.tungsten, 25)); + new Recipe(production, ProductionBlocks.titaniumDrill, new ItemStack(Items.tungsten, 25)); + new Recipe(production, ProductionBlocks.laserdrill, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.nucleardrill, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.plasmadrill, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.cultivator, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.waterextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + new Recipe(production, ProductionBlocks.oilextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40)); + + new Recipe(power, PowerBlocks.powerNode, new ItemStack(Items.carbide, 3), new ItemStack(Items.tungsten, 3)); + new Recipe(power, PowerBlocks.powerNodeLarge, new ItemStack(Items.carbide, 3), new ItemStack(Items.tungsten, 3)); + new Recipe(power, PowerBlocks.battery, new ItemStack(Items.carbide, 5), new ItemStack(Items.tungsten, 5)); + new Recipe(power, PowerBlocks.batteryLarge, new ItemStack(Items.carbide, 5), new ItemStack(Items.tungsten, 5)); + new Recipe(power, PowerBlocks.combustionGenerator, new ItemStack(Items.tungsten, 1)); + + new Recipe(power, PowerBlocks.turbineGenerator, new ItemStack(Items.tungsten, 1)); + new Recipe(power, PowerBlocks.thermalGenerator, new ItemStack(Items.carbide, 1)); + new Recipe(power, PowerBlocks.rtgGenerator, new ItemStack(Items.titanium, 1), new ItemStack(Items.carbide, 1)); + new Recipe(power, PowerBlocks.solarPanel, new ItemStack(Items.tungsten, 30), new ItemStack(Items.silicon, 20)); + new Recipe(power, PowerBlocks.largeSolarPanel, new ItemStack(Items.tungsten, 30), new ItemStack(Items.silicon, 20)); + new Recipe(power, PowerBlocks.nuclearReactor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40), new ItemStack(Items.carbide, 50)); + new Recipe(power, PowerBlocks.fusionReactor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40), new ItemStack(Items.carbide, 50)); + + new Recipe(distribution, PowerBlocks.warpGate, new ItemStack(Items.carbide, 1)); + + new Recipe(liquid, LiquidBlocks.conduit, new ItemStack(Items.carbide, 1)); + new Recipe(liquid, LiquidBlocks.pulseConduit, new ItemStack(Items.titanium, 1), new ItemStack(Items.carbide, 1)); + new Recipe(liquid, LiquidBlocks.liquidRouter, new ItemStack(Items.carbide, 2)); + new Recipe(liquid, LiquidBlocks.liquidtank, new ItemStack(Items.carbide, 2)); + new Recipe(liquid, LiquidBlocks.liquidJunction, new ItemStack(Items.carbide, 2)); + new Recipe(liquid, LiquidBlocks.bridgeConduit, new ItemStack(Items.titanium, 2), new ItemStack(Items.carbide, 2)); + new Recipe(liquid, LiquidBlocks.phaseConduit, new ItemStack(Items.titanium, 2), new ItemStack(Items.carbide, 2)); + + new Recipe(liquid, LiquidBlocks.mechanicalPump, new ItemStack(Items.carbide, 10)); + new Recipe(liquid, LiquidBlocks.rotaryPump, new ItemStack(Items.carbide, 10), new ItemStack(Items.surgealloy, 5)); + new Recipe(liquid, LiquidBlocks.thermalPump, new ItemStack(Items.carbide, 10), new ItemStack(Items.surgealloy, 5)); + + new Recipe(units, UnitBlocks.repairPoint, new ItemStack(Items.carbide, 10)); + new Recipe(units, UnitBlocks.dropPoint, new ItemStack(Items.carbide, 10)); + new Recipe(units, UnitBlocks.resupplyPoint, new ItemStack(Items.carbide, 10)); + + new Recipe(units, UnitBlocks.droneFactory, new ItemStack(Items.tungsten, 50)); + new Recipe(units, UnitBlocks.reconstructor, new ItemStack(Items.tungsten, 1)); + + new Recipe(units, UnitBlocks.overdriveProjector, new ItemStack(Items.tungsten, 1)); + new Recipe(units, UnitBlocks.shieldProjector, new ItemStack(Items.tungsten, 1)); + + new Recipe(units, UpgradeBlocks.omegaFactory, new ItemStack(Items.tungsten, 1)); + new Recipe(units, UpgradeBlocks.deltaFactory, new ItemStack(Items.tungsten, 1)); + new Recipe(units, UpgradeBlocks.tauFactory, new ItemStack(Items.tungsten, 1)); + + new Recipe(units, UpgradeBlocks.tridentFactory, new ItemStack(Items.tungsten, 1)); + new Recipe(units, UpgradeBlocks.javelinFactory, new ItemStack(Items.tungsten, 1)); + new Recipe(units, UpgradeBlocks.halberdFactory, new ItemStack(Items.tungsten, 1)); + + new Recipe(units, DebugBlocks.itemSource, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.itemVoid, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.liquidSource, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.powerVoid, new ItemStack(Items.carbide, 10)).setDebug(); + new Recipe(units, DebugBlocks.powerInfinite, new ItemStack(Items.carbide, 10), new ItemStack(Items.surgealloy, 5)).setDebug();*/ + } + + @Override + public Array getAll(){ + return Recipe.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/StatusEffects.java b/core/src/io/anuke/mindustry/content/StatusEffects.java new file mode 100644 index 0000000000..f17656c2e3 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/StatusEffects.java @@ -0,0 +1,155 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.fx.EnvironmentFx; +import io.anuke.mindustry.entities.StatusController.StatusEntry; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.StatusEffect; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.util.Mathf; + +public class StatusEffects implements ContentList{ + public static StatusEffect none, burning, freezing, wet, melting, tarred, overdrive, shielded; + + @Override + public void load(){ + + none = new StatusEffect(0); + + burning = new StatusEffect(4 * 60f){ + { + oppositeScale = 0.5f; + } + + @Override + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == tarred){ + unit.damage(1f); + Effects.effect(EnvironmentFx.burning, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + return result.set(this, Math.min(time + newTime, baseDuration + tarred.baseDuration)); + } + + return super.getTransition(unit, to, time, newTime, result); + } + + @Override + public void update(Unit unit, float time){ + unit.damagePeriodic(0.04f); + + if(Mathf.chance(Timers.delta() * 0.2f)){ + Effects.effect(EnvironmentFx.burning, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + } + } + }; + + freezing = new StatusEffect(5 * 60f){ + { + oppositeScale = 0.4f; + speedMultiplier = 0.7f; + } + + @Override + public void update(Unit unit, float time){ + + if(Mathf.chance(Timers.delta() * 0.15f)){ + Effects.effect(EnvironmentFx.freezing, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + } + } + }; + + wet = new StatusEffect(3 * 60f){ + { + oppositeScale = 0.5f; + speedMultiplier = 0.999f; + } + + @Override + public void update(Unit unit, float time){ + if(Mathf.chance(Timers.delta() * 0.15f)){ + Effects.effect(EnvironmentFx.wet, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + } + } + }; + + melting = new StatusEffect(5 * 60f){ + { + oppositeScale = 0.2f; + speedMultiplier = 0.8f; + armorMultiplier = 0.8f; + } + + @Override + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == tarred){ + return result.set(this, Math.min(time + newTime / 2f, baseDuration)); + } + + return super.getTransition(unit, to, time, newTime, result); + } + + @Override + public void update(Unit unit, float time){ + unit.damagePeriodic(0.3f); + + if(Mathf.chance(Timers.delta() * 0.2f)){ + Effects.effect(EnvironmentFx.melting, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + } + } + }; + + tarred = new StatusEffect(4 * 60f){ + { + speedMultiplier = 0.6f; + } + + @Override + public void update(Unit unit, float time){ + if(Mathf.chance(Timers.delta() * 0.15f)){ + Effects.effect(EnvironmentFx.oily, unit.x + Mathf.range(unit.getSize() / 2f), unit.y + Mathf.range(unit.getSize() / 2f)); + } + } + + @Override + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(to == melting || to == burning){ + return result.set(to, newTime + time); + } + + return result.set(to, newTime); + } + }; + + overdrive = new StatusEffect(6f){ + { + armorMultiplier = 0.95f; + speedMultiplier = 1.05f; + damageMultiplier = 1.4f; + } + + @Override + public void update(Unit unit, float time){ + //idle regen boosted + unit.health += 0.01f * Timers.delta(); + } + }; + + shielded = new StatusEffect(6f){ + { + armorMultiplier = 3f; + } + }; + + melting.setOpposites(wet, freezing); + wet.setOpposites(burning); + freezing.setOpposites(burning, melting); + burning.setOpposites(wet, freezing); + } + + @Override + public Array getAll(){ + return StatusEffect.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/UnitTypes.java b/core/src/io/anuke/mindustry/content/UnitTypes.java new file mode 100644 index 0000000000..e17da55df6 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/UnitTypes.java @@ -0,0 +1,79 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.entities.units.UnitType; +import io.anuke.mindustry.entities.units.types.*; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; + +public class UnitTypes implements ContentList{ + public static UnitType drone, scout, vtol, monsoon, titan, fabricator; + + @Override + public void load(){ + drone = new UnitType("drone", Drone.class, Drone::new){{ + isFlying = true; + drag = 0.01f; + speed = 0.2f; + maxVelocity = 0.8f; + ammoCapacity = 0; + range = 50f; + healSpeed = 0.05f; + health = 45; + }}; + + scout = new UnitType("scout", Scout.class, Scout::new){{ + maxVelocity = 1.1f; + speed = 0.2f; + drag = 0.4f; + range = 40f; + weapon = Weapons.chainBlaster; + health = 70; + }}; + + titan = new UnitType("titan", Titan.class, Titan::new){{ + maxVelocity = 0.8f; + speed = 0.18f; + drag = 0.4f; + range = 10f; + weapon = Weapons.chainBlaster; + health = 260; + }}; + + vtol = new UnitType("vtol", Vtol.class, Vtol::new){{ + speed = 0.3f; + maxVelocity = 1.9f; + drag = 0.01f; + isFlying = true; + }}; + + monsoon = new UnitType("monsoon", Monsoon.class, Monsoon::new){{ + health = 230; + speed = 0.2f; + maxVelocity = 1.4f; + drag = 0.01f; + isFlying = true; + weapon = Weapons.bomber; + }}; + + fabricator = new UnitType("fabricator", Fabricator.class, Fabricator::new){{ + isFlying = true; + drag = 0.01f; + speed = 0.2f; + maxVelocity = 0.6f; + ammoCapacity = 0; + range = 70f; + itemCapacity = 70; + health = 120; + health = 45; + buildPower = 0.9f; + minePower = 1.1f; + healSpeed = 0.09f; + }}; + } + + @Override + public Array getAll(){ + return UnitType.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/Weapons.java b/core/src/io/anuke/mindustry/content/Weapons.java new file mode 100644 index 0000000000..5ccc7456fc --- /dev/null +++ b/core/src/io/anuke/mindustry/content/Weapons.java @@ -0,0 +1,109 @@ +package io.anuke.mindustry.content; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.content.fx.ShootFx; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Upgrade; +import io.anuke.mindustry.type.Weapon; + +public class Weapons implements ContentList{ + public static Weapon blaster, chainBlaster, shockgun, sapper, swarmer, bomber, flakgun, flamethrower, missiles; + + @Override + public void load(){ + + blaster = new Weapon("blaster"){{ + length = 1.5f; + reload = 15f; + roundrobin = true; + ejectEffect = ShootFx.shellEjectSmall; + setAmmo(AmmoTypes.bulletLead); + }}; + + missiles = new Weapon("missiles"){{ + length = 1.5f; + reload = 40f; + shots = 2; + inaccuracy = 10f; + roundrobin = false; + roundrobin = true; + ejectEffect = Fx.none; + setAmmo(AmmoTypes.weaponMissile); + }}; + + chainBlaster = new Weapon("chain-blaster"){{ + length = 1.5f; + reload = 30f; + roundrobin = true; + ejectEffect = ShootFx.shellEjectSmall; + setAmmo(AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletTungsten, AmmoTypes.bulletSilicon, AmmoTypes.bulletThorium); + }}; + + shockgun = new Weapon("shockgun"){{ + length = 1f; + reload = 50f; + roundrobin = true; + shots = 6; + inaccuracy = 15f; + recoil = 2f; + velocityRnd = 0.7f; + ejectEffect = ShootFx.shellEjectSmall; + setAmmo(AmmoTypes.shotgunTungsten); + }}; + + flakgun = new Weapon("flakgun"){{ + length = 1f; + reload = 70f; + roundrobin = true; + shots = 1; + inaccuracy = 3f; + recoil = 3f; + velocityRnd = 0.1f; + ejectEffect = ShootFx.shellEjectMedium; + setAmmo(AmmoTypes.shellCarbide); + }}; + + flamethrower = new Weapon("flamethrower"){{ + length = 1f; + reload = 14f; + roundrobin = true; + recoil = 1f; + ejectEffect = Fx.none; + setAmmo(AmmoTypes.flamerThermite); + }}; + + sapper = new Weapon("sapper"){{ + length = 1.5f; + reload = 12f; + roundrobin = true; + ejectEffect = ShootFx.shellEjectSmall; + setAmmo(AmmoTypes.bulletCarbide); + }}; + + swarmer = new Weapon("swarmer"){{ + length = 1.5f; + reload = 10f; + roundrobin = true; + ejectEffect = ShootFx.shellEjectSmall; + setAmmo(AmmoTypes.bulletPyratite); + }}; + + bomber = new Weapon("bomber"){{ + length = 0f; + width = 2f; + reload = 5f; + roundrobin = true; + ejectEffect = Fx.none; + velocityRnd = 1f; + inaccuracy = 40f; + setAmmo(AmmoTypes.bombExplosive, AmmoTypes.bombIncendiary, AmmoTypes.bombOil); + }}; + } + + @Override + public Array getAll(){ + return Upgrade.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/BlockList.java b/core/src/io/anuke/mindustry/content/blocks/BlockList.java new file mode 100644 index 0000000000..e1b7819220 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/BlockList.java @@ -0,0 +1,14 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; + +public abstract class BlockList implements ContentList{ + + @Override + public Array getAll(){ + return Block.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/Blocks.java b/core/src/io/anuke/mindustry/content/blocks/Blocks.java new file mode 100644 index 0000000000..1268255a74 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/Blocks.java @@ -0,0 +1,163 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.graphics.CacheLayer; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.*; + +public class Blocks extends BlockList implements ContentList{ + public static Block air, spawn, blockpart, space, metalfloor, deepwater, water, lava, oil, stone, blackstone, dirt, sand, ice, snow, grass, shrub, rock, icerock, blackrock; + + + @Override + public void load(){ + air = new Floor("air"){ + { + blend = false; + } + + //don't draw + public void draw(Tile tile){ + } + + public void load(){ + } + + public void init(){ + } + }; + + blockpart = new BlockPart(); + + for(int i = 1; i <= 6; i++){ + new BuildBlock("build" + i); + } + + space = new Floor("space"){{ + placeableOn = false; + variants = 0; + cacheLayer = CacheLayer.space; + solid = true; + blend = false; + minimapColor = Color.valueOf("000001"); + }}; + + metalfloor = new Floor("metalfloor"){{ + variants = 6; + }}; + + deepwater = new Floor("deepwater"){{ + liquidColor = Color.valueOf("546bb3"); + speedMultiplier = 0.2f; + variants = 0; + liquidDrop = Liquids.water; + isLiquid = true; + status = StatusEffects.wet; + statusIntensity = 1f; + drownTime = 140f; + cacheLayer = CacheLayer.water; + minimapColor = Color.valueOf("465a96"); + }}; + + water = new Floor("water"){{ + liquidColor = Color.valueOf("546bb3"); + speedMultiplier = 0.5f; + variants = 0; + status = StatusEffects.wet; + statusIntensity = 0.9f; + liquidDrop = Liquids.water; + isLiquid = true; + cacheLayer = CacheLayer.water; + minimapColor = Color.valueOf("506eb4"); + }}; + + lava = new Floor("lava"){{ + liquidColor = Color.valueOf("ed5334"); + speedMultiplier = 0.2f; + damageTaken = 0.5f; + status = StatusEffects.melting; + statusIntensity = 0.8f; + variants = 0; + liquidDrop = Liquids.lava; + isLiquid = true; + cacheLayer = CacheLayer.lava; + minimapColor = Color.valueOf("ed5334"); + }}; + + oil = new Floor("oil"){{ + liquidColor = Color.valueOf("292929"); + status = StatusEffects.tarred; + statusIntensity = 1f; + speedMultiplier = 0.2f; + variants = 0; + liquidDrop = Liquids.oil; + isLiquid = true; + cacheLayer = CacheLayer.oil; + minimapColor = Color.valueOf("292929"); + }}; + + stone = new Floor("stone"){{ + hasOres = true; + drops = new ItemStack(Items.stone, 1); + blends = block -> block != this && !(block instanceof Ore); + minimapColor = Color.valueOf("323232"); + playerUnmineable = true; + }}; + + blackstone = new Floor("blackstone"){{ + drops = new ItemStack(Items.stone, 1); + minimapColor = Color.valueOf("252525"); + playerUnmineable = true; + }}; + + dirt = new Floor("dirt"){{ + minimapColor = Color.valueOf("6e501e"); + }}; + + sand = new Floor("sand"){{ + drops = new ItemStack(Items.sand, 1); + minimapColor = Color.valueOf("988a67"); + hasOres = true; + playerUnmineable = true; + }}; + + ice = new Floor("ice"){{ + dragMultiplier = 0.3f; + speedMultiplier = 0.4f; + minimapColor = Color.valueOf("c4e3e7"); + hasOres = true; + }}; + + snow = new Floor("snow"){{ + minimapColor = Color.valueOf("c2d1d2"); + hasOres = true; + }}; + + grass = new Floor("grass"){{ + hasOres = true; + minimapColor = Color.valueOf("549d5b"); + }}; + + shrub = new Rock("shrub"){{ + shadow = "shrubshadow"; + }}; + + rock = new Rock("rock"){{ + variants = 2; + }}; + + icerock = new Rock("icerock"){{ + variants = 2; + }}; + + blackrock = new Rock("blackrock"){{ + variants = 1; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java new file mode 100644 index 0000000000..67ab558fdd --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/CraftingBlocks.java @@ -0,0 +1,253 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.production.*; + +public class CraftingBlocks extends BlockList implements ContentList{ + public static Block smelter, arcsmelter, siliconsmelter, plastaniumCompressor, phaseWeaver, alloysmelter, alloyfuser, + pyratiteMixer, blastMixer, + cryofluidmixer, melter, separator, centrifuge, biomatterCompressor, pulverizer, solidifier, incinerator; + + @Override + public void load(){ + smelter = new Smelter("smelter"){{ + health = 70; + result = Items.carbide; + craftTime = 45f; + burnDuration = 35f; + useFlux = true; + + consumes.items(new ItemStack[]{new ItemStack(Items.tungsten, 3)}); + consumes.item(Items.coal); + }}; + + arcsmelter = new PowerSmelter("arc-smelter"){{ + health = 90; + craftEffect = BlockFx.smeltsmoke; + result = Items.carbide; + craftTime = 30f; + size = 2; + + useFlux = true; + fluxNeeded = 2; + + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.tungsten, 2)}); + consumes.power(0.1f); + }}; + + siliconsmelter = new PowerSmelter("silicon-smelter"){{ + health = 90; + craftEffect = BlockFx.smeltsmoke; + result = Items.silicon; + craftTime = 40f; + size = 2; + hasLiquids = false; + flameColor = Color.valueOf("ffef99"); + + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.sand, 2)}); + consumes.power(0.05f); + }}; + + plastaniumCompressor = new PlastaniumCompressor("plastanium-compressor"){{ + hasItems = true; + liquidCapacity = 60f; + craftTime = 80f; + output = Items.plastanium; + itemCapacity = 30; + size = 2; + health = 320; + hasPower = hasLiquids = true; + craftEffect = BlockFx.formsmoke; + updateEffect = BlockFx.plasticburn; + + consumes.liquid(Liquids.oil, 0.3f); + consumes.power(0.4f); + consumes.item(Items.titanium, 2); + }}; + + phaseWeaver = new PhaseWeaver("phase-weaver"){{ + health = 90; + craftEffect = BlockFx.smeltsmoke; + result = Items.phasematter; + craftTime = 120f; + size = 2; + + consumes.items(new ItemStack[]{new ItemStack(Items.thorium, 4), new ItemStack(Items.sand, 10)}); + consumes.power(0.5f); + }}; + + alloysmelter = new PowerSmelter("alloy-smelter"){{ + health = 90; + craftEffect = BlockFx.smeltsmoke; + result = Items.surgealloy; + craftTime = 50f; + size = 2; + + useFlux = true; + fluxNeeded = 4; + + consumes.power(0.3f); + consumes.items(new ItemStack[]{new ItemStack(Items.titanium, 2), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}); + }}; + + alloyfuser = new PowerSmelter("alloy-fuser"){{ + health = 90; + craftEffect = BlockFx.smeltsmoke; + result = Items.surgealloy; + craftTime = 30f; + size = 3; + + useFlux = true; + fluxNeeded = 4; + + consumes.items(new ItemStack[]{new ItemStack(Items.titanium, 3), new ItemStack(Items.lead, 4), new ItemStack(Items.silicon, 3), new ItemStack(Items.plastanium, 2)}); + consumes.power(0.4f); + }}; + + cryofluidmixer = new LiquidMixer("cryofluidmixer"){{ + health = 200; + outputLiquid = Liquids.cryofluid; + liquidPerItem = 50f; + itemCapacity = 50; + size = 2; + hasPower = true; + + consumes.power(0.1f); + consumes.item(Items.titanium); + consumes.liquid(Liquids.water, 0.3f); + }}; + + blastMixer = new GenericCrafter("blast-mixer"){{ + itemCapacity = 20; + hasItems = true; + hasPower = true; + hasLiquids = true; + output = Items.blastCompound; + size = 2; + + consumes.liquid(Liquids.oil, 0.05f); + consumes.item(Items.pyratite, 1); + consumes.power(0.04f); + }}; + + pyratiteMixer = new PowerSmelter("pyratite-mixer"){{ + flameColor = Color.CLEAR; + itemCapacity = 20; + hasItems = true; + hasPower = true; + result = Items.pyratite; + + size = 2; + + consumes.power(0.02f); + consumes.items(new ItemStack[]{new ItemStack(Items.coal, 1), new ItemStack(Items.lead, 2), new ItemStack(Items.sand, 2)}); + }}; + + melter = new PowerCrafter("melter"){{ + health = 200; + outputLiquid = Liquids.lava; + outputLiquidAmount = 0.75f; + itemCapacity = 50; + craftTime = 10f; + hasLiquids = hasPower = true; + + consumes.power(0.1f); + consumes.item(Items.stone, 2); + }}; + + separator = new Separator("separator"){{ + results = new Item[]{ + null, null, null, null, null, null, null, null, null, null, + Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, + Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, + Items.tungsten, Items.tungsten, Items.tungsten, Items.tungsten, + Items.lead, Items.lead, + Items.coal, Items.coal, + Items.titanium + }; + filterTime = 40f; + itemCapacity = 40; + health = 50; + + consumes.item(Items.stone, 2); + consumes.liquid(Liquids.water, 0.3f); + }}; + + centrifuge = new Separator("centrifuge"){{ + results = new Item[]{ + null, null, null, null, null, null, null, null, null, null, null, null, null, + Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, Items.sand, + Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, Items.stone, + Items.tungsten, Items.tungsten, Items.tungsten, Items.tungsten, Items.tungsten, + Items.lead, Items.lead, Items.lead, + Items.coal, Items.coal, Items.coal, + Items.titanium, Items.titanium, + Items.thorium, + }; + + hasPower = true; + filterTime = 15f; + itemCapacity = 60; + health = 50 * 4; + spinnerLength = 1.5f; + spinnerRadius = 3.5f; + spinnerThickness = 1.5f; + spinnerSpeed = 3f; + size = 2; + + consumes.item(Items.stone, 2); + consumes.power(0.2f); + consumes.liquid(Liquids.water, 0.5f); + }}; + + biomatterCompressor = new Compressor("biomattercompressor"){{ + liquidCapacity = 60f; + itemCapacity = 50; + craftTime = 25f; + outputLiquid = Liquids.oil; + outputLiquidAmount = 0.14f; + size = 2; + health = 320; + hasLiquids = true; + + consumes.item(Items.biomatter, 1); + consumes.power(0.06f); + }}; + + pulverizer = new Pulverizer("pulverizer"){{ + itemCapacity = 40; + output = Items.sand; + health = 80; + craftEffect = BlockFx.pulverize; + craftTime = 60f; + updateEffect = BlockFx.pulverizeSmall; + hasItems = hasPower = true; + + consumes.item(Items.stone, 2); + consumes.power(0.2f); + }}; + + solidifier = new GenericCrafter("solidifer"){{ + liquidCapacity = 21f; + craftTime = 14; + output = Items.stone; + itemCapacity = 20; + health = 80; + craftEffect = BlockFx.purifystone; + hasLiquids = hasItems = true; + + consumes.liquid(Liquids.lava, 1f); + }}; + + incinerator = new Incinerator("incinerator"){{ + health = 90; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java new file mode 100644 index 0000000000..b23a545823 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/DebugBlocks.java @@ -0,0 +1,165 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.utils.Array; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.gen.CallBlocks; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.PowerBlock; +import io.anuke.mindustry.world.blocks.distribution.Sorter; +import io.anuke.mindustry.world.blocks.power.PowerNode; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.ButtonGroup; +import io.anuke.ucore.scene.ui.ImageButton; +import io.anuke.ucore.scene.ui.layout.Table; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class DebugBlocks extends BlockList implements ContentList{ + public static Block powerVoid, powerInfinite, itemSource, liquidSource, itemVoid; + + @Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true) + public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){ + LiquidSourceEntity entity = tile.entity(); + entity.source = liquid; + } + + @Override + public void load(){ + powerVoid = new PowerBlock("powervoid"){ + { + powerCapacity = Float.MAX_VALUE; + } + }; + + powerInfinite = new PowerNode("powerinfinite"){ + { + powerCapacity = 10000f; + powerSpeed = 100f; + } + + @Override + public void update(Tile tile){ + super.update(tile); + tile.entity.power.amount = powerCapacity; + } + }; + + itemSource = new Sorter("itemsource"){ + { + hasItems = true; + } + + @Override + public void update(Tile tile){ + SorterEntity entity = tile.entity(); + entity.items.set(entity.sortItem, 1); + tryDump(tile, entity.sortItem); + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + return false; + } + }; + + liquidSource = new Block("liquidsource"){ + { + update = true; + solid = true; + hasLiquids = true; + liquidCapacity = 100f; + configurable = true; + } + + @Override + public void update(Tile tile){ + LiquidSourceEntity entity = tile.entity(); + + tile.entity.liquids.add(entity.source, liquidCapacity); + tryDumpLiquid(tile, entity.source); + } + + @Override + public void draw(Tile tile){ + super.draw(tile); + + LiquidSourceEntity entity = tile.entity(); + + Draw.color(entity.source.color); + Draw.rect("blank", tile.worldx(), tile.worldy(), 4f, 4f); + Draw.color(); + } + + @Override + public void buildTable(Tile tile, Table table){ + LiquidSourceEntity entity = tile.entity(); + + Array items = Liquid.all(); + + ButtonGroup group = new ButtonGroup<>(); + Table cont = new Table(); + + for(int i = 0; i < items.size; i++){ + if(i == 0) continue; + final int f = i; + ImageButton button = cont.addImageButton("white", "toggle", 24, () -> { + CallBlocks.setLiquidSourceLiquid(null, tile, items.get(f)); + }).size(38, 42).padBottom(-5.1f).group(group).get(); + button.getStyle().imageUpColor = items.get(i).color; + button.setChecked(entity.source.id == f); + + if(i % 4 == 3){ + cont.row(); + } + } + + table.add(cont); + } + + @Override + public TileEntity getEntity(){ + return new LiquidSourceEntity(); + } + }; + + itemVoid = new Block("itemvoid"){ + { + update = solid = true; + } + + @Override + public void handleItem(Item item, Tile tile, Tile source){ + } + + @Override + public boolean acceptItem(Item item, Tile tile, Tile source){ + return true; + } + }; + } + + class LiquidSourceEntity extends TileEntity{ + public Liquid source = Liquids.water; + + @Override + public void write(DataOutputStream stream) throws IOException{ + stream.writeByte(source.id); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + source = Liquid.getByID(stream.readByte()); + } + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java new file mode 100644 index 0000000000..ca40928d80 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/DefenseBlocks.java @@ -0,0 +1,76 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Wall; +import io.anuke.mindustry.world.blocks.defense.DeflectorWall; +import io.anuke.mindustry.world.blocks.defense.Door; +import io.anuke.mindustry.world.blocks.defense.PhaseWall; + +public class DefenseBlocks extends BlockList implements ContentList{ + public static Block tungstenWall, tungstenWallLarge, carbideWall, carbideWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge, deflectorwall, deflectorwalllarge, + phasewall, phasewalllarge; + + @Override + public void load(){ + int wallHealthMultiplier = 4; + + tungstenWall = new Wall("tungsten-wall"){{ + health = 80 * wallHealthMultiplier; + }}; + + tungstenWallLarge = new Wall("tungsten-wall-large"){{ + health = 80 * 4 * wallHealthMultiplier; + size = 2; + }}; + + carbideWall = new Wall("carbide-wall"){{ + health = 110 * wallHealthMultiplier; + }}; + + carbideWallLarge = new Wall("carbide-wall-large"){{ + health = 110 * wallHealthMultiplier * 4; + size = 2; + }}; + + thoriumWall = new Wall("thorium-wall"){{ + health = 200 * wallHealthMultiplier; + }}; + + thoriumWallLarge = new Wall("thorium-wall-large"){{ + health = 200 * wallHealthMultiplier * 4; + size = 2; + }}; + + deflectorwall = new DeflectorWall("deflector-wall"){{ + health = 150 * wallHealthMultiplier; + }}; + + deflectorwalllarge = new DeflectorWall("deflector-wall-large"){{ + health = 150 * 4 * wallHealthMultiplier; + size = 2; + }}; + + phasewall = new PhaseWall("phase-wall"){{ + health = 150 * wallHealthMultiplier; + }}; + + phasewalllarge = new PhaseWall("phase-wall-large"){{ + health = 150 * 4 * wallHealthMultiplier; + size = 2; + regenSpeed = 0.5f; + }}; + + door = new Door("door"){{ + health = 100 * wallHealthMultiplier; + }}; + + doorLarge = new Door("door-large"){{ + openfx = BlockFx.dooropenlarge; + closefx = BlockFx.doorcloselarge; + health = 100 * 4 * wallHealthMultiplier; + size = 2; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java new file mode 100644 index 0000000000..a0adfe48b5 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/DistributionBlocks.java @@ -0,0 +1,56 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.distribution.*; + +public class DistributionBlocks extends BlockList implements ContentList{ + public static Block conveyor, titaniumconveyor, distributor, junction, + bridgeConveyor, phaseConveyor, sorter, splitter, overflowGate, massDriver; + + @Override + public void load(){ + + conveyor = new Conveyor("conveyor"){{ + health = 45; + speed = 0.03f; + }}; + + titaniumconveyor = new Conveyor("titanium-conveyor"){{ + health = 65; + speed = 0.07f; + }}; + + junction = new Junction("junction"){{ + speed = 26; + capacity = 32; + }}; + + bridgeConveyor = new BufferedItemBridge("bridge-conveyor"){{ + range = 4; + speed = 60f; + }}; + + phaseConveyor = new ItemBridge("phase-conveyor"){{ + range = 11; + hasPower = true; + consumes.power(0.05f); + }}; + + sorter = new Sorter("sorter"); + + splitter = new Splitter("splitter"); + + distributor = new Splitter("distributor"){{ + size = 2; + }}; + + overflowGate = new OverflowGate("overflow-gate"); + + massDriver = new MassDriver("mass-driver"){{ + size = 3; + itemCapacity = 80; + range = 300f; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java new file mode 100644 index 0000000000..e824005518 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/LiquidBlocks.java @@ -0,0 +1,71 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.distribution.*; +import io.anuke.mindustry.world.blocks.production.Pump; + +public class LiquidBlocks extends BlockList implements ContentList{ + public static Block mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, liquidRouter, liquidtank, liquidJunction, bridgeConduit, phaseConduit; + + @Override + public void load(){ + + mechanicalPump = new Pump("mechanical-pump"){{ + shadow = "shadow-round-1"; + pumpAmount = 0.1f; + tier = 0; + }}; + + rotaryPump = new Pump("rotary-pump"){{ + shadow = "shadow-rounded-2"; + pumpAmount = 0.25f; + consumes.power(0.015f); + liquidCapacity = 30f; + hasPower = true; + size = 2; + tier = 1; + }}; + + thermalPump = new Pump("thermal-pump"){{ + pumpAmount = 0.3f; + consumes.power(0.05f); + liquidCapacity = 40f; + size = 2; + tier = 2; + }}; + + conduit = new Conduit("conduit"){{ + health = 45; + }}; + + pulseConduit = new Conduit("pulse-conduit"){{ + liquidCapacity = 16f; + liquidFlowFactor = 4.9f; + health = 90; + }}; + + liquidRouter = new LiquidRouter("liquid-router"){{ + liquidCapacity = 40f; + }}; + + liquidtank = new LiquidRouter("liquid-tank"){{ + size = 3; + liquidCapacity = 1500f; + health = 500; + }}; + + liquidJunction = new LiquidJunction("liquid-junction"); + + bridgeConduit = new LiquidExtendingBridge("bridge-conduit"){{ + range = 4; + hasPower = false; + }}; + + phaseConduit = new LiquidBridge("phase-conduit"){{ + range = 11; + hasPower = true; + consumes.power(0.05f); + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java b/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java new file mode 100644 index 0000000000..4cb86032f8 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/OreBlocks.java @@ -0,0 +1,35 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.blocks.OreBlock; + +public class OreBlocks extends BlockList{ + private static final ObjectMap> oreBlockMap = new ObjectMap<>(); + + public static Block get(Block floor, Item item){ + if(!oreBlockMap.containsKey(item)) throw new IllegalArgumentException("Item '" + item + "' is not an ore!"); + if(!oreBlockMap.get(item).containsKey(floor)) + throw new IllegalArgumentException("Block '" + floor.name + "' does not support ores!"); + return oreBlockMap.get(item).get(floor); + } + + @Override + public void load(){ + Item[] ores = {Items.tungsten, Items.lead, Items.coal, Items.titanium, Items.thorium}; + + for(Item item : ores){ + ObjectMap map = new ObjectMap<>(); + oreBlockMap.put(item, map); + + for(Block block : Block.all()){ + if(block instanceof Floor && ((Floor) block).hasOres){ + map.put(block, new OreBlock(item, (Floor) block)); + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java new file mode 100644 index 0000000000..df8ead1a55 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -0,0 +1,90 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.distribution.WarpGate; +import io.anuke.mindustry.world.blocks.power.*; + +public class PowerBlocks extends BlockList implements ContentList{ + public static Block combustionGenerator, thermalGenerator, turbineGenerator, rtgGenerator, solarPanel, largeSolarPanel, + nuclearReactor, fusionReactor, battery, batteryLarge, powerNode, powerNodeLarge, warpGate; + + @Override + public void load(){ + combustionGenerator = new BurnerGenerator("combustion-generator"){{ + powerOutput = 0.09f; + powerCapacity = 40f; + itemDuration = 40f; + }}; + + thermalGenerator = new LiquidHeatGenerator("thermal-generator"){{ + maxLiquidGenerate = 0.5f; + powerPerLiquid = 0.08f; + powerCapacity = 40f; + powerPerLiquid = 0.25f; + generateEffect = BlockFx.redgeneratespark; + size = 2; + }}; + + turbineGenerator = new TurbineGenerator("turbine-generator"){{ + powerOutput = 0.28f; + powerCapacity = 40f; + itemDuration = 30f; + powerPerLiquid = 0.7f; + consumes.liquid(Liquids.water, 0.05f); + size = 2; + }}; + + rtgGenerator = new DecayGenerator("rtg-generator"){{ + powerCapacity = 40f; + powerOutput = 0.02f; + itemDuration = 500f; + }}; + + solarPanel = new SolarGenerator("solar-panel"){{ + generation = 0.0045f; + }}; + + largeSolarPanel = new SolarGenerator("solar-panel-large"){{ + size = 3; + generation = 0.055f; + }}; + + nuclearReactor = new NuclearReactor("nuclear-reactor"){{ + size = 3; + health = 700; + powerMultiplier = 0.8f; + }}; + + fusionReactor = new FusionReactor("fusion-reactor"){{ + size = 4; + health = 600; + }}; + + battery = new PowerDistributor("battery"){{ + powerCapacity = 320f; + }}; + + batteryLarge = new PowerDistributor("battery-large"){{ + size = 3; + powerCapacity = 2000f; + }}; + + powerNode = new PowerNode("power-node"){{ + shadow = "shadow-round-1"; + }}; + + powerNodeLarge = new PowerNode("power-node-large"){{ + size = 2; + powerSpeed = 1f; + maxNodes = 5; + laserRange = 7.5f; + shadow = "shadow-round-2"; + }}; + + warpGate = new WarpGate("warp-gate"); + + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java new file mode 100644 index 0000000000..c07dc66303 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/ProductionBlocks.java @@ -0,0 +1,108 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.production.Cultivator; +import io.anuke.mindustry.world.blocks.production.Drill; +import io.anuke.mindustry.world.blocks.production.Fracker; +import io.anuke.mindustry.world.blocks.production.SolidPump; + +public class ProductionBlocks extends BlockList implements ContentList{ + public static Block tungstenDrill, carbideDrill, laserdrill, blastdrill, plasmadrill, waterextractor, oilextractor, cultivator; + + @Override + public void load(){ + tungstenDrill = new Drill("tungsten-drill"){{ + tier = 2; + drillTime = 340; + }}; + + carbideDrill = new Drill("carbide-drill"){{ + tier = 3; + drillTime = 280; + }}; + + laserdrill = new Drill("laser-drill"){{ + drillTime = 180; + size = 2; + hasPower = true; + tier = 4; + updateEffect = BlockFx.pulverizeMedium; + drillEffect = BlockFx.mineBig; + + consumes.power(0.2f); + }}; + + blastdrill = new Drill("blast-drill"){{ + drillTime = 120; + size = 3; + drawRim = true; + hasPower = true; + tier = 5; + updateEffect = BlockFx.pulverizeRed; + updateEffectChance = 0.03f; + drillEffect = BlockFx.mineHuge; + rotateSpeed = 6f; + warmupSpeed = 0.01f; + + consumes.power(0.5f); + }}; + + plasmadrill = new Drill("plasma-drill"){{ + heatColor = Color.valueOf("ff461b"); + drillTime = 90; + size = 4; + hasLiquids = true; + hasPower = true; + tier = 5; + rotateSpeed = 9f; + drawRim = true; + updateEffect = BlockFx.pulverizeRedder; + updateEffectChance = 0.04f; + drillEffect = BlockFx.mineHuge; + warmupSpeed = 0.005f; + + consumes.power(0.7f); + }}; + + waterextractor = new SolidPump("water-extractor"){{ + result = Liquids.water; + pumpAmount = 0.1f; + size = 2; + liquidCapacity = 30f; + rotateSpeed = 1.4f; + + consumes.power(0.2f); + }}; + + oilextractor = new Fracker("oil-extractor"){{ + result = Liquids.oil; + updateEffect = BlockFx.pulverize; + liquidCapacity = 50f; + updateEffectChance = 0.05f; + pumpAmount = 0.08f; + size = 3; + liquidCapacity = 30f; + + consumes.item(Items.sand); + consumes.power(0.5f); + consumes.liquid(Liquids.water, 0.3f); + }}; + + cultivator = new Cultivator("cultivator"){{ + result = Items.biomatter; + drillTime = 260; + size = 2; + hasLiquids = true; + hasPower = true; + + consumes.power(0.08f); + consumes.liquid(Liquids.water, 0.2f); + }}; + + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java new file mode 100644 index 0000000000..d3946f8339 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/StorageBlocks.java @@ -0,0 +1,33 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.storage.CoreBlock; +import io.anuke.mindustry.world.blocks.storage.SortedUnloader; +import io.anuke.mindustry.world.blocks.storage.Unloader; +import io.anuke.mindustry.world.blocks.storage.Vault; + +public class StorageBlocks extends BlockList implements ContentList{ + public static Block core, vault, unloader, sortedunloader; + + @Override + public void load(){ + core = new CoreBlock("core"){{ + health = 800; + }}; + + vault = new Vault("vault"){{ + size = 3; + health = 600; + itemCapacity = 2000; + }}; + + unloader = new Unloader("unloader"){{ + speed = 5; + }}; + + sortedunloader = new SortedUnloader("sortedunloader"){{ + speed = 5; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java new file mode 100644 index 0000000000..e408d82719 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/TurretBlocks.java @@ -0,0 +1,206 @@ +package io.anuke.mindustry.content.blocks; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.AmmoTypes; +import io.anuke.mindustry.content.fx.ShootFx; +import io.anuke.mindustry.type.AmmoType; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.defense.turrets.*; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Strings; + +public class TurretBlocks extends BlockList implements ContentList{ + public static Block duo, /*scatter,*/ + scorch, hail, wave, lancer, arc, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown; + + @Override + public void load(){ + duo = new DoubleTurret("duo"){{ + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletSilicon}; + reload = 25f; + restitution = 0.03f; + range = 90f; + shootCone = 15f; + ammoUseEffect = ShootFx.shellEjectSmall; + health = 80; + inaccuracy = 2f; + rotatespeed = 10f; + }}; +/* + scatter = new BurstTurret("scatter") {{ + ammoTypes = new AmmoType[]{AmmoTypes.flakLead, AmmoTypes.flakExplosive, AmmoTypes.flakPlastic}; + ammoPerShot = 1; + shots = 3; + reload = 60f; + restitution = 0.03f; + recoil = 1.5f; + burstSpacing = 1f; + inaccuracy = 7f; + ammoUseEffect = ShootFx.shellEjectSmall; + }};*/ + + hail = new ArtilleryTurret("hail"){{ + ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary}; + reload = 100f; + recoil = 2f; + range = 200f; + inaccuracy = 5f; + health = 120; + }}; + + scorch = new LiquidTurret("scorch"){{ + ammoTypes = new AmmoType[]{AmmoTypes.basicFlame}; + recoil = 0f; + reload = 4f; + shootCone = 50f; + ammoUseEffect = ShootFx.shellEjectSmall; + health = 160; + + drawer = (tile, entity) -> Draw.rect(entity.target != null ? name + "-shoot" : name, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + }}; + + wave = new LiquidTurret("wave"){{ + ammoTypes = new AmmoType[]{AmmoTypes.water, AmmoTypes.lava, AmmoTypes.cryofluid, AmmoTypes.oil}; + size = 2; + recoil = 0f; + reload = 4f; + inaccuracy = 5f; + shootCone = 50f; + shootEffect = ShootFx.shootLiquid; + range = 70f; + health = 360; + + drawer = (tile, entity) -> { + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + + Draw.color(entity.liquids.current().color); + Draw.alpha(entity.liquids.total() / liquidCapacity); + Draw.rect(name + "-liquid", tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + Draw.color(); + }; + }}; + + lancer = new LaserTurret("lancer"){{ + range = 90f; + chargeTime = 60f; + chargeMaxDelay = 30f; + chargeEffects = 7; + shootType = AmmoTypes.lancerLaser; + recoil = 2f; + reload = 100f; + cooldown = 0.03f; + powerUsed = 20f; + powerCapacity = 60f; + shootShake = 2f; + shootEffect = ShootFx.lancerLaserShoot; + smokeEffect = ShootFx.lancerLaserShootSmoke; + chargeEffect = ShootFx.lancerLaserCharge; + chargeBeginEffect = ShootFx.lancerLaserChargeBegin; + heatColor = Color.RED; + size = 2; + health = 320; + }}; + + arc = new LaserTurret("arc"){{ + shootType = AmmoTypes.lightning; + reload = 100f; + chargeTime = 70f; + shootShake = 1f; + chargeMaxDelay = 30f; + chargeEffects = 7; + shootEffect = ShootFx.lightningShoot; + chargeEffect = ShootFx.lightningCharge; + chargeBeginEffect = ShootFx.lancerLaserChargeBegin; + heatColor = Color.RED; + recoil = 3f; + size = 2; + }}; + + swarmer = new BurstTurret("swarmer"){{ + ammoTypes = new AmmoType[]{AmmoTypes.missileExplosive, AmmoTypes.missileIncindiary/*, AmmoTypes.missileSurge*/}; + reload = 60f; + shots = 4; + burstSpacing = 5; + inaccuracy = 10f; + range = 140f; + xRand = 6f; + size = 2; + health = 380; + }}; + + salvo = new BurstTurret("salvo"){{ + size = 2; + range = 120f; + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; + reload = 40f; + restitution = 0.03f; + ammoEjectBack = 3f; + cooldown = 0.03f; + recoil = 3f; + shootShake = 2f; + burstSpacing = 4; + shots = 3; + ammoUseEffect = ShootFx.shellEjectBig; + + drawer = (tile, entity) -> { + Draw.rect(region, tile.drawx() + tr2.x, tile.drawy() + tr2.y, entity.rotation - 90); + float offsetx = (int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 3f); + float offsety = -(int) (Mathf.abscurve(Mathf.curve(entity.reload / reload, 0.3f, 0.2f)) * 2f); + + for(int i : Mathf.signs){ + float rot = entity.rotation + 90 * i; + Draw.rect(name + "-panel-" + Strings.dir(i), + tile.drawx() + tr2.x + Angles.trnsx(rot, offsetx, offsety), + tile.drawy() + tr2.y + Angles.trnsy(rot, -offsetx, offsety), entity.rotation - 90); + } + }; + + health = 360; + }}; + + ripple = new ArtilleryTurret("ripple"){{ + ammoTypes = new AmmoType[]{AmmoTypes.artilleryCarbide, AmmoTypes.artilleryHoming, AmmoTypes.artilleryIncindiary, AmmoTypes.artilleryExplosive, AmmoTypes.artilleryPlastic}; + size = 3; + shots = 4; + inaccuracy = 12f; + reload = 60f; + ammoEjectBack = 5f; + ammoUseEffect = ShootFx.shellEjectBig; + cooldown = 0.03f; + velocityInaccuracy = 0.2f; + restitution = 0.02f; + recoil = 6f; + shootShake = 2f; + range = 300f; + + health = 550; + }}; + + cyclone = new ItemTurret("cyclone"){{ + ammoTypes = new AmmoType[]{AmmoTypes.flakLead, AmmoTypes.flakExplosive, AmmoTypes.flakPlastic, AmmoTypes.flakSurge}; + size = 3; + }}; + + fuse = new ItemTurret("fuse"){{ + //TODO make it use power + ammoTypes = new AmmoType[]{AmmoTypes.fuseShotgun}; + size = 3; + }}; + + spectre = new ItemTurret("spectre"){{ + ammoTypes = new AmmoType[]{AmmoTypes.bulletTungsten, AmmoTypes.bulletLead, AmmoTypes.bulletCarbide, AmmoTypes.bulletPyratite, AmmoTypes.bulletThorium, AmmoTypes.bulletSilicon}; + reload = 25f; + restitution = 0.03f; + ammoUseEffect = ShootFx.shellEjectSmall; + size = 4; + }}; + + meltdown = new PowerTurret("meltdown"){{ + shootType = AmmoTypes.meltdownLaser; + size = 4; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java new file mode 100644 index 0000000000..dc98341364 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/UnitBlocks.java @@ -0,0 +1,58 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.UnitTypes; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.units.*; + +public class UnitBlocks extends BlockList implements ContentList{ + public static Block resupplyPoint, repairPoint, droneFactory, fabricatorFactory, dropPoint, reconstructor, overdriveProjector, shieldProjector; + + @Override + public void load(){ + droneFactory = new UnitFactory("drone-factory"){{ + type = UnitTypes.drone; + produceTime = 800; + size = 2; + consumes.power(0.08f); + consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30)}); + }}; + + fabricatorFactory = new UnitFactory("fabricator-factory"){{ + type = UnitTypes.fabricator; + produceTime = 1600; + size = 2; + consumes.power(0.2f); + consumes.items(new ItemStack[]{new ItemStack(Items.silicon, 70), new ItemStack(Items.lead, 80), new ItemStack(Items.titanium, 80)}); + }}; + + resupplyPoint = new ResupplyPoint("resupply-point"){{ + shadow = "shadow-round-1"; + itemCapacity = 30; + }}; + + dropPoint = new DropPoint("drop-point"){{ + shadow = "shadow-round-1"; + itemCapacity = 40; + }}; + + repairPoint = new RepairPoint("repair-point"){{ + shadow = "shadow-round-1"; + repairSpeed = 0.1f; + }}; + + reconstructor = new Reconstructor("reconstructor"){{ + size = 2; + }}; + + overdriveProjector = new OverdriveProjector("overdrive-projector"){{ + size = 2; + }}; + + shieldProjector = new ShieldProjector("shieldprojector"){{ + size = 2; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java b/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java new file mode 100644 index 0000000000..c66cfba208 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/blocks/UpgradeBlocks.java @@ -0,0 +1,47 @@ +package io.anuke.mindustry.content.blocks; + +import io.anuke.mindustry.content.Mechs; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.units.MechFactory; + +public class UpgradeBlocks extends BlockList{ + public static Block deltaFactory, tauFactory, omegaFactory, dartFactory, javelinFactory, tridentFactory, halberdFactory; + + @Override + public void load(){ + deltaFactory = new MechFactory("delta-mech-factory"){{ + mech = Mechs.delta; + size = 2; + }}; + + tauFactory = new MechFactory("tau-mech-factory"){{ + mech = Mechs.tau; + size = 2; + }}; + + omegaFactory = new MechFactory("omega-mech-factory"){{ + mech = Mechs.omega; + size = 3; + }}; + + dartFactory = new MechFactory("dart-ship-factory"){{ + mech = Mechs.dart; + size = 2; + }}; + + javelinFactory = new MechFactory("javelin-ship-factory"){{ + mech = Mechs.javelin; + size = 2; + }}; + + tridentFactory = new MechFactory("trident-ship-factory"){{ + mech = Mechs.trident; + size = 2; + }}; + + halberdFactory = new MechFactory("halberd-ship-factory"){{ + mech = Mechs.halberd; + size = 3; + }}; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java b/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java new file mode 100644 index 0000000000..5898a70070 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/ArtilleryBullets.java @@ -0,0 +1,106 @@ +package io.anuke.mindustry.content.bullets; + +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.entities.bullet.ArtilleryBulletType; +import io.anuke.mindustry.entities.bullet.BasicBulletType; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; + +public class ArtilleryBullets extends BulletList implements ContentList{ + public static BulletType carbide, plastic, plasticFrag, homing, incindiary, explosive, surge; + + @Override + public void load(){ + + carbide = new ArtilleryBulletType(3f, 0, "shell"){ + { + hiteffect = BulletFx.flakExplosion; + knockback = 0.8f; + lifetime = 50f; + bulletWidth = bulletHeight = 11f; + collidesTiles = false; + splashDamageRadius = 25f; + splashDamage = 33f; + } + }; + + plasticFrag = new BasicBulletType(2.5f, 6, "bullet"){ + { + bulletWidth = 10f; + bulletHeight = 12f; + bulletShrink = 1f; + lifetime = 15f; + backColor = Palette.plastaniumBack; + frontColor = Palette.plastaniumFront; + } + }; + + plastic = new ArtilleryBulletType(3.3f, 0, "shell"){ + { + hiteffect = BulletFx.plasticExplosion; + knockback = 1f; + lifetime = 55f; + bulletWidth = bulletHeight = 13f; + collidesTiles = false; + splashDamageRadius = 35f; + splashDamage = 35f; + fragBullet = plasticFrag; + fragBullets = 9; + backColor = Palette.plastaniumBack; + frontColor = Palette.plastaniumFront; + } + }; + + homing = new ArtilleryBulletType(3f, 0, "shell"){ + { + hiteffect = BulletFx.flakExplosion; + knockback = 0.8f; + lifetime = 45f; + bulletWidth = bulletHeight = 11f; + collidesTiles = false; + splashDamageRadius = 25f; + splashDamage = 33f; + homingPower = 2f; + homingRange = 50f; + } + }; + + incindiary = new ArtilleryBulletType(3f, 0, "shell"){ + { + hiteffect = BulletFx.blastExplosion; + knockback = 0.8f; + lifetime = 60f; + bulletWidth = bulletHeight = 13f; + collidesTiles = false; + splashDamageRadius = 25f; + splashDamage = 30f; + incendAmount = 4; + incendSpread = 11f; + frontColor = Palette.lightishOrange; + backColor = Palette.lightOrange; + trailEffect = BulletFx.incendTrail; + } + }; + + explosive = new ArtilleryBulletType(2f, 0, "shell"){ + { + hiteffect = BulletFx.blastExplosion; + knockback = 0.8f; + lifetime = 70f; + bulletWidth = bulletHeight = 14f; + collidesTiles = false; + splashDamageRadius = 45f; + splashDamage = 50f; + backColor = Palette.missileYellowBack; + frontColor = Palette.missileYellow; + } + }; + + surge = new ArtilleryBulletType(3f, 0, "shell"){ + { + //TODO + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/BulletList.java b/core/src/io/anuke/mindustry/content/bullets/BulletList.java new file mode 100644 index 0000000000..ba0e465799 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/BulletList.java @@ -0,0 +1,14 @@ +package io.anuke.mindustry.content.bullets; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; + +public abstract class BulletList implements ContentList{ + + @Override + public Array getAll(){ + return BulletType.all(); + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java b/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java new file mode 100644 index 0000000000..276cb76c7d --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/FlakBullets.java @@ -0,0 +1,41 @@ +package io.anuke.mindustry.content.bullets; + +import io.anuke.mindustry.entities.bullet.BasicBulletType; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.type.ContentList; + +public class FlakBullets extends BulletList implements ContentList{ + public static BulletType lead, plastic, explosive, surge; + + @Override + public void load(){ + + lead = new BasicBulletType(3f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + + plastic = new BasicBulletType(3f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + + explosive = new BasicBulletType(3f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + + surge = new BasicBulletType(3f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java b/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java new file mode 100644 index 0000000000..ceea3423f2 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/MissileBullets.java @@ -0,0 +1,69 @@ +package io.anuke.mindustry.content.bullets; + +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.bullet.MissileBulletType; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; + +public class MissileBullets extends BulletList implements ContentList{ + public static BulletType explosive, incindiary, surge, javelin; + + @Override + public void load(){ + + explosive = new MissileBulletType(1.8f, 10, "missile"){ + { + bulletWidth = 8f; + bulletHeight = 8f; + bulletShrink = 0f; + drag = -0.01f; + splashDamageRadius = 30f; + splashDamage = 30f; + lifetime = 150f; + hiteffect = BulletFx.blastExplosion; + despawneffect = BulletFx.blastExplosion; + } + }; + + incindiary = new MissileBulletType(2f, 12, "missile"){ + { + frontColor = Palette.lightishOrange; + backColor = Palette.lightOrange; + bulletWidth = 7f; + bulletHeight = 8f; + bulletShrink = 0f; + drag = -0.01f; + homingPower = 7f; + splashDamageRadius = 10f; + splashDamage = 10f; + lifetime = 160f; + hiteffect = BulletFx.blastExplosion; + incendSpread = 10f; + incendAmount = 3; + } + }; + + surge = new MissileBulletType(3f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + + javelin = new MissileBulletType(2.5f, 10, "missile"){ + { + bulletWidth = 8f; + bulletHeight = 8f; + bulletShrink = 0f; + drag = -0.02f; + keepVelocity = false; + splashDamageRadius = 25f; + splashDamage = 15f; + lifetime = 90f; + hiteffect = BulletFx.blastExplosion; + despawneffect = BulletFx.blastExplosion; + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java b/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java new file mode 100644 index 0000000000..0c0939ac67 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/StandardBullets.java @@ -0,0 +1,64 @@ +package io.anuke.mindustry.content.bullets; + +import io.anuke.mindustry.entities.bullet.BasicBulletType; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; + +public class StandardBullets extends BulletList implements ContentList{ + public static BulletType tungsten, lead, carbide, thorium, homing, tracer; + + @Override + public void load(){ + + tungsten = new BasicBulletType(3.2f, 10, "bullet"){ + { + bulletWidth = 9f; + bulletHeight = 11f; + } + }; + + lead = new BasicBulletType(2.5f, 5, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + } + }; + + carbide = new BasicBulletType(3.5f, 18, "bullet"){ + { + bulletWidth = 9f; + bulletHeight = 12f; + armorPierce = 0.2f; + } + }; + + thorium = new BasicBulletType(4f, 29, "bullet"){ + { + bulletWidth = 10f; + bulletHeight = 13f; + armorPierce = 0.5f; + } + }; + + homing = new BasicBulletType(3f, 9, "bullet"){ + { + bulletWidth = 7f; + bulletHeight = 9f; + homingPower = 5f; + } + }; + + tracer = new BasicBulletType(3.2f, 11, "bullet"){ + { + bulletWidth = 10f; + bulletHeight = 12f; + frontColor = Palette.lightishOrange; + backColor = Palette.lightOrange; + incendSpread = 3f; + incendAmount = 1; + incendChance = 0.3f; + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java b/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java new file mode 100644 index 0000000000..177acacb35 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/TurretBullets.java @@ -0,0 +1,273 @@ +package io.anuke.mindustry.content.bullets; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.content.fx.EnvironmentFx; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.Damage; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.bullet.LiquidBulletType; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.effect.Lightning; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.distribution.MassDriver.DriverBulletData; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.world; + +public class TurretBullets extends BulletList implements ContentList{ + public static BulletType fireball, basicFlame, lancerLaser, fuseShot, waterShot, cryoShot, lavaShot, oilShot, lightning, driverBolt; + + @Override + public void load(){ + + fireball = new BulletType(1f, 4){ + { + pierce = true; + hitTiles = false; + collides = false; + collidesTiles = false; + drag = 0.03f; + } + + @Override + public void init(Bullet b){ + b.getVelocity().setLength(0.6f + Mathf.random(2f)); + } + + @Override + public void draw(Bullet b){ + //TODO add color to the bullet depending on the color of the flame it came from + Draw.color(Palette.lightFlame, Palette.darkFlame, Color.GRAY, b.fin()); + Fill.circle(b.x, b.y, 3f * b.fout()); + Draw.reset(); + } + + @Override + public void update(Bullet b){ + if(Mathf.chance(0.04 * Timers.delta())){ + Tile tile = world.tileWorld(b.x, b.y); + if(tile != null){ + Fire.create(tile); + } + } + + if(Mathf.chance(0.1 * Timers.delta())){ + Effects.effect(EnvironmentFx.fireballsmoke, b.x, b.y); + } + + if(Mathf.chance(0.1 * Timers.delta())){ + Effects.effect(EnvironmentFx.ballfire, b.x, b.y); + } + } + }; + + basicFlame = new BulletType(2f, 5){ + { + hitsize = 7f; + lifetime = 30f; + pierce = true; + drag = 0.07f; + hiteffect = BulletFx.hitFlameSmall; + despawneffect = Fx.none; + status = StatusEffects.burning; + } + + @Override + public void draw(Bullet b){ + } + }; + + lancerLaser = new BulletType(0.001f, 110){ + Color[] colors = {Palette.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Palette.lancerLaser, Color.WHITE}; + float[] tscales = {1f, 0.7f, 0.5f, 0.2f}; + float[] lenscales = {1f, 1.1f, 1.13f, 1.14f}; + float length = 90f; + + { + hiteffect = BulletFx.hitLancer; + despawneffect = Fx.none; + hitsize = 4; + lifetime = 16f; + pierce = true; + } + + @Override + public void init(Bullet b){ + Damage.collideLine(b, b.getTeam(), hiteffect, b.x, b.y, b.angle(), length); + } + + @Override + public void draw(Bullet b){ + float f = Mathf.curve(b.fin(), 0f, 0.2f); + float baseLen = length * f; + + Lines.lineAngle(b.x, b.y, b.angle(), baseLen); + for(int s = 0; s < 3; s++){ + Draw.color(colors[s]); + for(int i = 0; i < tscales.length; i++){ + Lines.stroke(7f * b.fout() * (s == 0 ? 1.5f : s == 1 ? 1f : 0.3f) * tscales[i]); + Lines.lineAngle(b.x, b.y, b.angle(), baseLen * lenscales[i]); + } + } + Draw.reset(); + } + }; + + fuseShot = new BulletType(0.01f, 100){ + //TODO + }; + + waterShot = new LiquidBulletType(Liquids.water){ + { + status = StatusEffects.wet; + statusIntensity = 0.5f; + knockback = 0.65f; + } + }; + cryoShot = new LiquidBulletType(Liquids.cryofluid){ + { + status = StatusEffects.freezing; + statusIntensity = 0.5f; + } + }; + lavaShot = new LiquidBulletType(Liquids.lava){ + { + damage = 4; + speed = 1.9f; + drag = 0.03f; + status = StatusEffects.melting; + statusIntensity = 0.5f; + } + }; + oilShot = new LiquidBulletType(Liquids.oil){ + { + speed = 2f; + drag = 0.03f; + status = StatusEffects.tarred; + statusIntensity = 0.5f; + } + }; + lightning = new BulletType(0.001f, 10){ + { + lifetime = 1; + despawneffect = Fx.none; + hiteffect = BulletFx.hitLancer; + } + + @Override + public void draw(Bullet b){ + } + + @Override + public void init(Bullet b){ + Lightning.create(b.getTeam(), hiteffect, Palette.lancerLaser, damage, b.x, b.y, b.angle(), 30); + } + }; + + driverBolt = new BulletType(5f, 20){ + { + collidesTiles = false; + lifetime = 200f; + despawneffect = BlockFx.smeltsmoke; + hiteffect = BulletFx.hitBulletBig; + drag = 0.02f; + } + + @Override + public void draw(Bullet b){ + Draw.color(Color.LIGHT_GRAY); + Fill.square(b.x, b.y, 3f, b.angle()); + + Draw.color(Palette.lighterOrange); + Fill.square(b.x, b.y, 2f, b.angle()); + Draw.reset(); + } + + @Override + public void update(Bullet b){ + //data MUST be an instance of DriverBulletData + if(!(b.getData() instanceof DriverBulletData)){ + hit(b); + return; + } + + float hitDst = 7f; + + DriverBulletData data = (DriverBulletData) b.getData(); + + //if the target is dead, just keep flying until the bullet explodes + if(data.to.isDead()){ + return; + } + + float baseDst = data.from.distanceTo(data.to); + float dst1 = b.distanceTo(data.from); + float dst2 = b.distanceTo(data.to); + + boolean intersect = false; + + //bullet has gone past the destination point: but did it intersect it? + if(dst1 > baseDst){ + float angleTo = b.angleTo(data.to); + float baseAngle = data.to.angleTo(data.from); + + //if angles are nearby, then yes, it did + if(Mathf.angNear(angleTo, baseAngle, 2f)){ + intersect = true; + //snap bullet position back; this is used for low-FPS situations + b.set(data.to.x + Angles.trnsx(baseAngle, hitDst), data.to.y + Angles.trnsy(baseAngle, hitDst)); + } + } + + //if on course and it's in range of the target + if(Math.abs(dst1 + dst2 - baseDst) < 4f && dst2 <= hitDst){ + intersect = true; + } //else, bullet has gone off course, does not get recieved. + + if(intersect){ + data.to.handlePayload(b, data); + } + } + + @Override + public void despawned(Bullet b){ + super.despawned(b); + + if(!(b.getData() instanceof DriverBulletData)) return; + + DriverBulletData data = (DriverBulletData) b.getData(); + data.to.isRecieving = false; + + for(int i = 0; i < data.items.length; i++){ + int amountDropped = Mathf.random(0, data.items[i]); + if(amountDropped > 0){ + float angle = b.angle() + Mathf.range(100f); + float vs = Mathf.random(0f, 4f); + ItemDrop.create(Item.getByID(i), amountDropped, b.x, b.y, Angles.trnsx(angle, vs), Angles.trnsy(angle, vs)); + } + } + } + + @Override + public void hit(Bullet b, float hitx, float hity){ + super.hit(b, hitx, hity); + despawned(b); + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java b/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java new file mode 100644 index 0000000000..04997b8c68 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/bullets/WeaponBullets.java @@ -0,0 +1,99 @@ +package io.anuke.mindustry.content.bullets; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.entities.bullet.BasicBulletType; +import io.anuke.mindustry.entities.bullet.BombBulletType; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.Puddle; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.world; + +public class WeaponBullets extends BulletList{ + public static BulletType tungstenShotgun, bombExplosive, bombIncendiary, bombOil, shellCarbide; + + @Override + public void load(){ + tungstenShotgun = new BasicBulletType(5f, 8, "bullet"){ + { + bulletWidth = 8f; + bulletHeight = 9f; + bulletShrink = 0.6f; + lifetime = 30f; + drag = 0.04f; + } + }; + + bombExplosive = new BombBulletType(20f, 20f, "shell"){ + { + bulletWidth = 9f; + bulletHeight = 13f; + hiteffect = BulletFx.flakExplosion; + } + }; + + bombIncendiary = new BombBulletType(15f, 10f, "shell"){ + { + bulletWidth = 8f; + bulletHeight = 12f; + hiteffect = BulletFx.flakExplosion; + backColor = Palette.lightOrange; + frontColor = Palette.lightishOrange; + } + + @Override + public void hit(Bullet b, float x, float y){ + super.hit(b, x, y); + + for(int i = 0; i < 3; i++){ + float cx = x + Mathf.range(10f); + float cy = y + Mathf.range(10f); + Tile tile = world.tileWorld(cx, cy); + if(tile != null){ + Fire.create(tile); + } + } + } + }; + + bombOil = new BombBulletType(3f, 3f, "shell"){ + { + bulletWidth = 8f; + bulletHeight = 12f; + hiteffect = BlockFx.pulverize; + backColor = new Color(0x4f4f4fff); + frontColor = Color.GRAY; + } + + @Override + public void hit(Bullet b, float x, float y){ + super.hit(b, x, y); + + for(int i = 0; i < 3; i++){ + Tile tile = world.tileWorld(x + Mathf.range(8f), y + Mathf.range(8f)); + Puddle.deposit(tile, Liquids.oil, 5f); + } + } + }; + + shellCarbide = new BasicBulletType(3.4f, 20, "bullet"){ + { + bulletWidth = 10f; + bulletHeight = 12f; + bulletShrink = 0.4f; + lifetime = 40f; + drag = 0.025f; + fragBullets = 5; + hiteffect = BulletFx.flakExplosion; + fragBullet = tungstenShotgun; + } + }; + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/BlockFx.java b/core/src/io/anuke/mindustry/content/fx/BlockFx.java new file mode 100644 index 0000000000..9260c76b0f --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/BlockFx.java @@ -0,0 +1,278 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Hue; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Tmp; + +import static io.anuke.mindustry.Vars.tilesize; + +public class BlockFx extends FxList implements ContentList{ + public static Effect reactorsmoke, nuclearsmoke, nuclearcloud, redgeneratespark, generatespark, fuelburn, plasticburn, pulverize, pulverizeRed, pulverizeRedder, pulverizeSmall, pulverizeMedium, producesmoke, smeltsmoke, formsmoke, blastsmoke, lava, dooropen, doorclose, dooropenlarge, doorcloselarge, purify, purifyoil, purifystone, generate, mine, mineBig, mineHuge, smelt, teleportActivate, teleport, teleportOut, ripple, bubble; + + @Override + public void load(){ + + reactorsmoke = new Effect(17, e -> { + Angles.randLenVectors(e.id, 4, e.fin() * 8f, (x, y) -> { + float size = 1f + e.fout() * 5f; + Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, size, size); + Draw.reset(); + }); + }); + nuclearsmoke = new Effect(40, e -> { + Angles.randLenVectors(e.id, 4, e.fin() * 13f, (x, y) -> { + float size = e.fslope() * 4f; + Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, size, size); + Draw.reset(); + }); + }); + nuclearcloud = new Effect(90, 200f, e -> { + Angles.randLenVectors(e.id, 10, e.finpow() * 90f, (x, y) -> { + float size = e.fout() * 14f; + Draw.color(Color.LIME, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, size, size); + Draw.reset(); + }); + }); + redgeneratespark = new Effect(18, e -> { + Angles.randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> { + float len = e.fout() * 4f; + Draw.color(Palette.redSpark, Color.GRAY, e.fin()); + //Draw.alpha(e.fout()); + Draw.rect("circle", e.x + x, e.y + y, len, len); + Draw.reset(); + }); + }); + generatespark = new Effect(18, e -> { + Angles.randLenVectors(e.id, 5, e.fin() * 8f, (x, y) -> { + float len = e.fout() * 4f; + Draw.color(Palette.orangeSpark, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, len, len); + Draw.reset(); + }); + }); + fuelburn = new Effect(23, e -> { + Angles.randLenVectors(e.id, 5, e.fin() * 9f, (x, y) -> { + float len = e.fout() * 4f; + Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, len, len); + Draw.reset(); + }); + }); + plasticburn = new Effect(40, e -> { + Angles.randLenVectors(e.id, 5, 3f + e.fin() * 5f, (x, y) -> { + Draw.color(Color.valueOf("e9ead3"), Color.GRAY, e.fin()); + Fill.circle(e.x + x, e.y + y, e.fout() * 1f); + Draw.reset(); + }); + }); + pulverize = new Effect(40, e -> { + Angles.randLenVectors(e.id, 5, 3f + e.fin() * 8f, (x, y) -> { + Draw.color(Palette.stoneGray); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2f + 0.5f, 45); + Draw.reset(); + }); + }); + pulverizeRed = new Effect(40, e -> { + Angles.randLenVectors(e.id, 5, 3f + e.fin() * 8f, (x, y) -> { + Draw.color(Palette.redDust, Palette.stoneGray, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2f + 0.5f, 45); + Draw.reset(); + }); + }); + pulverizeRedder = new Effect(40, e -> { + Angles.randLenVectors(e.id, 5, 3f + e.fin() * 9f, (x, y) -> { + Draw.color(Palette.redderDust, Palette.stoneGray, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2.5f + 0.5f, 45); + Draw.reset(); + }); + }); + pulverizeSmall = new Effect(30, e -> { + Angles.randLenVectors(e.id, 3, e.fin() * 5f, (x, y) -> { + Draw.color(Palette.stoneGray); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 1f + 0.5f, 45); + Draw.reset(); + }); + }); + pulverizeMedium = new Effect(30, e -> { + Angles.randLenVectors(e.id, 5, 3f + e.fin() * 8f, (x, y) -> { + Draw.color(Palette.stoneGray); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 1f + 0.5f, 45); + Draw.reset(); + }); + }); + producesmoke = new Effect(12, e -> { + Angles.randLenVectors(e.id, 8, 4f + e.fin() * 18f, (x, y) -> { + Draw.color(Color.WHITE, Palette.accent, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, 1f + e.fout() * 3f, 45); + Draw.reset(); + }); + }); + smeltsmoke = new Effect(15, e -> { + Angles.randLenVectors(e.id, 6, 4f + e.fin() * 5f, (x, y) -> { + Draw.color(Color.WHITE, e.color, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, 0.5f + e.fout() * 2f, 45); + Draw.reset(); + }); + }); + formsmoke = new Effect(40, e -> { + Angles.randLenVectors(e.id, 6, 5f + e.fin() * 8f, (x, y) -> { + Draw.color(Palette.plasticSmoke, Color.LIGHT_GRAY, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, 0.2f + e.fout() * 2f, 45); + Draw.reset(); + }); + }); + blastsmoke = new Effect(26, e -> { + Angles.randLenVectors(e.id, 12, 1f + e.fin() * 23f, (x, y) -> { + float size = 2f + e.fout() * 6f; + Draw.color(Color.LIGHT_GRAY, Color.DARK_GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, size, size); + Draw.reset(); + }); + }); + lava = new Effect(18, e -> { + Angles.randLenVectors(e.id, 3, 1f + e.fin() * 10f, (x, y) -> { + float size = e.fslope() * 4f; + Draw.color(Color.ORANGE, Color.GRAY, e.fin()); + Draw.rect("circle", e.x + x, e.y + y, size, size); + Draw.reset(); + }); + }); + dooropen = new Effect(10, e -> { + Lines.stroke(e.fout() * 1.6f); + Lines.square(e.x, e.y, tilesize / 2f + e.fin() * 2f); + Draw.reset(); + }); + doorclose = new Effect(10, e -> { + Lines.stroke(e.fout() * 1.6f); + Lines.square(e.x, e.y, tilesize / 2f + e.fout() * 2f); + Draw.reset(); + }); + dooropenlarge = new Effect(10, e -> { + Lines.stroke(e.fout() * 1.6f); + Lines.square(e.x, e.y, tilesize + e.fin() * 2f); + Draw.reset(); + }); + doorcloselarge = new Effect(10, e -> { + Lines.stroke(e.fout() * 1.6f); + Lines.square(e.x, e.y, tilesize + e.fout() * 2f); + Draw.reset(); + }); + purify = new Effect(10, e -> { + Draw.color(Color.ROYAL, Color.GRAY, e.fin()); + Lines.stroke(2f); + Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); + Draw.reset(); + }); + purifyoil = new Effect(10, e -> { + Draw.color(Color.BLACK, Color.GRAY, e.fin()); + Lines.stroke(2f); + Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); + Draw.reset(); + }); + purifystone = new Effect(10, e -> { + Draw.color(Color.ORANGE, Color.GRAY, e.fin()); + Lines.stroke(2f); + Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); + Draw.reset(); + }); + generate = new Effect(11, e -> { + Draw.color(Color.ORANGE, Color.YELLOW, e.fin()); + Lines.stroke(1f); + Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8); + Draw.reset(); + }); + mine = new Effect(20, e -> { + Angles.randLenVectors(e.id, 6, 3f + e.fin() * 6f, (x, y) -> { + Draw.color(e.color, Color.LIGHT_GRAY, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2f, 45); + Draw.reset(); + }); + }); + mineBig = new Effect(30, e -> { + Angles.randLenVectors(e.id, 6, 4f + e.fin() * 8f, (x, y) -> { + Draw.color(e.color, Color.LIGHT_GRAY, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2f + 0.2f, 45); + Draw.reset(); + }); + }); + mineHuge = new Effect(40, e -> { + Angles.randLenVectors(e.id, 8, 5f + e.fin() * 10f, (x, y) -> { + Draw.color(e.color, Color.LIGHT_GRAY, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, e.fout() * 2f + 0.5f, 45); + Draw.reset(); + }); + }); + smelt = new Effect(20, e -> { + Angles.randLenVectors(e.id, 6, 2f + e.fin() * 5f, (x, y) -> { + Draw.color(Color.WHITE, e.color, e.fin()); + Fill.poly(e.x + x, e.y + y, 4, 0.5f + e.fout() * 2f, 45); + Draw.reset(); + }); + }); + teleportActivate = new Effect(50, e -> { + Draw.color(e.color); + + e.scaled(8f, e2 -> { + Lines.stroke(e2.fout() * 4f); + Lines.circle(e2.x, e2.y, 4f + e2.fin() * 27f); + }); + + Lines.stroke(e.fout() * 2f); + + Angles.randLenVectors(e.id, 30, 4f + 40f * e.fin(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fin() * 4f + 1f); + }); + + Draw.reset(); + }); + teleport = new Effect(60, e -> { + Draw.color(e.color); + Lines.stroke(e.fin() * 2f); + Lines.circle(e.x, e.y, 7f + e.fout() * 8f); + + Angles.randLenVectors(e.id, 20, 6f + 20f * e.fout(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fin() * 4f + 1f); + }); + + Draw.reset(); + }); + teleportOut = new Effect(20, e -> { + Draw.color(e.color); + Lines.stroke(e.fout() * 2f); + Lines.circle(e.x, e.y, 7f + e.fin() * 8f); + + Angles.randLenVectors(e.id, 20, 4f + 20f * e.fin(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fslope() * 4f + 1f); + }); + + Draw.reset(); + }); + ripple = new GroundEffect(false, 30, e -> { + Draw.color(Hue.shift(Tmp.c1.set(e.color), 2, 0.1f)); + Lines.stroke(e.fout() + 0.4f); + Lines.circle(e.x, e.y, 2f + e.fin() * 4f); + Draw.reset(); + }); + + bubble = new Effect(20, e -> { + Draw.color(Hue.shift(Tmp.c1.set(e.color), 2, 0.1f)); + Lines.stroke(e.fout() + 0.2f); + Angles.randLenVectors(e.id, 2, 8f, (x, y) -> { + Lines.circle(e.x + x, e.y + y, 1f + e.fin() * 3f); + }); + Draw.reset(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/BulletFx.java b/core/src/io/anuke/mindustry/content/fx/BulletFx.java new file mode 100644 index 0000000000..73a53a6c70 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/BulletFx.java @@ -0,0 +1,181 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +public class BulletFx extends FxList implements ContentList{ + public static Effect hitBulletSmall, hitBulletBig, hitFlameSmall, hitLiquid, hitLancer, despawn, flakExplosion, blastExplosion, plasticExplosion, + artilleryTrail, incendTrail, missileTrail; + + @Override + public void load(){ + + hitBulletSmall = new Effect(14, e -> { + Draw.color(Color.WHITE, Palette.lightOrange, e.fin()); + Lines.stroke(0.5f + e.fout()); + + Angles.randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 50f, (x, y) -> { + float ang = Mathf.atan2(x, y); + Lines.lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f); + }); + + Draw.reset(); + }); + + hitBulletBig = new Effect(13, e -> { + Draw.color(Color.WHITE, Palette.lightOrange, e.fin()); + Lines.stroke(0.5f + e.fout() * 1.5f); + + Angles.randLenVectors(e.id, 8, e.finpow() * 30f, e.rotation, 50f, (x, y) -> { + float ang = Mathf.atan2(x, y); + Lines.lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1.5f); + }); + + Draw.reset(); + }); + + hitFlameSmall = new Effect(14, e -> { + Draw.color(Palette.lightFlame, Palette.darkFlame, e.fin()); + Lines.stroke(0.5f + e.fout()); + + Angles.randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 50f, (x, y) -> { + float ang = Mathf.atan2(x, y); + Lines.lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f); + }); + + Draw.reset(); + }); + + hitLiquid = new Effect(16, e -> { + Draw.color(e.color); + + Angles.randLenVectors(e.id, 5, e.fin() * 15f, e.rotation + 180f, 60f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 2f); + }); + + Draw.reset(); + }); + + hitLancer = new Effect(12, e -> { + Draw.color(Color.WHITE); + Lines.stroke(e.fout() * 1.5f); + + Angles.randLenVectors(e.id, 8, e.finpow() * 17f, e.rotation, 360f, (x, y) -> { + float ang = Mathf.atan2(x, y); + Lines.lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f); + }); + + Draw.reset(); + }); + + despawn = new Effect(12, e -> { + Draw.color(Palette.lighterOrange, Color.GRAY, e.fin()); + Lines.stroke(e.fout()); + + Angles.randLenVectors(e.id, 7, e.fin() * 7f, e.rotation, 40f, (x, y) -> { + float ang = Mathf.atan2(x, y); + Lines.lineAngle(e.x + x, e.y + y, ang, e.fout() * 2 + 1f); + }); + + Draw.reset(); + }); + + flakExplosion = new Effect(20, e -> { + + Draw.color(Palette.bulletYellow); + e.scaled(6, i -> { + Lines.stroke(3f * i.fout()); + Lines.circle(e.x, e.y, 3f + i.fin() * 10f); + }); + + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 5, 2f + 23f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f); + }); + + Draw.color(Palette.lighterOrange); + Lines.stroke(1f * e.fout()); + + Angles.randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), 1f + e.fout() * 3f); + }); + + Draw.reset(); + }); + + plasticExplosion = new Effect(24, e -> { + + Draw.color(Palette.plastaniumFront); + e.scaled(7, i -> { + Lines.stroke(3f * i.fout()); + Lines.circle(e.x, e.y, 3f + i.fin() * 24f); + }); + + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 7, 2f + 28f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f); + }); + + Draw.color(Palette.plastaniumBack); + Lines.stroke(1f * e.fout()); + + Angles.randLenVectors(e.id + 1, 4, 1f + 25f * e.finpow(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), 1f + e.fout() * 3f); + }); + + Draw.reset(); + }); + + blastExplosion = new Effect(22, e -> { + + Draw.color(Palette.missileYellow); + e.scaled(6, i -> { + Lines.stroke(3f * i.fout()); + Lines.circle(e.x, e.y, 3f + i.fin() * 15f); + }); + + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 5, 2f + 23f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.5f); + }); + + Draw.color(Palette.missileYellowBack); + Lines.stroke(1f * e.fout()); + + Angles.randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), 1f + e.fout() * 3f); + }); + + Draw.reset(); + }); + + artilleryTrail = new Effect(50, e -> { + Draw.color(e.color); + Fill.circle(e.x, e.y, e.rotation * e.fout()); + Draw.reset(); + }); + + incendTrail = new Effect(50, e -> { + Draw.color(Palette.lightOrange); + Fill.circle(e.x, e.y, e.rotation * e.fout()); + Draw.reset(); + }); + + missileTrail = new Effect(50, e -> { + Draw.color(Palette.missileYellowBack); + Fill.circle(e.x, e.y, e.rotation * e.fout()); + Draw.reset(); + }); + } + +} diff --git a/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java b/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java new file mode 100644 index 0000000000..e8f3fe6b2e --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/EnvironmentFx.java @@ -0,0 +1,119 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +public class EnvironmentFx extends FxList implements ContentList{ + public static Effect burning, fire, smoke, steam, fireballsmoke, ballfire, freezing, melting, wet, oily; + + @Override + public void load(){ + + burning = new Effect(35f, e -> { + Draw.color(Palette.lightFlame, Palette.darkFlame, e.fin()); + + Angles.randLenVectors(e.id, 3, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.1f + e.fout() * 1.4f); + }); + + Draw.color(); + }); + + fire = new Effect(35f, e -> { + Draw.color(Palette.lightFlame, Palette.darkFlame, e.fin()); + + Angles.randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f); + }); + + Draw.color(); + }); + + smoke = new Effect(35f, e -> { + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f); + }); + + Draw.color(); + }); + + steam = new Effect(35f, e -> { + Draw.color(Color.LIGHT_GRAY); + + Angles.randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f); + }); + + Draw.color(); + }); + + fireballsmoke = new Effect(25f, e -> { + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.2f + e.fout() * 1.5f); + }); + + Draw.color(); + }); + + ballfire = new Effect(25f, e -> { + Draw.color(Palette.lightFlame, Palette.darkFlame, e.fin()); + + Angles.randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.2f + e.fout() * 1.5f); + }); + + Draw.color(); + }); + + freezing = new Effect(40f, e -> { + Draw.color(Liquids.cryofluid.color); + + Angles.randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 1.2f); + }); + + Draw.color(); + }); + + melting = new Effect(40f, e -> { + Draw.color(Liquids.lava.color, Color.WHITE, e.fout() / 5f + Mathf.randomSeedRange(e.id, 0.12f)); + + Angles.randLenVectors(e.id, 2, 1f + e.fin() * 3f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, .2f + e.fout() * 1.2f); + }); + + Draw.color(); + }); + + wet = new Effect(40f, e -> { + Draw.color(Liquids.water.color); + + Angles.randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 1f); + }); + + Draw.color(); + }); + + oily = new Effect(42f, e -> { + Draw.color(Liquids.oil.color); + + Angles.randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 1f); + }); + + Draw.color(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java b/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java new file mode 100644 index 0000000000..457c2dfe32 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/ExplosionFx.java @@ -0,0 +1,97 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +public class ExplosionFx extends FxList implements ContentList{ + public static Effect shockwave, bigShockwave, nuclearShockwave, explosion, blockExplosion, blockExplosionSmoke; + + @Override + public void load(){ + + shockwave = new Effect(10f, 80f, e -> { + Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); + Lines.stroke(e.fout() * 2f + 0.2f); + Lines.circle(e.x, e.y, e.fin() * 28f); + Draw.reset(); + }); + + bigShockwave = new Effect(10f, 80f, e -> { + Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); + Lines.stroke(e.fout() * 3f); + Lines.circle(e.x, e.y, e.fin() * 50f); + Draw.reset(); + }); + + nuclearShockwave = new Effect(10f, 200f, e -> { + Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); + Lines.stroke(e.fout() * 3f + 0.2f); + Lines.poly(e.x, e.y, 40, e.fin() * 140f); + Draw.reset(); + }); + + explosion = new Effect(30, e -> { + e.scaled(7, i -> { + Lines.stroke(3f * i.fout()); + Lines.circle(e.x, e.y, 3f + i.fin() * 10f); + }); + + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f); + Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f); + }); + + Draw.color(Palette.lighterOrange, Palette.lightOrange, Color.GRAY, e.fin()); + Lines.stroke(1.5f * e.fout()); + + Angles.randLenVectors(e.id + 1, 8, 1f + 23f * e.finpow(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), 1f + e.fout() * 3f); + }); + + Draw.reset(); + }); + + blockExplosion = new Effect(30, e -> { + e.scaled(7, i -> { + Lines.stroke(3.1f * i.fout()); + Lines.circle(e.x, e.y, 3f + i.fin() * 14f); + }); + + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f); + Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f); + }); + + Draw.color(Palette.lighterOrange, Palette.lightOrange, Color.GRAY, e.fin()); + Lines.stroke(1.7f * e.fout()); + + Angles.randLenVectors(e.id + 1, 9, 1f + 23f * e.finpow(), (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), 1f + e.fout() * 3f); + }); + + Draw.reset(); + }); + + blockExplosionSmoke = new Effect(30, e -> { + Draw.color(Color.GRAY); + + Angles.randLenVectors(e.id, 6, 4f + 30f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 3f); + Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f); + }); + + Draw.reset(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/Fx.java b/core/src/io/anuke/mindustry/content/fx/Fx.java new file mode 100644 index 0000000000..f76f12d233 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/Fx.java @@ -0,0 +1,69 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; + +import static io.anuke.mindustry.Vars.tilesize; + +public class Fx extends FxList implements ContentList{ + public static Effect none, placeBlock, breakBlock, smoke, spawn, tapBlock, select; + + @Override + public void load(){ + + none = new Effect(0, 0f, e -> { + }); + + placeBlock = new Effect(16, e -> { + Draw.color(Palette.accent); + Lines.stroke(3f - e.fin() * 2f); + Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); + Draw.reset(); + }); + + tapBlock = new Effect(12, e -> { + Draw.color(Palette.accent); + Lines.stroke(3f - e.fin() * 2f); + Lines.circle(e.x, e.y, 4f + (tilesize / 1.5f * e.rotation) * e.fin()); + Draw.reset(); + }); + + breakBlock = new Effect(12, e -> { + Draw.color(Palette.remove); + Lines.stroke(3f - e.fin() * 2f); + Lines.square(e.x, e.y, tilesize / 2f * e.rotation + e.fin() * 3f); + + Angles.randLenVectors(e.id, 3 + (int) (e.rotation * 3), e.rotation * 2f + (tilesize * e.rotation) * e.finpow(), (x, y) -> { + Fill.square(e.x + x, e.y + y, 1f + e.fout() * (3f + e.rotation)); + }); + Draw.reset(); + }); + + select = new Effect(23, e -> { + Draw.color(Palette.accent); + Lines.stroke(e.fout() * 3f); + Lines.circle(e.x, e.y, 3f + e.fin() * 14f); + Draw.reset(); + }); + + smoke = new Effect(100, e -> { + Draw.color(Color.GRAY, Palette.darkishGray, e.fin()); + float size = 7f - e.fin() * 7f; + Draw.rect("circle", e.x, e.y, size, size); + Draw.reset(); + }); + + spawn = new Effect(23, e -> { + Lines.stroke(2f * e.fout()); + Draw.color(Palette.accent); + Lines.poly(e.x, e.y, 4, 3f + e.fin() * 8f); + Draw.reset(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/FxList.java b/core/src/io/anuke/mindustry/content/fx/FxList.java new file mode 100644 index 0000000000..fe87cc2eb8 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/FxList.java @@ -0,0 +1,13 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.ContentList; + +public abstract class FxList implements ContentList{ + + @Override + public Array getAll(){ + return Array.with(); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/ShootFx.java b/core/src/io/anuke/mindustry/content/fx/ShootFx.java new file mode 100644 index 0000000000..2e55246cc8 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/ShootFx.java @@ -0,0 +1,217 @@ +package io.anuke.mindustry.content.fx; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.graphics.Shapes; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +public class ShootFx extends FxList implements ContentList{ + public static Effect shootSmall, shootSmallSmoke, shootBig, shootBig2, shootBigSmoke, shootBigSmoke2, shootSmallFlame, shootLiquid, shellEjectSmall, shellEjectMedium, shellEjectBig, lancerLaserShoot, lancerLaserShootSmoke, lancerLaserCharge, lancerLaserChargeBegin, lightningCharge, lightningShoot; + + @Override + public void load(){ + + shootSmall = new Effect(8, e -> { + Draw.color(Palette.lighterOrange, Palette.lightOrange, e.fin()); + float w = 1f + 5 * e.fout(); + Shapes.tri(e.x, e.y, w, 15f * e.fout(), e.rotation); + Shapes.tri(e.x, e.y, w, 3f * e.fout(), e.rotation + 180f); + Draw.reset(); + }); + + shootSmallSmoke = new Effect(20f, e -> { + Draw.color(Palette.lighterOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin()); + + Angles.randLenVectors(e.id, 5, e.finpow() * 6f, e.rotation, 20f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f); + }); + + Draw.reset(); + }); + + shootBig = new Effect(9, e -> { + Draw.color(Palette.lighterOrange, Palette.lightOrange, e.fin()); + float w = 1.2f + 7 * e.fout(); + Shapes.tri(e.x, e.y, w, 25f * e.fout(), e.rotation); + Shapes.tri(e.x, e.y, w, 4f * e.fout(), e.rotation + 180f); + Draw.reset(); + }); + + shootBig2 = new Effect(10, e -> { + Draw.color(Palette.lightOrange, Color.GRAY, e.fin()); + float w = 1.2f + 8 * e.fout(); + Shapes.tri(e.x, e.y, w, 29f * e.fout(), e.rotation); + Shapes.tri(e.x, e.y, w, 5f * e.fout(), e.rotation + 180f); + Draw.reset(); + }); + + shootBigSmoke = new Effect(17f, e -> { + Draw.color(Palette.lighterOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin()); + + Angles.randLenVectors(e.id, 8, e.finpow() * 19f, e.rotation, 10f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 2f + 0.2f); + }); + + Draw.reset(); + }); + + shootBigSmoke2 = new Effect(18f, e -> { + Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Color.GRAY, e.fin()); + + Angles.randLenVectors(e.id, 9, e.finpow() * 23f, e.rotation, 20f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 2.4f + 0.2f); + }); + + Draw.reset(); + }); + + shootSmallFlame = new Effect(30f, e -> { + Draw.color(Palette.lightFlame, Palette.darkFlame, Color.GRAY, e.fin()); + + Angles.randLenVectors(e.id, 8, e.finpow() * 26f, e.rotation, 10f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.65f + e.fout() * 1.5f); + }); + + Draw.reset(); + }); + + shootLiquid = new Effect(40f, e -> { + Draw.color(e.color, Color.WHITE, e.fout() / 6f + Mathf.randomSeedRange(e.id, 0.1f)); + + Angles.randLenVectors(e.id, 6, e.finpow() * 60f, e.rotation, 11f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, 0.5f + e.fout() * 2.5f); + }); + + Draw.reset(); + }); + + shellEjectSmall = new GroundEffect(30f, 400f, e -> { + Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Palette.lightishGray, e.fin()); + float rot = Math.abs(e.rotation) + 90f; + + int i = Mathf.sign(e.rotation); + + float len = (2f + e.finpow() * 6f) * i; + float lr = rot + e.fin() * 30f * i; + Draw.rect("white", + e.x + Angles.trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()), + e.y + Angles.trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()), + 1f, 2f, rot + e.fin() * 50f * i); + + Draw.color(); + }); + + shellEjectMedium = new GroundEffect(34f, 400f, e -> { + Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Palette.lightishGray, e.fin()); + float rot = e.rotation + 90f; + for(int i : Mathf.signs){ + float len = (2f + e.finpow() * 10f) * i; + float lr = rot + e.fin() * 20f * i; + Draw.rect("casing", + e.x + Angles.trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()), + e.y + Angles.trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()), + 2f, 3f, rot); + } + + Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); + + for(int i : Mathf.signs){ + Angles.randLenVectors(e.id, 4, 1f + e.finpow() * 11f, e.rotation + 90f * i, 20f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f); + }); + } + + Draw.color(); + }); + + shellEjectBig = new GroundEffect(22f, 400f, e -> { + Draw.color(Palette.lightOrange, Color.LIGHT_GRAY, Palette.lightishGray, e.fin()); + float rot = e.rotation + 90f; + for(int i : Mathf.signs){ + float len = (4f + e.finpow() * 8f) * i; + float lr = rot + Mathf.randomSeedRange(e.id + i + 6, 20f * e.fin()) * i; + Draw.rect("casing", + e.x + Angles.trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()), + e.y + Angles.trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()), + 2.5f, 4f, + rot + e.fin() * 30f * i + Mathf.randomSeedRange(e.id + i + 9, 40f * e.fin())); + } + + Draw.color(Color.LIGHT_GRAY); + + for(int i : Mathf.signs){ + Angles.randLenVectors(e.id, 4, -e.finpow() * 15f, e.rotation + 90f * i, 25f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 2f); + }); + } + + Draw.color(); + }); + + lancerLaserShoot = new Effect(21f, e -> { + Draw.color(Palette.lancerLaser); + + for(int i : Mathf.signs){ + Shapes.tri(e.x, e.y, 4f * e.fout(), 29f, e.rotation + 90f * i); + } + + Draw.reset(); + }); + + lancerLaserShootSmoke = new Effect(26f, e -> { + Draw.color(Palette.lancerLaser); + + Angles.randLenVectors(e.id, 7, 80f, e.rotation, 0f, (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fout() * 9f); + }); + + Draw.reset(); + }); + + lancerLaserCharge = new Effect(38f, e -> { + Draw.color(Palette.lancerLaser); + + Angles.randLenVectors(e.id, 2, 1f + 20f * e.fout(), e.rotation, 120f, (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fslope() * 3f + 1f); + }); + + Draw.reset(); + }); + + lancerLaserChargeBegin = new Effect(71f, e -> { + Draw.color(Palette.lancerLaser); + Fill.circle(e.x, e.y, e.fin() * 3f); + + Draw.color(); + Fill.circle(e.x, e.y, e.fin() * 2f); + }); + + lightningCharge = new Effect(38f, e -> { + Draw.color(Palette.lancerLaser); + + Angles.randLenVectors(e.id, 2, 1f + 20f * e.fout(), e.rotation, 120f, (x, y) -> { + Shapes.tri(e.x + x, e.y + y, e.fslope() * 3f + 1, e.fslope() * 3f + 1, Mathf.atan2(x, y)); + }); + + Draw.reset(); + }); + + lightningShoot = new Effect(12f, e -> { + Draw.color(Color.WHITE, Palette.lancerLaser, e.fin()); + Lines.stroke(e.fout() * 1.2f + 0.5f); + + Angles.randLenVectors(e.id, 7, 25f * e.finpow(), e.rotation, 50f, (x, y) -> { + Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), e.fin() * 5f + 2f); + }); + + Draw.reset(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/content/fx/UnitFx.java b/core/src/io/anuke/mindustry/content/fx/UnitFx.java new file mode 100644 index 0000000000..69117a0237 --- /dev/null +++ b/core/src/io/anuke/mindustry/content/fx/UnitFx.java @@ -0,0 +1,49 @@ +package io.anuke.mindustry.content.fx; + +import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.ContentList; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +public class UnitFx extends FxList implements ContentList{ + public static Effect vtolHover, unitDrop, unitPickup, pickup; + + @Override + public void load(){ + + vtolHover = new Effect(40f, e -> { + float len = e.finpow() * 10f; + float ang = e.rotation + Mathf.randomSeedRange(e.id, 30f); + Draw.color(Palette.lightFlame, Palette.lightOrange, e.fin()); + Fill.circle(e.x + Angles.trnsx(ang, len), e.y + Angles.trnsy(ang, len), 2f * e.fout()); + Draw.reset(); + }); + + unitDrop = new GroundEffect(30, e -> { + Draw.color(Palette.lightishGray); + Angles.randLenVectors(e.id, 9, 3 + 20f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.4f); + }); + Draw.reset(); + }); + + unitPickup = new GroundEffect(18, e -> { + Draw.color(Palette.lightishGray); + Lines.stroke(e.fin() * 2f); + Lines.poly(e.x, e.y, 4, 13f * e.fout()); + Draw.reset(); + }); + + pickup = new Effect(18, e -> { + Draw.color(Palette.lightishGray); + Lines.stroke(e.fout() * 2f); + Lines.spikes(e.x, e.y, 1f + e.fin() * 6f, e.fout() * 4f, 6); + Draw.reset(); + }); + } +} diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java new file mode 100644 index 0000000000..db6a5ed7a2 --- /dev/null +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -0,0 +1,176 @@ +package io.anuke.mindustry.core; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.OrderedMap; +import com.badlogic.gdx.utils.OrderedSet; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.content.blocks.*; +import io.anuke.mindustry.content.bullets.*; +import io.anuke.mindustry.content.fx.*; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.effect.Lightning; +import io.anuke.mindustry.entities.effect.Puddle; +import io.anuke.mindustry.entities.traits.TypeTrait; +import io.anuke.mindustry.entities.units.UnitType; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.ColorMapper; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.util.Log; + +/** + * Loads all game content. + * Call load() before doing anything with content. + */ +public class ContentLoader{ + private static boolean loaded = false; + private static ObjectSet> contentSet = new OrderedSet<>(); + private static OrderedMap> contentMap = new OrderedMap<>(); + private static ObjectSet> initialization = new ObjectSet<>(); + private static ContentList[] content = { + //effects + new BlockFx(), + new BulletFx(), + new EnvironmentFx(), + new ExplosionFx(), + new Fx(), + new ShootFx(), + new UnitFx(), + + //items + new Items(), + + //status effects + new StatusEffects(), + + //liquids + new Liquids(), + + //bullets + new ArtilleryBullets(), + new FlakBullets(), + new MissileBullets(), + new StandardBullets(), + new TurretBullets(), + new WeaponBullets(), + + + //ammotypes + new AmmoTypes(), + + //weapons + new Weapons(), + + //mechs + new Mechs(), + + //units + new UnitTypes(), + + //blocks + new Blocks(), + new DefenseBlocks(), + new DistributionBlocks(), + new ProductionBlocks(), + new TurretBlocks(), + new DebugBlocks(), + new LiquidBlocks(), + new StorageBlocks(), + new UnitBlocks(), + new PowerBlocks(), + new CraftingBlocks(), + new UpgradeBlocks(), + new OreBlocks(), + + //not really a content class, but this makes initialization easier + new ColorMapper(), + + //recipes + new Recipes(), + }; + + /** + * Creates all content types. + */ + public static void load(){ + if(loaded){ + Log.info("Content already loaded, skipping."); + return; + } + + registerTypes(); + + for(ContentList list : content){ + list.load(); + } + + for(ContentList list : content){ + if(list.getAll().size != 0){ + String type = list.getAll().first().getContentTypeName(); + + if(!contentMap.containsKey(type)){ + contentMap.put(type, new Array<>()); + } + + contentMap.get(type).addAll(list.getAll()); + } + contentSet.add(list.getAll()); + } + + if(Block.all().size >= 256){ + throw new IllegalArgumentException("THE TIME HAS COME. More than 256 blocks have been created."); + } + + Log.info("--- CONTENT INFO ---"); + Log.info("Blocks loaded: {0}\nItems loaded: {1}\nLiquids loaded: {2}\nUpgrades loaded: {3}\nUnits loaded: {4}\nAmmo types loaded: {5}\nBullet types loaded: {6}\nStatus effects loaded: {7}\nRecipes loaded: {8}\nEffects loaded: {9}\nTotal content classes: {10}", + Block.all().size, Item.all().size, Liquid.all().size, Mech.all().size, UnitType.all().size, + AmmoType.all().size, BulletType.all().size, StatusEffect.all().size, Recipe.all().size, Effects.all().size, content.length); + + Log.info("-------------------"); + + loaded = true; + } + + /** + * Initializes all content with the specified function. + */ + public static void initialize(Consumer callable){ + if(initialization.contains(callable)) return; + + for(Array arr : contentSet){ + for(Content content : arr){ + callable.accept(content); + } + } + + initialization.add(callable); + } + + public static void dispose(){ + //TODO clear all content. + } + + public static OrderedMap> getContentMap(){ + return contentMap; + } + + /** + * Registers sync IDs for all types of sync entities. + * Do not register units here! + */ + private static void registerTypes(){ + TypeTrait.registerType(Player.class, Player::new); + TypeTrait.registerType(ItemDrop.class, ItemDrop::new); + TypeTrait.registerType(Fire.class, Fire::new); + TypeTrait.registerType(Puddle.class, Puddle::new); + TypeTrait.registerType(Bullet.class, Bullet::new); + TypeTrait.registerType(Lightning.class, Lightning::new); + } +} diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index c319cc7e0e..78ee87ed73 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -1,410 +1,409 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.game.ContentDatabase; import io.anuke.mindustry.game.EventType.*; -import io.anuke.mindustry.game.Tutorial; -import io.anuke.mindustry.game.UpgradeInventory; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.input.AndroidInput; import io.anuke.mindustry.input.DefaultKeybinds; import io.anuke.mindustry.input.DesktopInput; import io.anuke.mindustry.input.InputHandler; +import io.anuke.mindustry.input.MobileInput; +import io.anuke.mindustry.io.Map; import io.anuke.mindustry.io.Saves; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Map; -import io.anuke.ucore.UCore; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; import io.anuke.ucore.core.*; -import io.anuke.ucore.core.Inputs.DeviceType; import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.entities.EntityPhysics; import io.anuke.ucore.modules.Module; -import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Atlas; -import io.anuke.ucore.util.InputProxy; -import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; -/**Control module. +/** + * Control module. * Handles all input, saving, keybinds and keybinds. - * Should not handle any game-critical state. - * This class is not created in the headless server.*/ + * Should not handle any logic-critical state. + * This class is not created in the headless server. + */ public class Control extends Module{ - private UpgradeInventory upgrades = new UpgradeInventory(); - private Tutorial tutorial = new Tutorial(); - private boolean hiscore = false; + /** Minimum period of time between the same sound being played.*/ + private static final long minSoundPeriod = 100; - private boolean wasPaused = false; + private boolean hiscore = false; + private boolean wasPaused = false; + private Saves saves; + private ContentDatabase db; + private InputHandler[] inputs = {}; + private ObjectMap soundMap = new ObjectMap<>(); - private Saves saves; - - private float respawntime; - private InputHandler input; - - private InputProxy proxy; - private float controlx, controly; - private boolean controlling; private Throwable error; + private Input gdxInput; - public Control(){ - saves = new Saves(); + public Control(){ - Inputs.useControllers(!gwt); + saves = new Saves(); + db = new ContentDatabase(); - Gdx.input.setCatchBackKey(true); + Inputs.useControllers(!gwt); - if(mobile){ - input = new AndroidInput(); - }else{ - input = new DesktopInput(); - } + Gdx.input.setCatchBackKey(true); - proxy = new InputProxy(Gdx.input){ - @Override - public int getY() { - return controlling ? (int)controly : input.getY(); + Effects.setShakeFalloff(10000f); + + ContentLoader.initialize(Content::init); + Core.atlas = new Atlas("sprites.atlas"); + Core.atlas.setErrorRegion("error"); + ContentLoader.initialize(Content::load); + + db.load(); + + gdxInput = Gdx.input; + + Sounds.load("shoot.mp3", "place.mp3", "explosion.mp3", "enemyshoot.mp3", + "corexplode.mp3", "break.mp3", "spawn.mp3", "flame.mp3", "die.mp3", + "respawn.mp3", "purchase.mp3", "flame2.mp3", "bigshot.mp3", "laser.mp3", "lasershot.mp3", + "ping.mp3", "tesla.mp3", "waveend.mp3", "railgun.mp3", "blast.mp3", "bang2.mp3"); + + Sounds.setFalloff(9000f); + Sounds.setPlayer((sound, volume) -> { + long time = TimeUtils.millis(); + long value = soundMap.get(sound, 0L); + + if(TimeUtils.timeSinceMillis(value) >= minSoundPeriod){ + threads.run(() -> sound.play(volume)); + soundMap.put(sound, time); } - - @Override - public int getX() { - return controlling ? (int)controlx : input.getX(); - } - - @Override - public int getY(int pointer) { - return pointer == 0 ? getY() : super.getY(pointer); - } - - @Override - public int getX(int pointer) { - return pointer == 0 ? getX() : super.getX(pointer); - } - }; - - Inputs.addProcessor(input); - - Effects.setShakeFalloff(10000f); - - Core.atlas = new Atlas("sprites.atlas"); - - for(Item item : Item.getAllItems()){ - item.init(); - } - - Sounds.load("shoot.mp3", "place.mp3", "explosion.mp3", "enemyshoot.mp3", - "corexplode.mp3", "break.mp3", "spawn.mp3", "flame.mp3", "die.mp3", - "respawn.mp3", "purchase.mp3", "flame2.mp3", "bigshot.mp3", "laser.mp3", "lasershot.mp3", - "ping.mp3", "tesla.mp3", "waveend.mp3", "railgun.mp3", "blast.mp3", "bang2.mp3"); - - Sounds.setFalloff(9000f); + }); Musics.load("1.mp3", "2.mp3", "3.mp3", "4.mp3", "5.mp3", "6.mp3"); DefaultKeybinds.load(); - for(int i = 0; i < saveSlots; i ++){ - Settings.defaults("save-" + i + "-autosave", !gwt); - Settings.defaults("save-" + i + "-name", "untitled"); - Settings.defaults("save-" + i + "-data", "empty"); - } + Settings.defaultList( + "ip", "localhost", + "port", port + "", + "color-0", Color.rgba8888(playerColors[8]), + "color-1", Color.rgba8888(playerColors[11]), + "color-2", Color.rgba8888(playerColors[13]), + "color-3", Color.rgba8888(playerColors[9]), + "name", "player", + "lastBuild", 0 + ); - Settings.defaultList( - "ip", "localhost", - "port", port+"", - "name", mobile || gwt ? "player" : UCore.getProperty("user.name"), - "servers", "", - "color", Color.rgba8888(playerColors[8]), - "lastVersion", "3.2", - "lastBuild", 0 - ); + KeyBinds.load(); - KeyBinds.load(); + addPlayer(0); - for(Map map : world.maps().list()){ - Settings.defaults("hiscore" + map.name, 0); - } + saves.load(); - player = new Player(); - player.name = Settings.getString("name"); - player.isAndroid = mobile; - player.color.set(Settings.getInt("color")); - player.isLocal = true; + Events.on(StateChangeEvent.class, (from, to) -> { + if((from == State.playing && to == State.menu) || (from == State.menu && to != State.menu)){ + Timers.runTask(5f, Platform.instance::updateRPC); + } + }); - saves.load(); + Events.on(PlayEvent.class, () -> { + for(Player player : players){ + player.add(); + } - Events.on(StateChangeEvent.class, (from, to) -> { - if((from == State.playing && to == State.menu) || (from == State.menu && to != State.menu)){ - Timers.runTask(5f, Platform.instance::updateRPC); - } - }); - - Events.on(PlayEvent.class, () -> { - renderer.clearTiles(); - - player.set(world.getSpawnX(), world.getSpawnY()); - - Core.camera.position.set(player.x, player.y, 0); - - ui.hudfrag.updateItems(); - - state.set(State.playing); - }); - - Events.on(ResetEvent.class, () -> { - upgrades.reset(); - player.weaponLeft = player.weaponRight = Weapon.blaster; - - player.add(); - player.heal(); - - respawntime = -1; - hiscore = false; - - ui.hudfrag.updateItems(); - ui.hudfrag.updateWeapons(); - ui.hudfrag.fadeRespawn(false); - }); - - Events.on(WaveEvent.class, () -> { - Sounds.play("spawn"); - - int last = Settings.getInt("hiscore" + world.getMap().name, 0); - - if(state.wave > last && !state.mode.infiniteResources && !state.mode.disableWaveTimer){ - Settings.putInt("hiscore" + world.getMap().name, state.wave); - Settings.save(); - hiscore = true; - } - - Platform.instance.updateRPC(); - }); - - Events.on(GameOverEvent.class, () -> { - Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y); - Sounds.play("corexplode"); - for(int i = 0; i < 16; i ++){ - Timers.run(i*2, ()-> Effects.effect(Fx.explosion, world.getCore().worldx()+Mathf.range(40), world.getCore().worldy()+Mathf.range(40))); - } - Effects.effect(Fx.coreexplosion, world.getCore().worldx(), world.getCore().worldy()); - - ui.restart.show(); - - Timers.runTask(30f, () -> state.set(State.menu)); - }); - } - - //FIXME figure out what's causing this problem in the first place - public void triggerInputUpdate(){ - Gdx.input = proxy; - } - - public void setError(Throwable error){ - this.error = error; - } - - public UpgradeInventory upgrades() { - return upgrades; - } - - public Saves getSaves(){ - return saves; - } - - public boolean showCursor(){ - return controlling; - } - - public InputHandler input(){ - return input; - } - - public void playMap(Map map){ - ui.loadfrag.show(); - saves.resetSave(); - - Timers.runTask(10, () -> { - logic.reset(); - world.loadMap(map); - logic.play(); - }); - - Timers.runTask(18, () -> ui.loadfrag.hide()); - } - - public boolean isHighScore(){ - return hiscore; - } - - public float getRespawnTime(){ - return respawntime; - } - - public void setRespawnTime(float respawntime){ - this.respawntime = respawntime; - } - - public Tutorial tutorial(){ - return tutorial; - } - - private void checkOldUser(){ - boolean hasPlayed = false; - - for(Map map : world.maps().getAllMaps()){ - if(Settings.getInt("hiscore" + map.name) != 0){ - hasPlayed = true; - break; - } - } - - if(hasPlayed && Settings.getString("lastVersion").equals("3.2")){ - Timers.runTask(1f, () -> ui.showInfo("$text.changes")); - Settings.putString("lastVersion", "3.3"); - Settings.save(); - } - } - - @Override - public void dispose(){ - Platform.instance.onGameExit(); - Net.dispose(); - } - - @Override - public void pause(){ - wasPaused = state.is(State.paused); - if(state.is(State.playing)) state.set(State.paused); - } - - @Override - public void resume(){ - if(state.is(State.paused) && !wasPaused){ state.set(State.playing); - } - } + }); - @Override - public void init(){ - Timers.run(1f, Musics::shuffleAll); + Events.on(WorldLoadGraphicsEvent.class, () -> { + if(mobile){ + Core.camera.position.set(players[0].x, players[0].y, 0); + } + }); - Entities.initPhysics(); - Entities.collisions().setCollider(tilesize, world::solid); - - Platform.instance.updateRPC(); - - checkOldUser(); - } - - @Override - public void update(){ - - if(error != null){ - throw new RuntimeException(error); - } - - Gdx.input = proxy; - - if(Inputs.keyTap("console")){ - console = !console; - } - - if(KeyBinds.getSection("default").device.type == DeviceType.controller){ - if(Inputs.keyTap("select")){ - Inputs.getProcessor().touchDown(Gdx.input.getX(), Gdx.input.getY(), 0, Buttons.LEFT); + Events.on(ResetEvent.class, () -> { + for(Player player : players){ + player.reset(); } - if(Inputs.keyRelease("select")){ - Inputs.getProcessor().touchUp(Gdx.input.getX(), Gdx.input.getY(), 0, Buttons.LEFT); + hiscore = false; + + saves.resetSave(); + }); + + Events.on(WaveEvent.class, () -> { + + int last = Settings.getInt("hiscore" + world.getMap().name, 0); + + if(state.wave > last && !state.mode.infiniteResources && !state.mode.disableWaveTimer){ + Settings.putInt("hiscore" + world.getMap().name, state.wave); + Settings.save(); + hiscore = true; } - float xa = Inputs.getAxis("cursor_x"); - float ya = Inputs.getAxis("cursor_y"); + Platform.instance.updateRPC(); + }); - if(Math.abs(xa) > controllerMin || Math.abs(ya) > controllerMin) { - float scl = Settings.getInt("sensitivity")/100f * Unit.dp.scl(1f); - controlx += xa*baseControllerSpeed*scl; - controly -= ya*baseControllerSpeed*scl; - controlling = true; + Events.on(GameOverEvent.class, () -> { + Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y); - Gdx.input.setCursorCatched(true); + //TODO game over effect + ui.restart.show(); - Inputs.getProcessor().touchDragged(Gdx.input.getX(), Gdx.input.getY(), 0); - } + Timers.runTask(30f, () -> state.set(State.menu)); + }); - controlx = Mathf.clamp(controlx, 0, Gdx.graphics.getWidth()); - controly = Mathf.clamp(controly, 0, Gdx.graphics.getHeight()); + Events.on(WorldLoadEvent.class, () -> threads.runGraphics(() -> Events.fire(WorldLoadGraphicsEvent.class))); + } - if(Gdx.input.getDeltaX() > 1 || Gdx.input.getDeltaY() > 1) { - controlling = false; - Gdx.input.setCursorCatched(false); - } - }else{ - controlling = false; - Gdx.input.setCursorCatched(false); + public void addPlayer(int index){ + if(players.length != index + 1){ + Player[] old = players; + players = new Player[index + 1]; + System.arraycopy(old, 0, players, 0, old.length); } - if(!controlling){ - controlx = Gdx.input.getX(); - controly = Gdx.input.getY(); + if(inputs.length != index + 1){ + InputHandler[] oldi = inputs; + inputs = new InputHandler[index + 1]; + System.arraycopy(oldi, 0, inputs, 0, oldi.length); + } + + Player setTo = (index == 0 ? null : players[0]); + + Player player = new Player(); + player.name = Settings.getString("name"); + player.mech = mobile ? Mechs.starterMobile : Mechs.starterDesktop; + player.color.set(Settings.getInt("color-" + index)); + player.isLocal = true; + player.playerIndex = index; + player.isMobile = mobile; + players[index] = player; + + if(setTo != null){ + player.set(setTo.x, setTo.y); + } + + if(!state.is(State.menu)){ + player.add(); + } + + InputHandler input; + + if(mobile){ + input = new MobileInput(player); + }else{ + input = new DesktopInput(player); + } + + inputs[index] = input; + Inputs.addProcessor(input); + } + + public void removePlayer(){ + players[players.length - 1].remove(); + inputs[inputs.length - 1].remove(); + + Player[] old = players; + players = new Player[players.length - 1]; + System.arraycopy(old, 0, players, 0, players.length); + + InputHandler[] oldi = inputs; + inputs = new InputHandler[inputs.length - 1]; + System.arraycopy(oldi, 0, inputs, 0, inputs.length); + } + + public ContentDatabase database(){ + return db; + } + + public Input gdxInput(){ + return gdxInput; + } + + public void setError(Throwable error){ + this.error = error; + } + + public Saves getSaves(){ + return saves; + } + + public InputHandler input(int index){ + return inputs[index]; + } + + public void triggerUpdateInput(){ + //Gdx.input = proxy; + } + + public void playMap(Map map){ + ui.loadfrag.show(); + + Timers.run(5f, () -> + threads.run(() -> { + logic.reset(); + world.loadMap(map); + logic.play(); + + Gdx.app.postRunnable(ui.loadfrag::hide); + })); + } + + public boolean isHighScore(){ + return hiscore; + } + + private void checkUnlockableBlocks(){ + TileEntity entity = players[0].getClosestCore(); + + if(entity == null) return; + + entity.items.forEach((item, amount) -> control.database().unlockContent(item)); + + if(players[0].inventory.hasItem()){ + control.database().unlockContent(players[0].inventory.getItem().item); + } + + for(int i = 0; i < Recipe.all().size; i++){ + Recipe recipe = Recipe.all().get(i); + if(!recipe.debugOnly && entity.items.has(recipe.requirements, 1.4f)){ + if(control.database().unlockContent(recipe)){ + ui.hudfrag.showUnlock(recipe); + } + } + } + } + + @Override + public void dispose(){ + Platform.instance.onGameExit(); + ContentLoader.dispose(); + Net.dispose(); + ui.editor.dispose(); + inputs = new InputHandler[]{}; + players = new Player[]{}; + } + + @Override + public void pause(){ + wasPaused = state.is(State.paused); + if(state.is(State.playing)) state.set(State.paused); + } + + @Override + public void resume(){ + if(state.is(State.paused) && !wasPaused){ + state.set(State.playing); + } + } + + @Override + public void init(){ + EntityPhysics.initPhysics(); + + Platform.instance.updateRPC(); + + if(!Settings.has("4.0-warning")){ + Settings.putBool("4.0-warning", true); + + Timers.run(5f, () -> { + FloatingDialog dialog = new FloatingDialog("[orange]WARNING![]"); + dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); + dialog.content().add("The beta version you are about to play should be considered very unstable, and is [accent]not representative of the final 4.0 release.[]\n\n " + + "A large portion of content is still unimplemented. \nAll current art and UI is temporary, and will be re-drawn before release. " + + "\n\n[accent]Saves and maps may be corrupted without warning between updates.[] You have been warned!").wrap().width(500f); + dialog.show(); + + }); + } + + if(!Settings.has("4.0-no-sound")){ + Settings.putBool("4.0-no-sound", true); + + Timers.run(4f, () -> { + FloatingDialog dialog = new FloatingDialog("[orange]Attention![]"); + dialog.buttons().addButton("$text.ok", dialog::hide).size(100f, 60f); + dialog.content().add("You might have noticed that 4.0 does not have any sound.\nThis is [orange]intentional![] Sound will be added in a later update.\n\n[LIGHT_GRAY](now stop reporting this as a bug)").wrap().width(500f); + dialog.show(); + + }); + } + } + + /** Called from main logic thread.*/ + public void runUpdateLogic(){ + if(!state.is(State.menu)){ + renderer.minimap().updateUnitArray(); + } + } + + @Override + public void update(){ + + if(error != null){ + throw new RuntimeException(error); + } + + if(Inputs.keyTap("console")){ + console = !console; } saves.update(); - if(state.inventory.isUpdated() && (Timers.get("updateItems", 8) || state.is(State.paused))){ - ui.hudfrag.updateItems(); - state.inventory.setUpdated(false); - } + triggerUpdateInput(); - if(!state.is(State.menu)){ - input.update(); + for(InputHandler inputHandler : inputs){ + inputHandler.updateController(); + } - if(Inputs.keyTap("pause") && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ + if(!state.is(State.menu)){ + for(InputHandler input : inputs){ + input.update(); + } + + //check unlocks every 2 seconds + if(!state.mode.infiniteResources && Timers.get("timerCheckUnlock", 120)){ + checkUnlockableBlocks(); + + //save if the db changed, but don't save unlocks + if(db.isDirty() && !debug){ + db.save(); + } + } + + if(Inputs.keyTap("pause") && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ state.set(state.is(State.playing) ? State.paused : State.playing); - } + } - if(Inputs.keyTap("menu")){ - if(state.is(State.paused)){ - ui.paused.hide(); + if(Inputs.keyTap("menu")){ + if(state.is(State.paused)){ + ui.paused.hide(); state.set(State.playing); - }else if (!ui.restart.isShown()){ - if(ui.chatfrag.chatOpen()) { - ui.chatfrag.hide(); - }else{ - ui.paused.show(); + }else if(!ui.restart.isShown()){ + if(ui.chatfrag.chatOpen()){ + ui.chatfrag.hide(); + }else{ + ui.paused.show(); state.set(State.paused); - } - } - } + } + } + } - if(!state.is(State.paused) || Net.active()){ - Entities.update(effectGroup); - - if(respawntime > 0){ - - respawntime -= Timers.delta(); - - if(respawntime <= 0){ - player.set(world.getSpawnX(), world.getSpawnY()); - player.heal(); - player.add(); - Effects.sound("respawn"); - ui.hudfrag.fadeRespawn(false); - } - } - - if(tutorial.active()){ - tutorial.update(); - } - } - }else{ - if(!state.is(State.paused) || Net.active()){ - Timers.update(); - } - } - } + if(!state.is(State.paused) || Net.active()){ + Entities.update(effectGroup); + Entities.update(groundEffectGroup); + } + }else{ + if(!state.is(State.paused) || Net.active()){ + Timers.update(); + } + } + } } diff --git a/core/src/io/anuke/mindustry/core/GameState.java b/core/src/io/anuke/mindustry/core/GameState.java index 20acc304e3..92eee9a4d3 100644 --- a/core/src/io/anuke/mindustry/core/GameState.java +++ b/core/src/io/anuke/mindustry/core/GameState.java @@ -1,40 +1,37 @@ package io.anuke.mindustry.core; +import io.anuke.mindustry.ai.WaveSpawner; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.game.Inventory; +import io.anuke.mindustry.game.TeamInfo; import io.anuke.ucore.core.Events; public class GameState{ - private State state = State.menu; + public int wave = 1; + public float wavetime; + public boolean gameOver = false; + public GameMode mode = GameMode.waves; + public Difficulty difficulty = Difficulty.normal; + public boolean friendlyFire; + public WaveSpawner spawner = new WaveSpawner(); + public TeamInfo teams = new TeamInfo(); + private State state = State.menu; - public final Inventory inventory = new Inventory(); + public void set(State astate){ + Events.fire(StateChangeEvent.class, state, astate); + state = astate; + } - public int wave = 1; - public int lastUpdated = -1; - public float wavetime; - public float extrawavetime; - public int enemies = 0; - public boolean gameOver = false; - public GameMode mode = GameMode.waves; - public Difficulty difficulty = Difficulty.normal; - public boolean friendlyFire; - - public void set(State astate){ - Events.fire(StateChangeEvent.class, state, astate); - state = astate; - } - - public boolean is(State astate){ - return state == astate; - } + public boolean is(State astate){ + return state == astate; + } - public State getState(){ - return state; - } - - public enum State{ - paused, playing, menu - } + public State getState(){ + return state; + } + + public enum State{ + paused, playing, menu + } } diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index cb4f484c73..2e07b0a2e6 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -1,160 +1,173 @@ package io.anuke.mindustry.core; -import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.Items; import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.game.EnemySpawn; +import io.anuke.mindustry.entities.TileEntity; import io.anuke.mindustry.game.EventType.GameOverEvent; import io.anuke.mindustry.game.EventType.PlayEvent; import io.anuke.mindustry.game.EventType.ResetEvent; import io.anuke.mindustry.game.EventType.WaveEvent; -import io.anuke.mindustry.game.SpawnPoint; -import io.anuke.mindustry.game.WaveCreator; -import io.anuke.mindustry.graphics.Fx; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo; +import io.anuke.mindustry.game.TeamInfo.TeamData; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemType; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.ProductionBlocks; -import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.EntityPhysics; import io.anuke.ucore.modules.Module; -import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; -/**Logic module. +/** + * Logic module. * Handles all logic for entities and waves. * Handles game state events. * Does not store any game state itself. - * + *

* This class should not call any outside methods to change state of modules, but instead fire events. */ -public class Logic extends Module { - private final Array spawns = WaveCreator.getSpawns(); +public class Logic extends Module{ + public boolean doUpdate = true; + + public Logic(){ + state = new GameState(); + } @Override public void init(){ - Entities.initPhysics(); - Entities.collisions().setCollider(tilesize, world::solid); + EntityPhysics.initPhysics(); + EntityPhysics.collisions().setCollider(tilesize, world::solid); } public void play(){ + state.set(State.playing); state.wavetime = wavespace * state.difficulty.timeScaling * 2; - if(state.mode.infiniteResources){ - state.inventory.fill(); + //fill inventory with items for debugging + + for(TeamData team : state.teams.getTeams()){ + for(Tile tile : team.cores){ + if(debug){ + for(Item item : Item.all()){ + if(item.type == ItemType.material){ + tile.entity.items.add(item, 1000); + } + } + }else{ + tile.entity.items.add(Items.tungsten, 50); + tile.entity.items.add(Items.lead, 20); + } + } } + Events.fire(PlayEvent.class); } public void reset(){ state.wave = 1; - state.extrawavetime = maxwavespace * state.difficulty.maxTimeScaling; state.wavetime = wavespace * state.difficulty.timeScaling; - state.enemies = 0; - state.lastUpdated = -1; state.gameOver = false; - state.inventory.clearItems(); + state.teams = new TeamInfo(); + state.teams.add(Team.blue, true); + state.teams.add(Team.red, false); Timers.clear(); Entities.clear(); + TileEntity.sleepingEntities = 0; Events.fire(ResetEvent.class); } public void runWave(){ - - if(state.lastUpdated < state.wave + 1){ - world.pathfinder().resetPaths(); - state.lastUpdated = state.wave + 1; - } - - for(EnemySpawn spawn : spawns){ - Array spawns = world.getSpawns(); - - for(int lane = 0; lane < spawns.size; lane ++){ - int fl = lane; - Tile tile = spawns.get(lane).start; - int spawnamount = spawn.evaluate(state.wave, lane); - - for(int i = 0; i < spawnamount; i ++){ - float range = 12f; - - Timers.runTask(i*5f, () -> { - - Enemy enemy = new Enemy(spawn.type); - enemy.set(tile.worldx() + Mathf.range(range), tile.worldy() + Mathf.range(range)); - enemy.lane = fl; - enemy.tier = spawn.tier(state.wave, fl); - enemy.add(); - - Effects.effect(Fx.spawn, enemy); - - state.enemies ++; - }); - } - } - } - - state.wave ++; + state.spawner.spawnEnemies(); + state.wave++; state.wavetime = wavespace * state.difficulty.timeScaling; - state.extrawavetime = maxwavespace * state.difficulty.maxTimeScaling; Events.fire(WaveEvent.class); } + private void checkGameOver(){ + boolean gameOver = true; + + for(TeamData data : state.teams.getTeams(true)){ + if(data.cores.size > 0){ + gameOver = false; + break; + } + } + + if(gameOver && !state.gameOver){ + state.gameOver = true; + Events.fire(GameOverEvent.class); + } + } + @Override public void update(){ + if(threads.isEnabled() && !threads.isOnThread()) return; + + if(Vars.control != null){ + control.runUpdateLogic(); + } if(!state.is(State.menu)){ - if(control != null) control.triggerInputUpdate(); + if(control != null) control.triggerUpdateInput(); if(!state.is(State.paused) || Net.active()){ Timers.update(); } - if(!Net.client()) - world.pathfinder().update(); - - if(world.getCore() != null && world.getCore().block() != ProductionBlocks.core && !state.gameOver){ - state.gameOver = true; - if(Net.server()) NetEvents.handleGameOver(); - Events.fire(GameOverEvent.class); + if(!world.isInvalidMap()){ + checkGameOver(); } if(!state.is(State.paused) || Net.active()){ if(!state.mode.disableWaveTimer){ - - if(state.enemies <= 0){ - if(!world.getMap().name.equals("tutorial")) state.wavetime -= Timers.delta(); - - if(state.lastUpdated < state.wave + 1 && state.wavetime < aheadPathfinding){ //start updating beforehand - world.pathfinder().resetPaths(); - state.lastUpdated = state.wave + 1; - } - }else if(!world.getMap().name.equals("tutorial")){ - state.extrawavetime -= Timers.delta(); - } + state.wavetime -= Timers.delta(); } - if(!Net.client() && (state.wavetime <= 0 || state.extrawavetime <= 0)){ + if(!Net.client() && state.wavetime <= 0){ runWave(); } - Entities.update(Entities.defaultGroup()); + if(!Entities.defaultGroup().isEmpty()) + throw new RuntimeException("Do not add anything to the default group!"); + Entities.update(bulletGroup); - Entities.update(enemyGroup); + for(EntityGroup group : unitGroups){ + Entities.update(group); + } + Entities.update(puddleGroup); Entities.update(tileGroup); + Entities.update(fireGroup); Entities.update(shieldGroup); Entities.update(playerGroup); + Entities.update(itemGroup); - Entities.collideGroups(bulletGroup, enemyGroup); - Entities.collideGroups(bulletGroup, playerGroup); + //effect group only contains item drops in the headless version, update it! + if(headless){ + Entities.update(effectGroup); + } + + for(EntityGroup group : unitGroups){ + if(!group.isEmpty()){ + EntityPhysics.collideGroups(bulletGroup, group); + } + } + + EntityPhysics.collideGroups(bulletGroup, playerGroup); + EntityPhysics.collideGroups(itemGroup, playerGroup); + + world.pathfinder().update(); } } } diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index 85fc3b3ebc..4ba984c6d3 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -1,78 +1,138 @@ package io.anuke.mindustry.core; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.Base64Coder; import com.badlogic.gdx.utils.IntSet; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.Vars; +import io.anuke.annotations.Annotations.PacketPriority; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.Variant; import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.entities.Bullet; -import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.SyncEntity; -import io.anuke.mindustry.entities.enemies.Enemy; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.entities.traits.TypeTrait; +import io.anuke.mindustry.gen.Call; +import io.anuke.mindustry.gen.RemoteReadClient; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.NetworkIO; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.UpgradeRecipes; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Map; -import io.anuke.mindustry.world.Placement; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.ProductionBlocks; -import io.anuke.ucore.core.Effects; +import io.anuke.mindustry.net.TraceInfo; +import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.BaseBulletType; import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.io.ReusableByteArrayInputStream; +import io.anuke.ucore.io.delta.DEZDecoder; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Pooling; import io.anuke.ucore.util.Timer; -import java.nio.ByteBuffer; +import java.io.DataInputStream; +import java.util.Arrays; +import java.util.Random; import static io.anuke.mindustry.Vars.*; -public class NetClient extends Module { - private final static float dataTimeout = 60*18; //18 seconds timeout +public class NetClient extends Module{ + private final static float dataTimeout = 60 * 18; private final static float playerSyncTime = 2; - private final static int maxRequests = 50; private Timer timer = new Timer(5); + /** + * Whether the client is currently connecting. + */ private boolean connecting = false; - private boolean kicked = false; - private IntSet recieved = new IntSet(); - private IntMap recent = new IntMap<>(); - private int requests = 0; - private float timeoutTime = 0f; //data timeout counter + /** + * If true, no message will be shown on disconnect. + */ + private boolean quiet = false; + /** + * Counter for data timeout. + */ + private float timeoutTime = 0f; + /** + * Last sent client snapshot ID. + */ + private int lastSent; + + /** + * Last snapshot ID recieved. + */ + private int lastSnapshotBaseID = -1; + /** + * Last snapshot recieved. + */ + private byte[] lastSnapshotBase; + /** + * Current snapshot that is being built from chinks. + */ + private byte[] currentSnapshot; + /** + * Array of recieved chunk statuses. + */ + private boolean[] recievedChunks; + /** + * Counter of how many chunks have been recieved. + */ + private int recievedChunkCounter; + /** + * ID of snapshot that is currently being constructed. + */ + private int currentSnapshotID = -1; + + /** + * Decoder for uncompressing snapshots. + */ + private DEZDecoder decoder = new DEZDecoder(); + /** + * List of entities that were removed, and need not be added while syncing. + */ + private IntSet removed = new IntSet(); + /** + * Byte stream for reading in snapshots. + */ + private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream(); + private DataInputStream dataStream = new DataInputStream(byteStream); public NetClient(){ Net.handleClient(Connect.class, packet -> { + Player player = players[0]; + player.isAdmin = false; Net.setClientLoaded(false); - recieved.clear(); - recent.clear(); + removed.clear(); timeoutTime = 0f; connecting = true; - kicked = false; + quiet = false; + lastSent = 0; + lastSnapshotBase = null; + currentSnapshot = null; + currentSnapshotID = -1; + lastSnapshotBaseID = -1; ui.chatfrag.clearMessages(); ui.loadfrag.hide(); ui.loadfrag.show("$text.connecting.data"); + ui.loadfrag.setButton(() -> { + ui.loadfrag.hide(); + connecting = false; + quiet = true; + Net.disconnect(); + }); + Entities.clear(); ConnectPacket c = new ConnectPacket(); c.name = player.name; - c.android = mobile; + c.mobile = mobile; c.color = Color.rgba8888(player.color); + c.usid = getUsid(packet.addressTCP); c.uuid = Platform.instance.getUUID(); if(c.uuid == null){ @@ -86,7 +146,7 @@ public class NetClient extends Module { }); Net.handleClient(Disconnect.class, packet -> { - if (kicked) return; + if(quiet) return; Timers.runTask(3f, ui.loadfrag::hide); @@ -98,226 +158,177 @@ public class NetClient extends Module { Platform.instance.updateRPC(); }); - Net.handleClient(WorldData.class, data -> { + Net.handleClient(WorldStream.class, data -> { Log.info("Recieved world data: {0} bytes.", data.stream.available()); NetworkIO.loadWorld(data.stream); - player.set(world.getSpawnX(), world.getSpawnY()); finishConnecting(); }); - Net.handleClient(CustomMapPacket.class, packet -> { - Log.info("Recieved custom map: {0} bytes.", packet.stream.available()); - - //custom map is always sent before world data - Map map = NetworkIO.loadMap(packet.stream); - - world.maps().setNetworkMap(map); - - MapAckPacket ack = new MapAckPacket(); - Net.send(ack, SendMode.tcp); + Net.handleClient(InvokePacket.class, packet -> { + packet.writeBuffer.position(0); + RemoteReadClient.readPacket(packet.writeBuffer, packet.type); }); + } - Net.handleClient(SyncPacket.class, packet -> { - if (connecting) return; - int players = 0; - int enemies = 0; + @Remote(variants = Variant.one, priority = PacketPriority.high) + public static void onKick(KickReason reason){ + netClient.disconnectQuietly(); + state.set(State.menu); + if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name()); + ui.loadfrag.hide(); + } - ByteBuffer data = ByteBuffer.wrap(packet.data); - long time = data.getLong(); + @Remote(variants = Variant.one) + public static void onPositionSet(float x, float y){ + players[0].x = x; + players[0].y = y; + } - byte groupid = data.get(); + @Remote(variants = Variant.one) + public static void onTraceInfo(TraceInfo info){ + Player player = playerGroup.getByID(info.playerid); + ui.traces.show(player, info); + } - EntityGroup group = Entities.getGroup(groupid); + @Remote + public static void onPlayerDisconnect(int playerid){ + playerGroup.removeByID(playerid); + } - while (data.position() < data.capacity()) { - int id = data.getInt(); + @Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true) + public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){ + if(NetServer.showSnapshotSize) + Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID); - SyncEntity entity = (SyncEntity) group.getByID(id); + //skip snapshot IDs that have already been recieved OR snapshots that are too far in front + if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){ + if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT"); + return; + } - if(entity instanceof Player) players ++; - if(entity instanceof Enemy) enemies ++; + try{ + byte[] snapshot; - if (entity == null || id == player.id) { - if (id != player.id && requests < maxRequests) { - EntityRequestPacket req = new EntityRequestPacket(); - req.id = id; - req.group = groupid; - Net.send(req, SendMode.udp); - requests ++; + //total length exceeds that needed to hold one snapshot, therefore, it is split into chunks + if(totalLength > NetServer.maxSnapshotSize){ + //total amount of chunks to recieve + int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize); + + //reset status when a new snapshot sending begins + if(netClient.currentSnapshotID != snapshotID){ + netClient.currentSnapshotID = snapshotID; + netClient.currentSnapshot = new byte[totalLength]; + netClient.recievedChunkCounter = 0; + netClient.recievedChunks = new boolean[totalChunks]; + } + + //if this chunk hasn't been recieved yet... + if(!netClient.recievedChunks[chunkID]){ + netClient.recievedChunks[chunkID] = true; + netClient.recievedChunkCounter++; //update recieved status + //copy the recieved bytes into the holding array + System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize, + Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize)); + } + + //when all chunks have been recieved, begin + if(netClient.recievedChunkCounter >= totalChunks){ + snapshot = netClient.currentSnapshot; + }else{ + return; + } + }else{ + snapshot = chunk; + } + + if(NetServer.showSnapshotSize) + Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length); + + byte[] result; + int length; + if(base == -1){ //fresh snapshot + result = snapshot; + length = snapshot.length; + netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length); + }else{ //otherwise, last snapshot must not be null, decode it + if(NetServer.showSnapshotSize) + Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length); + netClient.decoder.init(netClient.lastSnapshotBase, snapshot); + result = netClient.decoder.decode(); + length = netClient.decoder.getDecodedLength(); + //set last snapshot to a copy to prevent issues + netClient.lastSnapshotBase = Arrays.copyOf(result, length); + } + + netClient.lastSnapshotBaseID = snapshotID; + + //set stream bytes to begin snapshot reaeding + netClient.byteStream.setBytes(result, 0, length); + + //get data input for reading from the stream + DataInputStream input = netClient.dataStream; + + //read wave info + state.wavetime = input.readFloat(); + state.wave = input.readInt(); + + byte cores = input.readByte(); + for(int i = 0; i < cores; i++){ + int pos = input.readInt(); + world.tile(pos).entity.items.read(input); + } + + long timestamp = input.readLong(); + + byte totalGroups = input.readByte(); + //for each group... + for(int i = 0; i < totalGroups; i++){ + //read group info + byte groupID = input.readByte(); + short amount = input.readShort(); + + EntityGroup group = Entities.getGroup(groupID); + + //go through each entity + for(int j = 0; j < amount; j++){ + int position = netClient.byteStream.position(); //save position to check read/write correctness + int id = input.readInt(); + byte typeID = input.readByte(); + + SyncTrait entity = (SyncTrait) group.getByID(id); + boolean add = false; + + //entity must not be added yet, so create it + if(entity == null){ + entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier + entity.resetID(id); + if(!netClient.isEntityUsed(entity.getID())){ + add = true; + } + } + + //read the entity + entity.read(input, timestamp); + + byte readLength = input.readByte(); + if(netClient.byteStream.position() - position - 1 != readLength){ + throw new RuntimeException("Error reading entity of type '" + group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1) + "]"); + } + + if(add){ + entity.add(); + netClient.addRemovedEntity(entity.getID()); } - data.position(data.position() + SyncEntity.getWriteSize((Class) group.getType())); - } else { - entity.read(data, time); } } - if(debugNet){ - clientDebug.setSyncDebug(players, enemies); - } - }); + //confirm that snapshot has been recieved + netClient.lastSnapshotBaseID = snapshotID; - Net.handleClient(StateSyncPacket.class, packet -> { - - System.arraycopy(packet.items, 0, state.inventory.getItems(), 0, packet.items.length); - - state.enemies = packet.enemies; - state.wavetime = packet.countdown; - state.wave = packet.wave; - - ui.hudfrag.updateItems(); - }); - - Net.handleClient(BlockLogRequestPacket.class, packet -> { - currentEditLogs = packet.editlogs; - }); - - Net.handleClient(PlacePacket.class, (packet) -> { - Placement.placeBlock(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, Timers.get("placeblocksound", 10)); - - if(packet.playerid == player.id){ - Tile tile = world.tile(packet.x, packet.y); - if(tile != null) Block.getByID(packet.block).placed(tile); - } - }); - - Net.handleClient(BreakPacket.class, (packet) -> - Placement.breakBlock(packet.x, packet.y, true, Timers.get("breakblocksound", 10))); - - Net.handleClient(EntitySpawnPacket.class, packet -> { - EntityGroup group = packet.group; - - //duplicates. - if (group.getByID(packet.entity.id) != null || - recieved.contains(packet.entity.id)) return; - - recieved.add(packet.entity.id); - recent.put(packet.entity.id, packet.entity); - - packet.entity.add(); - - Log.info("Recieved entity {0}", packet.entity.id); - }); - - Net.handleClient(EnemyDeathPacket.class, packet -> { - Enemy enemy = enemyGroup.getByID(packet.id); - if (enemy != null){ - enemy.type.onDeath(enemy, true); - }else if(recent.get(packet.id) != null){ - recent.get(packet.id).remove(); - }else{ - Log.err("Got remove for null entity! {0}", packet.id); - } - recieved.add(packet.id); - }); - - Net.handleClient(BulletPacket.class, packet -> { - //TODO shoot effects for enemies, clientside as well as serverside - BulletType type = (BulletType) BaseBulletType.getByID(packet.type); - Entity owner = enemyGroup.getByID(packet.owner); - new Bullet(type, owner, packet.x, packet.y, packet.angle).add(); - }); - - Net.handleClient(BlockDestroyPacket.class, packet -> { - Tile tile = world.tile(packet.position % world.width(), packet.position / world.width()); - if (tile != null && tile.entity != null) { - tile.entity.onDeath(true); - } - }); - - Net.handleClient(BlockUpdatePacket.class, packet -> { - Tile tile = world.tile(packet.position % world.width(), packet.position / world.width()); - if (tile != null && tile.entity != null) { - tile.entity.health = packet.health; - } - }); - - Net.handleClient(DisconnectPacket.class, packet -> { - Player player = playerGroup.getByID(packet.playerid); - - if (player != null) { - player.remove(); - } - - Platform.instance.updateRPC(); - }); - - Net.handleClient(KickPacket.class, packet -> { - kicked = true; - Net.disconnect(); - state.set(State.menu); - if(!packet.reason.quiet) ui.showError("$text.server.kicked." + packet.reason.name()); - ui.loadfrag.hide(); - }); - - Net.handleClient(GameOverPacket.class, packet -> { - if(world.getCore().block() != ProductionBlocks.core && - world.getCore().entity != null){ - world.getCore().entity.onDeath(true); - } - kicked = true; - ui.restart.show(); - }); - - Net.handleClient(FriendlyFireChangePacket.class, packet -> state.friendlyFire = packet.enabled); - - Net.handleClient(ItemTransferPacket.class, packet -> { - Runnable r = () -> { - Tile tile = world.tile(packet.position); - if (tile == null || tile.entity == null) return; - Tile next = tile.getNearby(packet.rotation); - tile.entity.items[packet.itemid] --; - next.block().handleItem(Item.getByID(packet.itemid), next, tile); - }; - - threads.run(r); - }); - - Net.handleClient(ItemSetPacket.class, packet -> { - Runnable r = () -> { - Tile tile = world.tile(packet.position); - if (tile == null || tile.entity == null) return; - tile.entity.items[packet.itemid] = packet.amount; - }; - - threads.run(r); - }); - - Net.handleClient(ItemOffloadPacket.class, packet -> { - Runnable r = () -> { - Tile tile = world.tile(packet.position); - if (tile == null || tile.entity == null) return; - Tile next = tile.getNearby(tile.getRotation()); - next.block().handleItem(Item.getByID(packet.itemid), next, tile); - }; - - threads.run(r); - }); - - Net.handleClient(NetErrorPacket.class, packet -> { - ui.showError(packet.message); - disconnectQuietly(); - }); - - Net.handleClient(PlayerAdminPacket.class, packet -> { - Player player = playerGroup.getByID(packet.id); - player.isAdmin = packet.admin; - ui.listfrag.rebuild(); - }); - - Net.handleClient(TracePacket.class, packet -> { - Player player = playerGroup.getByID(packet.info.playerid); - ui.traces.show(player, packet.info); - }); - - Net.handleClient(UpgradePacket.class, packet -> { - Weapon weapon = (Weapon) Upgrade.getByID(packet.id); - - state.inventory.removeItems(UpgradeRecipes.get(weapon)); - control.upgrades().addWeapon(weapon); - ui.hudfrag.updateWeapons(); - Effects.sound("purchase"); - }); + }catch(Exception e){ + throw new RuntimeException(e); + } } @Override @@ -333,7 +344,7 @@ public class NetClient extends Module { if(timeoutTime > dataTimeout){ Log.err("Failed to load data!"); ui.loadfrag.hide(); - kicked = true; + quiet = true; ui.showError("$text.disconnect.data"); Net.disconnect(); timeoutTime = 0f; @@ -351,7 +362,7 @@ public class NetClient extends Module { ui.loadfrag.hide(); ui.join.hide(); Net.setClientLoaded(true); - Timers.runTask(1f, () -> Net.send(new ConnectConfirmPacket(), SendMode.tcp)); + Gdx.app.postRunnable(Call::connectConfirm); Timers.runTask(40f, Platform.instance::updateRPC); } @@ -360,26 +371,25 @@ public class NetClient extends Module { } public void disconnectQuietly(){ - kicked = true; + quiet = true; Net.disconnect(); } - public void clearRecieved(){ - recieved.clear(); + public synchronized void addRemovedEntity(int id){ + removed.add(id); + } + + public synchronized boolean isEntityUsed(int id){ + return removed.contains(id); } void sync(){ - requests = 0; if(timer.get(0, playerSyncTime)){ - byte[] bytes = new byte[player.getWriteSize() + 8]; - ByteBuffer buffer = ByteBuffer.wrap(bytes); - buffer.putLong(TimeUtils.millis()); - player.write(buffer); - - PositionPacket packet = new PositionPacket(); - packet.data = bytes; + ClientSnapshotPacket packet = Pooling.obtain(ClientSnapshotPacket.class); + packet.lastSnapshot = lastSnapshotBaseID; + packet.snapid = lastSent++; Net.send(packet, SendMode.udp); } @@ -387,4 +397,17 @@ public class NetClient extends Module { Net.updatePing(); } } -} + + String getUsid(String ip){ + if(Settings.getString("usid-" + ip, null) != null){ + return Settings.getString("usid-" + ip, null); + }else{ + byte[] bytes = new byte[8]; + new Random().nextBytes(bytes); + String result = new String(Base64Coder.encode(bytes)); + Settings.putString("usid-" + ip, result); + Settings.save(); + return result; + } + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/core/NetCommon.java b/core/src/io/anuke/mindustry/core/NetCommon.java deleted file mode 100644 index 9b8a9435da..0000000000 --- a/core/src/io/anuke/mindustry/core/NetCommon.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.anuke.mindustry.core; - -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.Net.SendMode; -import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.modules.Module; - -import static io.anuke.mindustry.Vars.*; - -public class NetCommon extends Module { - - public NetCommon(){ - - Net.handle(ShootPacket.class, (packet) -> { - Player player = playerGroup.getByID(packet.playerid); - - Weapon weapon = (Weapon) Upgrade.getByID(packet.weaponid); - weapon.shoot(player, packet.x, packet.y, packet.rotation); - }); - - Net.handle(ChatPacket.class, (packet) -> { - ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name)); - }); - - Net.handle(WeaponSwitchPacket.class, (packet) -> { - Player player = playerGroup.getByID(packet.playerid); - - if (player == null) return; - - player.weaponLeft = (Weapon) Upgrade.getByID(packet.left); - player.weaponRight = (Weapon) Upgrade.getByID(packet.right); - }); - - Net.handle(BlockTapPacket.class, (packet) -> { - Tile tile = world.tile(packet.position); - tile.block().tapped(tile); - }); - - Net.handle(BlockConfigPacket.class, (packet) -> { - Tile tile = world.tile(packet.position); - if (tile != null) tile.block().configure(tile, packet.data); - }); - - Net.handle(PlayerDeathPacket.class, (packet) -> { - Player player = playerGroup.getByID(packet.id); - if(player == null) return; - - player.doRespawn(); - }); - } - - public void sendMessage(String message){ - ChatPacket packet = new ChatPacket(); - packet.name = null; - packet.text = message; - Net.send(packet, SendMode.tcp); - if(!headless) ui.chatfrag.addMessage(message, null); - } - - public String colorizeName(int id, String name){ - Player player = playerGroup.getByID(id); - if(name == null || player == null) return null; - return "[#" + player.color.toString().toUpperCase() + "]" + name; - } -} diff --git a/core/src/io/anuke/mindustry/core/NetServer.java b/core/src/io/anuke/mindustry/core/NetServer.java index 0559a441c6..fa2c5ac999 100644 --- a/core/src/io/anuke/mindustry/core/NetServer.java +++ b/core/src/io/anuke/mindustry/core/NetServer.java @@ -1,69 +1,102 @@ package io.anuke.mindustry.core; -import com.badlogic.gdx.utils.*; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Colors; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.Mechs; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.SyncEntity; -import io.anuke.mindustry.game.EventType.GameOverEvent; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.gen.Call; +import io.anuke.mindustry.gen.RemoteReadServer; import io.anuke.mindustry.io.Version; import io.anuke.mindustry.net.*; import io.anuke.mindustry.net.Administration.PlayerInfo; -import io.anuke.mindustry.net.Net.SendMode; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.resource.*; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Placement; import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.trait.Entity; +import io.anuke.ucore.io.CountableByteArrayOutputStream; +import io.anuke.ucore.io.delta.ByteDeltaEncoder; +import io.anuke.ucore.io.delta.ByteMatcherHash; +import io.anuke.ucore.io.delta.DEZEncoder; import io.anuke.ucore.modules.Module; import io.anuke.ucore.util.Log; -import io.anuke.ucore.util.Timer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; + import static io.anuke.mindustry.Vars.*; public class NetServer extends Module{ - private final static float serverSyncTime = 4, itemSyncTime = 10, kickDuration = 30 * 1000; + public final static int maxSnapshotSize = 2047; + public final static boolean showSnapshotSize = false; - private final static int timerEntitySync = 0; - private final static int timerStateSync = 1; + private final static byte[] reusableSnapArray = new byte[maxSnapshotSize]; + private final static float serverSyncTime = 4, kickDuration = 30 * 1000; + private final static Vector2 vector = new Vector2(); + /** + * If a play goes away of their server-side coordinates by this distance, they get teleported back. + */ + private final static float correctDist = 16f; public final Administration admins = new Administration(); - /**Maps connection IDs to players.*/ + /** + * Maps connection IDs to players. + */ private IntMap connections = new IntMap<>(); - private ObjectMap weapons = new ObjectMap<>(); private boolean closing = false; - private Timer timer = new Timer(5); + + /** + * Stream for writing player sync data to. + */ + private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream(); + /** + * Data stream for writing player sync data to. + */ + private DataOutputStream dataStream = new DataOutputStream(syncStream); + /** + * Encoder for computing snapshot deltas. + */ + private DEZEncoder encoder = new DEZEncoder(); public NetServer(){ - Events.on(GameOverEvent.class, () -> { - weapons.clear(); - admins.getEditLogs().clear(); - }); - Net.handleServer(Connect.class, (id, connect) -> { if(admins.isIPBanned(connect.addressTCP)){ kick(id, KickReason.banned); } }); + Net.handleServer(Disconnect.class, (id, packet) -> { + Player player = connections.get(id); + if(player != null){ + onDisconnect(player); + } + connections.remove(id); + }); + Net.handleServer(ConnectPacket.class, (id, packet) -> { - String uuid = new String(Base64Coder.encode(packet.uuid)); + String uuid = packet.uuid; if(Net.getConnection(id) == null || admins.isIPBanned(Net.getConnection(id).address)) return; - TraceInfo trace = admins.getTrace(Net.getConnection(id).address); + TraceInfo trace = admins.getTraceByID(uuid); PlayerInfo info = admins.getInfo(uuid); trace.uuid = uuid; - trace.android = packet.android; + trace.android = packet.mobile; if(admins.isIDBanned(uuid)){ kick(id, KickReason.banned); @@ -75,6 +108,34 @@ public class NetServer extends Module{ return; } + if(packet.version == -1 && Version.build != -1 && !admins.allowsCustomClients()){ + kick(id, KickReason.customClient); + return; + } + + boolean preventDuplicates = headless; + + if(preventDuplicates){ + for(Player player : playerGroup.all()){ + if(player.name.equalsIgnoreCase(packet.name)){ + kick(id, KickReason.nameInUse); + return; + } + + if(player.uuid.equals(packet.uuid)){ + kick(id, KickReason.idInUse); + return; + } + } + } + + packet.name = fixName(packet.name); + + if(packet.name.trim().length() <= 0){ + kick(id, KickReason.nameEmpty); + return; + } + Log.info("Recieved connect packet for player '{0}' / UUID {1} / IP {2}", packet.name, uuid, trace.ip); String ip = Net.getConnection(id).address; @@ -91,282 +152,180 @@ public class NetServer extends Module{ } Player player = new Player(); - player.isAdmin = admins.isAdmin(uuid, ip); - player.clientid = id; + player.isAdmin = admins.isAdmin(uuid, packet.usid); + player.con = Net.getConnection(id); + player.usid = packet.usid; player.name = packet.name; - player.isAndroid = packet.android; - player.set(world.getSpawnX(), world.getSpawnY()); - player.setNet(player.x, player.y); + player.uuid = uuid; + player.isMobile = packet.mobile; + player.mech = packet.mobile ? Mechs.starterMobile : Mechs.starterDesktop; + player.dead = true; player.setNet(player.x, player.y); player.color.set(packet.color); + player.color.a = 1f; connections.put(id, player); trace.playerid = player.id; - if(world.getMap().custom){ - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - NetworkIO.writeMap(world.getMap(), stream); - CustomMapPacket data = new CustomMapPacket(); - data.stream = new ByteArrayInputStream(stream.toByteArray()); - Net.sendStream(id, data); - - Log.info("Sending custom map: Packed {0} uncompressed bytes of MAP data.", stream.size()); - }else{ - //hack-- simulate the map ack packet recieved to send the world data to the client. - Net.handleServerReceived(id, new MapAckPacket()); - } - - Platform.instance.updateRPC(); - }); - - Net.handleServer(MapAckPacket.class, (id, packet) -> { - Player player = connections.get(id); - + //TODO try DeflaterOutputStream ByteArrayOutputStream stream = new ByteArrayOutputStream(); - NetworkIO.writeWorld(player, weapons.get(admins.getTrace(Net.getConnection(id).address).uuid, new ByteArray()), stream); - WorldData data = new WorldData(); + NetworkIO.writeWorld(player, stream); + WorldStream data = new WorldStream(); data.stream = new ByteArrayInputStream(stream.toByteArray()); Net.sendStream(id, data); Log.info("Packed {0} uncompressed bytes of WORLD data.", stream.size()); - }); - - Net.handleServer(ConnectConfirmPacket.class, (id, packet) -> { - Player player = connections.get(id); - - if (player == null) return; - - player.add(); - Log.info("&y{0} has connected.", player.name); - netCommon.sendMessage("[accent]" + player.name + " has connected."); - }); - - Net.handleServer(Disconnect.class, (id, packet) -> { - Player player = connections.get(packet.id); - - if (player == null) { - Log.err("Unknown client has disconnected (ID={0})", id); - return; - } - - Log.info("&y{0} has disconnected.", player.name); - netCommon.sendMessage("[accent]" + player.name + " has disconnected."); - player.remove(); - - DisconnectPacket dc = new DisconnectPacket(); - dc.playerid = player.id; - - Net.send(dc, SendMode.tcp); Platform.instance.updateRPC(); - admins.save(); }); - Net.handleServer(PositionPacket.class, (id, packet) -> { - ByteBuffer buffer = ByteBuffer.wrap(packet.data); - long time = buffer.getLong(); - + //update last recieved snapshot based on client snapshot + Net.handleServer(ClientSnapshotPacket.class, (id, packet) -> { Player player = connections.get(id); - player.read(buffer, time); + NetConnection connection = Net.getConnection(id); + if(player == null || connection == null || packet.snapid < connection.lastRecievedClientSnapshot) return; + + boolean verifyPosition = !player.isDead() && !debug && headless && !player.mech.flying && player.getCarrier() == null; + + if(connection.lastRecievedClientTime == 0) connection.lastRecievedClientTime = TimeUtils.millis() - 16; + + long elapsed = TimeUtils.timeSinceMillis(connection.lastRecievedClientTime); + + float maxSpeed = (packet.boosting && !player.mech.flying ? player.mech.boostSpeed : player.mech.speed) * 2.5f; + + //extra 1.1x multiplicaton is added just in case + float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f; + + player.pointerX = packet.pointerX; + player.pointerY = packet.pointerY; + player.setMineTile(packet.mining); + player.isBoosting = packet.boosting; + player.isShooting = packet.shooting; + player.getPlaceQueue().clear(); + if(packet.currentRequest != null){ + player.getPlaceQueue().addLast(packet.currentRequest); + } + + vector.set(packet.x - player.getInterpolator().target.x, packet.y - player.getInterpolator().target.y); + + vector.limit(maxMove); + + float prevx = player.x, prevy = player.y; + player.set(player.getInterpolator().target.x, player.getInterpolator().target.y); + player.move(vector.x, vector.y); + float newx = player.x, newy = player.y; + + if(!verifyPosition){ + player.x = prevx; + player.y = prevy; + newx = packet.x; + newy = packet.y; + }else if(Vector2.dst(packet.x, packet.y, newx, newy) > correctDist){ + Call.onPositionSet(id, newx, newy); //teleport and correct position when necessary + } + //reset player to previous synced position so it gets interpolated + player.x = prevx; + player.y = prevy; + + //set interpolator target to *new* position so it moves toward it + player.getInterpolator().read(player.x, player.y, newx, newy, packet.timeSent, packet.rotation, packet.baseRotation); + player.getVelocity().set(packet.xv, packet.yv); //only for visual calculation purposes, doesn't actually update the player + + //when the client confirms recieveing a snapshot, update base and clear map + if(packet.lastSnapshot > connection.currentBaseID){ + connection.currentBaseID = packet.lastSnapshot; + connection.currentBaseSnapshot = connection.lastSentRawSnapshot; + } + + connection.lastRecievedClientSnapshot = packet.snapid; + connection.lastRecievedClientTime = TimeUtils.millis(); }); - Net.handleServer(ShootPacket.class, (id, packet) -> { - TraceInfo info = admins.getTrace(Net.getConnection(id).address); - Weapon weapon = (Weapon)Upgrade.getByID(packet.weaponid); + Net.handleServer(InvokePacket.class, (id, packet) -> { + Player player = connections.get(id); + if(player == null) return; + RemoteReadServer.readPacket(packet.writeBuffer, packet.type, player); + }); + } - float wtrc = 80; - - if(!Timers.get("fastshoot-" + id + "-" + weapon.id, wtrc)){ - info.fastShots.getAndIncrement(weapon.id, 0, 1); - - if(info.fastShots.get(weapon.id, 0) > (int)(wtrc / (weapon.getReload() / 2f)) + 30){ - kick(id, KickReason.fastShoot); + /** + * Sends a raw byte[] snapshot to a client, splitting up into chunks when needed. + */ + private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){ + if(bytes.length < maxSnapshotSize){ + Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base); + }else{ + int remaining = bytes.length; + int offset = 0; + int chunkid = 0; + while(remaining > 0){ + int used = Math.min(remaining, maxSnapshotSize); + byte[] toSend; + //re-use sent byte arrays when possible + if(used == maxSnapshotSize){ + toSend = reusableSnapArray; + System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset); + }else{ + toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length)); } + Call.onSnapshot(userid, toSend, snapshotID, (short) chunkid, bytes.length, base); + + remaining -= used; + offset += used; + chunkid++; + } + } + } + + public static void onDisconnect(Player player){ + Call.sendMessage("[accent]" + player.name + " has disconnected."); + Call.onPlayerDisconnect(player.id); + player.remove(); + netServer.connections.remove(player.con.id); + } + + @Remote(targets = Loc.client, called = Loc.server) + public static void onAdminRequest(Player player, Player other, AdminAction action){ + + if(!player.isAdmin){ + Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.", + player.name, player.con.address); + return; + } + + if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself + Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name); + return; + } + + if(action == AdminAction.wave){ + //no verification is done, so admins can hypothetically spam waves + //not a real issue, because server owners may want to do just that + state.wavetime = 0f; + }else if(action == AdminAction.ban){ + netServer.admins.banPlayerIP(other.con.address); + netServer.kick(other.con.id, KickReason.banned); + Log.info("&lc{0} has banned {1}.", player.name, other.name); + }else if(action == AdminAction.kick){ + netServer.kick(other.con.id, KickReason.kick); + Log.info("&lc{0} has kicked {1}.", player.name, other.name); + }else if(action == AdminAction.trace){ + //TODO + if(player.con != null){ + Call.onTraceInfo(player.con.id, netServer.admins.getTraceByID(other.uuid)); }else{ - info.fastShots.put(weapon.id, 0); + NetClient.onTraceInfo(netServer.admins.getTraceByID(other.uuid)); } + Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name); + } + } - packet.playerid = connections.get(id).id; - Net.sendExcept(id, packet, SendMode.udp); - }); - - Net.handleServer(PlacePacket.class, (id, packet) -> { - packet.playerid = connections.get(id).id; - - Block block = Block.getByID(packet.block); - - if(!Placement.validPlace(packet.x, packet.y, block)) return; - - Recipe recipe = Recipes.getByResult(block); - - if(recipe == null) return; - - Tile tile = world.tile(packet.x, packet.y); - if(tile.synthetic() && admins.isValidateReplace() && !admins.validateBreak(admins.getTrace(Net.getConnection(id).address).uuid, Net.getConnection(id).address)){ - if(Timers.get("break-message-" + id, 120)){ - sendMessageTo(id, "[scarlet]Anti-grief: you are replacing blocks too quickly. wait until replacing again."); - } - return; - } - - state.inventory.removeItems(recipe.requirements); - - Placement.placeBlock(packet.x, packet.y, block, packet.rotation, true, false); - - admins.logEdit(packet.x, packet.y, connections.get(id), block, packet.rotation, EditLog.EditAction.PLACE); - admins.getTrace(Net.getConnection(id).address).lastBlockPlaced = block; - admins.getTrace(Net.getConnection(id).address).totalBlocksPlaced ++; - admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlockPlaced ++; - - Net.send(packet, SendMode.tcp); - }); - - Net.handleServer(BreakPacket.class, (id, packet) -> { - packet.playerid = connections.get(id).id; - - if(!Placement.validBreak(packet.x, packet.y)) return; - - Tile tile = world.tile(packet.x, packet.y); - - if(tile.synthetic() && !admins.validateBreak(admins.getTrace(Net.getConnection(id).address).uuid, Net.getConnection(id).address)){ - if(Timers.get("break-message-" + id, 120)){ - sendMessageTo(id, "[scarlet]Anti-grief: you are breaking blocks too quickly. wait until breaking again."); - } - return; - } - - Block block = Placement.breakBlock(packet.x, packet.y, true, false); - - if(block != null) { - admins.logEdit(packet.x, packet.y, connections.get(id), block, tile.getRotation(), EditLog.EditAction.BREAK); - admins.getTrace(Net.getConnection(id).address).lastBlockBroken = block; - admins.getTrace(Net.getConnection(id).address).totalBlocksBroken++; - admins.getInfo(admins.getTrace(Net.getConnection(id).address).uuid).totalBlocksBroken ++; - if (block.update || block.destructible) - admins.getTrace(Net.getConnection(id).address).structureBlocksBroken++; - } - - Net.send(packet, SendMode.tcp); - }); - - Net.handleServer(ChatPacket.class, (id, packet) -> { - if(!Timers.get("chatFlood" + id, 20)){ - ChatPacket warn = new ChatPacket(); - warn.text = "[scarlet]You are sending messages too quickly."; - Net.sendTo(id, warn, SendMode.tcp); - return; - } - Player player = connections.get(id); - packet.name = player.name; - packet.id = player.id; - Net.send(packet, SendMode.tcp); - }); - - Net.handleServer(UpgradePacket.class, (id, packet) -> { - Player player = connections.get(id); - - Weapon weapon = (Weapon) Upgrade.getByID(packet.id); - String uuid = admins.getTrace(Net.getConnection(id).address).uuid; - - if(!state.inventory.hasItems(UpgradeRecipes.get(weapon))){ - return; - } - - if (!weapons.containsKey(uuid)) weapons.put(uuid, new ByteArray()); - - if (!weapons.get(uuid).contains(weapon.id)){ - weapons.get(uuid).add(weapon.id); - }else{ - return; - } - - state.inventory.removeItems(UpgradeRecipes.get(weapon)); - Net.sendTo(id, packet, SendMode.tcp); - }); - - Net.handleServer(WeaponSwitchPacket.class, (id, packet) -> { - TraceInfo info = admins.getTrace(Net.getConnection(id).address); - - packet.playerid = connections.get(id).id; - Net.sendExcept(id, packet, SendMode.tcp); - }); - - Net.handleServer(BlockTapPacket.class, (id, packet) -> { - Net.sendExcept(id, packet, SendMode.tcp); - }); - - Net.handleServer(BlockConfigPacket.class, (id, packet) -> { - Net.sendExcept(id, packet, SendMode.tcp); - }); - - Net.handleServer(EntityRequestPacket.class, (cid, packet) -> { - - int id = packet.id; - int dest = cid; - EntityGroup group = Entities.getGroup(packet.group); - if(group.getByID(id) != null){ - EntitySpawnPacket p = new EntitySpawnPacket(); - p.entity = (SyncEntity)group.getByID(id); - p.group = group; - Net.sendTo(dest, p, SendMode.tcp); - } - }); - - Net.handleServer(PlayerDeathPacket.class, (id, packet) -> { - packet.id = connections.get(id).id; - Net.sendExcept(id, packet, SendMode.tcp); - }); - - Net.handleServer(AdministerRequestPacket.class, (id, packet) -> { - Player player = connections.get(id); - - if(!player.isAdmin){ - Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.", - player.name, Net.getConnection(player.clientid).address); - return; - } - - Player other = playerGroup.getByID(packet.id); - - if(other == null || other.isAdmin){ - Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name); - return; - } - - String ip = Net.getConnection(other.clientid).address; - - if(packet.action == AdminAction.ban){ - admins.banPlayerIP(ip); - kick(other.clientid, KickReason.banned); - Log.info("&lc{0} has banned {1}.", player.name, other.name); - }else if(packet.action == AdminAction.kick){ - kick(other.clientid, KickReason.kick); - Log.info("&lc{0} has kicked {1}.", player.name, other.name); - }else if(packet.action == AdminAction.trace){ - TracePacket trace = new TracePacket(); - trace.info = admins.getTrace(ip); - Net.sendTo(id, trace, SendMode.tcp); - Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name); - } - }); - - Net.handleServer(BlockLogRequestPacket.class, (id, packet) -> { - packet.editlogs = admins.getEditLogs().get(packet.x + packet.y * world.width(), new Array<>()); - Net.sendTo(id, packet, SendMode.udp); - }); - - Net.handleServer(RollbackRequestPacket.class, (id, packet) -> { - Player player = connections.get(id); - - if(!player.isAdmin){ - Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform a rollback without proper security access.", - player.name, Net.getConnection(player.clientid).address); - return; - } - - admins.rollbackWorld(packet.rollbackTimes); - Log.info("&lc{0} has rolled back the world {1} times.", player.name, packet.rollbackTimes); - }); + @Remote(targets = Loc.client) + public static void connectConfirm(Player player){ + player.add(); + player.con.hasConnected = true; + Call.sendMessage("[accent]" + player.name + " has connected."); + Log.info("&y{0} has connected.", player.name); } public void update(){ @@ -387,7 +346,6 @@ public class NetServer extends Module{ } public void reset(){ - weapons.clear(); admins.clearTraces(); } @@ -400,104 +358,184 @@ public class NetServer extends Module{ Log.info("Kicking connection #{0} / IP: {1}. Reason: {2}", connection, con.address, reason); } - if((reason == KickReason.kick || reason == KickReason.banned) && admins.getTrace(con.address).uuid != null){ - PlayerInfo info = admins.getInfo(admins.getTrace(con.address).uuid); - info.timesKicked ++; + if((reason == KickReason.kick || reason == KickReason.banned) && admins.getTraceByID(getUUID(con.id)).uuid != null){ + PlayerInfo info = admins.getInfo(admins.getTraceByID(getUUID(con.id)).uuid); + info.timesKicked++; info.lastKicked = TimeUtils.millis(); } - KickPacket p = new KickPacket(); - p.reason = reason; + //TODO kick player, send kick packet + Call.onKick(connection, reason); - con.send(p, SendMode.tcp); Timers.runTask(2f, con::close); admins.save(); } - void sendMessageTo(int id, String message){ - ChatPacket packet = new ChatPacket(); - packet.text = message; - Net.sendTo(id, packet, SendMode.tcp); + String getUUID(int connectionID){ + return connections.get(connectionID).uuid; } - void sync(){ + String fixName(String name){ - if(timer.get(timerEntitySync, serverSyncTime)){ - //scan through all groups with syncable entities - for(EntityGroup group : Entities.getAllGroups()) { - if(group.size() == 0 || !(group.all().iterator().next() instanceof SyncEntity)) continue; + for(int i = 0; i < name.length(); i++){ + if(name.charAt(i) == '[' && i != name.length() - 1 && name.charAt(i + 1) != '[' && (i == 0 || name.charAt(i - 1) != '[')){ + String prev = name.substring(0, i); + String next = name.substring(i); + String result = checkColor(next); - //get write size for one entity (adding 4, as you need to write the ID as well) - int writesize = SyncEntity.getWriteSize((Class)group.getType()) + 4; - //amount of entities - int amount = group.size(); - //maximum amount of entities per packet - int maxsize = 64; - - //current buffer you're writing to - ByteBuffer current = null; - //number of entities written to this packet/buffer - int written = 0; - - //for all the entities... - for (int i = 0; i < amount; i++) { - //if the buffer is null, create a new one - if(current == null){ - //calculate amount of entities to go into this packet - int csize = Math.min(amount-i, maxsize); - //create a byte array to write to - byte[] bytes = new byte[csize*writesize + 1 + 8]; - //wrap it for easy writing - current = ByteBuffer.wrap(bytes); - current.putLong(TimeUtils.millis()); - //write the group ID so the client knows which group this is - current.put((byte)group.getID()); - } - - SyncEntity entity = (SyncEntity) group.all().get(i); - - //write ID to the buffer - current.putInt(entity.id); - - int previous = current.position(); - //write extra data to the buffer - entity.write(current); - - written ++; - - //if the packet is too big now... - if(written >= maxsize){ - //send the packet. - SyncPacket packet = new SyncPacket(); - packet.data = current.array(); - Net.send(packet, SendMode.udp); - - //reset data, send the next packet - current = null; - written = 0; - } - } - - //make sure to send incomplete packets too - if(current != null){ - SyncPacket packet = new SyncPacket(); - packet.data = current.array(); - Net.send(packet, SendMode.udp); - } + name = prev + result; } } - if(timer.get(timerStateSync, itemSyncTime)){ - StateSyncPacket packet = new StateSyncPacket(); - packet.items = state.inventory.getItems(); - packet.countdown = state.wavetime; - packet.enemies = state.enemies; - packet.wave = state.wave; - packet.time = Timers.time(); - packet.timestamp = TimeUtils.millis(); - - Net.send(packet, SendMode.udp); + return name.substring(0, Math.min(name.length(), maxNameLength)); + } + + String checkColor(String str){ + + for(int i = 1; i < str.length(); i++){ + if(str.charAt(i) == ']'){ + String color = str.substring(1, i); + + if(Colors.get(color.toUpperCase()) != null || Colors.get(color.toLowerCase()) != null){ + Color result = (Colors.get(color.toLowerCase()) == null ? Colors.get(color.toUpperCase()) : Colors.get(color.toLowerCase())); + if(result.a <= 0.8f){ + return str.substring(i + 1); + } + }else{ + try{ + Color result = Color.valueOf(color); + if(result.a <= 0.8f){ + return str.substring(i + 1); + } + }catch(Exception e){ + return str; + } + } + } + } + return str; + } + + void sync(){ + try{ + + //iterate through each player + for(Player player : connections.values()){ + NetConnection connection = player.con; + + if(!connection.isConnected()){ + //player disconnected, ignore them + onDisconnect(player); + return; + } + + if(!player.timer.get(Player.timerSync, serverSyncTime) || !connection.hasConnected) continue; + + //if the player hasn't acknowledged that it has recieved the packet, send the same thing again + if(connection.currentBaseID < connection.lastSentSnapshotID){ + if(showSnapshotSize) + Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length); + sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID, connection.lastSentBase); + return; + } + + //reset stream to begin writing + syncStream.reset(); + + //write wave datas + dataStream.writeFloat(state.wavetime); + dataStream.writeInt(state.wave); + + Array cores = state.teams.get(player.getTeam()).cores; + + dataStream.writeByte(cores.size); + + //write all core inventory data + for(Tile tile : cores){ + dataStream.writeInt(tile.packedPosition()); + tile.entity.items.write(dataStream); + } + + //write timestamp + dataStream.writeLong(TimeUtils.millis()); + + int totalGroups = 0; + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && (group.all().get(0) instanceof SyncTrait)) totalGroups++; + } + + //write total amount of serializable groups + dataStream.writeByte(totalGroups); + + //check for syncable groups + for(EntityGroup group : Entities.getAllGroups()){ + //TODO range-check sync positions to optimize? + if(group.isEmpty() || !(group.all().get(0) instanceof SyncTrait)) continue; + + //make sure mapping is enabled for this group + if(!group.mappingEnabled()){ + throw new RuntimeException("Entity group '" + group.getType() + "' contains SyncTrait entities, yet mapping is not enabled. In order for syncing to work, you must enable mapping for this group."); + } + + int amount = 0; + + for(Entity entity : group.all()){ + if(((SyncTrait) entity).isSyncing()){ + amount++; + } + } + + //write group ID + group size + dataStream.writeByte(group.getID()); + dataStream.writeShort(amount); + + for(Entity entity : group.all()){ + if(!((SyncTrait) entity).isSyncing()) continue; + + int position = syncStream.position(); + //write all entities now + dataStream.writeInt(entity.getID()); //write id + dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID + ((SyncTrait) entity).write(dataStream); //write entity + int length = syncStream.position() - position; //length must always be less than 127 bytes + if(length > 127) + throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!"); + dataStream.writeByte(length); + } + } + + byte[] bytes = syncStream.toByteArray(); + + if(connection.currentBaseID == -1){ + //assign to last sent snapshot so that there is only ever one unique snapshot with ID 0 + if(connection.lastSentSnapshot != null){ + bytes = connection.lastSentSnapshot; + }else{ + connection.lastSentRawSnapshot = bytes; + connection.lastSentSnapshot = bytes; + } + + if(showSnapshotSize) Log.info("Sent raw snapshot: {0} bytes.", bytes.length); + ///Nothing to diff off of in this case, send the whole thing + sendSplitSnapshot(connection.id, bytes, 0, -1); + }else{ + connection.lastSentRawSnapshot = bytes; + + //send diff, otherwise + byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder); + if(showSnapshotSize) + Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length); + sendSplitSnapshot(connection.id, diff, connection.currentBaseID + 1, connection.currentBaseID); + connection.lastSentSnapshot = diff; + connection.lastSentSnapshotID = connection.currentBaseID + 1; + connection.lastSentBase = connection.currentBaseID; + } + } + + }catch(IOException e){ + e.printStackTrace(); } } } diff --git a/core/src/io/anuke/mindustry/core/Platform.java b/core/src/io/anuke/mindustry/core/Platform.java index 1c3ab23003..7462ba3eb2 100644 --- a/core/src/io/anuke/mindustry/core/Platform.java +++ b/core/src/io/anuke/mindustry/core/Platform.java @@ -4,8 +4,6 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Base64Coder; import io.anuke.mindustry.core.ThreadHandler.ThreadProvider; import io.anuke.ucore.core.Settings; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.entities.EntityGroup; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.scene.ui.TextField; @@ -13,77 +11,184 @@ import java.util.Date; import java.util.Locale; import java.util.Random; -public abstract class Platform { - /**Each separate game platform should set this instance to their own implementation.*/ - public static Platform instance = new Platform() {}; +public abstract class Platform{ + /** + * Each separate game platform should set this instance to their own implementation. + */ + public static Platform instance = new Platform(){ + }; - /**Format the date using the default date formatter.*/ - public String format(Date date){return "invalid";} - /**Format a number by adding in commas or periods where needed.*/ - public String format(int number){return "invalid";} - /**Show a native error dialog.*/ - public void showError(String text){} - /**Add a text input dialog that should show up after the field is tapped.*/ - public void addDialog(TextField field){ - addDialog(field, 16); - } - /**See addDialog().*/ - public void addDialog(TextField field, int maxLength){} - /**Update discord RPC.*/ - public void updateRPC(){} - /**Called when the game is exited.*/ - public void onGameExit(){} - /**Open donation dialog. Currently android only.*/ - public void openDonations(){} - /**Whether discord RPC is supported.*/ - public boolean hasDiscord(){return true;} - /**Request Android permissions for writing files.*/ - public void requestWritePerms(){} - /**Return the localized name for the locale. This is basically a workaround for GWT not supporting getName().*/ - public String getLocaleName(Locale locale){ - return locale.toString(); - } - /**Whether joining games is supported.*/ - public boolean canJoinGame(){ - return true; - } - /**Whether debug mode is enabled.*/ - public boolean isDebug(){return false;} - /**Must be 8 bytes in length.*/ - public byte[] getUUID(){ - String uuid = Settings.getString("uuid", ""); - if(uuid.isEmpty()){ - byte[] result = new byte[8]; - new Random().nextBytes(result); - uuid = new String(Base64Coder.encode(result)); - Settings.putString("uuid", uuid); - Settings.save(); - return result; - } - return Base64Coder.decode(uuid); - } - /**Only used for iOS or android: open the share menu for a map or save.*/ - public void shareFile(FileHandle file){} + /** + * Format the date using the default date formatter. + */ + public String format(Date date){ + return "invalid"; + } - /**Show a file chooser. Desktop only. + /** + * Format a number by adding in commas or periods where needed. + */ + public String format(int number){ + return "invalid"; + } + + /** + * Show a native error dialog. + */ + public void showError(String text){ + } + + /** + * Add a text input dialog that should show up after the field is tapped. + */ + public void addDialog(TextField field){ + addDialog(field, 16); + } + + /** + * See addDialog(). + */ + public void addDialog(TextField field, int maxLength){ + } + + /** + * Update discord RPC. + */ + public void updateRPC(){ + } + + /** + * Called when the game is exited. + */ + public void onGameExit(){ + } + + /** + * Open donation dialog. Currently android only. + */ + public void openDonations(){ + } + + /** + * Whether donating is supported. + */ + public boolean canDonate(){ + return false; + } + + /** + * Whether discord RPC is supported. + */ + public boolean hasDiscord(){ + return true; + } + + /** + * Return the localized name for the locale. This is basically a workaround for GWT not supporting getName(). + */ + public String getLocaleName(Locale locale){ + return locale.toString(); + } + + /** + * Whether joining games is supported. + */ + public boolean canJoinGame(){ + return true; + } + + /** + * Whether debug mode is enabled. + */ + public boolean isDebug(){ + return false; + } + + /** + * Must be a base64 string 8 bytes in length. + */ + public String getUUID(){ + String uuid = Settings.getString("uuid", ""); + if(uuid.isEmpty()){ + byte[] result = new byte[8]; + new Random().nextBytes(result); + uuid = new String(Base64Coder.encode(result)); + Settings.putString("uuid", uuid); + Settings.save(); + return uuid; + } + return uuid; + } + + /** + * Only used for iOS or android: open the share menu for a map or save. + */ + public void shareFile(FileHandle file){ + } + + /** + * Download a file. Only used on GWT backend. + */ + public void downloadFile(String name, byte[] bytes){ + } + + /** + * Show a file chooser. Desktop only. * * @param text File chooser title text - * @param content Type of files to be loaded + * @param content Description of the type of files to be loaded * @param cons Selection listener - * @param open Whether to open or save files. - * @param filetype File extensions to filter. + * @param open Whether to open or save files + * @param filetype File extension to filter */ - public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){} - /**Use the default thread provider from the kryonet module for this.*/ - public ThreadProvider getThreadProvider(){ - return new ThreadProvider() { - @Override public boolean isOnThread() {return true;} - @Override public void sleep(long ms) {} - @Override public void start(Runnable run) {} - @Override public void stop() {} - @Override public void notify(Object object) {} - @Override public void wait(Object object) {} - @Override public void switchContainer(EntityGroup group) {} - }; - } + public void showFileChooser(String text, String content, Consumer cons, boolean open, String filetype){ + } + + /** + * Use the default thread provider from the kryonet module for this. + */ + public ThreadProvider getThreadProvider(){ + return new ThreadProvider(){ + @Override + public boolean isOnThread(){ + return true; + } + + @Override + public void sleep(long ms){ + } + + @Override + public void start(Runnable run){ + } + + @Override + public void stop(){ + } + + @Override + public void notify(Object object){ + } + + @Override + public void wait(Object object){ + } + }; + } + + //TODO iOS implementation + + /** + * Forces the app into landscape mode. Currently Android only. + */ + public void beginForceLandscape(){ + } + + //TODO iOS implementation + + /** + * Stops forcing the app into landscape orientation. Currently Android only. + */ + public void endForceLandscape(){ + } } diff --git a/core/src/io/anuke/mindustry/core/Renderer.java b/core/src/io/anuke/mindustry/core/Renderer.java index 214b5785c0..65aa2f3eda 100644 --- a/core/src/io/anuke/mindustry/core/Renderer.java +++ b/core/src/io/anuke/mindustry/core/Renderer.java @@ -2,592 +2,475 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureWrap; -import com.badlogic.gdx.graphics.g2d.GlyphLayout; -import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.FloatArray; -import com.badlogic.gdx.utils.Pools; +import com.badlogic.gdx.utils.ObjectIntMap; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.mindustry.content.fx.Fx; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.SyncEntity; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.game.SpawnPoint; -import io.anuke.mindustry.graphics.BlockRenderer; -import io.anuke.mindustry.graphics.Shaders; -import io.anuke.mindustry.input.InputHandler; -import io.anuke.mindustry.input.PlaceMode; -import io.anuke.mindustry.ui.fragments.ToolFragment; -import io.anuke.mindustry.world.BlockBar; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.effect.GroundEffectEntity; +import io.anuke.mindustry.entities.effect.GroundEffectEntity.GroundEffect; +import io.anuke.mindustry.entities.traits.BelowLiquidTrait; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.ProductionBlocks; -import io.anuke.ucore.core.*; -import io.anuke.ucore.entities.EffectEntity; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.function.Callable; -import io.anuke.ucore.graphics.*; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.entities.EntityDraw; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.BaseEntity; +import io.anuke.ucore.entities.impl.EffectEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.SolidTrait; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Hue; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.graphics.Surface; import io.anuke.ucore.modules.RendererModule; -import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.scene.utils.Cursors; -import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Tmp; +import io.anuke.ucore.util.Pooling; +import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.core.Core.batch; import static io.anuke.ucore.core.Core.camera; public class Renderer extends RendererModule{ - private final static float shieldHitDuration = 18f; - - public Surface shadowSurface, shieldSurface, indicatorSurface; - - private int targetscale = baseCameraScale; - private Texture background = new Texture("sprites/background.png"); - private FloatArray shieldHits = new FloatArray(); - private Array shieldDraws = new Array<>(); - private Rectangle rect = new Rectangle(), rect2 = new Rectangle(); - private BlockRenderer blocks = new BlockRenderer(); + public Surface effectSurface; - public Renderer() { - Lines.setCircleVertices(14); + private int targetscale = baseCameraScale; + private Texture background = new Texture("sprites/background.png"); - Core.cameraScale = baseCameraScale; - Effects.setEffectProvider((name, color, x, y, rotation) -> { - if(Settings.getBool("effects")){ - Rectangle view = rect.setSize(camera.viewportWidth, camera.viewportHeight) - .setCenter(camera.position.x, camera.position.y); - Rectangle pos = rect2.setSize(name.size).setCenter(x, y); - if(view.overlaps(pos)){ - new EffectEntity(name, color, rotation).set(x, y).add(effectGroup); - } - } - }); + private Rectangle rect = new Rectangle(), rect2 = new Rectangle(); + private Vector2 avgPosition = new Translator(); + private Vector2 tmpVector1 = new Translator(); + private Vector2 tmpVector2 = new Translator(); - Cursors.cursorScaling = 3; - Cursors.outlineColor = Color.valueOf("444444"); - Cursors.arrow = Cursors.loadCursor("cursor"); - Cursors.hand = Cursors.loadCursor("hand"); - Cursors.ibeam = Cursors.loadCursor("ibar"); + private BlockRenderer blocks = new BlockRenderer(); + private MinimapRenderer minimap = new MinimapRenderer(); + private OverlayRenderer overlays = new OverlayRenderer(); + private FogRenderer fog = new FogRenderer(); - clearColor = Hue.lightness(0.4f); - clearColor.a = 1f; + public Renderer(){ + pixelate = true; + Lines.setCircleVertices(14); - background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); - } + Shaders.init(); - @Override - public void init(){ - pixelate = Settings.getBool("pixelate"); - int scale = Settings.getBool("pixelate") ? Core.cameraScale : 1; - - shadowSurface = Graphics.createSurface(scale); - shieldSurface = Graphics.createSurface(scale); - indicatorSurface = Graphics.createSurface(scale); - pixelSurface = Graphics.createSurface(scale); - } + Core.cameraScale = baseCameraScale; + Effects.setEffectProvider((effect, color, x, y, rotation, data) -> { + if(effect == Fx.none) return; + if(Settings.getBool("effects")){ + Rectangle view = rect.setSize(camera.viewportWidth, camera.viewportHeight) + .setCenter(camera.position.x, camera.position.y); + Rectangle pos = rect2.setSize(effect.size).setCenter(x, y); - public void setPixelate(boolean pixelate){ - this.pixelate = pixelate; - } + if(view.overlaps(pos)){ - @Override - public void update(){ + if(!(effect instanceof GroundEffect)){ + EffectEntity entity = Pooling.obtain(EffectEntity.class); + entity.effect = effect; + entity.color = color; + entity.rotation = rotation; + entity.data = data; + entity.id++; + entity.set(x, y); + if(data instanceof BaseEntity){ + entity.setParent((BaseEntity) data); + } + threads.runGraphics(() -> effectGroup.add(entity)); + }else{ + GroundEffectEntity entity = Pooling.obtain(GroundEffectEntity.class); + entity.effect = effect; + entity.color = color; + entity.rotation = rotation; + entity.id++; + entity.data = data; + entity.set(x, y); + threads.runGraphics(() -> groundEffectGroup.add(entity)); + } + } + } + }); - if(Core.cameraScale != targetscale){ - float targetzoom = (float) Core.cameraScale / targetscale; - camera.zoom = Mathf.lerpDelta(camera.zoom, targetzoom, 0.2f); + Cursors.cursorScaling = 3; + Cursors.outlineColor = Color.valueOf("444444"); - if(Mathf.in(camera.zoom, targetzoom, 0.005f)){ - camera.zoom = 1f; - Graphics.setCameraScale(targetscale); - control.input().resetCursor(); - } - }else{ - camera.zoom = Mathf.lerpDelta(camera.zoom, 1f, 0.2f); - } + Cursors.arrow = Cursors.loadCursor("cursor"); + Cursors.hand = Cursors.loadCursor("hand"); + Cursors.ibeam = Cursors.loadCursor("ibar"); + Cursors.restoreCursor(); + Cursors.loadCustom("drill"); + Cursors.loadCustom("unload"); - if(state.is(State.menu)){ - clearScreen(); - }else{ - boolean smoothcam = Settings.getBool("smoothcam"); + clearColor = Hue.lightness(0.4f); + clearColor.a = 1f; - if(world.getCore() == null || world.getCore().block() == ProductionBlocks.core){ - if(!smoothcam){ - setCamera(player.x, player.y); - }else{ - smoothCamera(player.x, player.y, mobile ? 0.3f : 0.14f); - } - }else{ - smoothCamera(world.getCore().worldx(), world.getCore().worldy(), 0.4f); - } - - if(Settings.getBool("pixelate")) - limitCamera(4f, player.x, player.y); - - float prex = camera.position.x, prey = camera.position.y; - updateShake(0.75f); - float prevx = camera.position.x, prevy = camera.position.y; - clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); - - float deltax = camera.position.x - prex, deltay = camera.position.y - prey; - - if(mobile){ - player.x += camera.position.x - prevx; - player.y += camera.position.y - prevy; - } - - float lastx = camera.position.x, lasty = camera.position.y; - - if(snapCamera && smoothcam && Settings.getBool("pixelate")){ - camera.position.set((int) camera.position.x, (int) camera.position.y, 0); - } - - if(Gdx.graphics.getHeight() / Core.cameraScale % 2 == 1){ - camera.position.add(0, -0.5f, 0); - } - - if(Gdx.graphics.getWidth() / Core.cameraScale % 2 == 1){ - camera.position.add(-0.5f, 0, 0); - } - - draw(); - - camera.position.set(lastx - deltax, lasty - deltay, 0); - - if(debug && !ui.chatfrag.chatOpen()) - record(); //this only does something if GdxGifRecorder is on the class path, which it usually isn't - } - } - - @Override - public void draw(){ - camera.update(); - - clearScreen(clearColor); - - batch.setProjectionMatrix(camera.combined); - - if(pixelate) - Graphics.surface(pixelSurface, false); - else - batch.begin(); - - //clears shield surface - Graphics.surface(shieldSurface); - Graphics.surface(); - - drawPadding(); - - blocks.drawFloor(); - blocks.processBlocks(); - blocks.drawBlocks(false); - - Graphics.shader(Shaders.outline, false); - Entities.draw(enemyGroup); - Entities.draw(playerGroup, p -> !p.isAndroid); - Graphics.shader(); - - Entities.draw(Entities.defaultGroup()); - - blocks.drawBlocks(true); - - Graphics.shader(Shaders.outline, false); - Entities.draw(playerGroup, p -> p.isAndroid); - Graphics.shader(); - - Entities.draw(bulletGroup); - Entities.draw(effectGroup); - - drawShield(); - - drawOverlay(); - - if(Settings.getBool("indicators") && showUI){ - drawEnemyMarkers(); - } - - if(pixelate) - Graphics.flushSurface(); - - drawPlayerNames(); - - batch.end(); - } - - @Override - public void resize(int width, int height){ - super.resize(width, height); - control.input().resetCursor(); - camera.position.set(player.x, player.y, 0); - } - - @Override - public void dispose() { - background.dispose(); - } - - public void clearTiles(){ - blocks.clearTiles(); - } - - void drawPadding(){ - float vw = world.width() * tilesize; - float cw = camera.viewportWidth * camera.zoom; - float ch = camera.viewportHeight * camera.zoom; - if(vw < cw){ - batch.draw(background, - camera.position.x + vw/2, - Mathf.round(camera.position.y - ch/2, tilesize), - (cw - vw) /2, - ch + tilesize, - 0, 0, - ((cw - vw) / 2 / tilesize), -ch / tilesize + 1); - - batch.draw(background, - camera.position.x - vw/2, - Mathf.round(camera.position.y - ch/2, tilesize), - -(cw - vw) /2, - ch + tilesize, - 0, 0, - -((cw - vw) / 2 / tilesize), -ch / tilesize + 1); - } - } - - void drawPlayerNames(){ - GlyphLayout layout = Pools.obtain(GlyphLayout.class); - - Draw.tscl(0.25f/2); - for(Player player : playerGroup.all()){ - if(!player.isLocal && !player.isDead()){ - layout.setText(Core.font, player.name); - Draw.color(0f, 0f, 0f, 0.3f); - Draw.rect("blank", player.getDrawPosition().x, player.getDrawPosition().y + 8 - layout.height/2, layout.width + 2, layout.height + 2); - Draw.color(); - Draw.tcolor(player.getColor()); - Draw.text(player.name, player.getDrawPosition().x, player.getDrawPosition().y + 8); - - if(player.isAdmin){ - Draw.color(player.getColor()); - float s = 3f; - Draw.rect("icon-admin-small", player.getDrawPosition().x + layout.width/2f + 2 + 1, player.getDrawPosition().y + 7f, s, s); - } - Draw.reset(); - } - } - Pools.free(layout); - Draw.tscl(fontscale); + background.setWrap(TextureWrap.Repeat, TextureWrap.Repeat); } - void drawEnemyMarkers(){ - Graphics.surface(indicatorSurface); - Draw.color(Color.RED); + @Override + public void init(){ + int scale = Core.cameraScale; - for(Enemy enemy : enemyGroup.all()) { + effectSurface = Graphics.createSurface(scale); + pixelSurface = Graphics.createSurface(scale); + } - if (rect.setSize(camera.viewportWidth, camera.viewportHeight).setCenter(camera.position.x, camera.position.y) - .overlaps(enemy.hitbox.getRect(enemy.x, enemy.y))) { - continue; - } + @Override + public void update(){ - float angle = Angles.angle(camera.position.x, camera.position.y, enemy.x, enemy.y); - float tx = Angles.trnsx(angle, Unit.dp.scl(20f)); - float ty = Angles.trnsy(angle, Unit.dp.scl(20f)); - Draw.rect("enemyarrow", camera.position.x + tx, camera.position.y + ty, angle); - } + if(Core.cameraScale != targetscale){ + float targetzoom = (float) Core.cameraScale / targetscale; + camera.zoom = Mathf.lerpDelta(camera.zoom, targetzoom, 0.2f); - Draw.color(); - Draw.alpha(0.4f); - Graphics.flushSurface(); - Draw.color(); - } + if(Mathf.in(camera.zoom, targetzoom, 0.005f)){ + camera.zoom = 1f; + Graphics.setCameraScale(targetscale); + for(Player player : players){ + control.input(player.playerIndex).resetCursor(); + } + } + }else{ + camera.zoom = Mathf.lerpDelta(camera.zoom, 1f, 0.2f); + } - void drawShield(){ - if(shieldGroup.size() == 0 && shieldDraws.size == 0) return; - - Graphics.surface(renderer.shieldSurface, false); - Draw.color(Color.ROYAL); - Entities.draw(shieldGroup); - for(Callable c : shieldDraws){ - c.run(); - } - Draw.reset(); - Graphics.surface(); - - for(int i = 0; i < shieldHits.size / 3; i++){ - float time = shieldHits.get(i * 3 + 2); + if(state.is(State.menu)){ + Graphics.clear(Color.BLACK); + }else{ + Vector2 position = averagePosition(); - time += Timers.delta() / shieldHitDuration; - shieldHits.set(i * 3 + 2, time); + if(!mobile){ + setCamera(position.x + 0.0001f, position.y + 0.0001f); + } - if(time >= 1f){ - shieldHits.removeRange(i * 3, i * 3 + 2); - i--; - } - } + clampCamera(-tilesize / 2f, -tilesize / 2f + 1, world.width() * tilesize - tilesize / 2f, world.height() * tilesize - tilesize / 2f); - Texture texture = shieldSurface.texture(); - Shaders.shield.color.set(Color.SKY); + float prex = camera.position.x, prey = camera.position.y; + updateShake(0.75f); - Tmp.tr2.setRegion(texture); - Shaders.shield.region = Tmp.tr2; - Shaders.shield.hits = shieldHits; - - if(Shaders.shield.isFallback){ - Draw.color(1f, 1f, 1f, 0.3f); - Shaders.outline.color = Color.SKY; - Shaders.outline.region = Tmp.tr2; - } + float deltax = camera.position.x - prex, deltay = camera.position.y - prey; + float lastx = camera.position.x, lasty = camera.position.y; - Graphics.end(); - Graphics.shader(Shaders.shield.isFallback ? Shaders.outline : Shaders.shield); - Graphics.setScreen(); + if(snapCamera){ + camera.position.set((int) camera.position.x, (int) camera.position.y, 0); + } - Core.batch.draw(texture, 0, Gdx.graphics.getHeight(), Gdx.graphics.getWidth(), -Gdx.graphics.getHeight()); + if(Gdx.graphics.getHeight() / Core.cameraScale % 2 == 1){ + camera.position.add(0, -0.5f, 0); + } - Graphics.shader(); - Graphics.end(); - Graphics.beginCam(); - - Draw.color(); - shieldDraws.clear(); - } + if(Gdx.graphics.getWidth() / Core.cameraScale % 2 == 1){ + camera.position.add(-0.5f, 0, 0); + } - public BlockRenderer getBlocks() { - return blocks; - } + draw(); - public void addShieldHit(float x, float y){ - shieldHits.addAll(x, y, 0f); - } + camera.position.set(lastx - deltax, lasty - deltay, 0); + } - public void addShield(Callable call){ - shieldDraws.add(call); - } + if(debug && !ui.chatfrag.chatOpen()){ + renderer.record(); //this only does something if GdxGifRecorder is on the class path, which it usually isn't + } + } - void drawOverlay(){ + @Override + public void draw(){ + camera.update(); - //draw tutorial placement point - if(world.getMap().name.equals("tutorial") && control.tutorial().showBlock()){ - int x = world.getCore().x + control.tutorial().getPlacePoint().x; - int y = world.getCore().y + control.tutorial().getPlacePoint().y; - int rot = control.tutorial().getPlaceRotation(); + Graphics.clear(clearColor); - Lines.stroke(1f); - Draw.color(Color.YELLOW); - Lines.square(x * tilesize, y * tilesize, tilesize / 2f + Mathf.sin(Timers.time(), 4f, 1f)); + batch.setProjectionMatrix(camera.combined); - Draw.color(Color.ORANGE); - Lines.stroke(2f); - if(rot != -1){ - Lines.lineAngle(x * tilesize, y * tilesize, rot * 90, 6); - } - Draw.reset(); - } + Graphics.surface(pixelSurface, false); - //draw config selected block - if(ui.configfrag.isShown()){ - Tile tile = ui.configfrag.getSelectedTile(); - Draw.color(Colors.get("accent")); - Lines.stroke(1f); - Lines.square(tile.drawx(), tile.drawy(), - tile.block().width * tilesize / 2f + 1f); - Draw.reset(); - } - - int tilex = control.input().getBlockX(); - int tiley = control.input().getBlockY(); - - if(mobile){ - Vector2 vec = Graphics.world(Gdx.input.getX(0), Gdx.input.getY(0)); - tilex = Mathf.scl2(vec.x, tilesize); - tiley = Mathf.scl2(vec.y, tilesize); - } + drawPadding(); - InputHandler input = control.input(); + blocks.drawFloor(); - //draw placement box - if((input.recipe != null && state.inventory.hasItems(input.recipe.requirements) && (!ui.hasMouse() || mobile) - && control.input().drawPlace())){ + drawAndInterpolate(groundEffectGroup, e -> e instanceof BelowLiquidTrait); + drawAndInterpolate(puddleGroup); + drawAndInterpolate(groundEffectGroup, e -> !(e instanceof BelowLiquidTrait)); - input.placeMode.draw(control.input().getBlockX(), control.input().getBlockY(), - control.input().getBlockEndX(), control.input().getBlockEndY()); + blocks.processBlocks(); + blocks.drawBlocks(Layer.block); - Lines.stroke(1f); - Draw.color(Color.SCARLET); - for(SpawnPoint spawn : world.getSpawns()){ - Lines.dashCircle(spawn.start.worldx(), spawn.start.worldy(), enemyspawnspace); - } + Graphics.shader(Shaders.blockbuild, false); + blocks.drawBlocks(Layer.placement); + Graphics.shader(); - if(world.getCore() != null) { - Draw.color(Color.LIME); - Lines.poly(world.getSpawnX(), world.getSpawnY(), 4, 6f, Timers.time() * 2f); - } - - if(input.breakMode == PlaceMode.holdDelete) - input.breakMode.draw(tilex, tiley, 0, 0); - - }else if(input.breakMode.delete && control.input().drawPlace() - && (input.recipe == null || !state.inventory.hasItems(input.recipe.requirements)) - && (input.placeMode.delete || input.breakMode.both || !mobile)){ + blocks.drawBlocks(Layer.overlay); - if(input.breakMode == PlaceMode.holdDelete) - input.breakMode.draw(tilex, tiley, 0, 0); - else - input.breakMode.draw(control.input().getBlockX(), control.input().getBlockY(), - control.input().getBlockEndX(), control.input().getBlockEndY()); - } + if(itemGroup.size() > 0){ + Shaders.outline.color.set(Team.none.color); - if(ui.toolfrag.confirming){ - ToolFragment t = ui.toolfrag; - PlaceMode.areaDelete.draw(t.px, t.py, t.px2, t.py2); - } - - Draw.reset(); + Graphics.beginShaders(Shaders.outline); + drawAndInterpolate(itemGroup); + Graphics.endShaders(); + } - //draw selected block bars and info - if(input.recipe == null && !ui.hasMouse()){ - Tile tile = world.tileWorld(Graphics.mouseWorld().x, Graphics.mouseWorld().y); + drawAllTeams(false); - if(tile != null && tile.block() != Blocks.air){ - Tile target = tile; - if(tile.isLinked()) - target = tile.getLinked(); + blocks.skipLayer(Layer.turret); + blocks.drawBlocks(Layer.laser); - if(showBlockDebug && target.entity != null){ - Draw.color(Color.RED); - Lines.crect(target.drawx(), target.drawy(), target.block().width * tilesize, target.block().height * tilesize); - Vector2 v = new Vector2(); + drawFlyerShadows(); + drawAllTeams(true); + drawAndInterpolate(bulletGroup); + drawAndInterpolate(effectGroup); - Draw.tcolor(Color.YELLOW); - Draw.tscl(0.25f); - Array arr = target.block().getDebugInfo(target); - StringBuilder result = new StringBuilder(); - for(int i = 0; i < arr.size/2; i ++){ - result.append(arr.get(i*2)); - result.append(": "); - result.append(arr.get(i*2 + 1)); - result.append("\n"); - } - Draw.textc(result.toString(), target.drawx(), target.drawy(), v); - Draw.color(0f, 0f, 0f, 0.5f); - Fill.rect(target.drawx(), target.drawy(), v.x, v.y); - Draw.textc(result.toString(), target.drawx(), target.drawy(), v); - Draw.tscl(fontscale); - Draw.reset(); - } + overlays.drawBottom(); + drawAndInterpolate(playerGroup, p -> true, Player::drawBuildRequests); + overlays.drawTop(); - if(Inputs.keyDown("block_info") && target.block().fullDescription != null){ - Draw.color(Colors.get("accent")); - Lines.crect(target.drawx(), target.drawy(), target.block().width * tilesize, target.block().height * tilesize); - Draw.color(); - } - - if(Inputs.keyDown("block_logs")){ - Draw.color(Colors.get("accent")); - Lines.crect(target.drawx(), target.drawy(), target.block().width * tilesize, target.block().height * tilesize); - Draw.color(); - } + Graphics.flushSurface(); - if(target.entity != null) { - int bot = 0, top = 0; - for (BlockBar bar : target.block().bars) { - float offset = Mathf.sign(bar.top) * (target.block().height / 2f * tilesize + 3f + 4f * ((bar.top ? top : bot))) + - (bar.top ? -1f : 0f); + if(showPaths && debug) drawDebug(); - float value = bar.value.get(target); + batch.end(); - if(MathUtils.isEqual(value, -1f)) continue; + if(showFog){ + fog.draw(); + } - drawBar(bar.color, target.drawx(), target.drawy() + offset, value); + Graphics.beginCam(); + EntityDraw.setClip(false); + drawAndInterpolate(playerGroup, p -> !p.isDead() && !p.isLocal, Player::drawName); + EntityDraw.setClip(true); + Graphics.end(); + } - if (bar.top) - top++; - else - bot++; - } - } + private void drawFlyerShadows(){ + Graphics.surface(effectSurface, true, false); - target.block().drawSelect(target); - } - } - - if((!debug || showUI) && Settings.getBool("healthbars")){ + float trnsX = 12, trnsY = -13; - //draw entity health bars - for(Enemy entity : enemyGroup.all()){ - drawHealth(entity); - } + Graphics.end(); + Core.batch.getTransformMatrix().translate(trnsX, trnsY, 0); + Graphics.begin(); - for(Player player : playerGroup.all()){ - if(!player.isDead() && !player.isAndroid) drawHealth(player); - } - } - } + for(EntityGroup group : unitGroups){ + if(!group.isEmpty()){ + drawAndInterpolate(group, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); + } + } - void drawHealth(SyncEntity dest){ - float x = dest.getDrawPosition().x; - float y = dest.getDrawPosition().y; - if(dest instanceof Player && snapCamera && Settings.getBool("smoothcam") && Settings.getBool("pixelate")){ - drawHealth((int) x, (int) y - 7f, dest.health, dest.maxhealth); - }else{ - drawHealth(x, y - 7f, dest.health, dest.maxhealth); - } - } + if(!playerGroup.isEmpty()){ + drawAndInterpolate(playerGroup, unit -> unit.isFlying() && !unit.isDead(), Unit::drawShadow); + } - void drawHealth(float x, float y, float health, float maxhealth){ - drawBar(Color.RED, x, y, health / maxhealth); - } - - //TODO optimize! - public void drawBar(Color color, float x, float y, float finion){ - finion = Mathf.clamp(finion); + Graphics.end(); + Core.batch.getTransformMatrix().translate(-trnsX, -trnsY, 0); + Graphics.begin(); - if(finion > 0) finion = Mathf.clamp(finion + 0.2f, 0.24f, 1f); + //TODO this actually isn't necessary + Draw.color(0, 0, 0, 0.15f); + Graphics.flushSurface(); + Draw.color(); + } - float len = 3; + private void drawAllTeams(boolean flying){ + for(Team team : Team.all){ + EntityGroup group = unitGroups[team.ordinal()]; - float w = (int) (len * 2 * finion) + 0.5f; + if(group.count(p -> p.isFlying() == flying) + + playerGroup.count(p -> p.isFlying() == flying && p.getTeam() == team) == 0 && flying) continue; - x -= 0.5f; - y += 0.5f; + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawUnder); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawUnder); - Lines.stroke(3f); - Draw.color(Color.SLATE); - Lines.line(x - len + 1, y, x + len + 1.5f, y); - Lines.stroke(1f); - Draw.color(Color.BLACK); - Lines.line(x - len + 1, y, x + len + 0.5f, y); - Draw.color(color); - if(w >= 1) - Lines.line(x - len + 1, y, x - len + w, y); - Draw.reset(); - } + Shaders.outline.color.set(team.color); + Shaders.mix.color.set(Color.WHITE); - public void setCameraScale(int amount){ - targetscale = amount; - clampScale(); - //scale up all surfaces in preparation for the zoom - if(Settings.getBool("pixelate")){ - for(Surface surface : Graphics.getSurfaces()){ - surface.setScale(targetscale); - } - } - } + Graphics.beginShaders(Shaders.outline); + Graphics.shader(Shaders.mix, true); + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead()); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team); + Graphics.shader(); + blocks.drawTeamBlocks(Layer.turret, team); + Graphics.endShaders(); - public void scaleCamera(int amount){ - setCameraScale(targetscale + amount); - } + drawAndInterpolate(unitGroups[team.ordinal()], u -> u.isFlying() == flying && !u.isDead(), Unit::drawOver); + drawAndInterpolate(playerGroup, p -> p.isFlying() == flying && p.getTeam() == team, Unit::drawOver); + } + } - public void clampScale(){ - targetscale = Mathf.clamp(targetscale, Math.round(Unit.dp.scl(2)), Math.round(Unit.dp.scl((5)))); - } + public void drawAndInterpolate(EntityGroup group){ + drawAndInterpolate(group, t -> true, DrawTrait::draw); + } + + public void drawAndInterpolate(EntityGroup group, Predicate toDraw){ + drawAndInterpolate(group, toDraw, DrawTrait::draw); + } + + public void drawAndInterpolate(EntityGroup group, Predicate toDraw, Consumer drawer){ + EntityDraw.drawWith(group, toDraw, t -> { + float lastx = t.getX(), lasty = t.getY(), lastrot = 0f; + + if(threads.doInterpolate() && threads.isEnabled() && t instanceof SolidTrait){ + SolidTrait s = (SolidTrait) t; + + lastrot = s.getRotation(); + + if(s.lastUpdated() != 0){ + float timeSinceUpdate = TimeUtils.timeSinceMillis(s.lastUpdated()); + float alpha = Math.min(timeSinceUpdate / s.updateSpacing(), 1f); + + tmpVector1.set(s.lastPosition().x, s.lastPosition().y) + .lerp(tmpVector2.set(lastx, lasty), alpha); + s.setRotation(Mathf.slerp(s.lastPosition().z, lastrot, alpha)); + + s.setX(tmpVector1.x); + s.setY(tmpVector1.y); + } + } + + //TODO extremely hacky + if(t instanceof Player && ((Player) t).getCarry() != null && ((Player) t).getCarry() instanceof Player && ((Player) ((Player) t).getCarry()).isLocal){ + ((Player) t).x = ((Player) t).getCarry().getX(); + ((Player) t).y = ((Player) t).getCarry().getY(); + } + + drawer.accept(t); + + t.setX(lastx); + t.setY(lasty); + + if(threads.doInterpolate() && threads.isEnabled()){ + + if(t instanceof SolidTrait){ + ((SolidTrait) t).setRotation(lastrot); + } + } + }); + } + + @Override + public void resize(int width, int height){ + super.resize(width, height); + for(Player player : players){ + control.input(player.playerIndex).resetCursor(); + } + camera.position.set(players[0].x, players[0].y, 0); + } + + @Override + public void dispose(){ + background.dispose(); + fog.dispose(); + } + + public Vector2 averagePosition(){ + avgPosition.setZero(); + + drawAndInterpolate(playerGroup, p -> p.isLocal, p -> { + avgPosition.add(p.x, p.y); + }); + + avgPosition.scl(1f / players.length); + return avgPosition; + } + + public FogRenderer fog(){ + return fog; + } + + public MinimapRenderer minimap(){ + return minimap; + } + + void drawPadding(){ + float vw = world.width() * tilesize; + float cw = camera.viewportWidth * camera.zoom; + float ch = camera.viewportHeight * camera.zoom; + if(vw < cw){ + batch.draw(background, + camera.position.x + vw / 2, + Mathf.round(camera.position.y - ch / 2, tilesize), + (cw - vw) / 2, + ch + tilesize, + 0, 0, + ((cw - vw) / 2 / tilesize), -ch / tilesize + 1); + + batch.draw(background, + camera.position.x - vw / 2, + Mathf.round(camera.position.y - ch / 2, tilesize), + -(cw - vw) / 2, + ch + tilesize, + 0, 0, + -((cw - vw) / 2 / tilesize), -ch / tilesize + 1); + } + } + + void drawDebug(){ + int rangex = (int) (Core.camera.viewportWidth / tilesize / 2), rangey = (int) (Core.camera.viewportHeight / tilesize / 2); + + for(int x = -rangex; x <= rangex; x++){ + for(int y = -rangey; y <= rangey; y++){ + int worldx = Mathf.scl(camera.position.x, tilesize) + x; + int worldy = Mathf.scl(camera.position.y, tilesize) + y; + + if(world.tile(worldx, worldy) == null) continue; + + float value = world.pathfinder().getDebugValue(worldx, worldy); + Draw.color(Color.PURPLE); + Draw.alpha((value % 10f) / 10f); + Lines.square(worldx * tilesize, worldy * tilesize, 4f); + } + } + + Draw.color(Color.ORANGE); + Draw.tcolor(Color.ORANGE); + + ObjectIntMap seen = new ObjectIntMap<>(); + + for(BlockFlag flag : BlockFlag.values()){ + for(Tile tile : world.indexer().getEnemy(Team.blue, flag)){ + int index = seen.getAndIncrement(tile, 0, 1); + Draw.tscl(0.125f); + Draw.text(flag.name(), tile.drawx(), tile.drawy() + tile.block().size * tilesize / 2f + 4 + index * 3); + Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f); + } + } + Draw.tscl(fontScale); + Draw.tcolor(); + + Draw.color(); + } + + public BlockRenderer getBlocks(){ + return blocks; + } + + public void setCameraScale(int amount){ + targetscale = amount; + clampScale(); + //scale up all surfaces in preparation for the zoom + for(Surface surface : Graphics.getSurfaces()){ + surface.setScale(targetscale); + } + } + + public void scaleCamera(int amount){ + setCameraScale(targetscale + amount); + } + + public void clampScale(){ + float s = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(1f); + targetscale = Mathf.clamp(targetscale, Math.round(s * 2), Math.round(s * 5)); + } } diff --git a/core/src/io/anuke/mindustry/core/ThreadHandler.java b/core/src/io/anuke/mindustry/core/ThreadHandler.java index 683443be57..22bc5ff2fe 100644 --- a/core/src/io/anuke/mindustry/core/ThreadHandler.java +++ b/core/src/io/anuke/mindustry/core/ThreadHandler.java @@ -1,54 +1,68 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Queue; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.entities.EntityGroup; -import io.anuke.ucore.entities.EntityGroup.ArrayContainer; import io.anuke.ucore.util.Log; import static io.anuke.mindustry.Vars.control; import static io.anuke.mindustry.Vars.logic; -public class ThreadHandler { - private final Array toRun = new Array<>(); +public class ThreadHandler{ + private final Queue toRun = new Queue<>(); private final ThreadProvider impl; + private final Object updateLock = new Object(); private float delta = 1f; - private long frame = 0; + private float smoothDelta = 1f; + private long frame = 0, lastDeltaUpdate; private float framesSinceUpdate; private boolean enabled; - - private final Object updateLock = new Object(); private boolean rendered = true; public ThreadHandler(ThreadProvider impl){ this.impl = impl; Timers.setDeltaProvider(() -> { - float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime()*60f; - return Math.min(Float.isNaN(result) ? 1f : result, 12f); + float result = impl.isOnThread() ? delta : Gdx.graphics.getDeltaTime() * 60f; + return Math.min(Float.isNaN(result) ? 1f : result, 15f); }); } public void run(Runnable r){ - if(enabled) { - synchronized (toRun) { - toRun.add(r); + if(enabled){ + synchronized(toRun){ + toRun.addLast(r); } }else{ r.run(); } } - public int getFPS(){ - return (int)(60/delta); + public void runGraphics(Runnable r){ + if(enabled){ + Gdx.app.postRunnable(r); + }else{ + r.run(); + } + } + + public void runDelay(Runnable r){ + if(enabled){ + synchronized(toRun){ + toRun.addLast(r); + } + }else{ + Gdx.app.postRunnable(r); + } + } + + public int getTPS(){ + return (int) (60 / smoothDelta); } public long getFrameID(){ - return frame; + return enabled ? frame : Gdx.graphics.getFrameId(); } public float getFramesSinceUpdate(){ @@ -61,85 +75,105 @@ public class ThreadHandler { framesSinceUpdate += Timers.delta(); - synchronized (updateLock) { + synchronized(updateLock){ rendered = true; impl.notify(updateLock); } } - public void setEnabled(boolean enabled){ - if(enabled){ - logic.doUpdate = false; - for(EntityGroup group : Entities.getAllGroups()){ - impl.switchContainer(group); - } - Timers.runTask(2f, () -> { - impl.start(this::runLogic); - this.enabled = true; - }); - }else{ - this.enabled = false; - impl.stop(); - for(EntityGroup group : Entities.getAllGroups()){ - group.setContainer(new ArrayContainer<>()); - } - Timers.runTask(2f, () -> { - logic.doUpdate = true; - }); - } - } - public boolean isEnabled(){ return enabled; } - private void runLogic(){ - try { - while (true) { - long time = TimeUtils.millis(); + public void setEnabled(boolean enabled){ + if(enabled){ + logic.doUpdate = false; + Timers.runTask(2f, () -> { + impl.start(this::runLogic); + this.enabled = true; + }); + }else{ + this.enabled = false; + impl.stop(); + Timers.runTask(2f, () -> { + logic.doUpdate = true; + }); + } + } - synchronized (toRun) { - for(Runnable r : toRun){ - r.run(); + public boolean doInterpolate(){ + return enabled && Gdx.graphics.getFramesPerSecond() - getTPS() > 20 && getTPS() < 30; + } + + public boolean isOnThread(){ + return impl.isOnThread(); + } + + private void runLogic(){ + try{ + while(true){ + long time = TimeUtils.nanoTime(); + + while(true){ + Runnable r; + synchronized(toRun){ + if(toRun.size > 0){ + r = toRun.removeFirst(); + }else{ + break; + } } - toRun.clear(); + + r.run(); } + logic.doUpdate = true; logic.update(); + logic.doUpdate = false; - long elapsed = TimeUtils.timeSinceMillis(time); - long target = (long) (1000 / 60f); + long elapsed = TimeUtils.nanosToMillis(TimeUtils.timeSinceNanos(time)); + long target = (long) ((1000) / 60f); - delta = Math.max(elapsed, target) / 1000f * 60f; - - if (elapsed < target) { + if(elapsed < target){ impl.sleep(target - elapsed); } - synchronized(updateLock) { - while(!rendered) { + synchronized(updateLock){ + while(!rendered){ impl.wait(updateLock); } rendered = false; } - frame ++; + long actuallyElapsed = TimeUtils.nanosToMillis(TimeUtils.timeSinceNanos(time)); + delta = Math.max(actuallyElapsed, target) / 1000f * 60f; + + if(TimeUtils.timeSinceMillis(lastDeltaUpdate) > 1000){ + lastDeltaUpdate = TimeUtils.millis(); + smoothDelta = delta; + } + + frame++; framesSinceUpdate = 0; } - } catch (InterruptedException ex) { + }catch(InterruptedException ex){ Log.info("Stopping logic thread."); - } catch (Throwable ex) { + }catch(Throwable ex){ control.setError(ex); } } - public interface ThreadProvider { + public interface ThreadProvider{ boolean isOnThread(); + void sleep(long ms) throws InterruptedException; + void start(Runnable run); + void stop(); + void wait(Object object) throws InterruptedException; + void notify(Object object); - void switchContainer(EntityGroup group); } } diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 9df38d4949..331f8002e5 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -2,265 +2,304 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; -import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.utils.Align; import io.anuke.mindustry.Vars; -import io.anuke.mindustry.mapeditor.MapEditorDialog; +import io.anuke.mindustry.editor.MapEditorDialog; +import io.anuke.mindustry.game.EventType.ResizeEvent; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.input.InputHandler; import io.anuke.mindustry.ui.dialogs.*; import io.anuke.mindustry.ui.fragments.*; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Settings; -import io.anuke.ucore.core.Timers; +import io.anuke.ucore.core.*; +import io.anuke.ucore.function.Callable; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.function.Listenable; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.modules.SceneModule; +import io.anuke.ucore.scene.Group; import io.anuke.ucore.scene.Skin; +import io.anuke.ucore.scene.actions.Actions; import io.anuke.ucore.scene.builders.build; import io.anuke.ucore.scene.ui.Dialog; import io.anuke.ucore.scene.ui.TextField; import io.anuke.ucore.scene.ui.TextField.TextFieldFilter; import io.anuke.ucore.scene.ui.TooltipManager; +import io.anuke.ucore.scene.ui.layout.Table; import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Mathf; import java.util.Locale; import static io.anuke.mindustry.Vars.control; +import static io.anuke.mindustry.Vars.players; +import static io.anuke.mindustry.Vars.threads; import static io.anuke.ucore.scene.actions.Actions.*; public class UI extends SceneModule{ - public AboutDialog about; - public RestartDialog restart; - public LevelDialog levels; - public LoadDialog load; - public DiscordDialog discord; - public JoinDialog join; - public HostDialog host; - public PausedDialog paused; - public SettingsMenuDialog settings; - public ControlsDialog controls; - public MapEditorDialog editor; - public LanguageDialog language; - public BansDialog bans; - public AdminsDialog admins; - public TraceDialog traces; - public RollbackDialog rollback; - public ChangelogDialog changelog; - - public final MenuFragment menufrag = new MenuFragment(); - public final ToolFragment toolfrag = new ToolFragment(); + public final MenuFragment menufrag = new MenuFragment(); public final HudFragment hudfrag = new HudFragment(); - public final PlacementFragment placefrag = new PlacementFragment(); public final ChatFragment chatfrag = new ChatFragment(); public final PlayerListFragment listfrag = new PlayerListFragment(); public final BackgroundFragment backfrag = new BackgroundFragment(); public final LoadingFragment loadfrag = new LoadingFragment(); - public final BlockConfigFragment configfrag = new BlockConfigFragment(); public final DebugFragment debugfrag = new DebugFragment(); + public AboutDialog about; + public RestartDialog restart; + public LevelDialog levels; + public MapsDialog maps; + public LoadDialog load; + public DiscordDialog discord; + public JoinDialog join; + public HostDialog host; + public PausedDialog paused; + public SettingsMenuDialog settings; + public ControlsDialog controls; + public MapEditorDialog editor; + public LanguageDialog language; + public BansDialog bans; + public AdminsDialog admins; + public TraceDialog traces; + public RollbackDialog rollback; + public ChangelogDialog changelog; + public LocalPlayerDialog localplayers; + public UnlocksDialog unlocks; + public ContentInfoDialog content; + private Locale lastLocale; - - public UI() { - Dialog.setShowAction(()-> sequence( - alpha(0f), - originCenter(), - moveToAligned(Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2, Align.center), - scaleTo(0.0f, 1f), - parallel( - scaleTo(1f, 1f, 0.1f, Interpolation.fade), - fadeIn(0.1f, Interpolation.fade) - ) - )); - - Dialog.setHideAction(()-> sequence( - parallel( - scaleTo(0.01f, 0.01f, 0.1f, Interpolation.fade), - fadeOut(0.1f, Interpolation.fade) - ) - )); - - TooltipManager.getInstance().animations = false; - - Settings.setErrorHandler(()-> Timers.run(1f, ()-> showError("[crimson]Failed to access local storage.\nSettings will not be saved."))); - - Settings.defaults("pixelate", true); - - Dialog.closePadR = -1; - Dialog.closePadT = 5; - - Colors.put("description", Color.WHITE); - Colors.put("turretinfo", Color.ORANGE); - Colors.put("iteminfo", Color.LIGHT_GRAY); - Colors.put("powerinfo", Color.YELLOW); - Colors.put("liquidinfo", Color.ROYAL); - Colors.put("craftinfo", Color.LIGHT_GRAY); - Colors.put("missingitems", Color.SCARLET); - Colors.put("health", Color.YELLOW); - Colors.put("healthstats", Color.SCARLET); - Colors.put("interact", Color.ORANGE); - Colors.put("accent", Color.valueOf("f4ba6e")); - Colors.put("place", Color.PURPLE); - Colors.put("placeInvalid", Color.RED); - Colors.put("placeRotate", Color.ORANGE); - Colors.put("break", Color.CORAL); - Colors.put("breakStart", Color.YELLOW); - Colors.put("breakInvalid", Color.RED); - } - @Override - protected void loadSkin(){ - skin = new Skin(Gdx.files.internal("ui/uiskin.json"), Core.atlas); - Mathf.each(font -> { - font.setUseIntegerPositions(false); - font.getData().setScale(Vars.fontscale); - font.getData().down += Unit.dp.scl(4f); - font.getData().lineHeight -= Unit.dp.scl(2f); - }, skin.font(), skin.getFont("default-font-chat"), skin.getFont("korean"), skin.getFont("trad-chinese")); - } + public UI(){ + Dialog.setShowAction(() -> sequence( + alpha(0f), + originCenter(), + moveToAligned(Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, Align.center), + scaleTo(0.0f, 1f), + parallel( + scaleTo(1f, 1f, 0.1f, Interpolation.fade), + fadeIn(0.1f, Interpolation.fade) + ) + )); - @Override - public synchronized void update(){ - if(Vars.debug && !Vars.showUI) return; + Dialog.setHideAction(() -> sequence( + parallel( + scaleTo(0.01f, 0.01f, 0.1f, Interpolation.fade), + fadeOut(0.1f, Interpolation.fade) + ) + )); - if(Graphics.drawing()) Graphics.end(); - - act(); + TooltipManager.getInstance().animations = false; - if(control.showCursor()) { - Draw.color(); + Settings.setErrorHandler(() -> Timers.run(1f, () -> showError("[crimson]Failed to access local storage.\nSettings will not be saved."))); - float scl = Unit.dp.scl(3f); + Dialog.closePadR = -1; + Dialog.closePadT = 5; - Graphics.begin(); - Draw.rect("controller-cursor", Graphics.mouse().x, Graphics.mouse().y, 16*scl, 16*scl); - Graphics.end(); - } - } + Colors.put("description", Palette.description); + Colors.put("turretinfo", Palette.turretinfo); + Colors.put("iteminfo", Palette.iteminfo); + Colors.put("powerinfo", Palette.powerinfo); + Colors.put("liquidinfo", Palette.liquidinfo); + Colors.put("craftinfo", Palette.craftinfo); + Colors.put("missingitems", Palette.missingitems); + Colors.put("health", Palette.health); + Colors.put("healthstats", Palette.healthstats); + Colors.put("interact", Palette.interact); + Colors.put("accent", Palette.accent); + Colors.put("place", Palette.place); + Colors.put("remove", Palette.remove); + Colors.put("placeRotate", Palette.placeRotate); + Colors.put("range", Palette.range); + Colors.put("power", Palette.power); + } - @Override - public void init(){ + @Override + protected void loadSkin(){ + skin = new Skin(Gdx.files.internal("ui/uiskin.json"), Core.atlas); + Mathf.each(font -> { + font.setUseIntegerPositions(false); + font.getData().setScale(Vars.fontScale); + font.getData().down += Unit.dp.scl(4f); + font.getData().lineHeight -= Unit.dp.scl(2f); + }, skin.font(), skin.getFont("default-font-chat"), skin.getFont("korean"), skin.getFont("trad-chinese")); + } - editor = new MapEditorDialog(); - controls = new ControlsDialog(); - restart = new RestartDialog(); - join = new JoinDialog(); - discord = new DiscordDialog(); - load = new LoadDialog(); - levels = new LevelDialog(); - language = new LanguageDialog(); - settings = new SettingsMenuDialog(); - paused = new PausedDialog(); - changelog = new ChangelogDialog(); - about = new AboutDialog(); - host = new HostDialog(); - bans = new BansDialog(); - admins = new AdminsDialog(); - traces = new TraceDialog(); - rollback = new RollbackDialog(); - - build.begin(scene); + @Override + public synchronized void update(){ + if(Vars.debug && !Vars.showUI) return; - backfrag.build(); - hudfrag.build(); - configfrag.build(); - menufrag.build(); - placefrag.build(); - toolfrag.build(); - chatfrag.build(); - listfrag.build(); - debugfrag.build(); - loadfrag.build(); + if(Graphics.drawing()) Graphics.end(); - build.end(); - } + act(); - @Override - public synchronized boolean hasMouse() { - return super.hasMouse(); - } + Graphics.begin(); - public Locale getLocale(){ - String loc = Settings.getString("locale"); - if(loc.equals("default")){ - return Locale.getDefault(); - }else{ - if(lastLocale == null || !lastLocale.toString().equals(loc)){ - if(loc.contains("_")){ - String[] split = loc.split("_"); - lastLocale = new Locale(split[0], split[1]); - }else{ - lastLocale = new Locale(loc); - } - } + for(int i = 0; i < players.length; i++){ + InputHandler input = control.input(i); - return lastLocale; - } - } + if(input.isCursorVisible()){ + Draw.color(); - public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ - new Dialog(title, "dialog"){{ - content().margin(30).add(text).padRight(6f); - TextField field = content().addField(def, t->{}).size(170f, 50f).get(); - field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c)); - Platform.instance.addDialog(field); - buttons().defaults().size(120, 54).pad(4); - buttons().addButton("$text.ok", () -> { - confirmed.accept(field.getText()); - hide(); - }).disabled(b -> field.getText().isEmpty()); - buttons().addButton("$text.cancel", this::hide); - }}.show(); - } + float scl = Unit.dp.scl(3f); - public void showTextInput(String title, String text, String def, Consumer confirmed){ - showTextInput(title, text, def, (field, c) -> true, confirmed); - } + Draw.rect("controller-cursor", input.getMouseX(), Gdx.graphics.getHeight() - input.getMouseY(), 16 * scl, 16 * scl); + } + } - public void showInfo(String info){ - new Dialog("$text.info.title", "dialog"){{ - content().margin(15).add(info).width(600f).get().setWrap(true); - buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); - }}.show(); - } + Graphics.end(); + Draw.color(); + } - public void showError(String text){ - new Dialog("$text.error.title", "dialog"){{ - content().margin(15).add(text); - buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); - }}.show(); - } + @Override + public void init(){ + editor = new MapEditorDialog(); + controls = new ControlsDialog(); + restart = new RestartDialog(); + join = new JoinDialog(); + discord = new DiscordDialog(); + load = new LoadDialog(); + levels = new LevelDialog(); + language = new LanguageDialog(); + settings = new SettingsMenuDialog(); + paused = new PausedDialog(); + changelog = new ChangelogDialog(); + about = new AboutDialog(); + host = new HostDialog(); + bans = new BansDialog(); + admins = new AdminsDialog(); + traces = new TraceDialog(); + rollback = new RollbackDialog(); + maps = new MapsDialog(); + localplayers = new LocalPlayerDialog(); + unlocks = new UnlocksDialog(); + content = new ContentInfoDialog(); - public void showConfirm(String title, String text, Listenable confirmed){ - FloatingDialog dialog = new FloatingDialog(title); - dialog.content().add(text).pad(4f); - dialog.buttons().defaults().size(200f, 54f).pad(2f); - dialog.buttons().addButton("$text.cancel", dialog::hide); - dialog.buttons().addButton("$text.ok", () -> { - dialog.hide(); - confirmed.listen(); - }); - dialog.keyDown(Keys.ESCAPE, dialog::hide); - dialog.keyDown(Keys.BACK, dialog::hide); - dialog.show(); - } + build.begin(scene); - public void showConfirmListen(String title, String text, Consumer listener){ - FloatingDialog dialog = new FloatingDialog(title); - dialog.content().add(text).pad(4f); - dialog.buttons().defaults().size(200f, 54f).pad(2f); - dialog.buttons().addButton("$text.cancel", () -> { - dialog.hide(); - listener.accept(true); - }); - dialog.buttons().addButton("$text.ok", () -> { - dialog.hide(); - listener.accept(true); - }); - dialog.show(); - } - + Group group = Core.scene.getRoot(); + + backfrag.build(group); + hudfrag.build(group); + menufrag.build(group); + chatfrag.container().build(group); + listfrag.build(group); + debugfrag.build(group); + loadfrag.build(group); + + build.end(); + } + + @Override + public boolean hasMouse(){ + return super.hasMouse(); + } + + @Override + public void resize(int width, int height){ + super.resize(width, height); + + Events.fire(ResizeEvent.class); + } + + public Locale getLocale(){ + String loc = Settings.getString("locale"); + if(loc.equals("default")){ + return Locale.getDefault(); + }else{ + if(lastLocale == null || !lastLocale.toString().equals(loc)){ + if(loc.contains("_")){ + String[] split = loc.split("_"); + lastLocale = new Locale(split[0], split[1]); + }else{ + lastLocale = new Locale(loc); + } + } + + return lastLocale; + } + } + + public void loadAnd(Callable call){ + loadAnd("$text.loading", call); + } + + public void loadAnd(String text, Callable call){ + loadfrag.show(text); + Timers.runTask(7f, () -> { + call.run(); + loadfrag.hide(); + }); + } + + public void loadLogic(Callable call){ + loadLogic("$text.loading", call); + } + + public void loadLogic(String text, Callable call){ + loadfrag.show(); + Timers.runTask(7f, () -> { + threads.run(() -> { + call.run(); + threads.runGraphics(loadfrag::hide); + }); + }); + } + + public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer confirmed){ + new Dialog(title, "dialog"){{ + content().margin(30).add(text).padRight(6f); + TextField field = content().addField(def, t -> { + }).size(170f, 50f).get(); + field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c)); + Platform.instance.addDialog(field); + buttons().defaults().size(120, 54).pad(4); + buttons().addButton("$text.ok", () -> { + confirmed.accept(field.getText()); + hide(); + }).disabled(b -> field.getText().isEmpty()); + buttons().addButton("$text.cancel", this::hide); + }}.show(); + } + + public void showTextInput(String title, String text, String def, Consumer confirmed){ + showTextInput(title, text, def, (field, c) -> true, confirmed); + } + + public void showInfoFade(String info){ + Table table = new Table(); + table.setFillParent(true); + table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.removeActor()); + table.top().add(info).padTop(8); + Core.scene.add(table); + } + + public void showInfo(String info){ + new Dialog("$text.info.title", "dialog"){{ + getCell(content()).growX(); + content().margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center); + buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); + }}.show(); + } + + public void showError(String text){ + new Dialog("$text.error.title", "dialog"){{ + content().margin(15).add(text).width(400f).wrap().get().setAlignment(Align.center, Align.center); + buttons().addButton("$text.ok", this::hide).size(90, 50).pad(4); + }}.show(); + } + + public void showConfirm(String title, String text, Listenable confirmed){ + FloatingDialog dialog = new FloatingDialog(title); + dialog.content().add(text).width(400f).wrap().pad(4f).get().setAlignment(Align.center, Align.center); + dialog.buttons().defaults().size(200f, 54f).pad(2f); + dialog.buttons().addButton("$text.cancel", dialog::hide); + dialog.buttons().addButton("$text.ok", () -> { + dialog.hide(); + confirmed.listen(); + }); + dialog.keyDown(Keys.ESCAPE, dialog::hide); + dialog.keyDown(Keys.BACK, dialog::hide); + dialog.show(); + } } diff --git a/core/src/io/anuke/mindustry/core/World.java b/core/src/io/anuke/mindustry/core/World.java index 18ba128b32..0a6a0300de 100644 --- a/core/src/io/anuke/mindustry/core/World.java +++ b/core/src/io/anuke/mindustry/core/World.java @@ -2,358 +2,380 @@ package io.anuke.mindustry.core; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.ai.Pathfind; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.game.SpawnPoint; +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.ai.BlockIndexer; +import io.anuke.mindustry.ai.Pathfinder; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.game.EventType.TileChangeEvent; +import io.anuke.mindustry.game.EventType.WorldLoadEvent; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.MapIO; +import io.anuke.mindustry.io.MapMeta; import io.anuke.mindustry.io.Maps; -import io.anuke.mindustry.world.*; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.DistributionBlocks; -import io.anuke.mindustry.world.blocks.ProductionBlocks; -import io.anuke.mindustry.world.blocks.WeaponBlocks; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.mapgen.WorldGenerator; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityPhysics; import io.anuke.ucore.modules.Module; +import io.anuke.ucore.util.Log; import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.ThreadArray; import io.anuke.ucore.util.Tmp; -import static io.anuke.mindustry.Vars.control; -import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.*; public class World extends Module{ - private int seed; - - private Map currentMap; - private Tile[][] tiles; - private Pathfind pathfind = new Pathfind(); - private Maps maps = new Maps(); - private Tile core; - private Array spawns = new Array<>(); + private int seed; - private Tile[] temptiles = new Tile[4]; - - public World(){ - maps.loadMaps(); - currentMap = maps.getMap(0); - } - - @Override - public void dispose(){ - maps.dispose(); - } + private Map currentMap; + private Tile[][] tiles; + private Pathfinder pathfinder = new Pathfinder(); + private BlockIndexer indexer = new BlockIndexer(); + private Maps maps = new Maps(); - public Array getSpawns(){ - return spawns; - } + private Array tempTiles = new ThreadArray<>(); + private boolean generating, invalidMap; - public Tile getCore(){ - return core; - } - - public Maps maps(){ - return maps; - } - - public Pathfind pathfinder(){ - return pathfind; - } + public World(){ + maps.load(); + } - public float getSpawnX(){ - return core.worldx(); - } + @Override + public void dispose(){ + maps.dispose(); + } - public float getSpawnY(){ - return core.worldy() - tilesize*2; - } - - public boolean solid(int x, int y){ - Tile tile = tile(x, y); - - return tile == null || tile.solid(); - } - - public boolean passable(int x, int y){ - Tile tile = tile(x, y); - - return tile != null && tile.passable(); - } - - public boolean wallSolid(int x, int y){ - Tile tile = tile(x, y); - return tile == null || tile.block().solid; - } - - public boolean isAccessible(int x, int y){ - return !wallSolid(x, y-1) || !wallSolid(x, y+1) || !wallSolid(x-1, y) ||!wallSolid(x+1, y); - } - - public boolean blends(Block block, int x, int y){ - return !floorBlends(x, y-1, block) || !floorBlends(x, y+1, block) - || !floorBlends(x-1, y, block) ||!floorBlends(x+1, y, block); - } - - public boolean floorBlends(int x, int y, Block block){ - Tile tile = tile(x, y); - return tile == null || tile.floor().id <= block.id; - } - - public Map getMap(){ - return currentMap; - } - - public int width(){ - return currentMap.getWidth(); - } - - public int height(){ - return currentMap.getHeight(); - } + public Maps maps(){ + return maps; + } - public Tile tile(int packed){ - return tile(packed % width(), packed / width()); - } - - public Tile tile(int x, int y){ - if(tiles == null){ - return null; - } - if(!Mathf.inBounds(x, y, tiles)) return null; - return tiles[x][y]; - } - - public Tile tileWorld(float x, float y){ - return tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize)); - } + public BlockIndexer indexer(){ + return indexer; + } - public int toTile(float coord){ - return Mathf.scl2(coord, tilesize); - } - - public Tile[][] getTiles(){ - return tiles; - } - - private void createTiles(){ - for(int x = 0; x < tiles.length; x ++){ - for(int y = 0; y < tiles[0].length; y ++){ - if(tiles[x][y] == null){ - tiles[x][y] = new Tile(x, y, Blocks.stone); - } - } - } - } - - private void clearTileEntities(){ - for(int x = 0; x < tiles.length; x ++){ - for(int y = 0; y < tiles[0].length; y ++){ - if(tiles[x][y] != null && tiles[x][y].entity != null){ - tiles[x][y].entity.remove(); - } - } - } - } - - public void loadMap(Map map){ - loadMap(map, MathUtils.random(0, 99999)); - } - - public void loadMap(Map map, int seed){ - currentMap = map; - - if(tiles != null){ - clearTileEntities(); - - if(tiles.length != map.getWidth() || tiles[0].length != map.getHeight()){ - tiles = new Tile[map.getWidth()][map.getHeight()]; - } - - createTiles(); - }else{ - tiles = new Tile[map.getWidth()][map.getHeight()]; - - createTiles(); - } - - spawns.clear(); - - Entities.resizeTree(0, 0, map.getWidth() * tilesize, map.getHeight() * tilesize); - - this.seed = seed; - - core = WorldGenerator.generate(map.pixmap, tiles, spawns); + public Pathfinder pathfinder(){ + return pathfinder; + } - Placement.placeBlock(core.x, core.y, ProductionBlocks.core, 0, false, false); - - if(!map.name.equals("tutorial")){ - setDefaultBlocks(); - }else{ - control.tutorial().setDefaultBlocks(core.x, core.y); - } - - pathfind.resetPaths(); - } - - void setDefaultBlocks(){ - int x = core.x, y = core.y; - int flip = Mathf.sign(!currentMap.flipBase); - int fr = currentMap.flipBase ? 2 : 0; - - set(x, y-2*flip, DistributionBlocks.conveyor, 1 + fr); - set(x, y-3*flip, DistributionBlocks.conveyor, 1 + fr); - - for(int i = 0; i < 2; i ++){ - int d = Mathf.sign(i-0.5f); - - set(x+2*d, y-2*flip, ProductionBlocks.stonedrill, d); - set(x+2*d, y-1*flip, DistributionBlocks.conveyor, 1 + fr); - set(x+2*d, y, DistributionBlocks.conveyor, 1 + fr); - set(x+2*d, y+1*flip, WeaponBlocks.doubleturret, 0 + fr); - - set(x+1*d, y-3*flip, DistributionBlocks.conveyor, 2*d); - set(x+2*d, y-3*flip, DistributionBlocks.conveyor, 2*d); - set(x+2*d, y-4*flip, DistributionBlocks.conveyor, 1 + fr); - set(x+2*d, y-5*flip, DistributionBlocks.conveyor, 1 + fr); - - set(x+3*d, y-5*flip, ProductionBlocks.stonedrill, 0 + fr); - set(x+3*d, y-4*flip, ProductionBlocks.stonedrill, 0 + fr); - set(x+3*d, y-3*flip, ProductionBlocks.stonedrill, 0 + fr); - } - } - - void set(int x, int y, Block type, int rot){ - if(!Mathf.inBounds(x, y, tiles)){ - return; - } - if(type == ProductionBlocks.stonedrill){ - tiles[x][y].setFloor(Blocks.stone); - } - tiles[x][y].setBlock(type, rot); - } - - public int getSeed(){ - return seed; - } + public boolean isInvalidMap(){ + return invalidMap; + } - public void removeBlock(Tile tile){ - if(!tile.block().isMultiblock() && !tile.isLinked()){ - tile.setBlock(Blocks.air); - }else{ - Tile target = tile.target(); - Array removals = target.getLinkedTiles(); - for(Tile toremove : removals){ - //note that setting a new block automatically unlinks it - if(toremove != null) toremove.setBlock(Blocks.air); - } - } - } - - public TileEntity findTileTarget(float x, float y, Tile tile, float range, boolean damaged){ - Entity closest = null; - float dst = 0; - - int rad = (int)(range/tilesize)+1; - int tilex = Mathf.scl2(x, tilesize); - int tiley = Mathf.scl2(y, tilesize); - - for(int rx = -rad; rx <= rad; rx ++){ - for(int ry = -rad; ry <= rad; ry ++){ - Tile other = tile(rx+tilex, ry+tiley); - - if(other != null && other.getLinked() != null){ - other = other.getLinked(); - } - - if(other == null || other.entity == null || (tile != null && other.entity == tile.entity)) continue; - - TileEntity e = other.entity; - - if(damaged && e.health >= e.tile.block().health) - continue; - - float ndst = Vector2.dst(x, y, e.x, e.y); - if(ndst < range && (closest == null || ndst < dst)){ - dst = ndst; - closest = e; - } - } - } + public boolean solid(int x, int y){ + Tile tile = tile(x, y); - return (TileEntity) closest; - } + return tile == null || tile.solid(); + } - /**Raycast, but with world coordinates.*/ - public GridPoint2 raycastWorld(float x, float y, float x2, float y2){ - return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize), - Mathf.scl2(x2, tilesize), Mathf.scl2(y2, tilesize)); - } - - /** - * Input is in block coordinates, not world coordinates. - * @return null if no collisions found, block position otherwise.*/ - public GridPoint2 raycast(int x0f, int y0f, int x1, int y1){ - int x0 = x0f; - int y0 = y0f; - int dx = Math.abs(x1 - x0); - int dy = Math.abs(y1 - y0); + public boolean passable(int x, int y){ + Tile tile = tile(x, y); - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; + return tile != null && tile.passable(); + } - int err = dx - dy; - int e2; - while(true){ + public boolean wallSolid(int x, int y){ + Tile tile = tile(x, y); + return tile == null || tile.block().solid; + } - if(!passable(x0, y0)){ - return Tmp.g1.set(x0, y0); - } - if(x0 == x1 && y0 == y1) break; + public boolean isAccessible(int x, int y){ + return !wallSolid(x, y - 1) || !wallSolid(x, y + 1) || !wallSolid(x - 1, y) || !wallSolid(x + 1, y); + } - e2 = 2 * err; - if(e2 > -dy){ - err = err - dy; - x0 = x0 + sx; - } + public boolean floorBlends(int x, int y, Block block){ + Tile tile = tile(x, y); + return tile == null || tile.floor().id <= block.id; + } - if(e2 < dx){ - err = err + dx; - y0 = y0 + sy; - } - } - return null; - } + public Map getMap(){ + return currentMap; + } - public void raycastEach(int x0f, int y0f, int x1, int y1, Raycaster cons){ - int x0 = x0f; - int y0 = y0f; - int dx = Math.abs(x1 - x0); - int dy = Math.abs(y1 - y0); + public void setMap(Map map){ + this.currentMap = map; + } - int sx = x0 < x1 ? 1 : -1; - int sy = y0 < y1 ? 1 : -1; + public int width(){ + return tiles == null ? 0 : tiles.length; + } - int err = dx - dy; - int e2; - while(true){ + public int height(){ + return tiles == null ? 0 : tiles[0].length; + } - if(cons.accept(x0, y0)) break; - if(x0 == x1 && y0 == y1) break; + public int toPacked(int x, int y){ + return x + y * width(); + } - e2 = 2 * err; - if(e2 > -dy){ - err = err - dy; - x0 = x0 + sx; - } + public Tile tile(int packed){ + return tiles == null ? null : tile(packed % width(), packed / width()); + } - if(e2 < dx){ - err = err + dx; - y0 = y0 + sy; - } - } - } + public Tile tile(int x, int y){ + if(tiles == null){ + return null; + } + if(!Mathf.inBounds(x, y, tiles)) return null; + return tiles[x][y]; + } - public interface Raycaster{ - boolean accept(int x, int y); - } + public Tile rawTile(int x, int y){ + return tiles[x][y]; + } + + public Tile tileWorld(float x, float y){ + return tile(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize)); + } + + public int toTile(float coord){ + return Mathf.scl2(coord, tilesize); + } + + public Tile[][] getTiles(){ + return tiles; + } + + private void clearTileEntities(){ + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + if(tiles[x][y] != null && tiles[x][y].entity != null){ + tiles[x][y].entity.remove(); + } + } + } + } + + /** + * Resizes the tile array to the specified size and returns the resulting tile array. + * Only use for loading saves! + */ + public Tile[][] createTiles(int width, int height){ + if(tiles != null){ + clearTileEntities(); + + if(tiles.length != width || tiles[0].length != height){ + tiles = new Tile[width][height]; + } + }else{ + tiles = new Tile[width][height]; + } + + return tiles; + } + + /** + * Call to signify the beginning of map loading. + * TileChangeEvents will not be fired until endMapLoad(). + */ + public void beginMapLoad(){ + generating = true; + } + + /** + * Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world. + * A WorldLoadEvent will be fire. + */ + public void endMapLoad(){ + for(int x = 0; x < tiles.length; x++){ + for(int y = 0; y < tiles[0].length; y++){ + tiles[x][y].updateOcclusion(); + + if(tiles[x][y].entity != null){ + tiles[x][y].entity.updateProximity(); + } + } + } + + EntityPhysics.resizeTree(0, 0, tiles.length * tilesize, tiles[0].length * tilesize); + + generating = false; + Events.fire(WorldLoadEvent.class); + } + + /** + * Loads up a procedural map. This does not call play(), but calls reset(). + */ + public void loadProceduralMap(){ + Timers.mark(); + Timers.mark(); + + logic.reset(); + + beginMapLoad(); + + int width = 400, height = 400; + + Tile[][] tiles = createTiles(width, height); + + Map map = new Map("Generated Map", new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); + setMap(map); + + EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); + + Timers.mark(); + WorldGenerator.generateMap(tiles, Mathf.random(9999999)); + Log.info("Time to generate base map: {0}", Timers.elapsed()); + + Log.info("Time to generate fully without additional events: {0}", Timers.elapsed()); + + endMapLoad(); + + Log.info("Full time to generate: {0}", Timers.elapsed()); + } + + public void loadMap(Map map){ + loadMap(map, MathUtils.random(0, 999999)); + } + + public void loadMap(Map map, int seed){ + beginMapLoad(); + this.currentMap = map; + this.seed = seed; + + int width = map.meta.width, height = map.meta.height; + + createTiles(width, height); + + EntityPhysics.resizeTree(0, 0, width * tilesize, height * tilesize); + + WorldGenerator.loadTileData(tiles, MapIO.readTileData(map, true), map.meta.hasOreGen(), seed); + + if(!headless && state.teams.get(players[0].getTeam()).cores.size == 0){ + ui.showError("$text.map.nospawn"); + threads.runDelay(() -> state.set(State.menu)); + invalidMap = true; + }else{ + invalidMap = false; + } + + endMapLoad(); + } + + public int getSeed(){ + return seed; + } + + public void notifyChanged(Tile tile){ + if(!generating){ + threads.runDelay(() -> Events.fire(TileChangeEvent.class, tile)); + } + } + + public void removeBlock(Tile tile){ + if(!tile.block().isMultiblock() && !tile.isLinked()){ + tile.setBlock(Blocks.air); + }else{ + Tile target = tile.target(); + Array removals = target.getLinkedTiles(tempTiles); + for(Tile toremove : removals){ + //note that setting a new block automatically unlinks it + if(toremove != null) toremove.setBlock(Blocks.air); + } + } + } + + public void setBlock(Tile tile, Block block, Team team){ + tile.setBlock(block); + if(block.isMultiblock()){ + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + int worldx = dx + offsetx + tile.x; + int worldy = dy + offsety + tile.y; + if(!(worldx == tile.x && worldy == tile.y)){ + Tile toplace = world.tile(worldx, worldy); + if(toplace != null){ + toplace.setLinked((byte) (dx + offsetx), (byte) (dy + offsety)); + toplace.setTeam(team); + } + } + } + } + } + } + + /** + * Raycast, but with world coordinates. + */ + public GridPoint2 raycastWorld(float x, float y, float x2, float y2){ + return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize), + Mathf.scl2(x2, tilesize), Mathf.scl2(y2, tilesize)); + } + + /** + * Input is in block coordinates, not world coordinates. + * + * @return null if no collisions found, block position otherwise. + */ + public GridPoint2 raycast(int x0f, int y0f, int x1, int y1){ + int x0 = x0f; + int y0 = y0f; + int dx = Math.abs(x1 - x0); + int dy = Math.abs(y1 - y0); + + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + + int err = dx - dy; + int e2; + while(true){ + + if(!passable(x0, y0)){ + return Tmp.g1.set(x0, y0); + } + if(x0 == x1 && y0 == y1) break; + + e2 = 2 * err; + if(e2 > -dy){ + err = err - dy; + x0 = x0 + sx; + } + + if(e2 < dx){ + err = err + dx; + y0 = y0 + sy; + } + } + return null; + } + + public void raycastEach(int x0f, int y0f, int x1, int y1, Raycaster cons){ + int x0 = x0f; + int y0 = y0f; + int dx = Math.abs(x1 - x0); + int dy = Math.abs(y1 - y0); + + int sx = x0 < x1 ? 1 : -1; + int sy = y0 < y1 ? 1 : -1; + + int err = dx - dy; + int e2; + while(true){ + + if(cons.accept(x0, y0)) break; + if(x0 == x1 && y0 == y1) break; + + e2 = 2 * err; + if(e2 > -dy){ + err = err - dy; + x0 = x0 + sx; + } + + if(e2 < dx){ + err = err + dx; + y0 = y0 + sy; + } + } + } + + public interface Raycaster{ + boolean accept(int x, int y); + } } diff --git a/core/src/io/anuke/mindustry/editor/DrawOperation.java b/core/src/io/anuke/mindustry/editor/DrawOperation.java new file mode 100755 index 0000000000..421a34bd6c --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/DrawOperation.java @@ -0,0 +1,72 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntSet; +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.io.MapTileData.TileDataMarker; +import io.anuke.ucore.util.Bits; + +public class DrawOperation{ + /** + * Data to apply operation to. + */ + private MapTileData data; + /** + * List of per-tile operations that occurred. + */ + private Array operations = new Array<>(); + /** + * Checks for duplicate operations, useful for brushes. + */ + private IntSet checks = new IntSet(); + + public DrawOperation(MapTileData data){ + this.data = data; + } + + public boolean isEmpty(){ + return operations.size == 0; + } + + public boolean checkDuplicate(short x, short y){ + int i = Bits.packInt(x, y); + if(checks.contains(i)) return true; + + checks.add(i); + return false; + } + + public void addOperation(TileOperation op){ + operations.add(op); + } + + public void undo(MapEditor editor){ + for(int i = operations.size - 1; i >= 0; i--){ + TileOperation op = operations.get(i); + data.position(op.x, op.y); + data.write(op.from); + editor.renderer().updatePoint(op.x, op.y); + } + } + + public void redo(MapEditor editor){ + for(TileOperation op : operations){ + data.position(op.x, op.y); + data.write(op.to); + editor.renderer().updatePoint(op.x, op.y); + } + } + + public static class TileOperation{ + public short x, y; + public TileDataMarker from; + public TileDataMarker to; + + public TileOperation(short x, short y, TileDataMarker from, TileDataMarker to){ + this.x = x; + this.y = y; + this.from = from; + this.to = to; + } + } +} diff --git a/core/src/io/anuke/mindustry/editor/EditorTool.java b/core/src/io/anuke/mindustry/editor/EditorTool.java new file mode 100644 index 0000000000..70c69897e1 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/EditorTool.java @@ -0,0 +1,143 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.IntSet; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.io.MapTileData.DataPosition; +import io.anuke.mindustry.io.MapTileData.TileDataMarker; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.ucore.util.Bits; + +import static io.anuke.mindustry.Vars.ui; + +public enum EditorTool{ + pick{ + public void touched(MapEditor editor, int x, int y){ + byte bf = editor.getMap().read(x, y, DataPosition.floor); + byte bw = editor.getMap().read(x, y, DataPosition.wall); + byte link = editor.getMap().read(x, y, DataPosition.link); + + if(link != 0){ + x -= (Bits.getLeftByte(link) - 8); + y -= (Bits.getRightByte(link) - 8); + bf = editor.getMap().read(x, y, DataPosition.floor); + bw = editor.getMap().read(x, y, DataPosition.wall); + } + + Block block = Block.getByID(bw == 0 ? bf : bw); + editor.setDrawBlock(block); + ui.editor.updateSelectedBlock(); + } + }, + pencil{ + { + edit = true; + draggable = true; + } + + @Override + public void touched(MapEditor editor, int x, int y){ + editor.draw(x, y); + } + }, + eraser{ + { + edit = true; + draggable = true; + } + + @Override + public void touched(MapEditor editor, int x, int y){ + editor.draw(x, y, Blocks.air); + } + }, + elevation{ + { + edit = true; + draggable = true; + } + + @Override + public void touched(MapEditor editor, int x, int y){ + editor.elevate(x, y); + } + }, + line{ + { + + } + }, + fill{ + { + edit = true; + } + + public void touched(MapEditor editor, int x, int y){ + if(editor.getDrawBlock().isMultiblock()){ + //don't fill multiblocks, thanks + pencil.touched(editor, x, y); + return; + } + + boolean floor = editor.getDrawBlock() instanceof Floor; + + byte bf = editor.getMap().read(x, y, DataPosition.floor); + byte bw = editor.getMap().read(x, y, DataPosition.wall); + boolean synth = editor.getDrawBlock().synthetic(); + byte brt = Bits.packByte((byte) editor.getDrawRotation(), (byte) editor.getDrawTeam().ordinal()); + + byte dest = floor ? bf : bw; + byte draw = (byte) editor.getDrawBlock().id; + + int width = editor.getMap().width(); + int height = editor.getMap().height(); + + IntSet set = new IntSet(); + IntArray points = new IntArray(); + points.add(asInt(x, y, editor.getMap().width())); + + while(points.size != 0){ + int pos = points.pop(); + int px = pos % width; + int py = pos / width; + set.add(pos); + + byte nbf = editor.getMap().read(px, py, DataPosition.floor); + byte nbw = editor.getMap().read(px, py, DataPosition.wall); + + if((floor ? nbf : nbw) == dest){ + TileDataMarker prev = editor.getPrev(px, py, false); + + if(floor){ + editor.getMap().write(px, py, DataPosition.floor, draw); + }else{ + editor.getMap().write(px, py, DataPosition.wall, draw); + } + + if(synth){ + editor.getMap().write(px, py, DataPosition.rotationTeam, brt); + } + + if(px > 0 && !set.contains(asInt(px - 1, py, width))) points.add(asInt(px - 1, py, width)); + if(py > 0 && !set.contains(asInt(px, py - 1, width))) points.add(asInt(px, py - 1, width)); + if(px < width - 1 && !set.contains(asInt(px + 1, py, width))) points.add(asInt(px + 1, py, width)); + if(py < height - 1 && !set.contains(asInt(px, py + 1, width))) points.add(asInt(px, py + 1, width)); + + editor.onWrite(px, py, prev); + } + } + } + + int asInt(int x, int y, int width){ + return x + y * width; + } + }, + zoom; + + boolean edit, draggable; + + public void touched(MapEditor editor, int x, int y){ + + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapEditor.java b/core/src/io/anuke/mindustry/editor/MapEditor.java new file mode 100644 index 0000000000..0f61541c3f --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapEditor.java @@ -0,0 +1,282 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.editor.DrawOperation.TileOperation; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.io.MapTileData.DataPosition; +import io.anuke.mindustry.io.MapTileData.TileDataMarker; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.ucore.util.Bits; +import io.anuke.ucore.util.Mathf; + +public class MapEditor{ + public static final int minMapSize = 128, maxMapSize = 512; + public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15}; + + private MapTileData map; + private ObjectMap tags = new ObjectMap<>(); + private MapRenderer renderer = new MapRenderer(this); + + private int brushSize = 1; + private byte elevation; + private int rotation; + private Block drawBlock = Blocks.stone; + private Team drawTeam = Team.none; + + public MapEditor(){ + + } + + public MapTileData getMap(){ + return map; + } + + public ObjectMap getTags(){ + return tags; + } + + public void beginEdit(MapTileData map, ObjectMap tags, boolean clear){ + this.map = map; + this.brushSize = 1; + this.tags = tags; + + if(clear){ + for(int x = 0; x < map.width(); x++){ + for(int y = 0; y < map.height(); y++){ + map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + } + } + } + + drawBlock = Blocks.stone; + renderer.resize(map.width(), map.height()); + } + + public byte getDrawElevation(){ + return elevation; + } + + public void setDrawElevation(int elevation){ + this.elevation = (byte) elevation; + } + + public int getDrawRotation(){ + return rotation; + } + + public void setDrawRotation(int rotation){ + this.rotation = rotation; + } + + public Team getDrawTeam(){ + return drawTeam; + } + + public void setDrawTeam(Team team){ + this.drawTeam = team; + } + + public Block getDrawBlock(){ + return drawBlock; + } + + public void setDrawBlock(Block block){ + this.drawBlock = block; + } + + public int getBrushSize(){ + return brushSize; + } + + public void setBrushSize(int size){ + this.brushSize = size; + } + + public void draw(int x, int y){ + draw(x, y, drawBlock); + } + + public void draw(int x, int y, Block drawBlock){ + if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ + return; + } + + byte writeID = (byte) drawBlock.id; + byte partID = (byte) Blocks.blockpart.id; + byte rotationTeam = Bits.packByte(drawBlock.rotate ? (byte) rotation : 0, drawBlock.synthetic() ? (byte) drawTeam.ordinal() : 0); + + boolean isfloor = drawBlock instanceof Floor && drawBlock != Blocks.air; + + if(drawBlock.isMultiblock()){ + x = Mathf.clamp(x, (drawBlock.size-1)/2, map.width() - drawBlock.size/2 - 1); + y = Mathf.clamp(y, (drawBlock.size-1)/2, map.height() - drawBlock.size/2 - 1); + + int offsetx = -(drawBlock.size - 1) / 2; + int offsety = -(drawBlock.size - 1) / 2; + + for(int i = 0; i < 2; i++){ + for(int dx = 0; dx < drawBlock.size; dx++){ + for(int dy = 0; dy < drawBlock.size; dy++){ + int worldx = dx + offsetx + x; + int worldy = dy + offsety + y; + + if(Mathf.inBounds(worldx, worldy, map.width(), map.height())){ + TileDataMarker prev = getPrev(worldx, worldy, false); + + if(i == 1){ + map.write(worldx, worldy, DataPosition.wall, partID); + map.write(worldx, worldy, DataPosition.rotationTeam, rotationTeam); + map.write(worldx, worldy, DataPosition.link, Bits.packByte((byte) (dx + offsetx + 8), (byte) (dy + offsety + 8))); + }else{ + byte link = map.read(worldx, worldy, DataPosition.link); + byte block = map.read(worldx, worldy, DataPosition.wall); + + if(link != 0){ + removeLinked(worldx - (Bits.getLeftByte(link) - 8), worldy - (Bits.getRightByte(link) - 8)); + }else if(Block.getByID(block).isMultiblock()){ + removeLinked(worldx, worldy); + } + } + + onWrite(worldx, worldy, prev); + } + } + } + } + + TileDataMarker prev = getPrev(x, y, false); + + map.write(x, y, DataPosition.wall, writeID); + map.write(x, y, DataPosition.link, (byte) 0); + map.write(x, y, DataPosition.rotationTeam, rotationTeam); + + onWrite(x, y, prev); + }else{ + + for(int rx = -brushSize; rx <= brushSize; rx++){ + for(int ry = -brushSize; ry <= brushSize; ry++){ + if(Mathf.dst(rx, ry) <= brushSize - 0.5f){ + int wx = x + rx, wy = y + ry; + + if(wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()){ + continue; + } + + TileDataMarker prev = getPrev(wx, wy, true); + + if(!isfloor){ + byte link = map.read(wx, wy, DataPosition.link); + + if(link != 0){ + removeLinked(wx - (Bits.getLeftByte(link) - 8), wy - (Bits.getRightByte(link) - 8)); + } + } + + if(isfloor){ + map.write(wx, wy, DataPosition.floor, writeID); + map.write(wx, wy, DataPosition.elevation, elevation); + }else{ + map.write(wx, wy, DataPosition.wall, writeID); + map.write(wx, wy, DataPosition.link, (byte) 0); + map.write(wx, wy, DataPosition.rotationTeam, rotationTeam); + } + + onWrite(x + rx, y + ry, prev); + } + } + } + } + } + + public void elevate(int x, int y){ + if(x < 0 || y < 0 || x >= map.width() || y >= map.height()){ + return; + } + + for(int rx = -brushSize; rx <= brushSize; rx++){ + for(int ry = -brushSize; ry <= brushSize; ry++){ + if(Mathf.dst(rx, ry) <= brushSize - 0.5f){ + int wx = x + rx, wy = y + ry; + + if(wx < 0 || wy < 0 || wx >= map.width() || wy >= map.height()){ + continue; + } + + TileDataMarker prev = getPrev(wx, wy, true); + + map.write(wx, wy, DataPosition.elevation, elevation); + + onWrite(x + rx, y + ry, prev); + } + } + } + } + + private void removeLinked(int x, int y){ + Block block = Block.getByID(map.read(x, y, DataPosition.wall)); + + int offsetx = -(block.size - 1) / 2; + int offsety = -(block.size - 1) / 2; + for(int dx = 0; dx < block.size; dx++){ + for(int dy = 0; dy < block.size; dy++){ + int worldx = x + dx + offsetx, worldy = y + dy + offsety; + if(Mathf.inBounds(worldx, worldy, map.width(), map.height())){ + TileDataMarker prev = getPrev(worldx, worldy, false); + + map.write(worldx, worldy, DataPosition.link, (byte) 0); + map.write(worldx, worldy, DataPosition.rotationTeam, (byte) 0); + map.write(worldx, worldy, DataPosition.wall, (byte) 0); + + onWrite(worldx, worldy, prev); + } + } + } + } + + boolean checkDupes(int x, int y){ + return Vars.ui.editor.getView().checkForDuplicates((short) x, (short) y); + } + + void onWrite(int x, int y, TileDataMarker previous){ + if(previous == null){ + renderer.updatePoint(x, y); + return; + } + + TileDataMarker current = map.new TileDataMarker(); + map.position(x, y); + map.read(current); + + Vars.ui.editor.getView().addTileOp(new TileOperation((short) x, (short) y, previous, current)); + renderer.updatePoint(x, y); + } + + TileDataMarker getPrev(int x, int y, boolean checkDupes){ + if(checkDupes && checkDupes(x, y)){ + return null; + }else{ + TileDataMarker marker = map.newDataMarker(); + map.position(x, y); + map.read(marker); + return marker; + } + } + + public MapRenderer renderer(){ + return renderer; + } + + public void resize(int width, int height){ + map = new MapTileData(width, height); + for(int x = 0; x < map.width(); x++){ + for(int y = 0; y < map.height(); y++){ + map.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + } + } + renderer.resize(width, height); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapEditorDialog.java b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java new file mode 100644 index 0000000000..f004d59c63 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapEditorDialog.java @@ -0,0 +1,623 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.content.blocks.StorageBlocks; +import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.MapIO; +import io.anuke.mindustry.io.MapMeta; +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.mindustry.world.Block; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Listenable; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.input.Input; +import io.anuke.ucore.scene.actions.Actions; +import io.anuke.ucore.scene.builders.build; +import io.anuke.ucore.scene.builders.label; +import io.anuke.ucore.scene.builders.table; +import io.anuke.ucore.scene.ui.*; +import io.anuke.ucore.scene.ui.layout.Stack; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.scene.ui.layout.Unit; +import io.anuke.ucore.scene.utils.UIUtils; +import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Strings; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import static io.anuke.mindustry.Vars.*; + +public class MapEditorDialog extends Dialog implements Disposable{ + private MapEditor editor; + private MapView view; + private MapInfoDialog infoDialog; + private MapLoadDialog loadDialog; + private MapResizeDialog resizeDialog; + private ScrollPane pane; + private FloatingDialog menu; + private boolean saved = false; + private boolean shownWithMap = false; + + private ButtonGroup blockgroup; + + public MapEditorDialog(){ + super("$text.mapeditor", "dialog"); + + editor = new MapEditor(); + view = new MapView(editor); + + infoDialog = new MapInfoDialog(editor); + + menu = new FloatingDialog("$text.menu"); + menu.addCloseButton(); + + float isize = 16 * 2f; + float swidth = 180f; + + menu.content().table(t -> { + t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5); + + t.addImageTextButton("$text.editor.savemap", "icon-floppy-16", isize, this::save).size(swidth * 2f + 10, 60f).colspan(2); + + t.row(); + + t.addImageTextButton("$text.editor.mapinfo", "icon-pencil", isize, () -> { + infoDialog.show(); + menu.hide(); + }); + + t.addImageTextButton("$text.editor.resize", "icon-resize", isize, () -> { + resizeDialog.show(); + menu.hide(); + }); + + t.row(); + + t.addImageTextButton("$text.editor.import", "icon-load-map", isize, () -> + createDialog("$text.editor.import", + "$text.editor.importmap", "$text.editor.importmap.description", "icon-load-map", (Listenable) loadDialog::show, + "$text.editor.importfile", "$text.editor.importfile.description", "icon-file", (Listenable) () -> { + Platform.instance.showFileChooser("$text.loadimage", "Map Files", file -> { + ui.loadAnd(() -> { + try{ + DataInputStream stream = new DataInputStream(file.read()); + + MapMeta meta = MapIO.readMapMeta(stream); + MapTileData data = MapIO.readTileData(stream, meta, false); + + editor.beginEdit(data, meta.tags, false); + view.clearStack(); + }catch(Exception e){ + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, true, mapExtension); + }/*, + "$text.editor.importimage", "$text.editor.importimage.description", "icon-file-image", (Listenable)() -> { + if(gwt){ + ui.showError("$text.web.unsupported"); + }else { + Platform.instance.showFileChooser("$text.loadimage", "Image Files", file -> { + ui.loadAnd(() -> { + try{ + MapTileData data = MapIO.readPixmap(new Pixmap(file)); + + editor.beginEdit(data, editor.getTags(), false); + view.clearStack(); + }catch (Exception e){ + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, true, "png"); + } + }*/)); + + t.addImageTextButton("$text.editor.export", "icon-save-map", isize, () -> createDialog("$text.editor.export", + "$text.editor.exportfile", "$text.editor.exportfile.description", "icon-file", (Listenable) () -> { + if(!gwt){ + Platform.instance.showFileChooser("$text.saveimage", "Map Files", file -> { + file = file.parent().child(file.nameWithoutExtension() + "." + mapExtension); + FileHandle result = file; + ui.loadAnd(() -> { + + try{ + if(!editor.getTags().containsKey("name")){ + editor.getTags().put("name", result.nameWithoutExtension()); + } + MapIO.writeMap(result.write(false), editor.getTags(), editor.getMap()); + }catch(Exception e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, false, mapExtension); + }else{ + try{ + ByteArrayOutputStream ba = new ByteArrayOutputStream(); + MapIO.writeMap(ba, editor.getTags(), editor.getMap()); + Platform.instance.downloadFile(editor.getTags().get("name", "unknown") + "." + mapExtension, ba.toByteArray()); + }catch(IOException e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + Log.err(e); + } + } + }/*, + "$text.editor.exportimage", "$text.editor.exportimage.description", "icon-file-image", (Listenable)() -> { + if(gwt){ + ui.showError("$text.web.unsupported"); + }else{ + Platform.instance.showFileChooser("$text.saveimage", "Image Files", file -> { + file = file.parent().child(file.nameWithoutExtension() + ".png"); + FileHandle result = file; + ui.loadAnd(() -> { + try{ + Pixmaps.write(MapIO.generatePixmap(editor.getMap()), result); + }catch (Exception e){ + ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); + Log.err(e); + } + }); + }, false, "png"); + } + }*/)); + + t.row(); + + t.row(); + }); + + menu.content().row(); + + menu.content().addImageTextButton("$text.quit", "icon-back", isize, () -> { + tryExit(); + menu.hide(); + }).padTop(-5).size(swidth * 2f + 10, 60f); + + resizeDialog = new MapResizeDialog(editor, (x, y) -> { + if(!(editor.getMap().width() == x && editor.getMap().height() == y)){ + ui.loadAnd(() -> { + editor.resize(x, y); + view.clearStack(); + }); + } + }); + + loadDialog = new MapLoadDialog(map -> { + + ui.loadAnd(() -> { + try(DataInputStream stream = new DataInputStream(map.stream.get())){ + MapMeta meta = MapIO.readMapMeta(stream); + MapTileData data = MapIO.readTileData(stream, meta, false); + + editor.beginEdit(data, meta.tags, false); + view.clearStack(); + }catch(IOException e){ + ui.showError(Bundles.format("text.editor.errormapload", Strings.parseException(e, false))); + Log.err(e); + } + }); + }); + + setFillParent(true); + + clearChildren(); + margin(0); + build.begin(this); + build(); + build.end(); + + update(() -> { + if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){ + return; + } + + Vector2 v = pane.stageToLocalCoordinates(Graphics.mouse()); + + if(v.x >= 0 && v.y >= 0 && v.x <= pane.getWidth() && v.y <= pane.getHeight()){ + Core.scene.setScrollFocus(pane); + }else{ + Core.scene.setScrollFocus(null); + } + + if(Core.scene != null && Core.scene.getKeyboardFocus() == this){ + doInput(); + } + }); + + shown(() -> { + saved = true; + Platform.instance.beginForceLandscape(); + view.clearStack(); + Core.scene.setScrollFocus(view); + if(!shownWithMap){ + editor.beginEdit(new MapTileData(256, 256), new ObjectMap<>(), true); + } + shownWithMap = false; + + Timers.runTask(10f, Platform.instance::updateRPC); + }); + + hidden(() -> { + Platform.instance.updateRPC(); + Platform.instance.endForceLandscape(); + }); + } + + private void save(){ + String name = editor.getTags().get("name", ""); + + if(name.isEmpty()){ + ui.showError("$text.editor.save.noname"); + }else{ + Map map = world.maps().getByName(name); + if(map != null && !map.custom){ + ui.showError("$text.editor.save.overwrite"); + }else{ + world.maps().saveMap(name, editor.getMap(), editor.getTags()); + ui.showInfoFade("$text.editor.saved"); + } + } + + menu.hide(); + saved = true; + } + + /** + * Argument format: + * 0) button name + * 1) description + * 2) icon name + * 3) listener + */ + private FloatingDialog createDialog(String title, Object... arguments){ + FloatingDialog dialog = new FloatingDialog(title); + + float h = 90f; + + dialog.content().defaults().size(360f, h).padBottom(5).padRight(5).padLeft(5); + + for(int i = 0; i < arguments.length; i += 4){ + String name = (String) arguments[i]; + String description = (String) arguments[i + 1]; + String iconname = (String) arguments[i + 2]; + Listenable listenable = (Listenable) arguments[i + 3]; + + TextButton button = dialog.content().addButton(name, () -> { + listenable.listen(); + dialog.hide(); + menu.hide(); + }).left().get(); + + button.clearChildren(); + button.table("button", t -> { + t.addImage(iconname).size(16 * 3); + t.update(() -> t.background(button.getClickListener().isOver() ? "button-over" : "button")); + }).padLeft(-10).padBottom(-3).size(h); + button.table(t -> { + t.add(name).growX().wrap(); + t.row(); + t.add(description).color(Color.GRAY).growX().wrap(); + }).growX().padLeft(8); + + button.row(); + + dialog.content().row(); + } + + dialog.addCloseButton(); + dialog.show(); + + return dialog; + } + + @Override + public Dialog show(){ + return super.show(Core.scene, Actions.sequence(Actions.alpha(0f), Actions.scaleTo(1f, 1f), Actions.fadeIn(0.3f))); + } + + @Override + public void dispose(){ + editor.renderer().dispose(); + } + + public void beginEditMap(InputStream is){ + ui.loadAnd(() -> { + try{ + shownWithMap = true; + DataInputStream stream = new DataInputStream(is); + MapMeta meta = MapIO.readMapMeta(stream); + editor.beginEdit(MapIO.readTileData(stream, meta, false), meta.tags, false); + is.close(); + show(); + }catch(Exception e){ + Log.err(e); + ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); + } + }); + } + + public MapView getView(){ + return view; + } + + public void resetSaved(){ + saved = false; + } + + public void updateSelectedBlock(){ + Block block = editor.getDrawBlock(); + for(int j = 0; j < Block.all().size; j++){ + if(block.id == j && j < blockgroup.getButtons().size){ + blockgroup.getButtons().get(j).setChecked(true); + break; + } + } + } + + public boolean hasPane(){ + return Core.scene.getScrollFocus() == pane || Core.scene.getKeyboardFocus() != this; + } + + public void build(){ + float amount = 10f, baseSize = 60f; + + float size = mobile ? (int) (Math.min(Gdx.graphics.getHeight(), Gdx.graphics.getWidth()) / amount / Unit.dp.scl(1f)) : + Math.min(Gdx.graphics.getDisplayMode().height / amount, baseSize); + + new table(){{ + aleft(); + + new table("button"){{ + margin(0); + Table tools = new Table(); + tools.top(); + atop(); + + ButtonGroup group = new ButtonGroup<>(); + + Consumer addTool = tool -> { + ImageButton button = new ImageButton("icon-" + tool.name(), "toggle"); + button.clicked(() -> view.setTool(tool)); + button.resizeImage(16 * 2f); + button.update(() -> button.setChecked(view.getTool() == tool)); + group.add(button); + if(tool == EditorTool.pencil) + button.setChecked(true); + + tools.add(button).padBottom(-5.1f); + }; + + tools.defaults().size(size, size + 4f).padBottom(-5.1f); + + //tools.addImageButton("icon-back", 16*2, () -> tryExit()); + + tools.addImageButton("icon-menu-large", 16 * 2f, menu::show); + + ImageButton grid = tools.addImageButton("icon-grid", "toggle", 16 * 2f, () -> view.setGrid(!view.isGrid())).get(); + + addTool.accept(EditorTool.zoom); + + tools.row(); + + ImageButton undo = tools.addImageButton("icon-undo", 16 * 2f, () -> view.undo()).get(); + ImageButton redo = tools.addImageButton("icon-redo", 16 * 2f, () -> view.redo()).get(); + + addTool.accept(EditorTool.pick); + + tools.row(); + + undo.setDisabled(() -> !view.getStack().canUndo()); + redo.setDisabled(() -> !view.getStack().canRedo()); + + undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE)); + redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE)); + grid.update(() -> grid.setChecked(view.isGrid())); + + addTool.accept(EditorTool.line); + addTool.accept(EditorTool.pencil); + addTool.accept(EditorTool.eraser); + + tools.row(); + + addTool.accept(EditorTool.fill); + addTool.accept(EditorTool.elevation); + + ImageButton rotate = tools.addImageButton("icon-arrow-16", 16 * 2f, () -> editor.setDrawRotation((editor.getDrawRotation() + 1) % 4)).get(); + rotate.getImage().update(() -> { + rotate.getImage().setRotation(editor.getDrawRotation() * 90); + rotate.getImage().setOrigin(Align.center); + }); + + tools.row(); + + tools.table("button", t -> { + t.add("$text.editor.teams"); + }).colspan(3).height(40).width(size * 3f); + + tools.row(); + + ButtonGroup teamgroup = new ButtonGroup<>(); + + int i = 0; + + for(Team team : Team.all){ + ImageButton button = new ImageButton("white", "toggle"); + button.margin(4f, 4f, 10f, 4f); + button.getImageCell().grow(); + button.getStyle().imageUpColor = team.color; + button.clicked(() -> editor.setDrawTeam(team)); + button.update(() -> button.setChecked(editor.getDrawTeam() == team)); + teamgroup.add(button); + tools.add(button).padBottom(-5.1f); + + if(i++ % 3 == 2) tools.row(); + } + + add(tools).top().padBottom(-6); + + row(); + + new table("button"){{ + atop(); + Slider slider = new Slider(0, MapEditor.brushSizes.length - 1, 1, false); + slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int) (float) f])); + new label("brush"); + row(); + add(slider).width(size * 3f - 20).padTop(4f); + }}.padTop(5).growX().growY().top().end(); + + row(); + + get().table("button", t -> { + t.add("$text.editor.elevation"); + }).colspan(3).height(40).width(size * 3f); + + row(); + + get().table("button", t -> { + t.margin(0); + t.addImageButton("icon-arrow-left", 16 * 2f, () -> editor.setDrawElevation(editor.getDrawElevation() - 1)) + .disabled(b -> editor.getDrawElevation() <= -1).size(size); + + t.label(() -> editor.getDrawElevation() == -1 ? "$text.editor.slope" : (editor.getDrawElevation() + "")) + .size(size).get().setAlignment(Align.center, Align.center); + + t.addImageButton("icon-arrow-right", 16 * 2f, () -> editor.setDrawElevation(editor.getDrawElevation() + 1)) + .disabled(b -> editor.getDrawElevation() >= 127).size(size); + }).colspan(3).height(size).padTop(-5).width(size * 3f); + + }}.left().growY().end(); + + + new table("button"){{ + margin(5); + marginBottom(10); + add(view).grow(); + }}.grow().end(); + + new table(){{ + + row(); + + addBlockSelection(get()); + + row(); + + }}.right().growY().end(); + }}.grow().end(); + } + + private void doInput(){ + //tool select + for(int i = 0; i < EditorTool.values().length; i++){ + if(Inputs.keyTap(Input.valueOf("NUM_" + (i + 1)))){ + view.setTool(EditorTool.values()[i]); + break; + } + } + + if(Inputs.keyTap(Input.R)){ + editor.setDrawRotation((editor.getDrawRotation() + 1) % 4); + } + + if(Inputs.keyTap(Input.E)){ + editor.setDrawRotation(Mathf.mod((editor.getDrawRotation() + 1), 4)); + } + + //ctrl keys (undo, redo, save) + if(UIUtils.ctrl()){ + if(Inputs.keyTap(Input.Z)){ + view.undo(); + } + + if(Inputs.keyTap(Input.Y)){ + view.redo(); + } + + if(Inputs.keyTap(Input.S)){ + save(); + } + + if(Inputs.keyTap(Input.G)){ + view.setGrid(!view.isGrid()); + } + } + } + + private void tryExit(){ + if(!saved){ + ui.showConfirm("$text.confirm", "$text.editor.unsaved", this::hide); + }else{ + hide(); + } + } + + private void addBlockSelection(Table table){ + Table content = new Table(); + pane = new ScrollPane(content, "volume"); + pane.setFadeScrollBars(false); + pane.setOverscroll(true, false); + ButtonGroup group = new ButtonGroup<>(); + blockgroup = group; + + int i = 0; + + for(Block block : Block.all()){ + TextureRegion[] regions = block.getCompactIcon(); + if((block.synthetic() && (Recipe.getByResult(block) == null || !control.database().isUnlocked(Recipe.getByResult(block)))) && !debug && block != StorageBlocks.core) + continue; + + if(regions.length == 0 || regions[0] == Draw.region("jjfgj")) continue; + + Stack stack = new Stack(); + + for(TextureRegion region : regions){ + stack.add(new Image(region)); + } + + ImageButton button = new ImageButton("white", "toggle"); + button.clicked(() -> editor.setDrawBlock(block)); + button.resizeImage(8 * 4f); + button.getImageCell().setActor(stack); + button.addChild(stack); + button.getImage().remove(); + button.update(() -> button.setChecked(editor.getDrawBlock() == block)); + group.add(button); + content.add(button).pad(4f).size(53f, 58f); + + if(i++ % 3 == 2){ + content.row(); + } + } + + group.getButtons().get(2).setChecked(true); + + Table extra = new Table("button"); + extra.labelWrap(() -> editor.getDrawBlock().formalName).width(220f).center(); + table.add(extra).growX(); + table.row(); + table.add(pane).growY().fillX(); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapInfoDialog.java b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java new file mode 100644 index 0000000000..2fc2427399 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapInfoDialog.java @@ -0,0 +1,78 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.scene.ui.TextArea; +import io.anuke.ucore.scene.ui.TextField; + +public class MapInfoDialog extends FloatingDialog{ + private final MapEditor editor; + + private TextArea description; + private TextField author; + private TextField name; + + public MapInfoDialog(MapEditor editor){ + super("$text.editor.mapinfo"); + this.editor = editor; + + addCloseButton(); + + shown(this::setup); + + hidden(() -> { + + }); + } + + private void setup(){ + content().clear(); + + ObjectMap tags = editor.getTags(); + + content().add("$text.editor.name").padRight(8).left(); + + content().defaults().padTop(15); + + name = content().addField(tags.get("name", ""), text -> { + tags.put("name", text); + }).size(400, 55f).get(); + name.setMessageText("$text.unknown"); + + content().row(); + + content().add("$text.editor.description").padRight(8).left(); + + description = content().addArea(tags.get("description", ""), "textarea", text -> { + tags.put("description", text); + }).size(400f, 140f).get(); + + content().row(); + + content().add("$text.editor.author").padRight(8).left(); + + author = content().addField(tags.get("author", Settings.getString("mapAuthor", "")), text -> { + tags.put("author", text); + Settings.putString("mapAuthor", text); + Settings.save(); + }).size(400, 55f).get(); + author.setMessageText("$text.unknown"); + + content().row(); + + content().add().padRight(8).left(); + content().addCheck("$text.editor.oregen", enabled -> { + tags.put("oregen", enabled ? "1" : "0"); + }).left(); + + name.change(); + description.change(); + author.change(); + + Platform.instance.addDialog(name, 50); + Platform.instance.addDialog(author, 50); + Platform.instance.addDialog(description, 1000); + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapLoadDialog.java b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java new file mode 100644 index 0000000000..c9c249636e --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapLoadDialog.java @@ -0,0 +1,81 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.ui.BorderImage; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.scene.ui.ButtonGroup; +import io.anuke.ucore.scene.ui.ScrollPane; +import io.anuke.ucore.scene.ui.ScrollPane.ScrollPaneStyle; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.layout.Table; + +import static io.anuke.mindustry.Vars.world; + +public class MapLoadDialog extends FloatingDialog{ + private Map selected = null; + + public MapLoadDialog(Consumer loader){ + super("$text.editor.loadmap"); + + shown(this::rebuild); + rebuild(); + + TextButton button = new TextButton("$text.load"); + button.setDisabled(() -> selected == null); + button.clicked(() -> { + if(selected != null){ + loader.accept(selected); + hide(); + } + }); + + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().add(button); + } + + public void rebuild(){ + content().clear(); + if(world.maps().all().size > 0){ + selected = world.maps().all().first(); + } + + ButtonGroup group = new ButtonGroup<>(); + + int maxcol = 3; + + int i = 0; + + Table table = new Table(); + table.defaults().size(200f, 90f).pad(4f); + table.margin(10f); + + ScrollPane pane = new ScrollPane(table, "horizontal"); + pane.setFadeScrollBars(false); + + for(Map map : world.maps().all()){ + + TextButton button = new TextButton(map.getDisplayName(), "toggle"); + button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); + button.getCells().reverse(); + button.clicked(() -> selected = map); + button.getLabelCell().grow().left().padLeft(5f); + group.add(button); + table.add(button); + if(++i % maxcol == 0) table.row(); + } + + if(world.maps().all().size == 0){ + pane.setStyle(Core.skin.get("clear", ScrollPaneStyle.class)); + table.add("$text.maps.none").center(); + }else{ + content().add("$text.editor.loadmap"); + } + + content().row(); + content().add(pane); + } + +} diff --git a/core/src/io/anuke/mindustry/editor/MapRenderer.java b/core/src/io/anuke/mindustry/editor/MapRenderer.java new file mode 100644 index 0000000000..b86c8bf7e1 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapRenderer.java @@ -0,0 +1,187 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.IntSet; +import com.badlogic.gdx.utils.IntSet.IntSetIterator; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.MapTileData.DataPosition; +import io.anuke.mindustry.world.Block; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.IndexedRenderer; +import io.anuke.ucore.util.Bits; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.tilesize; + +public class MapRenderer implements Disposable{ + private static final int chunksize = 64; + private IndexedRenderer[][] chunks; + private IntSet updates = new IntSet(); + private IntSet delayedUpdates = new IntSet(); + private MapEditor editor; + private int width, height; + private Color tmpColor = Color.WHITE.cpy(); + + public MapRenderer(MapEditor editor){ + this.editor = editor; + } + + public void resize(int width, int height){ + if(chunks != null){ + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ + chunks[x][y].dispose(); + } + } + } + + chunks = new IndexedRenderer[(int) Math.ceil((float) width / chunksize)][(int) Math.ceil((float) height / chunksize)]; + + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ + chunks[x][y] = new IndexedRenderer(chunksize * chunksize * 2); + } + } + this.width = width; + this.height = height; + updateAll(); + } + + + public void draw(float tx, float ty, float tw, float th){ + Graphics.end(); + + IntSetIterator it = updates.iterator(); + while(it.hasNext){ + int i = it.next(); + int x = i % width; + int y = i / width; + render(x, y); + } + updates.clear(); + + updates.addAll(delayedUpdates); + delayedUpdates.clear(); + + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ + IndexedRenderer mesh = chunks[x][y]; + + mesh.getTransformMatrix().setToTranslation(tx, ty, 0).scl(tw / (width * tilesize), + th / (height * tilesize), 1f); + mesh.setProjectionMatrix(Core.batch.getProjectionMatrix()); + + mesh.render(Core.atlas.getTextures().first()); + } + } + + Graphics.begin(); + } + + public void updatePoint(int x, int y){ + //TODO spread out over multiple frames? + updates.add(x + y * width); + } + + public void updateAll(){ + for(int x = 0; x < width; x++){ + for(int y = 0; y < height; y++){ + render(x, y); + } + } + } + + private void render(int wx, int wy){ + int x = wx / chunksize, y = wy / chunksize; + IndexedRenderer mesh = chunks[x][y]; + //TileDataMarker data = editor.getMap().readAt(wx, wy); + byte bf = editor.getMap().read(wx, wy, DataPosition.floor); + byte bw = editor.getMap().read(wx, wy, DataPosition.wall); + byte btr = editor.getMap().read(wx, wy, DataPosition.rotationTeam); + byte elev = editor.getMap().read(wx, wy, DataPosition.elevation); + byte rotation = Bits.getLeftByte(btr); + Team team = Team.all[Bits.getRightByte(btr)]; + + Block floor = Block.getByID(bf); + Block wall = Block.getByID(bw); + + int offsetx = -(wall.size - 1) / 2; + int offsety = -(wall.size - 1) / 2; + + TextureRegion region; + + if(bw != 0){ + region = wall.getEditorIcon(); + + if(wall.rotate){ + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, + wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, + region.getRegionWidth(), region.getRegionHeight(), rotation * 90 - 90); + }else{ + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, + wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, + region.getRegionWidth(), region.getRegionHeight()); + } + }else{ + region = floor.getEditorIcon(); + + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize, region, wx * tilesize, wy * tilesize, 8, 8); + } + + boolean check = checkElevation(elev, wx, wy); + + if(wall.update || wall.destructible){ + mesh.setColor(team.color); + region = Draw.region("block-border"); + }else if(elev > 0 && check){ + mesh.setColor(tmpColor.fromHsv((360f * elev / 127f * 4f) % 360f, 0.5f + (elev / 4f) % 0.5f, 1f)); + region = Draw.region("block-elevation"); + }else if(elev == -1){ + region = Draw.region("block-slope"); + }else{ + region = Draw.region("clear"); + } + + mesh.draw((wx % chunksize) + (wy % chunksize) * chunksize + chunksize * chunksize, region, + wx * tilesize + offsetx * tilesize, wy * tilesize + offsety * tilesize, + region.getRegionWidth(), region.getRegionHeight()); + mesh.setColor(Color.WHITE); + } + + private boolean checkElevation(byte elev, int x, int y){ + for(GridPoint2 p : Geometry.d4){ + int wx = x + p.x, wy = y + p.y; + if(!Mathf.inBounds(wx, wy, editor.getMap().width(), editor.getMap().height())){ + return true; + } + byte value = editor.getMap().read(wx, wy, DataPosition.elevation); + + if(value < elev){ + return true; + }else if(value > elev){ + delayedUpdates.add(wx + wy * width); + } + } + return false; + } + + @Override + public void dispose(){ + if(chunks == null){ + return; + } + for(int x = 0; x < chunks.length; x++){ + for(int y = 0; y < chunks[0].length; y++){ + if(chunks[x][y] != null){ + chunks[x][y].dispose(); + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapResizeDialog.java b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java new file mode 100644 index 0000000000..a5ffc63835 --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapResizeDialog.java @@ -0,0 +1,63 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.io.MapTileData; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.function.BiConsumer; +import io.anuke.ucore.scene.ui.ButtonGroup; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Mathf; + +public class MapResizeDialog extends FloatingDialog{ + int[] validMapSizes = {200, 300, 400, 500}; + int width, height; + + public MapResizeDialog(MapEditor editor, BiConsumer cons){ + super("$text.editor.resizemap"); + shown(() -> { + content().clear(); + MapTileData data = editor.getMap(); + width = data.width(); + height = data.height(); + + Table table = new Table(); + + for(boolean w : Mathf.booleans){ + int curr = w ? data.width() : data.height(); + int idx = 0; + for(int i = 0; i < validMapSizes.length; i++){ + if(validMapSizes[i] == curr) idx = i; + } + + table.add(w ? "$text.width" : "$text.height").padRight(8f); + ButtonGroup group = new ButtonGroup<>(); + for(int i = 0; i < validMapSizes.length; i++){ + int size = validMapSizes[i]; + TextButton button = new TextButton(size + "", "toggle"); + button.clicked(() -> { + if(w) + width = size; + else + height = size; + }); + group.add(button); + if(i == idx) button.setChecked(true); + table.add(button).size(100f, 54f).pad(2f); + } + + table.row(); + } + content().row(); + content().add(table); + + }); + + buttons().defaults().size(200f, 50f); + buttons().addButton("$text.cancel", this::hide); + buttons().addButton("$text.editor.resize", () -> { + cons.accept(width, height); + hide(); + }); + + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapSaveDialog.java b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java new file mode 100644 index 0000000000..c11318298b --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapSaveDialog.java @@ -0,0 +1,75 @@ +package io.anuke.mindustry.editor; + +import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.scene.ui.TextButton; +import io.anuke.ucore.scene.ui.TextField; + +import static io.anuke.mindustry.Vars.ui; +import static io.anuke.mindustry.Vars.world; + +public class MapSaveDialog extends FloatingDialog{ + private TextField field; + private Consumer listener; + + public MapSaveDialog(Consumer cons){ + super("$text.editor.savemap"); + field = new TextField(); + listener = cons; + + Platform.instance.addDialog(field); + + shown(() -> { + content().clear(); + content().label(() -> { + Map map = world.maps().getByName(field.getText()); + if(map != null){ + if(map.custom){ + return "$text.editor.overwrite"; + }else{ + return "$text.editor.failoverwrite"; + } + } + return ""; + }).colspan(2); + content().row(); + content().add("$text.editor.mapname").padRight(14f); + content().add(field).size(220f, 48f); + }); + + buttons().defaults().size(200f, 50f).pad(2f); + buttons().addButton("$text.cancel", this::hide); + + TextButton button = new TextButton("$text.save"); + button.clicked(() -> { + if(!invalid()){ + cons.accept(field.getText()); + hide(); + } + }); + button.setDisabled(this::invalid); + buttons().add(button); + } + + public void save(){ + if(!invalid()){ + listener.accept(field.getText()); + }else{ + ui.showError("$text.editor.failoverwrite"); + } + } + + public void setFieldText(String text){ + field.setText(text); + } + + private boolean invalid(){ + if(field.getText().isEmpty()){ + return true; + } + Map map = world.maps().getByName(field.getText()); + return map != null && !map.custom; + } +} diff --git a/core/src/io/anuke/mindustry/editor/MapView.java b/core/src/io/anuke/mindustry/editor/MapView.java new file mode 100644 index 0000000000..e50885787a --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/MapView.java @@ -0,0 +1,398 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.input.GestureDetector; +import com.badlogic.gdx.input.GestureDetector.GestureListener; +import com.badlogic.gdx.math.Bresenham2; +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.editor.DrawOperation.TileOperation; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.ui.GridImage; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.scene.Element; +import io.anuke.ucore.scene.event.InputEvent; +import io.anuke.ucore.scene.event.InputListener; +import io.anuke.ucore.scene.event.Touchable; +import io.anuke.ucore.scene.ui.TextField; +import io.anuke.ucore.scene.ui.layout.Unit; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Tmp; + +import static io.anuke.mindustry.Vars.mobile; +import static io.anuke.mindustry.Vars.ui; + +public class MapView extends Element implements GestureListener{ + private MapEditor editor; + private EditorTool tool = EditorTool.pencil; + private OperationStack stack = new OperationStack(); + private DrawOperation op; + private Bresenham2 br = new Bresenham2(); + private boolean updated = false; + private float offsetx, offsety; + private float zoom = 1f; + private boolean grid = false; + private GridImage image = new GridImage(0, 0); + private Vector2 vec = new Vector2(); + private Rectangle rect = new Rectangle(); + private Vector2[][] brushPolygons = new Vector2[MapEditor.brushSizes.length][0]; + + private boolean drawing; + private int lastx, lasty; + private int startx, starty; + private float mousex, mousey; + private EditorTool lastTool; + + public MapView(MapEditor editor){ + this.editor = editor; + + for(int i = 0; i < MapEditor.brushSizes.length; i++){ + float size = MapEditor.brushSizes[i]; + brushPolygons[i] = Geometry.pixelCircle(size, (index, x, y) -> Vector2.dst(x, y, index, index) <= index - 0.5f); + } + + Inputs.addProcessor(0, new GestureDetector(20, 0.5f, 2, 0.15f, this)); + setTouchable(Touchable.enabled); + + addListener(new InputListener(){ + + @Override + public boolean mouseMoved(InputEvent event, float x, float y){ + mousex = x; + mousey = y; + + return false; + } + + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button){ + if(pointer != 0){ + return false; + } + + if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ + return true; + } + + if(button == Buttons.MIDDLE){ + lastTool = tool; + tool = EditorTool.zoom; + } + + mousex = x; + mousey = y; + + op = new DrawOperation(editor.getMap()); + + updated = false; + + GridPoint2 p = project(x, y); + lastx = p.x; + lasty = p.y; + startx = p.x; + starty = p.y; + tool.touched(editor, p.x, p.y); + + if(tool.edit){ + updated = true; + ui.editor.resetSaved(); + } + + drawing = true; + return true; + } + + @Override + public void touchUp(InputEvent event, float x, float y, int pointer, int button){ + if(!mobile && button != Buttons.LEFT && button != Buttons.MIDDLE){ + return; + } + + drawing = false; + + GridPoint2 p = project(x, y); + + if(tool == EditorTool.line){ + ui.editor.resetSaved(); + Array points = br.line(startx, starty, p.x, p.y); + for(GridPoint2 point : points){ + editor.draw(point.x, point.y); + } + updated = true; + } + + if(op != null && updated){ + if(!op.isEmpty()){ + stack.add(op); + } + op = null; + } + + if(lastTool != null){ + tool = lastTool; + lastTool = null; + } + + } + + @Override + public void touchDragged(InputEvent event, float x, float y, int pointer){ + mousex = x; + mousey = y; + + GridPoint2 p = project(x, y); + + if(drawing && tool.draggable){ + ui.editor.resetSaved(); + Array points = br.line(lastx, lasty, p.x, p.y); + for(GridPoint2 point : points){ + tool.touched(editor, point.x, point.y); + } + updated = true; + } + lastx = p.x; + lasty = p.y; + } + }); + } + + public EditorTool getTool(){ + return tool; + } + + public void setTool(EditorTool tool){ + this.tool = tool; + } + + public void clearStack(){ + stack.clear(); + //TODO clear und obuffer + } + + public OperationStack getStack(){ + return stack; + } + + public boolean isGrid(){ + return grid; + } + + public void setGrid(boolean grid){ + this.grid = grid; + } + + public void undo(){ + if(stack.canUndo()){ + stack.undo(editor); + } + } + + public void redo(){ + if(stack.canRedo()){ + stack.redo(editor); + } + } + + public void addTileOp(TileOperation t){ + op.addOperation(t); + } + + public boolean checkForDuplicates(short x, short y){ + return op.checkDuplicate(x, y); + } + + @Override + public void act(float delta){ + super.act(delta); + + if(Core.scene.getKeyboardFocus() == null || !(Core.scene.getKeyboardFocus() instanceof TextField) && + !Inputs.keyDown(io.anuke.ucore.input.Input.CONTROL_LEFT)){ + float ax = Inputs.getAxis("move_x"); + float ay = Inputs.getAxis("move_y"); + offsetx -= ax * 15f / zoom; + offsety -= ay * 15f / zoom; + } + + if(ui.editor.hasPane()) return; + + zoom += Inputs.scroll() / 10f * zoom; + clampZoom(); + } + + private void clampZoom(){ + zoom = Mathf.clamp(zoom, 0.2f, 12f); + } + + private GridPoint2 project(float x, float y){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + x = (x - getWidth() / 2 + sclwidth / 2 - offsetx * zoom) / sclwidth * editor.getMap().width(); + y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.getMap().height(); + + if(editor.getDrawBlock().size % 2 == 0 && tool != EditorTool.eraser){ + return Tmp.g1.set((int) (x - 0.5f), (int) (y - 0.5f)); + }else{ + return Tmp.g1.set((int) x, (int) y); + } + } + + private Vector2 unproject(int x, int y){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float px = ((float) x / editor.getMap().width()) * sclwidth + offsetx * zoom - sclwidth / 2 + getWidth() / 2; + float py = ((float) (y) / editor.getMap().height()) * sclheight + + offsety * zoom - sclheight / 2 + getHeight() / 2; + return vec.set(px, py); + } + + @Override + public void draw(Batch batch, float alpha){ + float ratio = 1f / ((float) editor.getMap().width() / editor.getMap().height()); + float size = Math.min(width, height); + float sclwidth = size * zoom; + float sclheight = size * zoom * ratio; + float centerx = x + width / 2 + offsetx * zoom; + float centery = y + height / 2 + offsety * zoom; + + image.setImageSize(editor.getMap().width(), editor.getMap().height()); + + batch.flush(); + boolean pop = ScissorStack.pushScissors(rect.set(x, y, width, height)); + + Draw.color(Color.LIGHT_GRAY); + Lines.stroke(-2f); + Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2); + editor.renderer().draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight); + Draw.reset(); + + if(grid){ + Draw.color(Color.GRAY); + image.setBounds(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight); + image.draw(batch, alpha); + Draw.color(); + } + + int index = 0; + for(int i = 0; i < MapEditor.brushSizes.length; i++){ + if(editor.getBrushSize() == MapEditor.brushSizes[i]){ + index = i; + break; + } + } + + //todo is it really math.max? + float scaling = zoom * Math.min(width, height) / Math.max(editor.getMap().width(), editor.getMap().height()); + + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(1f * zoom)); + + if(!editor.getDrawBlock().isMultiblock() || tool == EditorTool.eraser){ + if(tool == EditorTool.line && drawing){ + Vector2 v1 = unproject(startx, starty).add(x, y); + float sx = v1.x, sy = v1.y; + Vector2 v2 = unproject(lastx, lasty).add(x, y); + + Lines.poly(brushPolygons[index], sx, sy, scaling); + Lines.poly(brushPolygons[index], v2.x, v2.y, scaling); + } + + if(tool.edit && (!mobile || drawing)){ + GridPoint2 p = project(mousex, mousey); + Vector2 v = unproject(p.x, p.y).add(x, y); + Lines.poly(brushPolygons[index], v.x, v.y, scaling); + } + }else{ + if((tool.edit || tool == EditorTool.line) && (!mobile || drawing)){ + GridPoint2 p = project(mousex, mousey); + Vector2 v = unproject(p.x, p.y).add(x, y); + float offset = (editor.getDrawBlock().size % 2 == 0 ? scaling / 2f : 0f); + Lines.square( + v.x + scaling / 2f + offset, + v.y + scaling / 2f + offset, + scaling * editor.getDrawBlock().size / 2f); + } + } + + batch.flush(); + + if(pop) ScissorStack.popScissors(); + + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(3f)); + Lines.rect(x, y, width, height); + Draw.reset(); + } + + private boolean active(){ + return Core.scene.getKeyboardFocus() != null + && Core.scene.getKeyboardFocus().isDescendantOf(ui.editor) + && ui.editor.isShown() && tool == EditorTool.zoom && + Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true) == this; + } + + @Override + public boolean touchDown(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean tap(float x, float y, int count, int button){ + return false; + } + + @Override + public boolean longPress(float x, float y){ + return false; + } + + @Override + public boolean fling(float velocityX, float velocityY, int button){ + return false; + } + + @Override + public boolean pan(float x, float y, float deltaX, float deltaY){ + if(!active()) return false; + offsetx += deltaX / zoom; + offsety -= deltaY / zoom; + return false; + } + + @Override + public boolean panStop(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean zoom(float initialDistance, float distance){ + if(!active()) return false; + float nzoom = distance - initialDistance; + zoom += nzoom / 10000f / Unit.dp.scl(1f) * zoom; + clampZoom(); + return false; + } + + @Override + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ + return false; + } + + @Override + public void pinchStop(){ + + } +} diff --git a/core/src/io/anuke/mindustry/editor/OperationStack.java b/core/src/io/anuke/mindustry/editor/OperationStack.java new file mode 100755 index 0000000000..402da3cf5e --- /dev/null +++ b/core/src/io/anuke/mindustry/editor/OperationStack.java @@ -0,0 +1,51 @@ +package io.anuke.mindustry.editor; + +import com.badlogic.gdx.utils.Array; + +public class OperationStack{ + private final static int maxSize = 10; + private Array stack = new Array<>(); + private int index = 0; + + public OperationStack(){ + + } + + public void clear(){ + stack.clear(); + index = 0; + } + + public void add(DrawOperation action){ + stack.truncate(stack.size + index); + index = 0; + stack.add(action); + + if(stack.size > maxSize){ + stack.removeIndex(0); + } + } + + public boolean canUndo(){ + return !(stack.size - 1 + index < 0); + } + + public boolean canRedo(){ + return !(index > -1 || stack.size + index < 0); + } + + public void undo(MapEditor editor){ + if(!canUndo()) return; + + stack.get(stack.size - 1 + index).undo(editor); + index--; + } + + public void redo(MapEditor editor){ + if(!canRedo()) return; + + index++; + stack.get(stack.size - 1 + index).redo(editor); + + } +} diff --git a/core/src/io/anuke/mindustry/entities/Bullet.java b/core/src/io/anuke/mindustry/entities/Bullet.java deleted file mode 100644 index 279993b4c4..0000000000 --- a/core/src/io/anuke/mindustry/entities/Bullet.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.anuke.mindustry.entities; - -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.entities.BulletEntity; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.util.Timer; - -import static io.anuke.mindustry.Vars.*; - -public class Bullet extends BulletEntity{ - public Timer timer = new Timer(3); - - public Bullet(BulletType type, Entity owner, float x, float y, float angle){ - super(type, owner, angle); - set(x, y); - this.type = type; - } - - public void draw(){ - //interpolate position linearly at low tick speeds - if(SyncEntity.isSmoothing()){ - x += threads.getFramesSinceUpdate() * velocity.x; - y += threads.getFramesSinceUpdate() * velocity.y; - - type.draw(this); - - x -= threads.getFramesSinceUpdate() * velocity.x; - y -= threads.getFramesSinceUpdate() * velocity.y; - }else{ - type.draw(this); - } - } - - public float drawSize(){ - return 8; - } - - public boolean collidesTiles(){ - return owner instanceof Enemy; - } - - @Override - public void update(){ - super.update(); - - if (collidesTiles()) { - world.raycastEach(world.toTile(lastX), world.toTile(lastY), world.toTile(x), world.toTile(y), (x, y) -> { - - Tile tile = world.tile(x, y); - if (tile == null) return false; - tile = tile.target(); - - if (tile.entity != null && tile.entity.collide(this) && !tile.entity.dead) { - tile.entity.collision(this); - remove(); - type.hit(this); - - return true; - } - - return false; - }); - } - } - - @Override - public int getDamage(){ - return damage == -1 ? type.damage : damage; - } - - @Override - public Bullet add(){ - return super.add(bulletGroup); - } -} diff --git a/core/src/io/anuke/mindustry/entities/BulletType.java b/core/src/io/anuke/mindustry/entities/BulletType.java deleted file mode 100644 index 592854f9b2..0000000000 --- a/core/src/io/anuke/mindustry/entities/BulletType.java +++ /dev/null @@ -1,485 +0,0 @@ -package io.anuke.mindustry.entities; - -import com.badlogic.gdx.graphics.Color; -import io.anuke.mindustry.entities.effect.DamageArea; -import io.anuke.mindustry.entities.effect.EMP; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.BaseBulletType; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.graphics.Fx.*; - -public abstract class BulletType extends BaseBulletType{ - - public static final BulletType - - none = new BulletType(0f, 0){ - public void draw(Bullet b){} - }, - stone = new BulletType(1.5f, 2){ - public void draw(Bullet b){ - Draw.colorl(0.64f); - Draw.rect("blank", b.x, b.y, 2f, 2f); - Draw.reset(); - } - }, - iron = new BulletType(1.7f, 2){ - public void draw(Bullet b){ - Draw.color(Color.GRAY); - Draw.rect("bullet", b.x, b.y, b.angle()); - Draw.reset(); - } - }, - chain = new BulletType(2f, 8){ - public void draw(Bullet b){ - Draw.color(whiteOrange); - Draw.rect("chainbullet", b.x, b.y, b.angle()); - Draw.reset(); - } - }, - sniper = new BulletType(3f, 25){ - public void draw(Bullet b){ - Draw.color(Color.LIGHT_GRAY); - Lines.stroke(1f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), 3f); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 4)){ - Effects.effect(Fx.railsmoke, b.x, b.y); - } - } - }, - emp = new BulletType(1.6f, 8){ - { - lifetime = 50f; - hitsize = 6f; - } - - public void draw(Bullet b){ - float rad = 6f + Mathf.sin(Timers.time(), 5f, 2f); - - Draw.color(Color.SKY); - Lines.circle(b.x, b.y, 4f); - Draw.rect("circle", b.x, b.y, rad, rad); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 2)){ - Effects.effect(Fx.empspark, b.x + Mathf.range(2), b.y + Mathf.range(2)); - } - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Timers.run(5f, ()-> new EMP(b.x, b.y, b.getDamage()).add()); - Effects.effect(Fx.empshockwave, b); - Effects.shake(3f, 3f, b); - } - }, - //TODO better visuals for shell - shell = new BulletType(1.1f, 60){ - { - lifetime = 110f; - hitsize = 11f; - } - - public void draw(Bullet b){ - float rad = 8f; - Draw.color(Color.ORANGE); - Draw.color(Color.GRAY); - Draw.rect("circle", b.x, b.y, rad, rad); - rad += Mathf.sin(Timers.time(), 3f, 1f); - Draw.color(Color.ORANGE); - Draw.rect("circle", b.x, b.y, rad/1.7f, rad/1.7f); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 7)){ - Effects.effect(Fx.smoke, b.x + Mathf.range(2), b.y + Mathf.range(2)); - } - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Effects.shake(3f, 3f, b); - - Effects.effect(Fx.shellsmoke, b); - Effects.effect(Fx.shellexplosion, b); - - DamageArea.damage(!(b.owner instanceof Enemy), b.x, b.y, 25f, (int)(damage * 2f/3f)); - } - }, - flak = new BulletType(2.9f, 8) { - - public void init(Bullet b) { - b.velocity.scl(Mathf.random(0.6f, 1f)); - } - - public void update(Bullet b){ - if(b.timer.get(0, 7)){ - Effects.effect(Fx.smoke, b.x + Mathf.range(2), b.y + Mathf.range(2)); - } - } - - public void draw(Bullet b) { - Draw.color(Color.GRAY); - Lines.stroke(3f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), 2f); - Lines.stroke(1.5f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), 5f); - Draw.reset(); - } - - public void hit(Bullet b, float hitx, float hity) { - Effects.effect(shellsmoke, b); - for(int i = 0; i < 3; i ++){ - Bullet bullet = new Bullet(flakspark, b.owner, hitx, hity, b.angle() + Mathf.range(120f)); - bullet.add(); - } - } - - public void despawned(Bullet b) { - hit(b, b.x, b.y); - } - }, - flakspark = new BulletType(2f, 2) { - { - drag = 0.05f; - } - - public void init(Bullet b) { - b.velocity.scl(Mathf.random(0.6f, 1f)); - } - - public void draw(Bullet b) { - Draw.color(Color.LIGHT_GRAY, Color.GRAY, b.fin()); - Lines.stroke(2f - b.fin()); - Lines.lineAngleCenter(b.x, b.y, b.angle(), 2f); - Draw.reset(); - } - }, - titanshell = new BulletType(1.8f, 38){ - { - lifetime = 70f; - hitsize = 15f; - } - - public void draw(Bullet b){ - Draw.color(whiteOrange); - Draw.rect("titanshell", b.x, b.y, b.angle()); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 4)){ - Effects.effect(Fx.smoke, b.x + Mathf.range(2), b.y + Mathf.range(2)); - } - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Effects.shake(3f, 3f, b); - - Effects.effect(Fx.shellsmoke, b); - Effects.effect(Fx.shockwaveSmall, b); - - DamageArea.damage(!(b.owner instanceof Enemy), b.x, b.y, 50f, (int)(damage * 2f/3f)); - } - }, - yellowshell = new BulletType(1.2f, 20){ - { - lifetime = 60f; - hitsize = 11f; - } - - public void draw(Bullet b){ - Draw.color(whiteYellow); - Draw.rect("titanshell", b.x, b.y, b.angle()); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 4)){ - Effects.effect(Fx.smoke, b.x + Mathf.range(2), b.y + Mathf.range(2)); - } - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Effects.shake(3f, 3f, b); - - Effects.effect(Fx.shellsmoke, b); - Effects.effect(Fx.shockwaveSmall, b); - - DamageArea.damage(!(b.owner instanceof Enemy), b.x, b.y, 25f, (int)(damage * 2f/3f)); - } - }, - blast = new BulletType(1.1f, 90){ - { - lifetime = 0f; - hitsize = 8f; - speed = 0f; - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Effects.shake(3f, 3f, b); - - Effects.effect(Fx.blastsmoke, b); - Effects.effect(Fx.blastexplosion, b); - - //TODO remove translation() usage - Angles.circleVectors(30, 6f, (nx, ny) -> { - float ang = Mathf.atan2(nx, ny); - Bullet o = new Bullet(blastshot, b.owner, b.x + nx, b.y + ny, ang).add(); - o.damage = b.damage/9; - }); - } - - public void draw(Bullet b){} - }, - blastshot = new BulletType(1.6f, 6){ - { - lifetime = 7f; - } - public void draw(Bullet b){} - }, - small = new BulletType(1.5f, 2){ - public void draw(Bullet b){ - Draw.color(glowy); - Draw.rect("shot", b.x, b.y, b.angle() - 45); - Draw.reset(); - } - }, - smallSlow = new BulletType(1.2f, 2){ - public void draw(Bullet b){ - Draw.color(Color.ORANGE); - Draw.rect("shot", b.x, b.y, b.angle() - 45); - Draw.reset(); - } - }, - purple = new BulletType(1.6f, 2){ - Color color = new Color(0x8b5ec9ff); - - public void draw(Bullet b){ - Draw.color(color); - Draw.rect("bullet", b.x, b.y, b.angle()); - Draw.reset(); - } - }, - flame = new BulletType(0.7f, 5){ //for turrets - public void draw(Bullet b){ - Draw.color(Color.YELLOW, Color.SCARLET, b.time/lifetime); - float size = 6f-b.time/lifetime*5f; - Draw.rect("circle", b.x, b.y, size, size); - Draw.reset(); - } - }, - plasmaflame = new BulletType(0.8f, 17){ - { - lifetime = 65f; - } - public void draw(Bullet b){ - Draw.color(Color.valueOf("efa66c"), Color.valueOf("72deaf"), b.time/lifetime); - float size = 7f-b.time/lifetime*6f; - Draw.rect("circle", b.x, b.y, size, size); - Draw.reset(); - } - }, - flameshot = new BulletType(0.5f, 3){ //for enemies - public void draw(Bullet b){ - Draw.color(Color.ORANGE, Color.SCARLET, b.time/lifetime); - float size = 6f-b.time/lifetime*5f; - Draw.rect("circle", b.x, b.y, size, size); - Draw.reset(); - } - }, - shot = new BulletType(2.7f, 5){ - { - lifetime = 40; - } - - public void draw(Bullet b){ - Draw.color(Color.WHITE, lightOrange, b.fout()/2f + 0.25f); - Lines.stroke(1.5f); - Lines.lineAngle(b.x, b.y, b.angle(), 3f); - Draw.reset(); - } - }, - spread = new BulletType(2.4f, 9) { - { - lifetime = 70; - } - - public void draw(Bullet b) { - float size = 3f - b.fin()*1f; - - Draw.color(Color.PURPLE, Color.WHITE, 0.8f); - Lines.stroke(1f); - Lines.circle(b.x, b.y, size); - Draw.reset(); - } - }, - cluster = new BulletType(4.5f, 12){ - { - lifetime = 60; - drag = 0.05f; - } - - public void draw(Bullet b){ - Lines.stroke(2f); - Draw.color(lightOrange, Color.WHITE, 0.4f); - Lines.poly(b.x, b.y, 3, 1.6f, b.angle()); - Lines.stroke(1f); - Draw.color(Color.WHITE, lightOrange, b.fin()/2f); - Draw.alpha(b.fin()); - Lines.spikes(b.x, b.y, 1.5f, 2f, 6); - Draw.reset(); - } - - public void despawned(Bullet b){ - hit(b); - } - - public void hit(Bullet b, float hitx, float hity){ - Effects.shake(1.5f, 1.5f, b); - - Effects.effect(Fx.clusterbomb, b); - - DamageArea.damage(!(b.owner instanceof Enemy), b.x, b.y, 35f, damage); - } - }, - vulcan = new BulletType(4.5f, 12) { - { - lifetime = 50; - } - - public void init(Bullet b) { - Timers.reset(b, "smoke", Mathf.random(4f)); - } - - public void draw(Bullet b){ - Draw.color(lightGray); - Lines.stroke(1f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), 2f); - Draw.reset(); - } - - public void update(Bullet b){ - if(b.timer.get(0, 4)){ - Effects.effect(Fx.chainsmoke, b.x, b.y); - } - } - }, - shockshell = new BulletType(5.5f, 11) { - - { - drag = 0.03f; - lifetime = 30f; - } - - public void init(Bullet b) { - b.velocity.scl(Mathf.random(0.5f, 1f)); - } - - public void draw(Bullet b) { - Draw.color(Color.WHITE, Color.ORANGE, b.fin()); - Lines.stroke(2f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), b.fout()*5f); - Draw.reset(); - } - - public void despawned(Bullet b) { - hit(b); - } - - public void hit(Bullet b, float hitx, float hity) { - for(int i = 0; i < 4; i ++){ - Bullet bullet = new Bullet(scrap, b.owner, b.x, b.y, b.angle() + Mathf.range(80f)); - bullet.add(); - } - } - }, - scrap = new BulletType(2f, 3) { - { - drag = 0.06f; - lifetime = 30f; - } - - public void init(Bullet b) { - b.velocity.scl(Mathf.random(0.5f, 1f)); - } - - public void draw(Bullet b) { - Draw.color(Color.WHITE, Color.ORANGE, b.fin()); - Lines.stroke(1f); - Lines.lineAngleCenter(b.x, b.y, b.angle(), b.fout()*4f); - Draw.reset(); - } - }, - beamlaser = new BulletType(0.001f, 38) { - float length = 230f; - { - drawSize = length*2f+20f; - lifetime = 15f; - } - - public void init(Bullet b) { - DamageArea.damageLine(b.owner, Fx.beamhit, b.x, b.y, b.angle(), length, damage); - } - - public void draw(Bullet b) { - float f = b.fout()*1.5f; - - Draw.color(beam); - Draw.rect("circle", b.x, b.y, 6f*f, 6f*f); - Lines.stroke(3f * f); - Lines.lineAngle(b.x, b.y, b.angle(), length); - - Lines.stroke(2f * f); - Lines.lineAngle(b.x, b.y, b.angle(), length + 6f); - Lines.stroke(1f * f); - Lines.lineAngle(b.x, b.y, b.angle(), length + 12f); - - Draw.color(beamLight); - Lines.stroke(1.5f * f); - Draw.rect("circle", b.x, b.y, 3f*f, 3f*f); - Lines.lineAngle(b.x, b.y, b.angle(), length); - } - }; - - private BulletType(float speed, int damage){ - this.speed = speed; - this.damage = damage; - } - - @Override - public void hit(Bullet b, float hitx, float hity){ - Effects.effect(Fx.hit, hitx, hity); - } -} diff --git a/core/src/io/anuke/mindustry/entities/Damage.java b/core/src/io/anuke/mindustry/entities/Damage.java new file mode 100644 index 0000000000..0c7ac56454 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/Damage.java @@ -0,0 +1,200 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.content.bullets.TurretBullets; +import io.anuke.mindustry.content.fx.ExplosionFx; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.Lightning; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.impl.SolidEntity; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Physics; +import io.anuke.ucore.util.Translator; + +import static io.anuke.mindustry.Vars.*; + +/** + * Utility class for damaging in an area. + */ +public class Damage{ + private static Rectangle rect = new Rectangle(); + private static Rectangle hitrect = new Rectangle(); + private static Translator tr = new Translator(); + + /** + * Creates a dynamic explosion based on specified parameters. + */ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color){ + for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){ + int branches = 5 + Mathf.clamp((int) (power / 30), 1, 20); + Timers.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.none, Fx.none, Palette.power, 3, + x, y, Mathf.random(360f), branches + Mathf.range(2))); + } + + for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){ + Timers.run(i / 2, () -> CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f))); + } + + int waves = Mathf.clamp((int) (explosiveness / 4), 0, 30); + + for(int i = 0; i < waves; i++){ + int f = i; + Timers.run(i * 2f, () -> { + Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f); + Effects.effect(ExplosionFx.blockExplosionSmoke, x + Mathf.range(radius), y + Mathf.range(radius)); + }); + } + + if(explosiveness > 15f){ + Effects.effect(ExplosionFx.shockwave, x, y); + } + + if(explosiveness > 30f){ + Effects.effect(ExplosionFx.bigShockwave, x, y); + } + + float shake = Math.min(explosiveness / 4f + 3f, 9f); + Effects.shake(shake, shake, x, y); + Effects.effect(ExplosionFx.blockExplosion, x, y); + } + + public static void createIncend(float x, float y, float range, int amount){ + for(int i = 0; i < amount; i++){ + float cx = x + Mathf.range(range); + float cy = y + Mathf.range(range); + Tile tile = world.tileWorld(cx, cy); + if(tile != null){ + Fire.create(tile); + } + } + } + + /** + * Damages entities in a line. + * Only enemies of the specified team are damaged. + */ + public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){ + tr.trns(angle, length); + rect.setPosition(x, y).setSize(tr.x, tr.y); + float x2 = tr.x + x, y2 = tr.y + y; + + if(rect.width < 0){ + rect.x += rect.width; + rect.width *= -1; + } + + if(rect.height < 0){ + rect.y += rect.height; + rect.height *= -1; + } + + float expand = 3f; + + rect.y -= expand; + rect.x -= expand; + rect.width += expand * 2; + rect.height += expand * 2; + + Consumer cons = e -> { + e.getHitbox(hitrect); + Rectangle other = hitrect; + other.y -= expand; + other.x -= expand; + other.width += expand * 2; + other.height += expand * 2; + + Vector2 vec = Physics.raycastRect(x, y, x2, y2, other); + + if(vec != null){ + Effects.effect(effect, vec.x, vec.y); + e.collision(hitter, vec.x, vec.y); + hitter.collision(e, vec.x, vec.y); + } + }; + + Units.getNearbyEnemies(team, rect, cons); + } + + /** + * Damages all entities and blocks in a radius that are enemies of the team. + */ + public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate predicate, Consumer acceptor){ + Consumer cons = entity -> { + if(!predicate.test(entity)) return; + + entity.getHitbox(hitrect); + if(!hitrect.overlaps(rect)){ + return; + } + entity.damage(damage); + acceptor.accept(entity); + }; + + rect.setSize(size * 2).setCenter(x, y); + if(team != null){ + Units.getNearbyEnemies(team, rect, cons); + }else{ + Units.getNearby(rect, cons); + } + } + + /** + * Damages everything in a radius. + */ + public static void damage(float x, float y, float radius, float damage){ + damage(null, x, y, radius, damage); + } + + /** + * Damages all entities and blocks in a radius that are enemies of the team. + */ + public static void damage(Team team, float x, float y, float radius, float damage){ + Consumer cons = entity -> { + if(entity.team == team || entity.distanceTo(x, y) > radius){ + return; + } + float amount = calculateDamage(x, y, entity.x, entity.y, radius, damage); + entity.damage(amount); + //TODO better velocity displacement + float dst = tr.set(entity.x - x, entity.y - y).len(); + entity.getVelocity().add(tr.setLength((1f - dst / radius) * 2f)); + }; + + rect.setSize(radius * 2).setCenter(x, y); + if(team != null){ + Units.getNearbyEnemies(team, rect, cons); + }else{ + Units.getNearby(rect, cons); + } + + int trad = (int) (radius / tilesize); + for(int dx = -trad; dx <= trad; dx++){ + for(int dy = -trad; dy <= trad; dy++){ + Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy); + if(tile != null && tile.entity != null && (team == null || state.teams.areEnemies(team, tile.getTeam())) && Vector2.dst(dx, dy, 0, 0) <= trad){ + float amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage); + tile.entity.damage(amount); + } + } + } + + } + + private static float calculateDamage(float x, float y, float tx, float ty, float radius, float damage){ + float dist = Vector2.dst(x, y, tx, ty); + float falloff = 0.4f; + float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff); + return damage * scaled; + } +} diff --git a/core/src/io/anuke/mindustry/entities/Player.java b/core/src/io/anuke/mindustry/entities/Player.java index 63463baa14..9a4641dce2 100644 --- a/core/src/io/anuke/mindustry/entities/Player.java +++ b/core/src/io/anuke/mindustry/entities/Player.java @@ -1,309 +1,800 @@ package io.anuke.mindustry.entities; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.graphics.Shaders; +import com.badlogic.gdx.utils.Pools; +import com.badlogic.gdx.utils.Queue; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.Mechs; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.effect.ScorchDecal; +import io.anuke.mindustry.entities.traits.*; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.graphics.Trail; +import io.anuke.mindustry.net.In; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.mindustry.resource.Mech; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; +import io.anuke.mindustry.net.NetConnection; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.ucore.core.*; -import io.anuke.ucore.entities.SolidEntity; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.blocks.storage.CoreBlock.CoreEntity; +import io.anuke.mindustry.world.blocks.units.MechFactory; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.trait.SolidTrait; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Timer; -import io.anuke.ucore.util.Translator; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.*; -import java.nio.ByteBuffer; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; import static io.anuke.mindustry.Vars.*; -public class Player extends SyncEntity{ - static final float speed = 1.1f; - static final float dashSpeed = 1.8f; +public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTrait{ + public static final int timerSync = 2; + private static final int timerShootLeft = 0; + private static final int timerShootRight = 1; - static final int timerDash = 0; - static final int timerShootLeft = 1; - static final int timerShootRight = 2; - static final int timerRegen = 3; + //region instance variables, constructor + public float baseRotation; - public String name = "name"; - public boolean isAndroid; - public boolean isAdmin; - public Color color = new Color(); + public float pointerX, pointerY; + public String name = "name"; + public String uuid, usid; + public boolean isAdmin, isTransferring, isShooting, isBoosting, isMobile; + public float boostHeat; + public Color color = new Color(); + public Mech mech; + public int spawner; - public Weapon weaponLeft = Weapon.blaster; - public Weapon weaponRight = Weapon.blaster; - public Mech mech = Mech.standard; + public NetConnection con; + public int playerIndex = 0; + public boolean isLocal = false; + public Timer timer = new Timer(4); + public TargetTrait target; + public TargetTrait moveTarget; - public float targetAngle = 0f; - public boolean dashing = false; + private float walktime; + private Queue placeQueue = new ThreadQueue<>(); + private Tile mining; + private CarriableTrait carrying; + private Trail trail = new Trail(12); + private Vector2 movement = new Vector2(); + private boolean moved; - public int clientid = -1; - public boolean isLocal = false; - public Timer timer = new Timer(4); - - private Vector2 movement = new Vector2(); - private Translator tr = new Translator(); - - public Player(){ - hitbox.setSize(5); - hitboxTile.setSize(4f); - - maxhealth = 200; - heal(); - } - - @Override - public void damage(float amount){ - if(debug || isAndroid) return; - - health -= amount; - if(health <= 0 && !dead && isLocal){ //remote players don't die normally - onDeath(); - dead = true; - } - } - - @Override - public boolean collides(SolidEntity other){ - if(other instanceof Bullet){ - Bullet b = (Bullet)other; - if(!state.friendlyFire && b.owner instanceof Player){ - return false; - } - } - return !isDead() && super.collides(other) && !isAndroid; - } - - @Override - public void onDeath(){ - dead = true; - if(Net.active()){ - NetEvents.handlePlayerDeath(); - } - - Effects.effect(Fx.explosion, this); - Effects.shake(4f, 5f, this); - Effects.sound("die", this); - - control.setRespawnTime(respawnduration); - ui.hudfrag.fadeRespawn(true); - } - - /**called when a remote player death event is recieved*/ - public void doRespawn(){ - dead = true; - Effects.effect(Fx.explosion, this); - Effects.shake(4f, 5f, this); - Effects.sound("die", this); - - Timers.run(respawnduration + 5f, () -> { - heal(); - set(world.getSpawnX(), world.getSpawnY()); - interpolator.target.set(x, y); - }); - } - - @Override - public void drawSmooth(){ - if((debug && (!showPlayer || !showUI)) || (isAndroid && isLocal) || dead) return; - boolean snap = snapCamera && Settings.getBool("smoothcam") && Settings.getBool("pixelate") && isLocal; - - String part = isAndroid ? "ship" : "mech"; - - Shaders.outline.color.set(getColor()); - Shaders.outline.lighten = 0f; - Shaders.outline.region = Draw.region(part + "-" + mech.name); - - Shaders.outline.apply(); - - if(!isAndroid) { - for (int i : Mathf.signs) { - Weapon weapon = i < 0 ? weaponLeft : weaponRight; - tr.trns(angle - 90, 3*i, 2); - float w = i > 0 ? -8 : 8; - if(snap){ - Draw.rect(weapon.name + "-equip", (int)x + tr.x, (int)y + tr.y, w, 8, angle - 90); - }else{ - Draw.rect(weapon.name + "-equip", x + tr.x, y + tr.y, w, 8, angle - 90); - } - } - } - - if(snap){ - Draw.rect(part + "-" + mech.name, (int)x, (int)y, angle-90); - }else{ - Draw.rect(part + "-" + mech.name, x, y, angle-90); - } - - Graphics.flush(); - } - - @Override - public void update(){ - if(!isLocal || isAndroid){ - if(isAndroid && isLocal){ - angle = Mathf.slerpDelta(angle, targetAngle, 0.2f); - } - if(!isLocal) interpolate(); - return; - } - - if(isDead()) return; - - Tile tile = world.tileWorld(x, y); - - //if player is in solid block - if(tile != null && ((tile.floor().liquid && tile.block() == Blocks.air) || tile.solid())) { - damage(health + 1); //die instantly - } - - if(ui.chatfrag.chatOpen()) return; - - dashing = Inputs.keyDown("dash"); - - float speed = dashing ? (debug ? Player.dashSpeed * 5f : Player.dashSpeed) : Player.speed; - - if(health < maxhealth && timer.get(timerRegen, 20)) - health ++; - - health = Mathf.clamp(health, -1, maxhealth); - - movement.set(0, 0); - - float xa = Inputs.getAxis("move_x"); - float ya = Inputs.getAxis("move_y"); - if(Math.abs(xa) < 0.3) xa = 0; - if(Math.abs(ya) < 0.3) ya = 0; - - movement.y += ya*speed; - movement.x += xa*speed; - - boolean shooting = !Inputs.keyDown("dash") && Inputs.keyDown("shoot") && control.input().recipe == null - && !ui.hasMouse() && !control.input().onConfigurable(); - - if(shooting){ - weaponLeft.update(player, true); - weaponRight.update(player, false); - } - - if(dashing && timer.get(timerDash, 3) && movement.len() > 0){ - Effects.effect(Fx.dashsmoke, x + Angles.trnsx(angle + 180f, 3f), y + Angles.trnsy(angle + 180f, 3f)); - } - - movement.limit(speed); - - if(!noclip){ - move(movement.x*Timers.delta(), movement.y*Timers.delta()); - }else{ - x += movement.x*Timers.delta(); - y += movement.y*Timers.delta(); - } - - if(!shooting){ - if(!movement.isZero()) - angle = Mathf.slerpDelta(angle, movement.angle(), 0.13f); - }else{ - float angle = Angles.mouseAngle(x, y); - this.angle = Mathf.slerpDelta(this.angle, angle, 0.1f); - } - - x = Mathf.clamp(x, 0, world.width() * tilesize); - y = Mathf.clamp(y, 0, world.height() * tilesize); - } - - @Override - public Player add(){ - return add(playerGroup); - } - - @Override - public String toString() { - return "Player{" + id + ", android=" + isAndroid + ", local=" + isLocal + ", " + x + ", " + y + "}\n"; + public Player(){ + hitbox.setSize(5); + hitboxTile.setSize(4f); } - @Override - public void writeSpawn(ByteBuffer buffer) { - buffer.put((byte)name.getBytes().length); - buffer.put(name.getBytes()); - buffer.put(weaponLeft.id); - buffer.put(weaponRight.id); - buffer.put(isAndroid ? 1 : (byte)0); - buffer.put(isAdmin ? 1 : (byte)0); - buffer.putInt(Color.rgba8888(color)); - buffer.putFloat(x); - buffer.putFloat(y); - } + //endregion - @Override - public void readSpawn(ByteBuffer buffer) { - byte nlength = buffer.get(); - byte[] n = new byte[nlength]; - buffer.get(n); - name = new String(n); - weaponLeft = (Weapon) Upgrade.getByID(buffer.get()); - weaponRight = (Weapon) Upgrade.getByID(buffer.get()); - isAndroid = buffer.get() == 1; - isAdmin = buffer.get() == 1; - color.set(buffer.getInt()); - x = buffer.getFloat(); - y = buffer.getFloat(); - setNet(x, y); - } + //region unit and event overrides, utility methods - @Override - public void write(ByteBuffer data) { - if(Net.client() || isLocal) { - data.putFloat(x); - data.putFloat(y); - }else{ - data.putFloat(interpolator.target.x); - data.putFloat(interpolator.target.y); - } - data.putFloat(angle); - data.putShort((short)health); - data.put((byte)(dashing ? 1 : 0)); - } + @Remote(in = In.entities, targets = Loc.server, called = Loc.server) + public static void onPlayerDamage(Player player, float amount){ + if(player == null) return; - @Override - public void read(ByteBuffer data, long time) { - float x = data.getFloat(); - float y = data.getFloat(); - float angle = data.getFloat(); - short health = data.getShort(); - byte dashing = data.get(); + player.hitTime = hitDuration; + player.health -= amount; + } - this.health = health; - this.dashing = dashing == 1; + @Remote(in = In.entities, targets = Loc.server, called = Loc.server) + public static void onPlayerDeath(Player player){ + if(player == null) return; - interpolator.read(this.x, this.y, x, y, angle, time); - } + player.dead = true; + player.placeQueue.clear(); - @Override - public void interpolate() { - super.interpolate(); + player.dropCarry(); - Interpolator i = interpolator; + float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f); + float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f); + Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize() / 2f, Palette.darkFlame); - float tx = x + Angles.trnsx(angle + 180f, 4f); - float ty = y + Angles.trnsy(angle + 180f, 4f); + ScorchDecal.create(player.x, player.y); + player.onDeath(); + } - if(isAndroid && i.target.dst(i.last) > 2f && timer.get(timerDash, 1)){ - Effects.effect(Fx.dashsmoke, tx, ty); - } + @Override + public Timer getTimer(){ + return timer; + } - if(dashing && !dead && timer.get(timerDash, 3) && i.target.dst(i.last) > 1f){ - Effects.effect(Fx.dashsmoke, tx, ty); - } - } + @Override + public int getShootTimer(boolean left){ + return left ? timerShootLeft : timerShootRight; + } - public Color getColor(){ - return color; - } + @Override + public Weapon getWeapon(){ + return mech.weapon; + } + + @Override + public float getMinePower(){ + return mech.mineSpeed; + } + + @Override + public TextureRegion getIconRegion(){ + return mech.iconRegion; + } + + @Override + public int getItemCapacity(){ + return mech.itemCapacity; + } + + @Override + public int getAmmoCapacity(){ + return mech.ammoCapacity; + } + + @Override + public void interpolate(){ + super.interpolate(); + + if(interpolator.values.length > 1){ + baseRotation = interpolator.values[1]; + } + + if(interpolator.target.dst(interpolator.last) > 1f){ + walktime += Timers.delta(); + } + } + + @Override + public CarriableTrait getCarry(){ + return carrying; + } + + @Override + public void setCarry(CarriableTrait unit){ + this.carrying = unit; + } + + @Override + public float getCarryWeight(){ + return mech.carryWeight; + } + + @Override + public float getBuildPower(Tile tile){ + return mech.buildPower; + } + + @Override + public float maxHealth(){ + return 200; + } + + @Override + public Tile getMineTile(){ + return mining; + } + + @Override + public void setMineTile(Tile tile){ + this.mining = tile; + } + + @Override + public float getArmor(){ + return mech.armor; + } + + @Override + public boolean acceptsAmmo(Item item){ + return mech.weapon.getAmmoType(item) != null && inventory.canAcceptAmmo(mech.weapon.getAmmoType(item)); + } + + @Override + public void added(){ + baseRotation = 90f; + } + + @Override + public void addAmmo(Item item){ + inventory.addAmmo(mech.weapon.getAmmoType(item)); + } + + @Override + public float getMass(){ + return mech.mass; + } + + @Override + public boolean isFlying(){ + return mech.flying || noclip || isCarried(); + } + + @Override + public float getSize(){ + return 8; + } + + @Override + public void damage(float amount){ + CallEntity.onPlayerDamage(this, calculateDamage(amount)); + + if(health <= 0 && !dead){ + CallEntity.onPlayerDeath(this); + } + } + + @Override + public boolean collides(SolidTrait other){ + return super.collides(other) || other instanceof ItemDrop; + } + + @Override + public void set(float x, float y){ + this.x = x; + this.y = y; + + if(isFlying() && isLocal){ + Core.camera.position.set(x, y, 0f); + } + } + + @Override + public void removed(){ + dropCarryLocal(); + + TileEntity core = getClosestCore(); + if(core != null && ((CoreEntity) core).currentUnit == this){ + ((CoreEntity) core).currentUnit = null; + } + } + + @Override + public EntityGroup targetGroup(){ + return playerGroup; + } + + //endregion + + //region draw methods + + @Override + public float drawSize(){ + return isLocal ? Float.MAX_VALUE : 40; + } + + @Override + public void drawShadow(){ + Draw.rect(mech.iconRegion, x + elevation * elevationScale, y - elevation * elevationScale, rotation - 90); + } + + @Override + public void draw(){ + if((debug && (!showPlayer || !showUI)) || dead) return; + + if(!movement.isZero() && moved){ + walktime += Timers.delta() * movement.len() / 0.7f * getFloorOn().speedMultiplier; + baseRotation = Mathf.slerpDelta(baseRotation, movement.angle(), 0.13f); + } + + boostHeat = Mathf.lerpDelta(boostHeat, isBoosting && ((!movement.isZero() && moved) || !isLocal) ? 1f : 0f, 0.08f); + + boolean snap = snapCamera && isLocal; + + float px = x, py = y; + + if(snap){ + x = (int) (x + 0.0001f); + y = (int) (y + 0.0001f); + } + + float ft = Mathf.sin(walktime, 6f, 2f) * (1f - boostHeat); + + Floor floor = getFloorOn(); + + Draw.color(); + Draw.alpha(hitTime / hitDuration); + + if(!mech.flying){ + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, 0.5f); + } + + float boostTrnsY = -boostHeat * 3f; + float boostTrnsX = boostHeat * 3f; + float boostAng = boostHeat * 40f; + + for(int i : Mathf.signs){ + Draw.rect(mech.legRegion, + x + Angles.trnsx(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i), + y + Angles.trnsy(baseRotation, ft * i + boostTrnsY, -boostTrnsX * i), + 12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90 + boostAng * i); + } + + Draw.rect(mech.baseRegion, x, y, baseRotation - 90); + } + + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f); + }else{ + Draw.tint(Color.WHITE); + } + + Draw.rect(mech.region, x, y, rotation - 90); + + for(int i : Mathf.signs){ + float tra = rotation - 90, trY = -mech.weapon.getRecoil(this, i > 0) + mech.weaponOffsetY; + float w = i > 0 ? -12 : 12; + Draw.rect(mech.weapon.equipRegion, + x + Angles.trnsx(tra, mech.weaponOffsetX * i, trY), + y + Angles.trnsy(tra, mech.weaponOffsetX * i, trY), w, 12, rotation - 90); + } + + float backTrns = 4f, itemSize = 5f; + if(inventory.hasItem()){ + ItemStack stack = inventory.getItem(); + int stored = Mathf.clamp(stack.amount / 6, 1, 8); + + for(int i = 0; i < stored; i++){ + float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 1, 60f); + float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 1f) - 1f; + Draw.rect(stack.item.region, + x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), + y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), + itemSize, itemSize, rotation); + } + } + + Draw.alpha(1f); + + x = px; + y = py; + } + + @Override + public void drawOver(){ + if(dead) return; + + if(!isShooting()){ + drawBuilding(this); + } + + if(mech.flying || boostHeat > 0.001f){ + float wobblyness = 0.6f; + trail.update(x + Angles.trnsx(rotation + 180f, 5f) + Mathf.range(wobblyness), + y + Angles.trnsy(rotation + 180f, 5f) + Mathf.range(wobblyness)); + trail.draw(mech.trailColor, 5f * (isFlying() ? 1f : boostHeat)); + }else{ + trail.clear(); + } + } + + public void drawName(){ + GlyphLayout layout = Pools.obtain(GlyphLayout.class); + + Draw.tscl(0.25f / 2); + layout.setText(Core.font, name); + Draw.color(0f, 0f, 0f, 0.3f); + Draw.rect("blank", x, y + 8 - layout.height / 2, layout.width + 2, layout.height + 2); + Draw.color(); + Draw.tcolor(color); + Draw.text(name, x, y + 8); + + if(isAdmin){ + Draw.color(color); + float s = 3f; + Draw.rect("icon-admin-small", x + layout.width / 2f + 2 + 1, y + 7f, s, s); + } + + Draw.reset(); + Pools.free(layout); + Draw.tscl(fontScale); + } + + /** + * Draw all current build requests. Does not draw the beam effect, only the positions. + */ + public void drawBuildRequests(){ + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + + if(request.remove){ + Block block = world.tile(request.x, request.y).target().block(); + + //draw removal request + Draw.color(Palette.remove); + + Lines.stroke((1f - request.progress)); + + Lines.poly(request.x * tilesize + block.offset(), + request.y * tilesize + block.offset(), + 4, block.size * tilesize / 2f, 45 + 15); + }else{ + //draw place request + Draw.color(Palette.accent); + + Lines.stroke((1f - request.progress)); + + Lines.poly(request.x * tilesize + request.recipe.result.offset(), + request.y * tilesize + request.recipe.result.offset(), + 4, request.recipe.result.size * tilesize / 2f, 45 + 15); + } + } + + Draw.reset(); + } + } + + //endregion + + //region update methods + + @Override + public void update(){ + hitTime = Math.max(0f, hitTime - Timers.delta()); + + if(isDead()){ + isBoosting = false; + boostHeat = 0f; + updateRespawning(); + return; + }else{ + spawner = -1; + } + + if(!isLocal){ + interpolate(); + updateBuilding(this); //building happens even with non-locals + status.update(this); //status effect updating also happens with non locals for effect purposes + + if(getCarrier() != null){ + x = getCarrier().getX(); + y = getCarrier().getY(); + } + + if(Net.server()){ + updateShooting(); //server simulates player shooting + } + return; + } + + if(mobile){ + updateFlying(); + }else{ + updateMech(); + } + + if(isLocal){ + avoidOthers(8f); + } + + if(!isShooting()){ + updateBuilding(this); + } + + x = Mathf.clamp(x, 0, world.width() * tilesize); + y = Mathf.clamp(y, 0, world.height() * tilesize); + } + + protected void updateMech(){ + Tile tile = world.tileWorld(x, y); + + //if player is in solid block + if(!mech.flying && tile != null && tile.solid() && !noclip){ + damage(health + 1); //die instantly + } + + float speed = isBoosting && !mech.flying ? debug ? 5f : mech.boostSpeed : mech.speed; + //fraction of speed when at max load + float carrySlowdown = 0.7f; + + speed *= ((inventory.hasItem() ? Mathf.lerp(1f, carrySlowdown, (float) inventory.getItem().amount / inventory.capacity()) : 1f)); + + if(mech.flying){ + //prevent strafing backwards, have a penalty for doing so + float angDist = Angles.angleDist(rotation, velocity.angle()) / 180f; + float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x + speed *= Mathf.lerp(1f, penalty, angDist); + } + + //drop from carrier on key press + if(!ui.chatfrag.chatOpen() && Inputs.keyTap("drop_unit")){ + if(!mech.flying){ + if(getCarrier() != null){ + CallEntity.dropSelf(this); + } + }else if(getCarry() != null){ + dropCarry(); + }else{ + Unit unit = Units.getClosest(team, x, y, 8f, + u -> !u.isFlying() && u.getMass() <= mech.carryWeight); + + if(unit != null){ + carry(unit); + } + } + } + + movement.setZero(); + + String section = control.input(playerIndex).section; + + float xa = Inputs.getAxis(section, "move_x"); + float ya = Inputs.getAxis(section, "move_y"); + + movement.y += ya * speed; + movement.x += xa * speed; + + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + pointerX = vec.x; + pointerY = vec.y; + updateShooting(); + + movement.limit(speed * Timers.delta()); + + if(getCarrier() == null){ + if(!ui.chatfrag.chatOpen()){ + velocity.add(movement); + } + float prex = x, prey = y; + updateVelocityStatus(mech.drag, 10f); + moved = distanceTo(prex, prey) > 0.01f; + }else{ + velocity.setZero(); + x = Mathf.lerpDelta(x, getCarrier().getX(), 0.1f); + y = Mathf.lerpDelta(y, getCarrier().getY(), 0.1f); + } + + if(!ui.chatfrag.chatOpen()){ + if(!isShooting()){ + if(!movement.isZero()){ + rotation = Mathf.slerpDelta(rotation, movement.angle(), 0.13f); + } + }else{ + float angle = control.input(playerIndex).mouseAngle(x, y); + this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f); + } + } + } + + protected void updateShooting(){ + if(isShooting()){ + mech.weapon.update(this, pointerX, pointerY); + } + } + + protected void updateFlying(){ + if(Units.invalidateTarget(target, this)){ + target = null; + } + + float targetX = Core.camera.position.x, targetY = Core.camera.position.y; + float attractDst = 15f; + + if(moveTarget != null && !moveTarget.isDead()){ + targetX = moveTarget.getX(); + targetY = moveTarget.getY(); + attractDst = 0f; + + if(distanceTo(moveTarget) < 2f){ + if(moveTarget instanceof CarriableTrait){ + carry((CarriableTrait) moveTarget); + }else if(moveTarget instanceof TileEntity && ((TileEntity) moveTarget).tile.block() instanceof MechFactory){ + Tile tile = ((TileEntity) moveTarget).tile; + tile.block().tapped(tile, this); + } + + moveTarget = null; + } + }else{ + moveTarget = null; + } + + movement.set(targetX - x, targetY - y).limit(mech.speed); + movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f)); + + if(distanceTo(targetX, targetY) < attractDst){ + movement.setZero(); + } + + velocity.add(movement); + + if(velocity.len() <= 0.2f){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 1f); + }else{ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len() / 10f); + } + + updateVelocityStatus(mech.drag, mech.maxSpeed); + + //hovering effect + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.08f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.08f); + + //update shooting if not building, not mining and there's ammo left + if(!isBuilding() && inventory.hasAmmo() && getMineTile() == null){ + + //autofire: mobile only! + if(mobile){ + + if(target == null){ + isShooting = false; + target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + }else if(target.isValid()){ + //rotate toward and shoot the target + rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f); + + Vector2 intercept = + Predict.intercept(x, y, target.getX(), target.getY(), target.getVelocity().x - velocity.x, target.getVelocity().y - velocity.y, inventory.getAmmo().bullet.speed); + + pointerX = intercept.x; + pointerY = intercept.y; + + updateShooting(); + isShooting = true; + } + + }else if(isShooting()){ + Vector2 vec = Graphics.world(Vars.control.input(playerIndex).getMouseX(), + Vars.control.input(playerIndex).getMouseY()); + pointerX = vec.x; + pointerY = vec.y; + + updateShooting(); + } + } + } + + //endregion + + //region utility methods + + public void toggleTeam(){ + team = (team == Team.blue ? Team.red : Team.blue); + } + + /** + * Resets all values of the player. + */ + public void reset(){ + status.clear(); + team = Team.blue; + inventory.clear(); + placeQueue.clear(); + dead = true; + trail.clear(); + health = maxHealth(); + mech = (mobile ? Mechs.starterMobile : Mechs.starterDesktop); + placeQueue.clear(); + + add(); + } + + public boolean isShooting(){ + return isShooting && inventory.hasAmmo() && (!isBoosting || mech.flying); + } + + public void updateRespawning(){ + + if(spawner != -1 && world.tile(spawner) != null && world.tile(spawner).entity instanceof SpawnerTrait){ + ((SpawnerTrait) world.tile(spawner).entity).updateSpawning(this); + }else{ + CoreEntity entity = (CoreEntity) getClosestCore(); + if(entity != null){ + this.spawner = entity.tile.id(); + } + } + } + + public void beginRespawning(SpawnerTrait spawner){ + this.spawner = spawner.getTile().packedPosition(); + this.dead = true; + } + + @Override + public Queue getPlaceQueue(){ + return placeQueue; + } + + @Override + public String toString(){ + return "Player{" + id + ", mech=" + mech.name + ", local=" + isLocal + ", " + x + ", " + y + "}\n"; + } + + //endregion + + //region read and write methods + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeBoolean(isLocal); + + if(isLocal){ + stream.writeByte(mech.id); + stream.writeByte(playerIndex); + super.writeSave(stream, false); + } + } + + @Override + public void readSave(DataInput stream) throws IOException{ + boolean local = stream.readBoolean(); + + if(local && !headless){ + byte mechid = stream.readByte(); + int index = stream.readByte(); + players[index].readSaveSuper(stream); + players[index].mech = Upgrade.getByID(mechid); + players[index].dead = false; + }else if(local){ + byte mechid = stream.readByte(); + stream.readByte(); + readSaveSuper(stream); + mech = Upgrade.getByID(mechid); + dead = false; + } + } + + private void readSaveSuper(DataInput stream) throws IOException{ + super.readSave(stream); + + add(); + } + + @Override + public void write(DataOutput buffer) throws IOException{ + super.writeSave(buffer, !isLocal); + buffer.writeUTF(name); //TODO writing strings is very inefficient + buffer.writeByte(Bits.toByte(isAdmin) | (Bits.toByte(dead) << 1) | (Bits.toByte(isBoosting) << 2)); + buffer.writeInt(Color.rgba8888(color)); + buffer.writeByte(mech.id); + buffer.writeInt(mining == null ? -1 : mining.packedPosition()); + buffer.writeInt(spawner); + buffer.writeShort((short) (baseRotation * 2)); + + writeBuilding(buffer); + } + + @Override + public void read(DataInput buffer, long time) throws IOException{ + float lastx = x, lasty = y, lastrot = rotation; + super.readSave(buffer); + name = buffer.readUTF(); + byte bools = buffer.readByte(); + isAdmin = (bools & 1) != 0; + dead = (bools & 2) != 0; + boolean boosting = (bools & 4) != 0; + color.set(buffer.readInt()); + mech = Upgrade.getByID(buffer.readByte()); + int mine = buffer.readInt(); + spawner = buffer.readInt(); + float baseRotation = buffer.readShort() / 2f; + + readBuilding(buffer, !isLocal); + + interpolator.read(lastx, lasty, x, y, time, rotation, baseRotation); + rotation = lastrot; + + if(isLocal){ + x = lastx; + y = lasty; + }else{ + mining = world.tile(mine); + isBoosting = boosting; + } + } + + //endregion } diff --git a/core/src/io/anuke/mindustry/entities/Predict.java b/core/src/io/anuke/mindustry/entities/Predict.java new file mode 100644 index 0000000000..06f86b540c --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/Predict.java @@ -0,0 +1,77 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.ucore.util.Mathf; + +/** + * Class for predicting shoot angles based on velocities of targets. + */ +public class Predict{ + private static Vector2 vec = new Vector2(); + private static Vector2 vresult = new Vector2(); + + /** + * Calculates of intercept of a stationary and moving target. Do not call from multiple threads! + * + * @param srcx X of shooter + * @param srcy Y of shooter + * @param dstx X of target + * @param dsty Y of target + * @param dstvx X velocity of target (subtract shooter X velocity if needed) + * @param dstvy Y velocity of target (subtract shooter Y velocity if needed) + * @param v speed of bullet + * @return the intercept location + */ + public static Vector2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float v){ + float tx = dstx - srcx, + ty = dsty - srcy; + + // Get quadratic equation components + float a = dstvx * dstvx + dstvy * dstvy - v * v; + float b = 2 * (dstvx * tx + dstvy * ty); + float c = tx * tx + ty * ty; + + // Solve quadratic + Vector2 ts = quad(a, b, c); + + // Find smallest positive solution + Vector2 sol = vresult.set(0, 0); + if(ts != null){ + float t0 = ts.x, t1 = ts.y; + float t = Math.min(t0, t1); + if(t < 0) t = Math.max(t0, t1); + if(t > 0){ + sol.set(dstx + dstvx * t, dsty + dstvy * t); + } + } + + return sol; + } + + /** + * See {@link #intercept(float, float, float, float, float, float, float)}. + */ + public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v){ + return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getVelocity().x - src.getVelocity().x, dst.getVelocity().x - src.getVelocity().y, v); + } + + private static Vector2 quad(float a, float b, float c){ + Vector2 sol = null; + if(Math.abs(a) < 1e-6){ + if(Math.abs(b) < 1e-6){ + sol = Math.abs(c) < 1e-6 ? vec.set(0, 0) : null; + }else{ + vec.set(-c / b, -c / b); + } + }else{ + float disc = b * b - 4 * a * c; + if(disc >= 0){ + disc = Mathf.sqrt(disc); + a = 2 * a; + sol = vec.set((-b - disc) / a, (-b + disc) / a); + } + } + return sol; + } +} diff --git a/core/src/io/anuke/mindustry/entities/StatusController.java b/core/src/io/anuke/mindustry/entities/StatusController.java new file mode 100644 index 0000000000..fe2e9e6248 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/StatusController.java @@ -0,0 +1,147 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.entities.traits.Saveable; +import io.anuke.mindustry.type.StatusEffect; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.util.Pooling; +import io.anuke.ucore.util.ThreadArray; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * Class for controlling status effects on an entity. + */ +public class StatusController implements Saveable{ + private static final StatusEntry globalResult = new StatusEntry(); + private static final Array removals = new ThreadArray<>(); + + private Array statuses = new ThreadArray<>(); + + private float speedMultiplier; + private float damageMultiplier; + private float armorMultiplier; + + public void handleApply(Unit unit, StatusEffect effect, float intensity){ + if(effect == StatusEffects.none) return; //don't apply empty effects + + float newTime = effect.baseDuration * intensity; + + if(statuses.size > 0){ + //check for opposite effects + for(StatusEntry entry : statuses){ + //extend effect + if(entry.effect == effect){ + entry.time = Math.max(entry.time, newTime); + return; + }else if(entry.effect.isOpposite(effect)){ //find opposite + entry.effect.getTransition(unit, effect, entry.time, newTime, globalResult); + entry.time = globalResult.time; + + if(globalResult.effect != entry.effect){ + entry.effect.onTransition(unit, globalResult.effect); + entry.effect = globalResult.effect; + } + + //stop looking when one is found + return; + } + } + } + + //otherwise, no opposites found, add direct effect + StatusEntry entry = Pooling.obtain(StatusEntry.class); + entry.set(effect, newTime); + statuses.add(entry); + } + + public void clear(){ + statuses.clear(); + } + + public void update(Unit unit){ + speedMultiplier = damageMultiplier = armorMultiplier = 1f; + + if(statuses.size == 0) return; + + removals.clear(); + + for(StatusEntry entry : statuses){ + entry.time = Math.max(entry.time - Timers.delta(), 0); + + if(entry.time <= 0){ + Pooling.free(entry); + removals.add(entry); + }else{ + speedMultiplier *= entry.effect.speedMultiplier; + armorMultiplier *= entry.effect.armorMultiplier; + damageMultiplier *= entry.effect.damageMultiplier; + entry.effect.update(unit, entry.time); + } + } + + if(removals.size > 0){ + statuses.removeAll(removals, true); + } + } + + public float getSpeedMultiplier(){ + return speedMultiplier; + } + + public float getDamageMultiplier(){ + return damageMultiplier; + } + + public float getArmorMultiplier(){ + return armorMultiplier; + } + + public boolean hasEffect(StatusEffect effect){ + for(StatusEntry entry : statuses){ + if(entry.effect == effect) return true; + } + return false; + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeByte(statuses.size); + for(StatusEntry entry : statuses){ + stream.writeByte(entry.effect.id); + stream.writeShort((short) (entry.time * 2)); + } + } + + @Override + public void readSave(DataInput stream) throws IOException{ + for(StatusEntry effect : statuses){ + Pooling.free(effect); + } + + statuses.clear(); + + byte amount = stream.readByte(); + for(int i = 0; i < amount; i++){ + byte id = stream.readByte(); + float time = stream.readShort() / 2f; + StatusEntry entry = Pooling.obtain(StatusEntry.class); + entry.set(StatusEffect.getByID(id), time); + statuses.add(entry); + } + } + + public static class StatusEntry{ + public StatusEffect effect; + public float time; + + public StatusEntry set(StatusEffect effect, float time){ + this.effect = effect; + this.time = time; + return this; + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/StatusEffect.java b/core/src/io/anuke/mindustry/entities/StatusEffect.java deleted file mode 100644 index f84d076f3d..0000000000 --- a/core/src/io/anuke/mindustry/entities/StatusEffect.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.anuke.mindustry.entities; - -public enum StatusEffect{ - none; -} diff --git a/core/src/io/anuke/mindustry/entities/SyncEntity.java b/core/src/io/anuke/mindustry/entities/SyncEntity.java deleted file mode 100644 index f24186e66b..0000000000 --- a/core/src/io/anuke/mindustry/entities/SyncEntity.java +++ /dev/null @@ -1,139 +0,0 @@ -package io.anuke.mindustry.entities; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.utils.ObjectIntMap; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.DestructibleEntity; -import io.anuke.ucore.util.Mathf; - -import java.nio.ByteBuffer; - -import static io.anuke.mindustry.Vars.threads; - -public abstract class SyncEntity extends DestructibleEntity{ - private static ObjectIntMap> writeSizes = new ObjectIntMap<>(); - - protected transient Interpolator interpolator = new Interpolator(); - - //smoothed position/angle - private Vector3 spos = new Vector3(); - - public float angle; - - static{ - setWriteSize(Enemy.class, 4 + 4 + 2 + 2); - setWriteSize(Player.class, 4 + 4 + 4 + 2 + 1); - } - - public static boolean isSmoothing(){ - return threads.isEnabled() && threads.getFPS() <= Gdx.graphics.getFramesPerSecond() / 2f; - } - - public abstract void writeSpawn(ByteBuffer data); - public abstract void readSpawn(ByteBuffer data); - - public abstract void write(ByteBuffer data); - public abstract void read(ByteBuffer data, long time); - - public void interpolate(){ - interpolator.update(); - - x = interpolator.pos.x; - y = interpolator.pos.y; - angle = interpolator.angle; - } - - @Override - public final void draw(){ - final float x = this.x, y = this.y, angle = this.angle; - - //interpolates data at low tick speeds. - if(isSmoothing()){ - if(Vector2.dst(spos.x, spos.y, x, y) > 128){ - spos.set(x, y, angle); - } - - this.x = spos.x = Mathf.lerpDelta(spos.x, x, 0.2f); - this.y = spos.y = Mathf.lerpDelta(spos.y, y, 0.2f); - this.angle = spos.z = Mathf.slerpDelta(spos.z, angle, 0.3f); - } - - drawSmooth(); - - this.x = x; - this.y = y; - this.angle = angle; - } - - public Vector3 getDrawPosition(){ - return isSmoothing() ? spos : spos.set(x, y, angle); - } - - public void drawSmooth(){} - - public int getWriteSize(){ - return getWriteSize(getClass()); - } - - public static int getWriteSize(Class type){ - int i = writeSizes.get(type, -1); - if(i == -1) throw new RuntimeException("Write size for class \"" + type + "\" is not defined!"); - return i; - } - - protected static void setWriteSize(Class type, int size){ - writeSizes.put(type, size); - } - - public T setNet(float x, float y){ - set(x, y); - interpolator.target.set(x, y); - interpolator.last.set(x, y); - interpolator.spacing = 1f; - interpolator.time = 0f; - return (T)this; - } - - public static class Interpolator { - //used for movement - public Vector2 target = new Vector2(); - public Vector2 last = new Vector2(); - public float targetrot; - public float spacing = 1f; - public float time; - - //current state - public Vector2 pos = new Vector2(); - public float angle; - - public void read(float cx, float cy, float x, float y, float angle, long sent){ - targetrot = angle; - time = 0f; - last.set(cx, cy); - target.set(x, y); - spacing = Math.min(Math.max(((TimeUtils.timeSinceMillis(sent) / 1000f) * 60f), 4f), 10); - } - - public void update(){ - - time += 1f / spacing * Math.min(Timers.delta(), 1f); - - time = Mathf.clamp(time, 0, 2f); - - Mathf.lerp2(pos.set(last), target, time); - - angle = Mathf.slerpDelta(angle, targetrot, 0.6f); - - if(target.dst(pos) > 128){ - pos.set(target); - last.set(target); - time = 0f; - } - - } - } -} diff --git a/core/src/io/anuke/mindustry/entities/TileEntity.java b/core/src/io/anuke/mindustry/entities/TileEntity.java index adf41edaa2..d4695604dc 100644 --- a/core/src/io/anuke/mindustry/entities/TileEntity.java +++ b/core/src/io/anuke/mindustry/entities/TileEntity.java @@ -1,15 +1,30 @@ package io.anuke.mindustry.entities; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.mindustry.resource.Item; +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallBlocks; +import io.anuke.mindustry.net.In; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Edges; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.types.Wall; +import io.anuke.mindustry.world.blocks.Wall; +import io.anuke.mindustry.world.consumers.Consume; +import io.anuke.mindustry.world.modules.ConsumeModule; +import io.anuke.mindustry.world.modules.InventoryModule; +import io.anuke.mindustry.world.modules.LiquidModule; +import io.anuke.mindustry.world.modules.PowerModule; import io.anuke.ucore.core.Effects; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entity; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.BaseEntity; import io.anuke.ucore.util.Mathf; import io.anuke.ucore.util.Timer; @@ -20,134 +35,219 @@ import java.io.IOException; import static io.anuke.mindustry.Vars.tileGroup; import static io.anuke.mindustry.Vars.world; -public class TileEntity extends Entity{ - public Tile tile; - public int[] items = new int[Item.getAllItems().size]; - public Timer timer; - public float health; - public boolean dead = false; - public boolean added; - - /**Sets this tile entity data to this tile, and adds it if necessary.*/ - public TileEntity init(Tile tile, boolean added){ - this.tile = tile; - this.added = added; - x = tile.worldx(); - y = tile.worldy(); +public class TileEntity extends BaseEntity implements TargetTrait{ + public static final float timeToSleep = 60f * 4; //4 seconds to fall asleep + private static final ObjectSet tmpTiles = new ObjectSet<>(); + /** + * This value is only used for debugging. + */ + public static int sleepingEntities = 0; + public Tile tile; + public Timer timer; + public float health; - health = tile.block().health; - - timer = new Timer(tile.block().timers); - - if(added){ - add(); - } - - return this; - } - - public void write(DataOutputStream stream) throws IOException{ - - } - - public void read(DataInputStream stream) throws IOException{ - - } + public PowerModule power; + public InventoryModule items; + public LiquidModule liquids; + public ConsumeModule cons; - public void readNetwork(DataInputStream stream, float elapsed) throws IOException{ - read(stream); - } - - public void onDeath(){ - onDeath(false); - } + /**List of (cached) tiles with entities in proximity, used for outputting to*/ + private Array proximity = new Array<>(8); + private boolean dead = false; + private boolean sleeping; + private float sleepTime; - public void onDeath(boolean force){ - if(Net.server()){ - NetEvents.handleBlockDestroyed(this); - } + @Remote(called = Loc.server, in = In.blocks) + public static void onTileDamage(Tile tile, float health){ + if(tile.entity != null){ + tile.entity.health = health; + } + } - if(!Net.active() || Net.server() || force){ + @Remote(called = Loc.server, in = In.blocks) + public static void onTileDestroyed(Tile tile){ + if(tile.entity == null) return; + tile.entity.onDeath(); + } - if(!dead) { - dead = true; - Block block = tile.block(); + /**Sets this tile entity data to this tile, and adds it if necessary.*/ + public TileEntity init(Tile tile, boolean added){ + this.tile = tile; + x = tile.drawx(); + y = tile.drawy(); - block.onDestroyed(tile); + health = tile.block().health; - world.removeBlock(tile); - remove(); - } - } - } - - public void collision(Bullet other){ - damage(other.getDamage()); - } - - public void damage(int damage){ - if(dead) return; - - int amount = tile.block().handleDamage(tile, damage); - health -= amount; - if(health <= 0) onDeath(); + timer = new Timer(tile.block().timers); - if(Net.server()){ - NetEvents.handleBlockDamaged(this); - } - } - - public boolean collide(Bullet other){ - return true; - } - - @Override - public void update(){ - synchronized (Tile.tileSetLock) { - if (health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) && - Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))) { + if(added){ + add(); + } - Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4)); - } + return this; + } - if (health <= 0) { - onDeath(); - } + /** + * Call when nothing is happening to the entity. + * This increments the internal sleep timer. + */ + public void sleep(){ + sleepTime += Timers.delta(); + if(!sleeping && sleepTime >= timeToSleep){ + remove(); + sleeping = true; + sleepingEntities++; + } + } - tile.block().update(tile); - } - } - - public int totalItems(){ - int sum = 0; - for(int i = 0; i < items.length; i ++){ - sum += items[i]; - } - return sum; - } - - public int getItem(Item item){ - return items[item.id]; - } - - public boolean hasItem(Item item){ - return getItem(item) > 0; - } - - public boolean hasItem(Item item, int amount){ - return getItem(item) >= amount; - } - - public void addItem(Item item, int amount){ - items[item.id] += amount; - } - - public void removeItem(Item item, int amount){ - items[item.id] -= amount; - } - - @Override - public TileEntity add(){ - return add(tileGroup); - } + /** + * Call when something just happened to the entity. + * If the entity was sleeping, this enables it. This also resets the sleep timer. + */ + public void wakeUp(){ + sleepTime = 0f; + if(sleeping){ + add(); + sleeping = false; + sleepingEntities--; + } + } + + public boolean isSleeping(){ + return sleeping; + } + + public boolean isDead(){ + return dead; + } + + public void write(DataOutputStream stream) throws IOException{ + } + + public void read(DataInputStream stream) throws IOException{ + } + + private void onDeath(){ + if(!dead){ + dead = true; + Block block = tile.block(); + + block.onDestroyed(tile); + world.removeBlock(tile); + block.afterDestroyed(tile, this); + remove(); + } + } + + public boolean collide(Bullet other){ + return true; + } + + public void collision(Bullet other){ + tile.block().handleBulletHit(this, other); + } + + public void damage(float damage){ + if(dead) return; + + CallBlocks.onTileDamage(tile, health - tile.block().handleDamage(tile, damage)); + + if(health <= 0){ + CallBlocks.onTileDestroyed(tile); + } + } + + public Tile getTile(){ + return tile; + } + + public boolean consumed(Class type){ + return tile.block().consumes.get(type).valid(tile.block(), this); + } + + public void removeFromProximity(){ + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for(GridPoint2 point : nearby){ + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + //remove this tile from all nearby tile's proximities + if(other != null){ + other = other.target(); + other.block().onProximityUpdate(other); + } + if(other != null && other.entity != null){ + other.entity.proximity.removeValue(tile, true); + } + } + } + + public void updateProximity(){ + tmpTiles.clear(); + proximity.clear(); + + GridPoint2[] nearby = Edges.getEdges(tile.block().size); + for(GridPoint2 point : nearby){ + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + + if(other != null){ + other.block().onProximityUpdate(other); + other = other.target(); + } + + if(other != null && other.entity != null){ + tmpTiles.add(other); + + //add this tile to proximity of nearby tiles + if(!other.entity.proximity.contains(tile, true)){ + other.entity.proximity.add(tile); + } + } + } + + //using a set to prevent duplicates + for(Tile tile : tmpTiles){ + proximity.add(tile); + } + + tile.block().onProximityUpdate(tile); + } + + public Array proximity(){ + return proximity; + } + + @Override + public Team getTeam(){ + return tile.getTeam(); + } + + @Override + public Vector2 getVelocity(){ + return Vector2.Zero; + } + + @Override + public void update(){ + synchronized(Tile.tileSetLock){ + //TODO better smoke effect, this one is awful + if(health != 0 && health < tile.block().health && !(tile.block() instanceof Wall) && + Mathf.chance(0.009f * Timers.delta() * (1f - health / tile.block().health))){ + + Effects.effect(Fx.smoke, x + Mathf.range(4), y + Mathf.range(4)); + } + + if(health <= 0){ + onDeath(); + } + + tile.block().update(tile); + if(cons != null){ + cons.update(this); + } + } + } + + @Override + public EntityGroup targetGroup(){ + return tileGroup; + } } diff --git a/core/src/io/anuke/mindustry/entities/Unit.java b/core/src/io/anuke/mindustry/entities/Unit.java new file mode 100644 index 0000000000..8ad6360042 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/Unit.java @@ -0,0 +1,352 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.entities.traits.*; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.net.Interpolator; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.StatusEffect; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityPhysics; +import io.anuke.ucore.entities.impl.DestructibleEntity; +import io.anuke.ucore.entities.trait.DamageTrait; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.SolidTrait; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.world; + +public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait{ + /** + * total duration of hit flash effect + */ + public static final float hitDuration = 9f; + /** + * Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks. + */ + public static final float velocityPercision = 8f; + /** + * Maximum absolute value of a velocity vector component. + */ + public static final float maxAbsVelocity = 127f / velocityPercision; + public static final float elevationScale = 4f; + + private static final Vector2 moveVector = new Vector2(); + + public UnitInventory inventory = new UnitInventory(this); + public float rotation; + + protected Interpolator interpolator = new Interpolator(); + protected StatusController status = new StatusController(); + protected Team team = Team.blue; + + protected CarryTrait carrier; + protected Vector2 velocity = new Translator(0f, 0.0001f); + protected float hitTime; + protected float drownTime; + protected float elevation; + + @Override + public UnitInventory getInventory(){ + return inventory; + } + + @Override + public float getRotation(){ + return rotation; + } + + @Override + public void setRotation(float rotation){ + this.rotation = rotation; + } + + @Override + public CarryTrait getCarrier(){ + return carrier; + } + + @Override + public void setCarrier(CarryTrait carrier){ + this.carrier = carrier; + } + + @Override + public Team getTeam(){ + return team; + } + + @Override + public void interpolate(){ + interpolator.update(); + + x = interpolator.pos.x; + y = interpolator.pos.y; + + if(interpolator.values.length > 0){ + rotation = interpolator.values[0]; + } + } + + @Override + public Interpolator getInterpolator(){ + return interpolator; + } + + @Override + public void damage(float amount){ + super.damage(calculateDamage(amount)); + hitTime = hitDuration; + } + + @Override + public boolean collides(SolidTrait other){ + return other instanceof DamageTrait && other + instanceof TeamTrait && state.teams.areEnemies((((TeamTrait) other).getTeam()), team) && !isDead(); + } + + @Override + public void onDeath(){ + inventory.clear(); + drownTime = 0f; + status.clear(); + } + + @Override + public Vector2 getVelocity(){ + return velocity; + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + writeSave(stream, false); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + byte team = stream.readByte(); + boolean dead = stream.readBoolean(); + float x = stream.readFloat(); + float y = stream.readFloat(); + byte xv = stream.readByte(); + byte yv = stream.readByte(); + float rotation = stream.readShort() / 2f; + int health = stream.readShort(); + + this.status.readSave(stream); + this.inventory.readSave(stream); + this.dead = dead; + this.team = Team.all[team]; + this.health = health; + this.x = x; + this.y = y; + this.velocity.set(xv / velocityPercision, yv / velocityPercision); + this.rotation = rotation; + } + + public void writeSave(DataOutput stream, boolean net) throws IOException{ + stream.writeByte(team.ordinal()); + stream.writeBoolean(isDead()); + stream.writeFloat(net ? interpolator.target.x : x); + stream.writeFloat(net ? interpolator.target.y : y); + stream.writeByte((byte) (Mathf.clamp(velocity.x, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeByte((byte) (Mathf.clamp(velocity.y, -maxAbsVelocity, maxAbsVelocity) * velocityPercision)); + stream.writeShort((short) (rotation * 2)); + stream.writeShort((short) health); + status.writeSave(stream); + inventory.writeSave(stream); + } + + public float calculateDamage(float amount){ + return amount * Mathf.clamp(1f - getArmor() / 100f * status.getArmorMultiplier()); + } + + public float getDamageMultipler(){ + return status.getDamageMultiplier(); + } + + public boolean hasEffect(StatusEffect effect){ + return status.hasEffect(effect); + } + + public TileEntity getClosestCore(){ + if(state.teams.has(team)){ + TeamData data = state.teams.get(team); + + Tile tile = Geometry.findClosest(x, y, data.cores); + if(tile == null){ + return null; + }else{ + return tile.entity; + } + }else{ + return null; + } + } + + public Floor getFloorOn(){ + Tile tile = world.tileWorld(x, y); + return tile == null ? (Floor) Blocks.air : tile.floor(); + } + + public void avoidOthers(float avoidRange){ + + EntityPhysics.getNearby(getGroup(), x, y, avoidRange * 2f, t -> { + if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) + return; + float dst = distanceTo(t); + if(dst > avoidRange) return; + velocity.add(moveVector.set(x, y).sub(t.getX(), t.getY()).setLength(1f * (1f - (dst / avoidRange)))); + }); + } + + /** + * Updates velocity and status effects. + */ + public void updateVelocityStatus(float drag, float maxVelocity){ + if(isCarried()){ //carried units do not take into account velocity normally + set(carrier.getX(), carrier.getY()); + velocity.set(carrier.getVelocity()); + return; + } + + Floor floor = getFloorOn(); + Tile tile = world.tileWorld(x, y); + + status.update(this); + + velocity.limit(maxVelocity).scl(status.getSpeedMultiplier()); + + if(isFlying()){ + x += velocity.x / getMass() * Timers.delta(); + y += velocity.y / getMass() * Timers.delta(); + + if(tile != null){ + elevation = Mathf.lerpDelta(elevation, tile.elevation, 0.04f); + } + }else{ + boolean onLiquid = floor.isLiquid; + + if(tile != null){ + tile.block().unitOn(tile, this); + if(tile.block() != Blocks.air){ + onLiquid = false; + } + + //on slope + if(tile.elevation == -1){ + velocity.scl(0.7f); + } + } + + if(onLiquid && velocity.len() > 0.4f && Timers.get(this, "flooreffect", 14 - (velocity.len() * floor.speedMultiplier) * 2f)){ + Effects.effect(floor.walkEffect, floor.liquidColor, x, y); + } + + status.handleApply(this, floor.status, floor.statusIntensity); + + if(floor.damageTaken > 0f){ + damagePeriodic(floor.damageTaken); + } + + if(onLiquid && floor.drownTime > 0){ + drownTime += Timers.delta() * 1f / floor.drownTime; + if(Timers.get(this, "drowneffect", 15)){ + Effects.effect(floor.drownUpdateEffect, floor.liquidColor, x, y); + } + }else{ + drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f); + } + + drownTime = Mathf.clamp(drownTime); + + if(drownTime >= 1f){ + damage(health + 1); + } + + float px = x, py = y; + move(velocity.x / getMass() * floor.speedMultiplier * Timers.delta(), velocity.y / getMass() * floor.speedMultiplier * Timers.delta()); + if(Math.abs(px - x) <= 0.0001f) velocity.x = 0f; + if(Math.abs(py - y) <= 0.0001f) velocity.y = 0f; + } + + velocity.scl(Mathf.clamp(1f - drag * floor.dragMultiplier * Timers.delta())); + } + + public void applyEffect(StatusEffect effect, float intensity){ + if(dead || Net.client()) return; //effects are synced and thus not applied through clients + status.handleApply(this, effect, intensity); + } + + public void damagePeriodic(float amount){ + damage(amount * Timers.delta(), Timers.get(this, "damageeffect", 20)); + } + + public void damage(float amount, boolean withEffect){ + float pre = hitTime; + + damage(amount); + + if(!withEffect){ + hitTime = pre; + } + } + + public float getAmmoFraction(){ + return inventory.totalAmmo() / (float) inventory.ammoCapacity(); + } + + public void drawUnder(){ + } + + public void drawOver(){ + } + + public void drawShadow(){ + } + + public void drawView(){ + Fill.circle(x, y, getViewDistance()); + } + + public boolean isInfiniteAmmo(){ + return false; + } + + public float getViewDistance(){ + return 135f; + } + + public abstract TextureRegion getIconRegion(); + + public abstract int getItemCapacity(); + + public abstract int getAmmoCapacity(); + + public abstract float getArmor(); + + public abstract boolean acceptsAmmo(Item item); + + public abstract void addAmmo(Item item); + + public abstract float getMass(); + + public abstract boolean isFlying(); + + public abstract float getSize(); +} diff --git a/core/src/io/anuke/mindustry/entities/UnitInventory.java b/core/src/io/anuke/mindustry/entities/UnitInventory.java new file mode 100644 index 0000000000..53fd627f88 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/UnitInventory.java @@ -0,0 +1,163 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.entities.traits.Saveable; +import io.anuke.mindustry.type.AmmoEntry; +import io.anuke.mindustry.type.AmmoType; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class UnitInventory implements Saveable{ + private final Unit unit; + private Array ammos = new Array<>(); + private int totalAmmo; + private ItemStack item = new ItemStack(Items.stone, 0); + + public UnitInventory(Unit unit){ + this.unit = unit; + } + + public boolean isFull(){ + return item != null && item.amount >= unit.getItemCapacity(); + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeShort(item.amount); + stream.writeByte(item.item.id); + stream.writeShort(totalAmmo); + stream.writeByte(ammos.size); + for(int i = 0; i < ammos.size; i++){ + stream.writeByte(ammos.get(i).type.id); + stream.writeShort(ammos.get(i).amount); + } + } + + @Override + public void readSave(DataInput stream) throws IOException{ + short iamount = stream.readShort(); + byte iid = stream.readByte(); + this.totalAmmo = stream.readShort(); + byte ammoa = stream.readByte(); + for(int i = 0; i < ammoa; i++){ + byte aid = stream.readByte(); + int am = stream.readShort(); + ammos.add(new AmmoEntry(AmmoType.getByID(aid), am)); + } + + item.item = Item.getByID(iid); + item.amount = iamount; + } + + /** + * Returns ammo range, or MAX_VALUE if this inventory has no ammo. + */ + public float getAmmoRange(){ + return hasAmmo() ? getAmmo().getRange() : Float.MAX_VALUE; + } + + public AmmoType getAmmo(){ + return ammos.size == 0 ? null : ammos.peek().type; + } + + public boolean hasAmmo(){ + return totalAmmo > 0; + } + + public void useAmmo(){ + if(unit.isInfiniteAmmo()) return; + AmmoEntry entry = ammos.peek(); + entry.amount--; + if(entry.amount == 0) ammos.pop(); + totalAmmo--; + } + + public int totalAmmo(){ + return totalAmmo; + } + + public int ammoCapacity(){ + return unit.getAmmoCapacity(); + } + + public boolean canAcceptAmmo(AmmoType type){ + return totalAmmo + type.quantityMultiplier <= unit.getAmmoCapacity(); + } + + public void addAmmo(AmmoType type){ + if(type == null) return; + totalAmmo += type.quantityMultiplier; + + //find ammo entry by type + for(int i = ammos.size - 1; i >= 0; i--){ + AmmoEntry entry = ammos.get(i); + + //if found, put it to the right + if(entry.type == type){ + entry.amount += type.quantityMultiplier; + ammos.swap(i, ammos.size - 1); + return; + } + } + + //must not be found + AmmoEntry entry = new AmmoEntry(type, (int) type.quantityMultiplier); + ammos.add(entry); + } + + public int capacity(){ + return unit.getItemCapacity(); + } + + public boolean isEmpty(){ + return item.amount == 0; + } + + public int itemCapacityUsed(Item type){ + if(canAcceptItem(type)){ + return !hasItem() ? unit.getItemCapacity() : (unit.getItemCapacity() - item.amount); + }else{ + return unit.getItemCapacity(); + } + } + + public boolean canAcceptItem(Item type){ + return (!hasItem() && 1 <= unit.getItemCapacity()) || (item.item == type && unit.getItemCapacity() - item.amount > 0); + } + + public boolean canAcceptItem(Item type, int amount){ + return (!hasItem() && amount <= unit.getItemCapacity()) || (item.item == type && item.amount + amount <= unit.getItemCapacity()); + } + + public void clear(){ + item.amount = 0; + ammos.clear(); + totalAmmo = 0; + } + + public void clearItem(){ + item.amount = 0; + } + + public boolean hasItem(){ + return item.amount > 0; + } + + public boolean hasItem(Item i, int amount){ + return item.item == i && item.amount >= amount; + } + + public void addItem(Item item, int amount){ + getItem().amount = getItem().item == item ? getItem().amount + amount : amount; + getItem().item = item; + } + + public ItemStack getItem(){ + return item; + } +} diff --git a/core/src/io/anuke/mindustry/entities/Units.java b/core/src/io/anuke/mindustry/entities/Units.java new file mode 100644 index 0000000000..1b9fcb27f2 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/Units.java @@ -0,0 +1,307 @@ +package io.anuke.mindustry.entities; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.EntityPhysics; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; + +import static io.anuke.mindustry.Vars.*; + +/** + * Utility class for unit and team interactions. + */ +public class Units{ + private static Rectangle rect = new Rectangle(); + private static Rectangle hitrect = new Rectangle(); + private static Unit result; + private static float cdist; + private static boolean boolResult; + + /** + * Validates a target. + * + * @param target The target to validate + * @param team The team of the thing doing tha targeting + * @param x The X position of the thing doign the targeting + * @param y The Y position of the thing doign the targeting + * @param range The maximum distance from the target X/Y the targeter can be for it to be valid + * @return whether the target is invalid + */ + public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y, float range){ + return target == null || (range != Float.MAX_VALUE && target.distanceTo(x, y) > range) || target.getTeam() == team || !target.isValid(); + + } + + /** + * See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} + */ + public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){ + return invalidateTarget(target, team, x, y, Float.MAX_VALUE); + } + + /** + * See {@link #invalidateTarget(TargetTrait, Team, float, float, float)} + */ + public static boolean invalidateTarget(TargetTrait target, Unit targeter){ + return invalidateTarget(target, targeter.team, targeter.x, targeter.y, targeter.inventory.getAmmoRange()); + } + + /** + * Returns whether there are any entities on this tile. + */ + public static boolean anyEntities(Tile tile){ + Block type = tile.block(); + rect.setSize(type.size * tilesize, type.size * tilesize); + rect.setCenter(tile.drawx(), tile.drawy()); + + boolResult = false; + + Units.getNearby(rect, unit -> { + if(boolResult) return; + if(!unit.isFlying()){ + unit.getHitbox(hitrect); + + if(hitrect.overlaps(rect)){ + boolResult = true; + } + } + }); + + return boolResult; + } + + /** + * Returns whether there are any entities on this tile, with the hitbox expanded. + */ + public static boolean anyEntities(Tile tile, float expansion, Predicate pred){ + Block type = tile.block(); + rect.setSize(type.size * tilesize + expansion, type.size * tilesize + expansion); + rect.setCenter(tile.drawx(), tile.drawy()); + + boolean[] value = new boolean[1]; + + Units.getNearby(rect, unit -> { + if(value[0] || !pred.test(unit) || unit.isDead()) return; + if(!unit.isFlying()){ + unit.getHitbox(hitrect); + + if(hitrect.overlaps(rect)){ + value[0] = true; + } + } + }); + + return value[0]; + } + + /** + * Returns the neareset ally tile in a range. + */ + public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate pred){ + for(Team enemy : state.teams.alliesOf(team)){ + TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred); + if(entity != null){ + return entity; + } + } + return null; + } + + /** + * Returns the neareset enemy tile in a range. + */ + public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate pred){ + for(Team enemy : state.teams.enemiesOf(team)){ + TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred); + if(entity != null){ + return entity; + } + } + return null; + } + + /** + * Iterates over all units on all teams, including players. + */ + public static void allUnits(Consumer cons){ + //check all unit groups first + for(EntityGroup group : unitGroups){ + if(!group.isEmpty()){ + for(BaseUnit unit : group.all()){ + cons.accept(unit); + } + } + } + + //then check all player groups + for(Player player : playerGroup.all()){ + cons.accept(player); + } + } + + /** + * Returns the closest target enemy. First, units are checked, then tile entities. + */ + public static TargetTrait getClosestTarget(Team team, float x, float y, float range){ + Unit unit = getClosestEnemy(team, x, y, range, u -> true); + if(unit != null){ + return unit; + }else{ + return findEnemyTile(team, x, y, range, tile -> true); + } + } + + /** + * Returns the closest enemy of this team. Filter by predicate. + */ + public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate predicate){ + result = null; + cdist = 0f; + + rect.setSize(range * 2f).setCenter(x, y); + + getNearbyEnemies(team, rect, e -> { + if(e.isDead() || !predicate.test(e)) + return; + + float dist = Vector2.dst(e.x, e.y, x, y); + if(dist < range){ + if(result == null || dist < cdist){ + result = e; + cdist = dist; + } + } + }); + + return result; + } + + /** + * Returns the closest ally of this team. Filter by predicate. + */ + public static Unit getClosest(Team team, float x, float y, float range, Predicate predicate){ + result = null; + cdist = 0f; + + rect.setSize(range * 2f).setCenter(x, y); + + getNearby(team, rect, e -> { + if(!predicate.test(e)) + return; + + float dist = Vector2.dst(e.x, e.y, x, y); + if(dist < range){ + if(result == null || dist < cdist){ + result = e; + cdist = dist; + } + } + }); + + return result; + } + + /** + * Iterates over all units in a rectangle. + */ + public static void getNearby(Team team, Rectangle rect, Consumer cons){ + + EntityGroup group = unitGroups[team.ordinal()]; + if(!group.isEmpty()){ + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); + } + + //now check all players + EntityPhysics.getNearby(playerGroup, rect, player -> { + if(((Unit) player).team == team) cons.accept((Unit) player); + }); + } + + /** + * Iterates over all units in a circle around this position. + */ + public static void getNearby(Team team, float x, float y, float radius, Consumer cons){ + rect.setSize(radius * 2).setCenter(x, y); + + EntityGroup group = unitGroups[team.ordinal()]; + if(!group.isEmpty()){ + EntityPhysics.getNearby(group, rect, entity -> { + if(entity.distanceTo(x, y) <= radius){ + cons.accept((Unit) entity); + } + }); + } + + //now check all players + EntityPhysics.getNearby(playerGroup, rect, player -> { + if(((Unit) player).team == team && player.distanceTo(x, y) <= radius){ + cons.accept((Unit) player); + } + }); + } + + /** + * Iterates over all units in a rectangle. + */ + public static void getNearby(Rectangle rect, Consumer cons){ + + for(Team team : Team.all){ + EntityGroup group = unitGroups[team.ordinal()]; + if(!group.isEmpty()){ + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); + } + } + + //now check all enemy players + EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit) player)); + } + + /** + * Iterates over all units that are enemies of this team. + */ + public static void getNearbyEnemies(Team team, Rectangle rect, Consumer cons){ + ObjectSet targets = state.teams.enemiesOf(team); + + for(Team other : targets){ + EntityGroup group = unitGroups[other.ordinal()]; + if(!group.isEmpty()){ + EntityPhysics.getNearby(group, rect, entity -> cons.accept((Unit) entity)); + } + } + + //now check all enemy players + EntityPhysics.getNearby(playerGroup, rect, player -> { + if(targets.contains(((Player) player).team)){ + cons.accept((Unit) player); + } + }); + } + + /** + * Iterates over all units. + */ + public static void getAllUnits(Consumer cons){ + + for(Team team : Team.all){ + EntityGroup group = unitGroups[team.ordinal()]; + for(Unit unit : group.all()){ + cons.accept(unit); + } + } + + //now check all enemy players + for(Unit unit : playerGroup.all()){ + cons.accept(unit); + } + } + + +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java new file mode 100644 index 0000000000..52a1594c96 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/ArtilleryBulletType.java @@ -0,0 +1,41 @@ +package io.anuke.mindustry.entities.bullet; + +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; + +//TODO scale velocity depending on fslope() +public class ArtilleryBulletType extends BasicBulletType{ + protected Effect trailEffect = BulletFx.artilleryTrail; + + public ArtilleryBulletType(float speed, float damage, String bulletSprite){ + super(speed, damage, bulletSprite); + collidesTiles = false; + collides = false; + hitShake = 1f; + } + + @Override + public void update(Bullet b){ + super.update(b); + + if(b.timer.get(0, 3 + b.fslope() * 2f)){ + Effects.effect(trailEffect, backColor, b.x, b.y, b.fslope() * 4f); + } + } + + @Override + public void draw(Bullet b){ + float baseScale = 0.7f; + float scale = (baseScale + b.fslope() * (1f - baseScale)); + + float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout()); + + Draw.color(backColor); + Draw.rect(backRegion, b.x, b.y, bulletWidth * scale, height * scale, b.angle() - 90); + Draw.color(frontColor); + Draw.rect(frontRegion, b.x, b.y, bulletWidth * scale, height * scale, b.angle() - 90); + Draw.color(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java new file mode 100644 index 0000000000..d5d1455eb0 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/BasicBulletType.java @@ -0,0 +1,109 @@ +package io.anuke.mindustry.entities.bullet; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.entities.Damage; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +/** + * A BulletType for most ammo-based bullets shot from turrets and units. + */ +public class BasicBulletType extends BulletType{ + public Color backColor = Palette.bulletYellowBack, frontColor = Palette.bulletYellow; + public float bulletWidth = 5f, bulletHeight = 7f; + public float bulletShrink = 0.5f; + public String bulletSprite; + + public int fragBullets = 9; + public float fragVelocityMin = 0.2f, fragVelocityMax = 1f; + public BulletType fragBullet = null; + + /** + * Use a negative value to disable splash damage. + */ + public float splashDamageRadius = -1f; + public float splashDamage = 6f; + + public int incendAmount = 0; + public float incendSpread = 8f; + public float incendChance = 1f; + + public float homingPower = 0f; + public float homingRange = 40f; + + public TextureRegion backRegion; + public TextureRegion frontRegion; + + public float hitShake = 0f; + + public BasicBulletType(float speed, float damage, String bulletSprite){ + super(speed, damage); + this.bulletSprite = bulletSprite; + } + + @Override + public void load(){ + backRegion = Draw.region(bulletSprite + "-back"); + frontRegion = Draw.region(bulletSprite); + } + + @Override + public void draw(Bullet b){ + float height = bulletHeight * ((1f - bulletShrink) + bulletShrink * b.fout()); + + Draw.color(backColor); + Draw.rect(backRegion, b.x, b.y, bulletWidth, height, b.angle() - 90); + Draw.color(frontColor); + Draw.rect(frontRegion, b.x, b.y, bulletWidth, height, b.angle() - 90); + Draw.color(); + } + + @Override + public void update(Bullet b){ + super.update(b); + + if(homingPower > 0.0001f){ + Unit target = Units.getClosestEnemy(b.getTeam(), b.x, b.y, homingRange, unit -> true); + if(target != null){ + b.getVelocity().setAngle(Angles.moveToward(b.getVelocity().angle(), b.angleTo(target), homingPower * Timers.delta())); + } + } + } + + @Override + public void hit(Bullet b, float x, float y){ + super.hit(b, x, y); + + Effects.shake(hitShake, hitShake, b); + + if(fragBullet != null){ + for(int i = 0; i < fragBullets; i++){ + float len = Mathf.random(1f, 7f); + float a = Mathf.random(360f); + Bullet.create(fragBullet, b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax)); + } + } + + if(Mathf.chance(incendChance)){ + Damage.createIncend(x, y, incendSpread, incendAmount); + } + + if(splashDamageRadius > 0){ + Damage.damage(b.getTeam(), x, y, splashDamageRadius, splashDamage); + } + } + + @Override + public void despawned(Bullet b){ + if(fragBullet != null || splashDamageRadius > 0){ + hit(b); + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java new file mode 100644 index 0000000000..cdca80de79 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/BombBulletType.java @@ -0,0 +1,16 @@ +package io.anuke.mindustry.entities.bullet; + +public class BombBulletType extends BasicBulletType{ + + public BombBulletType(float damage, float radius, String sprite){ + super(0.7f, 0, sprite); + splashDamageRadius = radius; + splashDamage = damage; + collidesTiles = false; + collides = false; + bulletShrink = 0.7f; + lifetime = 30f; + drag = 0.05f; + keepVelocity = false; + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/Bullet.java b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java new file mode 100644 index 0000000000..0e9322928f --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/Bullet.java @@ -0,0 +1,225 @@ +package io.anuke.mindustry.entities.bullet; + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.entities.traits.TeamTrait; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.BulletEntity; +import io.anuke.ucore.entities.trait.Entity; +import io.anuke.ucore.entities.trait.SolidTrait; +import io.anuke.ucore.entities.trait.VelocityTrait; +import io.anuke.ucore.util.Pooling; +import io.anuke.ucore.util.Timer; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.bulletGroup; +import static io.anuke.mindustry.Vars.world; + +public class Bullet extends BulletEntity implements TeamTrait, SyncTrait{ + private static Vector2 vector = new Vector2(); + public Timer timer = new Timer(3); + private Team team; + private Object data; + private boolean supressCollision; + + /** + * Internal use only! + */ + public Bullet(){ + } + + public static void create(BulletType type, TeamTrait owner, float x, float y, float angle){ + create(type, owner, owner.getTeam(), x, y, angle); + } + + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle){ + create(type, owner, team, x, y, angle, 1f); + } + + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl){ + create(type, owner, team, x, y, angle, velocityScl, null); + } + + public static void create(BulletType type, Entity owner, Team team, float x, float y, float angle, float velocityScl, Object data){ + Bullet bullet = Pooling.obtain(Bullet.class); + bullet.type = type; + bullet.owner = owner; + bullet.data = data; + + bullet.velocity.set(0, type.speed).setAngle(angle).scl(velocityScl); + if(type.keepVelocity){ + bullet.velocity.add(owner instanceof VelocityTrait ? ((VelocityTrait) owner).getVelocity() : Vector2.Zero); + } + bullet.hitbox.setSize(type.hitsize); + + bullet.team = team; + bullet.type = type; + + //translate bullets backwards, purely for visual reasons + float backDelta = Timers.delta(); + + bullet.lastPosition().set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta, bullet.angle()); + bullet.setLastUpdated(TimeUtils.millis()); + bullet.setUpdateSpacing((long) ((Timers.delta() / 60f) * 1000)); + bullet.set(x - bullet.velocity.x * backDelta, y - bullet.velocity.y * backDelta); + + bullet.add(); + } + + public static void create(BulletType type, Bullet parent, float x, float y, float angle){ + create(type, parent.owner, parent.team, x, y, angle); + } + + public static void create(BulletType type, Bullet parent, float x, float y, float angle, float velocityScl){ + create(type, parent.owner, parent.team, x, y, angle, velocityScl); + } + + @Remote(called = Loc.server, in = In.entities) + public static void createBullet(BulletType type, float x, float y, float angle){ + create(type, null, Team.none, x, y, angle); + } + + public boolean collidesTiles(){ + return type.collidesTiles; + } + + public void supressCollision(){ + supressCollision = true; + } + + public void resetOwner(Entity entity, Team team){ + this.owner = entity; + this.team = team; + } + + public void scaleTime(float add){ + time += add; + } + + public Object getData(){ + return data; + } + + @Override + public float getDamage(){ + if(owner instanceof Unit){ + return super.getDamage() * ((Unit) owner).getDamageMultipler(); + } + + return super.getDamage(); + } + + @Override + public boolean isSyncing(){ + return type.syncable; + } + + @Override + public void write(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + data.writeFloat(velocity.x); + data.writeFloat(velocity.y); + data.writeByte(team.ordinal()); + data.writeByte(type.id); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + x = data.readFloat(); + y = data.readFloat(); + velocity.x = data.readFloat(); + velocity.y = data.readFloat(); + team = Team.all[data.readByte()]; + type = BulletType.getByID(data.readByte()); + } + + @Override + public Team getTeam(){ + return team; + } + + @Override + public void draw(){ + type.draw(this); + } + + @Override + public float drawSize(){ + return 8; + } + + @Override + public boolean collides(SolidTrait other){ + return type.collides && super.collides(other); + } + + @Override + public void collision(SolidTrait other, float x, float y){ + super.collision(other, x, y); + + if(other instanceof Unit){ + Unit unit = (Unit) other; + unit.getVelocity().add(vector.set(other.getX(), other.getY()).sub(x, y).setLength(type.knockback / unit.getMass())); + unit.applyEffect(type.status, type.statusIntensity); + } + } + + @Override + public void update(){ + super.update(); + + if(type.hitTiles && collidesTiles() && !supressCollision){ + world.raycastEach(world.toTile(lastPosition().x), world.toTile(lastPosition().y), world.toTile(x), world.toTile(y), (x, y) -> { + + Tile tile = world.tile(x, y); + if(tile == null) return false; + tile = tile.target(); + + if(tile.entity != null && tile.entity.collide(this) && !tile.entity.isDead() && tile.entity.tile.getTeam() != team){ + tile.entity.collision(this); + + if(!supressCollision){ + type.hit(this); + remove(); + } + + return true; + } + + return false; + }); + } + + supressCollision = false; + } + + @Override + public void reset(){ + super.reset(); + timer.clear(); + team = null; + data = null; + } + + @Override + public void removed(){ + Pooling.free(this); + } + + @Override + public EntityGroup targetGroup(){ + return bulletGroup; + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/BulletType.java b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java new file mode 100644 index 0000000000..d4d750035d --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/BulletType.java @@ -0,0 +1,91 @@ +package io.anuke.mindustry.entities.bullet; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.type.StatusEffect; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.entities.impl.BaseBulletType; + +public abstract class BulletType extends BaseBulletType implements Content{ + private static int lastid = 0; + private static Array types = new Array<>(); + + public final int id; + /** + * Knockback in velocity. + */ + public float knockback; + /** + * Whether this bullet hits tiles. + */ + public boolean hitTiles = true; + /** + * Status effect applied on hit. + */ + public StatusEffect status = StatusEffects.none; + /** + * Intensity of applied status effect in terms of duration. + */ + public float statusIntensity = 0.5f; + /** + * What fraction of armor is pierced, 0-1 + */ + public float armorPierce = 0f; + /** + * Whether to sync this bullet to clients. + */ + public boolean syncable; + /** + * Whether this bullet type collides with tiles. + */ + public boolean collidesTiles = true; + /** + * Whether this bullet types collides with anything at all. + */ + public boolean collides = true; + /** + * Whether velocity is inherited from the shooter. + */ + public boolean keepVelocity = true; + + public BulletType(float speed, float damage){ + this.id = lastid++; + this.speed = speed; + this.damage = damage; + lifetime = 40f; + hiteffect = BulletFx.hitBulletSmall; + despawneffect = BulletFx.despawn; + + types.add(this); + } + + public static BulletType getByID(int id){ + return types.get(id); + } + + public static Array all(){ + return types; + } + + @Override + public void hit(Bullet b, float hitx, float hity){ + Effects.effect(hiteffect, hitx, hity, b.angle()); + } + + @Override + public void despawned(Bullet b){ + Effects.effect(despawneffect, b.x, b.y, b.angle()); + } + + @Override + public String getContentTypeName(){ + return "bullettype"; + } + + @Override + public Array getAll(){ + return types; + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java new file mode 100644 index 0000000000..3b034962d8 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/LiquidBulletType.java @@ -0,0 +1,53 @@ +package io.anuke.mindustry.entities.bullet; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.GridPoint2; +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.effect.Fire; +import io.anuke.mindustry.entities.effect.Puddle; +import io.anuke.mindustry.type.Liquid; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.world; + +public class LiquidBulletType extends BulletType{ + Liquid liquid; + + public LiquidBulletType(Liquid liquid){ + super(2.5f, 0); + this.liquid = liquid; + + lifetime = 70f; + despawneffect = Fx.none; + hiteffect = BulletFx.hitLiquid; + drag = 0.01f; + knockback = 0.5f; + } + + @Override + public void draw(Bullet b){ + Draw.color(liquid.color, Color.WHITE, b.fout() / 100f + Mathf.randomSeedRange(b.id, 0.1f)); + + Fill.circle(b.x, b.y, 0.5f + b.fout() * 2.5f); + } + + @Override + public void hit(Bullet b, float hitx, float hity){ + Effects.effect(hiteffect, liquid.color, hitx, hity); + Puddle.deposit(world.tileWorld(hitx, hity), liquid, 5f); + + if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){ + float intensity = 400f; + Fire.extinguish(world.tileWorld(hitx, hity), intensity); + for(GridPoint2 p : Geometry.d4){ + Fire.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity); + } + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java new file mode 100644 index 0000000000..6179d2e6a3 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/bullet/MissileBulletType.java @@ -0,0 +1,24 @@ +package io.anuke.mindustry.entities.bullet; + +import io.anuke.mindustry.content.fx.BulletFx; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.ucore.core.Effects; + +public class MissileBulletType extends BasicBulletType{ + + public MissileBulletType(float speed, float damage, String bulletSprite){ + super(speed, damage, bulletSprite); + backColor = Palette.missileYellowBack; + frontColor = Palette.missileYellow; + homingPower = 6f; + } + + @Override + public void update(Bullet b){ + super.update(b); + + if(b.timer.get(0, 4f)){ + Effects.effect(BulletFx.missileTrail, b.x, b.y, 2f); + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/DamageArea.java b/core/src/io/anuke/mindustry/entities/effect/DamageArea.java deleted file mode 100644 index c1ce0382f9..0000000000 --- a/core/src/io/anuke/mindustry/entities/effect/DamageArea.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.anuke.mindustry.entities.effect; - -import com.badlogic.gdx.math.Rectangle; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Effects.Effect; -import io.anuke.ucore.entities.DestructibleEntity; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.entities.SolidEntity; -import io.anuke.ucore.function.Consumer; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Physics; -import io.anuke.ucore.util.Translator; - -import static io.anuke.mindustry.Vars.*; - -public class DamageArea{ - private static Rectangle rect = new Rectangle(); - private static Translator tr = new Translator(); - - //only for entities, not tiles (yet!) - public static void damageLine(Entity owner, Effect effect, float x, float y, float angle, float length, int damage){ - tr.trns(angle, length); - rect.setPosition(x, y).setSize(tr.x, tr.y); - float x2 = tr.x + x, y2 = tr.y + y; - - if(rect.width < 0){ - rect.x += rect.width; - rect.width *= -1; - } - - if(rect.height < 0){ - rect.y += rect.height; - rect.height *= -1; - } - - float expand = 3f; - - rect.y -= expand; - rect.x -= expand; - rect.width += expand*2; - rect.height += expand*2; - - Consumer cons = e -> { - if(e == owner || (e instanceof Player && ((Player)e).isAndroid)) return; - DestructibleEntity enemy = (DestructibleEntity) e; - Rectangle other = enemy.hitbox.getRect(enemy.x, enemy.y); - other.y -= expand; - other.x -= expand; - other.width += expand * 2; - other.height += expand * 2; - - Vector2 vec = Physics.raycastRect(x, y, x2, y2, other); - - if (vec != null) { - Effects.effect(effect, vec.x, vec.y); - enemy.damage(damage); - } - }; - - Entities.getNearby(enemyGroup, rect, cons); - if(state.friendlyFire) Entities.getNearby(playerGroup, rect, cons); - } - - public static void damageEntities(float x, float y, float radius, int damage){ - damage(true, x, y, radius, damage); - - for(Player player : playerGroup.all()){ - if(player.isAndroid) continue; - int amount = calculateDamage(x, y, player.x, player.y, radius, damage); - player.damage(amount); - } - } - - public static void damage(boolean enemies, float x, float y, float radius, int damage){ - Consumer cons = entity -> { - DestructibleEntity enemy = (DestructibleEntity)entity; - if(enemy.distanceTo(x, y) > radius || (entity instanceof Player && ((Player)entity).isAndroid)){ - return; - } - int amount = calculateDamage(x, y, enemy.x, enemy.y, radius, damage); - enemy.damage(amount); - }; - - if(enemies){ - Entities.getNearby(enemyGroup, x, y, radius*2, cons); - }else{ - int trad = (int)(radius / tilesize); - for(int dx = -trad; dx <= trad; dx ++){ - for(int dy= -trad; dy <= trad; dy ++){ - Tile tile = world.tile(Mathf.scl2(x, tilesize) + dx, Mathf.scl2(y, tilesize) + dy); - if(tile != null && tile.entity != null && Vector2.dst(dx, dy, 0, 0) <= trad){ - int amount = calculateDamage(x, y, tile.worldx(), tile.worldy(), radius, damage); - tile.entity.damage(amount); - } - } - } - - Entities.getNearby(playerGroup, x, y, radius*2, cons); - } - } - - static int calculateDamage(float x, float y, float tx, float ty, float radius, int damage){ - float dist = Vector2.dst(x, y, tx, ty); - float scaled = 1f - dist/radius; - return (int)(damage * scaled); - } -} diff --git a/core/src/io/anuke/mindustry/entities/effect/Decal.java b/core/src/io/anuke/mindustry/entities/effect/Decal.java new file mode 100644 index 0000000000..35bd82f240 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/Decal.java @@ -0,0 +1,42 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.mindustry.entities.traits.BelowLiquidTrait; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.TimedEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.groundEffectGroup; + +/** + * Class for creating block rubble on the ground. + */ +public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{ + private static final Color color = Color.valueOf("52504e"); + + @Override + public float lifetime(){ + return 8200f; + } + + @Override + public void draw(){ + Draw.color(color.r, color.g, color.b, 1f - Mathf.curve(fin(), 0.98f)); + drawDecal(); + Draw.color(); + } + + @Override + public EntityGroup targetGroup(){ + return groundEffectGroup; + } + + @Override + public float fin() { + return time / lifetime(); + } + + abstract void drawDecal(); +} diff --git a/core/src/io/anuke/mindustry/entities/effect/EMP.java b/core/src/io/anuke/mindustry/entities/effect/EMP.java deleted file mode 100644 index e7afc7b366..0000000000 --- a/core/src/io/anuke/mindustry/entities/effect/EMP.java +++ /dev/null @@ -1,120 +0,0 @@ -package io.anuke.mindustry.entities.effect; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.types.PowerAcceptor; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.entities.TimedEntity; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Translator; - -import static io.anuke.mindustry.Vars.tilesize; -import static io.anuke.mindustry.Vars.world; - -public class EMP extends TimedEntity{ - static final int maxTargets = 8; - static Array array = new Array<>(); - static Translator tr = new Translator(); - - int radius = 4; - int damage = 6; - Array targets = new Array<>(maxTargets); - - public EMP(float x, float y, int damage){ - this.damage = damage; - set(x, y); - - lifetime = 30f; - - int worldx = Mathf.scl2(x, tilesize); - int worldy = Mathf.scl2(y, tilesize); - - array.clear(); - - for(int dx = -radius; dx <= radius; dx ++){ - for(int dy = -radius; dy <= radius; dy ++){ - if(Vector2.dst(dx, dy, 0, 0) < radius){ - Tile tile = world.tile(worldx + dx, worldy + dy); - - if(tile != null && tile.block().destructible){ - array.add(tile); - } - } - } - } - - array.shuffle(); - - for(int i = 0; i < array.size && i < maxTargets; i ++){ - Tile tile = array.get(i); - targets.add(tile); - - if(tile != null && tile.block() instanceof PowerAcceptor){ - PowerAcceptor p = (PowerAcceptor)tile.block(); - p.setPower(tile, 0f); - tile.entity.damage((int)(damage*2f)); //extra damage - } - - if(tile == null) continue; - - //entity may be null here, after the block is dead! - Effects.effect(Fx.empspark, tile.worldx(), tile.worldy()); - if(tile.entity != null) tile.entity.damage(damage); - } - } - - @Override - public void drawOver(){ - Draw.color(Color.SKY); - - for(int i = 0; i < targets.size; i ++){ - Tile target = targets.get(i); - - drawLine(target.worldx(), target.worldy()); - - float rad = 5f*fout(); - Draw.rect("circle", target.worldx(), target.worldy(), rad, rad); - } - - for(int i = 0; i < 14 - targets.size; i ++){ - tr.trns(Mathf.randomSeed(i + id*77)*360f, radius * tilesize); - drawLine(x + tr.x, y + tr.y); - } - - Lines.stroke(fout()*2f); - Lines.poly(x, y, 34, radius * tilesize); - - Draw.reset(); - } - - private void drawLine(float targetx, float targety){ - int joints = 3; - float r = 3f; - float lastx = x, lasty = y; - - for(int seg = 0; seg < joints; seg ++){ - float dx = Mathf.range(r), - dy = Mathf.range(r); - - float frac = (seg+1f)/joints; - - float tx = (targetx - x)*frac + x + dx, - ty = (targety - y)*frac + y + dy; - - drawLaser(lastx, lasty, tx, ty); - - lastx = tx; - lasty = ty; - } - } - - private void drawLaser(float x, float y, float x2, float y2){ - Lines.stroke(fout() * 2f); - Lines.line(x, y, x2, y2); - } -} diff --git a/core/src/io/anuke/mindustry/entities/effect/Fire.java b/core/src/io/anuke/mindustry/entities/effect/Fire.java new file mode 100644 index 0000000000..e933e5dfbf --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/Fire.java @@ -0,0 +1,211 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.Pool.Poolable; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.content.bullets.TurretBullets; +import io.anuke.mindustry.content.fx.EnvironmentFx; +import io.anuke.mindustry.entities.Damage; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.traits.SaveTrait; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.TimedEntity; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Pooling; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.*; + +public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable{ + private static final IntMap map = new IntMap<>(); + private static final float baseLifetime = 1000f; + + private int loadedPosition = -1; + private Tile tile; + private Block block; + private float baseFlammability = -1, puddleFlammability; + private float lifetime; + + /** + * Deserialization use only! + */ + public Fire(){ + } + + /** + * Start a fire on the tile. If there already is a file there, refreshes its lifetime. + */ + public static void create(Tile tile){ + if(Net.client() || tile == null) return; //not clientside. + + Fire fire = map.get(tile.packedPosition()); + + if(fire == null){ + fire = Pooling.obtain(Fire.class); + fire.tile = tile; + fire.lifetime = baseLifetime; + fire.set(tile.worldx(), tile.worldy()); + fire.add(); + map.put(tile.packedPosition(), fire); + }else{ + fire.lifetime = baseLifetime; + fire.time = 0f; + } + } + + /** + * Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing. + */ + public static void extinguish(Tile tile, float intensity){ + if(tile != null && map.containsKey(tile.packedPosition())){ + map.get(tile.packedPosition()).time += intensity * Timers.delta(); + } + } + + @Remote(called = Loc.server, in = In.entities) + public static void onFireRemoved(int fireid){ + fireGroup.removeByID(fireid); + } + + @Override + public float lifetime(){ + return lifetime; + } + + @Override + public void update(){ + if(Mathf.chance(0.1 * Timers.delta())){ + Effects.effect(EnvironmentFx.fire, x + Mathf.range(4f), y + Mathf.range(4f)); + } + + if(Mathf.chance(0.05 * Timers.delta())){ + Effects.effect(EnvironmentFx.smoke, x + Mathf.range(4f), y + Mathf.range(4f)); + } + + if(Net.client()){ + return; + } + + time = Mathf.clamp(time + Timers.delta(), 0, lifetime()); + + if(time >= lifetime() || tile == null){ + CallEntity.onFireRemoved(getID()); + remove(); + return; + } + + TileEntity entity = tile.target().entity; + boolean damage = entity != null; + + float flammability = baseFlammability + puddleFlammability; + + if(!damage && flammability <= 0){ + time += Timers.delta() * 8; + } + + if(baseFlammability < 0 || block != tile.block()){ + baseFlammability = tile.block().getFlammability(tile); + block = tile.block(); + } + + if(damage){ + lifetime += Mathf.clamp(flammability / 8f, 0f, 0.6f) * Timers.delta(); + } + + if(flammability > 1f && Mathf.chance(0.03 * Timers.delta() * Mathf.clamp(flammability / 5f, 0.3f, 2f))){ + GridPoint2 p = Mathf.select(Geometry.d4); + Tile other = world.tile(tile.x + p.x, tile.y + p.y); + create(other); + + if(Mathf.chance(0.05 * Timers.delta() * Mathf.clamp(flammability / 10.0))){ + CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f)); + } + } + + if(Mathf.chance(0.1 * Timers.delta())){ + Puddle p = Puddle.getPuddle(tile); + if(p != null){ + puddleFlammability = p.getFlammability() / 3f; + }else{ + puddleFlammability = 0; + } + + if(damage){ + entity.damage(0.4f); + } + Damage.damageUnits(null, tile.worldx(), tile.worldy(), tilesize, 3f, unit -> !unit.isFlying(), unit -> unit.applyEffect(StatusEffects.burning, 0.8f)); + } + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeInt(tile.packedPosition()); + stream.writeFloat(lifetime); + stream.writeFloat(time); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + this.loadedPosition = stream.readInt(); + this.lifetime = stream.readFloat(); + this.time = stream.readFloat(); + add(); + } + + @Override + public void write(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + x = data.readFloat(); + y = data.readFloat(); + } + + @Override + public void reset(){ + loadedPosition = -1; + tile = null; + baseFlammability = -1; + puddleFlammability = 0f; + } + + @Override + public void added(){ + if(loadedPosition != -1){ + map.put(loadedPosition, this); + tile = world.tile(loadedPosition); + set(tile.worldx(), tile.worldy()); + } + } + + @Override + public void removed(){ + if(tile != null){ + map.remove(tile.packedPosition()); + } + reset(); + } + + @Override + public EntityGroup targetGroup(){ + return fireGroup; + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java b/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java new file mode 100644 index 0000000000..889c04a7ce --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/GroundEffectEntity.java @@ -0,0 +1,90 @@ +package io.anuke.mindustry.entities.effect; + +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.impl.EffectEntity; +import io.anuke.ucore.function.EffectRenderer; +import io.anuke.ucore.util.Mathf; + +/** + * A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer. + */ +public class GroundEffectEntity extends EffectEntity{ + private boolean once; + + @Override + public void update(){ + GroundEffect effect = (GroundEffect) this.effect; + + if(effect.isStatic){ + time += Timers.delta(); + + time = Mathf.clamp(time, 0, effect.staticLife); + + if(!once && time >= lifetime()){ + once = true; + time = 0f; + Tile tile = Vars.world.tileWorld(x, y); + if(tile != null && tile.floor().isLiquid){ + remove(); + } + }else if(once && time >= effect.staticLife){ + remove(); + } + }else{ + super.update(); + } + } + + @Override + public void draw(){ + GroundEffect effect = (GroundEffect) this.effect; + + if(once && effect.isStatic) + Effects.renderEffect(id, effect, color, lifetime(), rotation, x, y, data); + else + Effects.renderEffect(id, effect, color, time, rotation, x, y, data); + } + + @Override + public void reset(){ + super.reset(); + once = false; + } + + /** + * An effect that is rendered on the ground layer as opposed to the top layer. + */ + public static class GroundEffect extends Effect{ + /** + * How long this effect stays on the ground when static. + */ + public final float staticLife; + /** + * If true, this effect will stop and lie on the ground for a specific duration, + * after its initial lifetime is over. + */ + public final boolean isStatic; + + public GroundEffect(float life, float staticLife, EffectRenderer draw){ + super(life, draw); + this.staticLife = staticLife; + this.isStatic = true; + } + + public GroundEffect(boolean isStatic, float life, EffectRenderer draw){ + super(life, draw); + this.staticLife = 0f; + this.isStatic = isStatic; + } + + public GroundEffect(float life, EffectRenderer draw){ + super(life, draw); + this.staticLife = 0f; + this.isStatic = false; + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java new file mode 100644 index 0000000000..284b66420d --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/ItemDrop.java @@ -0,0 +1,246 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Pool.Poolable; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.fx.UnitFx; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.traits.SaveTrait; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Interpolator; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.SolidEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.SolidTrait; +import io.anuke.ucore.entities.trait.TimeTrait; +import io.anuke.ucore.entities.trait.VelocityTrait; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Mathf; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.*; + +public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawTrait, VelocityTrait, TimeTrait, TargetTrait, Poolable{ + private static final float sinkLifetime = 80f; + + private Interpolator interpolator = new Interpolator(); + private Item item; + private int amount; + + private Vector2 velocity = new Vector2(); + private float time; + private float sinktime; + + /** + * Internal use only! + */ + public ItemDrop(){ + hitbox.setSize(5f); + hitboxTile.setSize(5f); + } + + public static ItemDrop create(Item item, int amount, float x, float y, float angle){ + ItemDrop drop = new ItemDrop(); + drop.item = item; + drop.amount = amount; + drop.velocity.set(4f, 0f).rotate(angle); + drop.setNet(x, y); + drop.add(); + + return drop; + } + + public static void create(Item item, int amount, float x, float y, float velocityX, float velocityY){ + create(item, amount, x, y, 0).getVelocity().set(velocityX, velocityY); + } + + @Remote(called = Loc.server, in = In.entities) + public static void onPickup(int itemid){ + ItemDrop drop = itemGroup.getByID(itemid); + if(drop != null){ + Effects.effect(UnitFx.pickup, drop); + } + itemGroup.removeByID(itemid); + if(Net.client()){ + netClient.addRemovedEntity(itemid); + } + } + + public Item getItem(){ + return item; + } + + public int getAmount(){ + return amount; + } + + @Override + public boolean isDead(){ + return !isAdded(); + } + + @Override + public Team getTeam(){ + return Team.none; + } + + @Override + public float lifetime(){ + return 60 * 60; + } + + @Override + public void time(float time){ + this.time = time; + } + + @Override + public float time(){ + return time; + } + + @Override + public Vector2 getVelocity(){ + return velocity; + } + + @Override + public boolean collides(SolidTrait other){ + return other instanceof Player && time > 20f; + } + + @Override + public void collision(SolidTrait other, float x, float y){ + Unit player = (Unit) other; + if(player.inventory.canAcceptItem(item, 1)){ + int used = Math.min(amount, player.inventory.capacity() - player.inventory.getItem().amount); + player.inventory.addItem(item, used); + amount -= used; + + if(amount <= 0){ + CallEntity.onPickup(getID()); + } + } + } + + @Override + public void draw(){ + float size = itemSize * (1f - sinktime / sinkLifetime) * (1f - Mathf.curve(fin(), 0.98f)); + + Tile tile = world.tileWorld(x, y); + + Draw.color(Color.WHITE, tile == null || !tile.floor().isLiquid ? Color.WHITE : tile.floor().liquidColor, sinktime / sinkLifetime); + Draw.rect(item.region, x, y, size, size); + + int stored = Mathf.clamp(amount / 6, 1, 8); + + for(int i = 0; i < stored; i++){ + float px = stored == 1 ? 0 : Mathf.randomSeedRange(i + 1, 4f); + float py = stored == 1 ? 0 : Mathf.randomSeedRange(i + 2, 4f); + Draw.rect(item.region, x + px, y + py, size, size); + } + + Draw.color(); + } + + @Override + public void update(){ + if(Net.client()){ + interpolate(); + }else{ + updateVelocity(0.2f); + updateTime(); + if(time >= lifetime()){ + CallEntity.onPickup(getID()); + } + } + + Tile tile = world.tileWorld(x, y); + + if(tile != null && tile.solid()){ + CallEntity.onPickup(getID()); + } + + if(tile != null && tile.floor().isLiquid){ + sinktime += Timers.delta(); + + if(Mathf.chance(0.04 * Timers.delta())){ + Effects.effect(tile.floor().drownUpdateEffect, tile.floor().liquidColor, x, y); + } + + if(sinktime >= sinkLifetime){ + remove(); + } + }else{ + sinktime = 0f; + } + } + + @Override + public void reset(){ + time = 0f; + interpolator.reset(); + } + + @Override + public Interpolator getInterpolator(){ + return interpolator; + } + + @Override + public float drawSize(){ + return 10; + } + + @Override + public EntityGroup targetGroup(){ + return itemGroup; + } + + @Override + public void writeSave(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + data.writeByte(item.id); + data.writeShort(amount); + } + + @Override + public void readSave(DataInput data) throws IOException{ + x = data.readFloat(); + y = data.readFloat(); + item = Item.getByID(data.readByte()); + amount = data.readShort(); + add(); + } + + @Override + public void write(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + data.writeByte(item.id); + data.writeShort(amount); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + interpolator.read(x, y, data.readFloat(), data.readFloat(), time); + item = Item.getByID(data.readByte()); + amount = data.readShort(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java new file mode 100644 index 0000000000..5351f5af64 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/ItemTransfer.java @@ -0,0 +1,142 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.math.Vector2; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.TimedEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.PosTrait; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Pooling; + +import static io.anuke.mindustry.Vars.effectGroup; +import static io.anuke.mindustry.Vars.threads; + +public class ItemTransfer extends TimedEntity implements DrawTrait{ + private Vector2 from = new Vector2(); + private Vector2 current = new Vector2(); + private Vector2 tovec = new Vector2(); + private Item item; + private float seed; + private PosTrait to; + private Runnable done; + + public ItemTransfer(){ + } + + @Remote(in = In.entities, called = Loc.server, unreliable = true) + public static void transferAmmo(Item item, float x, float y, Unit to){ + if(to == null) return; + to.addAmmo(item); + create(item, x, y, to, () -> { + }); + } + + @Remote(in = In.entities, called = Loc.server, unreliable = true) + public static void transferItemEffect(Item item, float x, float y, Unit to){ + if(to == null) return; + create(item, x, y, to, () -> { + }); + } + + @Remote(in = In.entities, called = Loc.server, unreliable = true) + public static void transferItemToUnit(Item item, float x, float y, Unit to){ + if(to == null) return; + create(item, x, y, to, () -> to.inventory.addItem(item, 1)); + } + + @Remote(in = In.entities, called = Loc.server) + public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){ + if(tile == null) return; + for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){ + Timers.run(i * 3, () -> create(item, x, y, tile, () -> { + })); + } + tile.entity.items.add(item, amount); + } + + public static void create(Item item, float fromx, float fromy, PosTrait to, Runnable done){ + ItemTransfer tr = Pooling.obtain(ItemTransfer.class); + tr.item = item; + tr.from.set(fromx, fromy); + tr.to = to; + tr.done = done; + tr.seed = Mathf.range(1f); + tr.add(); + } + + @Override + public float lifetime(){ + return 60; + } + + @Override + public void reset(){ + super.reset(); + item = null; + to = null; + done = null; + from.setZero(); + current.setZero(); + tovec.setZero(); + } + + @Override + public void removed(){ + if(done != null){ + threads.run(done); + } + Pooling.free(this); + } + + @Override + public void update(){ + if(to == null){ + remove(); + return; + } + + super.update(); + current.set(from).interpolate(tovec.set(to.getX(), to.getY()), fin(), Interpolation.pow3); + current.add(tovec.set(to.getX(), to.getY()).sub(from).nor().rotate90(1).scl(seed * fslope() * 10f)); + set(current.x, current.y); + } + + @Override + public void draw(){ + float length = fslope() * 6f; + float angle = current.set(x, y).sub(from).angle(); + Draw.color(Palette.accent); + Lines.stroke(fslope() * 2f); + + Lines.circle(x, y, fslope() * 2f); + Lines.lineAngleCenter(x, y, angle, length); + Lines.lineAngle(x, y, angle, fout() * 6f); + + Draw.color(item.color); + Fill.circle(x, y, fslope() * 1.5f); + + Draw.reset(); + } + + @Override + public EntityGroup targetGroup(){ + return effectGroup; + } + + @Override + public float fin(){ + return time() / lifetime(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/Lightning.java b/core/src/io/anuke/mindustry/entities/effect/Lightning.java new file mode 100644 index 0000000000..2ab599b003 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/Lightning.java @@ -0,0 +1,175 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pool.Poolable; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.net.In; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.TimedEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.entities.trait.SolidTrait; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Pooling; +import io.anuke.ucore.util.SeedRandom; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.bulletGroup; + +//TODO utterly broken +public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncTrait{ + private static Array entities = new Array<>(); + private static Rectangle rect = new Rectangle(); + private static Rectangle hitrect = new Rectangle(); + private static int lastSeed = 0; + private static float angle; + private static float wetDamageMultiplier = 2; + + private Array lines = new Array<>(); + private Color color = Palette.lancerLaser; + private SeedRandom random = new SeedRandom(); + + /** + * For pooling use only. Do not call directly! + */ + public Lightning(){ + } + + /** + * Create a lighting branch at a location. Use Team.none to damage everyone. + */ + public static void create(Team team, Effect effect, Color color, float damage, float x, float y, float targetAngle, int length){ + CallEntity.createLighting(lastSeed++, team, effect, color, damage, x, y, targetAngle, length); + } + + @Remote(called = Loc.server, in = In.entities) + public static void createLighting(int seed, Team team, Effect effect, Color color, float damage, float x, float y, float targetAngle, int length){ + Lightning l = Pooling.obtain(Lightning.class); + + l.x = x; + l.y = y; + l.random.setSeed(seed); + l.color = color; + + float step = 3f; + float range = 6f; + float attractRange = 20f; + + angle = targetAngle; + entities.clear(); + + Units.getNearbyEnemies(team, rect, entities::add); + + for(int i = 0; i < length; i++){ + l.lines.add(new Vector2(x, y)); + + float fx = x, fy = y; + float x2 = x + Angles.trnsx(angle, step); + float y2 = y + Angles.trnsy(angle, step); + float fangle = angle; + angle += Mathf.range(30f); + + rect.setSize(attractRange).setCenter(x, y); + + Units.getNearbyEnemies(team, rect, entity -> { + float dst = entity.distanceTo(x2, y2); + if(dst < attractRange){ + angle = Mathf.slerp(angle, Angles.angle(x2, y2, entity.x, entity.y), (attractRange - dst) / attractRange / 4f); + } + + entity.getHitbox(hitrect); + hitrect.x -= range / 2f; + hitrect.y -= range / 2f; + hitrect.width += range / 2f; + hitrect.height += range / 2f; + + if(hitrect.contains(x2, y2) || hitrect.contains(fx, fy)){ + float result = damage; + + if(entity.hasEffect(StatusEffects.wet)) + result = (result * wetDamageMultiplier); + + entity.damage(result); + Effects.effect(effect, x2, y2, fangle); + } + }); + + if(l.random.chance(0.1f)){ + createLighting(l.random.nextInt(), team, effect, color, damage, x2, y2, angle + l.random.range(100f), length / 3); + } + + x = x2; + y = y2; + } + + l.lines.add(new Vector2(x, y)); + l.add(); + } + + @Override + public boolean isSyncing(){ + return false; + } + + @Override + public void write(DataOutput data) throws IOException{ + + } + + @Override + public void read(DataInput data, long time) throws IOException{ + + } + + @Override + public float lifetime(){ + return 10; + } + + @Override + public void reset(){ + color = Palette.lancerLaser; + lines.clear(); + } + + @Override + public void removed(){ + Pooling.free(this); + } + + @Override + public void draw(){ + float lx = x, ly = y; + Draw.color(color, Color.WHITE, fin()); + for(int i = 0; i < lines.size; i++){ + Vector2 v = lines.get(i); + Lines.stroke(fout() * 3f + 1f - (float) i / lines.size); + Lines.line(lx, ly, v.x, v.y); + lx = v.x; + ly = v.y; + } + Draw.color(); + } + + @Override + public EntityGroup targetGroup(){ + return bulletGroup; + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/Puddle.java b/core/src/io/anuke/mindustry/entities/effect/Puddle.java new file mode 100644 index 0000000000..1fa0cd8a0b --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/Puddle.java @@ -0,0 +1,314 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.GridPoint2; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.Pool.Poolable; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.Liquids; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.content.bullets.TurretBullets; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.content.fx.EnvironmentFx; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.traits.SaveTrait; +import io.anuke.mindustry.entities.traits.SyncTrait; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.BaseEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Hue; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Pooling; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.puddleGroup; +import static io.anuke.mindustry.Vars.world; + +public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait, SyncTrait{ + private static final IntMap map = new IntMap<>(); + private static final float maxLiquid = 70f; + private static final int maxGeneration = 2; + private static final Color tmp = new Color(); + private static final Rectangle rect = new Rectangle(); + private static final Rectangle rect2 = new Rectangle(); + private static int seeds; + + private int loadedPosition = -1; + + private float updateTime; + private Tile tile; + private Liquid liquid; + private float amount, targetAmount; + private float accepting; + private byte generation; + + /** + * Deserialization use only! + */ + public Puddle(){ + } + + /** + * Deposists a puddle between tile and source. + */ + public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){ + deposit(tile, source, liquid, amount, 0); + } + + /** + * Deposists a puddle at a tile. + */ + public static void deposit(Tile tile, Liquid liquid, float amount){ + deposit(tile, tile, liquid, amount, 0); + } + + /** + * Returns the puddle on the specified tile. May return null. + */ + public static Puddle getPuddle(Tile tile){ + return map.get(tile.packedPosition()); + } + + private static void deposit(Tile tile, Tile source, Liquid liquid, float amount, int generation){ + if(tile.floor().isLiquid && !canStayOn(liquid, tile.floor().liquidDrop)){ + reactPuddle(tile.floor().liquidDrop, liquid, amount, tile, + (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); + + if(generation == 0 && Timers.get(tile, "ripple", 50)){ + Effects.effect(BlockFx.ripple, tile.floor().liquidDrop.color, + (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); + } + return; + } + + Puddle p = map.get(tile.packedPosition()); + if(p == null){ + if(Net.client()) return; //not clientside. + + Puddle puddle = Pooling.obtain(Puddle.class); + puddle.tile = tile; + puddle.liquid = liquid; + puddle.amount = amount; + puddle.generation = (byte) generation; + puddle.set((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); + puddle.add(); + map.put(tile.packedPosition(), puddle); + }else if(p.liquid == liquid){ + p.accepting = Math.max(amount, p.accepting); + + if(generation == 0 && Timers.get(p, "ripple2", 50) && p.amount >= maxLiquid / 2f){ + Effects.effect(BlockFx.ripple, p.liquid.color, (tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f); + } + }else{ + p.amount -= reactPuddle(p.liquid, liquid, amount, p.tile, p.x, p.y); + } + } + + /** + * Returns whether the first liquid can 'stay' on the second one. + * Currently, the only place where this can happen is oil on water. + */ + private static boolean canStayOn(Liquid liquid, Liquid other){ + return liquid == Liquids.oil && other == Liquids.water; + } + + /** + * Reacts two liquids together at a location. + */ + private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){ + if((dest.flammability > 0.3f && liquid.temperature > 0.7f) || + (liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid + Fire.create(tile); + if(Mathf.chance(0.006 * amount)){ + CallEntity.createBullet(TurretBullets.fireball, x, y, Mathf.random(360f)); + } + }else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot puddle + if(Mathf.chance(0.5f * amount)){ + Effects.effect(EnvironmentFx.steam, x, y); + } + return -0.1f * amount; + }else if(liquid.temperature > 0.7f && dest.temperature < 0.55f){ //hot liquid poured onto cold puddle + if(Mathf.chance(0.8f * amount)){ + Effects.effect(EnvironmentFx.steam, x, y); + } + return -0.4f * amount; + } + return 0f; + } + + @Remote(called = Loc.server, in = In.entities) + public static void onPuddleRemoved(int puddleid){ + puddleGroup.removeByID(puddleid); + } + + public float getFlammability(){ + return liquid.flammability * amount; + } + + @Override + public void update(){ + + //no updating happens clientside + if(Net.client()){ + amount = Mathf.lerpDelta(amount, targetAmount, 0.15f); + }else{ + //update code + float addSpeed = accepting > 0 ? 3f : 0f; + + amount -= Timers.delta() * (1f - liquid.viscosity) / (5f + addSpeed); + + amount += accepting; + accepting = 0f; + + if(amount >= maxLiquid / 1.5f && generation < maxGeneration){ + float deposited = Math.min((amount - maxLiquid / 1.5f) / 4f, 0.3f) * Timers.delta(); + for(GridPoint2 point : Geometry.d4){ + Tile other = world.tile(tile.x + point.x, tile.y + point.y); + if(other.block() == Blocks.air && other.cliffs == 0){ + deposit(other, tile, liquid, deposited, generation + 1); + amount -= deposited / 2f; //tweak to speed up/slow down puddle propagation + } + } + } + + amount = Mathf.clamp(amount, 0, maxLiquid); + + if(amount <= 0f){ + CallEntity.onPuddleRemoved(getID()); + } + } + + //effects-only code + if(amount >= maxLiquid / 2f && updateTime <= 0f){ + Units.getNearby(rect.setSize(Mathf.clamp(amount / (maxLiquid / 1.5f)) * 10f).setCenter(x, y), unit -> { + if(unit.isFlying()) return; + + unit.getHitbox(rect2); + if(!rect.overlaps(rect2)) return; + + unit.applyEffect(liquid.effect, 0.5f); + + if(unit.getVelocity().len() > 0.1){ + Effects.effect(BlockFx.ripple, liquid.color, unit.x, unit.y); + } + }); + + if(liquid.temperature > 0.7f && tile.entity != null && Mathf.chance(0.3 * Timers.delta())){ + Fire.create(tile); + } + + updateTime = 20f; + } + + updateTime -= Timers.delta(); + } + + @Override + public void draw(){ + seeds = id; + boolean onLiquid = tile.floor().isLiquid; + float f = Mathf.clamp(amount / (maxLiquid / 1.5f)); + float smag = onLiquid ? 0.8f : 0f; + float sscl = 20f; + + Draw.color(Hue.shift(tmp.set(liquid.color), 2, -0.05f)); + Fill.circle(x + Mathf.sin(Timers.time() + seeds * 532, sscl, smag), y + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 8f); + Angles.randLenVectors(id, 3, f * 6f, (ex, ey) -> { + Fill.circle(x + ex + Mathf.sin(Timers.time() + seeds * 532, sscl, smag), + y + ey + Mathf.sin(Timers.time() + seeds * 53, sscl, smag), f * 5f); + seeds++; + }); + Draw.color(); + } + + @Override + public float drawSize(){ + return 20; + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeInt(tile.packedPosition()); + stream.writeFloat(x); + stream.writeFloat(y); + stream.writeByte(liquid.id); + stream.writeFloat(amount); + stream.writeByte(generation); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + this.loadedPosition = stream.readInt(); + this.x = stream.readFloat(); + this.y = stream.readFloat(); + this.liquid = Liquid.getByID(stream.readByte()); + this.amount = stream.readFloat(); + this.generation = stream.readByte(); + add(); + } + + @Override + public void reset(){ + loadedPosition = -1; + tile = null; + liquid = null; + amount = 0; + generation = 0; + accepting = 0; + } + + @Override + public void added(){ + if(loadedPosition != -1){ + map.put(loadedPosition, this); + tile = world.tile(loadedPosition); + } + } + + @Override + public void removed(){ + map.remove(tile.packedPosition()); + reset(); + } + + @Override + public void write(DataOutput data) throws IOException{ + data.writeFloat(x); + data.writeFloat(y); + data.writeByte(liquid.id); + data.writeShort((short) (amount * 4)); + data.writeInt(tile.packedPosition()); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + x = data.readFloat(); + y = data.readFloat(); + liquid = Liquid.getByID(data.readByte()); + targetAmount = data.readShort() / 4f; + tile = world.tile(data.readInt()); + + map.put(tile.packedPosition(), this); + } + + @Override + public EntityGroup targetGroup(){ + return puddleGroup; + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java b/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java new file mode 100644 index 0000000000..8fb5dedf98 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/RubbleDecal.java @@ -0,0 +1,30 @@ +package io.anuke.mindustry.entities.effect; + +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Mathf; + +public class RubbleDecal extends Decal{ + private int size; + + /** + * Creates a rubble effect at a position. Provide a block size to use. + */ + public static void create(float x, float y, int size){ + RubbleDecal decal = new RubbleDecal(); + decal.size = size; + decal.set(x, y); + decal.add(); + } + + @Override + public void drawDecal(){ + String region = "rubble-" + size + "-" + Mathf.randomSeed(id, 0, 1); + + if(!Draw.hasRegion(region)){ + remove(); + return; + } + + Draw.rect(region, x, y, Mathf.randomSeed(id, 0, 4) * 90); + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java b/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java new file mode 100644 index 0000000000..7bfc3e369e --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/effect/ScorchDecal.java @@ -0,0 +1,41 @@ +package io.anuke.mindustry.entities.effect; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.world; + +public class ScorchDecal extends Decal{ + private static final int scorches = 5; + private static final TextureRegion[] regions = new TextureRegion[scorches]; + + public static void create(float x, float y){ + if(regions[0] == null){ + for(int i = 0; i < regions.length; i++){ + regions[i] = Draw.region("scorch" + (i + 1)); + } + } + + Tile tile = world.tileWorld(x, y); + + if(tile == null || tile.floor().liquidDrop != null) return; + + ScorchDecal decal = new ScorchDecal(); + decal.set(x, y); + decal.add(); + } + + @Override + public void drawDecal(){ + + for(int i = 0; i < 5; i++){ + TextureRegion region = regions[Mathf.randomSeed(id - i, 0, scorches - 1)]; + float rotation = Mathf.randomSeed(id + i, 0, 360); + float space = 1.5f + Mathf.randomSeed(id + i + 1, 0, 20) / 10f; + Draw.grect(region, x + Angles.trnsx(rotation, space), y + Angles.trnsy(rotation, space), rotation - 90); + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/effect/Shield.java b/core/src/io/anuke/mindustry/entities/effect/Shield.java index 405389e7e6..da10da3fde 100644 --- a/core/src/io/anuke/mindustry/entities/effect/Shield.java +++ b/core/src/io/anuke/mindustry/entities/effect/Shield.java @@ -1,61 +1,60 @@ package io.anuke.mindustry.entities.effect; import com.badlogic.gdx.math.Interpolation; -import io.anuke.mindustry.entities.enemies.Enemy; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.types.defense.ShieldBlock; +import io.anuke.mindustry.world.blocks.defense.ShieldBlock; import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.BulletEntity; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.impl.BaseEntity; +import io.anuke.ucore.entities.trait.DrawTrait; +import io.anuke.ucore.graphics.Fill; import io.anuke.ucore.util.Mathf; -import static io.anuke.mindustry.Vars.bulletGroup; import static io.anuke.mindustry.Vars.shieldGroup; -public class Shield extends Entity{ - public boolean active; - public boolean hitPlayers = false; - public float radius = 0f; - - private float uptime = 0f; - private final Tile tile; - - public Shield(Tile tile){ - this.tile = tile; - this.x = tile.worldx(); - this.y = tile.worldy(); - } - - public float drawSize(){ - return 150; - } - - @Override - public void update(){ - float alpha = 0.1f; - Interpolation interp = Interpolation.fade; - - if(active){ - uptime = interp.apply(uptime, 1f, alpha * Timers.delta()); - }else{ - uptime = interp.apply(uptime, 0f, alpha * Timers.delta()); - if(uptime <= 0.05f) - remove(); - } - uptime = Mathf.clamp(uptime); - - if(!(tile.block() instanceof ShieldBlock)){ - remove(); - return; - } - - ShieldBlock block = (ShieldBlock)tile.block(); - +//todo re-implement +public class Shield extends BaseEntity implements DrawTrait{ + private final Tile tile; + public boolean active; + public boolean hitPlayers = false; + public float radius = 0f; + private float uptime = 0f; + + public Shield(Tile tile){ + this.tile = tile; + this.x = tile.worldx(); + this.y = tile.worldy(); + } + + public float drawSize(){ + return 150; + } + + @Override + public void update(){ + float alpha = 0.1f; + Interpolation interp = Interpolation.fade; + + if(active){ + uptime = interp.apply(uptime, 1f, alpha * Timers.delta()); + }else{ + uptime = interp.apply(uptime, 0f, alpha * Timers.delta()); + if(uptime <= 0.05f) + remove(); + } + uptime = Mathf.clamp(uptime); + + if(!(tile.block() instanceof ShieldBlock)){ + remove(); + return; + } + + ShieldBlock block = (ShieldBlock) tile.block(); + + /* Entities.getNearby(bulletGroup, x, y, block.shieldRadius * 2*uptime + 10, entity->{ BulletEntity bullet = (BulletEntity)entity; - if((bullet.owner instanceof Enemy || hitPlayers)){ + if((bullet.owner instanceof BaseUnit || hitPlayers)){ float dst = entity.distanceTo(this); @@ -63,41 +62,40 @@ public class Shield extends Entity{ ((ShieldBlock)tile.block()).handleBullet(tile, bullet); } } - }); - } - - @Override - public void draw(){ - if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){ - return; - } - - float rad = drawRadius(); - Draw.rect("circle2", x, y, rad, rad); - } - - float drawRadius(){ - return (radius*2 + Mathf.sin(Timers.time(), 25f, 2f)); - } - - public void removeDelay(){ - active = false; - } - - @Override - public Shield add(){ - return super.add(shieldGroup); - } - - @Override - public void added(){ - active = true; - } - - @Override - public void removed(){ - active = false; - uptime = 0f; - } - + });*/ + } + + @Override + public void draw(){ + if(!(tile.block() instanceof ShieldBlock) || radius <= 1f){ + return; + } + + Fill.circle(x, y, drawRadius()); + } + + float drawRadius(){ + return (radius + Mathf.sin(Timers.time(), 25f, 1f)); + } + + public void removeDelay(){ + active = false; + } + + @Override + public EntityGroup targetGroup(){ + return shieldGroup; + } + + @Override + public void added(){ + active = true; + } + + @Override + public void removed(){ + active = false; + uptime = 0f; + } + } diff --git a/core/src/io/anuke/mindustry/entities/effect/TeslaOrb.java b/core/src/io/anuke/mindustry/entities/effect/TeslaOrb.java deleted file mode 100644 index 40b860a01c..0000000000 --- a/core/src/io/anuke/mindustry/entities/effect/TeslaOrb.java +++ /dev/null @@ -1,135 +0,0 @@ -package io.anuke.mindustry.entities.effect; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.ObjectSet; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.entities.SolidEntity; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.enemyGroup; - -public class TeslaOrb extends Entity{ - private Array points = new Array<>(); - private ObjectSet hit = new ObjectSet<>(); - private int damage = 0; - private float range = 0; - private float lifetime = 30f; - private float life = 0f; - private Vector2 vector = new Vector2(); - - public TeslaOrb(float x, float y, float range, int damage){ - set(x, y); - this.damage = damage; - this.range = range; - } - - void shock(){ - float stopchance = 0.1f; - float curx = x, cury = y; - float shake = 3f; - - int max = 7; - - while(points.size < max){ - if(Mathf.chance(stopchance)){ - break; - } - - Array enemies = Entities.getNearby(enemyGroup, curx, cury, range*2f); - - synchronized (Entities.entityLock) { - - for (SolidEntity entity : enemies) { - if (entity != null && entity.distanceTo(curx, cury) < range && !hit.contains((Enemy) entity)) { - hit.add((Enemy) entity); - points.add(new Vector2(entity.x + Mathf.range(shake), entity.y + Mathf.range(shake))); - damageEnemy((Enemy) entity); - curx = entity.x; - cury = entity.y; - break; - } - } - } - } - - if(points.size == 0){ - remove(); - } - } - - void damageEnemy(Enemy enemy){ - enemy.damage(damage); - Effects.effect(Fx.laserhit, enemy.x + Mathf.range(2f), enemy.y + Mathf.range(2f)); - } - - @Override - public void update(){ - life += Timers.delta(); - - if(life >= lifetime){ - remove(); - } - } - - @Override - public void drawOver(){ - if(points.size == 0) return; - - float range = 1f; - - Vector2 previous = vector.set(x, y); - - for(Vector2 enemy : points){ - - - float x1 = previous.x + Mathf.range(range), - y1 = previous.y + Mathf.range(range), - x2 = enemy.x + Mathf.range(range), - y2 = enemy.y + Mathf.range(range); - - Draw.color(Color.WHITE); - Draw.alpha(1f-life/lifetime); - - Lines.stroke(3f - life/lifetime*2f); - Lines.line(x1, y1, x2, y2); - - float rad = 7f - life/lifetime*5f; - - Draw.rect("circle", x2, y2, rad, rad); - - if(previous.epsilonEquals(x, y, 0.001f)){ - Draw.rect("circle", x, y, rad, rad); - } - - //Draw.color(Color.WHITE); - - //Draw.stroke(2f - life/lifetime*2f); - //Draw.line(x1, y1, x2, y2); - - Draw.reset(); - - previous = enemy; - } - } - - @Override - public void added(){ - Timers.run(1f, ()->{ - shock(); - }); - } - - @Override - public float drawSize(){ - return 200; - } -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java b/core/src/io/anuke/mindustry/entities/enemies/Enemy.java deleted file mode 100644 index da56d84fde..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/Enemy.java +++ /dev/null @@ -1,164 +0,0 @@ -package io.anuke.mindustry.entities.enemies; - -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.entities.Bullet; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.SyncEntity; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.entities.SolidEntity; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Timer; -import io.anuke.ucore.util.Translator; - -import java.nio.ByteBuffer; - -import static io.anuke.mindustry.Vars.enemyGroup; - -public class Enemy extends SyncEntity { - public EnemyType type; - - public Timer timer = new Timer(5); - public float idletime = 0f; - public int lane; - public int node = -1; - - public Enemy spawner; - public int spawned; - - public Vector2 velocity = new Vector2(); - public Vector2 totalMove = new Vector2(); - public Vector2 tpos = new Vector2(-999, -999); - public Entity target; - public float hitTime; - public int tier = 1; - - public TextureRegion region; - public Translator tr = new Translator(); - - public Enemy(EnemyType type){ - this.type = type; - } - - /**internal constructor used for deserialization, DO NOT USE*/ - public Enemy(){} - - @Override - public void update(){ - type.update(this); - } - - @Override - public void drawSmooth(){ - type.draw(this); - } - - @Override - public void drawOver(){ - type.drawOver(this); - } - - @Override - public float drawSize(){ - return 14; - } - - @Override - public boolean collides(SolidEntity other){ - return (other instanceof Bullet) && !(((Bullet) other).owner instanceof Enemy); - } - - @Override - public void damage(float amount){ - super.damage(amount); - hitTime = EnemyType.hitDuration; - } - - @Override - public void onDeath(){ - type.onDeath(this, false); - } - - @Override - public void removed(){ - type.removed(this); - } - - @Override - public void added(){ - hitbox.setSize(type.hitsize); - hitboxTile.setSize(type.hitsizeTile); - maxhealth = type.health * tier; - region = Draw.region(type.name + "-t" + Mathf.clamp(tier, 1, 3)); - - heal(); - } - - @Override - public Enemy add(){ - return add(enemyGroup); - } - - @Override - public void writeSpawn(ByteBuffer buffer) { - buffer.put(type.id); - buffer.put((byte)lane); - buffer.put((byte)tier); - buffer.putFloat(x); - buffer.putFloat(y); - buffer.putShort((short)health); - } - - @Override - public void readSpawn(ByteBuffer buffer) { - type = EnemyType.getByID(buffer.get()); - lane = buffer.get(); - tier = buffer.get(); - x = buffer.getFloat(); - y = buffer.getFloat(); - health = buffer.getShort(); - setNet(x, y); - } - - @Override - public void write(ByteBuffer data) { - data.putFloat(x); - data.putFloat(y); - data.putShort((short)(angle*2)); - data.putShort((short)health); - } - - @Override - public void read(ByteBuffer data, long time) { - - float x = data.getFloat(); - float y = data.getFloat(); - short angle = data.getShort(); - short health = data.getShort(); - - this.health = health; - - interpolator.read(this.x, this.y, x, y, angle/2f, time); - } - - public void shoot(BulletType bullet){ - shoot(bullet, 0); - } - - public void shoot(BulletType bullet, float rotation){ - - if(!(Net.client())) { - tr.trns(angle + rotation, type.length); - Bullet out = new Bullet(bullet, this, x + tr.x, y + tr.y, this.angle + rotation).add(); - out.damage = (int) ((bullet.damage * (1 + (tier - 1) * 1f))); - type.onShoot(this, bullet, rotation); - - if(Net.server()){ - NetEvents.handleBullet(bullet, this, x + tr.x, y + tr.y, this.angle + rotation, (short)out.damage); - } - } - } -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java b/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java deleted file mode 100644 index b5603fd8a9..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/EnemyType.java +++ /dev/null @@ -1,289 +0,0 @@ -package io.anuke.mindustry.entities.enemies; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.graphics.Shaders; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Strings; - -import static io.anuke.mindustry.Vars.*; - -public class EnemyType { - - //TODO documentation, comments - private static byte lastid = 0; - private static Array types = new Array<>(); - - public final static Color[] tierColors = { - Color.valueOf("ffe451"), Color.valueOf("f48e20"), Color.valueOf("ff6757"), - Color.valueOf("ff2d86"), Color.valueOf("cb2dff"), Color.valueOf("362020") }; - public final static int maxtier = tierColors.length; - public final static float maxIdleLife = 60f*2f; //2 seconds idle = death - public final static float hitDuration = 5f; - - public final String name; - public final byte id; - - protected int timeid; - protected int health = 60; - protected float hitsize = 5f; - protected float hitsizeTile = 4f; - protected float speed = 0.4f; - protected float reload = 32; - protected float range = 60; - protected float length = 4; - protected float rotatespeed = 0.1f; - protected float turretrotatespeed = 0.2f; - protected boolean alwaysRotate = false; - protected BulletType bullet = BulletType.small; - protected String shootsound = "enemyshoot"; - protected boolean targetCore = false; - protected boolean stopNearCore = true; - protected boolean targetClient = false; - protected float mass = 1f; - - protected final int timerTarget = timeid ++; - protected final int timerReload = timeid ++; - protected final int timerReset = timeid ++; - - protected final Vector2 shift = new Vector2(); - protected final Vector2 move = new Vector2(); - protected final Vector2 calc = new Vector2(); - - public EnemyType(String name){ - this.id = lastid++; - this.name = name; - types.add(this); - } - - public void draw(Enemy enemy){ - Shaders.outline.color.set(tierColors[enemy.tier - 1]); - Shaders.outline.lighten = Mathf.clamp(enemy.hitTime/hitDuration); - Shaders.outline.region = enemy.region; - - Shaders.outline.apply(); - - Draw.rect(enemy.region, enemy.x, enemy.y, enemy.angle - 90); - Draw.color(); - - Graphics.flush(); - - if(isCalculating(enemy)){ - Draw.color(Color.SKY); - Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f); - Lines.polySeg(20, 0, 4, enemy.x, enemy.y, 11f, Timers.time() * 2f + enemy.id*52f + 180f); - Draw.color(); - } - - if(showPaths){ - Draw.tscl(0.25f); - Draw.text((int)enemy.idletime + " " + enemy.node + " " + enemy.id + "\n" + Strings.toFixed(enemy.totalMove.x, 2) + ", " - + Strings.toFixed(enemy.totalMove.x, 2), enemy.x, enemy.y); - Draw.tscl(fontscale); - } - - Shaders.outline.lighten = 0f; - } - - public void drawOver(Enemy enemy){ } - - public void update(Enemy enemy){ - float lastx = enemy.x, lasty = enemy.y; - if(enemy.hitTime > 0){ - enemy.hitTime -= Timers.delta(); - } - - if(enemy.lane >= world.getSpawns().size || enemy.lane < 0) enemy.lane = 0; - - boolean waiting = enemy.lane >= world.getSpawns().size || enemy.lane < 0 - || world.getSpawns().get(enemy.lane).pathTiles == null || enemy.node <= 0; - - move(enemy); - - enemy.velocity.set(enemy.x - lastx, enemy.y - lasty).scl(1f / Timers.delta()); - enemy.totalMove.add(enemy.velocity); - - float minv = 0.07f; - - if(enemy.timer.get(timerReset, 80)){ - enemy.totalMove.setZero(); - } - - if(enemy.velocity.len() < minv && !waiting && enemy.target == null){ - enemy.idletime += Timers.delta(); - }else{ - enemy.idletime = 0; - } - - if(enemy.timer.getTime(timerReset) > 50 && enemy.totalMove.len() < 0.2f && !waiting && enemy.target == null){ - enemy.idletime = 999999f; - } - - Tile tile = world.tileWorld(enemy.x, enemy.y); - if(tile != null && tile.floor().liquid && tile.block() == Blocks.air){ - enemy.damage(enemy.health+1); //drown - } - - if(Float.isNaN(enemy.angle)){ - enemy.angle = 0; - } - - if(enemy.target == null || alwaysRotate){ - enemy.angle = Mathf.slerpDelta(enemy.angle, enemy.velocity.angle(), rotatespeed); - }else{ - enemy.angle = Mathf.slerpDelta(enemy.angle, enemy.angleTo(enemy.target), turretrotatespeed); - } - - enemy.x = Mathf.clamp(enemy.x, 0, world.width() * tilesize); - enemy.y = Mathf.clamp(enemy.y, 0, world.height() * tilesize); - } - - public void move(Enemy enemy){ - if(Net.client()){ - enemy.interpolate(); - if(targetClient) updateTargeting(enemy, false); - return; - } - - float speed = this.speed + 0.04f * enemy.tier; - float range = this.range + enemy.tier * 5; - - Tile core = world.getCore(); - - if(core == null) return; - - if(enemy.idletime > maxIdleLife && enemy.node > 0){ - enemy.onDeath(); - return; - } - - boolean nearCore = enemy.distanceTo(core.worldx(), core.worldy()) <= range - 18f && stopNearCore; - Vector2 vec; - - if(nearCore){ - vec = move.setZero(); - if(targetCore) enemy.target = core.entity; - }else{ - vec = world.pathfinder().find(enemy); - vec.sub(enemy.x, enemy.y).limit(speed); - } - - shift.setZero(); - float shiftRange = enemy.hitbox.width + 2f; - float avoidRange = shiftRange + 4f; - float attractRange = avoidRange + 7f; - float avoidSpeed = this.speed/2.7f; - - Entities.getNearby(enemyGroup, enemy.x, enemy.y, range, en -> { - Enemy other = (Enemy)en; - if(other == enemy) return; - float dst = other.distanceTo(enemy); - - if(dst < shiftRange){ - float scl = Mathf.clamp(1.4f - dst / shiftRange) * mass * 1f/mass; - shift.add((enemy.x - other.x) * scl, (enemy.y - other.y) * scl); - }else if(dst < avoidRange){ - calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed); - shift.add(calc.scl(1.1f)); - }else if(dst < attractRange && !nearCore && !isCalculating(enemy)){ - calc.set((enemy.x - other.x), (enemy.y - other.y)).setLength(avoidSpeed); - shift.add(calc.scl(-1)); - } - }); - - shift.limit(1f); - vec.add(shift.scl(0.5f)); - - enemy.move(vec.x * Timers.delta(), vec.y * Timers.delta()); - - updateTargeting(enemy, nearCore); - - behavior(enemy); - } - - public void behavior(Enemy enemy){} - - public void updateTargeting(Enemy enemy, boolean nearCore){ - if(enemy.target != null && enemy.target instanceof TileEntity && ((TileEntity)enemy.target).dead){ - enemy.target = null; - } - - if(enemy.timer.get(timerTarget, 15) && !nearCore){ - enemy.target = world.findTileTarget(enemy.x, enemy.y, null, range, false); - - //no tile found - if(enemy.target == null){ - enemy.target = Entities.getClosest(playerGroup, enemy.x, enemy.y, range, e -> !((Player)e).isAndroid && - !((Player)e).isDead()); - } - }else if(nearCore){ - enemy.target = world.getCore().entity; - } - - if(enemy.target != null && bullet != null){ - updateShooting(enemy); - } - } - - public void updateShooting(Enemy enemy){ - float reload = this.reload / Math.max(enemy.tier / 1.5f, 1f); - - if(enemy.timer.get(timerReload, reload)){ - shoot(enemy); - } - } - - public void shoot(Enemy enemy){ - enemy.shoot(bullet); - if(shootsound != null) Effects.sound(shootsound, enemy); - } - - public void onShoot(Enemy enemy, BulletType type, float rotation){} - - public void onDeath(Enemy enemy, boolean force){ - if(Net.server()){ - NetEvents.handleEnemyDeath(enemy); - } - - if(!Net.client() || force) { - Effects.effect(Fx.explosion, enemy); - Effects.shake(3f, 4f, enemy); - Effects.sound("bang2", enemy); - enemy.remove(); - enemy.dead = true; - } - } - - public void removed(Enemy enemy){ - if(!enemy.dead){ - if(enemy.spawner != null){ - enemy.spawner.spawned --; - }else{ - state.enemies --; - } - } - } - - public boolean isCalculating(Enemy enemy){ - return enemy.node < 0 && !Net.client(); - } - - public static EnemyType getByID(byte id){ - return types.get(id); - } -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/EnemyTypes.java b/core/src/io/anuke/mindustry/entities/enemies/EnemyTypes.java deleted file mode 100644 index 799b8fb1be..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/EnemyTypes.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.anuke.mindustry.entities.enemies; - -import io.anuke.mindustry.entities.enemies.types.BlastType; -import io.anuke.mindustry.entities.enemies.types.EmpType; -import io.anuke.mindustry.entities.enemies.types.FastType; -import io.anuke.mindustry.entities.enemies.types.FlamerType; -import io.anuke.mindustry.entities.enemies.types.FortressType; -import io.anuke.mindustry.entities.enemies.types.HealerType; -import io.anuke.mindustry.entities.enemies.types.MortarType; -import io.anuke.mindustry.entities.enemies.types.RapidType; -import io.anuke.mindustry.entities.enemies.types.*; -import io.anuke.mindustry.entities.enemies.types.TankType; -import io.anuke.mindustry.entities.enemies.types.TargetType; -import io.anuke.mindustry.entities.enemies.types.TitanType; - -public class EnemyTypes { - public static final EnemyType - - standard = new StandardType(), - fast = new FastType(), - rapid = new RapidType(), - flamer = new FlamerType(), - tank = new TankType(), - blast = new BlastType(), - mortar = new MortarType(), - healer = new HealerType(), - titan = new TitanType(), - emp = new EmpType(), - fortress = new FortressType(), - target = new TargetType(); - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/BlastType.java b/core/src/io/anuke/mindustry/entities/enemies/types/BlastType.java deleted file mode 100644 index 5ce1e804ed..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/BlastType.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.Bullet; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; - -import static io.anuke.mindustry.Vars.tilesize; - -public class BlastType extends EnemyType { - - public BlastType() { - super("blastenemy"); - health = 30; - speed = 0.8f; - bullet = null; - turretrotatespeed = 0f; - mass = 0.8f; - stopNearCore = false; - } - - @Override - public void behavior(Enemy enemy){ - - float range = 10f; - float ox = 0, oy = 0; - - if(enemy.target instanceof TileEntity){ - TileEntity e = (TileEntity)enemy.target; - range = (e.tile.block().width * tilesize) /2f + 8f; - ox = e.tile.block().getPlaceOffset().x; - oy = e.tile.block().getPlaceOffset().y; - } - - if(enemy.target != null && enemy.target.distanceTo(enemy.x - ox, enemy.y - oy) < range){ - explode(enemy); - } - } - - @Override - public void onDeath(Enemy enemy, boolean force){ - if(force) explode(enemy); - super.onDeath(enemy, force); - } - - void explode(Enemy enemy){ - Bullet b = new Bullet(BulletType.blast, enemy, enemy.x, enemy.y, 0).add(); - b.damage = BulletType.blast.damage + (enemy.tier-1) * 40; - enemy.damage(999); - enemy.remove(); - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/EmpType.java b/core/src/io/anuke/mindustry/entities/enemies/types/EmpType.java deleted file mode 100644 index cfe1a62da2..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/EmpType.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class EmpType extends EnemyType { - - public EmpType() { - super("empenemy"); - - speed = 0.3f; - reload = 70; - health = 210; - range = 80f; - bullet = BulletType.emp; - turretrotatespeed = 0.1f; - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/FastType.java b/core/src/io/anuke/mindustry/entities/enemies/types/FastType.java deleted file mode 100644 index 29c7ed5a0c..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/FastType.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class FastType extends EnemyType { - - public FastType() { - super("fastenemy"); - - speed = 0.73f; - reload = 20; - mass = 0.2f; - - health = 50; - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/FlamerType.java b/core/src/io/anuke/mindustry/entities/enemies/types/FlamerType.java deleted file mode 100644 index 0aad1e7b10..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/FlamerType.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class FlamerType extends EnemyType { - - public FlamerType() { - super("flamerenemy"); - - speed = 0.35f; - health = 150; - reload = 6; - bullet = BulletType.flameshot; - shootsound = "flame"; - mass = 1.5f; - range = 40; - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/FortressType.java b/core/src/io/anuke/mindustry/entities/enemies/types/FortressType.java deleted file mode 100644 index d02c85fe7c..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/FortressType.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.entities.enemies.EnemyTypes; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.util.Angles; - -import static io.anuke.mindustry.Vars.world; - -public class FortressType extends EnemyType { - final int maxSpawn = 6; - final float spawnTime = 190; - - public FortressType() { - super("fortressenemy"); - - speed = 0.25f; - reload = 90; - health = 700; - range = 70f; - bullet = BulletType.yellowshell; - hitsize = 10f; - turretrotatespeed = rotatespeed = 0.08f; - length = 7f; - mass = 7f; - } - - @Override - public void behavior(Enemy enemy){ - if(enemy.distanceTo(world.getCore().worldx(), - world.getCore().worldy()) <= 90f){ - - if(Timers.get(this, "spawn", spawnTime) && enemy.spawned < maxSpawn){ - enemy.tr.trns(enemy.angle, 20f); - - Enemy s = new Enemy(EnemyTypes.fast); - s.lane = enemy.lane; - s.tier = enemy.tier; - s.spawner = enemy; - s.set(enemy.x + enemy.tr.x, enemy.y + enemy.tr.y); - s.add(); - - Effects.effect(Fx.spawn, enemy); - enemy.spawned ++; - } - - } - } - - - public void onShoot(Enemy enemy, BulletType type, float rotation){ - Effects.effect(Fx.largeCannonShot, enemy.x + enemy.tr.x, enemy.y + enemy.tr.y, enemy.angle); - Effects.shake(3f, 3f, enemy); - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/HealerType.java b/core/src/io/anuke/mindustry/entities/enemies/types/HealerType.java deleted file mode 100644 index 938f33c27c..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/HealerType.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.MathUtils; -import io.anuke.mindustry.entities.Bullet; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.graphics.Shaders; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Hue; -import io.anuke.ucore.graphics.Shapes; - -import static io.anuke.mindustry.Vars.enemyGroup; - -public class HealerType extends EnemyType { - - public HealerType() { - super("healerenemy"); - speed = 0.25f; - reload = 10; - health = 200; - bullet = BulletType.shot; - range = 40f; - alwaysRotate = false; - targetCore = false; - stopNearCore = true; - targetClient = true; - mass = 1.1f; - } - - - @Override - public void behavior(Enemy enemy){ - - if(enemy.idletime > 60f*3){ //explode after 3 seconds of stillness - explode(enemy); - Effects.effect(Fx.shellexplosion, enemy); - Effects.effect(Fx.shellsmoke, enemy); - } - } - - @Override - public void updateTargeting(Enemy enemy, boolean nearCore){ - if(enemy.timer.get(timerTarget, 15)){ - enemy.target = Entities.getClosest(enemyGroup, - enemy.x, enemy.y, range, e -> e instanceof Enemy && e != enemy && ((Enemy)e).healthfrac() < 1f); - } - - if(enemy.target != null){ - updateShooting(enemy); - } - } - - - @Override - public void updateShooting(Enemy enemy){ - Enemy target = (Enemy)enemy.target; - - if(target.health < target.maxhealth && enemy.timer.get(timerReload, reload)){ - target.health ++; - enemy.idletime = 0; - } - } - - @Override - public void drawOver(Enemy enemy){ - Enemy target = (Enemy)enemy.target; - - if(target == null) return; - - enemy.tr.trns(enemy.angleTo(target), 5f); - - Shaders.outline.color.set(Color.CLEAR); - Shaders.outline.apply(); - - if(target.health < target.maxhealth){ - Draw.color(Hue.rgb(138, 244, 138, (MathUtils.sin(Timers.time()) + 1f) / 13f)); - Draw.alpha(0.9f); - Shapes.laser("laser", "laserend", enemy.x + enemy.tr.x, enemy.y + enemy.tr.y, target.x - enemy.tr.x/1.5f, target.y - enemy.tr.y/1.5f); - Draw.color(); - } - - Graphics.flush(); - } - - void explode(Enemy enemy){ - Bullet b = new Bullet(BulletType.blast, enemy, enemy.x, enemy.y, 0).add(); - b.damage = BulletType.blast.damage + (enemy.tier-1) * 30; - enemy.damage(999); - enemy.remove(); - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/MortarType.java b/core/src/io/anuke/mindustry/entities/enemies/types/MortarType.java deleted file mode 100644 index ad1ac1bfbc..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/MortarType.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class MortarType extends EnemyType { - - public MortarType() { - super("mortarenemy"); - - health = 200; - speed = 0.25f; - reload = 100f; - bullet = BulletType.shell; - turretrotatespeed = 0.15f; - rotatespeed = 0.05f; - range = 120f; - mass = 1.2f; - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/RapidType.java b/core/src/io/anuke/mindustry/entities/enemies/types/RapidType.java deleted file mode 100644 index ed2621d74c..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/RapidType.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class RapidType extends EnemyType { - - public RapidType() { - super("rapidenemy"); - - reload = 8; - bullet = BulletType.purple; - rotatespeed = 0.08f; - health = 260; - speed = 0.33f; - hitsize = 8f; - mass = 3f; - range = 70; - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/StandardType.java b/core/src/io/anuke/mindustry/entities/enemies/types/StandardType.java deleted file mode 100644 index 112155beeb..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/StandardType.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.enemies.EnemyType; - -public class StandardType extends EnemyType { - - public StandardType(){ - super("standardenemy"); - } -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/TankType.java b/core/src/io/anuke/mindustry/entities/enemies/types/TankType.java deleted file mode 100644 index 64a827c1b2..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/TankType.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.ucore.util.Angles; - -public class TankType extends EnemyType { - - public TankType() { - super("tankenemy"); - - health = 350; - speed = 0.24f; - reload = 90f; - rotatespeed = 0.06f; - bullet = BulletType.small; - length = 3f; - mass = 1.4f; - length = 8f; - } - - @Override - public void shoot(Enemy enemy){ - super.shoot(enemy); - - Angles.shotgun(3, 8f, enemy.angle, f -> enemy.shoot(bullet, f)); - } - -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/TargetType.java b/core/src/io/anuke/mindustry/entities/enemies/types/TargetType.java deleted file mode 100644 index 822e9d5823..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/TargetType.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import com.badlogic.gdx.graphics.Color; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.entities.enemies.EnemyTypes; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.control; - -public class TargetType extends EnemyType { - - public TargetType(){ - super("targetenemy"); - - speed = 0f; - health = 40; - shootsound = null; - } - - @Override - public void move(Enemy enemy){ - - } - - @Override - public void shoot(Enemy enemy){ - //do nothing - } - - @Override - public void removed(Enemy enemy){ - //don't call enemy death since this is only a target - } - - @Override - public void draw(Enemy enemy){ - super.draw(enemy); - - Draw.color(Color.YELLOW); - - if(control.tutorial().showTarget()){ - Lines.spikes(enemy.x, enemy.y, 11f + Mathf.sin(Timers.time(), 7f, 1f), 4f, 8, Timers.time()); - } - - Draw.color(); - } - - @Override - public void onDeath(Enemy enemy, boolean force){ - super.onDeath(enemy, force); - Timers.run(100f, ()->{ - new Enemy(EnemyTypes.target).set(enemy.x, enemy.y).add(); - }); - } - - @Override - public boolean isCalculating(Enemy enemy){ - return false; - } -} diff --git a/core/src/io/anuke/mindustry/entities/enemies/types/TitanType.java b/core/src/io/anuke/mindustry/entities/enemies/types/TitanType.java deleted file mode 100644 index c8e2786b94..0000000000 --- a/core/src/io/anuke/mindustry/entities/enemies/types/TitanType.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.anuke.mindustry.entities.enemies.types; - -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; - -public class TitanType extends EnemyType { - - public TitanType() { - super("titanenemy"); - - speed = 0.26f; - reload = 30; - health = 430; - range = 60f; - bullet = BulletType.small; - hitsize = 7f; - mass = 4f; - } - - @Override - public void updateShooting(Enemy enemy){ - Timers.get(enemy, "salvo", 240); - - if(Timers.getTime(enemy, "salvo") < 60){ - if(Timers.get(enemy, "salvoShoot", 6)){ - enemy.shoot(BulletType.flameshot, Mathf.range(20f)); - } - } - - if(Timers.get(enemy, "shotgun", 80)){ - Angles.shotgun(5, 10f, 0f, f->{ - enemy.shoot(BulletType.smallSlow, f); - }); - } - - if(Timers.get(enemy, "circle", 200)){ - Angles.circle(8, f->{ - enemy.shoot(BulletType.smallSlow, f); - }); - } - } - -} diff --git a/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java b/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java new file mode 100644 index 0000000000..c4abd8abf0 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/BelowLiquidTrait.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.traits; + +/** + * A flag interface for marking an effect as appearing below liquids. + */ +public interface BelowLiquidTrait{ +} diff --git a/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java new file mode 100644 index 0000000000..bbf7df9f9d --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/BuilderTrait.java @@ -0,0 +1,380 @@ +package io.anuke.mindustry.entities.traits; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Queue; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.content.fx.BlockFx; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.world.Build; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BuildBlock; +import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.trait.Entity; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.graphics.Shapes; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.tmptr; +import static io.anuke.mindustry.Vars.world; + +/** + * Interface for units that build, break or mine things. + */ +public interface BuilderTrait extends Entity{ + //these are not instance variables! + float placeDistance = 140f; + float mineDistance = 70f; + + /** + * Returns the queue for storing build requests. + */ + Queue getPlaceQueue(); + + /** + * Returns the tile this builder is currently mining. + */ + Tile getMineTile(); + + /** + * Sets the tile this builder is currently mining. + */ + void setMineTile(Tile tile); + + /** + * Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc. + */ + float getMinePower(); + + /** + * Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all. + */ + float getBuildPower(Tile tile); + + /** + * Whether this type of builder can begin creating new blocks. + */ + default boolean canCreateBlocks(){ + return true; + } + + default void writeBuilding(DataOutput output) throws IOException{ + BuildRequest request = getCurrentRequest(); + + if(request != null){ + output.writeByte(request.remove ? 1 : 0); + output.writeInt(world.toPacked(request.x, request.y)); + if(!request.remove){ + output.writeByte(request.recipe.id); + output.writeByte(request.rotation); + } + }else{ + output.writeByte(-1); + } + } + + default void readBuilding(DataInput input) throws IOException{ + readBuilding(input, true); + } + + default void readBuilding(DataInput input, boolean applyChanges) throws IOException{ + synchronized(getPlaceQueue()){ + if(applyChanges) getPlaceQueue().clear(); + + byte type = input.readByte(); + if(type != -1){ + int position = input.readInt(); + BuildRequest request; + + if(type == 1){ //remove + request = new BuildRequest(position % world.width(), position / world.width()); + }else{ //place + byte recipe = input.readByte(); + byte rotation = input.readByte(); + request = new BuildRequest(position % world.width(), position / world.width(), rotation, Recipe.getByID(recipe)); + } + + if(applyChanges){ + getPlaceQueue().addLast(request); + } + } + } + } + + /** + * Return whether this builder's place queue contains items. + */ + default boolean isBuilding(){ + return getPlaceQueue().size != 0; + } + + /** + * If a place request matching this signature is present, it is removed. + * Otherwise, a new place request is added to the queue. + */ + default void replaceBuilding(int x, int y, int rotation, Recipe recipe){ + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + if(request.x == x && request.y == y){ + clearBuilding(); + addBuildRequest(request); + return; + } + } + } + + addBuildRequest(new BuildRequest(x, y, rotation, recipe)); + } + + /** + * Clears the placement queue. + */ + default void clearBuilding(){ + getPlaceQueue().clear(); + } + + /** + * Add another build requests to the tail of the queue, if it doesn't exist there yet. + */ + default void addBuildRequest(BuildRequest place){ + synchronized(getPlaceQueue()){ + for(BuildRequest request : getPlaceQueue()){ + if(request.x == place.x && request.y == place.y){ + return; + } + } + getPlaceQueue().addLast(place); + } + } + + /** + * Return the build requests currently active, or the one at the top of the queue. + * May return null. + */ + default BuildRequest getCurrentRequest(){ + synchronized(getPlaceQueue()){ + return getPlaceQueue().size == 0 ? null : getPlaceQueue().first(); + } + } + + /** + * Update building mechanism for this unit. + * This includes mining. + */ + default void updateBuilding(Unit unit){ + BuildRequest current = getCurrentRequest(); + + //update mining here + if(current == null){ + if(getMineTile() != null){ + updateMining(unit); + } + return; + }else{ + setMineTile(null); + } + + TileEntity core = unit.getClosestCore(); + + //if there is no core to build with, stop building! + if(core == null){ + return; + } + + Tile tile = world.tile(current.x, current.y); + + if(!(tile.block() instanceof BuildBlock)){ + if(canCreateBlocks() && !current.remove && Build.validPlace(unit.getTeam(), current.x, current.y, current.recipe.result, current.rotation)){ + Build.beginPlace(unit.getTeam(), current.x, current.y, current.recipe, current.rotation); + }else if(canCreateBlocks() && current.remove && Build.validBreak(unit.getTeam(), current.x, current.y)){ + Build.beginBreak(unit.getTeam(), current.x, current.y); + }else{ + getPlaceQueue().removeFirst(); + return; + } + } + + //otherwise, update it. + BuildEntity entity = tile.entity(); + + //deconstructing is 2x as fast + if(current.remove){ + entity.deconstruct(unit, core, 2f / entity.buildCost * Timers.delta() * getBuildPower(tile)); + }else{ + entity.construct(unit, core, 1f / entity.buildCost * Timers.delta() * getBuildPower(tile)); + } + + if(unit.distanceTo(tile) <= placeDistance){ + unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(entity), 0.4f); + } + current.progress = entity.progress(); + } + + /** + * Do not call directly. + */ + default void updateMining(Unit unit){ + Tile tile = getMineTile(); + + if(tile.block() != Blocks.air || unit.distanceTo(tile.worldx(), tile.worldy()) > mineDistance || !unit.inventory.canAcceptItem(tile.floor().drops.item)){ + setMineTile(null); + }else{ + Item item = tile.floor().drops.item; + unit.rotation = Mathf.slerpDelta(unit.rotation, unit.angleTo(tile.worldx(), tile.worldy()), 0.4f); + + if(unit.inventory.canAcceptItem(item) && + Mathf.chance(Timers.delta() * (0.06 - item.hardness * 0.01) * getMinePower())){ + CallEntity.transferItemToUnit(item, + tile.worldx() + Mathf.range(tilesize / 2f), + tile.worldy() + Mathf.range(tilesize / 2f), + unit); + } + + if(Mathf.chance(0.06 * Timers.delta())){ + Effects.effect(BlockFx.pulverizeSmall, + tile.worldx() + Mathf.range(tilesize / 2f), + tile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color); + } + } + } + + /** + * Draw placement effects for an entity. This includes mining + */ + default void drawBuilding(Unit unit){ + BuildRequest request; + + synchronized(getPlaceQueue()){ + if(!isBuilding()){ + if(getMineTile() != null){ + drawMining(unit); + } + return; + } + + request = getCurrentRequest(); + } + + Tile tile = world.tile(request.x, request.y); + + if(unit.distanceTo(tile) > placeDistance){ + return; + } + + Draw.color(Palette.accent); + float focusLen = 3.8f + Mathf.absin(Timers.time(), 1.1f, 0.6f); + float px = unit.x + Angles.trnsx(unit.rotation, focusLen); + float py = unit.y + Angles.trnsy(unit.rotation, focusLen); + + float sz = Vars.tilesize * tile.block().size / 2f; + float ang = unit.angleTo(tile); + + tmptr[0].set(tile.drawx() - sz, tile.drawy() - sz); + tmptr[1].set(tile.drawx() + sz, tile.drawy() - sz); + tmptr[2].set(tile.drawx() - sz, tile.drawy() + sz); + tmptr[3].set(tile.drawx() + sz, tile.drawy() + sz); + + Arrays.sort(tmptr, (a, b) -> -Float.compare(Angles.angleDist(Angles.angle(unit.x, unit.y, a.x, a.y), ang), + Angles.angleDist(Angles.angle(unit.x, unit.y, b.x, b.y), ang))); + + float x1 = tmptr[0].x, y1 = tmptr[0].y, + x3 = tmptr[1].x, y3 = tmptr[1].y; + Translator close = Geometry.findClosest(unit.x, unit.y, tmptr); + float x2 = close.x, y2 = close.y; + + Draw.alpha(0.3f + Mathf.absin(Timers.time(), 0.9f, 0.2f)); + + Fill.tri(px, py, x2, y2, x1, y1); + Fill.tri(px, py, x2, y2, x3, y3); + + Draw.alpha(1f); + + Lines.line(px, py, x1, y1); + Lines.line(px, py, x3, y3); + + Fill.circle(px, py, 1.6f + Mathf.absin(Timers.time(), 0.8f, 1.5f)); + + Draw.color(); + } + + /** + * Internal use only. + */ + default void drawMining(Unit unit){ + Tile tile = getMineTile(); + + if(tile == null) return; + + float focusLen = 4f + Mathf.absin(Timers.time(), 1.1f, 0.5f); + float swingScl = 12f, swingMag = tilesize / 8f; + float flashScl = 0.3f; + + float px = unit.x + Angles.trnsx(unit.rotation, focusLen); + float py = unit.y + Angles.trnsy(unit.rotation, focusLen); + + float ex = tile.worldx() + Mathf.sin(Timers.time() + 48, swingScl, swingMag); + float ey = tile.worldy() + Mathf.sin(Timers.time() + 48, swingScl + 2f, swingMag); + + Draw.color(Color.LIGHT_GRAY, Color.WHITE, 1f - flashScl + Mathf.absin(Timers.time(), 0.5f, flashScl)); + Shapes.laser("minelaser", "minelaser-end", px, py, ex, ey); + + if(unit instanceof Player && ((Player) unit).isLocal){ + Draw.color(Palette.accent); + Lines.poly(tile.worldx(), tile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Timers.time()); + } + + Draw.color(); + } + + /** + * Class for storing build requests. Can be either a place or remove request. + */ + class BuildRequest{ + public final int x, y, rotation; + public final Recipe recipe; + public final boolean remove; + + public float progress; + + /** + * This creates a build request. + */ + public BuildRequest(int x, int y, int rotation, Recipe recipe){ + this.x = x; + this.y = y; + this.rotation = rotation; + this.recipe = recipe; + this.remove = false; + } + + /** + * This creates a remove request. + */ + public BuildRequest(int x, int y){ + this.x = x; + this.y = y; + this.rotation = -1; + this.recipe = Recipe.getByResult(world.tile(x, y).block()); + this.remove = true; + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java new file mode 100644 index 0000000000..296b782154 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/CarriableTrait.java @@ -0,0 +1,14 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.ucore.entities.trait.SolidTrait; + +public interface CarriableTrait extends TeamTrait, TargetTrait, SolidTrait{ + + default boolean isCarried(){ + return getCarrier() != null; + } + + CarryTrait getCarrier(); + + void setCarrier(CarryTrait carrier); +} diff --git a/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java new file mode 100644 index 0000000000..63c10a4578 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/CarryTrait.java @@ -0,0 +1,77 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.fx.UnitFx; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.entities.trait.SolidTrait; + +public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{ + @Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities) + static void dropSelf(Player player){ + if(player.getCarrier() != null){ + player.getCarrier().dropCarry(); + } + } + + @Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities) + static void setCarryOf(Player player, CarryTrait trait, CarriableTrait unit){ + if(trait == null) return; + if(player != null){ //when a server recieves this called from a player, set the carrier to the player. + trait = player; + } + + if(trait.getCarry() != null){ //already carrying something, drop it + //drop current + Effects.effect(UnitFx.unitDrop, trait.getCarry()); + trait.getCarry().setCarrier(null); + trait.setCarry(null); + + if(unit != null){ + trait.carry(unit); //now carry this new thing + } + }else if(unit != null){ //not currently carrying anything, make sure it's not null + trait.setCarry(unit); + unit.setCarrier(trait); + + Effects.effect(UnitFx.unitPickup, trait); + } + } + + /** + * Returns the thing this carrier is carrying. + */ + CarriableTrait getCarry(); + + /** + * Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state. + */ + void setCarry(CarriableTrait unit); + + /** + * Returns maximum mass this carrier can carry. + */ + float getCarryWeight(); + + /** + * Drops the unit that is being carried, if applicable. + */ + default void dropCarry(){ + carry(null); + } + + default void dropCarryLocal(){ + setCarryOf(null, this, null); + } + + /** + * Do not override unless absolutely necessary. + * Carries a unit. To drop a unit, call with {@code null}. + */ + default void carry(CarriableTrait unit){ + CallEntity.setCarryOf(this instanceof Player ? (Player) this : null, this, unit); + } +} diff --git a/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java b/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java new file mode 100644 index 0000000000..c84eaea6b4 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/InventoryTrait.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.mindustry.entities.UnitInventory; + +public interface InventoryTrait{ + UnitInventory getInventory(); +} diff --git a/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java b/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java new file mode 100644 index 0000000000..4712730e4c --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/RepairTrait.java @@ -0,0 +1,19 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.ucore.entities.trait.HealthTrait; + +//TODO implement +public interface RepairTrait extends TeamTrait{ + + HealthTrait getRepairing(); + + void setRepairing(HealthTrait trait); + + default void drawRepair(){ + if(getRepairing() == null) return; + } + + default void updateRepair(){ + if(getRepairing() == null) return; + } +} diff --git a/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java new file mode 100644 index 0000000000..4e7009771e --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/SaveTrait.java @@ -0,0 +1,9 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.ucore.entities.trait.Entity; + +/** + * Marks an entity as serializable. + */ +public interface SaveTrait extends Entity, TypeTrait, Saveable{ +} diff --git a/core/src/io/anuke/mindustry/entities/traits/Saveable.java b/core/src/io/anuke/mindustry/entities/traits/Saveable.java new file mode 100644 index 0000000000..6f3950bcb0 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/Saveable.java @@ -0,0 +1,11 @@ +package io.anuke.mindustry.entities.traits; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public interface Saveable{ + void writeSave(DataOutput stream) throws IOException; + + void readSave(DataInput stream) throws IOException; +} diff --git a/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java b/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java new file mode 100644 index 0000000000..12df85dda1 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/ShooterTrait.java @@ -0,0 +1,14 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.mindustry.type.Weapon; +import io.anuke.ucore.entities.trait.VelocityTrait; +import io.anuke.ucore.util.Timer; + +public interface ShooterTrait extends VelocityTrait, TeamTrait, InventoryTrait{ + + Timer getTimer(); + + int getShootTimer(boolean left); + + Weapon getWeapon(); +} diff --git a/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java b/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java new file mode 100644 index 0000000000..55c1763b1f --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/SpawnerTrait.java @@ -0,0 +1,12 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.world.Tile; + +public interface SpawnerTrait{ + Tile getTile(); + + void updateSpawning(Unit unit); + + float getSpawnProgress(); +} diff --git a/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java new file mode 100644 index 0000000000..56bd43f948 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/SyncTrait.java @@ -0,0 +1,68 @@ +package io.anuke.mindustry.entities.traits; + +import com.badlogic.gdx.Gdx; +import io.anuke.mindustry.net.Interpolator; +import io.anuke.ucore.entities.trait.Entity; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.threads; + +public interface SyncTrait extends Entity, TypeTrait{ + + /** + * Whether smoothing of entities is enabled when using multithreading; not yet implemented. + */ + static boolean isSmoothing(){ + return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f; + } + + /** + * Sets the position of this entity and updated the interpolator. + */ + default void setNet(float x, float y){ + set(x, y); + + if(getInterpolator() != null){ + getInterpolator().target.set(x, y); + getInterpolator().last.set(x, y); + getInterpolator().pos.set(0, 0); + getInterpolator().updateSpacing = 16; + getInterpolator().lastUpdated = 0; + } + } + + /** + * Interpolate entity position only. Override if you need to interpolate rotations or other values. + */ + default void interpolate(){ + if(getInterpolator() == null) + throw new RuntimeException("This entity must have an interpolator to interpolate()!"); + + getInterpolator().update(); + + setX(getInterpolator().pos.x); + setY(getInterpolator().pos.y); + } + + /** + * Return the interpolator used for smoothing the position. Optional. + */ + default Interpolator getInterpolator(){ + return null; + } + + /** + * Whether syncing is enabled for this entity; true by default. + */ + default boolean isSyncing(){ + return true; + } + + //Read and write sync data, usually position + void write(DataOutput data) throws IOException; + + void read(DataInput data, long time) throws IOException; +} diff --git a/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java b/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java new file mode 100644 index 0000000000..f63cca760f --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/TargetTrait.java @@ -0,0 +1,22 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.mindustry.game.Team; +import io.anuke.ucore.entities.trait.PosTrait; +import io.anuke.ucore.entities.trait.VelocityTrait; + +/** + * Base interface for targetable entities. + */ +public interface TargetTrait extends PosTrait, VelocityTrait{ + + boolean isDead(); + + Team getTeam(); + + /** + * Whether this entity is a valid target. + */ + default boolean isValid(){ + return !isDead(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java b/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java new file mode 100644 index 0000000000..3c7cbf7dd3 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/TeamTrait.java @@ -0,0 +1,8 @@ +package io.anuke.mindustry.entities.traits; + +import io.anuke.mindustry.game.Team; +import io.anuke.ucore.entities.trait.Entity; + +public interface TeamTrait extends Entity{ + Team getTeam(); +} diff --git a/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java new file mode 100644 index 0000000000..9bde9509fc --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/traits/TypeTrait.java @@ -0,0 +1,46 @@ +package io.anuke.mindustry.entities.traits; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectIntMap; +import io.anuke.ucore.function.Supplier; + +public interface TypeTrait{ + int[] lastRegisteredID = {0}; + Array> registeredTypes = new Array<>(); + ObjectIntMap> typeToID = new ObjectIntMap<>(); + + /** + * Register and return a type ID. The supplier should return a fresh instace of that type. + */ + static void registerType(Class type, Supplier supplier){ + if(typeToID.get(type, -1) != -1){ + throw new RuntimeException("Type is already registered: '" + type + "'!"); + } + + registeredTypes.add(supplier); + int result = lastRegisteredID[0]; + typeToID.put(type, result); + lastRegisteredID[0]++; + } + + /** + * Registers a syncable type by ID. + */ + static Supplier getTypeByID(int id){ + if(id == -1){ + throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?"); + } + return registeredTypes.get(id); + } + + /** + * Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX. + * Do not override! + */ + default int getTypeID(){ + int id = typeToID.get(getClass(), -1); + if(id == -1) + throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?"); + return id; + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/BaseUnit.java b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java new file mode 100644 index 0000000000..51a2e11c9a --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/BaseUnit.java @@ -0,0 +1,433 @@ +package io.anuke.mindustry.entities.units; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.fx.ExplosionFx; +import io.anuke.mindustry.entities.Damage; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.effect.ScorchDecal; +import io.anuke.mindustry.entities.traits.ShooterTrait; +import io.anuke.mindustry.entities.traits.SpawnerTrait; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.type.Weapon; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.units.UnitFactory.UnitFactoryEntity; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Timer; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.*; + +public abstract class BaseUnit extends Unit implements ShooterTrait{ + protected static int timerIndex = 0; + + protected static final int timerTarget = timerIndex++; + + protected static final int timerShootLeft = timerIndex++; + protected static final int timerShootRight = timerIndex++; + + protected UnitType type; + protected Timer timer = new Timer(5); + protected StateMachine state = new StateMachine(); + protected TargetTrait target; + + protected boolean isWave; + protected Squad squad; + protected int spawner; + + /** + * internal constructor used for deserialization, DO NOT USE + */ + public BaseUnit(){ + } + + @Remote(called = Loc.server, in = In.entities) + public static void onUnitDeath(BaseUnit unit){ + if(unit == null) return; + + if(Net.server() || !Net.active()){ + UnitDrops.dropItems(unit); + } + + float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f); + float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f); + Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize() / 2f, Palette.darkFlame); + + unit.onSuperDeath(); + + ScorchDecal.create(unit.x, unit.y); + Effects.effect(ExplosionFx.explosion, unit); + Effects.shake(2f, 2f, unit); + + //must run afterwards so the unit's group is not null + threads.runDelay(unit::remove); + } + + /** + * Initialize the type and team of this unit. Only call once! + */ + public void init(UnitType type, Team team){ + if(this.type != null) throw new RuntimeException("This unit is already initialized!"); + + this.type = type; + this.team = team; + } + + public UnitType getType(){ + return type; + } + + public Tile getSpawner(){ + return world.tile(spawner); + } + + public void setSpawner(Tile tile){ + this.spawner = tile.packedPosition(); + } + + /** + * Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo. + */ + public void setWave(){ + isWave = true; + } + + public void setSquad(Squad squad){ + this.squad = squad; + squad.units++; + } + + public void rotate(float angle){ + rotation = Mathf.slerpDelta(rotation, angle, type.rotatespeed); + } + + public boolean targetHasFlag(BlockFlag flag){ + return target instanceof TileEntity && + ((TileEntity) target).tile.block().flags.contains(flag); + } + + public void updateRespawning(){ + if(spawner == -1) return; + + Tile tile = world.tile(spawner); + if(tile != null && tile.entity != null){ + if(tile.entity instanceof SpawnerTrait){ + ((SpawnerTrait) tile.entity).updateSpawning(this); + } + }else{ + spawner = -1; + } + } + + public void setState(UnitState state){ + this.state.set(state); + } + + public void retarget(Runnable run){ + if(timer.get(timerTarget, 20)){ + run.run(); + } + } + + /** + * Only runs when the unit has a target. + */ + public void behavior(){ + + } + + public void updateTargeting(){ + if(target == null || (target instanceof Unit && (target.isDead() || target.getTeam() == team)) + || (target instanceof TileEntity && ((TileEntity) target).tile.entity == null)){ + target = null; + } + } + + public void targetClosestAllyFlag(BlockFlag flag){ + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag)); + if(target != null) this.target = target.entity; + } + + public void targetClosestEnemyFlag(BlockFlag flag){ + Tile target = Geometry.findClosest(x, y, world.indexer().getEnemy(team, flag)); + if(target != null) this.target = target.entity; + } + + public void targetClosest(){ + target = Units.getClosestTarget(team, x, y, inventory.getAmmoRange()); + } + + public TileEntity getClosestEnemyCore(){ + if(Vars.state.teams.has(team)){ + ObjectSet datas = Vars.state.teams.enemyDataOf(team); + + for(TeamData data : datas){ + Tile tile = Geometry.findClosest(x, y, data.cores); + if(tile != null){ + return tile.entity; + } + } + } + return null; + } + + public UnitState getStartState(){ + return null; + } + + protected void drawItems(){ + float backTrns = 4f, itemSize = 5f; + if(inventory.hasItem()){ + ItemStack stack = inventory.getItem(); + int stored = Mathf.clamp(stack.amount / 6, 1, 8); + + for(int i = 0; i < stored; i++){ + float angT = i == 0 ? 0 : Mathf.randomSeedRange(i + 2, 60f); + float lenT = i == 0 ? 0 : Mathf.randomSeedRange(i + 3, 1f) - 1f; + Draw.rect(stack.item.region, + x + Angles.trnsx(rotation + 180f + angT, backTrns + lenT), + y + Angles.trnsy(rotation + 180f + angT, backTrns + lenT), + itemSize, itemSize, rotation); + } + } + } + + @Override + public boolean isValid(){ + return super.isValid() && isAdded(); + } + + @Override + public Timer getTimer(){ + return timer; + } + + @Override + public int getShootTimer(boolean left){ + return left ? timerShootLeft : timerShootRight; + } + + @Override + public Weapon getWeapon(){ + return type.weapon; + } + + @Override + public TextureRegion getIconRegion(){ + return type.iconRegion; + } + + @Override + public int getItemCapacity(){ + return type.itemCapacity; + } + + @Override + public int getAmmoCapacity(){ + return type.ammoCapacity; + } + + @Override + public boolean isInfiniteAmmo(){ + return isWave; + } + + @Override + public void interpolate(){ + super.interpolate(); + + if(interpolator.values.length > 0){ + rotation = interpolator.values[0]; + } + } + + @Override + public float maxHealth(){ + return type.health; + } + + @Override + public float getArmor(){ + return type.armor; + } + + @Override + public boolean acceptsAmmo(Item item){ + return getWeapon().getAmmoType(item) != null && inventory.canAcceptAmmo(getWeapon().getAmmoType(item)); + } + + @Override + public void addAmmo(Item item){ + inventory.addAmmo(getWeapon().getAmmoType(item)); + } + + @Override + public float getSize(){ + return 8; + } + + @Override + public float getMass(){ + return type.mass; + } + + @Override + public boolean isFlying(){ + return type.isFlying; + } + + @Override + public void update(){ + if(hitTime > 0){ + hitTime -= Timers.delta(); + } + + if(hitTime < 0) hitTime = 0; + + if(isDead()){ + updateRespawning(); + return; + } + + if(Net.client()){ + interpolate(); + status.update(this); + return; + } + + if(!Net.client()){ + avoidOthers(8f); + } + + if(squad != null){ + squad.update(); + } + + updateTargeting(); + + state.update(); + updateVelocityStatus(type.drag, type.maxVelocity); + + if(target != null) behavior(); + + if(!isWave){ + x = Mathf.clamp(x, 0, world.width() * tilesize); + y = Mathf.clamp(y, 0, world.height() * tilesize); + } + } + + @Override + public void draw(){ + + } + + @Override + public void drawUnder(){ + + } + + @Override + public void drawOver(){ + + } + + @Override + public void removed(){ + Tile tile = world.tile(spawner); + + if(tile != null && tile.entity instanceof UnitFactoryEntity){ + UnitFactoryEntity factory = (UnitFactoryEntity) tile.entity; + factory.hasSpawned = false; + } + + spawner = -1; + } + + @Override + public float drawSize(){ + return 14; + } + + @Override + public void onDeath(){ + CallEntity.onUnitDeath(this); + } + + @Override + public void added(){ + hitbox.setSize(type.hitsize); + hitboxTile.setSize(type.hitsizeTile); + state.set(getStartState()); + + health(maxHealth()); + } + + @Override + public EntityGroup targetGroup(){ + return unitGroups[team.ordinal()]; + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + super.writeSave(stream); + stream.writeByte(type.id); + stream.writeBoolean(isWave); + stream.writeInt(spawner); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + super.readSave(stream); + byte type = stream.readByte(); + this.isWave = stream.readBoolean(); + this.spawner = stream.readInt(); + + this.type = UnitType.getByID(type); + add(); + } + + @Override + public void write(DataOutput data) throws IOException{ + super.writeSave(data); + data.writeByte(type.id); + data.writeInt(spawner); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + float lastx = x, lasty = y, lastrot = rotation; + super.readSave(data); + this.type = UnitType.getByID(data.readByte()); + this.spawner = data.readInt(); + + interpolator.read(lastx, lasty, x, y, time, rotation); + rotation = lastrot; + } + + public void onSuperDeath(){ + super.onDeath(); + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java new file mode 100644 index 0000000000..b003e03984 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/FlyingUnit.java @@ -0,0 +1,258 @@ +package io.anuke.mindustry.entities.units; + +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.entities.Predict; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.traits.CarriableTrait; +import io.anuke.mindustry.entities.traits.CarryTrait; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.graphics.Trail; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.AmmoType; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; + +import static io.anuke.mindustry.Vars.world; + +public abstract class FlyingUnit extends BaseUnit implements CarryTrait{ + protected static Translator vec = new Translator(); + protected static float wobblyness = 0.6f; + protected final UnitState + + resupply = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.resupplyPoint)){ + retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint)); + }else{ + circle(20f); + } + } + }, + + idle = new UnitState(){ + public void update(){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); + + if(target != null){ + setState(attack); + } + }); + + target = getClosestCore(); + if(target != null){ + circle(50f); + } + velocity.scl(0.8f); + } + }, + + attack = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(Units.invalidateTarget(target, team, x, y)){ + target = null; + } + + if(!inventory.hasAmmo()){ + state.set(resupply); + }else if(target == null){ + retarget(() -> { + targetClosest(); + targetClosestEnemyFlag(BlockFlag.target); + targetClosestEnemyFlag(BlockFlag.producer); + + if(target == null){ + setState(idle); + } + }); + }else{ + attack(150f); + + if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation + && distanceTo(target) < inventory.getAmmo().getRange()){ + AmmoType ammo = inventory.getAmmo(); + inventory.useAmmo(); + + Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed); + + getWeapon().update(FlyingUnit.this, to.x, to.y); + } + } + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= maxHealth()){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + retarget(() -> { + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) FlyingUnit.this.target = target.entity; + }); + }else{ + circle(20f); + } + } + }; + protected Trail trail = new Trail(8); + protected CarriableTrait carrying; + + //instantiation only + public FlyingUnit(){ + + } + + @Override + public void drawShadow(){ + Draw.rect(type.region, x + elevation * elevationScale, y - elevation * elevationScale, rotation - 90); + } + + @Override + public CarriableTrait getCarry(){ + return carrying; + } + + @Override + public void setCarry(CarriableTrait unit){ + this.carrying = unit; + } + + @Override + public float getCarryWeight(){ + return type.carryWeight; + } + + @Override + public void update(){ + super.update(); + + updateRotation(); + trail.update(x + Angles.trnsx(rotation + 180f, 6f) + Mathf.range(wobblyness), + y + Angles.trnsy(rotation + 180f, 6f) + Mathf.range(wobblyness)); + + wobble(); + } + + @Override + public void draw(){ + Draw.alpha(hitTime / hitDuration); + + Draw.rect(type.name, x, y, rotation - 90); + + drawItems(); + + Draw.alpha(1f); + } + + @Override + public void drawOver(){ + trail.draw(Palette.lightTrail, 5f); + } + + @Override + public void behavior(){ + if(health <= health * type.retreatPercent && !isWave && + Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){ + setState(retreat); + } + + if(squad != null){ + squad.direction.add(velocity.x / squad.units, velocity.y / squad.units); + velocity.setAngle(Mathf.slerpDelta(velocity.angle(), squad.direction.angle(), 0.3f)); + } + } + + @Override + public UnitState getStartState(){ + return attack; + } + + @Override + public float drawSize(){ + return 60; + } + + protected void wobble(){ + if(Net.client()) return; + + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); + + if(velocity.len() <= 0.2f){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 8f); + } + } + + protected void updateRotation(){ + rotation = velocity.angle(); + } + + protected void circle(float circleLength){ + circle(circleLength, type.speed); + } + + protected void circle(float circleLength, float speed){ + if(target == null) return; + + vec.set(target.getX() - x, target.getY() - y); + + if(vec.len() < circleLength){ + vec.rotate((circleLength - vec.len()) / circleLength * 180f); + } + + vec.setLength(speed * Timers.delta()); + + velocity.add(vec); + } + + protected void moveTo(float circleLength){ + if(target == null) return; + + vec.set(target.getX() - x, target.getY() - y); + + float length = Mathf.clamp((distanceTo(target) - circleLength) / 100f, -1f, 1f); + + vec.setLength(type.speed * Timers.delta() * length); + if(length < 0) vec.rotate(180f); + + velocity.add(vec); + } + + protected void attack(float circleLength){ + vec.set(target.getX() - x, target.getY() - y); + + float ang = angleTo(target); + float diff = Angles.angleDist(ang, rotation); + + if(diff > 100f && vec.len() < circleLength){ + vec.setAngle(velocity.angle()); + }else{ + vec.setAngle(Mathf.slerpDelta(velocity.angle(), vec.angle(), 0.44f)); + } + + vec.setLength(type.speed * Timers.delta()); + + velocity.add(vec); + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/GroundUnit.java b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java new file mode 100644 index 0000000000..5dba19a82d --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/GroundUnit.java @@ -0,0 +1,269 @@ +package io.anuke.mindustry.entities.units; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Vector2; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.entities.Predict; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.type.AmmoType; +import io.anuke.mindustry.type.Upgrade; +import io.anuke.mindustry.type.Weapon; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.world; + +public abstract class GroundUnit extends BaseUnit{ + protected static Translator vec = new Translator(); + + protected float walkTime; + protected float baseRotation; + public final UnitState + + resupply = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint)); + + if(tile != null && distanceTo(tile) > 40){ + moveAwayFromCore(); + } + + //TODO move toward resupply point + if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){ + state.set(attack); + } + } + }, + attack = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + TileEntity core = getClosestEnemyCore(); + float dst = core == null ? 0 : distanceTo(core); + + if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange() / 1.1f){ + target = core; + }else{ + retarget(() -> targetClosest()); + } + + if(!inventory.hasAmmo()){ + state.set(resupply); + }else if(target != null){ + if(core != null){ + if(dst > inventory.getAmmo().getRange() * 0.5f){ + moveToCore(); + } + + }else{ + moveToCore(); + } + + if(distanceTo(target) < inventory.getAmmo().getRange()){ + rotate(angleTo(target)); + + if(Mathf.angNear(angleTo(target), rotation, 13f)){ + AmmoType ammo = inventory.getAmmo(); + + Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed); + + getWeapon().update(GroundUnit.this, to.x, to.y); + } + } + + }else{ + moveToCore(); + } + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= health){ + state.set(attack); + } + + moveAwayFromCore(); + } + }; + protected Weapon weapon; + + @Override + public void init(UnitType type, Team team){ + super.init(type, team); + this.weapon = type.weapon; + } + + @Override + public void interpolate(){ + super.interpolate(); + + if(interpolator.values.length > 1){ + baseRotation = interpolator.values[1]; + } + } + + @Override + public void move(float x, float y){ + if(Mathf.dst(x, y) > 0.01f){ + baseRotation = Mathf.slerpDelta(baseRotation, Mathf.atan2(x, y), type.baseRotateSpeed); + } + super.move(x, y); + } + + @Override + public UnitState getStartState(){ + return resupply; + } + + @Override + public void update(){ + super.update(); + + if(!velocity.isZero(0.0001f) && (target == null || !inventory.hasAmmo() || (inventory.hasAmmo() && distanceTo(target) > inventory.getAmmoRange()))){ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.2f); + } + } + + @Override + public Weapon getWeapon(){ + return weapon; + } + + public void setWeapon(Weapon weapon){ + this.weapon = weapon; + } + + @Override + public void draw(){ + Draw.alpha(hitTime / hitDuration); + + float walktime = walkTime; + + float ft = Mathf.sin(walktime, 6f, 2f); + + Floor floor = getFloorOn(); + + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, 0.5f); + } + + for(int i : Mathf.signs){ + Draw.rect(type.legRegion, + x + Angles.trnsx(baseRotation, ft * i), + y + Angles.trnsy(baseRotation, ft * i), + 12f * i, 12f - Mathf.clamp(ft * i, 0, 2), baseRotation - 90); + } + + if(floor.isLiquid){ + Draw.tint(Color.WHITE, floor.liquidColor, drownTime * 0.4f); + }else{ + Draw.tint(Color.WHITE); + } + + Draw.rect(type.baseRegion, x, y, baseRotation - 90); + + Draw.rect(type.region, x, y, rotation - 90); + + for(int i : Mathf.signs){ + float tra = rotation - 90, trY = -weapon.getRecoil(this, i > 0) + type.weaponOffsetY; + float w = i > 0 ? -12 : 12; + Draw.rect(weapon.equipRegion, + x + Angles.trnsx(tra, type.weaponOffsetX * i, trY), + y + Angles.trnsy(tra, type.weaponOffsetX * i, trY), w, 12, rotation - 90); + } + + drawItems(); + + Draw.alpha(1f); + } + + @Override + public void behavior(){ + if(health <= health * type.retreatPercent && !isWave){ + setState(retreat); + } + } + + @Override + public void updateTargeting(){ + super.updateTargeting(); + + if(Units.invalidateTarget(target, team, x, y, Float.MAX_VALUE)){ + target = null; + } + } + + @Override + public void write(DataOutput data) throws IOException{ + super.write(data); + data.writeByte(weapon.id); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + super.read(data, time); + weapon = Upgrade.getByID(data.readByte()); + } + + @Override + public void writeSave(DataOutput stream) throws IOException{ + stream.writeByte(weapon.id); + super.writeSave(stream); + } + + @Override + public void readSave(DataInput stream) throws IOException{ + weapon = Upgrade.getByID(stream.readByte()); + super.readSave(stream); + } + + protected void moveToCore(){ + Tile tile = world.tileWorld(x, y); + if(tile == null) return; + Tile targetTile = world.pathfinder().getTargetTile(team, tile); + + if(tile == targetTile) return; + + vec.trns(baseRotation, type.speed); + + baseRotation = Mathf.slerpDelta(baseRotation, angleTo(targetTile), 0.05f); + walkTime += Timers.delta(); + velocity.add(vec); + } + + protected void moveAwayFromCore(){ + Tile tile = world.tileWorld(x, y); + Tile targetTile = world.pathfinder().getTargetTile(Vars.state.teams.enemiesOf(team).first(), tile); + + if(tile == targetTile) return; + + vec.trns(baseRotation, type.speed); + + baseRotation = Mathf.slerpDelta(baseRotation, angleTo(targetTile), 0.05f); + walkTime += Timers.delta(); + velocity.add(vec); + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/Squad.java b/core/src/io/anuke/mindustry/entities/units/Squad.java new file mode 100644 index 0000000000..1068de0a6b --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/Squad.java @@ -0,0 +1,24 @@ +package io.anuke.mindustry.entities.units; + +import com.badlogic.gdx.math.Vector2; +import io.anuke.ucore.util.Translator; + +import static io.anuke.mindustry.Vars.threads; + +/** + * Used to group entities together, for formations and such. + * Usually, squads are used by units spawned in the same wave. + */ +public class Squad{ + public Vector2 direction = new Translator(); + public int units; + + private long lastUpdated; + + protected void update(){ + if(threads.getFrameID() != lastUpdated){ + direction.setZero(); + lastUpdated = threads.getFrameID(); + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/StateMachine.java b/core/src/io/anuke/mindustry/entities/units/StateMachine.java new file mode 100644 index 0000000000..a676716693 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/StateMachine.java @@ -0,0 +1,20 @@ +package io.anuke.mindustry.entities.units; + +public class StateMachine{ + private UnitState state; + + public void update(){ + if(state != null) state.update(); + } + + public void set(UnitState next){ + if(next == state) return; + if(state != null) state.exited(); + this.state = next; + if(next != null) next.entered(); + } + + public boolean is(UnitState state){ + return this.state == state; + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/UnitDrops.java b/core/src/io/anuke/mindustry/entities/units/UnitDrops.java new file mode 100644 index 0000000000..2ba240de70 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/UnitDrops.java @@ -0,0 +1,32 @@ +package io.anuke.mindustry.entities.units; + +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.type.Item; +import io.anuke.ucore.util.Mathf; + +public class UnitDrops{ + private static final int maxItems = 200; + private static Item[] dropTable; + + public static void dropItems(BaseUnit unit){ + if(Vars.itemGroup.size() > maxItems){ + return; + } + + if(dropTable == null){ + dropTable = new Item[]{Items.tungsten, Items.lead, Items.carbide}; + } + + for(int i = 0; i < 3; i++){ + for(Item item : dropTable){ + if(Mathf.chance(0.03)){ + int amount = Mathf.random(20, 40); + ItemDrop.create(item, amount, unit.x + Mathf.range(2f), unit.y + Mathf.range(2f), + unit.getVelocity().x + Mathf.range(3f), unit.getVelocity().y + Mathf.range(3f)); + } + } + } + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/UnitState.java b/core/src/io/anuke/mindustry/entities/units/UnitState.java new file mode 100644 index 0000000000..5c5f9c64a8 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/UnitState.java @@ -0,0 +1,12 @@ +package io.anuke.mindustry.entities.units; + +public interface UnitState{ + default void entered(){ + } + + default void exited(){ + } + + default void update(){ + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/UnitType.java b/core/src/io/anuke/mindustry/entities/units/UnitType.java new file mode 100644 index 0000000000..848ef6d372 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/UnitType.java @@ -0,0 +1,111 @@ +package io.anuke.mindustry.entities.units; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.Weapons; +import io.anuke.mindustry.entities.traits.TypeTrait; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.type.Weapon; +import io.anuke.mindustry.ui.ContentDisplay; +import io.anuke.ucore.function.Supplier; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; + +//TODO merge unit type with mech +public class UnitType implements UnlockableContent{ + private static byte lastid = 0; + private static Array types = new Array<>(); + public final String name; + public final byte id; + protected final Supplier constructor; + public float health = 60; + public float hitsize = 5f; + public float hitsizeTile = 4f; + public float speed = 0.4f; + public float range = 160; + public float rotatespeed = 0.1f; + public float baseRotateSpeed = 0.1f; + public float mass = 1f; + public boolean isFlying; + public float drag = 0.1f; + public float maxVelocity = 5f; + public float retreatPercent = 0.2f; + public float armor = 0f; + public float carryWeight = 1f; + public int ammoCapacity = 100; + public int itemCapacity = 30; + public int mineLevel = 2; + public float buildPower = 0.3f, minePower = 0.7f, healSpeed = 0.1f; + public Weapon weapon = Weapons.blaster; + public float weaponOffsetX, weaponOffsetY; + + public TextureRegion iconRegion, legRegion, baseRegion, region; + + public UnitType(String name, Class type, Supplier mainConstructor){ + this.id = lastid++; + this.name = name; + this.constructor = mainConstructor; + + types.add(this); + + TypeTrait.registerType(type, mainConstructor); + } + + public static UnitType getByID(byte id){ + return types.get(id); + } + + public static Array all(){ + return types; + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayUnit(table, this); + } + + @Override + public String localizedName(){ + return Bundles.get("unit." + name + ".name"); + } + + @Override + public TextureRegion getContentIcon(){ + return iconRegion; + } + + @Override + public void load(){ + iconRegion = Draw.region("unit-icon-" + name); + region = Draw.region(name); + + if(!isFlying){ + legRegion = Draw.region(name + "-leg"); + baseRegion = Draw.region(name + "-base"); + } + } + + @Override + public String getContentTypeName(){ + return "unit-type"; + } + + @Override + public String getContentName(){ + return name; + } + + @Override + public Array getAll(){ + return types; + } + + public BaseUnit create(Team team){ + BaseUnit unit = constructor.get(); + unit.init(this, team); + return unit; + } +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Drone.java b/core/src/io/anuke/mindustry/entities/units/types/Drone.java new file mode 100644 index 0000000000..f51229f6cf --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Drone.java @@ -0,0 +1,457 @@ +package io.anuke.mindustry.entities.units.types; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.ObjectSet; +import com.badlogic.gdx.utils.Queue; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.traits.BuilderTrait; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.entities.units.FlyingUnit; +import io.anuke.mindustry.entities.units.UnitState; +import io.anuke.mindustry.game.EventType.BlockBuildEvent; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.net.Net; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BuildBlock; +import io.anuke.mindustry.world.blocks.BuildBlock.BuildEntity; +import io.anuke.mindustry.world.meta.BlockFlag; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.EntityPhysics; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Shapes; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Geometry; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.ThreadQueue; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.*; + +public class Drone extends FlyingUnit implements BuilderTrait{ + protected static ObjectSet toMine; + protected static float discoverRange = 120f; + protected static boolean initialized; + + protected Item targetItem; + protected Tile mineTile; + protected Queue placeQueue = new ThreadQueue<>(); + + public final UnitState + + build = new UnitState(){ + + public void entered(){ + if(!(target instanceof BuildEntity)){ + target = null; + } + } + + public void update(){ + BuildEntity entity = (BuildEntity) target; + TileEntity core = getClosestCore(); + + if(entity == null){ + setState(repair); + return; + } + + if(core == null) return; + + if(entity.progress() < 1f && entity.tile.block() instanceof BuildBlock){ //building is valid + if(!isBuilding() && distanceTo(target) < placeDistance * 0.9f){ //within distance, begin placing + getPlaceQueue().addLast(new BuildRequest(entity.tile.x, entity.tile.y, entity.tile.getRotation(), entity.recipe)); + } + + //if it's missing requirements, try and mine them + for(ItemStack stack : entity.recipe.requirements){ + if(!core.items.has(stack.item, stack.amount) && toMine.contains(stack.item)){ + targetItem = stack.item; + getPlaceQueue().clear(); + setState(mine); + return; + } + } + + circle(placeDistance * 0.7f); + }else{ //building isn't valid + setState(repair); + } + } + }, + + repair = new UnitState(){ + + public void entered(){ + target = null; + } + + public void update(){ + if(target != null && (((TileEntity) target).health >= ((TileEntity) target).tile.block().health + || target.distanceTo(Drone.this) > discoverRange)){ + target = null; + } + + if(target == null){ + retarget(() -> { + target = Units.findAllyTile(team, x, y, discoverRange, + tile -> tile.entity != null && tile.entity.health + 0.0001f < tile.block().health); + + if(target == null){ + setState(mine); + } + }); + }else if(target.distanceTo(Drone.this) > type.range){ + circle(type.range); + }else{ + TileEntity entity = (TileEntity) target; + entity.health += type.healSpeed * Timers.delta(); + entity.health = Mathf.clamp(entity.health, 0, entity.tile.block().health); + } + } + }, + + mine = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + TileEntity entity = getClosestCore(); + + if(entity == null) return; + + if(targetItem == null){ + findItem(); + } + + //core full + if(targetItem != null && entity.tile.block().acceptStack(targetItem, 1, entity.tile, Drone.this) == 0){ + setState(repair); + return; + } + + //if inventory is full, drop it off. + if(inventory.isFull()){ + setState(drop); + }else{ + if(targetItem != null && !inventory.canAcceptItem(targetItem)){ + setState(drop); + return; + } + + retarget(() -> { + if(findItemDrop()){ + return; + } + + if(getMineTile() == null){ + findItem(); + } + + if(targetItem == null) return; + + target = world.indexer().findClosestOre(x, y, targetItem); + }); + + if(target instanceof Tile){ + moveTo(type.range / 1.5f); + + if(distanceTo(target) < type.range && mineTile != target){ + setMineTile((Tile) target); + } + + if(((Tile) target).block() != Blocks.air){ + setState(drop); + } + } + } + } + + public void exited(){ + setMineTile(null); + } + }, + pickup = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + ItemDrop item = (ItemDrop) target; + + if(inventory.isFull() || !inventory.canAcceptItem(item.getItem(), 1)){ + setState(drop); + return; + } + + if(distanceTo(item) < 4){ + item.collision(Drone.this, x, y); + } + + //item has been picked up + if(item.getAmount() == 0){ + if(!findItemDrop()){ + setState(drop); + } + } + + moveTo(0f); + } + }, + drop = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(inventory.isEmpty()){ + setState(mine); + return; + } + + target = getClosestCore(); + + if(target == null) return; + + TileEntity tile = (TileEntity) target; + + if(distanceTo(target) < type.range){ + if(tile.tile.block().acceptStack(inventory.getItem().item, inventory.getItem().amount, tile.tile, Drone.this) == inventory.getItem().amount){ + CallEntity.transferItemTo(inventory.getItem().item, inventory.getItem().amount, x, y, tile.tile); + inventory.clearItem(); + } + + setState(repair); + } + + circle(type.range / 1.8f); + } + }, + retreat = new UnitState(){ + public void entered(){ + target = null; + } + + public void update(){ + if(health >= health){ + state.set(attack); + }else if(!targetHasFlag(BlockFlag.repair)){ + if(timer.get(timerTarget, 20)){ + Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)); + if(target != null) Drone.this.target = target.entity; + } + }else{ + circle(40f); + } + } + }; + + { + initEvents(); + } + + /** + * Initialize placement event notifier system. + * Static initialization is to be avoided, thus, this is done lazily. + */ + private static void initEvents(){ + if(initialized) return; + + toMine = ObjectSet.with(Items.lead, Items.tungsten); + + Events.on(BlockBuildEvent.class, (team, tile) -> { + EntityGroup group = unitGroups[team.ordinal()]; + + if(!(tile.entity instanceof BuildEntity)) return; + BuildEntity entity = tile.entity(); + + for(BaseUnit unit : group.all()){ + if(unit instanceof Drone){ + ((Drone) unit).notifyPlaced(entity); + } + } + }); + + initialized = true; + } + + private void notifyPlaced(BuildEntity entity){ + float timeToBuild = entity.recipe.cost; + float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0); + + if(dist / type.maxVelocity < timeToBuild * 0.9f){ + //CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe); + target = entity; + setState(build); + } + } + + @Override + public float getBuildPower(Tile tile){ + return type.buildPower; + } + + @Override + public float getMinePower(){ + return type.minePower; + } + + @Override + public Queue getPlaceQueue(){ + return placeQueue; + } + + @Override + public Tile getMineTile(){ + return mineTile; + } + + @Override + public void setMineTile(Tile tile){ + mineTile = tile; + } + + @Override + public void update(){ + super.update(); + + if(Net.client() && state.is(repair) && target instanceof TileEntity){ + TileEntity entity = (TileEntity) target; + entity.health += type.healSpeed * Timers.delta(); + entity.health = Mathf.clamp(entity.health, 0, entity.tile.block().health); + } + + x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f); + y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f); + + updateBuilding(this); + } + + @Override + protected void updateRotation(){ + if(target != null && (state.is(repair) || state.is(mine))){ + rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f); + }else{ + rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f); + } + + if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){ + rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f); + } + } + + @Override + public void behavior(){ + if(health <= health * type.retreatPercent && + Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){ + setState(retreat); + } + } + + @Override + public UnitState getStartState(){ + return repair; + } + + @Override + public void drawOver(){ + trail.draw(Palette.lightTrail, 3f); + + TargetTrait entity = target; + + if(entity instanceof TileEntity && state.is(repair)){ + float len = 5f; + Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f)); + Shapes.laser("beam", "beam-end", + x + Angles.trnsx(rotation, len), + y + Angles.trnsy(rotation, len), + entity.getX(), entity.getY()); + Draw.color(); + } + + drawBuilding(this); + } + + @Override + public float drawSize(){ + return isBuilding() ? placeDistance * 2f : 30f; + } + + @Override + public float getAmmoFraction(){ + return inventory.getItem().amount / (float) type.itemCapacity; + } + + protected void findItem(){ + TileEntity entity = getClosestCore(); + if(entity == null){ + return; + } + targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b))); + } + + protected boolean findItemDrop(){ + TileEntity core = getClosestCore(); + + if(core == null) return false; + + //find nearby dropped items to pick up if applicable + ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f, + item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() && + inventory.canAcceptItem(item.getItem(), 1)); + if(drop != null){ + setState(pickup); + target = drop; + return true; + } + return false; + } + + @Override + public boolean canCreateBlocks(){ + return false; + } + + @Override + public void write(DataOutput data) throws IOException{ + super.write(data); + data.writeInt(mineTile == null ? -1 : mineTile.packedPosition()); + data.writeInt(state.is(repair) && target instanceof TileEntity ? ((TileEntity)target).tile.packedPosition() : -1); + writeBuilding(data); + } + + @Override + public void read(DataInput data, long time) throws IOException{ + super.read(data, time); + int mined = data.readInt(); + int repairing = data.readInt(); + + readBuilding(data); + + if(mined != -1){ + mineTile = world.tile(mined); + } + + if(repairing != -1){ + Tile tile = world.tile(repairing); + target = tile.entity; + state.set(repair); + }else{ + state.set(retreat); + } + } + +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java b/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java new file mode 100644 index 0000000000..90119c8550 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Fabricator.java @@ -0,0 +1,5 @@ +package io.anuke.mindustry.entities.units.types; + +public class Fabricator extends Drone{ + +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java b/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java new file mode 100644 index 0000000000..b87e0d2f81 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Monsoon.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.units.types; + +import io.anuke.mindustry.entities.units.FlyingUnit; + +public class Monsoon extends FlyingUnit{ + +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Scout.java b/core/src/io/anuke/mindustry/entities/units/types/Scout.java new file mode 100644 index 0000000000..bdfdbe4f89 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Scout.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.units.types; + +import io.anuke.mindustry.entities.units.GroundUnit; + +public class Scout extends GroundUnit{ + +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Titan.java b/core/src/io/anuke/mindustry/entities/units/types/Titan.java new file mode 100644 index 0000000000..e2c6a845bc --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Titan.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.units.types; + +import io.anuke.mindustry.entities.units.GroundUnit; + +public class Titan extends GroundUnit{ + +} diff --git a/core/src/io/anuke/mindustry/entities/units/types/Vtol.java b/core/src/io/anuke/mindustry/entities/units/types/Vtol.java new file mode 100644 index 0000000000..cf02014794 --- /dev/null +++ b/core/src/io/anuke/mindustry/entities/units/types/Vtol.java @@ -0,0 +1,7 @@ +package io.anuke.mindustry.entities.units.types; + +import io.anuke.mindustry.entities.units.FlyingUnit; + +public class Vtol extends FlyingUnit{ + +} diff --git a/core/src/io/anuke/mindustry/game/Content.java b/core/src/io/anuke/mindustry/game/Content.java new file mode 100644 index 0000000000..accf8d0947 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Content.java @@ -0,0 +1,33 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.utils.Array; + +/** + * Base interface for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. + */ +public interface Content{ + + /** + * Returns the type name of this piece of content. + * This should return the same value for all instances of this content type. + */ + String getContentTypeName(); + + /** + * Returns a list of all instances of this content. + */ + Array getAll(); + + /** + * Called after all content is created. Do not use to load regions or texture data! + */ + default void init(){ + } + + /** + * Called after all content is created, only on non-headless versions. + * Use for loading regions or other image data. + */ + default void load(){ + } +} diff --git a/core/src/io/anuke/mindustry/game/ContentDatabase.java b/core/src/io/anuke/mindustry/game/ContentDatabase.java new file mode 100644 index 0000000000..64afe3e782 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/ContentDatabase.java @@ -0,0 +1,90 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectMap.Entry; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.game.EventType.UnlockEvent; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Settings; + +public class ContentDatabase{ + /** Maps unlockable type names to a set of unlocked content.*/ + private ObjectMap> unlocked = new ObjectMap<>(); + /** Whether unlockables have changed since the last save.*/ + private boolean dirty; + + /** Returns whether or not this piece of content is unlocked yet.*/ + public boolean isUnlocked(UnlockableContent content){ + if(!unlocked.containsKey(content.getContentTypeName())){ + unlocked.put(content.getContentTypeName(), new ObjectSet<>()); + } + + ObjectSet set = unlocked.get(content.getContentTypeName()); + + return set.contains(content.getContentName()); + } + + /** + * Makes this piece of content 'unlocked', if possible. + * If this piece of content is already unlocked or cannot be unlocked due to dependencies, nothing changes. + * Results are not saved until you call {@link #save()}. + * + * @return whether or not this content was newly unlocked. + */ + public boolean unlockContent(UnlockableContent content){ + if(!content.canBeUnlocked()) return false; + + if(!unlocked.containsKey(content.getContentTypeName())){ + unlocked.put(content.getContentTypeName(), new ObjectSet<>()); + } + + boolean ret = unlocked.get(content.getContentTypeName()).add(content.getContentName()); + + //fire unlock event so other classes can use it + if(ret){ + content.onUnlock(); + Events.fire(UnlockEvent.class, content); + dirty = true; + } + + return ret; + } + + /** Returns whether unlockables have changed since the last save.*/ + public boolean isDirty(){ + return dirty; + } + + /** Clears all unlocked content.*/ + public void reset(){ + unlocked.clear(); + dirty = true; + } + + public void load(){ + ObjectMap> result = Settings.getJson("content-database", ObjectMap.class); + + for(Entry> entry : result.entries()){ + ObjectSet set = new ObjectSet<>(); + set.addAll(entry.value); + unlocked.put(entry.key, set); + } + + dirty = false; + } + + public void save(){ + + ObjectMap> write = new ObjectMap<>(); + + for(Entry> entry : unlocked.entries()){ + write.put(entry.key, entry.value.iterator().toArray()); + } + + Settings.putJson("content-database", write); + Settings.save(); + dirty = false; + } + +} diff --git a/core/src/io/anuke/mindustry/game/Difficulty.java b/core/src/io/anuke/mindustry/game/Difficulty.java index 0acfd411b9..7c86e5685e 100644 --- a/core/src/io/anuke/mindustry/game/Difficulty.java +++ b/core/src/io/anuke/mindustry/game/Difficulty.java @@ -1,46 +1,43 @@ package io.anuke.mindustry.game; -import com.badlogic.gdx.ai.pfa.Heuristic; -import io.anuke.mindustry.ai.Heuristics.DestrutiveHeuristic; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.types.LiquidBlock; -import io.anuke.mindustry.world.blocks.types.PowerBlock; -import io.anuke.mindustry.world.blocks.types.defense.Turret; -import io.anuke.mindustry.world.blocks.types.distribution.Conveyor; -import io.anuke.mindustry.world.blocks.types.distribution.Router; -import io.anuke.mindustry.world.blocks.types.production.Drill; -import io.anuke.mindustry.world.blocks.types.production.Generator; -import io.anuke.mindustry.world.blocks.types.production.Smelter; import io.anuke.ucore.util.Bundles; -public enum Difficulty { - easy(4f, 2f, 1f, new DestrutiveHeuristic(b -> b instanceof Generator)), - normal(2f, 1f, 1f, new DestrutiveHeuristic(b -> b instanceof Smelter || b instanceof Generator)), - hard(1.5f, 0.5f, 0.75f, new DestrutiveHeuristic(b -> b instanceof Turret || b instanceof Generator || b instanceof Drill || b instanceof Smelter)), - insane(0.5f, 0.25f, 0.5f, new DestrutiveHeuristic(b -> b instanceof Generator || b instanceof Drill || b instanceof Smelter || b instanceof Router)), - purge(0.25f, 0.01f, 0.25f, new DestrutiveHeuristic(b -> b instanceof Generator || b instanceof Drill || b instanceof Router - || b instanceof Smelter || b instanceof Conveyor || b instanceof LiquidBlock || b instanceof PowerBlock)); +public enum Difficulty{ + easy(4f, 2f, 1f), + normal(2f, 1f, 1f), + hard(1.5f, 0.5f, 0.75f), + insane(0.5f, 0.25f, 0.5f); + //purge removed due to new wave system + /*purge(0.25f, 0.01f, 0.25f)*/; - /**The scaling of how many waves it takes for one more enemy of a type to appear. + /** + * The scaling of how many waves it takes for one more enemy of a type to appear. * For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for - * an enemy spawn to go from 1->2 enemies.*/ + * an enemy spawn to go from 1->2 enemies. + */ public final float enemyScaling; - /**Multiplier of the time between waves.*/ + /** + * Multiplier of the time between waves. + */ public final float timeScaling; - /**Scaling of max time between waves. Default time is 4 minutes.*/ + /** + * Scaling of max time between waves. Default time is 4 minutes. + */ public final float maxTimeScaling; - /**Pathfdining heuristic for calculating tile costs.*/ - public final Heuristic heuristic; - Difficulty(float enemyScaling, float timeScaling, float maxTimeScaling, Heuristic heuristic){ + private String value; + + Difficulty(float enemyScaling, float timeScaling, float maxTimeScaling){ this.enemyScaling = enemyScaling; this.timeScaling = timeScaling; - this.heuristic = heuristic; this.maxTimeScaling = maxTimeScaling; } @Override - public String toString() { - return Bundles.get("setting.difficulty." + name()); + public String toString(){ + if(value == null){ + value = Bundles.get("setting.difficulty." + name()); + } + return value; } } diff --git a/core/src/io/anuke/mindustry/game/EnemySpawn.java b/core/src/io/anuke/mindustry/game/EnemySpawn.java deleted file mode 100644 index eff8e10a44..0000000000 --- a/core/src/io/anuke/mindustry/game/EnemySpawn.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.anuke.mindustry.game; - -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.state; - -public class EnemySpawn{ - /**The enemy type spawned*/ - public final EnemyType type; - /**When this spawns should end*/ - protected int before = Integer.MAX_VALUE; - /**When this spawns should start*/ - protected int after; - /**The spacing, in waves, of spawns. 2 = spawns every other wave*/ - protected int spacing = 1; - /**How many waves need to pass after the start of this spawn for the tier to increase by one*/ - protected int tierscale = 17; - /**How many more enemies there are, every time the tier increases*/ - protected int tierscaleback = 0; - /**The tier this spawn starts at.*/ - protected int tier = 1; - /**Maximum amount of enemies that spawn*/ - protected int max = 60; - /**How many waves need to pass before the amount of enemies increases by 1*/ - protected float scaling = 9999f; - /**Amount of enemies spawned initially, with no scaling*/ - protected int amount = 1; - - public EnemySpawn(EnemyType type){ - this.type = type; - } - - public int evaluate(int wave, int lane){ - if(wave < after || wave > before || (wave - after) % spacing != 0){ - return 0; - } - float scaling = this.scaling * state.difficulty.enemyScaling; - - return Math.min(amount-1 + Math.max((int)((wave / spacing) / scaling), 1) + (tier(wave, lane)-1) * tierscaleback, max); - } - - public int tier(int wave, int lane){ - return Mathf.clamp(tier + (wave-after)/tierscale, 1, EnemyType.maxtier); - } -} diff --git a/core/src/io/anuke/mindustry/game/EventType.java b/core/src/io/anuke/mindustry/game/EventType.java index c900741b7e..ec880f3be9 100644 --- a/core/src/io/anuke/mindustry/game/EventType.java +++ b/core/src/io/anuke/mindustry/game/EventType.java @@ -1,16 +1,10 @@ package io.anuke.mindustry.game; import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.entities.Entity; import io.anuke.ucore.function.Event; -public class EventType { +public class EventType{ public interface PlayEvent extends Event{ void handle(); @@ -28,63 +22,48 @@ public class EventType { void handle(); } + /** + * This event is called from the logic thread. + * DO NOT INITIALIZE GRAPHICS HERE. + */ + public interface WorldLoadEvent extends Event{ + void handle(); + } + + /** + * Called after the WorldLoadEvent is, and all logic has been loaded. + * It is safe to intialize graphics here. + */ + public interface WorldLoadGraphicsEvent extends Event{ + void handle(); + } + + /** + * Called from the logic thread. Do not access graphics here! + */ + public interface TileChangeEvent extends Event{ + void handle(Tile tile); + } + + //TODO unimplemented; remove? + public interface TileRemoveEvent extends Event{ + void handle(Tile tile, Team oldTeam); + } + public interface StateChangeEvent extends Event{ void handle(State from, State to); } - public interface FriendlyFireChange extends Event{ - void handle(boolean on); + public interface UnlockEvent extends Event{ + void handle(Content content); } - public interface BulletEvent extends Event{ - void handle(BulletType type, Entity owner, float x, float y, float angle, short damage); + public interface BlockBuildEvent extends Event{ + void handle(Team team, Tile tile); } - public interface EnemyDeathEvent extends Event{ - void handle(Enemy enemy); - } - - public interface BlockDestroyEvent extends Event{ - void handle(TileEntity entity); - } - - public interface BlockDamageEvent extends Event{ - void handle(TileEntity entity); - } - - public interface PlayerDeathEvent extends Event{ + public interface ResizeEvent extends Event{ void handle(); } - - public interface BlockConfigEvent extends Event{ - void handle(Tile tile, byte data); - } - - public interface BlockTapEvent extends Event{ - void handle(Tile tile); - } - - public interface WeaponSwitchEvent extends Event{ - void handle(); - } - - public interface UpgradeEvent extends Event{ - void handle(Weapon weapon); - } - - public interface MessageSendEvent extends Event{ - void handle(String message); - } - - public interface ShootEvent extends Event{ - void handle(Weapon weapon, float x, float y, float angle); - } - - public interface PlaceEvent extends Event{ - void handle(int x, int y, Block block, int rotation); - } - - public interface BreakEvent extends Event{ - void handle(int x, int y); - } } + diff --git a/core/src/io/anuke/mindustry/game/GameMode.java b/core/src/io/anuke/mindustry/game/GameMode.java index 8481f1a97f..fcf9e8c09a 100644 --- a/core/src/io/anuke/mindustry/game/GameMode.java +++ b/core/src/io/anuke/mindustry/game/GameMode.java @@ -3,28 +3,29 @@ package io.anuke.mindustry.game; import io.anuke.ucore.util.Bundles; public enum GameMode{ - waves, - sandbox{ + waves, + //disabled for technical reasons + /*sandbox{ { infiniteResources = true; disableWaveTimer = true; } - }, + },*/ freebuild{ { disableWaveTimer = true; } }; - public boolean infiniteResources; - public boolean disableWaveTimer; + public boolean infiniteResources; + public boolean disableWaveTimer; - public String description(){ - return Bundles.get("mode."+name()+".description"); - } + public String description(){ + return Bundles.get("mode." + name() + ".description"); + } - @Override - public String toString(){ - return Bundles.get("mode."+name()+".name"); - } + @Override + public String toString(){ + return Bundles.get("mode." + name() + ".name"); + } } diff --git a/core/src/io/anuke/mindustry/game/Inventory.java b/core/src/io/anuke/mindustry/game/Inventory.java deleted file mode 100644 index c3b48b6459..0000000000 --- a/core/src/io/anuke/mindustry/game/Inventory.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.anuke.mindustry.game; - -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.ItemStack; - -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.debug; - -public class Inventory { - private final int[] items = new int[Item.getAllItems().size]; - private boolean updated; - - public boolean isUpdated(){ - return updated; - } - - public void setUpdated(boolean updated){ - this.updated = updated; - } - - public void clearItems(){ - updated = true; - Arrays.fill(items, 0); - - addItem(Item.stone, 40); - - if(debug){ - Arrays.fill(items, 99999); - } - } - - public void fill(){ - Arrays.fill(items, 999999999); - } - - public int getAmount(Item item){ - return items[item.id]; - } - - public void addItem(Item item, int amount){ - updated = true; - items[item.id] += amount; - } - - public boolean hasItems(ItemStack[] items){ - for(ItemStack stack : items) - if(!hasItem(stack)) - return false; - return true; - } - - public boolean hasItems(ItemStack[] items, int scaling){ - for(ItemStack stack : items) - if(!hasItem(stack.item, stack.amount * scaling)) - return false; - return true; - } - - public boolean hasItem(ItemStack req){ - updated = true; - return items[req.item.id] >= req.amount; - } - - public boolean hasItem(Item item, int amount){ - updated = true; - return items[item.id] >= amount; - } - - public void removeItem(ItemStack req){ - updated = true; - items[req.item.id] -= req.amount; - if(items[req.item.id] < 0) items[req.item.id] = 0; //prevents negative item glitches in multiplayer - } - - public void removeItems(ItemStack... reqs){ - updated = true; - for(ItemStack req : reqs) - removeItem(req); - } - - public int[] getItems(){ - updated = true; - return items; - } -} diff --git a/core/src/io/anuke/mindustry/game/SpawnGroup.java b/core/src/io/anuke/mindustry/game/SpawnGroup.java new file mode 100644 index 0000000000..4d8fe850be --- /dev/null +++ b/core/src/io/anuke/mindustry/game/SpawnGroup.java @@ -0,0 +1,125 @@ +package io.anuke.mindustry.game; + +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.entities.units.GroundUnit; +import io.anuke.mindustry.entities.units.UnitType; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.type.StatusEffect; +import io.anuke.mindustry.type.Weapon; + +/** + * A spawn group defines spawn information for a specific type of unit, with optional extra information like + * weapon equipped, ammo used, and status effects. + * Each spawn group can have multiple sub-groups spawned in different areas of the map. + */ +public class SpawnGroup{ + /** + * The unit type spawned + */ + public final UnitType type; + /** + * When this spawn should end + */ + protected int end = Integer.MAX_VALUE; + /** + * When this spawn should start + */ + protected int begin; + /** + * The spacing, in waves, of spawns. For example, 2 = spawns every other wave + */ + protected int spacing = 1; + /** + * Maximum amount of units that spawn + */ + protected int max = 60; + /** + * How many waves need to pass before the amount of units spawned increases by 1 + */ + protected float unitScaling = 9999f; + /** + * How many waves need to pass before the amount of instances of this group increases by 1 + */ + protected float groupScaling = 9999f; + /** + * Amount of enemies spawned initially, with no scaling + */ + protected int unitAmount = 1; + /** + * Amount of enemies spawned initially, with no scaling + */ + protected int groupAmount = 1; + /** + * Weapon used by the spawned unit. Null to disable. Only applicable to ground units. + */ + protected Weapon weapon; + /** + * Status effect applied to the spawned unit. Null to disable. + */ + protected StatusEffect effect; + /** + * Items this unit spawns with. Null to disable. + */ + protected ItemStack items; + /** + * Ammo type this unit spawns with. Null to use the first available ammo. + */ + protected Item ammoItem; + + public SpawnGroup(UnitType type){ + this.type = type; + } + + /** + * Returns the amount of units spawned on a specific wave. + */ + public int getUnitsSpawned(int wave){ + if(wave < begin || wave > end || (wave - begin) % spacing != 0){ + return 0; + } + float scaling = this.unitScaling; + + return Math.min(unitAmount - 1 + Math.max((int) ((wave / spacing) / scaling), 1), max); + } + + /** + * Returns the amount of different unit groups at a specific wave. + */ + public int getGroupsSpawned(int wave){ + if(wave < begin || wave > end || (wave - begin) % spacing != 0){ + return 0; + } + float scaling = this.groupScaling; + + return Math.min(groupAmount - 1 + Math.max((int) ((wave / spacing) / groupScaling), 1), max); + } + + /** + * Creates a unit, and assigns correct values based on this group's data. + * This method does not add() the unit. + */ + public BaseUnit createUnit(Team team){ + BaseUnit unit = type.create(team); + + if(unit instanceof GroundUnit && weapon != null){ + ((GroundUnit) unit).setWeapon(weapon); + } + + if(effect != null){ + unit.applyEffect(effect, 10000f); + } + + if(items != null){ + unit.inventory.addItem(items.item, items.amount); + } + + if(ammoItem != null){ + unit.inventory.addAmmo(unit.getWeapon().getAmmoType(ammoItem)); + }else{ + unit.inventory.addAmmo(unit.getWeapon().getAmmoType(unit.getWeapon().getAcceptedItems().iterator().next())); + } + + return unit; + } +} diff --git a/core/src/io/anuke/mindustry/game/SpawnPoint.java b/core/src/io/anuke/mindustry/game/SpawnPoint.java deleted file mode 100644 index 62e41e70bb..0000000000 --- a/core/src/io/anuke/mindustry/game/SpawnPoint.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.anuke.mindustry.game; - -import com.badlogic.gdx.ai.pfa.PathFinder; -import com.badlogic.gdx.ai.pfa.PathFinderRequest; - -import io.anuke.mindustry.ai.SmoothGraphPath; -import io.anuke.mindustry.world.Tile; - -public class SpawnPoint{ - public Tile start; - public Tile[] pathTiles; - public PathFinder finder; - public SmoothGraphPath path = new SmoothGraphPath(); - public PathFinderRequest request; - - public SpawnPoint(Tile start){ - this.start = start; - } -} diff --git a/core/src/io/anuke/mindustry/game/Team.java b/core/src/io/anuke/mindustry/game/Team.java new file mode 100644 index 0000000000..7fce5dc6fb --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Team.java @@ -0,0 +1,21 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.graphics.Color; + +public enum Team{ + none(Color.DARK_GRAY), + blue(Color.ROYAL), + red(Color.valueOf("e84737")), + green(Color.valueOf("1dc645")), + purple(Color.valueOf("ba5bd9")), + orange(Color.valueOf("e8c66a")); + + public final static Team[] all = values(); + public final Color color; + public final int intColor; + + Team(Color color){ + this.color = color; + intColor = Color.rgba8888(color); + } +} diff --git a/core/src/io/anuke/mindustry/game/TeamInfo.java b/core/src/io/anuke/mindustry/game/TeamInfo.java new file mode 100644 index 0000000000..8559fde11d --- /dev/null +++ b/core/src/io/anuke/mindustry/game/TeamInfo.java @@ -0,0 +1,142 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.util.ThreadArray; +import io.anuke.ucore.util.ThreadSet; + +/** + * Class for various team-based utilities. + */ +public class TeamInfo{ + private ObjectMap map = new ObjectMap<>(); + private ThreadSet allies = new ThreadSet<>(), + enemies = new ThreadSet<>(); + private ThreadSet allyData = new ThreadSet<>(), + enemyData = new ThreadSet<>(); + private ThreadSet allTeamData = new ThreadSet<>(); + private ThreadSet allTeams = new ThreadSet<>(); + private int allyBits = 0; + private int enemyBits = 0; + + /** + * Returns all teams on a side. + */ + public ObjectSet getTeams(boolean ally){ + return ally ? allyData : enemyData; + } + + /** + * Returns all team data. + */ + public ObjectSet getTeams(){ + return allTeamData; + } + + /** + * Register a team. + * + * @param team The team type enum. + * @param ally Whether this team is an ally with the player or an enemy with the player. + * In PvP situations with dedicated servers, the sides can be arbitrary. + */ + public void add(Team team, boolean ally){ + if(has(team)) throw new RuntimeException("Can't define team information twice!"); + + TeamData data = new TeamData(team, ally); + + if(ally){ + allies.add(team); + allyData.add(data); + allyBits |= (1 << team.ordinal()); + }else{ + enemies.add(team); + enemyData.add(data); + enemyBits |= (1 << team.ordinal()); + } + + allTeamData.add(data); + allTeams.add(team); + + map.put(team, data); + } + + /** + * Returns team data by type. Call {@link #has(Team)} first to make sure it's active! + */ + public TeamData get(Team team){ + if(!has(team)) throw new RuntimeException("This team is not active! Check has() before calling get()."); + return map.get(team); + } + + /** + * Returns whether the specified team is active, e.g. whether it is participating in the game. + */ + public boolean has(Team team){ + return map.containsKey(team); + } + + /** + * Returns a set of all teams that are enemies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet enemiesOf(Team team){ + boolean ally = allies.contains(team); + boolean enemy = enemies.contains(team); + + //this team isn't even in the game, so target everything! + if(!ally && !enemy) return allTeams; + + return ally ? enemies : allies; + } + + /** + * Returns a set of all teams that are allies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet alliesOf(Team team){ + boolean ally = allies.contains(team); + boolean enemy = enemies.contains(team); + + //this team isn't even in the game, so target everything! + if(!ally && !enemy) return allTeams; + + return !ally ? enemies : allies; + } + + /** + * Returns a set of all teams that are enemies of this team. + * For teams not active, an empty set is returned. + */ + public ObjectSet enemyDataOf(Team team){ + boolean ally = allies.contains(team); + boolean enemy = enemies.contains(team); + + //this team isn't even in the game, so target everything! + if(!ally && !enemy) return allTeamData; + + return ally ? enemyData : allyData; + } + + /** + * Returns whether or not these two teams are enemies. + */ + public boolean areEnemies(Team team, Team other){ + if(team == other) return false; //fast fail to be more efficient + boolean ally = (allyBits & (1 << team.ordinal())) != 0; + boolean enemy = (enemyBits & (1 << other.ordinal())) != 0; + return (ally == enemy) || !ally; //if it's not in the game, target everything. + } + + public class TeamData{ + public final ThreadArray cores = new ThreadArray<>(); + public final Team team; + public final boolean ally; + + public TeamData(Team team, boolean ally){ + this.team = team; + this.ally = ally; + } + } +} diff --git a/core/src/io/anuke/mindustry/game/Tutorial.java b/core/src/io/anuke/mindustry/game/Tutorial.java deleted file mode 100644 index 538aad606e..0000000000 --- a/core/src/io/anuke/mindustry/game/Tutorial.java +++ /dev/null @@ -1,604 +0,0 @@ -package io.anuke.mindustry.game; - -import com.badlogic.gdx.math.GridPoint2; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.*; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.scene.builders.button; -import io.anuke.ucore.scene.builders.label; -import io.anuke.ucore.scene.builders.table; -import io.anuke.ucore.scene.ui.ImageButton; -import io.anuke.ucore.scene.ui.Label; -import io.anuke.ucore.scene.ui.TextButton; -import io.anuke.ucore.util.Bundles; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Tmp; - -import static io.anuke.mindustry.Vars.*; - -public class Tutorial{ - private Stage stage; - private Label info; - private TextButton next, prev; - - public Tutorial(){ - reset(); - } - - public boolean active(){ - return world.getMap() != null && world.getMap().name.equals("tutorial") && !state.is(State.menu); - } - - public void buildUI(table table){ - - table.atop(); - - new table("pane"){{ - atop(); - margin(12); - - info = new label(()->stage.text).pad(10f).padBottom(5f).width(340f).colspan(2).get(); - info.setWrap(true); - - row(); - - prev = new button("$text.tutorial.back", ()->{ - if(!prev.isDisabled()) - move(false); - }).left().get(); - - next = new button("$text.tutorial.next", ()->{ - if(!next.isDisabled()) - move(true); - }).right().get(); - - - }}.end(); - - prev.margin(16); - next.margin(16); - - prev.setDisabled(()->!canMove(false) || !stage.canBack); - next.setDisabled(()->!stage.canForward); - } - - public void update(){ - stage.update(this); - //info.setText(stage.text); - - if(stage.showBlock){ - Tile tile = world.tile(world.getCore().x + stage.blockPlaceX, world.getCore().y + stage.blockPlaceY); - - if(tile.block() == stage.targetBlock && (tile.getRotation() == stage.blockRotation || stage.blockRotation == -1)){ - move(true); - } - } - } - - public void reset(){ - stage = Stage.values()[0]; - stage.onSwitch(); - } - - public void complete(){ - //new TextDialog("Congratulations!", "You have completed the tutorial!").padText(Unit.dp.inPixels(10f)).show(); - state.set(State.menu); - reset(); - } - - void move(boolean forward){ - - if(forward && !canMove(forward)){ - complete(); - }else{ - int current = stage.ordinal(); - - while(true){ - current += Mathf.sign(forward); - - if(current < 0 || current >= Stage.values().length){ - break; - }else if(mobile == Stage.values()[current].androidOnly || mobile != Stage.values()[current].desktopOnly){ - stage = Stage.values()[current]; - stage.onSwitch(); - break; - } - } - } - } - - boolean canMove(boolean forward){ - int current = stage.ordinal(); - - while(true){ - current += Mathf.sign(forward); - - if(current < 0 || current >= Stage.values().length){ - return false; - }else if(mobile == Stage.values()[current].androidOnly || mobile != Stage.values()[current].desktopOnly){ - return true; - } - } - - } - - public boolean showTarget(){ - return stage == Stage.shoot; - } - - public boolean canPlace(){ - return stage.canPlace; - } - - public boolean showBlock(){ - return stage.showBlock; - } - - public Block getPlaceBlock(){ - return stage.targetBlock; - } - - public GridPoint2 getPlacePoint(){ - return Tmp.g1.set(stage.blockPlaceX, stage.blockPlaceY); - } - - public int getPlaceRotation(){ - return stage.blockRotation; - } - - public void setDefaultBlocks(int corex, int corey){ - world.tile(corex, corey - 2).setBlock(Blocks.air); - world.tile(corex, corey - 3).setBlock(Blocks.air); - world.tile(corex, corey - 3).setFloor(Blocks.stone); - - world.tile(corex + 1, corey - 8).setFloor(Blocks.iron); - world.tile(corex - 1, corey - 8).setFloor(Blocks.coal); - - int r = 10; - - for(int x = -r; x <= r; x ++){ - for(int y = -r; y <= r; y ++){ - if(world.tile(corex + x, corey + y).block() == Blocks.rock){ - world.tile(corex + x, corey + y).setBlock(Blocks.air); - } - } - } - } - - public enum Stage{ - intro{ - { - } - }, - moveDesktop{ - { - desktopOnly = true; - } - }, - shoot{ - { - desktopOnly = true; - } - }, - moveAndroid{ - { - androidOnly = true; - } - }, - placeSelect{ - { - canBack = false; - canPlace = true; - } - - void onSwitch(){ - ui.find("sectionbuttondistribution").fireClick(); - } - }, - placeConveyorDesktop{ - { - desktopOnly = true; - canPlace = true; - showBlock = true; - canForward = false; - blockRotation = 1; - blockPlaceX = 0; - blockPlaceY = -2; - targetBlock = DistributionBlocks.conveyor; - } - }, - placeConveyorAndroid{ - { - androidOnly = true; - canPlace = true; - showBlock = true; - canForward = false; - blockRotation = 1; - blockPlaceX = 0; - blockPlaceY = -2; - targetBlock = DistributionBlocks.conveyor; - } - }, - placeConveyorAndroidInfo{ - { - androidOnly = true; - canBack = false; - } - - void onSwitch(){ - //player.recipe = null; - } - }, - placeDrill{ - { - canPlace = true; - canBack = false; - showBlock = true; - canForward = false; - blockPlaceX = 0; - blockPlaceY = -3; - targetBlock = ProductionBlocks.stonedrill; - } - - void onSwitch(){ - ui.find("sectionbuttonproduction").fireClick(); - } - }, - blockInfo{ - { - canBack = true; - } - }, - deselectDesktop{ - { - desktopOnly = true; - canBack = false; - } - }, - deselectAndroid{ - { - androidOnly = true; - canBack = false; - } - }, - drillPlaced{ - { - canBack = false; - } - - void onSwitch(){ - control.input().recipe = null; - } - }, - drillInfo{ - { - } - }, - drillPlaced2{ - { - } - }, - moreDrills{ - { - canBack = false; - } - - void onSwitch(){ - for(int flip : new int[]{1, -1}){ - world.tile(world.getCore().x + flip, world.getCore().y - 2).setBlock(DistributionBlocks.conveyor, 2 * flip); - world.tile(world.getCore().x + flip*2, world.getCore().y - 2).setBlock(DistributionBlocks.conveyor, 2 * flip); - world.tile(world.getCore().x + flip*2, world.getCore().y - 3).setBlock(DistributionBlocks.conveyor, 2 * flip); - world.tile(world.getCore().x + flip*2, world.getCore().y - 3).setBlock(DistributionBlocks.conveyor, 1); - world.tile(world.getCore().x + flip*2, world.getCore().y - 4).setFloor(Blocks.stone); - world.tile(world.getCore().x + flip*2, world.getCore().y - 4).setBlock(ProductionBlocks.stonedrill); - - } - } - }, - deleteBlock{ - { - canBack = false; - canForward = false; - showBlock = true; - targetBlock = Blocks.air; - blockPlaceX = 2; - blockPlaceY = -2; - desktopOnly = true; - } - }, - deleteBlockAndroid{ - { - canBack = false; - canForward = false; - showBlock = true; - targetBlock = Blocks.air; - blockPlaceX = 2; - blockPlaceY = -2; - androidOnly = true; - } - }, - placeTurret{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = WeaponBlocks.turret; - blockPlaceX = 2; - blockPlaceY = 2; - } - - void onSwitch(){ - ui.find("sectionbuttonweapon").fireClick(); - } - }, - placedTurretAmmo{ - { - canBack = false; - } - - void onSwitch(){ - for(int i = 0; i < 4; i ++){ - world.tile(world.getCore().x + 2, world.getCore().y - 2 + i).setBlock(DistributionBlocks.conveyor, 1); - } - - control.input().recipe = null; - } - }, - turretExplanation{ - { - canBack = false; - } - }, - waves{ - { - } - }, - coreDestruction{ - { - } - }, - pausingDesktop{ - { - desktopOnly = true; - } - }, - pausingAndroid{ - { - androidOnly = true; - } - }, - //TODO re-add tutorial on weapons - - spawnWave{ - float warmup = 0f; - { - canBack = false; - canForward = false; - } - - void update(Tutorial t){ - warmup += Timers.delta(); - if(state.enemies == 0 && warmup > 60f){ - t.move(true); - } - } - - void onSwitch(){ - warmup = 0f; - logic.runWave(); - } - }, - pumpDesc{ - { - canBack = false; - } - }, - pumpPlace{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = ProductionBlocks.pump; - blockPlaceX = 6; - blockPlaceY = -2; - } - - void onSwitch(){ - ui.find("sectionbuttonproduction").fireClick(); - state.inventory.addItem(Item.steel, 60); - state.inventory.addItem(Item.iron, 60); - } - }, - conduitUse{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = DistributionBlocks.conduit; - blockPlaceX = 5; - blockPlaceY = -2; - blockRotation = 2; - } - - void onSwitch(){ - ui.find("sectionbuttondistribution").fireClick(); - world.tile(blockPlaceX + world.getCore().x, blockPlaceY + world.getCore().y).setBlock(Blocks.air); - } - }, - conduitUse2{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = DistributionBlocks.conduit; - blockPlaceX = 4; - blockPlaceY = -2; - blockRotation = 1; - } - - void onSwitch(){ - world.tile(blockPlaceX + world.getCore().x, blockPlaceY + world.getCore().y).setBlock(Blocks.air); - } - }, - conduitUse3{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = DistributionBlocks.conduit; - blockPlaceX = 4; - blockPlaceY = -1; - blockRotation = 1; - } - - void onSwitch(){ - world.tile(blockPlaceX + world.getCore().x, blockPlaceY + world.getCore().y).setBlock(Blocks.air); - } - }, - generator{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - targetBlock = ProductionBlocks.combustiongenerator; - blockPlaceX = 4; - blockPlaceY = 0; - } - - void onSwitch(){ - world.tile(blockPlaceX + world.getCore().x, blockPlaceY + world.getCore().y).setBlock(Blocks.air); - ui.find("sectionbuttonpower").fireClick(); - state.inventory.addItem(Item.steel, 60); - state.inventory.addItem(Item.iron, 60); - } - }, - generatorExplain{ - { - canBack = false; - } - }, - lasers{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - blockPlaceX = 4; - blockPlaceY = 4; - blockRotation = 2; - targetBlock = DistributionBlocks.powerlaser; - } - - void onSwitch(){ - ui.find("sectionbuttonpower").fireClick(); - } - }, - laserExplain{ - { - canBack = false; - } - }, - laserMore{ - { - canBack = false; - } - }, - healingTurret{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - canBack = false; - blockPlaceX = 1; - blockPlaceY = 4; - targetBlock = DefenseBlocks.repairturret; - } - - void onSwitch(){ - ui.find("sectionbuttonpower").fireClick(); - } - }, - healingTurretExplain{ - { - canBack = false; - } - }, - smeltery{ - { - canBack = false; - canForward = false; - showBlock = true; - canPlace = true; - canBack = false; - blockPlaceX = 0; - blockPlaceY = -7; - targetBlock = ProductionBlocks.smelter; - } - - void onSwitch(){ - state.inventory.addItem(Item.stone, 40); - state.inventory.addItem(Item.iron, 40); - ui.find("sectionbuttoncrafting").fireClick(); - - } - }, - smelterySetup{ - { - canBack = false; - } - - void onSwitch(){ - for(int i = 0; i < 5; i ++){ - world.tile(world.getCore().x, world.getCore().y - 6 + i).setBlock(DistributionBlocks.conveyor, 1); - } - - world.tile(world.getCore().x, world.getCore().y - 6 + 1).setBlock(DistributionBlocks.tunnel, 3); - world.tile(world.getCore().x, world.getCore().y - 6 + 2).setBlock(DefenseBlocks.stonewall, 0); - world.tile(world.getCore().x, world.getCore().y - 6 + 3).setBlock(DistributionBlocks.tunnel, 1); - - world.tile(world.getCore().x+1, world.getCore().y - 8).setBlock(ProductionBlocks.irondrill); - world.tile(world.getCore().x-1, world.getCore().y - 8).setBlock(ProductionBlocks.coaldrill); - - world.tile(world.getCore().x+1, world.getCore().y - 7).setBlock(DistributionBlocks.conveyor, 2); - world.tile(world.getCore().x-1, world.getCore().y - 7).setBlock(DistributionBlocks.conveyor, 0); - } - }, - tunnelExplain{ - { - canBack = false; - } - }, - end{ - { - canBack = false; - } - }; - public final String text = Bundles.getNotNull("tutorial."+name()+".text"); - - boolean androidOnly; - boolean desktopOnly; - - boolean canBack = true; - boolean canForward = true; - boolean canPlace = false; - boolean showBlock = false; - - int blockPlaceX = 0; - int blockPlaceY = 0; - int blockRotation = -1; - Block targetBlock = null; - - void update(Tutorial t){}; - void onSwitch(){} - } -} diff --git a/core/src/io/anuke/mindustry/game/UnlockableContent.java b/core/src/io/anuke/mindustry/game/UnlockableContent.java new file mode 100644 index 0000000000..6c04c7ad06 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/UnlockableContent.java @@ -0,0 +1,69 @@ +package io.anuke.mindustry.game; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.ucore.scene.ui.layout.Table; + +import static io.anuke.mindustry.Vars.control; + +/** + * Base interface for an unlockable content type. + */ +public interface UnlockableContent extends Content{ + + /** + * Returns the unqiue name of this piece of content. + * The name only needs to be unique for all content of this type. + * Do not use IDs for names! Make sure this string stays constant with each update unless removed. + * (e.g. having a recipe and a block, both with name "wall" is fine, as they are different types). + */ + String getContentName(); + + /** + * Returns the localized name of this content. + */ + String localizedName(); + + TextureRegion getContentIcon(); + + /** + * This should show all necessary info about this content in the specified table. + */ + void displayInfo(Table table); + + /** + * Called when this content is unlocked. Use this to unlock other related content. + */ + default void onUnlock(){ + } + + /** + * Whether this content is always hidden in the content info dialog. + */ + default boolean isHidden(){ + return false; + } + + /** + * Lists the content that must be unlocked in order for this specific content to become unlocked. May return null. + */ + default UnlockableContent[] getDependencies(){ + return null; + } + + /** + * Returns whether dependencies are satisfied for unlocking this content. + */ + default boolean canBeUnlocked(){ + UnlockableContent[] depend = getDependencies(); + if(depend == null){ + return true; + }else{ + for(UnlockableContent cont : depend){ + if(!control.database().isUnlocked(cont)){ + return false; + } + } + return true; + } + } +} diff --git a/core/src/io/anuke/mindustry/game/UpgradeInventory.java b/core/src/io/anuke/mindustry/game/UpgradeInventory.java deleted file mode 100644 index 24fadf2d12..0000000000 --- a/core/src/io/anuke/mindustry/game/UpgradeInventory.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.anuke.mindustry.game; - -import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.resource.Weapon; - -public class UpgradeInventory { - private final Array weapons = new Array<>(); - - public boolean hasWeapon(Weapon weapon){ - return weapons.contains(weapon, true); - } - - public void addWeapon(Weapon weapon){ - weapons.add(weapon); - } - - public Array getWeapons(){ - return weapons; - } - - public void reset(){ - weapons.clear(); - weapons.add(Weapon.blaster); - } -} diff --git a/core/src/io/anuke/mindustry/game/WaveCreator.java b/core/src/io/anuke/mindustry/game/WaveCreator.java index b44fcbf98e..a5e5e25340 100644 --- a/core/src/io/anuke/mindustry/game/WaveCreator.java +++ b/core/src/io/anuke/mindustry/game/WaveCreator.java @@ -1,148 +1,190 @@ package io.anuke.mindustry.game; import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.entities.enemies.EnemyTypes; +import io.anuke.mindustry.content.Items; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.content.UnitTypes; +import io.anuke.mindustry.content.Weapons; +import io.anuke.mindustry.type.ItemStack; public class WaveCreator{ - - public static Array getSpawns(){ - return Array.with( - new EnemySpawn(EnemyTypes.standard){{ - scaling = 1; - before = 3; - }}, - - new EnemySpawn(EnemyTypes.fast){{ - scaling = 1; - after = 3; - spacing = 5; - amount = 3; - tierscaleback = 0; - }}, - - new EnemySpawn(EnemyTypes.blast){{ - after = 4; - amount = 2; - spacing = 5; - scaling = 2; - tierscaleback = 1; - }}, - - new EnemySpawn(EnemyTypes.tank){{ - after = 5; - spacing = 5; - scaling = 2; - amount = 2; - }}, - - new EnemySpawn(EnemyTypes.rapid){{ - after = 7; - spacing = 5; - scaling = 2; - amount = 3; - }}, - - new EnemySpawn(EnemyTypes.healer){{ - after = 5; - spacing = 5; - scaling = 1; - amount = 1; - }}, - - new EnemySpawn(EnemyTypes.standard){{ - scaling = 3; - after = 8; - spacing = 4; - tier = 2; - }}, - - new EnemySpawn(EnemyTypes.titan){{ - after = 6; - amount = 2; - spacing = 5; - scaling = 3; - }}, - - new EnemySpawn(EnemyTypes.flamer){{ - after = 12; - amount = 2; - spacing = 5; - scaling = 3; - }}, - - new EnemySpawn(EnemyTypes.emp){{ - after = 15; - amount = 1; - spacing = 5; - scaling = 2; - }}, - - new EnemySpawn(EnemyTypes.blast){{ - after = 4 + 5 + 5; - amount = 3; - spacing = 5; - scaling = 2; - tierscaleback = 0; - }}, - //boss wave - new EnemySpawn(EnemyTypes.fortress){{ - after = 16; - amount = 1; - spacing = 5; - scaling = 1; - }}, - - new EnemySpawn(EnemyTypes.titan){{ - after = 16; - amount = 1; - spacing = 5; - scaling = 3; - tierscaleback = 0; - }}, - - new EnemySpawn(EnemyTypes.healer){{ - after = 16; - spacing = 5; - scaling = 2; - amount = 2; - }}, - //end boss wave - - //enchanced boss wave - new EnemySpawn(EnemyTypes.mortar){{ - after = 16 + 5; - amount = 1; - spacing = 5; - scaling = 3; - }}, - - new EnemySpawn(EnemyTypes.emp){{ - after = 16 + 5; - amount = 1; - spacing = 5; - scaling = 3; - }} - //end enchanced boss wave - ); - } + public static Array getSpawns(){ + return Array.with( + new SpawnGroup(UnitTypes.scout){{ + end = 8; + unitScaling = 2; + }}, - public static void testWaves(int from, int to){ - Array spawns = getSpawns(); - for(int i = from; i <= to; i ++){ - System.out.print(i+": "); - int total = 0; - for(EnemySpawn spawn : spawns){ - int a = spawn.evaluate(i, 0); - int t = spawn.tier(i, 0); - total += a; - - if(a > 0){ - System.out.print(a+"x" + spawn.type.name + "-" + t + " "); - } - } - System.out.print(" (" + total + ")"); - System.out.println(); - } - } + new SpawnGroup(UnitTypes.vtol){{ + begin = 12; + end = 14; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 11; + unitScaling = 2; + spacing = 2; + max = 4; + }}, + + new SpawnGroup(UnitTypes.titan){{ + begin = 9; + spacing = 3; + unitScaling = 2; + + end = 30; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 10; + unitScaling = 2; + unitAmount = 1; + spacing = 2; + ammoItem = Items.tungsten; + end = 30; + }}, + + new SpawnGroup(UnitTypes.titan){{ + begin = 28; + spacing = 3; + unitScaling = 2; + weapon = Weapons.flamethrower; + end = 40; + }}, + + new SpawnGroup(UnitTypes.titan){{ + begin = 45; + spacing = 3; + unitScaling = 2; + weapon = Weapons.flamethrower; + effect = StatusEffects.overdrive; + }}, + + new SpawnGroup(UnitTypes.titan){{ + begin = 120; + spacing = 2; + unitScaling = 3; + unitAmount = 5; + weapon = Weapons.flakgun; + effect = StatusEffects.overdrive; + }}, + + new SpawnGroup(UnitTypes.vtol){{ + begin = 16; + unitScaling = 2; + spacing = 2; + + end = 39; + max = 7; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 82; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + unitScaling = 3; + effect = StatusEffects.overdrive; + ammoItem = Items.silicon; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 41; + spacing = 5; + unitAmount = 1; + unitScaling = 3; + effect = StatusEffects.shielded; + ammoItem = Items.thorium; + max = 10; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 35; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + effect = StatusEffects.overdrive; + items = new ItemStack(Items.blastCompound, 60); + end = 60; + }}, + + new SpawnGroup(UnitTypes.scout){{ + begin = 42; + spacing = 3; + unitAmount = 4; + groupAmount = 2; + effect = StatusEffects.overdrive; + items = new ItemStack(Items.pyratite, 100); + end = 130; + }}, + + new SpawnGroup(UnitTypes.monsoon){{ + begin = 40; + ammoItem = Items.blastCompound; + unitAmount = 2; + spacing = 2; + unitScaling = 3; + max = 8; + }}, + + new SpawnGroup(UnitTypes.vtol){{ + begin = 50; + unitAmount = 4; + unitScaling = 3; + spacing = 5; + groupAmount = 2; + effect = StatusEffects.overdrive; + max = 8; + }}, + + new SpawnGroup(UnitTypes.monsoon){{ + begin = 53; + ammoItem = Items.pyratite; + unitAmount = 2; + unitScaling = 3; + spacing = 4; + max = 8; + end = 74; + }}, + + new SpawnGroup(UnitTypes.monsoon){{ + begin = 53; + ammoItem = Items.coal; + unitAmount = 2; + unitScaling = 3; + spacing = 4; + max = 8; + end = 74; + }} + ); + } + + public static void testWaves(int from, int to){ + Array spawns = getSpawns(); + for(int i = from; i <= to; i++){ + System.out.print(i + ": "); + int total = 0; + for(SpawnGroup spawn : spawns){ + int a = spawn.getUnitsSpawned(i) * spawn.getGroupsSpawned(i); + total += a; + + if(a > 0){ + System.out.print(a + "x" + spawn.type.name); + + if(spawn.weapon != null){ + System.out.print(":" + spawn.weapon.name); + } + + if(spawn.ammoItem != null){ + System.out.print(":" + spawn.ammoItem.name); + } + + System.out.print(" "); + } + } + System.out.print(" (" + total + ")"); + System.out.println(); + } + } } diff --git a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java index 7d94d46476..0f6317a7e0 100644 --- a/core/src/io/anuke/mindustry/graphics/BlockRenderer.java +++ b/core/src/io/anuke/mindustry/graphics/BlockRenderer.java @@ -1,331 +1,221 @@ package io.anuke.mindustry.graphics; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.game.SpawnPoint; +import com.badlogic.gdx.utils.Sort; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.game.Team; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Layer; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.StaticBlock; -import io.anuke.mindustry.world.blocks.types.defense.Turret; -import io.anuke.ucore.core.Core; +import io.anuke.mindustry.world.blocks.StaticBlock; import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Settings; -import io.anuke.ucore.graphics.CacheBatch; import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.util.Mathf; -import java.util.Arrays; import static io.anuke.mindustry.Vars.*; import static io.anuke.ucore.core.Core.camera; public class BlockRenderer{ - private final static int chunksize = 32; - private final static int initialRequests = 32*32; - private static float storeX = 0; - private static float storeY = 0; - - private int[][][] cache; - private CacheBatch cbatch; - - private Array requests = new Array(initialRequests); - private int requestidx = 0; - private int iterateidx = 0; - - public BlockRenderer(){ - for(int i = 0; i < requests.size; i ++){ - requests.set(i, new BlockRequest()); - } - } - - private class BlockRequest implements Comparable{ - Tile tile; - Layer layer; - - @Override - public int compareTo(BlockRequest other){ - return layer.compareTo(other.layer); - } - - @Override - public String toString(){ - return tile.block().name + ":" + layer.toString(); - } - } - - /**Process all blocks to draw, simultaneously drawing block shadows and static blocks.*/ - public void processBlocks(){ - requestidx = 0; - - int crangex = (int) (camera.viewportWidth / (chunksize * tilesize)) + 1; - int crangey = (int) (camera.viewportHeight / (chunksize * tilesize)) + 1; - - int rangex = (int) (camera.viewportWidth * camera.zoom / tilesize / 2)+2; - int rangey = (int) (camera.viewportHeight * camera.zoom / tilesize / 2)+2; - - int expandr = 3; - - Graphics.surface(renderer.shadowSurface); - - for(int x = -rangex - expandr; x <= rangex + expandr; x++){ - for(int y = -rangey - expandr; y <= rangey + expandr; y++){ - int worldx = Mathf.scl(camera.position.x, tilesize) + x; - int worldy = Mathf.scl(camera.position.y, tilesize) + y; - boolean expanded = (x < -rangex || x > rangex || y < -rangey || y > rangey); - - Tile tile = world.tile(worldx, worldy); - - if(tile != null){ - Block block = tile.block(); - - if(!expanded && block != Blocks.air && world.isAccessible(worldx, worldy)){ - block.drawShadow(tile); - } - - if(!(block instanceof StaticBlock)){ - if(block == Blocks.air){ - if(!state.is(State.paused)) tile.floor().update(tile); - }else{ - - if(!expanded){ - addRequest(tile, Layer.block); - } - - if(block.expanded || !expanded){ - if(block.layer != null && block.isLayer(tile)){ - addRequest(tile, block.layer); - } - - if(block.layer2 != null && block.isLayer2(tile)){ - addRequest(tile, block.layer2); - } - } - } - } - } - } - } - - Draw.color(0, 0, 0, 0.15f); - Graphics.flushSurface(); - Draw.color(); - - Graphics.end(); - drawCache(1, crangex, crangey); - Graphics.begin(); - - Arrays.sort(requests.items, 0, requestidx); - iterateidx = 0; - } - - public int getRequests(){ - return requestidx; - } - - public void drawBlocks(boolean top){ - Layer stopAt = top ? Layer.laser : Layer.overlay; - - for(; iterateidx < requestidx; iterateidx ++){ - - if(iterateidx < requests.size - 1 && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ - break; - } - - BlockRequest req = requests.get(iterateidx); - Block block = req.tile.block(); - - if(req.layer == Layer.block){ - block.draw(req.tile); - }else if(req.layer == block.layer){ - block.drawLayer(req.tile); - }else if(req.layer == block.layer2){ - block.drawLayer2(req.tile); - } - } - } - - private void addRequest(Tile tile, Layer layer){ - if(requestidx >= requests.size){ - requests.add(new BlockRequest()); - } - BlockRequest r = requests.get(requestidx); - if(r == null){ - requests.set(requestidx, r = new BlockRequest()); - } - r.tile = tile; - r.layer = layer; - requestidx ++; - } - - public void drawFloor(){ - int chunksx = world.width() / chunksize, chunksy = world.height() / chunksize; - - //render the entire map - if(cache == null || cache.length != chunksx || cache[0].length != chunksy){ - cache = new int[chunksx][chunksy][2]; - - for(int x = 0; x < chunksx; x++){ - for(int y = 0; y < chunksy; y++){ - cacheChunk(x, y, true); - cacheChunk(x, y, false); - } - } - } - - OrthographicCamera camera = Core.camera; - - if(Graphics.drawing()) Graphics.end(); - - int crangex = (int)(camera.viewportWidth * camera.zoom / (chunksize * tilesize))+1; - int crangey = (int)(camera.viewportHeight * camera.zoom / (chunksize * tilesize))+1; - - drawCache(0, crangex, crangey); - - Graphics.begin(); - - Draw.reset(); - - if(showPaths && debug){ - drawPaths(); - } - - if(debug && debugChunks){ - Draw.color(Color.YELLOW); - Lines.stroke(1f); - for(int x = -crangex; x <= crangex; x++){ - for(int y = -crangey; y <= crangey; y++){ - int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; - int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; - - if(!Mathf.inBounds(worldx, worldy, cache)) - continue; - Lines.rect(worldx * chunksize * tilesize, worldy * chunksize * tilesize, chunksize * tilesize, chunksize * tilesize); - } - } - Draw.reset(); - } - } - - void drawPaths(){ - Draw.color(Color.RED); - for(SpawnPoint point : world.getSpawns()){ - if(point.pathTiles != null){ - for(int i = 1; i < point.pathTiles.length; i ++){ - Lines.line(point.pathTiles[i-1].worldx(), point.pathTiles[i-1].worldy(), - point.pathTiles[i].worldx(), point.pathTiles[i].worldy()); - Lines.circle(point.pathTiles[i-1].worldx(), point.pathTiles[i-1].worldy(), 6f); - } - } - } - Draw.reset(); - } - - - void drawCache(int layer, int crangex, int crangey){ - Gdx.gl.glEnable(GL20.GL_BLEND); - - cbatch.setProjectionMatrix(Core.camera.combined); - cbatch.beginDraw(); - for(int x = -crangex; x <= crangex; x++){ - for(int y = -crangey; y <= crangey; y++){ - int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; - int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; - - if(!Mathf.inBounds(worldx, worldy, cache)) - continue; - - cbatch.drawCache(cache[worldx][worldy][layer]); - } - } - - cbatch.endDraw(); - } - - void cacheChunk(int cx, int cy, boolean floor){ - if(cbatch == null){ - createBatch(); - } - - cbatch.begin(); - Graphics.useBatch(cbatch); - - for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){ - for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){ - Tile tile = world.tile(tilex, tiley); - if(tile == null) continue; - if(floor){ - if(!(tile.block() instanceof StaticBlock)){ - tile.floor().draw(tile); - } - }else if(tile.block() instanceof StaticBlock){ - tile.block().draw(tile); - } - } - } - Graphics.popBatch(); - cbatch.end(); - cache[cx][cy][floor ? 0 : 1] = cbatch.getLastCache(); - } - - public void clearTiles(){ - cache = null; - createBatch(); - } - - private void createBatch(){ - if(cbatch != null) - cbatch.dispose(); - cbatch = new CacheBatch(world.width() * world.height() * 4); - } - - public void drawPreview(Block block, float drawx, float drawy, float rotation, float opacity) { - Draw.alpha(opacity); - Draw.rect(block.name(), drawx, drawy, rotation); - } - - public void handlePreview(Block block, float rotation, float drawx, float drawy, int tilex, int tiley) { - - if(control.input().recipe != null && state.inventory.hasItems(control.input().recipe.requirements) - && control.input().validPlace(tilex, tiley, block) && (mobile || control.input().cursorNear())) { - - if(block.isMultiblock()) { - float halfBlockWidth = (block.width * tilesize) / 2; - float halfBlockHeight = (block.height * tilesize) / 2; - if((storeX == 0 && storeY == 0)) { - storeX = drawx; - storeY = drawy; - } - if((storeX == drawx - halfBlockWidth || storeX == drawx + halfBlockWidth || storeY == drawy - halfBlockHeight || storeY == drawy + halfBlockHeight) && - ((tiley - control.input().getBlockY()) % block.height != 0 || (tilex - control.input().getBlockX()) % block.width != 0)) { - return; - } - else { - storeX = drawx; - storeY = drawy; - } - } - - float opacity = (float) Settings.getInt("previewopacity") / 100f; - Draw.color(Color.WHITE); - Draw.alpha(opacity); - - if(block instanceof Turret) { - if (block.isMultiblock()) { - Draw.rect("block-" + block.width + "x" + block.height, drawx, drawy); - } else { - Draw.rect("block", drawx, drawy); - } - } - - drawPreview(block, drawx, drawy, rotation, opacity); - - Draw.reset(); - } - } -} \ No newline at end of file + private final static int initialRequests = 32 * 32; + + private FloorRenderer floorRenderer; + + private Array requests = new Array<>(initialRequests); + private Layer lastLayer; + private int requestidx = 0; + private int iterateidx = 0; + + public BlockRenderer(){ + floorRenderer = new FloorRenderer(); + + for(int i = 0; i < requests.size; i++){ + requests.set(i, new BlockRequest()); + } + } + + /** + * Process all blocks to draw, simultaneously drawing block shadows and static blocks. + */ + public void processBlocks(){ + requestidx = 0; + lastLayer = null; + + int rangex = (int) (camera.viewportWidth * camera.zoom / tilesize / 2) + 2; + int rangey = (int) (camera.viewportHeight * camera.zoom / tilesize / 2) + 2; + + int expandr = 4; + + Graphics.surface(renderer.effectSurface, true, false); + + int avgx = Mathf.scl(camera.position.x, tilesize); + int avgy = Mathf.scl(camera.position.y, tilesize); + + int minx = Math.max(avgx - rangex - expandr, 0); + int miny = Math.max(avgy - rangey - expandr, 0); + int maxx = Math.min(world.width() - 1, avgx + rangex + expandr); + int maxy = Math.min(world.height() - 1, avgy + rangey + expandr); + + for(int x = minx; x <= maxx; x++){ + for(int y = miny; y <= maxy; y++){ + boolean expanded = (Math.abs(x - avgx) > rangex || Math.abs(y - avgy) > rangey); + + synchronized(Tile.tileSetLock){ + Tile tile = world.rawTile(x, y); + + if(tile != null){ + Block block = tile.block(); + + if(!expanded && block != Blocks.air && world.isAccessible(x, y)){ + tile.block().drawShadow(tile); + } + + if(!(block instanceof StaticBlock)){ + if(block != Blocks.air){ + if(!expanded){ + addRequest(tile, Layer.block); + } + + if(block.expanded || !expanded){ + if(block.layer != null && block.isLayer(tile)){ + addRequest(tile, block.layer); + } + + if(block.layer2 != null && block.isLayer2(tile)){ + addRequest(tile, block.layer2); + } + } + } + } + } + } + } + } + + //TODO this actually isn't necessary + Draw.color(0, 0, 0, 0.15f); + Graphics.flushSurface(); + Draw.color(); + + Graphics.end(); + floorRenderer.beginDraw(); + floorRenderer.drawLayer(CacheLayer.walls); + floorRenderer.endDraw(); + Graphics.begin(); + + Sort.instance().sort(requests.items, 0, requestidx); + iterateidx = 0; + } + + public int getRequests(){ + return requestidx; + } + + public void drawBlocks(Layer stopAt){ + + for(; iterateidx < requestidx; iterateidx++){ + + if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ + break; + } + + BlockRequest req = requests.get(iterateidx); + + if(req.layer != lastLayer){ + if(lastLayer != null) layerEnds(lastLayer); + layerBegins(req.layer); + } + + synchronized(Tile.tileSetLock){ + Block block = req.tile.block(); + + if(req.layer == Layer.block){ + block.draw(req.tile); + }else if(req.layer == block.layer){ + block.drawLayer(req.tile); + }else if(req.layer == block.layer2){ + block.drawLayer2(req.tile); + } + } + + lastLayer = req.layer; + } + } + + public void drawTeamBlocks(Layer layer, Team team){ + int index = this.iterateidx; + + for(; index < requestidx; index++){ + + if(index < requests.size && requests.get(index).layer.ordinal() > layer.ordinal()){ + break; + } + + BlockRequest req = requests.get(index); + if(req.tile.getTeam() != team) continue; + + synchronized(Tile.tileSetLock){ + Block block = req.tile.block(); + + if(req.layer == block.layer){ + block.drawLayer(req.tile); + }else if(req.layer == block.layer2){ + block.drawLayer2(req.tile); + } + } + } + } + + public void skipLayer(Layer stopAt){ + + for(; iterateidx < requestidx; iterateidx++){ + if(iterateidx < requests.size && requests.get(iterateidx).layer.ordinal() > stopAt.ordinal()){ + break; + } + } + } + + public void beginFloor(){ + floorRenderer.beginDraw(); + } + + public void endFloor(){ + floorRenderer.endDraw(); + } + + public void drawFloor(){ + floorRenderer.drawFloor(); + } + + private void layerBegins(Layer layer){ + } + + private void layerEnds(Layer layer){ + } + + private void addRequest(Tile tile, Layer layer){ + if(requestidx >= requests.size){ + requests.add(new BlockRequest()); + } + BlockRequest r = requests.get(requestidx); + if(r == null){ + requests.set(requestidx, r = new BlockRequest()); + } + r.tile = tile; + r.layer = layer; + requestidx++; + } + + private class BlockRequest implements Comparable{ + Tile tile; + Layer layer; + + @Override + public int compareTo(BlockRequest other){ + return layer.compareTo(other.layer); + } + + @Override + public String toString(){ + return tile.block().name + ":" + layer.toString(); + } + } +} diff --git a/core/src/io/anuke/mindustry/graphics/CacheLayer.java b/core/src/io/anuke/mindustry/graphics/CacheLayer.java new file mode 100644 index 0000000000..aecf703ae2 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/CacheLayer.java @@ -0,0 +1,87 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.graphics.Color; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Shader; + +import static io.anuke.mindustry.Vars.renderer; + +public enum CacheLayer{ + water{ + @Override + public void begin(){ + beginShader(); + } + + @Override + public void end(){ + endShader(Shaders.water); + } + }, + lava{ + @Override + public void begin(){ + beginShader(); + } + + @Override + public void end(){ + endShader(Shaders.lava); + } + }, + oil{ + @Override + public void begin(){ + beginShader(); + } + + @Override + public void end(){ + endShader(Shaders.oil); + } + }, + space{ + @Override + public void begin(){ + beginShader(); + } + + @Override + public void end(){ + endShader(Shaders.space); + } + }, + normal, + walls; + + public void begin(){ + + } + + public void end(){ + + } + + protected void beginShader(){ + //renderer.getBlocks().endFloor(); + renderer.effectSurface.getBuffer().bind(); + Graphics.clear(Color.CLEAR); + //renderer.getBlocks().beginFloor(); + } + + public void endShader(Shader shader){ + renderer.getBlocks().endFloor(); + + renderer.pixelSurface.getBuffer().bind(); + + Graphics.shader(shader); + Graphics.begin(); + Draw.rect(renderer.effectSurface.texture(), Core.camera.position.x, Core.camera.position.y, + Core.camera.viewportWidth * Core.camera.zoom, -Core.camera.viewportHeight * Core.camera.zoom); + Graphics.end(); + Graphics.shader(); + renderer.getBlocks().beginFloor(); + } +} diff --git a/core/src/io/anuke/mindustry/graphics/FloorRenderer.java b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java new file mode 100644 index 0000000000..5ccefa23be --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/FloorRenderer.java @@ -0,0 +1,271 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.IntSet; +import com.badlogic.gdx.utils.IntSet.IntSetIterator; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.game.EventType.WorldLoadGraphicsEvent; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.graphics.CacheBatch; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Mathf; + +import java.util.Arrays; + +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.world; + +public class FloorRenderer{ + private final static int chunksize = 64; + + private Chunk[][] cache; + private CacheBatch cbatch; + private IntSet drawnLayerSet = new IntSet(); + private IntArray drawnLayers = new IntArray(); + + public FloorRenderer(){ + Events.on(WorldLoadGraphicsEvent.class, this::clearTiles); + } + + static ShaderProgram createDefaultShader(){ + String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // + + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // + + "uniform mat4 u_projTrans;\n" // + + "varying vec2 v_texCoords;\n" // + + "\n" // + + "void main()\n" // + + "{\n" // + + " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" // + + " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" // + + "}\n"; + String fragmentShader = "#ifdef GL_ES\n" // + + "#define LOWP lowp\n" // + + "precision mediump float;\n" // + + "#else\n" // + + "#define LOWP \n" // + + "#endif\n" // + + "varying vec2 v_texCoords;\n" // + + "uniform sampler2D u_texture;\n" // + + "void main()\n"// + + "{\n" // + + " gl_FragColor = texture2D(u_texture, v_texCoords);\n" // + + "}"; + + ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader); + if(!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog()); + return shader; + } + + public void drawFloor(){ + if(cache == null){ + return; + } + + OrthographicCamera camera = Core.camera; + + int crangex = (int) (camera.viewportWidth * camera.zoom / (chunksize * tilesize)) + 1; + int crangey = (int) (camera.viewportHeight * camera.zoom / (chunksize * tilesize)) + 1; + + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ + int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; + int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; + + if(!Mathf.inBounds(worldx, worldy, cache)) + continue; + + fillChunk(worldx * chunksize * tilesize, worldy * chunksize * tilesize); + } + } + + int layers = CacheLayer.values().length; + + drawnLayers.clear(); + drawnLayerSet.clear(); + + //preliminary layer check: + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ + int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; + int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; + + if(!Mathf.inBounds(worldx, worldy, cache)) + continue; + + Chunk chunk = cache[worldx][worldy]; + + //loop through all layers, and add layer index if it exists + for(int i = 0; i < layers - 1; i++){ + if(chunk.caches[i] != -1){ + drawnLayerSet.add(i); + } + } + } + } + + IntSetIterator it = drawnLayerSet.iterator(); + while(it.hasNext){ + drawnLayers.add(it.next()); + } + + drawnLayers.sort(); + + Graphics.end(); + beginDraw(); + + for(int i = 0; i < drawnLayers.size; i++){ + CacheLayer layer = CacheLayer.values()[drawnLayers.get(i)]; + + drawLayer(layer); + } + + endDraw(); + Graphics.begin(); + } + + public void beginDraw(){ + if(cache == null){ + return; + } + + cbatch.setProjectionMatrix(Core.camera.combined); + cbatch.beginDraw(); + + Gdx.gl.glEnable(GL20.GL_BLEND); + } + + public void endDraw(){ + if(cache == null){ + return; + } + + cbatch.endDraw(); + } + + public void drawLayer(CacheLayer layer){ + if(cache == null){ + return; + } + + OrthographicCamera camera = Core.camera; + + int crangex = (int) (camera.viewportWidth * camera.zoom / (chunksize * tilesize)) + 1; + int crangey = (int) (camera.viewportHeight * camera.zoom / (chunksize * tilesize)) + 1; + + layer.begin(); + + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ + int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; + int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; + + if(!Mathf.inBounds(worldx, worldy, cache)){ + continue; + } + + Chunk chunk = cache[worldx][worldy]; + if(chunk.caches[layer.ordinal()] == -1) continue; + cbatch.drawCache(chunk.caches[layer.ordinal()]); + } + } + + layer.end(); + } + + private void fillChunk(float x, float y){ + Draw.color(Color.GRAY); + Draw.crect("white", x, y, chunksize * tilesize, chunksize * tilesize); + Draw.color(); + } + + private void cacheChunk(int cx, int cy){ + Chunk chunk = cache[cx][cy]; + //long time = TimeUtils.nanoTime(); + + ObjectSet used = new ObjectSet<>(); + + for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){ + for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){ + Tile tile = world.tile(tilex, tiley); + if(tile != null){ + used.add(tile.block().cacheLayer == CacheLayer.walls ? + CacheLayer.walls : tile.floor().cacheLayer); + } + } + } + + for(CacheLayer layer : used){ + cacheChunkLayer(cx, cy, chunk, layer); + } + + // Log.info("Time to cache a chunk: {0}", TimeUtils.timeSinceNanos(time) / 1000000f); + } + + private void cacheChunkLayer(int cx, int cy, Chunk chunk, CacheLayer layer){ + + Graphics.useBatch(cbatch); + cbatch.begin(); + + for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){ + for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){ + Tile tile = world.tile(tilex, tiley); + if(tile == null) continue; + + if(tile.floor().cacheLayer == layer && tile.block().cacheLayer != CacheLayer.walls){ + tile.floor().draw(tile); + }else if(tile.floor().cacheLayer.ordinal() < layer.ordinal() && tile.block().cacheLayer != CacheLayer.walls && layer != CacheLayer.walls){ + tile.floor().drawNonLayer(tile); + } + + if(tile.block().cacheLayer == layer && layer == CacheLayer.walls){ + Block block = tile.block(); + block.draw(tile); + } + } + } + + cbatch.end(); + Graphics.popBatch(); + chunk.caches[layer.ordinal()] = cbatch.getLastCache(); + } + + public void clearTiles(){ + if(cbatch != null) cbatch.dispose(); + + Timers.mark(); + + int chunksx = Mathf.ceil((float) world.width() / chunksize), chunksy = Mathf.ceil((float) world.height() / chunksize); + cache = new Chunk[chunksx][chunksy]; + cbatch = new CacheBatch(world.width() * world.height() * 4 * 4); + + Log.info("Time to create: {0}", Timers.elapsed()); + + Timers.mark(); + + for(int x = 0; x < chunksx; x++){ + for(int y = 0; y < chunksy; y++){ + cache[x][y] = new Chunk(); + Arrays.fill(cache[x][y].caches, -1); + + cacheChunk(x, y); + } + } + + Log.info("Time to cache: {0}", Timers.elapsed()); + } + + private class Chunk{ + int[] caches = new int[CacheLayer.values().length]; + } +} diff --git a/core/src/io/anuke/mindustry/graphics/FogRenderer.java b/core/src/io/anuke/mindustry/graphics/FogRenderer.java new file mode 100644 index 0000000000..6ebdfb8f90 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/FogRenderer.java @@ -0,0 +1,145 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.glutils.FrameBuffer; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.game.EventType.TileChangeEvent; +import io.anuke.mindustry.game.EventType.WorldLoadGraphicsEvent; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.entities.EntityDraw; +import io.anuke.ucore.graphics.ClipSpriteBatch; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; + +import java.nio.ByteBuffer; + +import static io.anuke.mindustry.Vars.*; + +/** + * Used for rendering fog of war. A framebuffer is used for this. + */ +public class FogRenderer implements Disposable{ + private TextureRegion region = new TextureRegion(); + private FrameBuffer buffer; + private ByteBuffer pixelBuffer = ByteBuffer.allocateDirect(4); + private Array changeQueue = new Array<>(); + + public FogRenderer(){ + Events.on(WorldLoadGraphicsEvent.class, () -> { + dispose(); + buffer = new FrameBuffer(Format.RGBA8888, world.width(), world.height(), false); + changeQueue.clear(); + + //clear buffer to black + buffer.begin(); + Graphics.clear(0, 0, 0, 1f); + buffer.end(); + + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + Tile tile = world.tile(x, y); + + if(tile.getTeam() == players[0].getTeam() && tile.block().synthetic() && tile.block().viewRange > 0){ + changeQueue.add(tile); + } + } + } + }); + + Events.on(TileChangeEvent.class, tile -> threads.runGraphics(() -> { + if(tile.getTeam() == players[0].getTeam() && tile.block().synthetic() && tile.block().viewRange > 0){ + changeQueue.add(tile); + } + })); + } + + public void draw(){ + if(buffer == null) return; + + float vw = Core.camera.viewportWidth * Core.camera.zoom; + float vh = Core.camera.viewportHeight * Core.camera.zoom; + + float px = Core.camera.position.x -= vw / 2f; + float py = Core.camera.position.y -= vh / 2f; + + float u = px / tilesize / world.width(); + float v = py / tilesize / world.height(); + + float u2 = (px + vw) / tilesize / world.width(); + float v2 = (py + vh) / tilesize / world.height(); + + if(Core.batch instanceof ClipSpriteBatch){ + ((ClipSpriteBatch) Core.batch).enableClip(false); + } + + Core.batch.getProjectionMatrix().setToOrtho2D(0, 0, world.width() * tilesize, world.height() * tilesize); + + Draw.color(Color.WHITE); + + buffer.begin(); + + //TODO use this for per-tile visibility to show/hide units + //pixelBuffer.position(0); + //Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1); + //Gdx.gl.glReadPixels(world.width()/2, world.height()/2 + 20, 1, 1, GL20.GL_RGB, GL20.GL_UNSIGNED_BYTE, pixelBuffer); + //Log.info(pixelBuffer.get(0)); + + Graphics.begin(); + EntityDraw.setClip(false); + + renderer.drawAndInterpolate(playerGroup, player -> !player.isDead() && player.getTeam() == players[0].getTeam(), Unit::drawView); + renderer.drawAndInterpolate(unitGroups[players[0].getTeam().ordinal()], unit -> !unit.isDead(), Unit::drawView); + + for(Tile tile : changeQueue){ + float viewRange = tile.block().viewRange; + if(viewRange < 0) continue; + Fill.circle(tile.drawx(), tile.drawy(), tile.block().viewRange); + } + + changeQueue.clear(); + + EntityDraw.setClip(true); + Graphics.end(); + buffer.end(); + + region.setTexture(buffer.getColorBufferTexture()); + region.setRegion(u, v2, u2, v); + + Core.batch.setProjectionMatrix(Core.camera.combined); + Graphics.shader(Shaders.fog); + renderer.pixelSurface.getBuffer().begin(); + Graphics.begin(); + + Core.batch.draw(region, px, py, vw, vh); + + Graphics.end(); + renderer.pixelSurface.getBuffer().end(); + Graphics.shader(); + + Graphics.setScreen(); + Core.batch.draw(renderer.pixelSurface.texture(), 0, Gdx.graphics.getHeight(), Gdx.graphics.getWidth(), -Gdx.graphics.getHeight()); + Graphics.end(); + + if(Core.batch instanceof ClipSpriteBatch){ + ((ClipSpriteBatch) Core.batch).enableClip(true); + } + } + + public Texture getTexture(){ + return buffer.getColorBufferTexture(); + } + + @Override + public void dispose(){ + if(buffer != null) buffer.dispose(); + } +} diff --git a/core/src/io/anuke/mindustry/graphics/Fx.java b/core/src/io/anuke/mindustry/graphics/Fx.java deleted file mode 100644 index f1315889a2..0000000000 --- a/core/src/io/anuke/mindustry/graphics/Fx.java +++ /dev/null @@ -1,520 +0,0 @@ -package io.anuke.mindustry.graphics; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Colors; -import io.anuke.ucore.core.Effects.Effect; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Hue; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.graphics.Shapes; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.respawnduration; -import static io.anuke.mindustry.Vars.tilesize; - -public class Fx{ - public static Color lightRed = Hue.mix(Color.WHITE, Color.FIREBRICK, 0.1f); - public static Color lightOrange = Color.valueOf("f68021"); - public static Color lighterOrange = Color.valueOf("f6e096"); - public static Color whiteOrange = Hue.mix(lightOrange, Color.WHITE, 0.6f); - public static Color whiteYellow = Hue.mix(Color.YELLOW, Color.WHITE, 0.6f); - public static Color lightGray = Color.valueOf("b0b0b0"); - public static Color glowy = Color.valueOf("fdc056"); - public static Color beam = Color.valueOf("9bffbe"); - public static Color beamLight = Color.valueOf("ddffe9"); - - public static final Effect - - generatorexplosion = new Effect(28, 40f, e -> { - Angles.randLenVectors(e.id, 16, 10f + e.fin()*8f, (x, y)->{ - float size = e.fout()*12f + 1f; - Draw.color(Color.WHITE, lightOrange, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - reactorsmoke = new Effect(17, e -> { - Angles.randLenVectors(e.id, 4, e.fin()*8f, (x, y)->{ - float size = 1f+e.fout()*5f; - Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - nuclearsmoke = new Effect(40, e -> { - Angles.randLenVectors(e.id, 4, e.fin()*13f, (x, y)->{ - float size = e.finpow()*4f; - Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - nuclearcloud = new Effect(90, 200f, e -> { - Angles.randLenVectors(e.id, 10, e.finpow()*90f, (x, y)->{ - float size = e.fout()*14f; - Draw.color(Color.LIME, Color.GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - chainshot = new Effect(9f, e -> { - Draw.color(Color.WHITE, lightOrange, e.fin()); - Lines.stroke(e.fout()*4f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*7f); - Lines.stroke(e.fout()*2f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*10f); - Draw.reset(); - }), - - mortarshot = new Effect(10f, e -> { - Draw.color(Color.WHITE, Color.DARK_GRAY, e.fin()); - Lines.stroke(e.fout()*6f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*10f); - Lines.stroke(e.fout()*5f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*14f); - Lines.stroke(e.fout()*1f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*16f); - Draw.reset(); - }), - - railshot = new Effect(9f, e -> { - Draw.color(Color.WHITE, Color.DARK_GRAY, e.fin()); - Lines.stroke(e.fout()*5f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*8f); - Lines.stroke(e.fout()*4f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*12f); - Lines.stroke(e.fout()*1f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*14f); - Draw.reset(); - }), - - titanshot = new Effect(12f, e -> { - Draw.color(Color.WHITE, lightOrange, e.fin()); - Lines.stroke(e.fout()*7f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*12f); - Lines.stroke(e.fout()*4f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*16f); - Lines.stroke(e.fout()*2f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*18f); - Draw.reset(); - }), - - largeCannonShot = new Effect(11f, e -> { - Draw.color(Color.WHITE, whiteYellow, e.fin()); - Lines.stroke(e.fout()*6f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*12f); - Lines.stroke(e.fout()*3f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*16f); - Lines.stroke(e.fout()*1f); - Lines.lineAngle(e.x, e.y, e.rotation, e.fout()*18f); - Draw.reset(); - }), - - shockwave = new Effect(10f, 80f, e -> { - Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); - Lines.stroke(e.fout()*2f + 0.2f); - Lines.circle(e.x, e.y, e.fin()*28f); - Draw.reset(); - }), - - nuclearShockwave = new Effect(10f, 200f, e -> { - Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); - Lines.stroke(e.fout()*3f + 0.2f); - Lines.poly(e.x, e.y, 40, e.fin()*140f); - Draw.reset(); - }), - - shockwaveSmall = new Effect(10f, e -> { - Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); - Lines.stroke(e.fout()*2f + 0.1f); - Lines.circle(e.x, e.y, e.fin()*15f); - Draw.reset(); - }), - - empshockwave = new Effect(7f, e -> { - Draw.color(Color.WHITE, Color.SKY, e.fin()); - Lines.stroke(e.fout()*2f); - Lines.circle(e.x, e.y, e.fin()*40f); - Draw.reset(); - }), - - empspark = new Effect(13, e -> { - Angles.randLenVectors(e.id, 7, 1f + e.fin()*12f, (x, y)->{ - float len = 1f+e.fout()*6f; - Draw.color(Color.SKY); - Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), len); - Draw.reset(); - }); - }), - - redgeneratespark = new Effect(18, e -> { - Angles.randLenVectors(e.id, 5, e.fin()*8f, (x, y)->{ - float len = e.fout()*4f; - Draw.color(Color.valueOf("fbb97f"), Color.GRAY, e.fin()); - //Draw.alpha(e.fout()); - Draw.rect("circle", e.x + x, e.y + y, len, len); - Draw.reset(); - }); - }), - - generatespark = new Effect(18, e -> { - Angles.randLenVectors(e.id, 5, e.fin()*8f, (x, y)->{ - float len = e.fout()*4f; - Draw.color(Color.valueOf("d2b29c"), Color.GRAY, e.fin()); - //Draw.alpha(e.fout()); - Draw.rect("circle", e.x + x, e.y + y, len, len); - Draw.reset(); - }); - }), - - fuelburn = new Effect(23, e -> { - Angles.randLenVectors(e.id, 5, e.fin()*9f, (x, y)->{ - float len = e.fout()*4f; - Draw.color(Color.LIGHT_GRAY, Color.GRAY, e.fin()); - //Draw.alpha(e.fout()); - Draw.rect("circle", e.x + x, e.y + y, len, len); - Draw.reset(); - }); - }), - - laserspark = new Effect(14, e -> { - Angles.randLenVectors(e.id, 8, 1f + e.fin()*11f, (x, y)->{ - float len = 1f+e.fout()*5f; - Draw.color(Color.WHITE, Color.CORAL, e.fin()); - Draw.alpha(e.fin()/1.3f); - Lines.lineAngle(e.x + x, e.y + y, Mathf.atan2(x, y), len); - Draw.reset(); - }); - }), - - shellsmoke = new Effect(20, e -> { - Angles.randLenVectors(e.id, 8, 3f + e.fin()*17f, (x, y)->{ - float size = 2f+e.fout()*5f; - Draw.color(Color.LIGHT_GRAY, Color.DARK_GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - blastsmoke = new Effect(26, e -> { - Angles.randLenVectors(e.id, 12, 1f + e.fin()*23f, (x, y)->{ - float size = 2f+e.fout()*6f; - Draw.color(Color.LIGHT_GRAY, Color.DARK_GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - lava = new Effect(18, e -> { - Angles.randLenVectors(e.id, 3, 1f + e.fin()*10f, (x, y)->{ - float size = e.finpow()*4f; - Draw.color(Color.ORANGE, Color.GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - }), - - lavabubble = new Effect(45f, e -> { - Draw.color(Color.ORANGE); - float scl = 0.35f; - Lines.stroke(1f - Mathf.clamp(e.fin() - (1f-scl)) * (1f/scl)); - Lines.circle(e.x, e.y, e.fin()*4f); - Draw.reset(); - }), - - oilbubble = new Effect(64f, e -> { - Draw.color(Color.DARK_GRAY); - float scl = 0.25f; - Lines.stroke(1f - Mathf.clamp(e.fin() - (1f-scl)) * (1f/scl)); - Lines.circle(e.x, e.y, e.fin()*3f); - Draw.reset(); - }), - - shellexplosion = new Effect(9, e -> { - Lines.stroke(2f - e.fin()*1.7f); - Draw.color(Color.WHITE, Color.LIGHT_GRAY, e.fin()); - Lines.circle(e.x, e.y, 3f + e.fin() * 9f); - Draw.reset(); - }), - - blastexplosion = new Effect(14, e -> { - Lines.stroke(1.2f - e.fin()); - Draw.color(Color.WHITE, lightOrange, e.fin()); - Lines.circle(e.x, e.y, 1.5f + e.fin() * 9f); - Draw.reset(); - }), - - place = new Effect(16, e -> { - Lines.stroke(3f - e.fin() * 2f); - Lines.square(e.x, e.y, tilesize / 2f + e.fin() * 3f); - Draw.reset(); - }), - - dooropen = new Effect(10, e -> { - Lines.stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize / 2f + e.fin() * 2f); - Draw.reset(); - }), - - doorclose= new Effect(10, e -> { - Lines.stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize / 2f + e.fout() * 2f); - Draw.reset(); - }), - - dooropenlarge = new Effect(10, e -> { - Lines.stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize + e.fin() * 2f); - Draw.reset(); - }), - - doorcloselarge = new Effect(10, e -> { - Lines.stroke(e.fout() * 1.6f); - Lines.square(e.x, e.y, tilesize + e.fout() * 2f); - Draw.reset(); - }), - - purify = new Effect(10, e -> { - Draw.color(Color.ROYAL, Color.GRAY, e.fin()); - Lines.stroke(2f); - Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); - Draw.reset(); - }), - - purifyoil = new Effect(10, e -> { - Draw.color(Color.BLACK, Color.GRAY, e.fin()); - Lines.stroke(2f); - Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); - Draw.reset(); - }), - - purifystone = new Effect(10, e -> { - Draw.color(Color.ORANGE, Color.GRAY, e.fin()); - Lines.stroke(2f); - Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6); - Draw.reset(); - }), - - generate = new Effect(11, e -> { - Draw.color(Color.ORANGE, Color.YELLOW, e.fin()); - Lines.stroke(1f); - Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8); - Draw.reset(); - }), - - spark = new Effect(10, e -> { - Lines.stroke(1f); - Draw.color(Color.WHITE, Color.GRAY, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8); - Draw.reset(); - }), - - sparkbig = new Effect(11, e -> { - Lines.stroke(1f); - Draw.color(lightRed, Color.GRAY, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 5f, 2.3f, 8); - Draw.reset(); - }), - - smelt = new Effect(10, e -> { - Lines.stroke(1f); - Draw.color(Color.YELLOW, Color.RED, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 5f, 1f, 8); - Draw.reset(); - }), - - breakBlock = new Effect(12, e -> { - Lines.stroke(2f); - Draw.color(Color.WHITE, Colors.get("break"), e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 6f, 2, 5, 90); - Draw.reset(); - }), - - hit = new Effect(10, e -> { - Lines.stroke(1f); - Draw.color(Color.WHITE, Color.ORANGE, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 3f, 2, 8); - Draw.reset(); - }), - - laserhit = new Effect(10, e -> { - Lines.stroke(1f); - Draw.color(Color.WHITE, Color.SKY, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 2f, 2, 6); - Draw.reset(); - }), - - shieldhit = new Effect(9, e -> { - Lines.stroke(1f); - Draw.color(Color.WHITE, Color.SKY, e.fin()); - Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 6); - Lines.stroke(4f*e.fout()); - Lines.circle(e.x, e.y, e.fin()*14f); - Draw.reset(); - }), - - laserShoot = new Effect(8, e -> { - Draw.color(Color.WHITE, lightOrange, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation, 3, e.fout(), 6f, 2f, 0.8f); - Draw.reset(); - }), - - spreadShoot = new Effect(12, e -> { - Draw.color(Color.WHITE, Color.PURPLE, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation, 3, e.fout(), 9f, 3.5f, 0.8f); - Draw.reset(); - }), - - clusterShoot = new Effect(12, e -> { - Draw.color(Color.WHITE, lightOrange, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation, 3, e.fout(), 10f, 2.5f, 0.7f); - Draw.reset(); - }), - - vulcanShoot = new Effect(8, e -> { - Draw.color(lighterOrange, lightOrange, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation, 3, e.fout(), 10f, 2f, 0.7f); - Draw.reset(); - }), - - shockShoot = new Effect(8, e -> { - Draw.color(Color.WHITE, Color.ORANGE, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation, 3, e.fout(), 14f, 4f, 0.8f); - Draw.reset(); - }), - - beamShoot = new Effect(8, e -> { - Draw.color(beamLight, beam, e.fin()); - Shapes.lineShot(e.x, e.y, e.rotation - 70, 3, e.fout(), 12f, 1f, 0.5f); - Shapes.lineShot(e.x, e.y, e.rotation + 70, 3, e.fout(), 12f, 1f, 0.5f); - Draw.reset(); - }), - - beamhit = new Effect(8, e -> { - Draw.color(beamLight, beam, e.fin()); - Lines.stroke(e.fout()*3f+0.5f); - Lines.circle(e.x, e.y, e.fin()*8f); - Lines.spikes(e.x, e.y, e.fin()*6f, 2f, 4, 45); - Draw.reset(); - }), - - titanExplosion = new Effect(11, 48f, e -> { - Lines.stroke(2f*e.fout()+0.5f); - Draw.color(Color.WHITE, Color.DARK_GRAY, e.finpow()); - Lines.circle(e.x, e.y, 5f + e.finpow() * 8f); - - Draw.color(e.fin() < 0.5f ? whiteOrange : Color.DARK_GRAY); - float rad = e.fout()*10f + 5f; - Angles.randLenVectors(e.id, 5, 9f, (x, y)->{ - Draw.rect("circle2", e.x + x, e.y + y, rad, rad); - }); - - Draw.reset(); - }), - - explosion = new Effect(11, e -> { - Lines.stroke(2f*e.fout()+0.5f); - Draw.color(Color.WHITE, Color.DARK_GRAY, e.finpow()); - Lines.circle(e.x, e.y, 5f + e.finpow() * 6f); - - Draw.color(e.fin() < 0.5f ? Color.WHITE : Color.DARK_GRAY); - float rad = e.fout()*10f + 5f; - Angles.randLenVectors(e.id, 5, 8f, (x, y)->{ - Draw.rect("circle2", e.x + x, e.y + y, rad, rad); - }); - - Draw.reset(); - }), - - - blockexplosion = new Effect(13, e -> { - Angles.randLenVectors(e.id+1, 8, 5f + e.fin()*11f, (x, y)->{ - float size = 2f+e.fout()*8f; - Draw.color(Color.LIGHT_GRAY, Color.DARK_GRAY, e.fin()); - Draw.rect("circle", e.x + x, e.y + y, size, size); - Draw.reset(); - }); - - Lines.stroke(2f*e.fout()+0.4f); - Draw.color(Color.WHITE, Color.ORANGE, e.finpow()); - Lines.circle(e.x, e.y, 2f + e.finpow() * 9f); - - Draw.color(e.fin() < 0.5f ? Color.WHITE : Color.DARK_GRAY); - float rad = e.fout()*10f + 2f; - Angles.randLenVectors(e.id, 5, 8f, (x, y)->{ - Draw.rect("circle2", e.x + x, e.y + y, rad, rad); - }); - - Draw.reset(); - }), - - clusterbomb = new Effect(10f, e -> { - Draw.color(Color.WHITE, lightOrange, e.fin()); - Lines.stroke(e.fout()*1.5f); - Lines.poly(e.x, e.y, 4, e.fout()*8f); - Lines.circle(e.x, e.y, e.fin()*14f); - Draw.reset(); - }), - - coreexplosion = new Effect(13, e -> { - Lines.stroke(3f-e.fin()*2f); - Draw.color(Color.ORANGE, Color.WHITE, e.fin()); - Lines.spikes(e.x, e.y, 5f + e.fin() * 40f, 6, 6); - Lines.circle(e.x, e.y, 4f + e.fin() * 40f); - Draw.reset(); - }), - - smoke = new Effect(100, e -> { - Draw.color(Color.GRAY, new Color(0.3f, 0.3f, 0.3f, 1f), e.fin()); - float size = 7f-e.fin()*7f; - Draw.rect("circle", e.x, e.y, size, size); - Draw.reset(); - }), - - railsmoke = new Effect(30, e -> { - Draw.color(Color.LIGHT_GRAY, Color.WHITE, e.fin()); - float size = e.fout()*4f; - Draw.rect("circle", e.x, e.y, size, size); - Draw.reset(); - }), - - chainsmoke = new Effect(30, e -> { - Draw.color(lightGray); - float size = e.fout()*4f; - Draw.rect("circle", e.x, e.y, size, size); - Draw.reset(); - }), - - dashsmoke = new Effect(30, e -> { - Draw.color(Color.CORAL, Color.GRAY, e.fin()); - float size = e.fout()*4f; - Draw.rect("circle", e.x, e.y, size, size); - Draw.reset(); - }), - - spawn = new Effect(23, e -> { - Lines.stroke(2f); - Draw.color(Color.DARK_GRAY, Color.SCARLET, e.fin()); - Lines.circle(e.x, e.y, 7f - e.fin() * 6f); - Draw.reset(); - }), - - respawn = new Effect(respawnduration, e -> { - Draw.tcolor(Color.SCARLET); - Draw.tscl(0.25f); - Draw.text("Respawning in " + (int)((e.lifetime-e.time)/60), e.x, e.y); - Draw.tscl(0.5f); - Draw.reset(); - }), - transfer = new Effect(20, e -> { - Draw.color(Color.SCARLET, Color.CLEAR, e.fout()); - Lines.square(e.x, e.y, 4); - Lines.lineAngle(e.x, e.y, e.rotation, 5f); - Draw.reset(); - }); -} diff --git a/core/src/io/anuke/mindustry/graphics/Layer.java b/core/src/io/anuke/mindustry/graphics/Layer.java new file mode 100644 index 0000000000..f04144a2cd --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/Layer.java @@ -0,0 +1,28 @@ +package io.anuke.mindustry.graphics; + +public enum Layer{ + /** + * Base block layer. + */ + block, + /** + * for placement + */ + placement, + /** + * First overlay. Stuff like conveyor items. + */ + overlay, + /** + * "High" blocks, like turrets. + */ + turret, + /** + * Power lasers. + */ + power, + /** + * Extra lasers, like healing turrets. + */ + laser +} diff --git a/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java new file mode 100644 index 0000000000..2760dbdc78 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/MinimapRenderer.java @@ -0,0 +1,160 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Disposable; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.game.EventType.TileChangeEvent; +import io.anuke.mindustry.game.EventType.WorldLoadGraphicsEvent; +import io.anuke.mindustry.world.ColorMapper; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Events; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Pixmaps; +import io.anuke.ucore.scene.utils.ScissorStack; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.ThreadArray; + +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.world; + +public class MinimapRenderer implements Disposable{ + private static final int baseSize = 16; + private final Array units = new ThreadArray<>(); + private Pixmap pixmap; + private Texture texture; + private TextureRegion region; + private Rectangle rect = new Rectangle(); + private Rectangle clipRect = new Rectangle(); + private Color tmpColor = new Color(); + private int zoom = 4; + + public MinimapRenderer(){ + Events.on(WorldLoadGraphicsEvent.class, () -> { + reset(); + updateAll(); + }); + + //make sure to call on the graphics thread + Events.on(TileChangeEvent.class, tile -> Gdx.app.postRunnable(() -> update(tile))); + } + + public Texture getTexture(){ + return texture; + } + + public void zoomBy(int amount){ + zoom += amount; + zoom = Mathf.clamp(zoom, 1, Math.min(world.width(), world.height()) / baseSize / 2); + } + + public void reset(){ + if(pixmap != null){ + pixmap.dispose(); + texture.dispose(); + } + pixmap = new Pixmap(world.width(), world.height(), Format.RGBA8888); + texture = new Texture(pixmap); + region = new TextureRegion(texture); + } + + public void drawEntities(float x, float y, float w, float h){ + int sz = baseSize * zoom; + float dx = (Core.camera.position.x / tilesize); + float dy = (Core.camera.position.y / tilesize); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); + + synchronized(units){ + rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize); + Graphics.flush(); + + boolean clip = ScissorStack.pushScissors(clipRect.set(x, y, w, h)); + + for(Unit unit : units){ + float rx = (unit.x - rect.x) / rect.width * w, ry = (unit.y - rect.y) / rect.width * h; + Draw.color(unit.getTeam().color); + Draw.rect("white", x + rx, y + ry, w / (sz * 2), h / (sz * 2)); + } + + Draw.color(); + + Graphics.flush(); + if(clip) ScissorStack.popScissors(); + } + } + + public TextureRegion getRegion(){ + if(texture == null) return null; + + int sz = Mathf.clamp(baseSize * zoom, baseSize, Math.min(world.width(), world.height())); + float dx = (Core.camera.position.x / tilesize); + float dy = (Core.camera.position.y / tilesize); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); + float invTexWidth = 1f / texture.getWidth(); + float invTexHeight = 1f / texture.getHeight(); + float x = dx - sz, y = world.height() - dy - sz, width = sz * 2, height = sz * 2; + region.setRegion(x * invTexWidth, y * invTexHeight, (x + width) * invTexWidth, (y + height) * invTexHeight); + return region; + } + + public void updateAll(){ + for(int x = 0; x < world.width(); x++){ + for(int y = 0; y < world.height(); y++){ + pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, colorFor(world.tile(x, y))); + } + } + texture.draw(pixmap, 0, 0); + } + + public void update(Tile tile){ + int color = colorFor(world.tile(tile.x, tile.y)); + pixmap.drawPixel(tile.x, pixmap.getHeight() - 1 - tile.y, color); + + Pixmaps.drawPixel(texture, tile.x, pixmap.getHeight() - 1 - tile.y, color); + } + + public void updateUnitArray(){ + int sz = baseSize * zoom; + float dx = (Core.camera.position.x / tilesize); + float dy = (Core.camera.position.y / tilesize); + dx = Mathf.clamp(dx, sz, world.width() - sz); + dy = Mathf.clamp(dy, sz, world.height() - sz); + + synchronized(units){ + rect.set((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize); + units.clear(); + Units.getNearby(rect, units::add); + } + } + + private int colorFor(Tile tile){ + int color = tile.breakable() ? tile.target().getTeam().intColor : ColorMapper.getBlockColor(tile.block()); + if(color == 0) color = ColorMapper.getBlockColor(tile.floor()); + if(tile.elevation > 0){ + float mul = 1.1f + tile.elevation / 4f; + tmpColor.set(color); + tmpColor.mul(mul, mul, mul, 1f); + color = Color.rgba8888(tmpColor); + } + return color; + } + + @Override + public void dispose(){ + pixmap.dispose(); + texture.dispose(); + texture = null; + pixmap = null; + } +} diff --git a/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java new file mode 100644 index 0000000000..d207cb61b4 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/OverlayRenderer.java @@ -0,0 +1,215 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.TileEntity; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.input.InputHandler; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.meta.BlockBar; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.function.Callable; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.*; + +public class OverlayRenderer{ + + public void drawBottom(){ + for(Player player : players){ + InputHandler input = control.input(player.playerIndex); + + if(!input.isDrawing() || player.isDead()) continue; + + Shaders.outline.color.set(Palette.accent); + Graphics.beginShaders(Shaders.outline); + + input.drawOutlined(); + + Graphics.endShaders(); + } + } + + public void drawTop(){ + + for(Player player : players){ + if(player.isDead()) continue; //dead player don't draw + + InputHandler input = control.input(player.playerIndex); + + //draw config selected block + if(input.frag.config.isShown()){ + Tile tile = input.frag.config.getSelectedTile(); + + synchronized(Tile.tileSetLock){ + tile.block().drawConfigure(tile); + } + } + + input.drawTop(); + + Draw.reset(); + + //draw selected block bars and info + if(input.recipe == null && !ui.hasMouse() && !input.frag.config.isShown()){ + Vector2 vec = Graphics.world(input.getMouseX(), input.getMouseY()); + Tile tile = world.tileWorld(vec.x, vec.y); + + if(tile != null && tile.block() != Blocks.air){ + Tile target = tile.target(); + + if(showBlockDebug && target.entity != null){ + Draw.color(Color.RED); + Lines.crect(target.drawx(), target.drawy(), target.block().size * tilesize, target.block().size * tilesize); + Vector2 v = new Vector2(); + + Draw.tcolor(Color.YELLOW); + Draw.tscl(0.25f); + Array arr = target.block().getDebugInfo(target); + StringBuilder result = new StringBuilder(); + for(int i = 0; i < arr.size / 2; i++){ + result.append(arr.get(i * 2)); + result.append(": "); + result.append(arr.get(i * 2 + 1)); + result.append("\n"); + } + Draw.textc(result.toString(), target.drawx(), target.drawy(), v); + Draw.color(0f, 0f, 0f, 0.5f); + Fill.rect(target.drawx(), target.drawy(), v.x, v.y); + Draw.textc(result.toString(), target.drawx(), target.drawy(), v); + Draw.tscl(fontScale); + Draw.reset(); + } + + synchronized(Tile.tileSetLock){ + Block block = target.block(); + TileEntity entity = target.entity; + + if(entity != null){ + int[] values = {0, 0}; + boolean[] doDraw = {false}; + + Callable drawbars = () -> { + for(BlockBar bar : block.bars.list()){ + float offset = Mathf.sign(bar.top) * (block.size / 2f * tilesize + 2f + (bar.top ? values[0] : values[1])); + + float value = bar.value.get(target); + + if(MathUtils.isEqual(value, -1f)) continue; + + if(doDraw[0]){ + drawBar(bar.type.color, target.drawx(), target.drawy() + offset, value); + } + + if(bar.top) + values[0]++; + else + values[1]++; + } + }; + + drawbars.run(); + + if(values[0] > 0){ + drawEncloser(target.drawx(), target.drawy() + block.size * tilesize / 2f + 2f, values[0]); + } + + if(values[1] > 0){ + drawEncloser(target.drawx(), target.drawy() - block.size * tilesize / 2f - 2f - values[1], values[1]); + } + + doDraw[0] = true; + values[0] = 0; + values[1] = 1; + + drawbars.run(); + } + + + target.block().drawSelect(target); + } + } + } + + if(input.isDroppingItem()){ + Vector2 v = Graphics.world(input.getMouseX(), input.getMouseY()); + float size = 8; + Draw.rect(player.inventory.getItem().item.region, v.x, v.y, size, size); + Draw.color(Palette.accent); + Lines.circle(v.x, v.y, 6 + Mathf.absin(Timers.time(), 5f, 1f)); + Draw.reset(); + + Tile tile = world.tileWorld(v.x, v.y); + if(tile != null) tile = tile.target(); + if(tile != null && tile.block().acceptStack(player.inventory.getItem().item, player.inventory.getItem().amount, tile, player) > 0){ + Draw.color(Palette.place); + Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1 + Mathf.absin(Timers.time(), 5f, 1f)); + Draw.color(); + } + } + + } + + if((!debug || showUI) && Settings.getBool("healthbars")){ + for(TeamData ally : (debug ? state.teams.getTeams() : state.teams.getTeams(true))){ + renderer.drawAndInterpolate(unitGroups[ally.team.ordinal()], u -> !u.isDead(), this::drawStats); + } + + renderer.drawAndInterpolate(playerGroup, u -> !u.isDead(), this::drawStats); + } + } + + void drawStats(Unit unit){ + if(unit.isDead()) return; + + float x = unit.x; + float y = unit.y; + + if(unit == players[0] && players.length == 1 && snapCamera){ + x = (int) (x + 0.0001f); + y = (int) (y + 0.0001f); + } + + drawEncloser(x, y - 9f, 2f); + drawBar(Palette.healthstats, x, y - 8f, unit.healthf()); + drawBar(Palette.ammo, x, y - 9f, unit.getAmmoFraction()); + } + + void drawBar(Color color, float x, float y, float finion){ + finion = Mathf.clamp(finion); + + if(finion > 0) finion = Mathf.clamp(finion + 0.2f, 0.24f, 1f); + + float len = 3; + + float w = (int) (len * 2 * finion); + + Draw.color(Color.BLACK); + Fill.crect(x - len, y, len * 2f, 1); + if(finion > 0){ + Draw.color(color); + Fill.crect(x - len, y, Math.max(1, w), 1); + } + Draw.color(); + } + + void drawEncloser(float x, float y, float height){ + + float len = 4; + + Draw.color(Palette.bar); + Fill.crect(x - len, y - 1, len * 2f, height + 2f); + Draw.color(); + } +} diff --git a/core/src/io/anuke/mindustry/graphics/Palette.java b/core/src/io/anuke/mindustry/graphics/Palette.java new file mode 100644 index 0000000000..1f369df6f2 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/Palette.java @@ -0,0 +1,70 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.graphics.Color; + +public class Palette{ + public static final Color bulletYellow = Color.valueOf("ffeec9"); + public static final Color bulletYellowBack = Color.valueOf("f9c87a"); + + public static final Color missileYellow = Color.valueOf("ffd2ae"); + public static final Color missileYellowBack = Color.valueOf("e58956"); + + public static final Color plastaniumBack = Color.valueOf("d8d97f"); + public static final Color plastaniumFront = Color.valueOf("fffac6"); + + public static final Color lightFlame = Color.valueOf("ffdd55"); + public static final Color darkFlame = Color.valueOf("db401c"); + + public static final Color turretHeat = Color.valueOf("ab3400"); + + public static final Color lightOrange = Color.valueOf("f68021"); + public static final Color lightishOrange = Color.valueOf("f8ad42"); + public static final Color lighterOrange = Color.valueOf("f6e096"); + + public static final Color lightishGray = Color.valueOf("a2a2a2"); + public static final Color darkishGray = new Color(0.3f, 0.3f, 0.3f, 1f); + + public static final Color lancerLaser = Color.valueOf("a9d8ff"); + + public static final Color stoneGray = Color.valueOf("8f8f8f"); + + public static final Color portalLight = Color.valueOf("9054ea"); + public static final Color portal = Color.valueOf("6344d7"); + public static final Color portalDark = Color.valueOf("3f3dac"); + + public static final Color powerLaserFrom = Color.valueOf("e3e3e3"); + public static final Color powerLaserTo = Color.valueOf("ffe7a8"); + + public static final Color description = Color.WHITE; + public static final Color turretinfo = Color.ORANGE; + public static final Color iteminfo = Color.LIGHT_GRAY; + public static final Color powerinfo = Color.YELLOW; + public static final Color liquidinfo = Color.ROYAL; + public static final Color craftinfo = Color.LIGHT_GRAY; + + public static final Color missingitems = Color.SCARLET; + public static final Color health = Color.YELLOW; + public static final Color ammo = Color.valueOf("32cf6d"); + public static final Color healthstats = Color.SCARLET; + public static final Color bar = Color.SLATE; + public static final Color interact = Color.ORANGE; + public static final Color accent = Color.valueOf("f4ba6e"); + public static final Color place = Color.valueOf("6335f8"); + public static final Color remove = Color.valueOf("e55454"); + public static final Color placeRotate = accent; + public static final Color breakInvalid = Color.valueOf("d44b3d"); + public static final Color range = Color.valueOf("f4ba6e"); + public static final Color power = Color.valueOf("fbd367"); + public static final Color placing = Color.valueOf("616161"); + + public static final Color lightTrail = Color.valueOf("ffe2a9"); + + public static final Color redSpark = Color.valueOf("fbb97f"); + public static final Color orangeSpark = Color.valueOf("d2b29c"); + + public static final Color redDust = Color.valueOf("ffa480"); + public static final Color redderDust = Color.valueOf("ff7b69"); + + public static final Color plasticSmoke = Color.valueOf("f1e479"); + public static final Color plasticBurn = Color.valueOf("e9ead3"); +} diff --git a/core/src/io/anuke/mindustry/graphics/Shaders.java b/core/src/io/anuke/mindustry/graphics/Shaders.java index 61e04d8861..996d8e07fe 100644 --- a/core/src/io/anuke/mindustry/graphics/Shaders.java +++ b/core/src/io/anuke/mindustry/graphics/Shaders.java @@ -1,62 +1,192 @@ package io.anuke.mindustry.graphics; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.FloatArray; import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Shader; import io.anuke.ucore.scene.ui.layout.Unit; +import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.world; + public class Shaders{ - public static final Outline outline = new Outline(); - public static final Shield shield = new Shield(); + public static Outline outline; + public static BlockBuild blockbuild; + public static BlockPreview blockpreview; + public static Shield shield; + public static SurfaceShader water; + public static SurfaceShader lava; + public static SurfaceShader oil; + public static Space space; + public static UnitBuild build; + public static MixShader mix; + public static Shader fullMix; + public static FogShader fog; - private static final Vector2 vec = new Vector2(); - - public static class Outline extends Shader{ - public Color color = new Color(); - public float lighten = 0f; + public static void init(){ + outline = new Outline(); + blockbuild = new BlockBuild(); + blockpreview = new BlockPreview(); + shield = new Shield(); + water = new SurfaceShader("water"); + lava = new SurfaceShader("lava"); + oil = new SurfaceShader("oil"); + space = new Space(); + build = new UnitBuild(); + mix = new MixShader(); + fog = new FogShader(); + fullMix = new Shader("fullmix", "default"); + } - public Outline(){ - super("outline", "default"); - } - - @Override - public void apply(){ - shader.setUniformf("u_color", color); - shader.setUniformf("u_lighten", lighten); - shader.setUniformf("u_texsize", vec.set(region.getTexture().getWidth(), region.getTexture().getHeight())); - } - - } - - public static class Shield extends Shader{ - public static final int MAX_HITS = 3*64; - public Color color = new Color(); - public FloatArray hits; - - public Shield(){ - super("shield", "default"); - } - - @Override - public void apply(){ - float scale = Settings.getBool("pixelate") ? 1 : Core.cameraScale / Core.camera.zoom; - float scaling = Core.cameraScale / 4f / Core.camera.zoom; - if(hits.size > 0){ - shader.setUniform3fv("u_hits[0]", hits.items, 0, Math.min(hits.size, MAX_HITS)); - shader.setUniformi("u_hitamount", Math.min(hits.size, MAX_HITS)/3); - } - shader.setUniformf("u_dp", Unit.dp.scl(1f)); - shader.setUniformf("u_color", color); - shader.setUniformf("u_time", Timers.time() / Unit.dp.scl(1f)); - shader.setUniformf("u_scaling", scaling); - shader.setUniformf("u_offset", vec.set(Core.camera.position.x, Core.camera.position.y)); - shader.setUniformf("u_texsize", vec.set(region.getTexture().getWidth() / scale, - region.getTexture().getHeight() / scale)); - } - - } + public static class FogShader extends Shader{ + public FogShader(){ + super("fog", "default"); + } + } + + public static class MixShader extends Shader{ + public Color color = new Color(Color.WHITE); + + public MixShader(){ + super("mix", "default"); + } + + @Override + public void apply(){ + super.apply(); + shader.setUniformf("u_color", color); + } + } + + public static class Space extends SurfaceShader{ + + public Space(){ + super("space2"); + } + + @Override + public void apply(){ + super.apply(); + shader.setUniformf("u_center", world.width() * tilesize / 2f, world.height() * tilesize / 2f); + } + } + + public static class UnitBuild extends Shader{ + public float progress, time; + public Color color = new Color(); + public TextureRegion region; + + public UnitBuild(){ + super("build", "default"); + } + + @Override + public void apply(){ + shader.setUniformf("u_time", time); + shader.setUniformf("u_color", color); + shader.setUniformf("u_progress", progress); + shader.setUniformf("u_uv", region.getU(), region.getV()); + shader.setUniformf("u_uv2", region.getU2(), region.getV2()); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } + + public static class Outline extends Shader{ + public Color color = new Color(); + + public Outline(){ + super("outline", "default"); + } + + @Override + public void apply(){ + shader.setUniformf("u_color", color); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } + + public static class BlockBuild extends Shader{ + public Color color = new Color(); + public float progress; + + public BlockBuild(){ + super("blockbuild", "default"); + } + + @Override + public void apply(){ + shader.setUniformf("u_progress", progress); + shader.setUniformf("u_color", color); + shader.setUniformf("u_uv", region.getU(), region.getV()); + shader.setUniformf("u_uv2", region.getU2(), region.getV2()); + shader.setUniformf("u_time", Timers.time()); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } + + public static class BlockPreview extends Shader{ + public Color color = new Color(); + + public BlockPreview(){ + super("blockpreview", "default"); + } + + @Override + public void apply(){ + // shader.setUniformf("u_progress", progress); + shader.setUniformf("u_color", color); + shader.setUniformf("u_uv", region.getU(), region.getV()); + shader.setUniformf("u_uv2", region.getU2(), region.getV2()); + //shader.setUniformf("u_time", Timers.time()); + shader.setUniformf("u_texsize", region.getTexture().getWidth(), region.getTexture().getHeight()); + } + } + + public static class Shield extends Shader{ + public static final int MAX_HITS = 3 * 64; + public Color color = new Color(); + public FloatArray hits; + + public Shield(){ + super("shield", "default"); + } + + @Override + public void apply(){ + float scaling = Core.cameraScale / 4f / Core.camera.zoom; + if(hits.size > 0){ + shader.setUniform3fv("u_hits[0]", hits.items, 0, Math.min(hits.size, MAX_HITS)); + shader.setUniformi("u_hitamount", Math.min(hits.size, MAX_HITS) / 3); + } + shader.setUniformf("u_dp", Unit.dp.scl(1f)); + shader.setUniformf("u_color", color); + shader.setUniformf("u_time", Timers.time() / Unit.dp.scl(1f)); + shader.setUniformf("u_scaling", scaling); + shader.setUniformf("u_offset", + Core.camera.position.x - Core.camera.viewportWidth / 2 * Core.camera.zoom, + Core.camera.position.y - Core.camera.viewportHeight / 2 * Core.camera.zoom); + shader.setUniformf("u_texsize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, + Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); + } + } + + public static class SurfaceShader extends Shader{ + + public SurfaceShader(String frag){ + super(frag, "default"); + } + + @Override + public void apply(){ + shader.setUniformf("camerapos", + Core.camera.position.x - Core.camera.viewportWidth / 2 * Core.camera.zoom, + Core.camera.position.y - Core.camera.viewportHeight / 2 * Core.camera.zoom); + shader.setUniformf("screensize", Gdx.graphics.getWidth() / Core.cameraScale * Core.camera.zoom, + Gdx.graphics.getHeight() / Core.cameraScale * Core.camera.zoom); + shader.setUniformf("time", Timers.time()); + } + } } diff --git a/core/src/io/anuke/mindustry/graphics/Trail.java b/core/src/io/anuke/mindustry/graphics/Trail.java new file mode 100644 index 0000000000..badf28a389 --- /dev/null +++ b/core/src/io/anuke/mindustry/graphics/Trail.java @@ -0,0 +1,65 @@ +package io.anuke.mindustry.graphics; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.FloatArray; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Fill; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.util.Mathf; + +/** + * Class that renders a trail. + */ +public class Trail{ + private final static float maxJump = 15f; + private final int length; + private final FloatArray points = new FloatArray(); + private float lastX, lastY; + + public Trail(int length){ + this.length = length; + } + + public synchronized void update(float curx, float cury){ + if(Vector2.dst(curx, cury, lastX, lastY) >= maxJump){ + points.clear(); + } + + points.add(curx, cury); + + if(points.size > length * 2){ + float[] items = points.items; + System.arraycopy(items, 2, items, 0, points.size - 2); + points.size -= 2; + } + + lastX = curx; + lastY = cury; + } + + public synchronized void clear(){ + points.clear(); + } + + public synchronized void draw(Color color, float stroke){ + Draw.color(color); + + for(int i = 0; i < points.size - 2; i += 2){ + float x = points.get(i); + float y = points.get(i + 1); + float x2 = points.get(i + 2); + float y2 = points.get(i + 3); + float s = Mathf.clamp((float) (i) / points.size); + + Lines.stroke(s * stroke); + Lines.line(x, y, x2, y2); + } + + if(points.size >= 2){ + Fill.circle(points.get(points.size - 2), points.get(points.size - 1), stroke / 2f); + } + + Draw.reset(); + } +} diff --git a/core/src/io/anuke/mindustry/input/AndroidInput.java b/core/src/io/anuke/mindustry/input/AndroidInput.java deleted file mode 100644 index 7a859da9f1..0000000000 --- a/core/src/io/anuke/mindustry/input/AndroidInput.java +++ /dev/null @@ -1,191 +0,0 @@ -package io.anuke.mindustry.input; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.input.GestureDetector; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.mindustry.resource.ItemStack; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Inputs; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.scene.ui.layout.Unit; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.*; - -public class AndroidInput extends InputHandler{ - public float lmousex, lmousey; - public float mousex, mousey; - public boolean brokeBlock = false; - public boolean placing = false; - - private boolean enableHold = false; - private float warmup; - private float warmupDelay = 20; - - public AndroidInput(){ - Inputs.addProcessor(new GestureDetector(20, 0.5f, 2, 0.15f, new GestureHandler(this))); - } - - @Override public float getCursorEndX(){ return Gdx.input.getX(0); } - @Override public float getCursorEndY(){ return Gdx.input.getY(0); } - @Override public float getCursorX(){ return mousex; } - @Override public float getCursorY(){ return mousey; } - @Override public boolean drawPlace(){ return (placing && !brokeBlock) || (placeMode.pan && recipe != null); } - - @Override - public boolean touchUp(int screenX, int screenY, int pointer, int button){ - if(brokeBlock){ - brokeBlock = false; - return false; - } - - if(placing && pointer == 0 && !placeMode.pan && !breaking()){ - placeMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY()); - }else if(pointer == 0 && !breakMode.pan && breaking() && drawPlace()){ - breakMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY()); - } - - placing = false; - return false; - } - - @Override - public boolean touchDown(int screenX, int screenY, int pointer, int button){ - if(ui.hasMouse()){ - brokeBlock = true; - return false; - } - - lmousex = screenX; - lmousey = screenY; - - if((!placeMode.pan || breaking()) && pointer == 0){ - mousex = screenX; - mousey = screenY; - } - - placing = pointer == 0; - - warmup = 0; - - if(!state.is(State.menu)){ - Tile cursor = world.tile(Mathf.scl2(Graphics.mouseWorld().x, tilesize), Mathf.scl2(Graphics.mouseWorld().y, tilesize)); - if(cursor != null && !ui.hasMouse(screenX, screenY)){ - Tile linked = cursor.isLinked() ? cursor.getLinked() : cursor; - if(linked != null && linked.block().isConfigurable(linked)){ - ui.configfrag.showConfig(linked); - }else if(!ui.configfrag.hasConfigMouse()){ - ui.configfrag.hideConfig(); - } - - if(linked != null) { - linked.block().tapped(linked); - if(Net.active()) NetEvents.handleBlockTap(linked); - } - } - } - return false; - } - - @Override - public void resetCursor(){ - mousex = Gdx.graphics.getWidth()/2; - mousey = Gdx.graphics.getHeight()/2; - } - - @Override - public boolean cursorNear(){ - return true; - } - - public Tile selected(){ - Vector2 vec = Graphics.world(mousex, mousey); - return world.tile(Mathf.scl2(vec.x, tilesize), Mathf.scl2(vec.y, tilesize)); - } - - public void breakBlock(){ - Tile tile = selected(); - breaktime += Timers.delta(); - - if(breaktime >= tile.block().breaktime){ - brokeBlock = true; - breakBlock(tile.x, tile.y, true); - breaktime = 0f; - } - } - - @Override - public void update(){ - enableHold = breakMode == PlaceMode.holdDelete; - - float xa = Inputs.getAxis("move_x"); - float ya = Inputs.getAxis("move_y"); - if(Math.abs(xa) < controllerMin) xa = 0; - if(Math.abs(ya) < controllerMin) ya = 0; - - player.x += xa * 4f; - player.y += ya * 4f; - - rotation += Inputs.getAxis("rotate_alt"); - rotation += Inputs.getAxis("rotate"); - rotation = Mathf.mod(rotation, 4); - - if(enableHold && Gdx.input.isTouched(0) && Mathf.near2d(lmousex, lmousey, Gdx.input.getX(0), Gdx.input.getY(0), Unit.dp.scl(50)) - && !ui.hasMouse()){ - warmup += Timers.delta(); - - float lx = mousex, ly = mousey; - - mousex = Gdx.input.getX(0); - mousey = Gdx.input.getY(0); - - Tile sel = selected(); - - if(sel == null) - return; - - if(warmup > warmupDelay && validBreak(sel.x, sel.y)){ - breaktime += Timers.delta(); - - if(breaktime > selected().block().breaktime){ - breakBlock(); - breaktime = 0; - } - } - - mousex = lx; - mousey = ly; - }else{ - warmup = 0; - breaktime = 0; - - mousex = Mathf.clamp(mousex, 0, Gdx.graphics.getWidth()); - mousey = Mathf.clamp(mousey, 0, Gdx.graphics.getHeight()); - } - } - - @Override - public boolean tryPlaceBlock(int x, int y, boolean sound){ - if(recipe != null && - validPlace(x, y, recipe.result) && cursorNear() && - state.inventory.hasItems(recipe.requirements)){ - - placeBlock(x, y, recipe.result, rotation, true, sound); - - for(ItemStack stack : recipe.requirements){ - state.inventory.removeItem(stack); - } - - return true; - } - return false; - } - - public boolean breaking(){ - return recipe == null; - } -} diff --git a/core/src/io/anuke/mindustry/input/CursorType.java b/core/src/io/anuke/mindustry/input/CursorType.java new file mode 100644 index 0000000000..3b8773f6cb --- /dev/null +++ b/core/src/io/anuke/mindustry/input/CursorType.java @@ -0,0 +1,27 @@ +package io.anuke.mindustry.input; + +import io.anuke.ucore.function.Callable; +import io.anuke.ucore.scene.utils.Cursors; + +/** + * Type of cursor for displaying on desktop. + */ +public enum CursorType{ + normal(Cursors::restoreCursor), + hand(Cursors::setHand), + drill(() -> Cursors.set("drill")), + unload(() -> Cursors.set("unload")); + + private final Callable call; + + CursorType(Callable call){ + this.call = call; + } + + /** + * Sets the current system cursor to this. + */ + void set(){ + call.run(); + } +} diff --git a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java index d35b60e229..94e07bf924 100644 --- a/core/src/io/anuke/mindustry/input/DefaultKeybinds.java +++ b/core/src/io/anuke/mindustry/input/DefaultKeybinds.java @@ -5,69 +5,65 @@ import com.badlogic.gdx.Gdx; import io.anuke.ucore.core.Inputs.Axis; import io.anuke.ucore.core.Inputs.DeviceType; import io.anuke.ucore.core.KeyBinds; -import io.anuke.ucore.util.Input; +import io.anuke.ucore.core.KeyBinds.Category; +import io.anuke.ucore.input.Input; -public class DefaultKeybinds { +public class DefaultKeybinds{ public static void load(){ - KeyBinds.defaults( - "move_x", new Axis(Input.A, Input.D), - "move_y", new Axis(Input.S, Input.W), - "select", Input.MOUSE_LEFT, - "break", Input.MOUSE_RIGHT, - "shoot", Input.MOUSE_LEFT, - "zoom_hold", Input.CONTROL_LEFT, - "zoom", new Axis(Input.SCROLL), - "menu", Gdx.app.getType() == ApplicationType.Android ? Input.BACK : Input.ESCAPE, - "pause", Input.SPACE, - "dash", Input.SHIFT_LEFT, - "rotate_alt", new Axis(Input.R, Input.E), - "rotate", new Axis(Input.SCROLL), - "toggle_menus", Input.C, - "block_info", Input.CONTROL_LEFT, - "block_logs", Input.I, - "player_list", Input.TAB, - "chat", Input.ENTER, - "chat_history_prev", Input.UP, - "chat_history_next", Input.DOWN, - "chat_scroll", new Axis(Input.SCROLL), - "console", Input.GRAVE, - "weapon_1", Input.NUM_1, - "weapon_2", Input.NUM_2, - "weapon_3", Input.NUM_3, - "weapon_4", Input.NUM_4, - "weapon_5", Input.NUM_5, - "weapon_6", Input.NUM_6 - ); + String[] sections = {"player_1"}; - KeyBinds.defaults( - DeviceType.controller, - "move_x", new Axis(Input.CONTROLLER_L_STICK_HORIZONTAL_AXIS), - "move_y", new Axis(Input.CONTROLLER_L_STICK_VERTICAL_AXIS), - "cursor_x", new Axis(Input.CONTROLLER_R_STICK_HORIZONTAL_AXIS), - "cursor_y", new Axis(Input.CONTROLLER_R_STICK_VERTICAL_AXIS), - "select", Input.CONTROLLER_R_BUMPER, - "break", Input.CONTROLLER_L_BUMPER, - "shoot", Input.CONTROLLER_R_TRIGGER, - "zoom_hold", Input.ANY_KEY, - "zoom", new Axis(Input.CONTROLLER_DPAD_DOWN, Input.CONTROLLER_DPAD_UP), - "menu", Input.CONTROLLER_X, - "pause", Input.CONTROLLER_L_TRIGGER, - "dash", Input.CONTROLLER_Y, - "rotate_alt", new Axis(Input.CONTROLLER_DPAD_RIGHT, Input.CONTROLLER_DPAD_LEFT), - "rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B), - "player_list", Input.CONTROLLER_START, - "chat", Input.ENTER, - "chat_history_prev", Input.UP, - "chat_history_next", Input.DOWN, - "chat_scroll", new Axis(Input.SCROLL), - "console", Input.GRAVE, - "weapon_1", Input.NUM_1, - "weapon_2", Input.NUM_2, - "weapon_3", Input.NUM_3, - "weapon_4", Input.NUM_4, - "weapon_5", Input.NUM_5, - "weapon_6", Input.NUM_6 - ); + for(String section : sections){ + + KeyBinds.defaultSection(section, DeviceType.keyboard, + new Category("General"), + "move_x", new Axis(Input.A, Input.D), + "move_y", new Axis(Input.S, Input.W), + //"select", Input.MOUSE_LEFT, + //"break", Input.MOUSE_RIGHT, + //"shoot", Input.MOUSE_LEFT, + "rotate", new Axis(Input.SCROLL), + "dash", Input.SHIFT_LEFT, + "drop_unit", Input.SHIFT_LEFT, + new Category("View"), + "zoom_hold", Input.CONTROL_LEFT, + "zoom", new Axis(Input.SCROLL), + "zoom_minimap", new Axis(Input.MINUS, Input.PLUS), + "menu", Gdx.app.getType() == ApplicationType.Android ? Input.BACK : Input.ESCAPE, + "pause", Input.SPACE, + "toggle_menus", Input.C, + new Category("Multiplayer"), + "player_list", Input.TAB, + "chat", Input.ENTER, + "chat_history_prev", Input.UP, + "chat_history_next", Input.DOWN, + "chat_scroll", new Axis(Input.SCROLL), + "console", Input.GRAVE + ); + + KeyBinds.defaultSection(section, DeviceType.controller, + new Category("General"), + "move_x", new Axis(Input.CONTROLLER_L_STICK_HORIZONTAL_AXIS), + "move_y", new Axis(Input.CONTROLLER_L_STICK_VERTICAL_AXIS), + "cursor_x", new Axis(Input.CONTROLLER_R_STICK_HORIZONTAL_AXIS), + "cursor_y", new Axis(Input.CONTROLLER_R_STICK_VERTICAL_AXIS), + //"select", Input.CONTROLLER_R_BUMPER, + //"break", Input.CONTROLLER_L_BUMPER, + //"shoot", Input.CONTROLLER_R_TRIGGER, + "dash", Input.CONTROLLER_Y, + "rotate_alt", new Axis(Input.CONTROLLER_DPAD_RIGHT, Input.CONTROLLER_DPAD_LEFT), + "rotate", new Axis(Input.CONTROLLER_A, Input.CONTROLLER_B), + new Category("View"), + "zoom_hold", Input.ANY_KEY, + "zoom", new Axis(Input.CONTROLLER_DPAD_DOWN, Input.CONTROLLER_DPAD_UP), + "menu", Input.CONTROLLER_X, + "pause", Input.CONTROLLER_L_TRIGGER, + new Category("Multiplayer"), + "player_list", Input.CONTROLLER_START + ); + + } + + KeyBinds.setSectionAlias("default", "player_1"); } } diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 7564fae8aa..a156939e1a 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -1,190 +1,350 @@ package io.anuke.mindustry.input; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.Input.Buttons; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult; +import io.anuke.mindustry.input.PlaceUtils.NormalizeResult; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; +import io.anuke.mindustry.world.Block; import io.anuke.mindustry.world.Tile; import io.anuke.ucore.core.Graphics; import io.anuke.ucore.core.Inputs; import io.anuke.ucore.core.Inputs.DeviceType; import io.anuke.ucore.core.KeyBinds; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.scene.utils.Cursors; +import io.anuke.ucore.core.Settings; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.scene.ui.layout.Unit; import io.anuke.ucore.util.Mathf; import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.input.CursorType.*; +import static io.anuke.mindustry.input.PlaceMode.*; public class DesktopInput extends InputHandler{ - float mousex, mousey; - float endx, endy; - private boolean enableHold = false; - private boolean beganBreak; - private boolean rotated = false, rotatedAlt, zoomed; - - @Override public float getCursorEndX(){ return endx; } - @Override public float getCursorEndY(){ return endy; } - @Override public float getCursorX(){ return Graphics.screen(mousex, mousey).x; } - @Override public float getCursorY(){ return Gdx.graphics.getHeight() - 1 - Graphics.screen(mousex, mousey).y; } - @Override public boolean drawPlace(){ return !beganBreak; } + private final String section; + //controller info + private float controlx, controly; + private boolean controlling; + /** + * Current cursor type. + */ + private CursorType cursorType = normal; - @Override - public void update(){ - if(player.isDead()) return; + /** + * Position where the player started dragging a line. + */ + private int selectX, selectY; + /** + * Whether selecting mode is active. + */ + private PlaceMode mode; + /** + * Animation scale for line. + */ + private float selectScale; - if(Inputs.keyRelease("select")){ - placeMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY()); - } + public DesktopInput(Player player){ + super(player); + this.section = "player_" + (player.playerIndex + 1); + } - if(Inputs.keyRelease("break") && !beganBreak){ - breakMode.released(getBlockX(), getBlockY(), getBlockEndX(), getBlockEndY()); - } + /** + * Draws a placement icon for a specific block. + */ + void drawPlace(int x, int y, Block block, int rotation){ + if(validPlace(x, y, block, rotation)){ + Draw.color(); - if((Inputs.keyTap("select") && recipe != null) || Inputs.keyTap("break")){ - Vector2 vec = Graphics.world(Gdx.input.getX(), Gdx.input.getY()); - mousex = vec.x; - mousey = vec.y; - } + TextureRegion[] regions = block.getBlockIcon(); - if(!Inputs.keyDown("select") && !Inputs.keyDown("break")){ - mousex = Graphics.mouseWorld().x; - mousey = Graphics.mouseWorld().y; - } - - endx = Gdx.input.getX(); - endy = Gdx.input.getY(); - - boolean controller = KeyBinds.getSection("default").device.type == DeviceType.controller; - - if(Inputs.getAxisActive("zoom") && (Inputs.keyDown("zoom_hold") || controller) - && !state.is(State.menu) && !ui.hasDialog()){ - if((!zoomed || !controller)) { - renderer.scaleCamera((int) Inputs.getAxis("zoom")); - } - zoomed = true; - }else{ - zoomed = false; - } - - if(!rotated) { - rotation += Inputs.getAxis("rotate_alt"); - rotated = true; - } - if(!Inputs.getAxisActive("rotate_alt")) rotated = false; - - if(!rotatedAlt) { - rotation += Inputs.getAxis("rotate"); - rotatedAlt = true; - } - if(!Inputs.getAxisActive("rotate")) rotatedAlt = false; - - rotation = Mathf.mod(rotation, 4); - - if(Inputs.keyDown("break")){ - breakMode = PlaceMode.areaDelete; - }else{ - breakMode = PlaceMode.hold; - } - - for(int i = 1; i <= 6 && i <= control.upgrades().getWeapons().size; i ++){ - if(Inputs.keyTap("weapon_" + i)){ - player.weaponLeft = player.weaponRight = control.upgrades().getWeapons().get(i - 1); - if(Net.active()) NetEvents.handleWeaponSwitch(); - ui.hudfrag.updateWeapons(); - } - } - - Tile cursor = world.tile(tilex(), tiley()); - Tile target = cursor == null ? null : cursor.isLinked() ? cursor.getLinked() : cursor; - boolean showCursor = false; - - if(recipe == null && target != null && !ui.hasMouse() && Inputs.keyDown("block_info") - && target.block().fullDescription != null){ - showCursor = true; - if(Inputs.keyTap("select")){ - ui.hudfrag.blockfrag.showBlockInfo(target.block()); - Cursors.restoreCursor(); + for(TextureRegion region : regions){ + Draw.rect(region, x * tilesize + block.offset(), y * tilesize + block.offset(), + region.getRegionWidth() * selectScale, region.getRegionHeight() * selectScale, block.rotate ? rotation * 90 : 0); } - } - - if(recipe == null && !ui.hasMouse() && Inputs.keyDown("block_logs")) { - showCursor = true; - if(Inputs.keyTap("select")){ - NetEvents.handleBlockLogRequest(getBlockX(), getBlockY()); - Timers.runTask(20f, () -> { - ui.hudfrag.blockfrag.showBlockLogs(getBlockX(), getBlockY()); - Cursors.restoreCursor(); - }); - } - } - - if(target != null && target.block().isConfigurable(target)){ - showCursor = true; + }else{ + Draw.color(Palette.remove); + Lines.square(x * tilesize + block.offset(), y * tilesize + block.offset(), block.size * tilesize / 2f); } - - if(target != null && Inputs.keyTap("select") && !ui.hasMouse()){ - if(target.block().isConfigurable(target)){ - ui.configfrag.showConfig(target); - }else if(!ui.configfrag.hasConfigMouse()){ - ui.configfrag.hideConfig(); - } + } - target.block().tapped(target); - if(Net.active()) NetEvents.handleBlockTap(target); - } - - if(Inputs.keyTap("break")){ - ui.configfrag.hideConfig(); - } - - if(Inputs.keyRelease("break")){ - beganBreak = false; - } + @Override + public boolean isDrawing(){ + return mode != none || recipe != null; + } - if(recipe != null && Inputs.keyTap("break")){ - beganBreak = true; - recipe = null; - } + @Override + public void drawOutlined(){ + Tile cursor = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); - //block breaking - if(enableHold && Inputs.keyDown("break") && cursor != null && validBreak(tilex(), tiley())){ - breaktime += Timers.delta(); - if(breaktime >= cursor.getBreakTime()){ - breakBlock(cursor.x, cursor.y, true); - breaktime = 0f; - } - }else{ - breaktime = 0f; - } + if(cursor == null) return; - if(recipe != null){ - showCursor = validPlace(tilex(), tiley(), control.input().recipe.result) && control.input().cursorNear(); - } + //draw selection(s) + if(mode == placing){ + NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, true, maxLength); - if(!ui.hasMouse()) { - if (showCursor) - Cursors.setHand(); - else - Cursors.restoreCursor(); - } + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ + int x = selectX + i * Mathf.sign(cursor.x - selectX) * Mathf.bool(result.isX()); + int y = selectY + i * Mathf.sign(cursor.y - selectY) * Mathf.bool(!result.isX()); - } + if(i + recipe.result.size > result.getLength() && recipe.result.rotate){ + Draw.color(!validPlace(x, y, recipe.result, result.rotation) ? Palette.remove : Palette.placeRotate); + Draw.grect("place-arrow", x * tilesize + recipe.result.offset(), + y * tilesize + recipe.result.offset(), result.rotation * 90 - 90); + } - public int tilex(){ - return (recipe != null && recipe.result.isMultiblock() && - recipe.result.width % 2 == 0) ? - Mathf.scl(Graphics.mouseWorld().x, tilesize) : Mathf.scl2(Graphics.mouseWorld().x, tilesize); - } + drawPlace(x, y, recipe.result, result.rotation); + } - public int tiley(){ - return (recipe != null && recipe.result.isMultiblock() && - recipe.result.height % 2 == 0) ? - Mathf.scl(Graphics.mouseWorld().y, tilesize) : Mathf.scl2(Graphics.mouseWorld().y, tilesize); - } + Draw.reset(); + }else if(mode == breaking){ + NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, selectX, selectY, cursor.x, cursor.y, false, maxLength, 1f); + NormalizeResult dresult = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, false, maxLength); + + Draw.color(Palette.remove); + + for(int x = dresult.x; x <= dresult.x2; x++){ + for(int y = dresult.y; y <= dresult.y2; y++){ + Tile tile = world.tile(x, y); + if(tile == null || !validBreak(tile.x, tile.y)) continue; + tile = tile.target(); + + Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize / 2f, 45 + 15); + } + } + + Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); + }else if(isPlacing()){ + if(recipe.result.rotate){ + Draw.color(!validPlace(cursor.x, cursor.y, recipe.result, rotation) ? Palette.remove : Palette.placeRotate); + Draw.grect("place-arrow", cursor.worldx() + recipe.result.offset(), + cursor.worldy() + recipe.result.offset(), rotation * 90 - 90); + } + drawPlace(cursor.x, cursor.y, recipe.result, rotation); + recipe.result.drawPlace(cursor.x, cursor.y, rotation, validPlace(cursor.x, cursor.y, recipe.result, rotation)); + } + + Draw.reset(); + } + + @Override + public void update(){ + if(Net.active() && Inputs.keyTap("player_list")){ + ui.listfrag.toggle(); + } + + if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return; + + if(recipe != null && !Settings.getBool("desktop-place-help", false)){ + ui.showInfo("Desktop controls have been changed.\nTo deselect a block or stop building, [accent]use the middle mouse button[]."); + Settings.putBool("desktop-place-help", true); + Settings.save(); + } + + player.isBoosting = Inputs.keyDown("dash"); + + //deslect if not placing + if(!isPlacing() && mode == placing){ + mode = none; + } + + if(player.isShooting && !canShoot()){ + player.isShooting = false; + } + + if(isPlacing()){ + cursorType = hand; + selectScale = Mathf.lerpDelta(selectScale, 1f, 0.2f); + }else{ + selectScale = 0f; + } + + boolean controller = KeyBinds.getSection(section).device.type == DeviceType.controller; + + //zoom and rotate things + if(Inputs.getAxisActive("zoom") && (Inputs.keyDown(section, "zoom_hold") || controller)){ + renderer.scaleCamera((int) Inputs.getAxisTapped(section, "zoom")); + } + + renderer.minimap().zoomBy(-(int) Inputs.getAxisTapped(section, "zoom_minimap")); + rotation = Mathf.mod(rotation + (int) Inputs.getAxisTapped(section, "rotate"), 4); + + Tile cursor = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); + + if(player.isDead()){ + cursorType = normal; + }else if(cursor != null){ + cursor = cursor.target(); + + cursorType = cursor.block().getCursor(cursor); + + if(isPlacing()){ + cursorType = hand; + } + + if(!isPlacing() && canMine(cursor)){ + cursorType = drill; + } + + if(canTapPlayer(Graphics.mouseWorld().x, Graphics.mouseWorld().y)){ + cursorType = unload; + } + } + + if(!ui.hasMouse()){ + cursorType.set(); + } + + cursorType = normal; + } + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ + if(player.isDead() || state.is(State.menu) || ui.hasDialog() || ui.hasMouse()) return false; + + Tile cursor = tileAt(screenX, screenY); + if(cursor == null) return false; + + float worldx = Graphics.world(screenX, screenY).x, worldy = Graphics.world(screenX, screenY).y; + + if(button == Buttons.LEFT){ //left = begin placing + if(isPlacing()){ + selectX = cursor.x; + selectY = cursor.y; + mode = placing; + }else{ + //only begin shooting if there's no cursor event + if(!tileTapped(cursor) && !tryTapPlayer(worldx, worldy) && player.getPlaceQueue().size == 0 && !droppingItem && + !tryBeginMine(cursor) && player.getMineTile() == null){ + player.isShooting = true; + } + } + }else if(button == Buttons.RIGHT){ //right = begin breaking + selectX = cursor.x; + selectY = cursor.y; + mode = breaking; + }else if(button == Buttons.MIDDLE){ //middle button = cancel placing + if(recipe == null){ + player.clearBuilding(); + } + + recipe = null; + mode = none; + } + + return false; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button){ + if(button == Buttons.LEFT){ + player.isShooting = false; + } + + if(player.isDead() || state.is(State.menu) || ui.hasDialog()) return false; + + Tile cursor = tileAt(screenX, screenY); + + if(cursor == null){ + mode = none; + return false; + } + + if(mode == placing){ //touch up while placing, place everything in selection + NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, true, maxLength); + + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ + int x = selectX + i * Mathf.sign(cursor.x - selectX) * Mathf.bool(result.isX()); + int y = selectY + i * Mathf.sign(cursor.y - selectY) * Mathf.bool(!result.isX()); + + rotation = result.rotation; + + tryPlaceBlock(x, y); + } + }else if(mode == breaking){ //touch up while breaking, break everything in selection + NormalizeResult result = PlaceUtils.normalizeArea(selectX, selectY, cursor.x, cursor.y, rotation, false, maxLength); + + for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){ + for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){ + int wx = selectX + x * Mathf.sign(cursor.x - selectX); + int wy = selectY + y * Mathf.sign(cursor.y - selectY); + + tryBreakBlock(wx, wy); + } + } + } + + tryDropItems(cursor.target(), Graphics.world(screenX, screenY).x, Graphics.world(screenX, screenY).y); + + mode = none; + + return false; + } + + @Override + public float getMouseX(){ + return !controlling ? control.gdxInput().getX() : controlx; + } + + @Override + public float getMouseY(){ + return !controlling ? control.gdxInput().getY() : controly; + } + + @Override + public boolean isCursorVisible(){ + return controlling; + } + + @Override + public void updateController(){ + boolean mousemove = Gdx.input.getDeltaX() > 1 || Gdx.input.getDeltaY() > 1; + + if(KeyBinds.getSection(section).device.type == DeviceType.controller && (!mousemove || player.playerIndex > 0)){ + if(player.playerIndex > 0){ + controlling = true; + } + /* + if(Inputs.keyTap(section,"select")){ + Inputs.getProcessor().touchDown((int)getMouseX(), (int)getMouseY(), player.playerIndex, Buttons.LEFT); + } + + if(Inputs.keyRelease(section,"select")){ + Inputs.getProcessor().touchUp((int)getMouseX(), (int)getMouseY(), player.playerIndex, Buttons.LEFT); + }*/ + + float xa = Inputs.getAxis(section, "cursor_x"); + float ya = Inputs.getAxis(section, "cursor_y"); + + if(Math.abs(xa) > controllerMin || Math.abs(ya) > controllerMin){ + float scl = Settings.getInt("sensitivity", 100) / 100f * Unit.dp.scl(1f); + controlx += xa * baseControllerSpeed * scl; + controly -= ya * baseControllerSpeed * scl; + controlling = true; + + if(player.playerIndex == 0){ + Gdx.input.setCursorCatched(true); + } + + Inputs.getProcessor().touchDragged((int) getMouseX(), (int) getMouseY(), player.playerIndex); + } + + controlx = Mathf.clamp(controlx, 0, Gdx.graphics.getWidth()); + controly = Mathf.clamp(controly, 0, Gdx.graphics.getHeight()); + }else{ + controlling = false; + Gdx.input.setCursorCatched(false); + } + + if(!controlling){ + controlx = control.gdxInput().getX(); + controly = control.gdxInput().getY(); + } + } - @Override - public boolean keyDown(int keycode) { - return super.keyDown(keycode); - } } diff --git a/core/src/io/anuke/mindustry/input/GestureHandler.java b/core/src/io/anuke/mindustry/input/GestureHandler.java deleted file mode 100644 index e7544ed613..0000000000 --- a/core/src/io/anuke/mindustry/input/GestureHandler.java +++ /dev/null @@ -1,116 +0,0 @@ -package io.anuke.mindustry.input; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.input.GestureDetector.GestureAdapter; -import com.badlogic.gdx.math.Vector2; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Inputs; -import io.anuke.ucore.scene.ui.layout.Unit; -import io.anuke.ucore.util.Mathf; - -import static io.anuke.mindustry.Vars.*; - -public class GestureHandler extends GestureAdapter{ - AndroidInput input; - Vector2 pinch1 = new Vector2(-1, -1), pinch2 = pinch1.cpy(); - Vector2 vector = new Vector2(); - float initzoom = -1; - boolean zoomed = false; - - GestureHandler(AndroidInput input){ - this.input = input; - } - - @Override - public boolean longPress(float x, float y){ - return false; - } - - @Override - public boolean tap (float x, float y, int count, int button) { - if(ui.hasMouse() || input.brokeBlock) return false; - - if(!control.input().placeMode.pan || control.input().recipe == null){ - input.mousex = x; - input.mousey = y; - - if(control.input().recipe == null) - control.input().breakMode.tapped(input.getBlockX(), input.getBlockY()); - else - control.input().placeMode.tapped(input.getBlockX(), input.getBlockY()); - } - return false; - } - - @Override - public boolean pan(float x, float y, float deltaX, float deltaY){ - if(control.showCursor() && !Inputs.keyDown("select")) return false; - - if((!control.showCursor() && !(control.input().recipe != null - && control.input().placeMode.lockCamera && state.inventory.hasItems(control.input().recipe.requirements)) && - !(control.input().recipe == null && control.input().breakMode.lockCamera)) && !ui.hasMouse(x, y)){ - float dx = deltaX*Core.camera.zoom/Core.cameraScale, dy = deltaY*Core.camera.zoom/Core.cameraScale; - player.x -= dx; - player.y += dy; - player.targetAngle = Mathf.atan2(dx, -dy) + 180f; - }else if(control.input().placeMode.lockCamera && (control.input().placeMode.pan && control.input().recipe != null)){ - input.mousex += deltaX; - input.mousey += deltaY; - } - - return false; - } - - @Override - public boolean pinch (Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { - if(control.input().recipe == null && !control.input().breakMode.lockCamera) - return false; - - if(pinch1.x < 0){ - pinch1.set(initialPointer1); - pinch2.set(initialPointer2); - } - - Vector2 vec = (vector.set(pointer1).add(pointer2).scl(0.5f)).sub(pinch1.add(pinch2).scl(0.5f)); - - player.x -= vec.x*Core.camera.zoom/Core.cameraScale; - player.y += vec.y*Core.camera.zoom/Core.cameraScale; - - pinch1.set(pointer1); - pinch2.set(pointer2); - - return false; - } - - @Override - public boolean zoom(float initialDistance, float distance){ - if(initzoom < 0){ - initzoom = initialDistance; - } - - if(Math.abs(distance - initzoom) > Unit.dp.scl(100f) && !zoomed){ - int amount = (distance > initzoom ? 1 : -1); - renderer.scaleCamera(Math.round(Unit.dp.scl(amount))); - initzoom = distance; - zoomed = true; - return true; - } - - return false; - } - - @Override - public void pinchStop () { - initzoom = -1; - pinch2.set(pinch1.set(-1, -1)); - zoomed = false; - } - - int touches(){ - int sum = 0; - for(int i = 0; i < 10; i ++){ - if(Gdx.input.isTouched(i)) sum++; - } - return sum; - } -} diff --git a/core/src/io/anuke/mindustry/input/InputHandler.java b/core/src/io/anuke/mindustry/input/InputHandler.java index 6e1a32b932..b3f094a889 100644 --- a/core/src/io/anuke/mindustry/input/InputHandler.java +++ b/core/src/io/anuke/mindustry/input/InputHandler.java @@ -1,141 +1,363 @@ package io.anuke.mindustry.input; import com.badlogic.gdx.InputAdapter; -import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.Vector2; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.effect.ItemDrop; +import io.anuke.mindustry.entities.effect.ItemTransfer; +import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest; +import io.anuke.mindustry.gen.CallBlocks; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.mindustry.resource.ItemStack; -import io.anuke.mindustry.resource.Recipe; +import io.anuke.mindustry.net.ValidateException; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.ui.fragments.OverlayFragment; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Placement; +import io.anuke.mindustry.world.Build; import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; +import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.core.Inputs; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.scene.Group; +import io.anuke.ucore.util.Angles; import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; import static io.anuke.mindustry.Vars.*; public abstract class InputHandler extends InputAdapter{ - public float breaktime = 0; - public Recipe recipe; - public int rotation; - public PlaceMode placeMode = mobile ? PlaceMode.cursor : PlaceMode.hold; - public PlaceMode breakMode = mobile ? PlaceMode.none : PlaceMode.holdDelete; - public PlaceMode lastPlaceMode = placeMode; - public PlaceMode lastBreakMode = breakMode; + /** + * Used for dropping items. + */ + final static float playerSelectRange = mobile ? 17f : 11f; + /** + * Maximum line length. + */ + final static int maxLength = 100; + final static Translator stackTrns = new Translator(); + /** + * Distance on the back from where items originate. + */ + final static float backTrns = 3f; - public abstract void update(); - public abstract float getCursorX(); - public abstract float getCursorY(); - public abstract float getCursorEndX(); - public abstract float getCursorEndY(); - public int getBlockX(){ return Mathf.sclb(Graphics.world(getCursorX(), getCursorY()).x, tilesize, round2()); } - public int getBlockY(){ return Mathf.sclb(Graphics.world(getCursorX(), getCursorY()).y, tilesize, round2()); } - public int getBlockEndX(){ return Mathf.sclb(Graphics.world(getCursorEndX(), getCursorEndY()).x, tilesize, round2()); } - public int getBlockEndY(){ return Mathf.sclb(Graphics.world(getCursorEndX(), getCursorEndY()).y, tilesize, round2()); } - public void resetCursor(){} - public boolean drawPlace(){ return true; } - - public boolean onConfigurable(){ - Tile tile = world.tile(getBlockX(), getBlockY()); - return tile != null && (tile.block().isConfigurable(tile) || (tile.isLinked() && tile.getLinked().block().isConfigurable(tile))); - } - - public boolean cursorNear(){ - return Vector2.dst(player.x, player.y, getBlockX() * tilesize, getBlockY() * tilesize) <= placerange || - state.mode.infiniteResources; - } - - public boolean tryPlaceBlock(int x, int y, boolean sound){ - if(recipe != null && - validPlace(x, y, recipe.result) && !ui.hasMouse() && cursorNear() && - state.inventory.hasItems(recipe.requirements)){ - - placeBlock(x, y, recipe.result, rotation, true, sound); - - for(ItemStack stack : recipe.requirements){ - state.inventory.removeItem(stack); - } + public final Player player; + public final String section; + public final OverlayFragment frag = new OverlayFragment(this); - return true; - } - return false; - } - - public boolean tryDeleteBlock(int x, int y, boolean sound){ - if(cursorNear() && validBreak(x, y)){ - breakBlock(x, y, sound); - return true; - } - return false; - } - - public boolean round2(){ - return !(recipe != null && recipe.result.isMultiblock() && recipe.result.height % 2 == 0); - } - - public boolean validPlace(int x, int y, Block type){ - - if(!type.isMultiblock() && control.tutorial().active() && - control.tutorial().showBlock()){ - - GridPoint2 point = control.tutorial().getPlacePoint(); - int rotation = control.tutorial().getPlaceRotation(); - Block block = control.tutorial().getPlaceBlock(); - - if(type != block || point.x != x - world.getCore().x || point.y != y - world.getCore().y - || (rotation != -1 && rotation != this.rotation)){ - return false; - } - }else if(control.tutorial().active()){ - return false; - } + public Recipe recipe; + public int rotation; + public boolean droppingItem; - return Placement.validPlace(x, y, type); - } - - public boolean validBreak(int x, int y){ - if(control.tutorial().active()){ - - if(control.tutorial().showBlock()){ - GridPoint2 point = control.tutorial().getPlacePoint(); - int rotation = control.tutorial().getPlaceRotation(); - Block block = control.tutorial().getPlaceBlock(); - - if(block != Blocks.air || point.x != x - world.getCore().x || point.y != y - world.getCore().y - || (rotation != -1 && rotation != this.rotation)){ - return false; - } - }else{ - return false; - } - } - - return Placement.validBreak(x, y); - } - - public void placeBlock(int x, int y, Block result, int rotation, boolean effects, boolean sound){ - if(!Net.client()){ //is server or singleplayer - Placement.placeBlock(x, y, result, rotation, effects, sound); - } + public InputHandler(Player player){ + this.player = player; + this.section = "player_" + (player.playerIndex + 1); + Timers.run(1f, () -> frag.build(Core.scene.getRoot())); + } - if(Net.active()){ - NetEvents.handlePlace(x, y, result, rotation); - } + //methods to override - if(!Net.client()){ - Tile tile = world.tile(x, y); - if(tile != null) result.placed(tile); - } - } + @Remote(targets = Loc.client, called = Loc.server, in = In.entities) + public static void dropItem(Player player, float angle){ + if(Net.server() && !player.inventory.hasItem()){ + throw new ValidateException(player, "Player cannot drop an item."); + } - public void breakBlock(int x, int y, boolean sound){ - if(!Net.client()) - Placement.breakBlock(x, y, true, sound); + ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle); + player.inventory.clearItem(); + } + + @Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks) + public static void transferInventory(Player player, Tile tile){ + if(Net.server() && (!player.inventory.hasItem() || player.isTransferring)){ + throw new ValidateException(player, "Player cannot transfer an item."); + } + + threads.run(() -> { + if(player == null || tile.entity == null) return; + + player.isTransferring = true; + + ItemStack stack = player.inventory.getItem(); + int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player); + + boolean clear = stack.amount == accepted; + int sent = Mathf.clamp(accepted / 4, 1, 8); + int removed = accepted / sent; + int[] remaining = {accepted, accepted}; + + for(int i = 0; i < sent; i++){ + boolean end = i == sent - 1; + Timers.run(i * 3, () -> { + tile.block().getStackOffset(stack.item, tile, stackTrns); + + ItemTransfer.create(stack.item, + player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns), + new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> { + + tile.block().handleStack(stack.item, removed, tile, player); + remaining[1] -= removed; + + if(end && remaining[1] > 0){ + tile.block().handleStack(stack.item, remaining[1], tile, player); + } + }); + + stack.amount -= removed; + remaining[0] -= removed; + + if(end){ + stack.amount -= remaining[0]; + if(clear){ + player.inventory.clearItem(); + } + player.isTransferring = false; + } + }); + } + }); + } + + @Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks) + public static void onTileTapped(Player player, Tile tile){ + if(tile == null || player == null) return; + tile.block().tapped(tile, player); + } + + public void update(){ + + } + + public float getMouseX(){ + return control.gdxInput().getX(); + } + + public float getMouseY(){ + return control.gdxInput().getY(); + } + + public void resetCursor(){ + + } + + public boolean isCursorVisible(){ + return false; + } + + public void buildUI(Group group){ + + } + + public void updateController(){ + + } + + public void drawOutlined(){ + + } + + public void drawTop(){ + + } + + public boolean isDrawing(){ + return false; + } + + /** + * Handles tile tap events that are not platform specific. + */ + boolean tileTapped(Tile tile){ + tile = tile.target(); + + boolean consumed = false, showedInventory = false, showedConsume = false; + + //check if tapped block is configurable + if(tile.block().configurable && tile.getTeam() == player.getTeam()){ + consumed = true; + if(((!frag.config.isShown() && tile.block().shouldShowConfigure(tile, player)) //if the config fragment is hidden, show + //alternatively, the current selected block can 'agree' to switch config tiles + || (frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)))){ + frag.config.showConfig(tile); + } + //otherwise... + }else if(!frag.config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor + //then, if it's shown and the current block 'agrees' to hide, hide it. + if(frag.config.isShown() && frag.config.getSelectedTile().block().onConfigureTileTapped(frag.config.getSelectedTile(), tile)){ + consumed = true; + frag.config.hideConfig(); + } + } + + //call tapped event + if(tile.getTeam() == player.getTeam()){ + CallBlocks.onTileTapped(player, tile); + } + + //consume tap event if necessary + if(tile.getTeam() == player.getTeam() && tile.block().consumesTap){ + consumed = true; + }else if(tile.getTeam() == player.getTeam() && tile.block().synthetic() && !consumed){ + if(tile.block().hasItems && tile.entity.items.total() > 0){ + frag.inv.showFor(tile); + consumed = true; + showedInventory = true; + } + + if(tile.block().consumes.hasAny()){ + frag.consume.show(tile); + consumed = true; + showedConsume = true; + } + } + + if(!showedInventory){ + frag.inv.hide(); + } + + if(!showedConsume){ + frag.consume.hide(); + } + + return consumed; + } + + /** + * Tries to select the player to drop off items, returns true if successful. + */ + boolean tryTapPlayer(float x, float y){ + if(canTapPlayer(x, y)){ + droppingItem = true; + return true; + } + return false; + } + + boolean canTapPlayer(float x, float y){ + return Vector2.dst(x, y, player.x, player.y) <= playerSelectRange && player.inventory.hasItem(); + } + + /** + * Tries to begin mining a tile, returns true if successful. + */ + boolean tryBeginMine(Tile tile){ + if(canMine(tile)){ + //if a block is clicked twice, reset it + player.setMineTile(player.getMineTile() == tile ? null : tile); + return true; + } + return false; + } + + boolean canMine(Tile tile){ + return !ui.hasMouse() + && tile.floor().drops != null && tile.floor().drops.item.hardness <= player.mech.drillPower + && !tile.floor().playerUnmineable + && player.inventory.canAcceptItem(tile.floor().drops.item) + && Units.getClosestEnemy(player.getTeam(), tile.worldx(), tile.worldy(), 40f, e -> true) == null //don't being mining when an enemy is near + && tile.block() == Blocks.air && player.distanceTo(tile.worldx(), tile.worldy()) <= Player.mineDistance; + } + + /** + * Returns the tile at the specified MOUSE coordinates. + */ + Tile tileAt(float x, float y){ + Vector2 vec = Graphics.world(x, y); + if(isPlacing()){ + vec.sub(recipe.result.offset(), recipe.result.offset()); + } + return world.tileWorld(vec.x, vec.y); + } + + public boolean isPlacing(){ + return recipe != null; + } + + public float mouseAngle(float x, float y){ + return Graphics.world(getMouseX(), getMouseY()).sub(x, y).angle(); + } + + public void remove(){ + Inputs.removeProcessor(this); + frag.remove(); + } + + public boolean canShoot(){ + return recipe == null && !ui.hasMouse() && !onConfigurable() && !isDroppingItem(); + } + + public boolean onConfigurable(){ + return false; + } + + public boolean isDroppingItem(){ + return droppingItem; + } + + public void tryDropItems(Tile tile, float x, float y){ + if(!droppingItem || !player.inventory.hasItem() || canTapPlayer(x, y)){ + droppingItem = false; + return; + } + + droppingItem = false; + + ItemStack stack = player.inventory.getItem(); + + if(tile.block().acceptStack(stack.item, stack.amount, tile, player) > 0 && tile.block().hasItems){ + CallBlocks.transferInventory(player, tile); + }else{ + CallEntity.dropItem(player.angleTo(x, y)); + } + } + + public boolean cursorNear(){ + return true; + } + + public void tryPlaceBlock(int x, int y){ + if(recipe != null && validPlace(x, y, recipe.result, rotation) && cursorNear()){ + placeBlock(x, y, recipe, rotation); + } + } + + public void tryBreakBlock(int x, int y){ + if(cursorNear() && validBreak(x, y)){ + breakBlock(x, y); + } + } + + public boolean validPlace(int x, int y, Block type, int rotation){ + for(Tile tile : state.teams.get(player.getTeam()).cores){ + if(tile.distanceTo(x * tilesize, y * tilesize) < coreBuildRange){ + return Build.validPlace(player.getTeam(), x, y, type, rotation) && + Vector2.dst(player.x, player.y, x * tilesize, y * tilesize) < Player.placeDistance; + } + } + + return false; + } + + public boolean validBreak(int x, int y){ + return Build.validBreak(player.getTeam(), x, y); + } + + public void placeBlock(int x, int y, Recipe recipe, int rotation){ + //todo multiplayer support + player.addBuildRequest(new BuildRequest(x, y, rotation, recipe)); + } + + public void breakBlock(int x, int y){ + + //todo multiplayer support + Tile tile = world.tile(x, y).target(); + player.addBuildRequest(new BuildRequest(tile.x, tile.y)); + } - if(Net.active()){ - NetEvents.handleBreak(x, y); - } - } } diff --git a/core/src/io/anuke/mindustry/input/MobileInput.java b/core/src/io/anuke/mindustry/input/MobileInput.java new file mode 100644 index 0000000000..4fc313a31c --- /dev/null +++ b/core/src/io/anuke/mindustry/input/MobileInput.java @@ -0,0 +1,782 @@ +package io.anuke.mindustry.input; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.input.GestureDetector; +import com.badlogic.gdx.input.GestureDetector.GestureListener; +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Align; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.core.GameState.State; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.Units; +import io.anuke.mindustry.entities.traits.TargetTrait; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.graphics.Shaders; +import io.anuke.mindustry.input.PlaceUtils.NormalizeDrawResult; +import io.anuke.mindustry.input.PlaceUtils.NormalizeResult; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.ui.dialogs.FloatingDialog; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.*; +import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.graphics.Lines; +import io.anuke.ucore.scene.Group; +import io.anuke.ucore.scene.builders.imagebutton; +import io.anuke.ucore.scene.builders.table; +import io.anuke.ucore.scene.event.Touchable; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.input.PlaceMode.*; + +public class MobileInput extends InputHandler implements GestureListener{ + /** Maximum speed the player can pan. */ + private static final float maxPanSpeed = 1.3f; + private static Rectangle r1 = new Rectangle(), r2 = new Rectangle(); + /** Distance to edge of screen to start panning. */ + private final float edgePan = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(60f); + + //gesture data + private Vector2 pinch1 = new Vector2(-1, -1), pinch2 = pinch1.cpy(); + private Vector2 vector = new Vector2(); + private float initzoom = -1; + private boolean zoomed = false; + /** Set of completed guides. */ + private ObjectSet guides = new ObjectSet<>(); + + /** Position where the player started dragging a line. */ + private int lineStartX, lineStartY; + + /** Animation scale for line. */ + private float lineScale; + /** Animation data for crosshair. */ + private float crosshairScale; + private TargetTrait lastTarget; + + /** List of currently selected tiles to place. */ + private Array selection = new Array<>(); + /** Place requests to be removed. */ + private Array removals = new Array<>(); + /** Whether or not the player is currently shifting all placed tiles. */ + private boolean selecting; + /** Whether the player is currently in line-place mode. */ + private boolean lineMode; + /** Current place mode. */ + private PlaceMode mode = none; + /** Whether no recipe was available when switching to break mode. */ + private Recipe lastRecipe; + /** Last placed request. Used for drawing block overlay. */ + private PlaceRequest lastPlaced; + + public MobileInput(Player player){ + super(player); + Inputs.addProcessor(new GestureDetector(20, 0.5f, 0.4f, 0.15f, this)); + } + + //region utility methods + + /** Check and assign targets for a specific position. */ + void checkTargets(float x, float y){ + synchronized(Entities.entityLock){ + Unit unit = Units.getClosestEnemy(player.getTeam(), x, y, 20f, u -> true); + + if(unit != null){ + player.target = unit; + }else{ + Tile tile = world.tileWorld(x, y); + if(tile != null) tile = tile.target(); + + if(tile != null && state.teams.areEnemies(player.getTeam(), tile.getTeam())){ + player.target = tile.entity; + } + } + } + } + + /** Returns whether this tile is in the list of requests, or at least colliding with one. */ + boolean hasRequest(Tile tile){ + return getRequest(tile) != null; + } + + /** Returns whether this block overlaps any selection requests. */ + boolean checkOverlapPlacement(int x, int y, Block block){ + r2.setSize(block.size * tilesize); + r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset()); + + for(PlaceRequest req : selection){ + Tile other = req.tile(); + + if(other == null || req.remove) continue; + + r1.setSize(req.recipe.result.size * tilesize); + r1.setCenter(other.worldx() + req.recipe.result.offset(), other.worldy() + req.recipe.result.offset()); + + if(r2.overlaps(r1)){ + return true; + } + } + return false; + } + + /** Returns the selection request that overlaps this tile, or null. */ + PlaceRequest getRequest(Tile tile){ + r2.setSize(tilesize); + r2.setCenter(tile.worldx(), tile.worldy()); + + for(PlaceRequest req : selection){ + Tile other = req.tile(); + + if(other == null) continue; + + if(!req.remove){ + r1.setSize(req.recipe.result.size * tilesize); + r1.setCenter(other.worldx() + req.recipe.result.offset(), other.worldy() + req.recipe.result.offset()); + + if(r2.overlaps(r1)){ + return req; + } + }else{ + + r1.setSize(other.block().size * tilesize); + r1.setCenter(other.worldx() + other.block().offset(), other.worldy() + other.block().offset()); + + if(r2.overlaps(r1)){ + return req; + } + } + } + return null; + } + + void removeRequest(PlaceRequest request){ + selection.removeValue(request, true); + removals.add(request); + } + + void drawRequest(PlaceRequest request){ + Tile tile = request.tile(); + + if(!request.remove){ + //draw placing request + float offset = request.recipe.result.offset(); + TextureRegion[] regions = request.recipe.result.getBlockIcon(); + + Draw.alpha(Mathf.clamp((1f - request.scale) / 0.5f)); + Draw.tint(Color.WHITE, Palette.breakInvalid, request.redness); + + for(TextureRegion region : regions){ + Draw.rect(region, tile.worldx() + offset, tile.worldy() + offset, + region.getRegionWidth() * request.scale, region.getRegionHeight() * request.scale, + request.recipe.result.rotate ? request.rotation * 90 : 0); + } + }else{ + Draw.color(Palette.remove); + //draw removing request + Lines.poly(tile.drawx(), tile.drawy(), 4, tile.block().size * tilesize / 2f * request.scale, 45 + 15); + } + } + + void showGuide(String type){ + if(!guides.contains(type) && !Settings.getBool(type, false)){ + FloatingDialog dialog = new FloatingDialog("$text." + type + ".title"); + dialog.addCloseButton(); + dialog.content().left(); + dialog.content().add("$text." + type).growX().wrap(); + dialog.content().row(); + dialog.content().addCheck("$text.showagain", false, checked -> { + Settings.putBool(type, checked); + Settings.save(); + }).growX().left().get().left(); + dialog.show(); + guides.add(type); + } + } + + //endregion + + //region UI and drawing + + @Override + public void buildUI(Group group){ + + //Create confirm/cancel table + new table(){{ + abottom().aleft(); + + new table("pane"){{ + margin(5); + defaults().size(60f); + + touchable(Touchable.enabled); + + //Add a cancel button + new imagebutton("icon-cancel", 16 * 2f, () -> { + mode = none; + recipe = null; + }); + + row(); + + //Add an accept button, which places everything. + new imagebutton("icon-check", 16 * 2f, () -> { + for(PlaceRequest request : selection){ + Tile tile = request.tile(); + + //actually place/break all selected blocks + if(tile != null){ + if(!request.remove){ + rotation = request.rotation; + recipe = request.recipe; + tryPlaceBlock(tile.x, tile.y); + }else{ + tryBreakBlock(tile.x, tile.y); + } + } + } + + //move all current requests to removal array to they fade out + removals.addAll(selection); + selection.clear(); + selecting = false; + }).cell.disabled(i -> selection.size == 0); + + row(); + + //Add a rotate button + new imagebutton("icon-arrow", 16 * 2f, () -> rotation = Mathf.mod(rotation + 1, 4)) + .update(i -> { + i.getImage().setRotation(rotation * 90); + i.getImage().setOrigin(Align.center); + }).cell.disabled(i -> recipe == null || !recipe.result.rotate); + }}.visible(() -> mode != none).end(); + + row(); + + new table("pane"){{ + margin(5); + defaults().size(60f); + + touchable(Touchable.enabled); + + //Add a break button. + new imagebutton("icon-break", "toggle", 16 * 2f, () -> { + mode = mode == breaking ? recipe == null ? none : placing : breaking; + lastRecipe = recipe; + if(mode == breaking){ + showGuide("deconstruction"); + } + }).update(l -> l.setChecked(mode == breaking)); + }}.end(); + + new table("pane"){{ + margin(5); + defaults().size(60f); + + touchable(Touchable.enabled); + + //Add a 'cancel building' button. + new imagebutton("icon-cancel", 16 * 2f, player::clearBuilding); + + visible(() -> player.getPlaceQueue().size > 0); + }}.left().colspan(2).end(); + }}.visible(() -> !state.is(State.menu)).end(); + } + + @Override + public boolean isDrawing(){ + return selection.size > 0 || removals.size > 0 || lineMode || player.target != null || mode != PlaceMode.none; + } + + @Override + public boolean isPlacing(){ + return super.isPlacing() && mode == placing; + } + + @Override + public void drawOutlined(){ + + //Draw.color(Palette.placing); + //Lines.poly(player.x, player.y, 100, Player.placeDistance); + //Draw.color(); + + Shaders.mix.color.set(Palette.accent); + Graphics.shader(Shaders.mix); + + //draw removals + for(PlaceRequest request : removals){ + Tile tile = request.tile(); + + if(tile == null) continue; + + request.scale = Mathf.lerpDelta(request.scale, 0f, 0.2f); + request.redness = Mathf.lerpDelta(request.redness, 0f, 0.2f); + + drawRequest(request); + } + + //draw list of requests + for(PlaceRequest request : selection){ + Tile tile = request.tile(); + + if(tile == null) continue; + + if((!request.remove && validPlace(tile.x, tile.y, request.recipe.result, request.rotation)) + || (request.remove && validBreak(tile.x, tile.y))){ + request.scale = Mathf.lerpDelta(request.scale, 1f, 0.2f); + request.redness = Mathf.lerpDelta(request.redness, 0f, 0.2f); + }else{ + request.scale = Mathf.lerpDelta(request.scale, 0.5f, 0.1f); + request.redness = Mathf.lerpDelta(request.redness, 1f, 0.2f); + } + + + drawRequest(request); + + //draw last placed request + if(!request.remove && request == lastPlaced && request.recipe != null){ + request.recipe.result.drawPlace(tile.x, tile.y, rotation, validPlace(tile.x, tile.y, request.recipe.result, rotation)); + } + } + + Graphics.shader(); + + Draw.color(Palette.accent); + + //Draw lines + if(lineMode){ + Tile tile = tileAt(control.gdxInput().getX(), control.gdxInput().getY()); + + if(tile != null){ + + //draw placing + if(mode == placing && recipe != null){ + NormalizeDrawResult dresult = PlaceUtils.normalizeDrawArea(recipe.result, lineStartX, lineStartY, tile.x, tile.y, true, maxLength, lineScale); + + Lines.rect(dresult.x, dresult.y, dresult.x2 - dresult.x, dresult.y2 - dresult.y); + + NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, true, maxLength); + + //go through each cell and draw the block to place if valid + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ + int x = lineStartX + i * Mathf.sign(tile.x - lineStartX) * Mathf.bool(result.isX()); + int y = lineStartY + i * Mathf.sign(tile.y - lineStartY) * Mathf.bool(!result.isX()); + + if(!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)){ + Draw.color(); + + TextureRegion[] regions = recipe.result.getBlockIcon(); + + for(TextureRegion region : regions){ + Draw.rect(region, x * tilesize + recipe.result.offset(), y * tilesize + recipe.result.offset(), + region.getRegionWidth() * lineScale, region.getRegionHeight() * lineScale, recipe.result.rotate ? result.rotation * 90 : 0); + } + }else{ + Draw.color(Palette.breakInvalid); + Lines.square(x * tilesize + recipe.result.offset(), y * tilesize + recipe.result.offset(), recipe.result.size * tilesize / 2f); + } + } + + }else if(mode == breaking){ + //draw breaking + NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, lineStartX, lineStartY, tile.x, tile.y, false, maxLength, 1f); + NormalizeResult dresult = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, false, maxLength); + + Draw.color(Palette.remove); + + Draw.alpha(0.6f); + Draw.alpha(1f); + + for(int x = dresult.x; x <= dresult.x2; x++){ + for(int y = dresult.y; y <= dresult.y2; y++){ + Tile other = world.tile(x, y); + if(other == null || !validBreak(other.x, other.y)) continue; + other = other.target(); + + Lines.poly(other.drawx(), other.drawy(), 4, other.block().size * tilesize / 2f, 45 + 15); + } + } + + Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y); + + } + } + } + + //draw targeting crosshair + if(player.target != null){ + if(player.target != lastTarget){ + crosshairScale = 0f; + lastTarget = player.target; + } + + crosshairScale = Mathf.lerpDelta(crosshairScale, 1f, 0.2f); + + Draw.color(Palette.remove); + Lines.stroke(1f); + + float radius = Interpolation.swingIn.apply(crosshairScale); + + Lines.poly(player.target.getX(), player.target.getY(), 4, 7f * radius, Timers.time() * 1.5f); + Lines.spikes(player.target.getX(), player.target.getY(), 3f * radius, 6f * radius, 4, Timers.time() * 1.5f); + } + + Draw.reset(); + } + + //endregion + + //region input events + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button){ + if(state.is(State.menu)) return false; + + //get tile on cursor + Tile cursor = tileAt(screenX, screenY); + + float worldx = Graphics.world(screenX, screenY).x, worldy = Graphics.world(screenX, screenY).y; + + //ignore off-screen taps + if(cursor == null || ui.hasMouse(screenX, screenY)) return false; + + //only begin selecting if the tapped block is a request + selecting = hasRequest(cursor) && isPlacing() && mode == placing; + + //call tap events + if(pointer == 0 && !selecting && mode == none){ + if(!tileTapped(cursor.target()) && !tryTapPlayer(worldx, worldy)){ + tryBeginMine(cursor); + } + } + + return false; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button){ + + //place down a line if in line mode + if(lineMode){ + Tile tile = tileAt(screenX, screenY); + + if(tile == null) return false; + + if(mode == placing && recipe != null){ + + //normalize area + NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, true, 100); + + rotation = result.rotation; + + //place blocks on line + for(int i = 0; i <= result.getLength(); i += recipe.result.size){ + int x = lineStartX + i * Mathf.sign(tile.x - lineStartX) * Mathf.bool(result.isX()); + int y = lineStartY + i * Mathf.sign(tile.y - lineStartY) * Mathf.bool(!result.isX()); + + if(!checkOverlapPlacement(x, y, recipe.result) && validPlace(x, y, recipe.result, result.rotation)){ + PlaceRequest request = new PlaceRequest(x * tilesize, y * tilesize, recipe, result.rotation); + request.scale = 1f; + selection.add(request); + } + } + + //reset last placed for convenience + lastPlaced = null; + + }else if(mode == breaking){ + //normalize area + NormalizeResult result = PlaceUtils.normalizeArea(lineStartX, lineStartY, tile.x, tile.y, rotation, false, maxLength); + + //break everything in area + for(int x = 0; x <= Math.abs(result.x2 - result.x); x++){ + for(int y = 0; y <= Math.abs(result.y2 - result.y); y++){ + int wx = lineStartX + x * Mathf.sign(tile.x - lineStartX); + int wy = lineStartY + y * Mathf.sign(tile.y - lineStartY); + + Tile tar = world.tile(wx, wy); + + if(tar == null) continue; + + tar = tar.target(); + + if(!hasRequest(world.tile(tar.x, tar.y)) && validBreak(tar.x, tar.y)){ + PlaceRequest request = new PlaceRequest(tar.worldx(), tar.worldy()); + request.scale = 1f; + selection.add(request); + } + } + } + } + + lineMode = false; + }else{ + Tile tile = tileAt(screenX, screenY); + + if(tile == null) return false; + + tryDropItems(tile.target(), Graphics.world(screenX, screenY).x, Graphics.world(screenX, screenY).y); + } + return false; + } + + @Override + public boolean longPress(float x, float y){ + if(state.is(State.menu) || mode == none) return false; + + //get tile on cursor + Tile cursor = tileAt(x, y); + + //ignore off-screen taps + if(cursor == null || ui.hasMouse(x, y)) return false; + + //remove request if it's there + //long pressing enables line mode otherwise + lineStartX = cursor.x; + lineStartY = cursor.y; + lineMode = true; + + if(mode == breaking){ + Effects.effect(Fx.tapBlock, cursor.worldx(), cursor.worldy(), 1f); + }else{ + Effects.effect(Fx.tapBlock, cursor.worldx() + recipe.result.offset(), cursor.worldy() + recipe.result.offset(), recipe.result.size); + } + + return false; + } + + @Override + public boolean tap(float x, float y, int count, int button){ + if(state.is(State.menu) || lineMode) return false; + + float worldx = Graphics.world(x, y).x, worldy = Graphics.world(x, y).y; + + checkTargets(worldx, worldy); + + //get tile on cursor + Tile cursor = tileAt(x, y); + + //ignore off-screen taps + if(cursor == null || ui.hasMouse(x, y)) return false; + + //remove if request present + if(hasRequest(cursor)){ + removeRequest(getRequest(cursor)); + }else if(mode == placing && isPlacing() && validPlace(cursor.x, cursor.y, recipe.result, rotation) && !checkOverlapPlacement(cursor.x, cursor.y, recipe.result)){ + //add to selection queue if it's a valid place position + selection.add(lastPlaced = new PlaceRequest(cursor.worldx(), cursor.worldy(), recipe, rotation)); + }else if(mode == breaking && validBreak(cursor.target().x, cursor.target().y) && !hasRequest(cursor.target())){ + //add to selection queue if it's a valid BREAK position + cursor = cursor.target(); + selection.add(new PlaceRequest(cursor.worldx(), cursor.worldy())); + }else{ //else, try and carry units + if(player.getCarry() != null){ + player.dropCarry(); //drop off unit + }else{ + Unit unit = Units.getClosest(player.getTeam(), Graphics.world(x, y).x, Graphics.world(x, y).y, 4f, u -> !u.isFlying() && u.getMass() <= player.mech.carryWeight); + + if(unit != null){ + player.moveTarget = unit; + Effects.effect(Fx.select, unit.getX(), unit.getY()); + } + } + } + + return false; + } + + @Override + public void update(){ + + //reset state when not placing + if(mode == none){ + selecting = false; + lineMode = false; + removals.addAll(selection); + selection.clear(); + } + + if(lineMode && mode == placing && recipe == null){ + lineMode = false; + } + + //if there is no mode and there's a recipe, switch to placing + if(recipe != null && mode == none){ + mode = placing; + } + + if(recipe != null){ + showGuide("construction"); + } + + //automatically switch to placing after a new recipe is selected + if(lastRecipe != recipe && mode == breaking && recipe != null){ + mode = placing; + lastRecipe = recipe; + } + + if(lineMode){ + lineScale = Mathf.lerpDelta(lineScale, 1f, 0.1f); + + //When in line mode, pan when near screen edges automatically + if(Gdx.input.isTouched(0) && lineMode){ + float screenX = Graphics.mouse().x, screenY = Graphics.mouse().y; + + float panX = 0, panY = 0; + + if(screenX <= edgePan){ + panX = -(edgePan - screenX); + } + + if(screenX >= Gdx.graphics.getWidth() - edgePan){ + panX = (screenX - Gdx.graphics.getWidth()) + edgePan; + } + + if(screenY <= edgePan){ + panY = -(edgePan - screenY); + } + + if(screenY >= Gdx.graphics.getHeight() - edgePan){ + panY = (screenY - Gdx.graphics.getHeight()) + edgePan; + } + + vector.set(panX, panY).scl((Core.camera.viewportWidth * Core.camera.zoom) / Gdx.graphics.getWidth()); + vector.limit(maxPanSpeed); + + //pan view + Core.camera.position.x += vector.x; + Core.camera.position.y += vector.y; + } + }else{ + lineScale = 0f; + } + + //remove place requests that have disappeared + for(int i = removals.size - 1; i >= 0; i--){ + PlaceRequest request = removals.get(i); + + if(request.scale <= 0.0001f){ + removals.removeIndex(i); + i--; + } + } + } + + @Override + public boolean pan(float x, float y, float deltaX, float deltaY){ + if(ui.hasMouse()) return false; + + //can't pan in line mode with one finger or while dropping items! + if((lineMode && !Gdx.input.isTouched(1)) || droppingItem){ + return false; + } + + float dx = deltaX * Core.camera.zoom / Core.cameraScale, dy = deltaY * Core.camera.zoom / Core.cameraScale; + + if(selecting){ //pan all requests + for(PlaceRequest req : selection){ + if(req.remove) continue; //don't shift removal requests + req.x += dx; + req.y -= dy; + } + }else{ + //pan player + Core.camera.position.x -= dx; + Core.camera.position.y += dy; + } + + return false; + } + + @Override + public boolean panStop(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ + if(pinch1.x < 0){ + pinch1.set(initialPointer1); + pinch2.set(initialPointer2); + } + + pinch1.set(pointer1); + pinch2.set(pointer2); + + return false; + } + + @Override + public boolean zoom(float initialDistance, float distance){ + if(initzoom < 0){ + initzoom = initialDistance; + } + + if(Math.abs(distance - initzoom) > io.anuke.ucore.scene.ui.layout.Unit.dp.scl(100f) && !zoomed){ + int amount = (distance > initzoom ? 1 : -1); + renderer.scaleCamera(Math.round(io.anuke.ucore.scene.ui.layout.Unit.dp.scl(amount))); + initzoom = distance; + zoomed = true; + return true; + } + + return false; + } + + @Override + public void pinchStop(){ + initzoom = -1; + pinch2.set(pinch1.set(-1, -1)); + zoomed = false; + } + + @Override + public boolean touchDown(float x, float y, int pointer, int button){ + return false; + } + + @Override + public boolean fling(float velocityX, float velocityY, int button){ + return false; + } + + //endregion + + class PlaceRequest{ + float x, y; + Recipe recipe; + int rotation; + boolean remove; + + //animation variables + float scale; + float redness; + + PlaceRequest(float x, float y, Recipe recipe, int rotation){ + this.x = x; + this.y = y; + this.recipe = recipe; + this.rotation = rotation; + this.remove = false; + } + + PlaceRequest(float x, float y){ + this.x = x; + this.y = y; + this.remove = true; + } + + Tile tile(){ + return world.tileWorld(x - (recipe == null ? 0 : recipe.result.offset()), y - (recipe == null ? 0 : recipe.result.offset())); + } + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/input/PlaceMode.java b/core/src/io/anuke/mindustry/input/PlaceMode.java index 6a6b633332..8eb6bb5105 100644 --- a/core/src/io/anuke/mindustry/input/PlaceMode.java +++ b/core/src/io/anuke/mindustry/input/PlaceMode.java @@ -1,417 +1,5 @@ package io.anuke.mindustry.input; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.Colors; -import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.math.Vector2; -import io.anuke.mindustry.ui.fragments.ToolFragment; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.util.Bundles; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Translator; - -import static io.anuke.mindustry.Vars.*; - -public enum PlaceMode{ - cursor{ - { - shown = true; - lockCamera = true; - pan = true; - } - - public void draw(int tilex, int tiley, int endx, int endy){ - float x = tilex * tilesize; - float y = tiley * tilesize; - - boolean valid = control.input().validPlace(tilex, tiley, control.input().recipe.result) && (mobile || control.input().cursorNear()); - - Vector2 offset = control.input().recipe.result.getPlaceOffset(); - - float si = MathUtils.sin(Timers.time() / 6f) + 1.5f; - - renderer.getBlocks().handlePreview(control.input().recipe.result, control.input().recipe.result.rotate ? control.input().rotation * 90 : 0f, x + offset.x, y + offset.y, tilex, tiley); - - Draw.color(valid ? Colors.get("place") : Colors.get("placeInvalid")); - Lines.stroke(2f); - Lines.crect(x + offset.x, y + offset.y, tilesize * control.input().recipe.result.width + si, - tilesize * control.input().recipe.result.height + si); - - control.input().recipe.result.drawPlace(tilex, tiley, control.input().rotation, valid); - - if(control.input().recipe.result.rotate){ - - Draw.color(Colors.get("placeRotate")); - tr.trns(control.input().rotation * 90, 7, 0); - Lines.line(x, y, x + tr.x, y + tr.y); - } - } - - public void tapped(int tilex, int tiley){ - control.input().tryPlaceBlock(tilex, tiley, true); - } - }, - touch{ - { - shown = true; - lockCamera = false; - showRotate = true; - showCancel = true; - } - - public void tapped(int x, int y){ - control.input().tryPlaceBlock(x, y, true); - } - }, - none{ - { - delete = true; - shown = true; - both = true; - } - }, - holdDelete{ - { - delete = true; - shown = true; - both = true; - } - - public void draw(int tilex, int tiley, int endx, int endy){ - Tile tile = world.tile(tilex, tiley); - - if(tile != null && control.input().validBreak(tilex, tiley)){ - if(tile.isLinked()) - tile = tile.getLinked(); - float fin = control.input().breaktime / tile.getBreakTime(); - - if(mobile && control.input().breaktime > 0){ - Draw.color(Colors.get("breakStart"), Colors.get("break"), fin); - Lines.poly(tile.drawx(), tile.drawy(), 25, 4 + (1f - fin) * 26); - } - Draw.reset(); - } - } - }, - touchDelete{ - { - shown = true; - lockCamera = false; - showRotate = true; - showCancel = true; - delete = true; - } - - public void tapped(int x, int y){ - control.input().tryDeleteBlock(x, y, true); - } - }, - areaDelete{ - int maxlen = 20; - int tilex; - int tiley; - int endx; - int endy; - - { - shown = true; - lockCamera = true; - delete = true; - } - - public void draw(int tilex, int tiley, int endx, int endy){ - float t = tilesize; - - process(tilex, tiley, endx, endy); - - tilex = this.tilex; tiley = this.tiley; - endx = this.endx; endy = this.endy; - float x = this.tilex * t, y = this.tiley * t, - x2 = this.endx * t, y2 = this.endy * t; - - if(x2 >= x){ - x -= t/2; - x2 += t/2; - } - - if(y2 >= y){ - y -= t/2; - y2 += t/2; - } - - Draw.color(Colors.get("break")); - Lines.stroke(1f); - for(int cx = tilex; cx <= endx; cx ++){ - for(int cy = tiley; cy <= endy; cy ++){ - Tile tile = world.tile(cx, cy); - if(tile != null && tile.getLinked() != null) - tile = tile.getLinked(); - if(tile != null && control.input().validBreak(tile.x, tile.y)){ - Lines.crect(tile.drawx(), tile.drawy(), - tile.block().width * t, tile.block().height * t); - } - } - } - - Lines.stroke(2f); - Draw.color(control.input().cursorNear() ? Colors.get("break") : Colors.get("breakInvalid")); - Lines.rect(x, y, x2 - x, y2 - y); - Draw.alpha(0.3f); - Draw.crect("blank", x, y, x2 - x, y2 - y); - Draw.reset(); - } - - public void released(int tilex, int tiley, int endx, int endy){ - process(tilex, tiley, endx, endy); - tilex = this.tilex; tiley = this.tiley; - endx = this.endx; endy = this.endy; - - if(mobile){ - ToolFragment t = ui.toolfrag; - if(!t.confirming || t.px != tilex || t.py != tiley || t.px2 != endx || t.py2 != endy) { - t.confirming = true; - t.px = tilex; - t.py = tiley; - t.px2 = endx; - t.py2 = endy; - return; - } - } - - boolean first = true; - - for(int cx = tilex; cx <= endx; cx ++){ - for(int cy = tiley; cy <= endy; cy ++){ - if(control.input().tryDeleteBlock(cx, cy, first)){ - first = false; - } - } - } - } - - void process(int tilex, int tiley, int endx, int endy){ - - if(Math.abs(endx - tilex) > maxlen){ - endx = Mathf.sign(endx - tilex) * maxlen + tilex; - } - - if(Math.abs(endy - tiley) > maxlen){ - endy = Mathf.sign(endy - tiley) * maxlen + tiley; - } - - if(endx < tilex){ - int t = endx; - endx = tilex; - tilex = t; - } - if(endy < tiley){ - int t = endy; - endy = tiley; - tiley = t; - } - - this.endx = endx; - this.endy = endy; - this.tilex = tilex; - this.tiley = tiley; - } - }, - hold{ - int maxlen = 20; - int tilex; - int tiley; - int endx; - int endy; - int rotation; - - { - lockCamera = true; - shown = true; - showCancel = true; - showRotate = true; - } - - public void draw(int tilex, int tiley, int endx, int endy){ - if(mobile && !Gdx.input.isTouched(0) && !control.showCursor()){ - return; - } - - float t = tilesize; - Block block = control.input().recipe.result; - Vector2 offset = block.getPlaceOffset(); - - process(tilex, tiley, endx, endy); - int tx = tilex, ty = tiley, ex = endx, ey = endy; - tilex = this.tilex; tiley = this.tiley; - endx = this.endx; endy = this.endy; - float x = this.tilex * t, y = this.tiley * t, - x2 = this.endx * t, y2 = this.endy * t; - - if(x2 >= x){ - x -= block.width * t/2; - x2 += block.width * t/2; - } - - if(y2 >= y){ - y -= block.height * t/2; - y2 += block.height * t/2; - } - - x += offset.x; - y += offset.y; - x2 += offset.x; - y2 += offset.y; - - if(tilex == endx && tiley == endy){ - cursor.draw(tilex, tiley, endx, endy); - }else{ - Lines.stroke(2f); - Draw.color(control.input().cursorNear() ? "place" : "placeInvalid"); - Lines.rect(x, y, x2 - x, y2 - y); - Draw.alpha(0.3f); - Draw.crect("blank", x, y, x2 - x, y2 - y); - - int amount = 1; - boolean isX = Math.abs(endx - tilex) >= Math.abs(endy - tiley); - - for(int cx = 0; cx <= Math.abs(endx - tilex); cx += (isX ? 0 : 1)){ - for(int cy = 0; cy <= Math.abs(endy - tiley); cy += (isX ? 1 : 0)){ - - int px = tx + cx * Mathf.sign(ex - tx), - py = ty + cy * Mathf.sign(ey - ty); - - //step by the block size if it's valid - if(control.input().validPlace(px, py, control.input().recipe.result) && state.inventory.hasItems(control.input().recipe.requirements, amount)){ - - renderer.getBlocks().handlePreview(control.input().recipe.result, block.rotate ? rotation * 90 : 0f, px * t + offset.x, py * t + offset.y, px, py); - - if(isX){ - cx += block.width; - }else{ - cy += block.width; - } - amount ++; - }else{ //otherwise, step by 1 until it is valid - if(control.input().cursorNear()){ - Lines.stroke(2f); - Draw.color("placeInvalid"); - Lines.crect( - px * t + (isX ? 0 : offset.x) + (ex < tx && isX && block.width > 1 ? t : 0) - (block.width == 3 && ex > tx && isX ? t : 0), - py * t + (isX ? offset.y : 0) + (ey < ty && !isX && block.height > 1 ? t : 0) - (block.height == 3 && ey > ty && !isX ? t : 0), - t*(isX ? 1 : block.width), - t*(isX ? block.height : 1)); - Draw.color("place"); - } - - if(isX){ - cx += 1; - }else{ - cy += 1; - } - } - } - } - - if(control.input().recipe.result.rotate){ - float cx = tx * t, cy = ty * t; - Lines.stroke(2f); - Draw.color(Colors.get("placeRotate")); - tr.trns(rotation * 90, 7, 0); - Lines.line(cx, cy, cx + tr.x, cy + tr.y); - } - Draw.reset(); - } - } - - public void released(int tilex, int tiley, int endx, int endy){ - process(tilex, tiley, endx, endy); - - control.input().rotation = this.rotation; - - boolean first = true; - for(int x = 0; x <= Math.abs(this.endx - this.tilex); x ++){ - for(int y = 0; y <= Math.abs(this.endy - this.tiley); y ++){ - if(control.input().tryPlaceBlock( - tilex + x * Mathf.sign(endx - tilex), - tiley + y * Mathf.sign(endy - tiley), first)){ - first = false; - } - - } - } - } - - void process(int tilex, int tiley, int endx, int endy){ - if(Math.abs(tilex - endx) > Math.abs(tiley - endy)){ - endy = tiley; - }else{ - endx = tilex; - } - - if(Math.abs(endx - tilex) > maxlen){ - endx = Mathf.sign(endx - tilex) * maxlen + tilex; - } - - if(Math.abs(endy - tiley) > maxlen){ - endy = Mathf.sign(endy - tiley) * maxlen + tiley; - } - - if(endx > tilex) - rotation = 0; - else if(endx < tilex) - rotation = 2; - else if(endy > tiley) - rotation = 1; - else if(endy < tiley) - rotation = 3; - else - rotation = control.input().rotation; - - if(endx < tilex){ - int t = endx; - endx = tilex; - tilex = t; - } - if(endy < tiley){ - int t = endy; - endy = tiley; - tiley = t; - } - - this.endx = endx; - this.endy = endy; - this.tilex = tilex; - this.tiley = tiley; - } - }; - public boolean lockCamera; - public boolean pan = false; - public boolean shown = false; - public boolean showRotate; - public boolean showCancel; - public boolean delete = false; - public boolean both = false; - - private static final Translator tr = new Translator(); - - public void draw(int tilex, int tiley, int endx, int endy){ - - } - - public void released(int tilex, int tiley, int endx, int endy){ - - } - - public void tapped(int x, int y){ - - } - - @Override - public String toString(){ - return Bundles.get("placemode."+name().toLowerCase()+".name"); - } -} \ No newline at end of file +enum PlaceMode{ + none, breaking, placing +} diff --git a/core/src/io/anuke/mindustry/input/PlaceUtils.java b/core/src/io/anuke/mindustry/input/PlaceUtils.java new file mode 100644 index 0000000000..b74b5b31a8 --- /dev/null +++ b/core/src/io/anuke/mindustry/input/PlaceUtils.java @@ -0,0 +1,147 @@ +package io.anuke.mindustry.input; + +import io.anuke.mindustry.world.Block; +import io.anuke.ucore.util.Mathf; + +import static io.anuke.mindustry.Vars.tilesize; + +public class PlaceUtils{ + private static final NormalizeResult result = new NormalizeResult(); + private static final NormalizeDrawResult drawResult = new NormalizeDrawResult(); + + /** + * Normalizes a placement area and returns the result, ready to be used for drawing a rectangle. + * Returned x2 and y2 will always be greater than x and y. + * + * @param block block that will be drawn + * @param startx starting X coordinate + * @param starty starting Y coordinate + * @param endx ending X coordinate + * @param endy ending Y coordinate + * @param snap whether to snap to a line + * @param maxLength maximum length of area + */ + public static NormalizeDrawResult normalizeDrawArea(Block block, int startx, int starty, int endx, int endy, boolean snap, int maxLength, float scaling){ + normalizeArea(startx, starty, endx, endy, 0, snap, maxLength); + + float offset = block.offset(); + + drawResult.x = result.x * tilesize; + drawResult.y = result.y * tilesize; + drawResult.x2 = result.x2 * tilesize; + drawResult.y2 = result.y2 * tilesize; + + drawResult.x -= block.size * scaling * tilesize / 2; + drawResult.x2 += block.size * scaling * tilesize / 2; + + + drawResult.y -= block.size * scaling * tilesize / 2; + drawResult.y2 += block.size * scaling * tilesize / 2; + + drawResult.x += offset; + drawResult.y += offset; + drawResult.x2 += offset; + drawResult.y2 += offset; + + return drawResult; + } + + /** + * Normalizes a placement area and returns the result. + * Returned x2 and y2 will always be greater than x and y. + * + * @param tilex starting X coordinate + * @param tiley starting Y coordinate + * @param endx ending X coordinate + * @param endy ending Y coordinate + * @param snap whether to snap to a line + * @param rotation placement rotation + * @param maxLength maximum length of area + */ + public static NormalizeResult normalizeArea(int tilex, int tiley, int endx, int endy, int rotation, boolean snap, int maxLength){ + + if(snap){ + if(Math.abs(tilex - endx) > Math.abs(tiley - endy)){ + endy = tiley; + }else{ + endx = tilex; + } + + if(Math.abs(endx - tilex) > maxLength){ + endx = Mathf.sign(endx - tilex) * maxLength + tilex; + } + + if(Math.abs(endy - tiley) > maxLength){ + endy = Mathf.sign(endy - tiley) * maxLength + tiley; + } + } + + int dx = endx - tilex, dy = endy - tiley; + + if(Math.abs(dx) > Math.abs(dy)){ + if(dx >= 0){ + rotation = 0; + }else{ + rotation = 2; + } + }else if(Math.abs(dx) < Math.abs(dy)){ + if(dy >= 0){ + rotation = 1; + }else{ + rotation = 3; + } + } + + if(endx < tilex){ + int t = endx; + endx = tilex; + tilex = t; + } + if(endy < tiley){ + int t = endy; + endy = tiley; + tiley = t; + } + + result.x2 = endx; + result.y2 = endy; + result.x = tilex; + result.y = tiley; + result.rotation = rotation; + + return result; + } + + static class NormalizeDrawResult{ + float x, y, x2, y2; + } + + static class NormalizeResult{ + int x, y, x2, y2, rotation; + + boolean isX(){ + return Math.abs(x2 - x) > Math.abs(y2 - y); + } + + /** + * Returns length of greater edge of the selection. + */ + int getLength(){ + return Math.max(x2 - x, y2 - y); + } + + /** + * Returns the X position of a specific index along this area as a line. + */ + int getScaledX(int i){ + return x + (x2 - x > y2 - y ? i : 0); + } + + /** + * Returns the Y position of a specific index along this area as a line. + */ + int getScaledY(int i){ + return y + (x2 - x > y2 - y ? 0 : i); + } + } +} diff --git a/core/src/io/anuke/mindustry/io/BlockLoader.java b/core/src/io/anuke/mindustry/io/BlockLoader.java deleted file mode 100644 index fead464606..0000000000 --- a/core/src/io/anuke/mindustry/io/BlockLoader.java +++ /dev/null @@ -1,144 +0,0 @@ -package io.anuke.mindustry.io; - -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.ObjectIntMap; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.blocks.*; -import io.anuke.ucore.util.Log; - -public class BlockLoader { - static final ObjectIntMap defaultMap = map( - "air", 0, - "blockpart", 1, - "deepwater", 2, - "water", 3, - "lava", 4, - "oil", 5, - "stone", 6, - "blackstone", 7, - "iron", 8, - "coal", 9, - "titanium", 10, - "uranium", 11, - "dirt", 12, - "sand", 13, - "ice", 14, - "snow", 15, - "grass", 16, - "sandblock", 17, - "snowblock", 18, - "stoneblock", 19, - "blackstoneblock", 20, - "grassblock", 21, - "mossblock", 22, - "shrub", 23, - "rock", 24, - "icerock", 25, - "blackrock", 26, - "dirtblock", 27, - "stonewall", 28, - "ironwall", 29, - "steelwall", 30, - "titaniumwall", 31, - "duriumwall", 32, - "compositewall", 33, - "steelwall-large", 34, - "titaniumwall-large", 35, - "duriumwall-large", 36, - "titaniumshieldwall", 37, - "repairturret", 38, - "megarepairturret", 39, - "shieldgenerator", 40, - "door", 41, - "door-large", 42, - "conduit", 43, - "pulseconduit", 44, - "liquidrouter", 45, - "conveyor", 46, - "steelconveyor", 47, - "poweredconveyor", 48, - "router", 49, - "junction", 50, - "conveyortunnel", 51, - "liquidjunction", 52, - "liquiditemjunction", 53, - "powerbooster", 54, - "powerlaser", 55, - "powerlaserrouter", 56, - "powerlasercorner", 57, - "teleporter", 58, - "sorter", 59, - "core", 60, - "pump", 61, - "fluxpump", 62, - "smelter", 63, - "crucible", 64, - "coalpurifier", 65, - "titaniumpurifier", 66, - "oilrefinery", 67, - "stoneformer", 68, - "lavasmelter", 69, - "stonedrill", 70, - "irondrill", 71, - "coaldrill", 72, - "uraniumdrill", 73, - "titaniumdrill", 74, - "omnidrill", 75, - "coalgenerator", 76, - "thermalgenerator", 77, - "combustiongenerator", 78, - "rtgenerator", 79, - "nuclearreactor", 80, - "turret", 81, - "doubleturret", 82, - "machineturret", 83, - "shotgunturret", 84, - "flameturret", 85, - "sniperturret", 86, - "mortarturret", 87, - "laserturret", 88, - "waveturret", 89, - "plasmaturret", 90, - "chainturret", 91, - "titancannon", 92, - "playerspawn", 93, - "enemyspawn", 94 - ); - static final IntMap blockmap = new IntMap<>(); - - public static void load(){ - - Block[] blockClasses = { - Blocks.air, - DefenseBlocks.compositewall, - DistributionBlocks.conduit, - ProductionBlocks.coaldrill, - WeaponBlocks.chainturret, - SpecialBlocks.enemySpawn - //add any new block sections here - }; - - for(String string : defaultMap.keys()){ - Block block = Block.getByName(string); - blockmap.put(defaultMap.get(string, -1), block); - } - - for(Block block : Block.getAllBlocks()){ - block.init(); - } - - Log.info("Total blocks loaded: {0}", Block.getAllBlocks().size); - } - - public static Block getByOldID(int id){ - return blockmap.get(id); - } - - private static ObjectIntMap map(Object... objects){ - ObjectIntMap map = new ObjectIntMap<>(); - for(int i = 0; i < objects.length/2; i ++){ - map.put((String)objects[i*2], (int)objects[i*2+1]); - } - return map; - } -} diff --git a/core/src/io/anuke/mindustry/io/BundleLoader.java b/core/src/io/anuke/mindustry/io/BundleLoader.java index a66da1b7b5..bc97c089e5 100644 --- a/core/src/io/anuke/mindustry/io/BundleLoader.java +++ b/core/src/io/anuke/mindustry/io/BundleLoader.java @@ -3,21 +3,21 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.I18NBundle; -import io.anuke.mindustry.core.Platform; +import io.anuke.mindustry.Vars; import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Settings; +import io.anuke.ucore.core.Timers; import io.anuke.ucore.util.Log; import java.util.Locale; import static io.anuke.mindustry.Vars.headless; -public class BundleLoader { - private static final boolean externalBundle = false; +public class BundleLoader{ public static void load(){ Settings.defaults("locale", "default"); - Settings.load("io.anuke.moment"); + Settings.load(headless ? "io.anuke.mindustry.server" : "io.anuke.mindustry"); loadBundle(); } @@ -27,10 +27,10 @@ public class BundleLoader { return Locale.getDefault(); }else{ Locale lastLocale; - if (loc.contains("_")) { + if(loc.contains("_")){ String[] split = loc.split("_"); lastLocale = new Locale(split[0], split[1]); - } else { + }else{ lastLocale = new Locale(loc); } @@ -40,20 +40,20 @@ public class BundleLoader { private static void loadBundle(){ I18NBundle.setExceptionOnMissingKey(false); + try{ + //try loading external bundle + FileHandle handle = Gdx.files.local("bundle"); - if(externalBundle){ - try { - FileHandle handle = Gdx.files.local("bundle"); + Locale locale = Locale.ENGLISH; + Core.bundle = I18NBundle.createBundle(handle, locale); - Locale locale = Locale.ENGLISH; - Core.bundle = I18NBundle.createBundle(handle, locale); - }catch (Exception e){ - Log.err(e); - Platform.instance.showError("Failed to find bundle!\nMake sure you have bundle.properties in the same directory\nas the jar file.\n\nIf the problem persists, try running it through the command prompt:\n" + - "Hold left-shift, then right click and select 'open command prompt here'.\nThen, type in 'java -jar mindustry.jar' without quotes."); - Gdx.app.exit(); + Log.info("NOTE: external translation bundle has been loaded."); + if(!headless){ + Timers.run(10f, () -> Vars.ui.showInfo("Note: You have successfully loaded an external translation bundle.")); } - }else{ + }catch(Throwable e){ + //no external bundle found + FileHandle handle = Gdx.files.internal("bundles/bundle"); Locale locale = getLocale(); @@ -61,5 +61,6 @@ public class BundleLoader { if(!headless) Log.info("Got locale: {0}", locale); Core.bundle = I18NBundle.createBundle(handle, locale); } + } } diff --git a/core/src/io/anuke/mindustry/io/Changelogs.java b/core/src/io/anuke/mindustry/io/Changelogs.java index c799647f1c..85d482ad00 100644 --- a/core/src/io/anuke/mindustry/io/Changelogs.java +++ b/core/src/io/anuke/mindustry/io/Changelogs.java @@ -8,7 +8,7 @@ import io.anuke.ucore.function.Consumer; import static io.anuke.mindustry.Vars.releasesURL; -public class Changelogs { +public class Changelogs{ public static void getChangelog(Consumer> success, Consumer fail){ Net.http(releasesURL, "GET", result -> { @@ -30,7 +30,7 @@ public class Changelogs { public final String name, description; public final int id, build; - public VersionInfo(String name, String description, int id, int build) { + public VersionInfo(String name, String description, int id, int build){ this.name = name; this.description = description; this.id = id; @@ -38,7 +38,7 @@ public class Changelogs { } @Override - public String toString() { + public String toString(){ return "VersionInfo{" + "name='" + name + '\'' + ", description='" + description + '\'' + diff --git a/core/src/io/anuke/mindustry/io/Map.java b/core/src/io/anuke/mindustry/io/Map.java new file mode 100644 index 0000000000..1959862c79 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/Map.java @@ -0,0 +1,45 @@ +package io.anuke.mindustry.io; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.ucore.function.Supplier; + +import java.io.InputStream; + +public class Map{ + /** + * Internal map name. This is the filename, without any extensions. + */ + public final String name; + /** + * Whether this is a custom map. + */ + public final boolean custom; + /** + * Metadata. Author description, display name, etc. + */ + public final MapMeta meta; + /** + * Supplies a new input stream with the data of this map. + */ + public final Supplier stream; + /** + * Preview texture. + */ + public Texture texture; + + public Map(String name, MapMeta meta, boolean custom, Supplier streamSupplier){ + this.name = name; + this.custom = custom; + this.meta = meta; + this.stream = streamSupplier; + } + + public Map(String unknownName, int width, int height){ + this(unknownName, new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); + } + + public String getDisplayName(){ + return meta.tags.get("name", name); + } +} diff --git a/core/src/io/anuke/mindustry/io/MapIO.java b/core/src/io/anuke/mindustry/io/MapIO.java new file mode 100644 index 0000000000..5b10c9d7ed --- /dev/null +++ b/core/src/io/anuke/mindustry/io/MapIO.java @@ -0,0 +1,178 @@ +package io.anuke.mindustry.io; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.utils.IntIntMap; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectMap.Entry; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.MapTileData.DataPosition; +import io.anuke.mindustry.io.MapTileData.TileDataMarker; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.ColorMapper; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Reads and writes map files. + */ +public class MapIO{ + private static final int version = 0; + private static IntIntMap defaultBlockMap = new IntIntMap(); + + private static void loadDefaultBlocks(){ + for(Block block : Block.all()){ + defaultBlockMap.put(block.id, block.id); + } + } + + public static Pixmap generatePixmap(MapTileData data){ + Pixmap pixmap = new Pixmap(data.width(), data.height(), Format.RGBA8888); + data.position(0, 0); + + TileDataMarker marker = data.newDataMarker(); + Color color = new Color(); + + for(int y = 0; y < data.height(); y++){ + for(int x = 0; x < data.width(); x++){ + data.read(marker); + Block floor = Block.getByID(marker.floor); + Block wall = Block.getByID(marker.wall); + int wallc = ColorMapper.getBlockColor(wall); + if(wallc == 0 && (wall.update || wall.solid || wall.breakable)) wallc = Team.all[marker.team].intColor; + wallc = wallc == 0 ? ColorMapper.getBlockColor(floor) : wallc; + if(marker.elevation > 0){ + float scaling = 1f + marker.elevation / 8f; + color.set(wallc); + color.mul(scaling, scaling, scaling, 1f); + wallc = Color.rgba8888(color); + } + pixmap.drawPixel(x, pixmap.getHeight() - 1 - y, wallc); + } + } + + data.position(0, 0); + + return pixmap; + } + + public static MapTileData readPixmap(Pixmap pixmap){ + MapTileData data = new MapTileData(pixmap.getWidth(), pixmap.getHeight()); + + for(int x = 0; x < data.width(); x++){ + for(int y = 0; y < data.height(); y++){ + Block block = ColorMapper.getByColor(pixmap.getPixel(y, pixmap.getWidth() - 1 - x)); + + if(block == null){ + data.write(x, y, DataPosition.floor, (byte) Blocks.stone.id); + }else{ + data.write(x, y, DataPosition.floor, (byte) block.id); + } + + data.write(x, y, DataPosition.wall, (byte) Blocks.air.id); + } + } + + return data; + } + + public static void writeMap(OutputStream stream, ObjectMap tags, MapTileData data) throws IOException{ + if(defaultBlockMap == null){ + loadDefaultBlocks(); + } + + MapMeta meta = new MapMeta(version, tags, data.width(), data.height(), defaultBlockMap); + + DataOutputStream ds = new DataOutputStream(stream); + + writeMapMeta(ds, meta); + ds.write(data.toArray()); + + ds.close(); + } + + /** + * Reads tile data, skipping meta. + */ + public static MapTileData readTileData(DataInputStream stream, boolean readOnly) throws IOException{ + MapMeta meta = readMapMeta(stream); + return readTileData(stream, meta, readOnly); + } + + + /** + * Does not skip meta. Call after reading meta. + */ + public static MapTileData readTileData(DataInputStream stream, MapMeta meta, boolean readOnly) throws IOException{ + byte[] bytes = new byte[stream.available()]; + stream.readFully(bytes); + return new MapTileData(bytes, meta.width, meta.height, meta.blockMap, readOnly); + } + + /** + * Reads tile data, skipping meta tags. + */ + public static MapTileData readTileData(Map map, boolean readOnly){ + try(DataInputStream ds = new DataInputStream(map.stream.get())){ + return MapIO.readTileData(ds, readOnly); + }catch(IOException e){ + throw new RuntimeException(e); + } + } + + public static MapMeta readMapMeta(DataInputStream stream) throws IOException{ + ObjectMap tags = new ObjectMap<>(); + IntIntMap map = new IntIntMap(); + + int version = stream.readInt(); + + byte tagAmount = stream.readByte(); + + for(int i = 0; i < tagAmount; i++){ + String name = stream.readUTF(); + String value = stream.readUTF(); + tags.put(name, value); + } + + short blocks = stream.readShort(); + for(int i = 0; i < blocks; i++){ + short id = stream.readShort(); + String name = stream.readUTF(); + Block block = Block.getByName(name); + if(block == null){ + //Log.info("Map load info: No block with name {0} found.", name); + block = Blocks.air; + } + map.put(id, block.id); + } + + int width = stream.readShort(); + int height = stream.readShort(); + + return new MapMeta(version, tags, width, height, map); + } + + public static void writeMapMeta(DataOutputStream stream, MapMeta meta) throws IOException{ + stream.writeInt(meta.version); + stream.writeByte((byte) meta.tags.size); + + for(Entry entry : meta.tags.entries()){ + stream.writeUTF(entry.key); + stream.writeUTF(entry.value); + } + + stream.writeShort(Block.all().size); + for(Block block : Block.all()){ + stream.writeShort(block.id); + stream.writeUTF(block.name); + } + + stream.writeShort(meta.width); + stream.writeShort(meta.height); + } +} diff --git a/core/src/io/anuke/mindustry/io/MapMeta.java b/core/src/io/anuke/mindustry/io/MapMeta.java new file mode 100644 index 0000000000..ab554148c5 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/MapMeta.java @@ -0,0 +1,35 @@ +package io.anuke.mindustry.io; + +import com.badlogic.gdx.utils.IntIntMap; +import com.badlogic.gdx.utils.ObjectMap; + +public class MapMeta{ + public final int version; + public final ObjectMap tags; + public final int width, height; + public final IntIntMap blockMap; + + public MapMeta(int version, ObjectMap tags, int width, int height, IntIntMap blockMap){ + this.version = version; + this.tags = tags; + this.width = width; + this.height = height; + this.blockMap = blockMap; + } + + public String author(){ + return tags.get("author", "unknown"); + } + + public String description(){ + return tags.get("description", "unknown"); + } + + public String name(){ + return tags.get("name", "unknown"); + } + + public boolean hasOreGen(){ + return !tags.get("oregen", "0").equals("0"); + } +} diff --git a/core/src/io/anuke/mindustry/io/MapTileData.java b/core/src/io/anuke/mindustry/io/MapTileData.java new file mode 100644 index 0000000000..1a2d307d40 --- /dev/null +++ b/core/src/io/anuke/mindustry/io/MapTileData.java @@ -0,0 +1,139 @@ +package io.anuke.mindustry.io; + +import com.badlogic.gdx.utils.IntIntMap; +import io.anuke.ucore.util.Bits; + +import java.nio.ByteBuffer; + +public class MapTileData{ + /** + * Tile size: 4 bytes.
+ * 0: ground tile
+ * 1: wall tile
+ * 2: rotation + team
+ * 3: link (x/y)
+ * 4: elevation
+ */ + private final static int TILE_SIZE = 5; + + private final ByteBuffer buffer; + private final int width, height; + private final boolean readOnly; + + private IntIntMap map; + + public MapTileData(int width, int height){ + this.width = width; + this.height = height; + this.map = null; + this.readOnly = false; + buffer = ByteBuffer.allocate(width * height * TILE_SIZE); + } + + public MapTileData(byte[] bytes, int width, int height, IntIntMap mapping, boolean readOnly){ + buffer = ByteBuffer.wrap(bytes); + this.width = width; + this.height = height; + this.map = mapping; + this.readOnly = readOnly; + + if(mapping != null && !readOnly){ + for(int i = 0; i < width * height; i++){ + TileDataMarker marker = new TileDataMarker(); + read(marker); + buffer.position(i * TILE_SIZE); + write(marker); + } + buffer.position(0); + this.map = null; + } + } + + public byte[] toArray(){ + return buffer.array(); + } + + public int width(){ + return width; + } + + public int height(){ + return height; + } + + /** + * Write a byte to a specific position. + */ + public void write(int x, int y, DataPosition position, byte data){ + buffer.put((x + width * y) * TILE_SIZE + position.ordinal(), data); + } + + /** + * Gets a byte at a specific position. + */ + public byte read(int x, int y, DataPosition position){ + return buffer.get((x + width * y) * TILE_SIZE + position.ordinal()); + } + + /** + * Reads and returns the next tile data. + */ + public TileDataMarker read(TileDataMarker marker){ + marker.read(buffer); + return marker; + } + + /** + * Writes this tile data marker. + */ + public void write(TileDataMarker marker){ + marker.write(buffer); + } + + /** + * Sets read position to the specified coordinates + */ + public void position(int x, int y){ + buffer.position((x + width * y) * TILE_SIZE); + } + + public TileDataMarker newDataMarker(){ + return new TileDataMarker(); + } + + public enum DataPosition{ + floor, wall, link, rotationTeam, elevation + } + + public class TileDataMarker{ + public byte floor, wall; + public byte link; + public byte rotation; + public byte team; + public byte elevation; + + public void read(ByteBuffer buffer){ + floor = buffer.get(); + wall = buffer.get(); + link = buffer.get(); + byte rt = buffer.get(); + elevation = buffer.get(); + rotation = Bits.getLeftByte(rt); + team = Bits.getRightByte(rt); + + if(map != null){ + floor = (byte) map.get(floor, floor); + wall = (byte) map.get(wall, wall); + } + } + + public void write(ByteBuffer buffer){ + if(readOnly) throw new IllegalArgumentException("This data is read-only."); + buffer.put(floor); + buffer.put(wall); + buffer.put(link); + buffer.put(Bits.packByte(rotation, team)); + buffer.put(elevation); + } + } +} diff --git a/core/src/io/anuke/mindustry/io/Maps.java b/core/src/io/anuke/mindustry/io/Maps.java index ad41c3f431..92ccdbd779 100644 --- a/core/src/io/anuke/mindustry/io/Maps.java +++ b/core/src/io/anuke/mindustry/io/Maps.java @@ -2,223 +2,222 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.utils.*; -import com.badlogic.gdx.utils.Json.Serializer; -import com.badlogic.gdx.utils.JsonWriter.OutputType; -import io.anuke.mindustry.world.Map; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Base64Coder; +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.ObjectMap; import io.anuke.ucore.core.Settings; -import io.anuke.ucore.graphics.Pixmaps; +import io.anuke.ucore.function.Supplier; import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.ThreadArray; + +import java.io.*; import static io.anuke.mindustry.Vars.*; public class Maps implements Disposable{ - private IntMap maps = new IntMap<>(); - private ObjectMap mapNames = new ObjectMap<>(); - private Array defaultMaps = new Array<>(); - private Map networkMap; - private int lastID; - private Json json = new Json(); - private Array array = new Array<>(); + /** + * List of all built-in maps. + */ + private static final String[] defaultMapNames = {}; + /** + * Tile format version. + */ + private static final int version = 0; - public Maps() { - json.setOutputType(OutputType.json); - json.setElementType(ArrayContainer.class, "maps", Map.class); - json.setSerializer(Color.class, new ColorSerializer()); - } + /** + * Maps map names to the real maps. + */ + private ObjectMap maps = new ObjectMap<>(); + /** + * All maps stored in an ordered array. + */ + private Array allMaps = new ThreadArray<>(); + /** + * Temporary array used for returning things. + */ + private Array returnArray = new ThreadArray<>(); + /** + * Used for storing a list of custom map names for GWT. + */ + private Array customMapNames; - public Iterable list(){ - return maps.values(); - } + public Maps(){ - public Array getDefaultMaps(){ - return defaultMaps; - } + } - public Array getCustomMaps(){ - array.clear(); - for(Map map : list()){ - if(map.custom) array.add(map); - } - return array; - } + /** + * Returns a list of all maps, including custom ones. + */ + public Array all(){ + return allMaps; + } - public Array getAllMaps(){ - array.clear(); - for(Map map : list()){ - array.add(map); - } - return array; - } + /** + * Returns a list of only custom maps. + */ + public Array customMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(map.custom) returnArray.add(map); + } + return returnArray; + } - public void setNetworkMap(Map map){ - if(networkMap != null){ - networkMap.pixmap.dispose(); - networkMap = null; - } + /** + * Returns a list of only default maps. + */ + public Array defaultMaps(){ + returnArray.clear(); + for(Map map : allMaps){ + if(!map.custom) returnArray.add(map); + } + return returnArray; + } - networkMap = map; - } + /** + * Returns map by internal name. + */ + public Map getByName(String name){ + return maps.get(name); + } - public Map getMap(int id){ - if(id == -1){ - return networkMap; - } - return maps.get(id); - } + /** + * Load all maps. Should be called at application start. + */ + public void load(){ + try{ + for(String name : defaultMapNames){ + FileHandle file = Gdx.files.internal("maps/" + name + "." + mapExtension); + loadMap(file.nameWithoutExtension(), file::read, false); + } + }catch(IOException e){ + throw new RuntimeException(e); + } - public Map getMap(String name){ - return mapNames.get(name); - } + loadCustomMaps(); + } - public void loadMaps(){ - if(!loadMapFile(Gdx.files.internal("maps/maps.json"), true)){ - throw new RuntimeException("Failed to load maps!"); - } + /** + * Save a map. This updates all values and stored data necessary. + */ + public void saveMap(String name, MapTileData data, ObjectMap tags){ + try{ + if(!gwt){ + FileHandle file = customMapDirectory.child(name + "." + mapExtension); + MapIO.writeMap(file.write(false), tags, data); + }else{ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + MapIO.writeMap(stream, tags, data); + Settings.putString("map-data-" + name, new String(Base64Coder.encode(stream.toByteArray()))); + if(!customMapNames.contains(name, false)){ + customMapNames.add(name); + Settings.putJson("custom-maps", customMapNames); + } + Settings.save(); + } - if(!gwt) { - if (!loadMapFile(customMapDirectory.child("maps.json"), false)) { - try { - Log.info("Failed to find custom map directory."); - customMapDirectory.child("maps.json").writeString("{}", false); - } catch (Exception e) { - throw new RuntimeException("Failed to create custom map directory!"); - } - } - } - } - - public void removeMap(Map map){ - maps.remove(map.id); - mapNames.remove(map.name); - Array out = new Array<>(); - for(Map m : maps.values()){ - if(m.custom){ - out.add(m); - } - } - saveMaps(out, customMapDirectory.child("maps.json")); - } - - public void saveAndReload(Map map, Pixmap out){ - if(map.pixmap != null && out != map.pixmap && map.texture != null){ - map.texture.dispose(); - map.texture = new Texture(out); - }else if (out == map.pixmap){ - map.texture.draw(out, 0, 0); - } - - map.pixmap = out; - if(map.texture == null) map.texture = new Texture(map.pixmap); - - if(map.id == -1){ - if(mapNames.containsKey(map.name)){ - map.id = mapNames.get(map.name).id; - }else{ - map.id = ++lastID; - } - } - - if(!Settings.has("hiscore" + map.name)){ - Settings.defaults("hiscore" + map.name, 0); - } - - saveCustomMap(map); - ui.levels.reload(); - } + if(maps.containsKey(name)){ + if(maps.get(name).texture != null){ + maps.get(name).texture.dispose(); + maps.get(name).texture = null; + } + allMaps.removeValue(maps.get(name), true); + } - public void saveMaps(Array array, FileHandle file){ - json.toJson(new ArrayContainer(array), file); - } - - public void saveCustomMap(Map toSave){ - toSave.custom = true; - Array out = new Array<>(); - boolean added = false; - for(Map map : maps.values()){ - if(map.custom){ - if(map.name.equals(toSave.name)){ - out.add(toSave); - toSave.id = map.id; - added = true; - }else{ - out.add(map); - } - } - } - if(!added){ - out.add(toSave); - } - maps.remove(toSave.id); - mapNames.remove(toSave.name); - maps.put(toSave.id, toSave); - mapNames.put(toSave.name, toSave); - Pixmaps.write(toSave.pixmap, customMapDirectory.child(toSave.name + ".png")); - saveMaps(out, customMapDirectory.child("maps.json")); - } + Map map = new Map(name, new MapMeta(version, tags, data.width(), data.height(), null), true, getStreamFor(name)); + if(!headless){ + map.texture = new Texture(MapIO.generatePixmap(data)); + } + allMaps.add(map); - private boolean loadMapFile(FileHandle file, boolean logException){ - try { - Array arr = json.fromJson(ArrayContainer.class, file).maps; - if (arr != null) { //can be an empty map file - for (Map map : arr) { - map.pixmap = new Pixmap(file.sibling(map.name + ".png")); - if (!headless) map.texture = new Texture(map.pixmap); - maps.put(map.id, map); - mapNames.put(map.name, map); - lastID = Math.max(lastID, map.id); - if (!map.custom) { - defaultMaps.add(map); - } - } - } - return true; - }catch (GdxRuntimeException e){ - Log.err(e); - return true; - }catch(Exception e){ - if(logException) { - Log.err(e); - Log.err("Failed loading map file: {0}", file); - } - return false; - } - } + maps.put(name, map); + }catch(IOException e){ + throw new RuntimeException(e); + } + } - @Override - public void dispose(){ - for(Map map : maps.values()){ - if(map.texture != null) map.texture.dispose(); - map.pixmap.dispose(); - } - maps.clear(); - } + /** + * Removes a map completely. + */ + public void removeMap(Map map){ + if(map.texture != null){ + map.texture.dispose(); + map.texture = null; + } - public static class ArrayContainer{ - Array maps; + maps.remove(map.name); + allMaps.removeValue(map, true); - public ArrayContainer() { - } + if(!gwt){ + customMapDirectory.child(map.name + "." + mapExtension).delete(); + }else{ + customMapNames.removeValue(map.name, false); + Settings.putString("map-data-" + map.name, ""); + Settings.putJson("custom-maps", customMapNames); + Settings.save(); + } + } - public ArrayContainer(Array maps) { - this.maps = maps; - } - } + private void loadMap(String name, Supplier supplier, boolean custom) throws IOException{ + try(DataInputStream ds = new DataInputStream(supplier.get())){ + MapMeta meta = MapIO.readMapMeta(ds); + Map map = new Map(name, meta, custom, supplier); - private class ColorSerializer implements Serializer{ + if(!headless){ + map.texture = new Texture(MapIO.generatePixmap(MapIO.readTileData(ds, meta, true))); + } - @Override - public void write(Json json, Color object, Class knownType){ - json.writeValue(object.toString().substring(0, 6)); - } + maps.put(map.name, map); + allMaps.add(map); + } + } - @Override - public Color read(Json json, JsonValue jsonData, Class type){ - return Color.valueOf(jsonData.asString()); - } + private void loadCustomMaps(){ + if(!gwt){ + for(FileHandle file : customMapDirectory.list()){ + try{ + if(file.extension().equalsIgnoreCase(mapExtension)){ + loadMap(file.nameWithoutExtension(), file::read, true); + } + }catch(Exception e){ + Log.err("Failed to load custom map file '{0}'!", file); + Log.err(e); + } + } - } + }else{ + customMapNames = Settings.getJson("custom-maps", Array.class); + + for(String name : customMapNames){ + try{ + String data = Settings.getString("map-data-" + name, ""); + byte[] bytes = Base64Coder.decode(data); + loadMap(name, () -> new ByteArrayInputStream(bytes), true); + }catch(Exception e){ + Log.err("Failed to load custom map '{0}'!", name); + Log.err(e); + } + } + } + } + + /** + * Returns an input stream supplier for a given map name. + */ + private Supplier getStreamFor(String name){ + if(!gwt){ + return customMapDirectory.child(name + "." + mapExtension)::read; + }else{ + String data = Settings.getString("map-data-" + name, ""); + byte[] bytes = Base64Coder.decode(data); + return () -> new ByteArrayInputStream(bytes); + } + } + + @Override + public void dispose(){ + + } } diff --git a/core/src/io/anuke/mindustry/io/SaveFileVersion.java b/core/src/io/anuke/mindustry/io/SaveFileVersion.java index 36b38f39f9..4a66242dfd 100644 --- a/core/src/io/anuke/mindustry/io/SaveFileVersion.java +++ b/core/src/io/anuke/mindustry/io/SaveFileVersion.java @@ -6,7 +6,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -public abstract class SaveFileVersion { +public abstract class SaveFileVersion{ public final int version; public SaveFileVersion(int version){ @@ -15,24 +15,15 @@ public abstract class SaveFileVersion { public SaveMeta getData(DataInputStream stream) throws IOException{ long time = stream.readLong(); //read last saved time + int build = stream.readInt(); byte mode = stream.readByte(); //read the gamemode - byte map = stream.readByte(); //read the map + String map = stream.readUTF(); //read the map int wave = stream.readInt(); //read the wave - return new SaveMeta(version, time, mode, map, wave, Difficulty.normal); + byte difficulty = stream.readByte(); //read the difficulty + return new SaveMeta(version, time, build, mode, map, wave, Difficulty.values()[difficulty]); } public abstract void read(DataInputStream stream) throws IOException; + public abstract void write(DataOutputStream stream) throws IOException; - - public static void writeString(DataOutputStream stream, String string) throws IOException{ - stream.writeByte(string.length()); - stream.writeBytes(string); - } - - public static String readString(DataInputStream stream) throws IOException{ - int length = stream.readByte(); - byte[] result = new byte[length]; - stream.read(result); - return new String(result); - } } diff --git a/core/src/io/anuke/mindustry/io/SaveIO.java b/core/src/io/anuke/mindustry/io/SaveIO.java index 0a150d675a..98ed4b251c 100644 --- a/core/src/io/anuke/mindustry/io/SaveIO.java +++ b/core/src/io/anuke/mindustry/io/SaveIO.java @@ -3,163 +3,164 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntMap; -import io.anuke.mindustry.io.versions.Save12; -import io.anuke.mindustry.io.versions.Save13; -import io.anuke.mindustry.io.versions.Save14; -import io.anuke.mindustry.io.versions.Save15; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.io.versions.Save16; import io.anuke.ucore.core.Settings; import java.io.*; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; import static io.anuke.mindustry.Vars.*; public class SaveIO{ - public static final IntMap versions = new IntMap<>(); - public static final Array versionArray = Array.with( - new Save12(), - new Save13(), - new Save14(), - new Save15() - ); + public static final IntArray breakingVersions = IntArray.with(47); + public static final IntMap versions = new IntMap<>(); + public static final Array versionArray = Array.with( + new Save16() + ); - static{ - for(SaveFileVersion version : versionArray){ - versions.put(version.version, version); - } - } + static{ + for(SaveFileVersion version : versionArray){ + versions.put(version.version, version); + } + } - public static void saveToSlot(int slot){ - if(gwt){ - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - write(stream); - Settings.putString("save-"+slot+"-data", new String(Base64Coder.encode(stream.toByteArray()))); - Settings.save(); - }else{ - FileHandle file = fileFor(slot); - boolean exists = file.exists(); - if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); - try { - write(fileFor(slot)); - }catch (Exception e){ - if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); - throw new RuntimeException(e); - } - } - } - - public static void loadFromSlot(int slot){ - if(gwt){ - String string = Settings.getString("save-"+slot+"-data"); - ByteArrayInputStream stream = new ByteArrayInputStream(Base64Coder.decode(string)); - load(stream); - }else{ - load(fileFor(slot)); - } - } + public static void saveToSlot(int slot){ + if(gwt){ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + write(stream); + Settings.putString("save-" + slot + "-data", new String(Base64Coder.encode(stream.toByteArray()))); + Settings.save(); + }else{ + FileHandle file = fileFor(slot); + boolean exists = file.exists(); + if(exists) file.moveTo(file.sibling(file.name() + "-backup." + file.extension())); + try{ + write(fileFor(slot)); + }catch(Exception e){ + if(exists) file.sibling(file.name() + "-backup." + file.extension()).moveTo(file); + throw new RuntimeException(e); + } + } + } - public static DataInputStream getSlotStream(int slot){ - if(gwt){ - String string = Settings.getString("save-"+slot+"-data"); - byte[] bytes = Base64Coder.decode(string); - return new DataInputStream(new ByteArrayInputStream(bytes)); - }else{ - return new DataInputStream(fileFor(slot).read()); - } - } - - public static boolean isSaveValid(int slot){ - try { - return isSaveValid(getSlotStream(slot)); - }catch (Exception e){ - return false; - } - } + public static void loadFromSlot(int slot){ + if(gwt){ + String string = Settings.getString("save-" + slot + "-data", ""); + ByteArrayInputStream stream = new ByteArrayInputStream(Base64Coder.decode(string)); + load(stream); + }else{ + load(fileFor(slot)); + } + } - public static boolean isSaveValid(FileHandle file){ - return isSaveValid(new DataInputStream(file.read())); - } + public static DataInputStream getSlotStream(int slot){ + if(gwt){ + String string = Settings.getString("save-" + slot + "-data", ""); + byte[] bytes = Base64Coder.decode(string); + return new DataInputStream(new ByteArrayInputStream(bytes)); + }else{ + return new DataInputStream(new InflaterInputStream(fileFor(slot).read())); + } + } - public static boolean isSaveValid(DataInputStream stream){ + public static boolean isSaveValid(int slot){ + try{ + return isSaveValid(getSlotStream(slot)); + }catch(Exception e){ + return false; + } + } - try{ - int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); - SaveMeta meta = ver.getData(stream); - return meta.map != null; - }catch (Exception e){ - return false; - } - } + public static boolean isSaveValid(FileHandle file){ + return isSaveValid(new DataInputStream(new InflaterInputStream(file.read()))); + } - public static SaveMeta getData(int slot){ - return getData(getSlotStream(slot)); - } - - public static SaveMeta getData(DataInputStream stream){ - - try{ - int version = stream.readInt(); - SaveMeta meta = versions.get(version).getData(stream); - stream.close(); - return meta; - }catch (IOException e){ - throw new RuntimeException(e); - } - } - - public static FileHandle fileFor(int slot){ - return saveDirectory.child(slot + ".mins"); - } + public static boolean isSaveValid(DataInputStream stream){ - public static void write(FileHandle file){ - write(file.write(false)); - } + try{ + int version = stream.readInt(); + SaveFileVersion ver = versions.get(version); + ver.getData(stream); + return true; + }catch(Exception e){ + e.printStackTrace(); + return false; + } + } - public static void write(OutputStream os){ + public static SaveMeta getData(int slot){ + return getData(getSlotStream(slot)); + } - DataOutputStream stream; - - try{ - stream = new DataOutputStream(os); - getVersion().write(stream); - stream.close(); - }catch (Exception e){ - throw new RuntimeException(e); - } - } + public static SaveMeta getData(DataInputStream stream){ - public static void load(FileHandle file){ - try { - load(file.read()); - }catch (RuntimeException e){ - e.printStackTrace(); - FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); - if(backup.exists()){ - load(backup.read()); - } - } - } + try{ + int version = stream.readInt(); + SaveMeta meta = versions.get(version).getData(stream); + stream.close(); + return meta; + }catch(IOException e){ + throw new RuntimeException(e); + } + } - public static void load(InputStream is){ - logic.reset(); + public static FileHandle fileFor(int slot){ + return saveDirectory.child(slot + "." + Vars.saveExtension); + } - DataInputStream stream; - - try{ - stream = new DataInputStream(is); - int version = stream.readInt(); - SaveFileVersion ver = versions.get(version); + public static void write(FileHandle file){ + write(file.write(false)); + } - ver.read(stream); + public static void write(OutputStream os){ + DataOutputStream stream; - stream.close(); - }catch (Exception e){ - throw new RuntimeException(e); - } - } + try{ + stream = new DataOutputStream(new DeflaterOutputStream(os)); + getVersion().write(stream); + stream.close(); + }catch(Exception e){ + throw new RuntimeException(e); + } + } - public static SaveFileVersion getVersion(){ - return versionArray.peek(); - } + public static void load(FileHandle file){ + try{ + load(new InflaterInputStream(file.read())); + }catch(RuntimeException e){ + e.printStackTrace(); + FileHandle backup = file.sibling(file.name() + "-backup." + file.extension()); + if(backup.exists()){ + load(new InflaterInputStream(backup.read())); + }else{ + throw new RuntimeException(e); + } + } + } + + public static void load(InputStream is){ + logic.reset(); + + DataInputStream stream; + + try{ + stream = new DataInputStream(is); + int version = stream.readInt(); + SaveFileVersion ver = versions.get(version); + + ver.read(stream); + + stream.close(); + }catch(Exception e){ + throw new RuntimeException(e); + } + } + + public static SaveFileVersion getVersion(){ + return versionArray.peek(); + } } diff --git a/core/src/io/anuke/mindustry/io/SaveMeta.java b/core/src/io/anuke/mindustry/io/SaveMeta.java index d937c90603..f782e74db1 100644 --- a/core/src/io/anuke/mindustry/io/SaveMeta.java +++ b/core/src/io/anuke/mindustry/io/SaveMeta.java @@ -3,25 +3,26 @@ package io.anuke.mindustry.io; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.world.Map; import java.util.Date; import static io.anuke.mindustry.Vars.world; -public class SaveMeta { +public class SaveMeta{ public int version; + public int build; public String date; public GameMode mode; public Map map; public int wave; public Difficulty difficulty; - public SaveMeta(int version, long date, int mode, int map, int wave, Difficulty difficulty){ + public SaveMeta(int version, long date, int build, int mode, String map, int wave, Difficulty difficulty){ this.version = version; + this.build = build; this.date = Platform.instance.format(new Date(date)); this.mode = GameMode.values()[mode]; - this.map = world.maps().getMap(map); + this.map = world.maps().getByName(map); this.wave = wave; this.difficulty = difficulty; } diff --git a/core/src/io/anuke/mindustry/io/Saves.java b/core/src/io/anuke/mindustry/io/Saves.java index 397d389a3c..0fd1792034 100644 --- a/core/src/io/anuke/mindustry/io/Saves.java +++ b/core/src/io/anuke/mindustry/io/Saves.java @@ -2,31 +2,37 @@ package io.anuke.mindustry.io; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.async.AsyncExecutor; import io.anuke.mindustry.core.GameState.State; import io.anuke.mindustry.game.Difficulty; +import io.anuke.mindustry.game.EventType.StateChangeEvent; import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.world.Map; +import io.anuke.ucore.core.Events; import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; +import io.anuke.ucore.util.ThreadArray; import java.io.IOException; -import static io.anuke.mindustry.Vars.saveSlots; -import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.*; -public class Saves { +public class Saves{ private int nextSlot; - private Array saves = new Array<>(); + private Array saves = new ThreadArray<>(); private SaveSlot current; private boolean saving; private float time; - private AsyncExecutor exec = new AsyncExecutor(1); + public Saves(){ + Events.on(StateChangeEvent.class, (prev, state) -> { + if(state == State.menu){ + threads.run(() -> current = null); + } + }); + } public void load(){ saves.clear(); - for(int i = 0; i < saveSlots; i ++){ + for(int i = 0; i < saveSlots; i++){ if(SaveIO.isSaveValid(i)){ SaveSlot slot = new SaveSlot(i); saves.add(slot); @@ -36,25 +42,25 @@ public class Saves { } } - public SaveSlot getCurrent() { + public SaveSlot getCurrent(){ return current; } public void update(){ - if(state.is(State.menu)){ - current = null; - } if(!state.is(State.menu) && !state.gameOver && current != null && current.isAutosave()){ time += Timers.delta(); - if(time > Settings.getInt("saveinterval")*60) { + if(time > Settings.getInt("saveinterval") * 60){ saving = true; - exec.submit(() -> { - SaveIO.saveToSlot(current.index); - current.meta = SaveIO.getData(current.index); + Timers.run(2f, () -> { + try{ + SaveIO.saveToSlot(current.index); + current.meta = SaveIO.getData(current.index); + }catch(Exception e){ + e.printStackTrace(); + } saving = false; - return true; }); time = 0; @@ -78,7 +84,7 @@ public class Saves { public void addSave(String name){ SaveSlot slot = new SaveSlot(nextSlot); - nextSlot ++; + nextSlot++; slot.setName(name); saves.add(slot); SaveIO.saveToSlot(slot.index); @@ -89,7 +95,7 @@ public class Saves { public SaveSlot importSave(FileHandle file) throws IOException{ SaveSlot slot = new SaveSlot(nextSlot); slot.importFile(file); - nextSlot ++; + nextSlot++; slot.setName(file.nameWithoutExtension()); saves.add(slot); slot.meta = SaveIO.getData(slot.index); @@ -110,15 +116,15 @@ public class Saves { } public void load(){ - current = this; SaveIO.loadFromSlot(index); meta = SaveIO.getData(index); + current = this; } public void save(){ - current = this; SaveIO.saveToSlot(index); meta = SaveIO.getData(index); + current = this; } public String getDate(){ @@ -130,14 +136,18 @@ public class Saves { } public String getName(){ - return Settings.getString("save-"+index+"-name"); + return Settings.getString("save-" + index + "-name", "untittled"); } public void setName(String name){ - Settings.putString("save-"+index+"-name", name); + Settings.putString("save-" + index + "-name", name); Settings.save(); } + public int getBuild(){ + return meta.build; + } + public int getWave(){ return meta.wave; } @@ -151,29 +161,29 @@ public class Saves { } public boolean isAutosave(){ - return Settings.getBool("save-"+index+"-autosave"); + return Settings.getBool("save-" + index + "-autosave", !gwt); } public void setAutosave(boolean save){ - Settings.putBool("save-"+index + "-autosave", save); + Settings.putBool("save-" + index + "-autosave", save); Settings.save(); } public void importFile(FileHandle file) throws IOException{ try{ file.copyTo(SaveIO.fileFor(index)); - }catch (Exception e){ + }catch(Exception e){ throw new IOException(e); } } public void exportFile(FileHandle file) throws IOException{ try{ - if(!file.extension().equals("mins")){ - file = file.parent().child(file.nameWithoutExtension() + ".mins"); + if(!file.extension().equals(saveExtension)){ + file = file.parent().child(file.nameWithoutExtension() + "." + saveExtension); } SaveIO.fileFor(index).copyTo(file); - }catch (Exception e){ + }catch(Exception e){ throw new IOException(e); } } diff --git a/core/src/io/anuke/mindustry/io/TypeIO.java b/core/src/io/anuke/mindustry/io/TypeIO.java new file mode 100644 index 0000000000..34475643ea --- /dev/null +++ b/core/src/io/anuke/mindustry/io/TypeIO.java @@ -0,0 +1,357 @@ +package io.anuke.mindustry.io; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.Base64Coder; +import io.anuke.annotations.Annotations.ReadClass; +import io.anuke.annotations.Annotations.WriteClass; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.entities.traits.CarriableTrait; +import io.anuke.mindustry.entities.traits.CarryTrait; +import io.anuke.mindustry.entities.traits.ShooterTrait; +import io.anuke.mindustry.entities.units.BaseUnit; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.net.Packets.AdminAction; +import io.anuke.mindustry.net.Packets.KickReason; +import io.anuke.mindustry.net.TraceInfo; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.entities.Entities; + +import java.nio.ByteBuffer; + +import static io.anuke.mindustry.Vars.*; + +/** + * Class for specifying read/write methods for code generation. + */ +public class TypeIO{ + + @WriteClass(Player.class) + public static void writePlayer(ByteBuffer buffer, Player player){ + if(player == null){ + buffer.putInt(-1); + }else{ + buffer.putInt(player.id); + } + } + + @ReadClass(Player.class) + public static Player readPlayer(ByteBuffer buffer){ + int id = buffer.getInt(); + return id == -1 ? null : playerGroup.getByID(id); + } + + @WriteClass(Unit.class) + public static void writeUnit(ByteBuffer buffer, Unit unit){ + buffer.put((byte) unit.getGroup().getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(Unit.class) + public static Unit readUnit(ByteBuffer buffer){ + byte gid = buffer.get(); + int id = buffer.getInt(); + return (Unit) Entities.getGroup(gid).getByID(id); + } + + @WriteClass(ShooterTrait.class) + public static void writeShooter(ByteBuffer buffer, ShooterTrait trait){ + buffer.put((byte) trait.getGroup().getID()); + buffer.putInt(trait.getID()); + } + + @ReadClass(ShooterTrait.class) + public static ShooterTrait readShooter(ByteBuffer buffer){ + byte gid = buffer.get(); + int id = buffer.getInt(); + return (ShooterTrait) Entities.getGroup(gid).getByID(id); + } + + @WriteClass(Bullet.class) + public static void writeBullet(ByteBuffer buffer, Bullet bullet){ + buffer.putInt(bullet.getID()); + } + + @ReadClass(Bullet.class) + public static Bullet readBullet(ByteBuffer buffer){ + int id = buffer.getInt(); + return bulletGroup.getByID(id); + } + + @WriteClass(CarriableTrait.class) + public static void writeCarriable(ByteBuffer buffer, CarriableTrait unit){ + if(unit == null){ + buffer.put((byte) -1); + return; + } + buffer.put((byte) unit.getGroup().getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(CarriableTrait.class) + public static CarriableTrait readCarriable(ByteBuffer buffer){ + byte gid = buffer.get(); + if(gid == -1){ + return null; + } + int id = buffer.getInt(); + return (CarriableTrait) Entities.getGroup(gid).getByID(id); + } + + @WriteClass(CarryTrait.class) + public static void writeCarry(ByteBuffer buffer, CarryTrait unit){ + if(unit == null){ + buffer.put((byte) -1); + return; + } + buffer.put((byte) unit.getGroup().getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(CarryTrait.class) + public static CarryTrait readCarry(ByteBuffer buffer){ + byte gid = buffer.get(); + if(gid == -1){ + return null; + } + int id = buffer.getInt(); + return (CarryTrait) Entities.getGroup(gid).getByID(id); + } + + @WriteClass(BaseUnit.class) + public static void writeBaseUnit(ByteBuffer buffer, BaseUnit unit){ + buffer.put((byte) unitGroups[unit.getTeam().ordinal()].getID()); + buffer.putInt(unit.getID()); + } + + @ReadClass(BaseUnit.class) + public static BaseUnit writeBaseUnit(ByteBuffer buffer){ + byte gid = buffer.get(); + int id = buffer.getInt(); + return (BaseUnit) Entities.getGroup(gid).getByID(id); + } + + @WriteClass(Tile.class) + public static void writeTile(ByteBuffer buffer, Tile tile){ + buffer.putInt(tile.packedPosition()); + } + + @ReadClass(Tile.class) + public static Tile readTile(ByteBuffer buffer){ + return world.tile(buffer.getInt()); + } + + @WriteClass(Block.class) + public static void writeBlock(ByteBuffer buffer, Block block){ + buffer.put((byte) block.id); + } + + @ReadClass(Block.class) + public static Block readBlock(ByteBuffer buffer){ + return Block.getByID(buffer.get()); + } + + @WriteClass(KickReason.class) + public static void writeKick(ByteBuffer buffer, KickReason reason){ + buffer.put((byte) reason.ordinal()); + } + + @ReadClass(KickReason.class) + public static KickReason readKick(ByteBuffer buffer){ + return KickReason.values()[buffer.get()]; + } + + @WriteClass(Team.class) + public static void writeTeam(ByteBuffer buffer, Team reason){ + buffer.put((byte) reason.ordinal()); + } + + @ReadClass(Team.class) + public static Team readTeam(ByteBuffer buffer){ + return Team.all[buffer.get()]; + } + + @WriteClass(AdminAction.class) + public static void writeAction(ByteBuffer buffer, AdminAction reason){ + buffer.put((byte) reason.ordinal()); + } + + @ReadClass(AdminAction.class) + public static AdminAction readAction(ByteBuffer buffer){ + return AdminAction.values()[buffer.get()]; + } + + @WriteClass(Effect.class) + public static void writeEffect(ByteBuffer buffer, Effect effect){ + buffer.putShort((short) effect.id); + } + + @ReadClass(Effect.class) + public static Effect readEffect(ByteBuffer buffer){ + return Effects.getEffect(buffer.getShort()); + } + + @WriteClass(Color.class) + public static void writeColor(ByteBuffer buffer, Color color){ + buffer.putInt(Color.rgba8888(color)); + } + + @ReadClass(Color.class) + public static Color readColor(ByteBuffer buffer){ + return new Color(buffer.getInt()); + } + + @WriteClass(Weapon.class) + public static void writeWeapon(ByteBuffer buffer, Weapon weapon){ + buffer.put(weapon.id); + } + + @ReadClass(Weapon.class) + public static Weapon readWeapon(ByteBuffer buffer){ + return Upgrade.getByID(buffer.get()); + } + + @WriteClass(Mech.class) + public static void writeMech(ByteBuffer buffer, Mech mech){ + buffer.put(mech.id); + } + + @ReadClass(Mech.class) + public static Mech readMech(ByteBuffer buffer){ + return Upgrade.getByID(buffer.get()); + } + + @WriteClass(Liquid.class) + public static void writeLiquid(ByteBuffer buffer, Liquid liquid){ + buffer.put((byte) liquid.id); + } + + @ReadClass(Liquid.class) + public static Liquid readLiquid(ByteBuffer buffer){ + return Liquid.getByID(buffer.get()); + } + + @WriteClass(AmmoType.class) + public static void writeAmmo(ByteBuffer buffer, AmmoType type){ + buffer.put(type.id); + } + + @ReadClass(AmmoType.class) + public static AmmoType readAmmo(ByteBuffer buffer){ + return AmmoType.getByID(buffer.get()); + } + + @WriteClass(BulletType.class) + public static void writeBulletType(ByteBuffer buffer, BulletType type){ + buffer.put((byte) type.id); + } + + @ReadClass(BulletType.class) + public static BulletType readBulletType(ByteBuffer buffer){ + return BulletType.getByID(buffer.get()); + } + + @WriteClass(Item.class) + public static void writeItem(ByteBuffer buffer, Item item){ + buffer.put((byte) item.id); + } + + @ReadClass(Item.class) + public static Item readItem(ByteBuffer buffer){ + return Item.getByID(buffer.get()); + } + + @WriteClass(Recipe.class) + public static void writeRecipe(ByteBuffer buffer, Recipe recipe){ + buffer.put((byte) recipe.id); + } + + @ReadClass(Recipe.class) + public static Recipe readRecipe(ByteBuffer buffer){ + return Recipe.getByID(buffer.get()); + } + + @WriteClass(String.class) + public static void writeString(ByteBuffer buffer, String string){ + if(string != null){ + byte[] bytes = string.getBytes(); + buffer.putShort((short) bytes.length); + buffer.put(bytes); + }else{ + buffer.putShort((short) -1); + } + } + + @ReadClass(String.class) + public static String readString(ByteBuffer buffer){ + short length = buffer.getShort(); + if(length != -1){ + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new String(bytes); + }else{ + return null; + } + } + + @WriteClass(byte[].class) + public static void writeBytes(ByteBuffer buffer, byte[] bytes){ + buffer.putShort((short) bytes.length); + buffer.put(bytes); + } + + @ReadClass(byte[].class) + public static byte[] readBytes(ByteBuffer buffer){ + short length = buffer.getShort(); + byte[] bytes = new byte[length]; + buffer.get(bytes); + return bytes; + } + + @WriteClass(TraceInfo.class) + public static void writeTrace(ByteBuffer buffer, TraceInfo info){ + buffer.putInt(info.playerid); + buffer.putShort((short) info.ip.getBytes().length); + buffer.put(info.ip.getBytes()); + buffer.put(info.modclient ? (byte) 1 : 0); + buffer.put(info.android ? (byte) 1 : 0); + + buffer.putInt(info.totalBlocksBroken); + buffer.putInt(info.structureBlocksBroken); + buffer.putInt(info.lastBlockBroken.id); + + buffer.putInt(info.totalBlocksPlaced); + buffer.putInt(info.lastBlockPlaced.id); + buffer.put(Base64Coder.decode(info.uuid)); + } + + @ReadClass(TraceInfo.class) + public static TraceInfo readTrace(ByteBuffer buffer){ + int id = buffer.getInt(); + short iplen = buffer.getShort(); + byte[] ipb = new byte[iplen]; + buffer.get(ipb); + + TraceInfo info = new TraceInfo(new String(ipb)); + + info.playerid = id; + info.modclient = buffer.get() == 1; + info.android = buffer.get() == 1; + info.totalBlocksBroken = buffer.getInt(); + info.structureBlocksBroken = buffer.getInt(); + info.lastBlockBroken = Block.getByID(buffer.getInt()); + info.totalBlocksPlaced = buffer.getInt(); + info.lastBlockPlaced = Block.getByID(buffer.getInt()); + byte[] uuid = new byte[8]; + buffer.get(uuid); + + info.uuid = new String(Base64Coder.encode(uuid)); + return info; + } +} diff --git a/core/src/io/anuke/mindustry/io/Version.java b/core/src/io/anuke/mindustry/io/Version.java index 6328d20ba6..8bd816fb89 100644 --- a/core/src/io/anuke/mindustry/io/Version.java +++ b/core/src/io/anuke/mindustry/io/Version.java @@ -8,15 +8,15 @@ import io.anuke.ucore.util.Strings; import java.io.IOException; -public class Version { - public static final String name; - public static final String type; - public static final String code; - public static final int build; - public static final String buildName; +public class Version{ + public static String name; + public static String type; + public static String code; + public static int build = 0; + public static String buildName; - static{ - try { + public static void init(){ + try{ FileHandle file = Gdx.files.internal("version.properties"); ObjectMap map = new ObjectMap<>(); @@ -28,7 +28,7 @@ public class Version { build = Strings.canParseInt(map.get("build")) ? Integer.parseInt(map.get("build")) : -1; buildName = build == -1 ? map.get("build") : "build " + build; - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } diff --git a/core/src/io/anuke/mindustry/io/versions/Save12.java b/core/src/io/anuke/mindustry/io/versions/Save12.java deleted file mode 100644 index bfa64ab57c..0000000000 --- a/core/src/io/anuke/mindustry/io/versions/Save12.java +++ /dev/null @@ -1,287 +0,0 @@ -package io.anuke.mindustry.io.versions; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.io.BlockLoader; -import io.anuke.mindustry.io.SaveFileVersion; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.EntityGroup.EntityContainer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.*; - -public class Save12 extends SaveFileVersion { - - public Save12(){ - super(12); - } - - @Override - public void read(DataInputStream stream) throws IOException { - /*long loadTime = */stream.readLong(); - - //general state - byte mode = stream.readByte(); - byte mapid = stream.readByte(); - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - - float playerx = stream.readFloat(); - float playery = stream.readFloat(); - - int playerhealth = stream.readInt(); - - Vars.player.x = playerx; - Vars.player.y = playery; - Vars.player.health = playerhealth; - state.mode = GameMode.values()[mode]; - Core.camera.position.set(playerx, playery, 0); - - //weapons - - control.upgrades().getWeapons().clear(); - control.upgrades().getWeapons().add(Weapon.blaster); - Vars.player.weaponLeft = Vars.player.weaponRight = Weapon.blaster; - - int weapons = stream.readByte(); - - for(int i = 0; i < weapons; i ++){ - control.upgrades().addWeapon((Weapon)Upgrade.getByID(stream.readByte())); - } - - ui.hudfrag.updateWeapons(); - - //inventory - - int totalItems = stream.readByte(); - - Arrays.fill(state.inventory.getItems(), 0); - - for(int i = 0; i < totalItems; i ++){ - Item item = Item.getByID(stream.readByte()); - int amount = stream.readInt(); - state.inventory.getItems()[item.id] = amount; - } - - ui.hudfrag.updateItems(); - - //enemies - - Entities.clear(); - - int enemies = stream.readInt(); - - Array enemiesToUpdate = new Array<>(); - - for(int i = 0; i < enemies; i ++){ - byte type = stream.readByte(); - int lane = stream.readByte(); - float x = stream.readFloat(); - float y = stream.readFloat(); - byte tier = stream.readByte(); - int health = stream.readInt(); - - try{ - Enemy enemy = new Enemy(EnemyType.getByID(type)); - enemy.lane = lane; - enemy.health = health; - enemy.x = x; - enemy.y = y; - enemy.tier = tier; - enemy.add(enemyGroup); - enemiesToUpdate.add(enemy); - }catch (Exception e){ - throw new RuntimeException(e); - } - } - - state.enemies = enemies; - state.wave = wave; - state.wavetime = wavetime; - - if(!mobile) - Vars.player.add(); - - //map - - int seed = stream.readInt(); - int tiles = stream.readInt(); - - world.loadMap(world.maps().getMap(mapid), seed); - renderer.clearTiles(); - - for(Enemy enemy : enemiesToUpdate){ - enemy.node = -2; - } - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - //remove breakables like rocks - if(tile.breakable()){ - world.tile(x, y).setBlock(Blocks.air); - } - } - } - - for(int i = 0; i < tiles; i ++){ - int pos = stream.readInt(); - byte link = stream.readByte(); - boolean hasEntity = stream.readBoolean(); - int blockid = stream.readInt(); - - Tile tile = world.tile(pos % world.width(), pos / world.width()); - tile.setBlock(BlockLoader.getByOldID(blockid)); - tile.link = link; - - if(hasEntity){ - byte rotation = stream.readByte(); - int health = stream.readInt(); - int items = stream.readByte(); - - tile.entity.health = health; - tile.setRotation(rotation); - - for(int j = 0; j < items; j ++){ - int itemid = stream.readByte(); - int itemamount = stream.readInt(); - tile.entity.items[itemid] = itemamount; - } - - tile.entity.read(stream); - } - } - } - - @Override - public void write(DataOutputStream stream) throws IOException { - - //--META-- - stream.writeInt(version); //version id - stream.writeLong(TimeUtils.millis()); //last saved - - //--GENERAL STATE-- - stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeByte(world.getMap().id); //map ID - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown - - stream.writeFloat(Vars.player.x); //player x/y - stream.writeFloat(Vars.player.y); - - stream.writeInt((int)Vars.player.health); //player health - - stream.writeByte(control.upgrades().getWeapons().size - 1); //amount of weapons - - //start at 1, because the first weapon is always the starter - ignore that - for(int i = 1; i < control.upgrades().getWeapons().size; i ++){ - stream.writeByte(control.upgrades().getWeapons().get(i).id); //weapon ordinal - } - - //--INVENTORY-- - - int l = state.inventory.getItems().length; - int itemsize = 0; - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - itemsize ++; - } - } - - stream.writeByte(itemsize); //amount of items - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(state.inventory.getItems()[i]); //item amount - } - } - - //--ENEMIES-- - - EntityContainer enemies = enemyGroup.all(); - - stream.writeInt(enemies.size()); //enemy amount - - for(int i = 0; i < enemies.size(); i ++){ - Enemy enemy = enemies.get(i); - stream.writeByte(enemy.type.id); //type - stream.writeByte(enemy.lane); //lane - stream.writeFloat(enemy.x); //x - stream.writeFloat(enemy.y); //y - stream.writeByte(enemy.tier); //tier - stream.writeInt((int)enemy.health); //health - } - - //--MAP DATA-- - - //seed - stream.writeInt(world.getSeed()); - - int totalblocks = 0; - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable()){ - totalblocks ++; - } - } - } - - //tile amount - stream.writeInt(totalblocks); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable()){ - - stream.writeInt(x + y*world.width()); //tile pos - stream.writeByte(tile.link); - stream.writeBoolean(tile.entity != null); //whether it has a tile entity - stream.writeInt(tile.block().id); //block ID - - if(tile.entity != null){ - stream.writeByte(tile.getRotation()); //rotation - stream.writeInt((int)tile.entity.health); //health - int amount = 0; - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0) amount ++; - } - stream.writeByte(amount); //amount of items - - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(tile.entity.items[i]); //item amount - } - } - - tile.entity.write(stream); - } - } - } - } - } -} diff --git a/core/src/io/anuke/mindustry/io/versions/Save13.java b/core/src/io/anuke/mindustry/io/versions/Save13.java deleted file mode 100644 index 2d8d6faf8d..0000000000 --- a/core/src/io/anuke/mindustry/io/versions/Save13.java +++ /dev/null @@ -1,320 +0,0 @@ -package io.anuke.mindustry.io.versions; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.io.BlockLoader; -import io.anuke.mindustry.io.SaveFileVersion; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.WorldGenerator; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.BlockPart; -import io.anuke.mindustry.world.blocks.types.Rock; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.EntityGroup.EntityContainer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.*; - -public class Save13 extends SaveFileVersion { - - public Save13(){ - super(13); - } - - @Override - public void read(DataInputStream stream) throws IOException { - /*long loadTime = */stream.readLong(); - - //general state - byte mode = stream.readByte(); - byte mapid = stream.readByte(); - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - - float playerx = stream.readFloat(); - float playery = stream.readFloat(); - - int playerhealth = stream.readInt(); - - Vars.player.x = playerx; - Vars.player.y = playery; - Vars.player.health = playerhealth; - state.mode = GameMode.values()[mode]; - Core.camera.position.set(playerx, playery, 0); - - //weapons - - control.upgrades().getWeapons().clear(); - control.upgrades().getWeapons().add(Weapon.blaster); - Vars.player.weaponLeft = Vars.player.weaponRight = Weapon.blaster; - - int weapons = stream.readByte(); - - for(int i = 0; i < weapons; i ++){ - control.upgrades().addWeapon((Weapon) Upgrade.getByID(stream.readByte())); - } - - ui.hudfrag.updateWeapons(); - - //inventory - - int totalItems = stream.readByte(); - - Arrays.fill(state.inventory.getItems(), 0); - - for(int i = 0; i < totalItems; i ++){ - Item item = Item.getByID(stream.readByte()); - int amount = stream.readInt(); - state.inventory.getItems()[item.id] = amount; - } - - ui.hudfrag.updateItems(); - - //enemies - - Entities.clear(); - - int enemies = stream.readInt(); - - Array enemiesToUpdate = new Array<>(); - - for(int i = 0; i < enemies; i ++){ - byte type = stream.readByte(); - int lane = stream.readByte(); - float x = stream.readFloat(); - float y = stream.readFloat(); - byte tier = stream.readByte(); - int health = stream.readShort(); - - try{ - Enemy enemy = new Enemy(EnemyType.getByID(type)); - enemy.lane = lane; - enemy.health = health; - enemy.x = x; - enemy.y = y; - enemy.tier = tier; - enemy.add(enemyGroup); - enemiesToUpdate.add(enemy); - }catch (Exception e){ - throw new RuntimeException(e); - } - } - - state.enemies = enemies; - state.wave = wave; - state.wavetime = wavetime; - - if(!mobile) - Vars.player.add(); - - //map - - int seed = stream.readInt(); - - world.loadMap(world.maps().getMap(mapid), seed); - renderer.clearTiles(); - - for(Enemy enemy : enemiesToUpdate){ - enemy.node = -2; - } - - int rocks = stream.readInt(); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - //remove breakables like rocks - if(tile.breakable()){ - world.tile(x, y).setBlock(Blocks.air); - } - } - } - - for(int i = 0; i < rocks; i ++){ - int pos = stream.readInt(); - Tile tile = world.tile(pos % world.width(), pos / world.width()); - Block result = WorldGenerator.rocks.get(tile.floor()); - if(result != null) tile.setBlock(result); - } - - int tiles = stream.readInt(); - - for(int i = 0; i < tiles; i ++){ - int pos = stream.readInt(); - int blockid = stream.readInt(); - - Tile tile = world.tile(pos % world.width(), pos / world.width()); - tile.setBlock(BlockLoader.getByOldID(blockid)); - - if(blockid == Blocks.blockpart.id){ - tile.link = stream.readByte(); - } - - if(tile.entity != null){ - byte rotation = stream.readByte(); - short health = stream.readShort(); - int items = stream.readByte(); - - tile.entity.health = health; - tile.setRotation(rotation); - - for(int j = 0; j < items; j ++){ - int itemid = stream.readByte(); - int itemamount = stream.readInt(); - tile.entity.items[itemid] = itemamount; - } - - tile.entity.read(stream); - } - } - } - - @Override - public void write(DataOutputStream stream) throws IOException { - //--META-- - stream.writeInt(version); //version id - stream.writeLong(TimeUtils.millis()); //last saved - - //--GENERAL STATE-- - stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeByte(world.getMap().id); //map ID - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown - - stream.writeFloat(Vars.player.x); //player x/y - stream.writeFloat(Vars.player.y); - - stream.writeInt((int)Vars.player.health); //player health - - stream.writeByte(control.upgrades().getWeapons().size - 1); //amount of weapons - - //start at 1, because the first weapon is always the starter - ignore that - for(int i = 1; i < control.upgrades().getWeapons().size; i ++){ - stream.writeByte(control.upgrades().getWeapons().get(i).id); //weapon ordinal - } - - //--INVENTORY-- - - int l = state.inventory.getItems().length; - int itemsize = 0; - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - itemsize ++; - } - } - - stream.writeByte(itemsize); //amount of items - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(state.inventory.getItems()[i]); //item amount - } - } - - //--ENEMIES-- - EntityContainer enemies = enemyGroup.all(); - - stream.writeInt(enemies.size()); //enemy amount - - for(int i = 0; i < enemies.size(); i ++){ - Enemy enemy = enemies.get(i); - stream.writeByte(enemy.type.id); //type - stream.writeByte(enemy.lane); //lane - stream.writeFloat(enemy.x); //x - stream.writeFloat(enemy.y); //y - stream.writeByte(enemy.tier); //tier - stream.writeShort((short)enemy.health); //health - } - - //--MAP DATA-- - - //seed - stream.writeInt(world.getSeed()); - - int totalblocks = 0; - int totalrocks = 0; - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable()){ - if(tile.block() instanceof Rock){ - totalrocks ++; - }else{ - totalblocks ++; - } - } - } - } - - //amount of rocks - stream.writeInt(totalrocks); - - //write all rocks - for(int x = 0; x < world.width(); x ++) { - for (int y = 0; y < world.height(); y++) { - Tile tile = world.tile(x, y); - - if (tile.block() instanceof Rock) { - stream.writeInt(tile.packedPosition()); - } - } - } - - //write all blocks - stream.writeInt(totalblocks); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable() && !(tile.block() instanceof Rock)){ - - stream.writeInt(x + y*world.width()); //tile pos - stream.writeInt(tile.block().id); //block ID - - if(tile.block() instanceof BlockPart) stream.writeByte(tile.link); - - if(tile.entity != null){ - stream.writeByte(tile.getRotation()); //rotation - stream.writeShort((short)tile.entity.health); //health - byte amount = 0; - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0) amount ++; - } - stream.writeByte(amount); //amount of items - - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(tile.entity.items[i]); //item amount - } - } - - tile.entity.write(stream); - } - } - } - } - } - -} diff --git a/core/src/io/anuke/mindustry/io/versions/Save14.java b/core/src/io/anuke/mindustry/io/versions/Save14.java deleted file mode 100644 index 789fac77d1..0000000000 --- a/core/src/io/anuke/mindustry/io/versions/Save14.java +++ /dev/null @@ -1,345 +0,0 @@ -package io.anuke.mindustry.io.versions; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.io.SaveFileVersion; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.WorldGenerator; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.BlockPart; -import io.anuke.mindustry.world.blocks.types.Rock; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.EntityGroup.EntityContainer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.*; - -public class Save14 extends SaveFileVersion{ - - public Save14(){ - super(14); - } - - @Override - public void read(DataInputStream stream) throws IOException { - /*long loadTime = */ - stream.readLong(); - - //general state - byte mode = stream.readByte(); - byte mapid = stream.readByte(); - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - - //block header - - int blocksize = stream.readInt(); - - IntMap map = new IntMap<>(); - - for(int i = 0; i < blocksize; i ++){ - String name = readString(stream); - int id = stream.readShort(); - - map.put(id, Block.getByName(name)); - } - - float playerx = stream.readFloat(); - float playery = stream.readFloat(); - - int playerhealth = stream.readInt(); - - Vars.player.x = playerx; - Vars.player.y = playery; - Vars.player.health = playerhealth; - state.mode = GameMode.values()[mode]; - Core.camera.position.set(playerx, playery, 0); - - //weapons - - control.upgrades().getWeapons().clear(); - control.upgrades().getWeapons().add(Weapon.blaster); - Vars.player.weaponLeft = Vars.player.weaponRight = Weapon.blaster; - - int weapons = stream.readByte(); - - for(int i = 0; i < weapons; i ++){ - control.upgrades().addWeapon((Weapon) Upgrade.getByID(stream.readByte())); - } - - ui.hudfrag.updateWeapons(); - - //inventory - - int totalItems = stream.readByte(); - - Arrays.fill(state.inventory.getItems(), 0); - - for(int i = 0; i < totalItems; i ++){ - Item item = Item.getByID(stream.readByte()); - int amount = stream.readInt(); - state.inventory.getItems()[item.id] = amount; - } - - ui.hudfrag.updateItems(); - - //enemies - - Entities.clear(); - - int enemies = stream.readInt(); - - Array enemiesToUpdate = new Array<>(); - - for(int i = 0; i < enemies; i ++){ - byte type = stream.readByte(); - int lane = stream.readByte(); - float x = stream.readFloat(); - float y = stream.readFloat(); - byte tier = stream.readByte(); - int health = stream.readShort(); - - try{ - Enemy enemy = new Enemy(EnemyType.getByID(type)); - enemy.lane = lane; - enemy.health = health; - enemy.x = x; - enemy.y = y; - enemy.tier = tier; - enemy.add(enemyGroup); - enemiesToUpdate.add(enemy); - }catch (Exception e){ - throw new RuntimeException(e); - } - } - - state.enemies = enemies; - state.wave = wave; - state.wavetime = wavetime; - - if(!mobile) - Vars.player.add(); - - //map - - int seed = stream.readInt(); - - world.loadMap(world.maps().getMap(mapid), seed); - renderer.clearTiles(); - - for(Enemy enemy : enemiesToUpdate){ - enemy.node = -2; - } - - int rocks = stream.readInt(); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - //remove breakables like rocks - if(tile.breakable()){ - world.tile(x, y).setBlock(Blocks.air); - } - } - } - - for(int i = 0; i < rocks; i ++){ - int pos = stream.readInt(); - Tile tile = world.tile(pos % world.width(), pos / world.width()); - if(tile == null) continue; - Block result = WorldGenerator.rocks.get(tile.floor()); - if(result != null) tile.setBlock(result); - } - - int tiles = stream.readInt(); - - for(int i = 0; i < tiles; i ++){ - int pos = stream.readInt(); - int blockid = stream.readInt(); - - Tile tile = world.tile(pos % world.width(), pos / world.width()); - tile.setBlock(map.get(blockid)); - - if(blockid == Blocks.blockpart.id){ - tile.link = stream.readByte(); - } - - if(tile.entity != null){ - byte rotation = stream.readByte(); - short health = stream.readShort(); - int items = stream.readByte(); - - tile.entity.health = health; - tile.setRotation(rotation); - - for(int j = 0; j < items; j ++){ - int itemid = stream.readByte(); - int itemamount = stream.readInt(); - tile.entity.items[itemid] = itemamount; - } - - tile.entity.read(stream); - } - } - } - - @Override - public void write(DataOutputStream stream) throws IOException { - //--META-- - stream.writeInt(version); //version id - stream.writeLong(TimeUtils.millis()); //last saved - - //--GENERAL STATE-- - stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeByte(world.getMap().id); //map ID - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown - - //--BLOCK HEADER-- - - stream.writeInt(Block.getAllBlocks().size); - - for(int i = 0; i < Block.getAllBlocks().size; i ++){ - Block block = Block.getAllBlocks().get(i); - writeString(stream, block.name); - stream.writeShort(block.id); - } - - stream.writeFloat(Vars.player.x); //player x/y - stream.writeFloat(Vars.player.y); - - stream.writeInt((int)Vars.player.health); //player health - - stream.writeByte(control.upgrades().getWeapons().size - 1); //amount of weapons - - //start at 1, because the first weapon is always the starter - ignore that - for(int i = 1; i < control.upgrades().getWeapons().size; i ++){ - stream.writeByte(control.upgrades().getWeapons().get(i).id); //weapon ordinal - } - - //--INVENTORY-- - - int l = state.inventory.getItems().length; - int itemsize = 0; - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - itemsize ++; - } - } - - stream.writeByte(itemsize); //amount of items - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(state.inventory.getItems()[i]); //item amount - } - } - - //--ENEMIES-- - - EntityContainer enemies = enemyGroup.all(); - - stream.writeInt(enemies.size()); //enemy amount - - for(int i = 0; i < enemies.size(); i ++){ - Enemy enemy = enemies.get(i); - stream.writeByte(enemy.type.id); //type - stream.writeByte(enemy.lane); //lane - stream.writeFloat(enemy.x); //x - stream.writeFloat(enemy.y); //y - stream.writeByte(enemy.tier); //tier - stream.writeShort((short)enemy.health); //health - } - - //--MAP DATA-- - - //seed - stream.writeInt(world.getSeed()); - - int totalblocks = 0; - int totalrocks = 0; - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable()){ - if(tile.block() instanceof Rock){ - totalrocks ++; - }else{ - totalblocks ++; - } - } - } - } - - //amount of rocks - stream.writeInt(totalrocks); - - //write all rocks - for(int x = 0; x < world.width(); x ++) { - for (int y = 0; y < world.height(); y++) { - Tile tile = world.tile(x, y); - - if (tile.block() instanceof Rock) { - stream.writeInt(tile.packedPosition()); - } - } - } - - //write all blocks - stream.writeInt(totalblocks); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable() && !(tile.block() instanceof Rock)){ - - stream.writeInt(x + y*world.width()); //tile pos - stream.writeInt(tile.block().id); //block ID - - if(tile.block() instanceof BlockPart) stream.writeByte(tile.link); - - if(tile.entity != null){ - stream.writeByte(tile.getRotation()); //rotation - stream.writeShort((short)tile.entity.health); //health - byte amount = 0; - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0) amount ++; - } - stream.writeByte(amount); //amount of items - - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(tile.entity.items[i]); //item amount - } - } - - tile.entity.write(stream); - } - } - } - } - } -} diff --git a/core/src/io/anuke/mindustry/io/versions/Save15.java b/core/src/io/anuke/mindustry/io/versions/Save15.java deleted file mode 100644 index e93d8acf8b..0000000000 --- a/core/src/io/anuke/mindustry/io/versions/Save15.java +++ /dev/null @@ -1,370 +0,0 @@ -package io.anuke.mindustry.io.versions; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.TimeUtils; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.entities.enemies.EnemyType; -import io.anuke.mindustry.game.Difficulty; -import io.anuke.mindustry.game.GameMode; -import io.anuke.mindustry.io.SaveFileVersion; -import io.anuke.mindustry.io.SaveMeta; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.WorldGenerator; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.BlockPart; -import io.anuke.mindustry.world.blocks.types.Rock; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.entities.EntityGroup.EntityContainer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.*; - -public class Save15 extends SaveFileVersion { - - public Save15(){ - super(15); - } - - public SaveMeta getData(DataInputStream stream) throws IOException{ - long time = stream.readLong(); //read last saved time - byte mode = stream.readByte(); //read the gamemode - byte map = stream.readByte(); //read the map - int wave = stream.readInt(); //read the wave - stream.readFloat(); //wave time - byte difficulty = stream.readByte(); - return new SaveMeta(version, time, mode, map, wave, Difficulty.values()[difficulty]); - } - - @Override - public void read(DataInputStream stream) throws IOException { - /*long loadTime = */ - stream.readLong(); - - //general state - byte mode = stream.readByte(); - byte mapid = stream.readByte(); - - int wave = stream.readInt(); - float wavetime = stream.readFloat(); - byte difficulty = stream.readByte(); - - state.difficulty = Difficulty.values()[difficulty]; - - //block header - - int blocksize = stream.readInt(); - - IntMap map = new IntMap<>(); - - for(int i = 0; i < blocksize; i ++){ - String name = readString(stream); - int id = stream.readShort(); - - map.put(id, Block.getByName(name)); - } - - float playerx = stream.readFloat(); - float playery = stream.readFloat(); - - int playerhealth = stream.readInt(); - - if(!headless) { - player.x = playerx; - player.y = playery; - player.health = playerhealth; - state.mode = GameMode.values()[mode]; - Core.camera.position.set(playerx, playery, 0); - - //weapons - - control.upgrades().getWeapons().clear(); - control.upgrades().getWeapons().add(Weapon.blaster); - player.weaponLeft = player.weaponRight = Weapon.blaster; - - int weapons = stream.readByte(); - - for (int i = 0; i < weapons; i++) { - control.upgrades().addWeapon((Weapon) Upgrade.getByID(stream.readByte())); - } - - ui.hudfrag.updateWeapons(); - }else{ - byte b = stream.readByte(); - for(int i = 0; i < b; i ++) stream.readByte(); - } - - //inventory - - int totalItems = stream.readByte(); - - Arrays.fill(state.inventory.getItems(), 0); - - for(int i = 0; i < totalItems; i ++){ - Item item = Item.getByID(stream.readByte()); - int amount = stream.readInt(); - state.inventory.getItems()[item.id] = amount; - } - - if(!headless) ui.hudfrag.updateItems(); - - //enemies - - int enemies = stream.readInt(); - - Array enemiesToUpdate = new Array<>(); - - for(int i = 0; i < enemies; i ++){ - byte type = stream.readByte(); - int lane = stream.readByte(); - float x = stream.readFloat(); - float y = stream.readFloat(); - byte tier = stream.readByte(); - int health = stream.readShort(); - - try{ - Enemy enemy = new Enemy(EnemyType.getByID(type)); - enemy.lane = lane; - enemy.health = health; - enemy.x = x; - enemy.y = y; - enemy.tier = tier; - enemy.add(enemyGroup); - enemiesToUpdate.add(enemy); - }catch (Exception e){ - throw new RuntimeException(e); - } - } - - state.enemies = enemies; - state.wave = wave; - state.wavetime = wavetime; - - if(!mobile && !headless) - player.add(); - - //map - - int seed = stream.readInt(); - - world.loadMap(world.maps().getMap(mapid), seed); - if(!headless) renderer.clearTiles(); - - for(Enemy enemy : enemiesToUpdate){ - enemy.node = -2; - } - - int rocks = stream.readInt(); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - //remove breakables like rocks - if(tile.breakable()){ - world.tile(x, y).setBlock(Blocks.air); - } - } - } - - for(int i = 0; i < rocks; i ++){ - int pos = stream.readInt(); - Tile tile = world.tile(pos % world.width(), pos / world.width()); - if(tile == null) continue; - Block result = WorldGenerator.rocks.get(tile.floor()); - if(result != null) tile.setBlock(result); - } - - int tiles = stream.readInt(); - - for(int i = 0; i < tiles; i ++){ - int pos = stream.readInt(); - int blockid = stream.readInt(); - - Tile tile = world.tile(pos % world.width(), pos / world.width()); - - tile.setBlock(map.get(blockid)); - - if(blockid == Blocks.blockpart.id){ - tile.link = stream.readByte(); - } - - if(tile.entity != null){ - byte rotation = stream.readByte(); - short health = stream.readShort(); - int items = stream.readByte(); - - tile.entity.health = health; - tile.setRotation(rotation); - - for(int j = 0; j < items; j ++){ - int itemid = stream.readByte(); - int itemamount = stream.readInt(); - tile.entity.items[itemid] = itemamount; - } - - tile.entity.read(stream); - } - } - } - - @Override - public void write(DataOutputStream stream) throws IOException { - //--META-- - stream.writeInt(version); //version id - stream.writeLong(TimeUtils.millis()); //last saved - - //--GENERAL STATE-- - stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeByte(world.getMap().id); //map ID - - stream.writeInt(state.wave); //wave - stream.writeFloat(state.wavetime); //wave countdown - stream.writeByte(state.difficulty.ordinal()); - - //--BLOCK HEADER-- - - stream.writeInt(Block.getAllBlocks().size); - - for(int i = 0; i < Block.getAllBlocks().size; i ++){ - Block block = Block.getAllBlocks().get(i); - writeString(stream, block.name); - stream.writeShort(block.id); - } - - if(!headless) { - stream.writeFloat(player.x); //player x/y - stream.writeFloat(player.y); - - stream.writeInt((int)player.health); //player health - - stream.writeByte(control.upgrades().getWeapons().size - 1); //amount of weapons - - //start at 1, because the first weapon is always the starter - ignore that - for (int i = 1; i < control.upgrades().getWeapons().size; i++) { - stream.writeByte(control.upgrades().getWeapons().get(i).id); //weapon ordinal - } - }else{ - stream.writeFloat(world.getSpawnX()); - stream.writeFloat(world.getSpawnY()); - stream.writeInt(150); - stream.writeByte(0); - } - - //--INVENTORY-- - - int l = state.inventory.getItems().length; - int itemsize = 0; - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - itemsize ++; - } - } - - stream.writeByte(itemsize); //amount of items - - for(int i = 0; i < l; i ++){ - if(state.inventory.getItems()[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(state.inventory.getItems()[i]); //item amount - } - } - - //--ENEMIES-- - - EntityContainer enemies = enemyGroup.all(); - - stream.writeInt(enemies.size()); //enemy amount - - for(int i = 0; i < enemies.size(); i ++){ - Enemy enemy = enemies.get(i); - stream.writeByte(enemy.type.id); //type - stream.writeByte(enemy.lane); //lane - stream.writeFloat(enemy.x); //x - stream.writeFloat(enemy.y); //y - stream.writeByte(enemy.tier); //tier - stream.writeShort((short)enemy.health); //health - } - - //--MAP DATA-- - - //seed - stream.writeInt(world.getSeed()); - - int totalblocks = 0; - int totalrocks = 0; - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile != null && tile.breakable()){ - if(tile.block() instanceof Rock){ - totalrocks ++; - }else{ - totalblocks ++; - } - } - } - } - - //amount of rocks - stream.writeInt(totalrocks); - - //write all rocks - for(int x = 0; x < world.width(); x ++) { - for (int y = 0; y < world.height(); y++) { - Tile tile = world.tile(x, y); - - if (tile != null && tile.block() instanceof Rock) { - stream.writeInt(tile.packedPosition()); - } - } - } - - //write all blocks - stream.writeInt(totalblocks); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile != null && tile.breakable() && !(tile.block() instanceof Rock)){ - - stream.writeInt(x + y*world.width()); //tile pos - stream.writeInt(tile.block().id); //block ID - - if(tile.block() instanceof BlockPart) stream.writeByte(tile.link); - - if(tile.entity != null){ - stream.writeByte(tile.getRotation()); //rotation - stream.writeShort((short)tile.entity.health); //health - byte amount = 0; - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0) amount ++; - } - stream.writeByte(amount); //amount of items - - for(int i = 0; i < tile.entity.items.length; i ++){ - if(tile.entity.items[i] > 0){ - stream.writeByte(i); //item ID - stream.writeInt(tile.entity.items[i]); //item amount - } - } - - tile.entity.write(stream); - } - } - } - } - } -} diff --git a/core/src/io/anuke/mindustry/io/versions/Save16.java b/core/src/io/anuke/mindustry/io/versions/Save16.java new file mode 100644 index 0000000000..afe056970c --- /dev/null +++ b/core/src/io/anuke/mindustry/io/versions/Save16.java @@ -0,0 +1,246 @@ +package io.anuke.mindustry.io.versions; + +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.mindustry.content.blocks.Blocks; +import io.anuke.mindustry.content.blocks.StorageBlocks; +import io.anuke.mindustry.entities.traits.SaveTrait; +import io.anuke.mindustry.entities.traits.TypeTrait; +import io.anuke.mindustry.game.Difficulty; +import io.anuke.mindustry.game.GameMode; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.SaveFileVersion; +import io.anuke.mindustry.io.Version; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BlockPart; +import io.anuke.ucore.core.Timers; +import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.entities.EntityGroup; +import io.anuke.ucore.entities.trait.Entity; +import io.anuke.ucore.util.Bits; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import static io.anuke.mindustry.Vars.state; +import static io.anuke.mindustry.Vars.world; + +public class Save16 extends SaveFileVersion{ + + public Save16(){ + super(16); + } + + @Override + public void read(DataInputStream stream) throws IOException{ + /*long loadTime = */ + stream.readLong(); + + //general state + byte mode = stream.readByte(); + String mapname = stream.readUTF(); + Map map = world.maps().getByName(mapname); + world.setMap(map); + + int wave = stream.readInt(); + byte difficulty = stream.readByte(); + float wavetime = stream.readFloat(); + + state.difficulty = Difficulty.values()[difficulty]; + state.mode = GameMode.values()[mode]; + state.wave = wave; + state.wavetime = wavetime; + + state.spawner.read(stream); + + //block header + + int blocksize = stream.readInt(); + + IntMap blockMap = new IntMap<>(); + + for(int i = 0; i < blocksize; i++){ + String name = stream.readUTF(); + int id = stream.readShort(); + + blockMap.put(id, Block.getByName(name)); + } + + //entities + + byte groups = stream.readByte(); + + for(int i = 0; i < groups; i++){ + int amount = stream.readInt(); + for(int j = 0; j < amount; j++){ + byte typeid = stream.readByte(); + SaveTrait trait = (SaveTrait) TypeTrait.getTypeByID(typeid).get(); + trait.readSave(stream); + } + } + + //map + + short width = stream.readShort(); + short height = stream.readShort(); + + if(map == null){ + world.setMap(new Map("unknown", width, height)); + } + + world.beginMapLoad(); + + Tile[][] tiles = world.createTiles(width, height); + + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + byte floorid = stream.readByte(); + byte wallid = stream.readByte(); + byte elevation = stream.readByte(); + + Tile tile = new Tile(x, y, floorid, wallid); + tile.elevation = elevation; + + if(wallid == Blocks.blockpart.id){ + tile.link = stream.readByte(); + }else if(tile.entity != null){ + byte tr = stream.readByte(); + short health = stream.readShort(); + + byte team = Bits.getLeftByte(tr); + byte rotation = Bits.getRightByte(tr); + + Team t = Team.all[team]; + + tile.setTeam(Team.all[team]); + tile.entity.health = health; + tile.setRotation(rotation); + + if(tile.entity.items != null) tile.entity.items.read(stream); + if(tile.entity.power != null) tile.entity.power.read(stream); + if(tile.entity.liquids != null) tile.entity.liquids.read(stream); + if(tile.entity.cons != null) tile.entity.cons.read(stream); + + tile.entity.read(stream); + + if(tile.block() == StorageBlocks.core && + state.teams.has(t)){ + state.teams.get(t).cores.add(tile); + } + }else if(wallid == 0){ + int consecutives = stream.readUnsignedByte(); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + Tile newTile = new Tile(newx, newy, floorid, wallid); + newTile.elevation = elevation; + tiles[newx][newy] = newTile; + } + + i += consecutives; + } + + tiles[x][y] = tile; + } + + world.endMapLoad(); + } + + @Override + public void write(DataOutputStream stream) throws IOException{ + //--META-- + stream.writeInt(version); //version id + stream.writeLong(TimeUtils.millis()); //last saved + stream.writeInt(Version.build); + + //--GENERAL STATE-- + stream.writeByte(state.mode.ordinal()); //gamemode + stream.writeUTF(world.getMap().name); //map ID + + stream.writeInt(state.wave); //wave + stream.writeByte(state.difficulty.ordinal()); //difficulty ordinal + stream.writeFloat(state.wavetime); //wave countdown + + state.spawner.write(stream); + + //--BLOCK HEADER-- + + stream.writeInt(Block.all().size); + + for(int i = 0; i < Block.all().size; i++){ + Block block = Block.all().get(i); + stream.writeUTF(block.name); + stream.writeShort(block.id); + } + + //--ENTITIES-- + + int groups = 0; + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + groups++; + } + } + + stream.writeByte(groups); + + for(EntityGroup group : Entities.getAllGroups()){ + if(!group.isEmpty() && group.all().get(0) instanceof SaveTrait){ + stream.writeInt(group.size()); + for(Entity entity : group.all()){ + stream.writeByte(((SaveTrait) entity).getTypeID()); + ((SaveTrait) entity).writeSave(stream); + } + } + } + + //--MAP DATA-- + + Timers.mark(); + + //write world size + stream.writeShort(world.width()); + stream.writeShort(world.height()); + + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i); + + stream.writeByte(tile.getFloorID()); + stream.writeByte(tile.getWallID()); + stream.writeByte(tile.elevation); + + if(tile.block() instanceof BlockPart){ + stream.writeByte(tile.link); + }else if(tile.entity != null){ + stream.writeByte(Bits.packByte(tile.getTeamID(), tile.getRotation())); //team + rotation + stream.writeShort((short) tile.entity.health); //health + + if(tile.entity.items != null) tile.entity.items.write(stream); + if(tile.entity.power != null) tile.entity.power.write(stream); + if(tile.entity.liquids != null) tile.entity.liquids.write(stream); + if(tile.entity.cons != null) tile.entity.cons.write(stream); + + tile.entity.write(stream); + }else if(tile.getWallID() == 0){ + int consecutives = 0; + + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ + Tile nextTile = world.tile(j); + + if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getWallID() != 0 || nextTile.elevation != tile.elevation){ + break; + } + + consecutives++; + } + + stream.writeByte(consecutives); + i += consecutives; + } + } + } +} diff --git a/core/src/io/anuke/mindustry/mapeditor/DrawOperation.java b/core/src/io/anuke/mindustry/mapeditor/DrawOperation.java deleted file mode 100755 index 5884250cd5..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/DrawOperation.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.utils.Disposable; -import io.anuke.ucore.graphics.Pixmaps; - -public class DrawOperation implements Disposable{ - Pixmap from, to, pixmap; - - public DrawOperation(Pixmap pixmap){ - this.pixmap = pixmap; - } - - public void add(Pixmap from, Pixmap to) { - this.from = from; - this.to = to; - } - - public void undo() { - if(from != null) pixmap.drawPixmap(from, 0, 0); - } - - public void redo() { - if(to != null) pixmap.drawPixmap(to, 0, 0); - } - - @Override - public void dispose() { - if(!Pixmaps.isDisposed(from)) - from.dispose(); - - if(!Pixmaps.isDisposed(to)) - to.dispose(); - } - - public void disposeFrom(){ - if(from != null) from.dispose(); - } - -} diff --git a/core/src/io/anuke/mindustry/mapeditor/EditorTool.java b/core/src/io/anuke/mindustry/mapeditor/EditorTool.java deleted file mode 100644 index 561a9abf42..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/EditorTool.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.math.GridPoint2; -import com.badlogic.gdx.utils.IntSet; -import static io.anuke.mindustry.Vars.*; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.ColorMapper; -import io.anuke.mindustry.world.ColorMapper.BlockPair; - -import java.util.Stack; - -public enum EditorTool{ - pick{ - public void touched(MapEditor editor, int x, int y){ - BlockPair pair = ColorMapper.get(editor.pixmap().getPixel(x, y)); - if(pair == null) return; - Block block = pair.dominant(); - editor.setDrawBlock(block); - ui.editor.updateSelectedBlock(); - } - }, - pencil{ - { - edit = true; - } - - public void touched(MapEditor editor, int x, int y){ - editor.draw(x, y); - } - }, - line{ - { - - } - - }, - fill{ - { - edit = true; - } - - public void touched(MapEditor editor, int x, int y){ - Pixmap pix = editor.pixmap(); - - int dest = pix.getPixel(x, y); - - int width = pix.getWidth(); - - IntSet set = new IntSet(); - Stack points = new Stack(); - points.add(new GridPoint2(x, y)); - - while( !points.isEmpty()){ - GridPoint2 pos = points.pop(); - set.add(asInt(pos.x, pos.y, width)); - - int pcolor = pix.getPixel(pos.x, pos.y); - if(colorEquals(pcolor, dest)){ - - pix.drawPixel(pos.x, pos.y); - - if(pos.x > 0 && !set.contains(asInt(pos.x - 1, pos.y, width))) points.add(new GridPoint2(pos).cpy().add( -1, 0)); - if(pos.y > 0 && !set.contains(asInt(pos.x, pos.y - 1, width))) points.add(new GridPoint2(pos).cpy().add(0, -1)); - if(pos.x < pix.getWidth() - 1 && !set.contains(asInt(pos.x + 1, pos.y, width))) points.add(new GridPoint2(pos).cpy().add(1, 0)); - if(pos.y < pix.getHeight() - 1 && !set.contains(asInt(pos.x, pos.y + 1, width))) points.add(new GridPoint2(pos).cpy().add(0, 1)); - } - } - - editor.updateTexture(); - } - - int asInt(int x, int y, int width){ - return x+y*width; - } - - boolean colorEquals(int a, int b){ - return a == b; - } - }, - zoom; - boolean edit; - - public void touched(MapEditor editor, int x, int y){ - - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapEditor.java b/core/src/io/anuke/mindustry/mapeditor/MapEditor.java deleted file mode 100644 index 7ef8f97a47..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapEditor.java +++ /dev/null @@ -1,201 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Pixmap.Format; -import com.badlogic.gdx.graphics.Texture; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.ColorMapper; -import io.anuke.mindustry.world.Map; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.ucore.graphics.Pixmaps; - -public class MapEditor{ - public static final int[] validMapSizes = {128, 256, 512}; - public static final int[] brushSizes = {1, 2, 3, 4, 5, 9, 15}; - public static final int maxSpawnpoints = 15; - public static final Format format = Format.RGBA8888; - - private Pixmap[] brushPixmaps = new Pixmap[brushSizes.length]; - - private Map map; - - private MapFilter filter = new MapFilter(); - private Pixmap filterPixmap; - private Texture filterTexture; - - private Pixmap pixmap; - private Texture texture; - private int brushSize = 1; - private Block drawBlock = Blocks.stone; - - public MapEditor(){ - for(int i = 0; i < brushSizes.length; i ++){ - int s = brushSizes[i]; - brushPixmaps[i] = new Pixmap(s*2-1, s*2-1, format); - } - } - - public Map getMap(){ - return map; - } - - public void updateTexture(){ - texture.draw(pixmap, 0, 0); - } - - public MapFilter getFilter(){ - return filter; - } - - public void applyFilterPreview(){ - if(filterPixmap != null && (filterPixmap.getWidth() != pixmap.getWidth() - || filterPixmap.getHeight() != pixmap.getHeight())){ - filterPixmap.dispose(); - filterTexture.dispose(); - filterPixmap = null; - filterTexture = null; - } - - if(filterPixmap == null){ - filterPixmap = Pixmaps.copy(pixmap); - - filter.process(filterPixmap); - filterTexture = new Texture(filterPixmap); - }else{ - filterPixmap.drawPixmap(pixmap, 0, 0); - filter.process(filterPixmap); - filterTexture.draw(filterPixmap, 0, 0); - } - - } - - public Texture getFilterTexture(){ - return filterTexture; - } - - public void applyFilter(){ - filter.process(pixmap); - texture.draw(pixmap, 0, 0); - } - - public void beginEdit(Map map){ - drawBlock = Blocks.stone; - this.map = map; - this.brushSize = 1; - if(map.pixmap == null){ - pixmap = new Pixmap(256, 256, format); - pixmap.setColor(ColorMapper.getColor(drawBlock)); - pixmap.fill(); - texture = new Texture(pixmap); - }else{ - pixmap = map.pixmap; - texture = map.texture; - pixmap.setColor(ColorMapper.getColor(drawBlock)); - } - - } - - public Block getDrawBlock(){ - return drawBlock; - } - - public void setDrawBlock(Block block){ - this.drawBlock = block; - pixmap.setColor(ColorMapper.getColor(block)); - } - - public void setBrushSize(int size){ - this.brushSize = size; - } - - public int getBrushSize() { - return brushSize; - } - - public void draw(int dx, int dy){ - if(dx < 0 || dy < 0 || dx >= pixmap.getWidth() || dy >= pixmap.getHeight()){ - return; - } - - Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, texture.getTextureObjectHandle()); - - int dstWidth = brushSize*2-1; - int dstHeight = brushSize*2-1; - int width = pixmap.getWidth(), height = pixmap.getHeight(); - - int x = dx - dstWidth/2; - int y = dy - dstHeight/2; - - if (x + dstWidth > width){ - x = width - dstWidth; - }else if (x < 0){ - x = 0; - } - - if (y + dstHeight > height){ - dstHeight = height - y; - }else if (y < 0){ - dstHeight += y; - y = 0; - } - - pixmap.fillCircle(dx, dy, brushSize-1); - - Pixmap dst = brush(brushSize); - dst.drawPixmap(pixmap, x, y, dstWidth, dstHeight, 0, 0, dstWidth, dstHeight); - - Gdx.gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, x, y, dstWidth, dstHeight, - dst.getGLFormat(), dst.getGLType(), dst.getPixels()); - } - - private Pixmap brush(int size){ - for(int i = 0; i < brushSizes.length; i ++){ - if(brushSizes[i] == size){ - return brushPixmaps[i]; - } - } - return null; - } - - public Texture texture(){ - return texture; - } - - public Pixmap pixmap(){ - return pixmap; - } - - public void setPixmap(Pixmap out){ - if(pixmap.getWidth() == out.getWidth() && pixmap.getHeight() == out.getHeight()){ - pixmap.dispose(); - pixmap = out; - texture.draw(out, 0, 0); - }else{ - pixmap.dispose(); - texture.dispose(); - pixmap = out; - texture = new Texture(out); - } - pixmap.setColor(ColorMapper.getColor(drawBlock)); - map.pixmap = pixmap; - map.texture = texture; - } - - public void resize(int width, int height){ - Pixmap out = Pixmaps.resize(pixmap, width, height, ColorMapper.getColor(Blocks.stone)); - pixmap.dispose(); - pixmap = out; - texture.dispose(); - texture = new Texture(out); - - if(filterPixmap != null){ - filterPixmap.dispose(); - filterTexture.dispose(); - filterPixmap = null; - filterTexture = null; - } - pixmap.setColor(ColorMapper.getColor(drawBlock)); - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapEditorDialog.java b/core/src/io/anuke/mindustry/mapeditor/MapEditorDialog.java deleted file mode 100644 index 7a5ed16c6d..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapEditorDialog.java +++ /dev/null @@ -1,451 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.files.FileHandle; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Texture; -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.ColorMapper; -import io.anuke.mindustry.world.ColorMapper.BlockPair; -import io.anuke.mindustry.world.Map; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.SpecialBlocks; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Inputs; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Pixmaps; -import io.anuke.ucore.scene.Element; -import io.anuke.ucore.scene.builders.build; -import io.anuke.ucore.scene.builders.imagebutton; -import io.anuke.ucore.scene.builders.label; -import io.anuke.ucore.scene.builders.table; -import io.anuke.ucore.scene.ui.*; -import io.anuke.ucore.scene.ui.layout.Table; -import io.anuke.ucore.util.Bundles; -import io.anuke.ucore.util.Input; -import io.anuke.ucore.util.Log; -import io.anuke.ucore.util.Strings; - -import java.util.Arrays; - -import static io.anuke.mindustry.Vars.*; - -public class MapEditorDialog extends Dialog{ - private MapEditor editor; - private MapView view; - private MapGenerateDialog dialog; - private MapLoadDialog loadDialog; - private MapSaveDialog saveDialog; - private MapResizeDialog resizeDialog; - private ScrollPane pane; - private boolean saved = false; - - private ButtonGroup blockgroup; - - public MapEditorDialog(){ - super("$text.mapeditor", "dialog"); - if(gwt) return; - - editor = new MapEditor(); - dialog = new MapGenerateDialog(editor); - view = new MapView(editor); - - loadDialog = new MapLoadDialog(map -> { - saveDialog.setFieldText(map.name); - ui.loadfrag.show(); - - Timers.run(3f, () -> { - Map copy = new Map(); - copy.name = map.name; - copy.id = -1; - copy.pixmap = Pixmaps.copy(map.pixmap); - copy.texture = new Texture(copy.pixmap); - copy.oreGen = map.oreGen; - editor.beginEdit(copy); - ui.loadfrag.hide(); - view.clearStack(); - }); - }); - - resizeDialog = new MapResizeDialog(editor, (x, y) -> { - Pixmap pix = editor.pixmap(); - if(!(pix.getWidth() == x && pix.getHeight() == y)){ - ui.loadfrag.show(); - Timers.run(10f, ()->{ - editor.resize(x, y); - view.clearStack(); - ui.loadfrag.hide(); - }); - } - }); - - saveDialog = new MapSaveDialog(name -> { - ui.loadfrag.show(); - if(verifyMap()){ - saved = true; - String before = editor.getMap().name; - editor.getMap().name = name; - Timers.run(10f, () -> { - world.maps().saveAndReload(editor.getMap(), editor.pixmap()); - loadDialog.rebuild(); - ui.loadfrag.hide(); - view.clearStack(); - - if(!name.equals(before)) { - Map map = new Map(); - map.name = editor.getMap().name; - map.oreGen = editor.getMap().oreGen; - map.pixmap = Pixmaps.copy(editor.getMap().pixmap); - map.texture = new Texture(map.pixmap); - map.custom = true; - editor.beginEdit(map); - } - }); - - }else{ - ui.loadfrag.hide(); - } - }); - - setFillParent(true); - - clearChildren(); - margin(0); - build.begin(this); - build(); - build.end(); - - tapped(() -> { - Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true); - if(e == null || !e.isDescendantOf(pane)) Core.scene.setScrollFocus(null); - }); - - update(() -> { - if(Core.scene != null && Core.scene.getKeyboardFocus() == this){ - doInput(); - } - }); - - shown(() -> { - saved = true; - editor.beginEdit(new Map()); - blockgroup.getButtons().get(2).setChecked(true); - Core.scene.setScrollFocus(view); - view.clearStack(); - - Timers.runTask(10f, Platform.instance::updateRPC); - }); - - hidden(() -> Platform.instance.updateRPC()); - } - - public MapView getView() { - return view; - } - - public void resetSaved(){ - saved = false; - } - - public void updateSelectedBlock(){ - Block block = editor.getDrawBlock(); - int i = 0; - for(BlockPair pair : ColorMapper.getPairs()){ - if(pair.wall == block || (pair.wall == Blocks.air && pair.floor == block)){ - blockgroup.getButtons().get(i).setChecked(true); - break; - } - i++; - } - } - - public boolean hasPane(){ - return Core.scene.getScrollFocus() == pane; - } - - public void build(){ - - new table(){{ - float isize = 16*2f; - aleft(); - - new table(){{ - - defaults().growY().width(130f).padBottom(-6); - - new imagebutton("icon-terrain", isize, () -> - dialog.show() - ).text("$text.editor.generate"); - - row(); - - new imagebutton("icon-resize", isize, () -> - resizeDialog.show() - ).text("$text.editor.resize").padTop(4f); - - row(); - - new imagebutton("icon-load-map", isize, () -> - loadDialog.show() - ).text("$text.editor.loadmap"); - - row(); - - new imagebutton("icon-save-map", isize, ()-> - saveDialog.show() - ).text("$text.editor.savemap"); - - row(); - - //iOS does not support loading raw files. - if(!ios) { - - new imagebutton("icon-load-image", isize, () -> { - Platform.instance.showFileChooser(Bundles.get("text.loadimage"), "Image Files", MapEditorDialog.this::tryLoadMap, true, "png"); - }).text("$text.editor.loadimage"); - - row(); - } - - new imagebutton("icon-save-image", isize, () -> { - //iOS doesn't really support saving raw files. Sharing is used instead. - if(!ios){ - Platform.instance.showFileChooser(Bundles.get("text.saveimage"), "Image Files", file -> { - if(!file.extension().toLowerCase().equals(".png")){ - file = file.parent().child(file.nameWithoutExtension() + ".png"); - } - FileHandle result = file; - ui.loadfrag.show(); - Timers.run(3f, () -> { - try{ - Pixmaps.write(editor.pixmap(), result); - }catch (Exception e){ - ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); - if(!mobile) Log.err(e); - } - ui.loadfrag.hide(); - }); - }, false, "png"); - }else{ - try{ - FileHandle file = Gdx.files.local(("map-" + ((editor.getMap().name == null) ? "unknown" : editor.getMap().name) + ".png")); - Pixmaps.write(editor.pixmap(), file); - Platform.instance.shareFile(file); - }catch (Exception e){ - ui.showError(Bundles.format("text.editor.errorimagesave", Strings.parseException(e, false))); - } - } - }).text("$text.editor.saveimage"); - - row(); - - new imagebutton("icon-back", isize, () -> { - if(!saved){ - ui.showConfirm("$text.confirm", "$text.editor.unsaved", - MapEditorDialog.this::hide); - }else{ - hide(); - } - }).padBottom(0).text("$text.back"); - - }}.left().growY().end(); - - new table("button"){{ - add(view).grow(); - }}.grow().end(); - - new table(){{ - Table tools = new Table("button"); - tools.top(); - tools.marginTop(0).marginBottom(6); - - ButtonGroup group = new ButtonGroup<>(); - int i = 1; - - tools.defaults().size(53f, 58f).padBottom(-6); - - ImageButton undo = tools.addImageButton("icon-undo", 16*2f, () -> view.undo()).get(); - ImageButton redo = tools.addImageButton("icon-redo", 16*2f, () -> view.redo()).get(); - ImageButton grid = tools.addImageButton("icon-grid", "toggle", 16*2f, () -> view.setGrid(!view.isGrid())).get(); - - undo.setDisabled(() -> !view.getStack().canUndo()); - redo.setDisabled(() -> !view.getStack().canRedo()); - - undo.update(() -> undo.getImage().setColor(undo.isDisabled() ? Color.GRAY : Color.WHITE)); - redo.update(() -> redo.getImage().setColor(redo.isDisabled() ? Color.GRAY : Color.WHITE)); - grid.update(() -> grid.setChecked(view.isGrid())); - - for(EditorTool tool : EditorTool.values()){ - ImageButton button = new ImageButton("icon-" + tool.name(), "toggle"); - button.clicked(() -> view.setTool(tool)); - button.resizeImage(16*2f); - button.update(() -> button.setChecked(view.getTool() == tool)); - group.add(button); - if (tool == EditorTool.pencil) - button.setChecked(true); - - tools.add(button).padBottom(-6f); - if(i++ % 4 == 1) tools.row(); - } - - add(tools).width(53*4).padBottom(-6); - - row(); - - new table("button"){{ - margin(10f); - Slider slider = new Slider(0, MapEditor.brushSizes.length-1, 1, false); - slider.moved(f -> editor.setBrushSize(MapEditor.brushSizes[(int)(float)f])); - new label(() -> Bundles.format("text.editor.brushsize", MapEditor.brushSizes[(int)slider.getValue()])).left(); - row(); - add(slider).growX().padTop(4f); - }}.growX().padBottom(-6).end(); - - row(); - - new table("button"){{ - get().addCheck("$text.oregen", b -> editor.getMap().oreGen = b) - .update(c -> c.setChecked(editor.getMap().oreGen)).padTop(3).padBottom(3); - }}.growX().padBottom(-6).end(); - - row(); - - addBlockSelection(get()); - - row(); - - }}.right().growY().end(); - }}.grow().end(); - } - - public void tryLoadMap(FileHandle file){ - ui.loadfrag.show(); - Timers.runTask(3f, () -> { - try{ - Pixmap pixmap = new Pixmap(file); - if(verifySize(pixmap)){ - editor.setPixmap(pixmap); - view.clearStack(); - }else{ - ui.showError(Bundles.format("text.editor.badsize", Arrays.toString(MapEditor.validMapSizes))); - } - }catch (Exception e){ - ui.showError(Bundles.format("text.editor.errorimageload", Strings.parseException(e, false))); - Log.err(e); - } - ui.loadfrag.hide(); - }); - } - - private void doInput(){ - //tool select - for(int i = 0; i < EditorTool.values().length; i ++){ - int code = i == 0 ? 5 : i; - if(Inputs.keyTap("weapon_" + code)){ - view.setTool(EditorTool.values()[i]); - break; - } - } - - //ctrl keys (undo, redo, save) - if(Inputs.keyDown(Input.CONTROL_LEFT)){ - if(Inputs.keyTap(Input.Z)){ - view.undo(); - } - - if(Inputs.keyTap(Input.Y)){ - view.redo(); - } - - if(Inputs.keyTap(Input.S)){ - saveDialog.save(); - } - - if(Inputs.keyTap(Input.G)){ - view.setGrid(!view.isGrid()); - } - } - } - - private boolean verifySize(Pixmap pix){ - boolean w = false, h = false; - for(int i : MapEditor.validMapSizes){ - if(pix.getWidth() == i) - w = true; - if(pix.getHeight() == i) - h = true; - } - - return w && h; - } - - private boolean verifyMap(){ - int psc = ColorMapper.getColor(SpecialBlocks.playerSpawn); - int esc = ColorMapper.getColor(SpecialBlocks.enemySpawn); - - int playerSpawns = 0; - int enemySpawns = 0; - Pixmap pix = editor.pixmap(); - - for(int x = 0; x < pix.getWidth(); x ++){ - for(int y = 0; y < pix.getHeight(); y ++){ - int i = pix.getPixel(x, y); - if(i == psc) playerSpawns ++; - if(i == esc) enemySpawns ++; - } - } - - if(playerSpawns == 0){ - ui.showError("$text.editor.noplayerspawn"); - return false; - }else if(playerSpawns > 1){ - ui.showError("$text.editor.manyplayerspawns"); - return false; - } - - if(enemySpawns > MapEditor.maxSpawnpoints){ - ui.showError(Bundles.format("text.editor.manyenemyspawns", MapEditor.maxSpawnpoints)); - return false; - } - - return true; - } - - private void addBlockSelection(Table table){ - Table content = new Table(); - pane = new ScrollPane(content, "volume"); - pane.setScrollingDisabled(true, false); - pane.setFadeScrollBars(false); - pane.setOverscroll(true, false); - ButtonGroup group = new ButtonGroup<>(); - blockgroup = group; - - int i = 0; - - for(BlockPair pair : ColorMapper.getPairs()){ - Block block = pair.wall == Blocks.air ? pair.floor : pair.wall; - - ImageButton button = new ImageButton(Draw.hasRegion(block.name) ? Draw.region(block.name) : Draw.region(block.name + "1"), "toggle"); - button.clicked(() -> editor.setDrawBlock(block)); - button.resizeImage(8*4f); - group.add(button); - content.add(button).pad(4f).size(53f, 58f); - - if(i++ % 2 == 1){ - content.row(); - } - } - - group.getButtons().get(2).setChecked(true); - - Table extra = new Table("button"); - extra.labelWrap(() -> editor.getDrawBlock().formalName).width(180f).center(); - table.add(extra).padBottom(-6).growX(); - table.row(); - table.add(pane).growY().fillX(); - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapFilter.java b/core/src/io/anuke/mindustry/mapeditor/MapFilter.java deleted file mode 100644 index bdc1127a90..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapFilter.java +++ /dev/null @@ -1,244 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.OrderedMap; - -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.ColorMapper; -import io.anuke.mindustry.world.ColorMapper.BlockPair; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.Floor; -import io.anuke.ucore.graphics.Pixmaps; -import io.anuke.ucore.noise.RidgedPerlin; -import io.anuke.ucore.noise.Simplex; -import io.anuke.ucore.util.Mathf; - -public class MapFilter{ - private ObjectMap prefs = map( - pref("replace", "whether to replace blocks"), - pref("terrain", "generate new terrain"), - pref("circle", "generate terrain in a circle"), - pref("distort", "distort the map image"), - pref("sand", "add patches of sand"), - pref("grass", "add patches of grass"), - pref("stone", "add patches of stone"), - pref("blackstone", "add patches of black stone"), - pref("allgrass", "fill map with grass"), - pref("allsnow", "fill map with snow"), - pref("allsand", "fill map with sand"), - pref("water", "add lakes"), - pref("oil", "add oil lakes"), - pref("lavariver", "add lava rivers"), - pref("slavariver", "ad small lava rivers"), - pref("river", "add rivers"), - pref("iceriver", "add frozen rivers"), - pref("oilriver", "add oil rivers") - ); - - private Simplex sim = new Simplex(); - private RidgedPerlin rid = new RidgedPerlin(1, 10, 20f); - private RidgedPerlin rid2 = new RidgedPerlin(1, 6, 1f); - private RidgedPerlin rid3 = new RidgedPerlin(1, 6, 1f); - - public MapFilter(){ - prefs.get("replace").enabled = true; - prefs.get("terrain").enabled = true; - randomize(); - } - - public void randomize(){ - sim.setSeed(Mathf.random(999999)); - rid.setSeed(Mathf.random(999999)); - rid2.setSeed(Mathf.random(999999)); - rid3.setSeed(Mathf.random(999999)); - } - - public ObjectMap getPrefs(){ - return prefs; - } - - public Pixmap process(Pixmap pixmap){ - if(prefs.get("terrain").enabled){ - for(int x = 0; x < pixmap.getWidth(); x++){ - for(int y = 0; y < pixmap.getHeight(); y++){ - float dist = Vector2.dst((float) x / pixmap.getWidth(), (float) y / pixmap.getHeight(), 0.5f, 0.5f) * 2f; - double noise = sim.octaveNoise2D(6, 0.6, 1 / 180.0, x, y + 9999) / (prefs.get("circle").enabled ? 1.7 : 1f) + dist / 10f; - - if(dist > 0.8){ - noise += 2 * (dist - 0.8); - } - - Block block = noise > 0.6 ? Blocks.stoneblock : Blocks.stone; - - pixmap.drawPixel(x, y, ColorMapper.getColor(block)); - } - } - } - - Pixmap src = Pixmaps.copy(pixmap); - - for(int x = 0; x < pixmap.getWidth(); x++){ - for(int y = 0; y < pixmap.getHeight(); y++){ - int dx = 0, dy = 0; - - if(prefs.get("distort").enabled){ - double intensity = 12; - double scale = 80; - double octaves = 4; - double falloff = 0.6; - double nx = (sim.octaveNoise2D(octaves, falloff, 1 / scale, x, y) - 0.5f) * intensity; - double ny = (sim.octaveNoise2D(octaves, falloff, 1 / scale, x, y + 99999) - 0.5f) * intensity; - dx = (int) nx; - dy = (int) ny; - } - - int pix = src.getPixel(x + dx, y + dy); - - BlockPair pair = ColorMapper.get(pix); - Block block = pair == null ? null : pair.wall == Blocks.air ? pair.floor : pair.wall; - - if(block == null) - continue; - - boolean floor = block instanceof Floor; - - double noise = sim.octaveNoise2D(4, 0.6, 1 / 170.0, x, y) + sim.octaveNoise2D(1, 1.0, 1 / 5.0, x, y) / 18.0; - double nwater = sim.octaveNoise2D(1, 1.0, 1 / 130.0, x, y); - noise += nwater / 5.0; - - double noil = sim.octaveNoise2D(1, 1.0, 1 / 150.0, x + 9999, y) + sim.octaveNoise2D(1, 1.0, 1 / 2.0, x, y) / 290.0; - - if(!floor || prefs.get("replace").enabled){ - - if(prefs.get("allgrass").enabled){ - block = floor ? Blocks.grass : Blocks.grassblock; - }else if(prefs.get("allsnow").enabled){ - block = floor ? Blocks.snow : Blocks.snowblock; - }else if(prefs.get("allsand").enabled){ - block = floor ? Blocks.sand : Blocks.sandblock; - }else if(prefs.get("replace").enabled){ - block = floor ? Blocks.stone : Blocks.stoneblock; - } - - if(noise > 0.7 && prefs.get("grass").enabled){ - block = floor ? Blocks.grass : Blocks.grassblock; - } - if(noise > 0.7 && prefs.get("blackstone").enabled){ - block = floor ? Blocks.blackstone : Blocks.blackstoneblock; - } - if(noise > 0.7 && prefs.get("sand").enabled){ - block = floor ? Blocks.sand : Blocks.sandblock; - } - if(noise > 0.8 && prefs.get("stone").enabled){ - block = floor ? Blocks.stone : Blocks.stoneblock; - } - } - - if(floor){ - if(nwater > 0.93 && prefs.get("water").enabled){ - block = Blocks.water; - if(nwater > 0.943){ - block = Blocks.deepwater; - } - } - - if(noil > 0.95 && prefs.get("oil").enabled){ - block = Blocks.dirt; - if(noil > 0.955){ - block = Blocks.oil; - } - } - } - - if(floor && prefs.get("lavariver").enabled){ - double lava = rid.getValue(x, y, 1 / 100f); - double t = 0.6; - if(lava > t){ - block = Blocks.lava; - }else if(lava > t - 0.2){ - block = Blocks.blackstone; - } - } - - if(floor && prefs.get("slavariver").enabled){ - double lava = rid.getValue(x, y, 1 / 40f); - double t = 0.7; - if(lava > t){ - block = Blocks.lava; - }else if(lava > t - 0.3){ - block = Blocks.blackstone; - } - } - - if(floor && prefs.get("oilriver").enabled){ - double lava = rid3.getValue(x, y, 1 / 100f); - double t = 0.9; - if(lava > t){ - block = Blocks.oil; - }else if(lava > t - 0.2){ - block = Blocks.dirt; - } - } - - if(floor && prefs.get("river").enabled){ - double riv = rid2.getValue(x, y, 1 / 140f); - double t = 0.4; - - if(riv > t + 0.1){ - block = Blocks.deepwater; - }else if(riv > t){ - block = Blocks.water; - }else if(riv > t - 0.2){ - block = Blocks.grass; - } - } - - if(floor && prefs.get("iceriver").enabled){ - double riv = rid2.getValue(x, y, 1 / 140f); - double t = 0.4; - - if(riv > t + 0.1){ - block = Blocks.ice; - }else if(riv > t){ - block = Blocks.ice; - }else if(riv > t - 0.2){ - block = Blocks.snow; - } - } - - pixmap.drawPixel(x, y, ColorMapper.getColor(block)); - } - } - - src.dispose(); - - return pixmap; - } - - private static OrderedMap map(GenPref...objects){ - OrderedMap prefs = new OrderedMap<>(); - - for(int i = 0; i < objects.length; i ++){ - GenPref pref = (GenPref)objects[i]; - prefs.put(pref.name, pref); - } - return prefs; - } - - private GenPref pref(String name, String desc){ - return new GenPref(name, desc); - } - - class GenPref{ - public final String name; - public final String description; - public boolean enabled; - - GenPref(String name, String description){ - this.name = name; - this.description = description; - } - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapGenerateDialog.java b/core/src/io/anuke/mindustry/mapeditor/MapGenerateDialog.java deleted file mode 100644 index ee6535aafe..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapGenerateDialog.java +++ /dev/null @@ -1,105 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.utils.Align; -import com.badlogic.gdx.utils.Scaling; - -import static io.anuke.mindustry.Vars.*; -import io.anuke.mindustry.mapeditor.MapFilter.GenPref; -import io.anuke.mindustry.ui.BorderImage; -import io.anuke.mindustry.ui.dialogs.FloatingDialog; -import io.anuke.ucore.core.Timers; -import io.anuke.ucore.graphics.Pixmaps; -import io.anuke.ucore.scene.style.TextureRegionDrawable; -import io.anuke.ucore.scene.ui.CheckBox; -import io.anuke.ucore.scene.ui.Image; -import io.anuke.ucore.scene.ui.ScrollPane; -import io.anuke.ucore.scene.ui.layout.Stack; -import io.anuke.ucore.scene.ui.layout.Table; - -public class MapGenerateDialog extends FloatingDialog{ - private MapEditor editor; - private Image image; - private boolean loading; - - public MapGenerateDialog(MapEditor editor) { - super("$text.editor.generate"); - this.editor = editor; - - Stack stack = new Stack(); - stack.add(image = new BorderImage()); - - Image loadImage = new Image("icon-loading"); - loadImage.setScaling(Scaling.none); - loadImage.setScale(3f); - loadImage.update(() -> loadImage.setOrigin(Align.center)); - loadImage.setVisible(() -> loading); - Image next = new Image("white"); - next.setScaling(Scaling.fit); - next.setColor(0, 0, 0, 0.6f); - next.setVisible(() -> loading); - - stack.add(next); - stack.add(loadImage); - - content().add(stack).grow(); - image.setScaling(Scaling.fit); - Table preft = new Table(); - preft.left(); - preft.margin(4f).marginRight(25f); - - for(GenPref pref : editor.getFilter().getPrefs().values()){ - CheckBox box = new CheckBox(pref.name); - box.setChecked(pref.enabled); - box.changed(() -> pref.enabled = box.isChecked()); - preft.add(box).pad(4f).left(); - preft.row(); - } - - ScrollPane pane = new ScrollPane(preft, "volume"); - pane.setFadeScrollBars(false); - pane.setScrollingDisabled(true, false); - - content().add(pane).fillY(); - - buttons().defaults().size(170f, 50f).pad(4f); - buttons().addButton("$text.back", this::hide); - buttons().addButton("$text.randomize", () ->{ - editor.getFilter().randomize(); - apply(); - }); - buttons().addButton("$text.update", this::apply); - buttons().addButton("$text.apply", () ->{ - ui.loadfrag.show(); - - Timers.run(3f, () ->{ - Pixmap copy = Pixmaps.copy(editor.pixmap()); - editor.applyFilter(); - ui.editor.getView().push(copy, Pixmaps.copy(editor.pixmap())); - ui.loadfrag.hide(); - ui.editor.resetSaved(); - hide(); - }); - }); - - shown(() ->{ - loading = true; - Timers.run(30f, () -> { - editor.applyFilterPreview(); - image.setDrawable(new TextureRegionDrawable(new TextureRegion(editor.getFilterTexture()))); - loading = false; - }); - }); - } - - private void apply(){ - loading = true; - Timers.run(3f, () -> { - editor.applyFilterPreview(); - loading = false; - }); - - } - -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapLoadDialog.java b/core/src/io/anuke/mindustry/mapeditor/MapLoadDialog.java deleted file mode 100644 index aa71160cfe..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapLoadDialog.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import io.anuke.mindustry.ui.BorderImage; -import io.anuke.mindustry.ui.dialogs.FloatingDialog; -import io.anuke.mindustry.world.Map; -import io.anuke.ucore.function.Consumer; -import io.anuke.ucore.scene.ui.ButtonGroup; -import io.anuke.ucore.scene.ui.ScrollPane; -import io.anuke.ucore.scene.ui.TextButton; -import io.anuke.ucore.scene.ui.layout.Table; - -import static io.anuke.mindustry.Vars.world; - -public class MapLoadDialog extends FloatingDialog{ - private Map selected = world.maps().getMap(0); - - public MapLoadDialog(Consumer loader) { - super("$text.editor.loadmap"); - - shown(this::rebuild); - rebuild(); - - TextButton button = new TextButton("$text.load"); - button.setDisabled(() -> selected == null); - button.clicked(() -> { - if (selected != null) { - loader.accept(selected); - hide(); - } - }); - - buttons().defaults().size(200f, 50f); - buttons().addButton("$text.cancel", this::hide); - buttons().add(button); - } - - public void rebuild(){ - content().clear(); - - selected = world.maps().getMap(0); - - ButtonGroup group = new ButtonGroup<>(); - - int maxcol = 3; - - int i = 0; - - Table table = new Table(); - table.defaults().size(200f, 90f).pad(4f); - table.margin(10f); - - ScrollPane pane = new ScrollPane(table, "horizontal"); - pane.setFadeScrollBars(false); - - for (Map map : world.maps().list()) { - if (!map.visible) continue; - - TextButton button = new TextButton(map.localized(), "toggle"); - button.add(new BorderImage(map.texture, 2f)).size(16 * 4f); - button.getCells().reverse(); - button.clicked(() -> selected = map); - button.getLabelCell().grow().left().padLeft(5f); - group.add(button); - table.add(button); - if (++i % maxcol == 0) table.row(); - } - - content().add("$text.editor.loadmap"); - content().row(); - content().add(pane); - } - -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapResizeDialog.java b/core/src/io/anuke/mindustry/mapeditor/MapResizeDialog.java deleted file mode 100644 index 6ec83d6cec..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapResizeDialog.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.utils.Align; - -import io.anuke.mindustry.ui.dialogs.FloatingDialog; -import io.anuke.ucore.function.BiConsumer; -import io.anuke.ucore.scene.ui.ButtonGroup; -import io.anuke.ucore.scene.ui.TextButton; -import io.anuke.ucore.scene.ui.layout.Table; - -public class MapResizeDialog extends FloatingDialog{ - int width, height; - - public MapResizeDialog(MapEditor editor, BiConsumer cons){ - super("$text.editor.resizemap"); - shown(() -> { - content().clear(); - Pixmap pix = editor.pixmap(); - width = pix.getWidth(); - height = pix.getHeight(); - - Table table = new Table(); - - for(int d = 0; d < 2; d ++){ - boolean w = d == 0; - int curr = d == 0 ? pix.getWidth() : pix.getHeight(); - int idx = 0; - for(int i = 0; i < MapEditor.validMapSizes.length; i ++) - if(MapEditor.validMapSizes[i] == curr) idx = i; - - table.add(d == 0 ? "$text.width": "$text.height").padRight(8f); - ButtonGroup group = new ButtonGroup<>(); - for(int i = 0; i < MapEditor.validMapSizes.length; i ++){ - int size = MapEditor.validMapSizes[i]; - TextButton button = new TextButton(size + "", "toggle"); - button.clicked(() -> { - if(w) - width = size; - else - height = size; - }); - group.add(button); - if(i == idx) button.setChecked(true); - table.add(button).size(100f, 54f).pad(2f); - } - - table.row(); - } - - content().label(() -> - width + height > 512 ? "$text.editor.resizebig" : "" - ).get().setAlignment(Align.center, Align.center); - content().row(); - content().add(table); - - }); - - buttons().defaults().size(200f, 50f); - buttons().addButton("$text.cancel", this::hide); - buttons().addButton("$text.editor.resize", () -> { - cons.accept(width, height); - hide(); - }); - - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapSaveDialog.java b/core/src/io/anuke/mindustry/mapeditor/MapSaveDialog.java deleted file mode 100644 index 40ab7cae61..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapSaveDialog.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.ui.dialogs.FloatingDialog; -import io.anuke.mindustry.world.Map; -import io.anuke.ucore.function.Consumer; -import io.anuke.ucore.scene.ui.TextButton; -import io.anuke.ucore.scene.ui.TextField; - -import static io.anuke.mindustry.Vars.*; - -public class MapSaveDialog extends FloatingDialog{ - private TextField field; - private Consumer listener; - - public MapSaveDialog(Consumer cons){ - super("$text.editor.savemap"); - field = new TextField(); - listener = cons; - - Platform.instance.addDialog(field); - - shown(() -> { - content().clear(); - content().label(() ->{ - Map map = world.maps().getMap(field.getText()); - if(map != null){ - if(map.custom){ - return "$text.editor.overwrite"; - }else{ - return "$text.editor.failoverwrite"; - } - } - return ""; - }).colspan(2); - content().row(); - content().add("$text.editor.mapname").padRight(14f); - content().add(field).size(220f, 48f); - }); - - buttons().defaults().size(200f, 50f).pad(2f); - buttons().addButton("$text.cancel", this::hide); - - TextButton button = new TextButton("$text.save"); - button.clicked(() -> { - if(!invalid()){ - cons.accept(field.getText()); - hide(); - } - }); - button.setDisabled(this::invalid); - buttons().add(button); - } - - public void save(){ - if(!invalid()){ - listener.accept(field.getText()); - }else{ - ui.showError("$text.editor.failoverwrite"); - } - } - - public void setFieldText(String text){ - field.setText(text); - } - - private boolean invalid(){ - if(field.getText().isEmpty()){ - return true; - } - Map map = world.maps().getMap(field.getText()); - return map != null && !map.custom; - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/MapView.java b/core/src/io/anuke/mindustry/mapeditor/MapView.java deleted file mode 100644 index d60f2b14a4..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/MapView.java +++ /dev/null @@ -1,328 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.Colors; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.g2d.Batch; -import com.badlogic.gdx.input.GestureDetector; -import com.badlogic.gdx.input.GestureDetector.GestureListener; -import com.badlogic.gdx.math.Bresenham2; -import com.badlogic.gdx.math.GridPoint2; -import com.badlogic.gdx.math.Rectangle; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack; -import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.ui.GridImage; -import io.anuke.mindustry.world.ColorMapper; -import io.anuke.ucore.core.Core; -import io.anuke.ucore.core.Graphics; -import io.anuke.ucore.core.Inputs; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.graphics.Lines; -import io.anuke.ucore.graphics.Pixmaps; -import io.anuke.ucore.scene.Element; -import io.anuke.ucore.scene.event.InputEvent; -import io.anuke.ucore.scene.event.InputListener; -import io.anuke.ucore.scene.event.Touchable; -import io.anuke.ucore.scene.ui.TextField; -import io.anuke.ucore.scene.ui.layout.Unit; -import io.anuke.ucore.util.Input; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Tmp; - -import static io.anuke.mindustry.Vars.ui; - -public class MapView extends Element implements GestureListener{ - private MapEditor editor; - private EditorTool tool = EditorTool.pencil; - private OperationStack stack = new OperationStack(); - private DrawOperation op; - private Pixmap current; - private Bresenham2 br = new Bresenham2(); - private boolean updated = false; - private float offsetx, offsety; - private float zoom = 1f; - private boolean grid = false; - private GridImage image = new GridImage(0, 0); - private Vector2 vec = new Vector2(); - private Rectangle rect = new Rectangle(); - - private boolean drawing; - private int lastx, lasty; - private int startx, starty; - - public void setTool(EditorTool tool){ - this.tool = tool; - } - - public EditorTool getTool() { - return tool; - } - - public void clearStack(){ - stack.clear(); - current = null; - } - - public OperationStack getStack() { - return stack; - } - - public void setGrid(boolean grid) { - this.grid = grid; - } - - public boolean isGrid() { - return grid; - } - - public void push(Pixmap previous, Pixmap add){ - DrawOperation op = new DrawOperation(editor.pixmap()); - op.add(previous, add); - stack.add(op); - this.current = add; - } - - public void undo(){ - if(stack.canUndo()){ - stack.undo(); - editor.updateTexture(); - } - } - - public void redo(){ - if(stack.canRedo()){ - stack.redo(); - editor.updateTexture(); - } - } - - public MapView(MapEditor editor){ - this.editor = editor; - - Inputs.addProcessor(0, new GestureDetector(20, 0.5f, 2, 0.15f, this)); - setTouchable(Touchable.enabled); - - addListener(new InputListener(){ - - @Override - public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { - if(pointer != 0){ - return false; - } - - if(current == null){ - current = Pixmaps.copy(editor.pixmap()); - } - updated = false; - - GridPoint2 p = project(x, y); - lastx = p.x; - lasty = p.y; - startx = p.x; - starty = p.y; - tool.touched(editor, p.x, p.y); - - if(tool.edit){ - updated = true; - ui.editor.resetSaved(); - } - - op = new DrawOperation(editor.pixmap()); - - drawing = true; - return true; - } - - @Override - public void touchUp (InputEvent event, float x, float y, int pointer, int button) { - drawing = false; - - GridPoint2 p = project(x, y); - - if(tool == EditorTool.line){ - ui.editor.resetSaved(); - Array points = br.line(startx, starty, p.x, p.y); - for(GridPoint2 point : points){ - editor.draw(point.x, point.y); - } - updated = true; - } - - if(updated){ - if(op == null) op = new DrawOperation(editor.pixmap()); - Pixmap next = Pixmaps.copy(editor.pixmap()); - op.add(current, next); - current = null; - stack.add(op); - op = null; - } - } - - @Override - public void touchDragged (InputEvent event, float x, float y, int pointer) { - GridPoint2 p = project(x, y); - - if(drawing && tool == EditorTool.pencil){ - ui.editor.resetSaved(); - Array points = br.line(lastx, lasty, p.x, p.y); - for(GridPoint2 point : points){ - editor.draw(point.x, point.y); - } - updated = true; - } - lastx = p.x; - lasty = p.y; - } - }); - } - - @Override - public void act(float delta){ - super.act(delta); - - if(Core.scene.getKeyboardFocus() == null || !(Core.scene.getKeyboardFocus() instanceof TextField) && - !Inputs.keyDown(Input.CONTROL_LEFT)) { - float ax = Inputs.getAxis("move_x"); - float ay = Inputs.getAxis("move_y"); - offsetx -= ax * 15f / zoom; - offsety -= ay * 15f / zoom; - } - - if(ui.editor.hasPane()) return; - - zoom += Inputs.scroll()/10f * zoom; - clampZoom(); - } - - private void clampZoom(){ - zoom = Mathf.clamp(zoom, 0.2f, 12f); - } - - private GridPoint2 project(float x, float y){ - float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - x = (x - getWidth()/2 + sclwidth/2 - offsetx*zoom) / sclwidth * editor.texture().getWidth(); - y = (y - getHeight()/2 + sclheight/2 - offsety*zoom) / sclheight * editor.texture().getHeight(); - return Tmp.g1.set((int)x, editor.texture().getHeight() - 1 - (int)y); - } - - private Vector2 unproject(int x, int y){ - float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - float px = ((float)x / editor.texture().getWidth()) * sclwidth + offsetx*zoom - sclwidth/2 + getWidth()/2; - float py = (float)((float)(editor.texture().getHeight() - 1 - y) / editor.texture().getHeight()) * sclheight - + offsety*zoom - sclheight/2 + getHeight()/2; - return vec.set(px, py); - } - - @Override - public void draw(Batch batch, float alpha){ - float ratio = 1f / ((float)editor.pixmap().getWidth() / editor.pixmap().getHeight()); - float size = Math.min(width, height); - float sclwidth = size * zoom; - float sclheight = size * zoom * ratio; - float centerx = x + width/2 + offsetx * zoom; - float centery = y + height/2 + offsety * zoom; - - image.setImageSize(editor.pixmap().getWidth(), editor.pixmap().getHeight()); - - batch.flush(); - boolean pop = ScissorStack.pushScissors(rect.set(x + width/2 - size/2, y + height/2 - size/2, size, size)); - - batch.draw(editor.texture(), centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); - - if(grid){ - Draw.color(Color.GRAY); - image.setBounds(centerx - sclwidth/2, centery - sclheight/2, sclwidth, sclheight); - image.draw(batch, alpha); - Draw.color(); - } - - if(tool == EditorTool.line && drawing){ - Vector2 v1 = unproject(startx, starty).add(x, y); - float sx = v1.x, sy = v1.y; - Vector2 v2 = unproject(lastx, lasty).add(x, y); - - Draw.color(Tmp.c1.set(ColorMapper.getColor(editor.getDrawBlock()))); - Lines.stroke(Unit.dp.scl(3f * zoom)); - Lines.line(sx, sy, v2.x, v2.y); - - Lines.poly(sx, sy, 40, editor.getBrushSize() * zoom * 3); - - Lines.poly(v2.x, v2.y, 40, editor.getBrushSize() * zoom * 3); - } - - batch.flush(); - - if(pop) ScissorStack.popScissors(); - - Draw.color(Colors.get("accent")); - Lines.stroke(Unit.dp.scl(3f)); - Lines.rect(x + width/2 - size/2, y + height/2 - size/2, size, size); - Draw.reset(); - } - - private boolean active(){ - return Core.scene.getKeyboardFocus() != null - && Core.scene.getKeyboardFocus().isDescendantOf(ui.editor) - && ui.editor.isShown() && tool == EditorTool.zoom && - Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true) == this; - } - - @Override - public boolean touchDown(float x, float y, int pointer, int button){ - return false; - } - - @Override - public boolean tap(float x, float y, int count, int button){ - return false; - } - - @Override - public boolean longPress(float x, float y){ - return false; - } - - @Override - public boolean fling(float velocityX, float velocityY, int button){ - return false; - } - - @Override - public boolean pan(float x, float y, float deltaX, float deltaY){ - if(!active()) return false; - offsetx += deltaX / zoom; - offsety -= deltaY / zoom; - return false; - } - - @Override - public boolean panStop(float x, float y, int pointer, int button){ - return false; - } - - @Override - public boolean zoom(float initialDistance, float distance){ - if(!active()) return false; - float nzoom = distance - initialDistance; - zoom += nzoom / 10000f / Unit.dp.scl(1f) * zoom; - clampZoom(); - return false; - } - - @Override - public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2){ - return false; - } - - @Override - public void pinchStop(){ - - } -} diff --git a/core/src/io/anuke/mindustry/mapeditor/OperationStack.java b/core/src/io/anuke/mindustry/mapeditor/OperationStack.java deleted file mode 100755 index 7de9a0c98d..0000000000 --- a/core/src/io/anuke/mindustry/mapeditor/OperationStack.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.anuke.mindustry.mapeditor; - -import com.badlogic.gdx.utils.Array; - -public class OperationStack{ - private final static int maxSize = 10; - private Array stack = new Array<>(); - private int index = 0; - - public OperationStack(){ - - } - - public void clear(){ - for(DrawOperation op : stack){ - op.dispose(); - } - stack.clear(); - index = 0; - } - - public void add(DrawOperation action){ - stack.truncate(stack.size + index); - index = 0; - stack.add(action); - - if(stack.size > maxSize){ - stack.get(0).disposeFrom(); - stack.removeIndex(0); - } - } - - public boolean canUndo(){ - return !(stack.size - 1 + index < 0); - } - - public boolean canRedo(){ - return !(index > -1 || stack.size + index < 0); - } - - public void undo(){ - if(!canUndo()) return; - - stack.get(stack.size - 1 + index).undo(); - index --; - } - - public void redo(){ - if(!canRedo()) return; - - index ++; - stack.get(stack.size - 1 + index).redo(); - - } -} diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index cd412e9c25..bb838d4afd 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -2,40 +2,43 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.Json; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Placement; -import io.anuke.mindustry.world.blocks.types.BlockPart; -import io.anuke.mindustry.world.blocks.types.Floor; -import io.anuke.mindustry.world.blocks.types.Rock; -import io.anuke.mindustry.world.blocks.types.StaticBlock; +import io.anuke.mindustry.world.blocks.BlockPart; +import io.anuke.mindustry.world.blocks.Floor; +import io.anuke.mindustry.world.blocks.Rock; +import io.anuke.mindustry.world.blocks.StaticBlock; import io.anuke.ucore.core.Settings; + +import static io.anuke.mindustry.Vars.headless; import static io.anuke.mindustry.Vars.world; -public class Administration { +public class Administration{ public static final int defaultMaxBrokenBlocks = 15; - public static final int defaultBreakCooldown = 1000*15; + public static final int defaultBreakCooldown = 1000 * 15; - private Json json = new Json(); - /**All player info. Maps UUIDs to info. This persists throughout restarts.*/ + /** + * All player info. Maps UUIDs to info. This persists throughout restarts. + */ private ObjectMap playerInfo = new ObjectMap<>(); - /**Maps UUIDs to trace infos. This is wiped when a player logs off.*/ + /** + * Maps UUIDs to trace infos. This is wiped when a player logs off. + */ private ObjectMap traceInfo = new ObjectMap<>(); - /**Maps packed coordinates to logs for that coordinate */ + /** + * Maps packed coordinates to logs for that coordinate + */ private IntMap> editLogs = new IntMap<>(); - + private Array bannedIPs = new Array<>(); public Administration(){ Settings.defaultList( - "playerInfo", "{}", - "bannedIPs", "{}", - "antigrief", false, - "antigrief-max", defaultMaxBrokenBlocks, - "antigrief-cooldown", defaultBreakCooldown + "antigrief", false, + "antigrief-max", defaultMaxBrokenBlocks, + "antigrief-cooldown", defaultBreakCooldown ); load(); @@ -45,74 +48,86 @@ public class Administration { return Settings.getBool("antigrief"); } - public boolean isValidateReplace(){ - return false; - } - public void setAntiGrief(boolean antiGrief){ Settings.putBool("antigrief", antiGrief); Settings.save(); } + public boolean allowsCustomClients(){ + return Settings.getBool("allow-custom", !headless); + } + + public void setCustomClients(boolean allowed){ + Settings.putBool("allow-custom", allowed); + Settings.save(); + } + + public boolean isValidateReplace(){ + return false; + } + public void setAntiGriefParams(int maxBreak, int cooldown){ Settings.putInt("antigrief-max", maxBreak); Settings.putInt("antigrief-cooldown", cooldown); Settings.save(); } - public IntMap> getEditLogs() { + public IntMap> getEditLogs(){ return editLogs; } - - public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action) { - if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) return; - if(editLogs.containsKey(x + y * world.width())) { - editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action)); - } - else { - Array logs = new Array<>(); - logs.add(new EditLog(player.name, block, rotation, action)); - editLogs.put(x + y * world.width(), logs); - } + + public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action){ + if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) + return; + if(editLogs.containsKey(x + y * world.width())){ + editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action)); + }else{ + Array logs = new Array<>(); + logs.add(new EditLog(player.name, block, rotation, action)); + editLogs.put(x + y * world.width(), logs); + } } - + + /* public void rollbackWorld(int rollbackTimes) { for(IntMap.Entry> editLog : editLogs.entries()) { int coords = editLog.key; Array logs = editLog.value; - + for(int i = 0; i < rollbackTimes; i++) { - + EditLog log = logs.get(logs.size - 1); - + int x = coords % world.width(); int y = coords / world.width(); Block result = log.block; int rotation = log.rotation; - + + //TODO fix this mess, broken with 4.0 + if(log.action == EditLog.EditAction.PLACE) { - Placement.breakBlock(x, y, false, false); - + // Build.breakBlock(x, y, false, false); + Packets.BreakPacket packet = new Packets.BreakPacket(); packet.x = (short) x; packet.y = (short) y; packet.playerid = 0; - + Net.send(packet, Net.SendMode.tcp); } else if(log.action == EditLog.EditAction.BREAK) { - Placement.placeBlock(x, y, result, rotation, false, false); - + //Build.placeBlock(x, y, result, rotation, false, false); + Packets.PlacePacket packet = new Packets.PlacePacket(); packet.x = (short) x; packet.y = (short) y; packet.rotation = (byte) rotation; packet.playerid = 0; - packet.block = result.id; - + //packet.block = result.id; + Net.send(packet, Net.SendMode.tcp); } - + logs.removeIndex(logs.size - 1); if(logs.size == 0) { editLogs.remove(coords); @@ -120,8 +135,8 @@ public class Administration { } } } - } - + }*/ + public boolean validateBreak(String id, String ip){ if(!isAntiGrief() || isAdmin(id, ip)) return true; @@ -134,18 +149,18 @@ public class Administration { long[] breaks = info.lastBroken; int shiftBy = 0; - for(int i = 0; i < breaks.length && breaks[i] != 0; i ++){ + for(int i = 0; i < breaks.length && breaks[i] != 0; i++){ if(TimeUtils.timeSinceMillis(breaks[i]) >= Settings.getInt("antigrief-cooldown")){ shiftBy = i; } } - for (int i = 0; i < breaks.length; i++) { + for(int i = 0; i < breaks.length; i++){ breaks[i] = (i + shiftBy >= breaks.length) ? 0 : breaks[i + shiftBy]; } int remaining = 0; - for(int i = 0; i < breaks.length; i ++){ + for(int i = 0; i < breaks.length; i++){ if(breaks[i] == 0){ remaining = breaks.length - i; break; @@ -158,29 +173,35 @@ public class Administration { return true; } - /**Call when a player joins to update their information here.*/ + /** + * Call when a player joins to update their information here. + */ public void updatePlayerJoined(String id, String ip, String name){ PlayerInfo info = getCreateInfo(id); info.lastName = name; info.lastIP = ip; - info.timesJoined ++; + info.timesJoined++; if(!info.names.contains(name, false)) info.names.add(name); if(!info.ips.contains(ip, false)) info.ips.add(ip); } - /**Returns trace info by IP.*/ - public TraceInfo getTrace(String ip){ - if(!traceInfo.containsKey(ip)) traceInfo.put(ip, new TraceInfo(ip)); + /** + * Returns trace info by IP. + */ + public TraceInfo getTraceByID(String uuid){ + if(!traceInfo.containsKey(uuid)) traceInfo.put(uuid, new TraceInfo(uuid)); - return traceInfo.get(ip); + return traceInfo.get(uuid); } public void clearTraces(){ traceInfo.clear(); } - /**Bans a player by IP; returns whether this player was already banned. - * If there are players who at any point had this IP, they will be UUID banned as well.*/ + /** + * Bans a player by IP; returns whether this player was already banned. + * If there are players who at any point had this IP, they will be UUID banned as well. + */ public boolean banPlayerIP(String ip){ if(bannedIPs.contains(ip, false)) return false; @@ -197,7 +218,9 @@ public class Administration { return true; } - /**Bans a player by UUID; returns whether this player was already banned.*/ + /** + * Bans a player by UUID; returns whether this player was already banned. + */ public boolean banPlayerID(String id){ if(playerInfo.containsKey(id) && playerInfo.get(id).banned) return false; @@ -209,8 +232,10 @@ public class Administration { return true; } - /**Unbans a player by IP; returns whether this player was banned in the first place. - * This method also unbans any player that was banned and had this IP.*/ + /** + * Unbans a player by IP; returns whether this player was banned in the first place. + * This method also unbans any player that was banned and had this IP. + */ public boolean unbanPlayerIP(String ip){ boolean found = bannedIPs.contains(ip, false); @@ -228,8 +253,10 @@ public class Administration { return found; } - /**Unbans a player by ID; returns whether this player was banned in the first place. - * This also unbans all IPs the player used.*/ + /** + * Unbans a player by ID; returns whether this player was banned in the first place. + * This also unbans all IPs the player used. + */ public boolean unbanPlayerID(String id){ PlayerInfo info = getCreateInfo(id); @@ -243,7 +270,9 @@ public class Administration { return true; } - /**Returns list of all players with admin status*/ + /** + * Returns list of all players with admin status + */ public Array getAdmins(){ Array result = new Array<>(); for(PlayerInfo info : playerInfo.values()){ @@ -254,7 +283,9 @@ public class Administration { return result; } - /**Returns list of all players with admin status*/ + /** + * Returns list of all players with admin status + */ public Array getBanned(){ Array result = new Array<>(); for(PlayerInfo info : playerInfo.values()){ @@ -265,26 +296,32 @@ public class Administration { return result; } - /**Returns all banned IPs. This does not include the IPs of ID-banned players.*/ + /** + * Returns all banned IPs. This does not include the IPs of ID-banned players. + */ public Array getBannedIPs(){ return bannedIPs; } - /**Makes a player an admin. Returns whether this player was already an admin.*/ - public boolean adminPlayer(String id, String ip){ + /** + * Makes a player an admin. Returns whether this player was already an admin. + */ + public boolean adminPlayer(String id, String usid){ PlayerInfo info = getCreateInfo(id); if(info.admin) return false; - info.validAdminIP = ip; + info.adminUsid = usid; info.admin = true; save(); return true; } - /**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/ + /** + * Makes a player no longer an admin. Returns whether this player was an admin in the first place. + */ public boolean unAdminPlayer(String id){ PlayerInfo info = getCreateInfo(id); @@ -305,9 +342,9 @@ public class Administration { return getCreateInfo(uuid).banned; } - public boolean isAdmin(String id, String ip){ + public boolean isAdmin(String id, String usip){ PlayerInfo info = getCreateInfo(id); - return info.admin && ip.equals(info.validAdminIP); + return info.admin && usip.equals(info.adminUsid); } public Array findByName(String name, boolean last){ @@ -363,23 +400,23 @@ public class Administration { } public void save(){ - Settings.putString("playerInfo", json.toJson(playerInfo)); - Settings.putString("bannedIPs", json.toJson(bannedIPs)); + Settings.putJson("player-info", playerInfo); + Settings.putJson("banned-ips", bannedIPs); Settings.save(); } private void load(){ - playerInfo = json.fromJson(ObjectMap.class, Settings.getString("playerInfo")); - bannedIPs = json.fromJson(Array.class, Settings.getString("bannedIPs")); + playerInfo = Settings.getJson("player-info", ObjectMap.class); + bannedIPs = Settings.getJson("banned-ips", Array.class); } public static class PlayerInfo{ public String id; public String lastName = "", lastIP = ""; - public String validAdminIP; public Array ips = new Array<>(); public Array names = new Array<>(); - public int timesKicked; //TODO not implemented! + public String adminUsid; + public int timesKicked; public int timesJoined; public int totalBlockPlaced; public int totalBlocksBroken; @@ -392,7 +429,8 @@ public class Administration { this.id = id; } - private PlayerInfo(){} + private PlayerInfo(){ + } } } diff --git a/core/src/io/anuke/mindustry/net/ClientDebug.java b/core/src/io/anuke/mindustry/net/ClientDebug.java deleted file mode 100644 index 60b0eb0bdf..0000000000 --- a/core/src/io/anuke/mindustry/net/ClientDebug.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.anuke.mindustry.net; - -import com.badlogic.gdx.utils.OrderedMap; -import com.badlogic.gdx.utils.TimeUtils; -import com.badlogic.gdx.utils.reflect.ClassReflection; - -public class ClientDebug { - private OrderedMap, Long> last = new OrderedMap<>(); - private int syncPlayers = 0; - private int syncEnemies = 0; - - public void handle(Object packet){ - last.put(packet.getClass(), TimeUtils.millis()); - } - - public void setSyncDebug(int players, int enemies){ - this.syncEnemies = enemies; - this.syncPlayers = players; - } - - public String getOut(){ - StringBuilder build = new StringBuilder(); - for(Class type : last.orderedKeys()){ - build.append(elapsed(type)); - build.append("\n"); - } - build.append("sync.players: "); - build.append(syncPlayers); - build.append("\n"); - build.append("sync.enemies: "); - build.append(syncEnemies); - build.append("\n"); - return build.toString(); - } - - private String elapsed(Class type){ - long t = last.get(type, -1L); - if(t == -1){ - return ClassReflection.getSimpleName(type) + ": "; - }else{ - float el = TimeUtils.timeSinceMillis(t) / 1000f; - String tu; - if(el > 1f){ - tu = (int)el + "s"; - }else{ - tu = (int)(el * 60) + "f"; - } - return ClassReflection.getSimpleName(type) + ": " + tu; - } - } -} diff --git a/core/src/io/anuke/mindustry/net/EditLog.java b/core/src/io/anuke/mindustry/net/EditLog.java index e9bbeec043..ab922ae8e0 100644 --- a/core/src/io/anuke/mindustry/net/EditLog.java +++ b/core/src/io/anuke/mindustry/net/EditLog.java @@ -1,26 +1,21 @@ package io.anuke.mindustry.net; -import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.world.Block; -public class EditLog { - public String playername; - public Block block; - public int rotation; - public EditAction action; - - EditLog(String playername, Block block, int rotation, EditAction action) { - this.playername = playername; - this.block = block; - this.rotation = rotation; - this.action = action; - } - - public String info() { - return String.format("Player: %s, Block: %s, Rotation: %s, Edit Action: %s", playername, block.name(), rotation, action.toString()); - } - - public enum EditAction { - PLACE, BREAK; - } +public class EditLog{ + public String playername; + public Block block; + public int rotation; + public EditAction action; + + EditLog(String playername, Block block, int rotation, EditAction action){ + this.playername = playername; + this.block = block; + this.rotation = rotation; + this.action = action; + } + + public enum EditAction{ + PLACE, BREAK + } } diff --git a/core/src/io/anuke/mindustry/net/Host.java b/core/src/io/anuke/mindustry/net/Host.java index 631f74a726..36b5a5ecb9 100644 --- a/core/src/io/anuke/mindustry/net/Host.java +++ b/core/src/io/anuke/mindustry/net/Host.java @@ -1,6 +1,6 @@ package io.anuke.mindustry.net; -public class Host { +public class Host{ public final String name; public final String address; public final String mapname; diff --git a/core/src/io/anuke/mindustry/net/In.java b/core/src/io/anuke/mindustry/net/In.java new file mode 100644 index 0000000000..d00e0aba41 --- /dev/null +++ b/core/src/io/anuke/mindustry/net/In.java @@ -0,0 +1,10 @@ +package io.anuke.mindustry.net; + +/** + * Stores class nameas for remote method invocation for consistency's sake. + */ +public class In{ + public static final String normal = "Call"; + public static final String entities = "CallEntity"; + public static final String blocks = "CallBlocks"; +} diff --git a/core/src/io/anuke/mindustry/net/Interpolator.java b/core/src/io/anuke/mindustry/net/Interpolator.java new file mode 100644 index 0000000000..9748544b28 --- /dev/null +++ b/core/src/io/anuke/mindustry/net/Interpolator.java @@ -0,0 +1,66 @@ +package io.anuke.mindustry.net; + + +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.ucore.util.Mathf; + +public class Interpolator{ + //used for movement + public Vector2 target = new Vector2(); + public Vector2 last = new Vector2(); + public float[] targets = {}; + public long lastUpdated, updateSpacing; + + //current state + public Vector2 pos = new Vector2(); + public float[] values = {}; + + public void read(float cx, float cy, float x, float y, long sent, float... target1ds){ + if(lastUpdated != 0) updateSpacing = TimeUtils.timeSinceMillis(lastUpdated); + + lastUpdated = sent; + + targets = target1ds; + last.set(cx, cy); + target.set(x, y); + } + + public void reset(){ + values = new float[0]; + targets = new float[0]; + target.setZero(); + last.setZero(); + lastUpdated = 0; + updateSpacing = 16; //1 frame + pos.setZero(); + } + + public void update(){ + + /* + if(pos.dst(target) > 128){ + pos.set(target); + lastUpdated = 0; + updateSpacing = 16; + }*/ + + if(lastUpdated != 0 && updateSpacing != 0){ + float timeSinceUpdate = TimeUtils.timeSinceMillis(lastUpdated); + float alpha = Math.min(timeSinceUpdate / updateSpacing, 2f); + + Mathf.lerp2(pos.set(last), target, alpha); + + if(values.length != targets.length){ + values = new float[targets.length]; + } + + for(int i = 0; i < values.length; i++){ + values[i] = Mathf.slerp(values[i], targets[i], alpha); + } + }else{ + pos.set(target); + } + + } +} \ No newline at end of file diff --git a/core/src/io/anuke/mindustry/net/Net.java b/core/src/io/anuke/mindustry/net/Net.java index 43e65dcd72..bd15fc6614 100644 --- a/core/src/io/anuke/mindustry/net/Net.java +++ b/core/src/io/anuke/mindustry/net/Net.java @@ -10,302 +10,408 @@ import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.core.Platform; -import io.anuke.mindustry.net.Packet.ImportantPacket; -import io.anuke.mindustry.net.Packet.UnimportantPacket; -import io.anuke.mindustry.net.Streamable.StreamBegin; +import io.anuke.mindustry.net.Packets.StreamBegin; +import io.anuke.mindustry.net.Packets.StreamChunk; import io.anuke.mindustry.net.Streamable.StreamBuilder; -import io.anuke.mindustry.net.Streamable.StreamChunk; import io.anuke.ucore.core.Timers; import io.anuke.ucore.function.BiConsumer; import io.anuke.ucore.function.Consumer; import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Pooling; import java.io.IOException; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.headless; +import static io.anuke.mindustry.Vars.ui; public class Net{ - private static boolean server; - private static boolean active; - private static boolean clientLoaded; - private static Array packetQueue = new Array<>(); - private static ObjectMap, Consumer> listeners = new ObjectMap<>(); - private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); - private static ObjectMap, BiConsumer> serverListeners = new ObjectMap<>(); - private static ClientProvider clientProvider; - private static ServerProvider serverProvider; + public static final Object packetPoolLock = new Object(); - private static IntMap streams = new IntMap<>(); + private static boolean server; + private static boolean active; + private static boolean clientLoaded; + private static Array packetQueue = new Array<>(); + private static ObjectMap, Consumer> clientListeners = new ObjectMap<>(); + private static ObjectMap, BiConsumer> serverListeners = new ObjectMap<>(); + private static ClientProvider clientProvider; + private static ServerProvider serverProvider; - /**Display a network error.*/ - public static void showError(String text){ - if(!headless){ - ui.showError(text); - }else{ - Log.err(text); - } - } + private static IntMap streams = new IntMap<>(); - /**Sets the client loaded status, or whether it will recieve normal packets from the server.*/ - public static void setClientLoaded(boolean loaded){ - clientLoaded = loaded; + /** + * Display a network error. + */ + public static void showError(String text){ + if(!headless){ + ui.showError(text); + }else{ + Log.err(text); + } + } - if(loaded){ - //handle all packets that were skipped while loading - for(int i = 0; i < packetQueue.size; i ++){ + /** + * Sets the client loaded status, or whether it will recieve normal packets from the server. + */ + public static void setClientLoaded(boolean loaded){ + clientLoaded = loaded; + + if(loaded){ + //handle all packets that were skipped while loading + for(int i = 0; i < packetQueue.size; i++){ Log.info("Processing {0} packet post-load.", ClassReflection.getSimpleName(packetQueue.get(i).getClass())); - handleClientReceived(packetQueue.get(i)); - } - } - //clear inbound packet queue - packetQueue.clear(); - } - - /**Connect to an address.*/ - public static void connect(String ip, int port) throws IOException{ - if(!active) { - clientProvider.connect(ip, port); - active = true; - server = false; - }else{ - throw new IOException("Already connected!"); - } - } + handleClientReceived(packetQueue.get(i)); + } + } + //clear inbound packet queue + packetQueue.clear(); + } - /**Host a server at an address*/ - public static void host(int port) throws IOException{ - serverProvider.host(port); - active = true; - server = true; + /** + * Connect to an address. + */ + public static void connect(String ip, int port) throws IOException{ + if(!active){ + clientProvider.connect(ip, port); + active = true; + server = false; + }else{ + throw new IOException("Already connected!"); + } + } - Timers.runTask(60f, Platform.instance::updateRPC); - } + /** + * Host a server at an address + */ + public static void host(int port) throws IOException{ + serverProvider.host(port); + active = true; + server = true; - /**Closes the server.*/ - public static void closeServer(){ + Timers.runTask(60f, Platform.instance::updateRPC); + } + + /** + * Closes the server. + */ + public static void closeServer(){ serverProvider.close(); server = false; active = false; } public static void disconnect(){ - clientProvider.disconnect(); - server = false; - active = false; - } + clientProvider.disconnect(); + server = false; + active = false; + } - /**Starts discovering servers on a different thread. Does not work with GWT. - * Callback is run on the main libGDX thread.*/ - public static void discoverServers(Consumer> cons){ - clientProvider.discover(cons); - } + /** + * Starts discovering servers on a different thread. Does not work with GWT. + * Callback is run on the main libGDX thread. + */ + public static void discoverServers(Consumer> cons){ + clientProvider.discover(cons); + } - /**Returns a list of all connections IDs.*/ - public static Array getConnections(){ - return (Array)serverProvider.getConnections(); - } + /** + * Returns a list of all connections IDs. + */ + public static Array getConnections(){ + return (Array) serverProvider.getConnections(); + } - /**Returns a connection by ID*/ - public static NetConnection getConnection(int id){ - return serverProvider.getByID(id); - } - - /**Send an object to all connected clients, or to the server if this is a client.*/ - public static void send(Object object, SendMode mode){ - if(server){ - if(serverProvider != null) serverProvider.send(object, mode); - }else { - if(clientProvider != null) clientProvider.send(object, mode); - } - } + /** + * Returns a connection by ID + */ + public static NetConnection getConnection(int id){ + return serverProvider.getByID(id); + } - /**Send an object to a certain client. Server-side only*/ - public static void sendTo(int id, Object object, SendMode mode){ - serverProvider.sendTo(id, object, mode); - } + /** + * Send an object to all connected clients, or to the server if this is a client. + */ + public static void send(Object object, SendMode mode){ + if(server){ + if(serverProvider != null) serverProvider.send(object, mode); + }else{ + if(clientProvider != null) clientProvider.send(object, mode); + } + } - /**Send an object to everyone EXCEPT certain client. Server-side only*/ - public static void sendExcept(int id, Object object, SendMode mode){ - serverProvider.sendExcept(id, object, mode); - } + /** + * Send an object to a certain client. Server-side only + */ + public static void sendTo(int id, Object object, SendMode mode){ + serverProvider.sendTo(id, object, mode); + } - /**Send a stream to a specific client. Server-side only.*/ - public static void sendStream(int id, Streamable stream){ - serverProvider.sendStream(id, stream); - } - - /**Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.*/ - public static void setClientProvider(ClientProvider provider){ - Net.clientProvider = provider; - } + /** + * Send an object to everyone EXCEPT certain client. Server-side only + */ + public static void sendExcept(int id, Object object, SendMode mode){ + serverProvider.sendExcept(id, object, mode); + } - /**Sets the net serverProvider, e.g. what handles hosting a server.*/ - public static void setServerProvider(ServerProvider provider){ - Net.serverProvider = provider; - } + /** + * Send a stream to a specific client. Server-side only. + */ + public static void sendStream(int id, Streamable stream){ + serverProvider.sendStream(id, stream); + } - /**Registers a common listener for when an object is recieved. Fired on both client and serve.r*/ - public static void handle(Class type, Consumer listener){ - listeners.put(type, listener); - } + /** + * Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server. + */ + public static void setClientProvider(ClientProvider provider){ + Net.clientProvider = provider; + } - /**Registers a client listener for when an object is recieved.*/ - public static void handleClient(Class type, Consumer listener){ - clientListeners.put(type, listener); - } + /** + * Sets the net serverProvider, e.g. what handles hosting a server. + */ + public static void setServerProvider(ServerProvider provider){ + Net.serverProvider = provider; + } - /**Registers a server listener for when an object is recieved.*/ - public static void handleServer(Class type, BiConsumer listener){ - serverListeners.put(type, (BiConsumer) listener); - } - - /**Call to handle a packet being recieved for the client.*/ - public static void handleClientReceived(Object object){ - if(debugNet) clientDebug.handle(object); + /** + * Registers a client listener for when an object is recieved. + */ + public static void handleClient(Class type, Consumer listener){ + clientListeners.put(type, listener); + } - if(object instanceof StreamBegin) { - StreamBegin b = (StreamBegin) object; - streams.put(b.id, new StreamBuilder(b)); - }else if(object instanceof StreamChunk) { - StreamChunk c = (StreamChunk)object; - StreamBuilder builder = streams.get(c.id); - if(builder == null){ - throw new RuntimeException("Recieved stream chunk without a StreamBegin beforehand!"); - } - builder.add(c.data); - if(builder.isDone()){ - streams.remove(builder.id); - handleClientReceived(builder.build()); - } - }else if(clientListeners.get(object.getClass()) != null || - listeners.get(object.getClass()) != null){ - if(clientLoaded || object instanceof ImportantPacket){ - if(clientListeners.get(object.getClass()) != null) clientListeners.get(object.getClass()).accept(object); - if(listeners.get(object.getClass()) != null) listeners.get(object.getClass()).accept(object); - }else if(!(object instanceof UnimportantPacket)){ - packetQueue.add(object); - Log.info("Queuing packet {0}.", ClassReflection.getSimpleName(object.getClass())); - } - }else{ - Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); - } - } + /** + * Registers a server listener for when an object is recieved. + */ + public static void handleServer(Class type, BiConsumer listener){ + serverListeners.put(type, (BiConsumer) listener); + } - /**Call to handle a packet being recieved for the server.*/ - public static void handleServerReceived(int connection, Object object){ - if(debugNet) serverDebug.handle(connection, object); + /** + * Call to handle a packet being recieved for the client. + */ + public static void handleClientReceived(Object object){ - if(serverListeners.get(object.getClass()) != null || listeners.get(object.getClass()) != null){ - if(serverListeners.get(object.getClass()) != null) serverListeners.get(object.getClass()).accept(connection, object); - if(listeners.get(object.getClass()) != null) listeners.get(object.getClass()).accept(object); - }else{ - Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); - } - } + if(object instanceof StreamBegin){ + StreamBegin b = (StreamBegin) object; + streams.put(b.id, new StreamBuilder(b)); + }else if(object instanceof StreamChunk){ + StreamChunk c = (StreamChunk) object; + StreamBuilder builder = streams.get(c.id); + if(builder == null){ + throw new RuntimeException("Recieved stream chunk without a StreamBegin beforehand!"); + } + builder.add(c.data); + if(builder.isDone()){ + streams.remove(builder.id); + handleClientReceived(builder.build()); + } + }else if(clientListeners.get(object.getClass()) != null){ - /**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */ - public static void pingHost(String address, int port, Consumer valid, Consumer failed){ - clientProvider.pingHost(address, port, valid, failed); - } + if(clientLoaded || ((object instanceof Packet) && ((Packet) object).isImportant())){ + if(clientListeners.get(object.getClass()) != null) + clientListeners.get(object.getClass()).accept(object); + synchronized(packetPoolLock){ + Pooling.free(object); + } + }else if(!((object instanceof Packet) && ((Packet) object).isUnimportant())){ + packetQueue.add(object); + Log.info("Queuing packet {0}.", ClassReflection.getSimpleName(object.getClass())); + }else{ + synchronized(packetPoolLock){ + Pooling.free(object); + } + } + }else{ + Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); + } + } - /**Update client ping.*/ - public static void updatePing(){ - clientProvider.updatePing(); - } + /** + * Call to handle a packet being recieved for the server. + */ + public static void handleServerReceived(int connection, Object object){ - /**Get the client ping. Only valid after updatePing().*/ - public static int getPing(){ - return server() ? 0 : clientProvider.getPing(); - } - - /**Whether the net is active, e.g. whether this is a multiplayer game.*/ - public static boolean active(){ - return active; - } - - /**Whether this is a server or not.*/ - public static boolean server(){ - return server && active; - } + if(serverListeners.get(object.getClass()) != null){ + if(serverListeners.get(object.getClass()) != null) + serverListeners.get(object.getClass()).accept(connection, object); + synchronized(packetPoolLock){ + Pooling.free(object); + } + }else{ + Log.err("Unhandled packet type: '{0}'!", ClassReflection.getSimpleName(object.getClass())); + } + } - /**Whether this is a client or not.*/ - public static boolean client(){ - return !server && active; - } + /** + * Pings a host in an new thread. If an error occured, failed() should be called with the exception. + */ + public static void pingHost(String address, int port, Consumer valid, Consumer failed){ + clientProvider.pingHost(address, port, valid, failed); + } - public static void dispose(){ - if(clientProvider != null) clientProvider.dispose(); - if(serverProvider != null) serverProvider.dispose(); - clientProvider = null; - serverProvider = null; - server = false; - active = false; - } + /** + * Update client ping. + */ + public static void updatePing(){ + clientProvider.updatePing(); + } - public static void http(String url, String method, Consumer listener, Consumer failure){ - HttpRequest req = new HttpRequestBuilder().newRequest() - .method(method).url(url).build(); + /** + * Get the client ping. Only valid after updatePing(). + */ + public static int getPing(){ + return server() ? 0 : clientProvider.getPing(); + } - Gdx.net.sendHttpRequest(req, new HttpResponseListener() { - @Override - public void handleHttpResponse(HttpResponse httpResponse) { - listener.accept(httpResponse.getResultAsString()); - } + /** + * Whether the net is active, e.g. whether this is a multiplayer game. + */ + public static boolean active(){ + return active; + } - @Override - public void failed(Throwable t) { - failure.accept(t); - } + /** + * Whether this is a server or not. + */ + public static boolean server(){ + return server && active; + } - @Override - public void cancelled() {} - }); - } + /** + * Whether this is a client or not. + */ + public static boolean client(){ + return !server && active; + } - /**Client implementation.*/ - public interface ClientProvider { - /**Connect to a server.*/ - void connect(String ip, int port) throws IOException; - /**Send an object to the server.*/ - void send(Object object, SendMode mode); - /**Update the ping. Should be done every second or so.*/ - void updatePing(); - /**Get ping in milliseconds. Will only be valid after a call to updatePing.*/ - int getPing(); - /**Disconnect from the server.*/ - void disconnect(); - /**Discover servers. This should run the callback regardless of whether any servers are found. Should not block. - * Callback should be run on libGDX main thread.*/ + public static void dispose(){ + if(clientProvider != null) clientProvider.dispose(); + if(serverProvider != null) serverProvider.dispose(); + clientProvider = null; + serverProvider = null; + server = false; + active = false; + } + + public static void http(String url, String method, Consumer listener, Consumer failure){ + HttpRequest req = new HttpRequestBuilder().newRequest() + .method(method).url(url).build(); + + Gdx.net.sendHttpRequest(req, new HttpResponseListener(){ + @Override + public void handleHttpResponse(HttpResponse httpResponse){ + listener.accept(httpResponse.getResultAsString()); + } + + @Override + public void failed(Throwable t){ + failure.accept(t); + } + + @Override + public void cancelled(){ + } + }); + } + + public enum SendMode{ + tcp, udp + } + + /** + * Client implementation. + */ + public interface ClientProvider{ + /** + * Connect to a server. + */ + void connect(String ip, int port) throws IOException; + + /** + * Send an object to the server. + */ + void send(Object object, SendMode mode); + + /** + * Update the ping. Should be done every second or so. + */ + void updatePing(); + + /** + * Get ping in milliseconds. Will only be valid after a call to updatePing. + */ + int getPing(); + + /** + * Disconnect from the server. + */ + void disconnect(); + + /** + * Discover servers. This should run the callback regardless of whether any servers are found. Should not block. + * Callback should be run on libGDX main thread. + */ void discover(Consumer> callback); - /**Ping a host. If an error occured, failed() should be called with the exception. */ + + /** + * Ping a host. If an error occured, failed() should be called with the exception. + */ void pingHost(String address, int port, Consumer valid, Consumer failed); - /**Close all connections.*/ - void dispose(); - } - /**Server implementation.*/ - public interface ServerProvider { - /**Host a server at specified port.*/ - void host(int port) throws IOException; - /**Sends a large stream of data to a specific client.*/ - void sendStream(int id, Streamable stream); - /**Send an object to everyone connected.*/ - void send(Object object, SendMode mode); - /**Send an object to a specific client ID.*/ - void sendTo(int id, Object object, SendMode mode); - /**Send an object to everyone except a client ID.*/ - void sendExcept(int id, Object object, SendMode mode); - /**Close the server connection.*/ - void close(); - /**Return all connected users.*/ - Array getConnections(); - /**Returns a connection by ID.*/ - NetConnection getByID(int id); - /**Close all connections.*/ - void dispose(); - } + /** + * Close all connections. + */ + void dispose(); + } - public enum SendMode{ - tcp, udp - } + /** + * Server implementation. + */ + public interface ServerProvider{ + /** + * Host a server at specified port. + */ + void host(int port) throws IOException; + + /** + * Sends a large stream of data to a specific client. + */ + void sendStream(int id, Streamable stream); + + /** + * Send an object to everyone connected. + */ + void send(Object object, SendMode mode); + + /** + * Send an object to a specific client ID. + */ + void sendTo(int id, Object object, SendMode mode); + + /** + * Send an object to everyone except a client ID. + */ + void sendExcept(int id, Object object, SendMode mode); + + /** + * Close the server connection. + */ + void close(); + + /** + * Return all connected users. + */ + Array getConnections(); + + /** + * Returns a connection by ID. + */ + NetConnection getByID(int id); + + /** + * Close all connections. + */ + void dispose(); + } } diff --git a/core/src/io/anuke/mindustry/net/NetConnection.java b/core/src/io/anuke/mindustry/net/NetConnection.java index ba6bfbf80c..9c66726434 100644 --- a/core/src/io/anuke/mindustry/net/NetConnection.java +++ b/core/src/io/anuke/mindustry/net/NetConnection.java @@ -2,15 +2,46 @@ package io.anuke.mindustry.net; import io.anuke.mindustry.net.Net.SendMode; -public abstract class NetConnection { +public abstract class NetConnection{ public final int id; public final String address; + /** + * The current base snapshot that the client is absolutely confirmed to have recieved. + * All sent snapshots should be taking the diff from this base snapshot, if it isn't null. + */ + public byte[] currentBaseSnapshot; + /** + * ID of the current base snapshot. + */ + public int currentBaseID = -1; + + public int lastSentBase = -1; + public byte[] lastSentSnapshot; + public byte[] lastSentRawSnapshot; + public int lastSentSnapshotID = -1; + + /** + * ID of last recieved client snapshot. + */ + public int lastRecievedClientSnapshot = -1; + /** + * Timestamp of last recieved snapshot. + */ + public long lastRecievedClientTime; + + public boolean hasConnected = false; + public NetConnection(int id, String address){ this.id = id; this.address = address; } + public boolean isConnected(){ + return true; + } + public abstract void send(Object object, SendMode mode); + public abstract void close(); } diff --git a/core/src/io/anuke/mindustry/net/NetEvents.java b/core/src/io/anuke/mindustry/net/NetEvents.java index c9603e92fc..01dbfe5b90 100644 --- a/core/src/io/anuke/mindustry/net/NetEvents.java +++ b/core/src/io/anuke/mindustry/net/NetEvents.java @@ -1,196 +1,37 @@ package io.anuke.mindustry.net; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.annotations.Annotations.Variant; import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.BulletType; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.TileEntity; -import io.anuke.mindustry.entities.enemies.Enemy; -import io.anuke.mindustry.net.Net.SendMode; -import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.Tile; -import io.anuke.ucore.entities.Entity; -import static io.anuke.mindustry.Vars.*; +import static io.anuke.mindustry.Vars.maxTextLength; +import static io.anuke.mindustry.Vars.playerGroup; -public class NetEvents { +public class NetEvents{ - public static void handleFriendlyFireChange(boolean enabled){ - FriendlyFireChangePacket packet = new FriendlyFireChangePacket(); - packet.enabled = enabled; + @Remote(called = Loc.server, targets = Loc.both, forward = true) + public static void sendMessage(Player player, String message){ + if(message.length() > maxTextLength){ + throw new ValidateException(player, "Player has sent a message above the text limit."); + } - netCommon.sendMessage(enabled ? "[accent]Friendly fire enabled." : "[accent]Friendly fire disabled."); - - Net.send(packet, SendMode.tcp); - } - - public static void handleGameOver(){ - Net.send(new GameOverPacket(), SendMode.tcp); - } - - public static void handleBullet(BulletType type, Entity owner, float x, float y, float angle, short damage){ - BulletPacket packet = new BulletPacket(); - packet.x = x; - packet.y = y; - packet.angle = angle; - packet.damage = damage; - packet.owner = owner.id; - packet.type = type.id; - Net.send(packet, SendMode.udp); - } - - public static void handleEnemyDeath(Enemy enemy){ - EnemyDeathPacket packet = new EnemyDeathPacket(); - packet.id = enemy.id; - Net.send(packet, SendMode.tcp); - } - - public static void handleBlockDestroyed(TileEntity entity){ - BlockDestroyPacket packet = new BlockDestroyPacket(); - packet.position = entity.tile.packedPosition(); - Net.send(packet, SendMode.tcp); - } - - public static void handleBlockDamaged(TileEntity entity){ - BlockUpdatePacket packet = new BlockUpdatePacket(); - packet.health = (int)entity.health; - packet.position = entity.tile.packedPosition(); - Net.send(packet, SendMode.udp); - } - - public static void handlePlayerDeath(){ - PlayerDeathPacket packet = new PlayerDeathPacket(); - packet.id = Vars.player.id; - Net.send(packet, SendMode.tcp); - } - - public static void handleBlockConfig(Tile tile, byte data){ - BlockConfigPacket packet = new BlockConfigPacket(); - packet.data = data; - packet.position = tile.packedPosition(); - Net.send(packet, SendMode.tcp); - } - - public static void handleBlockTap(Tile tile){ - BlockTapPacket packet = new BlockTapPacket(); - packet.position = tile.packedPosition(); - Net.send(packet, SendMode.tcp); - } - - public static void handleWeaponSwitch(){ - WeaponSwitchPacket packet = new WeaponSwitchPacket(); - packet.left = Vars.player.weaponLeft.id; - packet.right = Vars.player.weaponRight.id; - packet.playerid = Vars.player.id; - Net.send(packet, SendMode.tcp); - } - - public static void handleUpgrade(Weapon weapon){ - UpgradePacket packet = new UpgradePacket(); - packet.id = weapon.id; - Net.send(packet, SendMode.tcp); - } - - public static void handleSendMessage(String message){ - ChatPacket packet = new ChatPacket(); - packet.text = message; - packet.name = player.name; - packet.id = player.id; - Net.send(packet, SendMode.tcp); - - if(Net.server() && !headless){ - ui.chatfrag.addMessage(message, netCommon.colorizeName(player.id, player.name)); + if(Vars.ui != null){ + Vars.ui.chatfrag.addMessage(message, player == null ? null : colorizeName(player.id, player.name)); } } - public static void handleShoot(Weapon weapon, float x, float y, float angle){ - ShootPacket packet = new ShootPacket(); - packet.weaponid = weapon.id; - packet.x = x; - packet.y = y; - packet.rotation = angle; - packet.playerid = Vars.player.id; - Net.send(packet, SendMode.udp); - } - - public static void handlePlace(int x, int y, Block block, int rotation){ - PlacePacket packet = new PlacePacket(); - packet.x = (short)x; - packet.y = (short)y; - packet.rotation = (byte)rotation; - packet.playerid = Vars.player.id; - packet.block = block.id; - Net.send(packet, SendMode.tcp); - } - - public static void handleBreak(int x, int y){ - BreakPacket packet = new BreakPacket(); - packet.x = (short)x; - packet.y = (short)y; - Net.send(packet, SendMode.tcp); - } - - public static void handleTransfer(Tile tile, byte rotation, Item item){ - ItemTransferPacket packet = new ItemTransferPacket(); - packet.position = tile.packedPosition(); - packet.rotation = rotation; - packet.itemid = (byte)item.id; - Net.send(packet, SendMode.udp); - } - - public static void handleItemSet(Tile tile, Item item, byte amount){ - ItemSetPacket packet = new ItemSetPacket(); - packet.position = tile.packedPosition(); - packet.itemid = (byte)item.id; - packet.amount = amount; - Net.send(packet, SendMode.udp); - } - - public static void handleOffload(Tile tile, Item item){ - ItemOffloadPacket packet = new ItemOffloadPacket(); - packet.position = tile.packedPosition(); - packet.itemid = (byte)item.id; - Net.send(packet, SendMode.udp); - } - - public static void handleAdminSet(Player player, boolean admin){ - PlayerAdminPacket packet = new PlayerAdminPacket(); - packet.admin = admin; - packet.id = player.id; - player.isAdmin = admin; - Net.send(packet, SendMode.tcp); - } - - public static void handleAdministerRequest(Player target, AdminAction action){ - AdministerRequestPacket packet = new AdministerRequestPacket(); - packet.id = target.id; - packet.action = action; - Net.send(packet, SendMode.tcp); - } - - public static void handleTraceRequest(Player target){ - if(Net.client()) { - handleAdministerRequest(target, AdminAction.trace); - }else{ - ui.traces.show(target, netServer.admins.getTrace(Net.getConnection(target.clientid).address)); + @Remote(called = Loc.server, variants = Variant.both, forward = true) + public static void sendMessage(String message){ + if(Vars.ui != null){ + Vars.ui.chatfrag.addMessage(message, null); } } - - public static void handleBlockLogRequest(int x, int y) { - BlockLogRequestPacket packet = new BlockLogRequestPacket(); - packet.x = x; - packet.y = y; - packet.editlogs = Vars.currentEditLogs; - - Net.send(packet, SendMode.udp); - } - - public static void handleRollbackRequest(int rollbackTimes) { - RollbackRequestPacket packet = new RollbackRequestPacket(); - packet.rollbackTimes = rollbackTimes; - - Net.send(packet, SendMode.udp); + + private static String colorizeName(int id, String name){ + Player player = playerGroup.getByID(id); + if(name == null || player == null) return null; + return "[#" + player.color.toString().toUpperCase() + "]" + name; } } diff --git a/core/src/io/anuke/mindustry/net/NetworkIO.java b/core/src/io/anuke/mindustry/net/NetworkIO.java index 912881d26d..8ece7638a3 100644 --- a/core/src/io/anuke/mindustry/net/NetworkIO.java +++ b/core/src/io/anuke/mindustry/net/NetworkIO.java @@ -1,109 +1,32 @@ package io.anuke.mindustry.net; -import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Pixmap.Format; -import com.badlogic.gdx.utils.ByteArray; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.TimeUtils; +import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.game.GameMode; +import io.anuke.mindustry.game.Team; +import io.anuke.mindustry.game.TeamInfo; +import io.anuke.mindustry.game.TeamInfo.TeamData; +import io.anuke.mindustry.io.Map; +import io.anuke.mindustry.io.MapMeta; import io.anuke.mindustry.io.Version; -import io.anuke.mindustry.resource.Upgrade; -import io.anuke.mindustry.resource.Weapon; -import io.anuke.mindustry.world.*; -import io.anuke.mindustry.world.blocks.Blocks; -import io.anuke.mindustry.world.blocks.types.BlockPart; -import io.anuke.mindustry.world.blocks.types.Rock; +import io.anuke.mindustry.world.Tile; +import io.anuke.mindustry.world.blocks.BlockPart; +import io.anuke.ucore.core.Core; import io.anuke.ucore.core.Timers; import io.anuke.ucore.entities.Entities; +import io.anuke.ucore.util.Bits; import java.io.*; import java.nio.ByteBuffer; import static io.anuke.mindustry.Vars.*; -public class NetworkIO { +public class NetworkIO{ - public static void writeMap(Map map, OutputStream os){ - try(DataOutputStream stream = new DataOutputStream(os)){ - stream.writeUTF(map.name); - stream.writeBoolean(map.oreGen); - - stream.writeShort(map.getWidth()); - stream.writeShort(map.getHeight()); - - int width = map.getWidth(); - int cap = map.getWidth() * map.getHeight(); - int pos = 0; - - while(pos < cap){ - int color = map.pixmap.getPixel(pos % width, pos / width); - byte id = ColorMapper.getColorID(color); - - int length = 1; - while(true){ - if(pos >= cap || length > 254){ - break; - } - - pos ++; - - int next = map.pixmap.getPixel(pos % width, pos / width); - if(next != color){ - pos --; - break; - }else{ - length ++; - } - } - - if(id == -1) id = 2; - stream.writeByte((byte)(length > 127 ? length - 256 : length)); - stream.writeByte(id); - - pos ++; - } - }catch (IOException e){ - throw new RuntimeException(e); - } - } - - public static Map loadMap(InputStream is){ - try(DataInputStream stream = new DataInputStream(is)){ - String name = stream.readUTF(); - boolean ores = stream.readBoolean(); - - short width = stream.readShort(); - short height = stream.readShort(); - Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888); - - int pos = 0; - while(stream.available() > 0){ - int length = stream.readByte(); - byte id = stream.readByte(); - if(length < 0) length += 256; - int color = ColorMapper.getColorByID(id); - - for(int p = 0; p < length; p ++){ - pixmap.drawPixel(pos % width, pos / width,color); - pos ++; - } - } - - Map map = new Map(); - map.name = name; - map.oreGen = ores; - map.custom = true; - map.pixmap = pixmap; - map.visible = false; - map.id = -1; - - return map; - }catch (IOException e){ - throw new RuntimeException(e); - } - } - - public static void writeWorld(Player player, ByteArray upgrades, OutputStream os){ + public static void writeWorld(Player player, OutputStream os){ try(DataOutputStream stream = new DataOutputStream(os)){ @@ -112,121 +35,92 @@ public class NetworkIO { //--GENERAL STATE-- stream.writeByte(state.mode.ordinal()); //gamemode - stream.writeByte(world.getMap().custom ? -1 : world.getMap().id); //map ID + stream.writeUTF(world.getMap().name); //map name + + //write tags + ObjectMap tags = world.getMap().meta.tags; + stream.writeByte(tags.size); + for(Entry entry : tags.entries()){ + stream.writeUTF(entry.key); + stream.writeUTF(entry.value); + } stream.writeInt(state.wave); //wave stream.writeFloat(state.wavetime); //wave countdown - stream.writeInt(state.enemies); //enemy amount stream.writeBoolean(state.friendlyFire); //friendly fire state - stream.writeInt(player.id); //player remap ID - stream.writeBoolean(player.isAdmin); - //--INVENTORY-- - - for(int i = 0; i < state.inventory.getItems().length; i ++){ //items - stream.writeInt(state.inventory.getItems()[i]); - } - - stream.writeByte(upgrades.size); //upgrade data - - for(int i = 0; i < upgrades.size; i ++){ - stream.writeByte(upgrades.get(i)); - } + stream.writeInt(player.id); + player.write(stream); //--MAP DATA-- - //seed - stream.writeInt(world.getSeed()); + //map size + stream.writeShort(world.width()); + stream.writeShort(world.height()); - int totalblocks = 0; - int totalrocks = 0; + for(int i = 0; i < world.width() * world.height(); i++){ + Tile tile = world.tile(i); - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); + stream.writeByte(tile.getFloorID()); + stream.writeByte(tile.getWallID()); + stream.writeByte(tile.elevation); - if(tile.breakable()){ - if(tile.block() instanceof Rock){ - totalrocks ++; - }else{ - totalblocks ++; + if(tile.block() instanceof BlockPart){ + stream.writeByte(tile.link); + }else if(tile.entity != null){ + stream.writeByte(Bits.packByte(tile.getTeamID(), tile.getRotation())); //team + rotation + stream.writeShort((short) tile.entity.health); //health + + if(tile.entity.items != null) tile.entity.items.write(stream); + if(tile.entity.power != null) tile.entity.power.write(stream); + if(tile.entity.liquids != null) tile.entity.liquids.write(stream); + if(tile.entity.cons != null) tile.entity.cons.write(stream); + + tile.entity.write(stream); + }else if(tile.getWallID() == 0){ + int consecutives = 0; + + for(int j = i + 1; j < world.width() * world.height() && consecutives < 255; j++){ + Tile nextTile = world.tile(j); + + if(nextTile.getFloorID() != tile.getFloorID() || nextTile.getWallID() != 0 || nextTile.elevation != tile.elevation){ + break; } + + consecutives++; } + + stream.writeByte(consecutives); + i += consecutives; } } - //amount of rocks - stream.writeInt(totalrocks); - - //write all rocks - for(int x = 0; x < world.width(); x ++) { - for (int y = 0; y < world.height(); y++) { - Tile tile = world.tile(x, y); - - if (tile.block() instanceof Rock) { - stream.writeInt(tile.packedPosition()); - } + //write team data + stream.writeByte(state.teams.getTeams().size); + for(TeamData data : state.teams.getTeams()){ + stream.writeByte(data.team.ordinal()); + stream.writeBoolean(data.ally); + stream.writeShort(data.cores.size); + for(Tile tile : data.cores){ + stream.writeInt(tile.packedPosition()); } } - //tile amount - stream.writeInt(totalblocks); - - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); - - if(tile.breakable() && !(tile.block() instanceof Rock)){ - - stream.writeInt(x + y*world.width()); //tile pos - //TODO will break if block number gets over BYTE_MAX - stream.writeByte(tile.block().id); //block ID - - if(tile.block() instanceof BlockPart){ - stream.writeByte(tile.link); - } - - if(tile.entity != null){ - stream.writeShort(tile.getPackedData()); - stream.writeShort((short)tile.entity.health); //health - - //items - for(int i = 0; i < tile.entity.items.length; i ++){ - stream.writeInt(tile.entity.items[i]); - } - - //timer data - - //amount of active timers - byte times = 0; - - for(; times < tile.entity.timer.getTimes().length; times ++){ - if(tile.entity.timer.getTimes()[times] <= 1){ - break; - } - } - - stream.writeByte(times); - - for(int i = 0; i < times; i ++){ - stream.writeFloat(tile.entity.timer.getTimes()[i]); - } - - tile.entity.write(stream); - } - } - } - } - - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } - /**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/ + /** + * Return whether a custom map is expected, and thus whether the client should wait for additional data. + */ public static void loadWorld(InputStream is){ + Player player = players[0]; + + //TODO !! use map name as the network map in Maps, so getMap() isn't null. + try(DataInputStream stream = new DataInputStream(is)){ float timerTime = stream.readFloat(); long timestamp = stream.readLong(); @@ -236,109 +130,113 @@ public class NetworkIO { //general state byte mode = stream.readByte(); - byte mapid = stream.readByte(); + String map = stream.readUTF(); + ObjectMap tags = new ObjectMap<>(); + + byte tagSize = stream.readByte(); + for(int i = 0; i < tagSize; i++){ + String key = stream.readUTF(); + String value = stream.readUTF(); + tags.put(key, value); + } int wave = stream.readInt(); float wavetime = stream.readFloat(); - int enemies = stream.readInt(); + boolean friendlyfire = stream.readBoolean(); - state.enemies = enemies; state.wave = wave; state.wavetime = wavetime; state.mode = GameMode.values()[mode]; state.friendlyFire = friendlyfire; - int pid = stream.readInt(); - boolean admin = stream.readBoolean(); - - //inventory - for(int i = 0; i < state.inventory.getItems().length; i ++){ - state.inventory.getItems()[i] = stream.readInt(); - } - - ui.hudfrag.updateItems(); - - control.upgrades().getWeapons().clear(); - control.upgrades().getWeapons().add(Weapon.blaster); - - byte weapons = stream.readByte(); - - for(int i = 0; i < weapons; i ++){ - control.upgrades().getWeapons().add((Weapon) Upgrade.getByID(stream.readByte())); - } - - player.weaponLeft = player.weaponRight = control.upgrades().getWeapons().peek(); - ui.hudfrag.updateWeapons(); - Entities.clear(); - player.id = pid; - player.isAdmin = admin; + int id = stream.readInt(); + player.read(stream, TimeUtils.millis()); + player.resetID(id); player.add(); + world.beginMapLoad(); + //map + int width = stream.readShort(); + int height = stream.readShort(); - int seed = stream.readInt(); + //TODO send advanced map meta such as author, etc + //TODO scan for cores + Map currentMap = new Map(map, new MapMeta(0, new ObjectMap<>(), width, height, null), true, () -> null); + currentMap.meta.tags.clear(); + currentMap.meta.tags.putAll(tags); + world.setMap(currentMap); - world.loadMap(world.maps().getMap(mapid), seed); - renderer.clearTiles(); + Tile[][] tiles = world.createTiles(width, height); - player.set(world.getSpawnX(), world.getSpawnY()); + for(int i = 0; i < width * height; i++){ + int x = i % width, y = i / width; + byte floorid = stream.readByte(); + byte wallid = stream.readByte(); + byte elevation = stream.readByte(); - for(int x = 0; x < world.width(); x ++){ - for(int y = 0; y < world.height(); y ++){ - Tile tile = world.tile(x, y); + Tile tile = new Tile(x, y, floorid, wallid); + tile.elevation = elevation; - //remove breakables like rocks - if(tile.breakable()){ - world.tile(x, y).setBlock(Blocks.air); - } - } - } - - int rocks = stream.readInt(); - - for(int i = 0; i < rocks; i ++){ - int pos = stream.readInt(); - Tile tile = world.tile(pos % world.width(), pos / world.width()); - Block result = WorldGenerator.rocks.get(tile.floor()); - if(result != null) tile.setBlock(result); - } - - int tiles = stream.readInt(); - - for(int i = 0; i < tiles; i ++){ - int pos = stream.readInt(); - byte blockid = stream.readByte(); - - Tile tile = world.tile(pos % world.width(), pos / world.width()); - tile.setBlock(Block.getByID(blockid)); - - if(tile.block() == Blocks.blockpart){ + if(wallid == Blocks.blockpart.id){ tile.link = stream.readByte(); - } - - if(tile.entity != null){ - short data = stream.readShort(); + }else if(tile.entity != null){ + byte tr = stream.readByte(); short health = stream.readShort(); + byte team = Bits.getLeftByte(tr); + byte rotation = Bits.getRightByte(tr); + + tile.setTeam(Team.all[team]); tile.entity.health = health; - tile.setPackedData(data); + tile.setRotation(rotation); - for(int j = 0; j < tile.entity.items.length; j ++){ - tile.entity.items[j] = stream.readInt(); - } - - byte timers = stream.readByte(); - for(int time = 0; time < timers; time ++){ - tile.entity.timer.getTimes()[time] = stream.readFloat(); - } + if(tile.entity.items != null) tile.entity.items.read(stream); + if(tile.entity.power != null) tile.entity.power.read(stream); + if(tile.entity.liquids != null) tile.entity.liquids.read(stream); + if(tile.entity.cons != null) tile.entity.cons.read(stream); tile.entity.read(stream); + }else if(wallid == 0){ + int consecutives = stream.readUnsignedByte(); + + for(int j = i + 1; j < i + 1 + consecutives; j++){ + int newx = j % width, newy = j / width; + Tile newTile = new Tile(newx, newy, floorid, wallid); + newTile.elevation = elevation; + tiles[newx][newy] = newTile; + } + + i += consecutives; + } + + tiles[x][y] = tile; + } + + player.reset(); + state.teams = new TeamInfo(); + + byte teams = stream.readByte(); + for(int i = 0; i < teams; i++){ + Team team = Team.all[stream.readByte()]; + boolean ally = stream.readBoolean(); + short cores = stream.readShort(); + state.teams.add(team, ally); + + for(int j = 0; j < cores; j++){ + state.teams.get(team).cores.add(world.tile(stream.readInt())); + } + + if(team == players[0].getTeam() && cores > 0){ + Core.camera.position.set(state.teams.get(team).cores.first().drawx(), state.teams.get(team).cores.first().drawy(), 0); } } - }catch (IOException e){ + world.endMapLoad(); + + }catch(IOException e){ throw new RuntimeException(e); } } @@ -346,7 +244,7 @@ public class NetworkIO { public static ByteBuffer writeServerData(){ int maxlen = 32; - String host = (headless ? "Server" : player.name); + String host = (headless ? "Server" : players[0].name); String map = world.getMap().name; host = host.substring(0, Math.min(host.length(), maxlen)); @@ -354,10 +252,10 @@ public class NetworkIO { ByteBuffer buffer = ByteBuffer.allocate(128); - buffer.put((byte)host.getBytes().length); + buffer.put((byte) host.getBytes().length); buffer.put(host.getBytes()); - buffer.put((byte)map.getBytes().length); + buffer.put((byte) map.getBytes().length); buffer.put(map.getBytes()); buffer.putInt(playerGroup.size()); diff --git a/core/src/io/anuke/mindustry/net/Packet.java b/core/src/io/anuke/mindustry/net/Packet.java index 3c0084ba1e..ef34666dbb 100644 --- a/core/src/io/anuke/mindustry/net/Packet.java +++ b/core/src/io/anuke/mindustry/net/Packet.java @@ -1,11 +1,24 @@ package io.anuke.mindustry.net; +import com.badlogic.gdx.utils.Pool.Poolable; + import java.nio.ByteBuffer; -public interface Packet { - void read(ByteBuffer buffer); - void write(ByteBuffer buffer); +public interface Packet extends Poolable{ + default void read(ByteBuffer buffer){ + } - interface ImportantPacket{} - interface UnimportantPacket{} + default void write(ByteBuffer buffer){ + } + + default void reset(){ + } + + default boolean isImportant(){ + return false; + } + + default boolean isUnimportant(){ + return false; + } } diff --git a/core/src/io/anuke/mindustry/net/Packets.java b/core/src/io/anuke/mindustry/net/Packets.java index bd0b8b9d39..bcadd4c5cc 100644 --- a/core/src/io/anuke/mindustry/net/Packets.java +++ b/core/src/io/anuke/mindustry/net/Packets.java @@ -1,730 +1,263 @@ package io.anuke.mindustry.net; -import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Base64Coder; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.reflect.ClassReflection; -import com.badlogic.gdx.utils.reflect.ReflectionException; +import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.Vars; import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.entities.SyncEntity; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.entities.traits.BuilderTrait.BuildRequest; import io.anuke.mindustry.io.Version; -import io.anuke.mindustry.net.Packet.ImportantPacket; -import io.anuke.mindustry.net.Packet.UnimportantPacket; -import io.anuke.mindustry.resource.Item; -import io.anuke.mindustry.world.Block; -import io.anuke.ucore.entities.Entities; -import io.anuke.ucore.entities.EntityGroup; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.world.Tile; +import io.anuke.ucore.io.IOUtils; +import io.anuke.ucore.util.Mathf; + import java.nio.ByteBuffer; -/**Class for storing all packets.*/ -public class Packets { +import static io.anuke.mindustry.Vars.world; - public static class Connect implements ImportantPacket{ - public int id; - public String addressTCP; - } - - public static class Disconnect implements ImportantPacket{ - public int id; - public String addressTCP; - } - - public static class WorldData extends Streamable{ - - } - - public static class SyncPacket implements Packet, UnimportantPacket{ - public byte[] data; - - @Override - public void write(ByteBuffer buffer) { - buffer.putShort((short)data.length); - buffer.put(data); - } - - @Override - public void read(ByteBuffer buffer) { - data = new byte[buffer.getShort()]; - buffer.get(data); - } - } - - public static class BlockSyncPacket extends Streamable{ - - } - - public static class ConnectPacket implements Packet{ - public int version; - public String name; - public boolean android; - public int color; - public byte[] uuid; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(Version.build); - buffer.put((byte)name.getBytes().length); - buffer.put(name.getBytes()); - buffer.put(android ? (byte)1 : 0); - buffer.putInt(color); - buffer.put(uuid); - } - - @Override - public void read(ByteBuffer buffer) { - version = buffer.getInt(); - byte length = buffer.get(); - byte[] bytes = new byte[length]; - buffer.get(bytes); - name = new String(bytes); - android = buffer.get() == 1; - color = buffer.getInt(); - uuid = new byte[8]; - buffer.get(uuid); - } - } - - public static class ConnectConfirmPacket implements Packet{ - @Override - public void write(ByteBuffer buffer) { } - - @Override - public void read(ByteBuffer buffer) { } - } - - public static class DisconnectPacket implements Packet{ - public int playerid; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(playerid); - } - - @Override - public void read(ByteBuffer buffer) { - playerid = buffer.getInt(); - } - } - - public static class StateSyncPacket implements Packet, UnimportantPacket{ - public int[] items; - public float countdown, time; - public int enemies, wave; - public long timestamp; - - @Override - public void write(ByteBuffer buffer) { - for(int i = 0; i < items.length; i ++){ - buffer.putInt(items[i]); - } - - buffer.putFloat(countdown); - buffer.putFloat(time); - buffer.putShort((short)enemies); - buffer.putShort((short)wave); - buffer.putLong(timestamp); - } - - @Override - public void read(ByteBuffer buffer) { - items = new int[Item.getAllItems().size]; - - for(int i = 0; i < items.length; i ++){ - items[i] = buffer.getInt(); - } - - countdown = buffer.getFloat(); - time = buffer.getFloat(); - enemies = buffer.getShort(); - wave = buffer.getShort(); - timestamp = buffer.getLong(); - } - } - - public static class BlockLogRequestPacket implements Packet { - public int x; - public int y; - public Array editlogs; - - @Override - public void write(ByteBuffer buffer) { - buffer.putShort((short)x); - buffer.putShort((short)y); - buffer.putInt(editlogs.size); - for(EditLog value : editlogs) { - buffer.put((byte)value.playername.getBytes().length); - buffer.put(value.playername.getBytes()); - buffer.putInt(value.block.id); - buffer.put((byte) value.rotation); - buffer.put((byte) value.action.ordinal()); - } - } - - @Override - public void read(ByteBuffer buffer) { - x = buffer.getShort(); - y = buffer.getShort(); - editlogs = new Array<>(); - int arraySize = buffer.getInt(); - for(int a = 0; a < arraySize; a ++) { - byte length = buffer.get(); - byte[] bytes = new byte[length]; - buffer.get(bytes); - String name = new String(bytes); - - int blockid = buffer.getInt(); - int rotation = buffer.get(); - int ordinal = buffer.get(); - - editlogs.add(new EditLog(name, Block.getByID(blockid), rotation, EditLog.EditAction.values()[ordinal])); - } - } - } - - public static class RollbackRequestPacket implements Packet { - public int rollbackTimes; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(rollbackTimes); - } - - @Override - public void read(ByteBuffer buffer) { - rollbackTimes = buffer.getInt(); - } - } - - public static class PositionPacket implements Packet{ - public byte[] data; - - @Override - public void write(ByteBuffer buffer) { - buffer.put(data); - } - - @Override - public void read(ByteBuffer buffer) { - data = new byte[SyncEntity.getWriteSize(Player.class) + 8]; - buffer.get(data); - } - } - - //not a real packet. - public static class EffectPacket{ - public int id; - public float x, y, rotation; - public int color; - } - - public static class ShootPacket implements Packet, UnimportantPacket{ - public byte weaponid; - public float x, y, rotation; - public int playerid; - - @Override - public void write(ByteBuffer buffer) { - buffer.put(weaponid); - buffer.putFloat(x); - buffer.putFloat(y); - buffer.putFloat(rotation); - buffer.putInt(playerid); - } - - @Override - public void read(ByteBuffer buffer) { - weaponid = buffer.get(); - x = buffer.getFloat(); - y = buffer.getFloat(); - rotation = buffer.getFloat(); - playerid = buffer.getInt(); - } - } - - public static class BulletPacket implements Packet, UnimportantPacket{ - public int type, owner; - public float x, y, angle; - public short damage; - - @Override - public void write(ByteBuffer buffer) { - buffer.putShort((short)type); - buffer.putInt(owner); - buffer.putFloat(x); - buffer.putFloat(y); - buffer.putFloat(angle); - buffer.putShort(damage); - } - - @Override - public void read(ByteBuffer buffer) { - type = buffer.getShort(); - owner = buffer.getInt(); - x = buffer.getFloat(); - y = buffer.getFloat(); - angle = buffer.getFloat(); - damage = buffer.getShort(); - } - } - - public static class PlacePacket implements Packet{ - public int playerid; - public byte rotation; - public short x, y; - public int block; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(playerid); - buffer.put(rotation); - buffer.putShort(x); - buffer.putShort(y); - buffer.putInt(block); - } - - @Override - public void read(ByteBuffer buffer) { - playerid = buffer.getInt(); - rotation = buffer.get(); - x = buffer.getShort(); - y = buffer.getShort(); - block = buffer.getInt(); - } - } - - public static class BreakPacket implements Packet{ - public int playerid; - public short x, y; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(playerid); - buffer.putShort(x); - buffer.putShort(y); - } - - @Override - public void read(ByteBuffer buffer) { - playerid = buffer.getInt(); - x = buffer.getShort(); - y = buffer.getShort(); - } - } - - public static class EntitySpawnPacket implements Packet{ - public SyncEntity entity; - public EntityGroup group; - - @Override - public void write(ByteBuffer buffer){ - buffer.put((byte)group.getID()); - buffer.putInt(entity.id); - entity.writeSpawn(buffer); - } - - @Override - public void read(ByteBuffer buffer) { - byte groupid = buffer.get(); - int id = buffer.getInt(); - group = Entities.getGroup(groupid); - try { - entity = (SyncEntity) ClassReflection.newInstance(group.getType()); - entity.id = id; - entity.readSpawn(buffer); - entity.setNet(entity.x, entity.y); - }catch (ReflectionException e){ - throw new RuntimeException(e); - } - } - } - - public static class EnemyDeathPacket implements Packet{ - public int id; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(id); - } - - @Override - public void read(ByteBuffer buffer) { - id = buffer.getInt(); - } - } - - public static class BlockDestroyPacket implements Packet{ - public int position; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(position); - } - - @Override - public void read(ByteBuffer buffer) { - position = buffer.getInt(); - } - } - - public static class BlockUpdatePacket implements Packet{ - public int health, position; - - @Override - public void write(ByteBuffer buffer) { - buffer.putShort((short)health); - buffer.putInt(position); - } - - @Override - public void read(ByteBuffer buffer) { - health = buffer.getShort(); - position = buffer.getInt(); - } - } - - public static class ChatPacket implements Packet{ - public String name; - public String text; - public int id; - - @Override - public void write(ByteBuffer buffer) { - if(name != null) { - buffer.putShort((short) name.getBytes().length); - buffer.put(name.getBytes()); - }else{ - buffer.putShort((short)-1); - } - buffer.putShort((short)text.getBytes().length); - buffer.put(text.getBytes()); - buffer.putInt(id); - } - - @Override - public void read(ByteBuffer buffer) { - short nlength = buffer.getShort(); - if(nlength != -1) { - byte[] n = new byte[nlength]; - buffer.get(n); - name = new String(n); - } - short tlength = buffer.getShort(); - byte[] t = new byte[tlength]; - buffer.get(t); - - id = buffer.getInt(); - text = new String(t); - } - } - - public static class KickPacket implements Packet, ImportantPacket{ - public KickReason reason; - - @Override - public void write(ByteBuffer buffer) { - buffer.put((byte)reason.ordinal()); - } - - @Override - public void read(ByteBuffer buffer) { - reason = KickReason.values()[buffer.get()]; - } - } +/** + * Class for storing all packets. + */ +public class Packets{ public enum KickReason{ - kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick, fastShoot; + kick, invalidPassword, clientOutdated, serverOutdated, banned, gameover(true), recentKick, nameInUse, idInUse, fastShoot, nameEmpty, customClient; public final boolean quiet; - KickReason(){ quiet = false; } + KickReason(){ + quiet = false; + } KickReason(boolean quiet){ this.quiet = quiet; } } - public static class UpgradePacket implements Packet{ - public byte id; //weapon ID only, currently + public enum AdminAction{ + kick, ban, trace, wave + } + + public static class Connect implements Packet{ + public int id; + public String addressTCP; @Override - public void write(ByteBuffer buffer) { - buffer.put(id); - } - - @Override - public void read(ByteBuffer buffer) { - id = buffer.get(); + public boolean isImportant(){ + return true; } } - public static class WeaponSwitchPacket implements Packet{ - public int playerid; - public byte left, right; + public static class Disconnect implements Packet{ + public int id; @Override - public void write(ByteBuffer buffer) { - buffer.putInt(playerid); - buffer.put(left); - buffer.put(right); - } - - @Override - public void read(ByteBuffer buffer) { - playerid = buffer.getInt(); - left = buffer.get(); - right = buffer.get(); + public boolean isImportant(){ + return true; } } - public static class BlockTapPacket implements Packet{ - public int position; + public static class WorldStream extends Streamable{ + + } + + public static class ConnectPacket implements Packet{ + public int version; + public String name, uuid, usid; + public boolean mobile; + public int color; @Override - public void write(ByteBuffer buffer) { - buffer.putInt(position); + public void write(ByteBuffer buffer){ + buffer.putInt(Version.build); + IOUtils.writeString(buffer, name); + IOUtils.writeString(buffer, usid); + buffer.put(mobile ? (byte) 1 : 0); + buffer.putInt(color); + buffer.put(Base64Coder.decode(uuid)); } @Override - public void read(ByteBuffer buffer) { - position = buffer.getInt(); + public void read(ByteBuffer buffer){ + version = buffer.getInt(); + name = IOUtils.readString(buffer); + usid = IOUtils.readString(buffer); + mobile = buffer.get() == 1; + color = buffer.getInt(); + byte[] idbytes = new byte[8]; + buffer.get(idbytes); + uuid = new String(Base64Coder.encode(idbytes)); } } - public static class BlockConfigPacket implements Packet{ - public int position; - public byte data; + public static class InvokePacket implements Packet{ + public byte type, priority; + + public ByteBuffer writeBuffer; + public int writeLength; @Override - public void write(ByteBuffer buffer) { - buffer.putInt(position); + public void read(ByteBuffer buffer){ + type = buffer.get(); + priority = buffer.get(); + writeLength = buffer.getShort(); + byte[] bytes = new byte[writeLength]; + buffer.get(bytes); + writeBuffer = ByteBuffer.wrap(bytes); + } + + @Override + public void write(ByteBuffer buffer){ + buffer.put(type); + buffer.put(priority); + buffer.putShort((short) writeLength); + + writeBuffer.position(0); + for(int i = 0; i < writeLength; i++){ + buffer.put(writeBuffer.get()); + } + } + + @Override + public void reset(){ + priority = 0; + } + + @Override + public boolean isImportant(){ + return priority == 1; + } + + @Override + public boolean isUnimportant(){ + return priority == 2; + } + } + + public static class ClientSnapshotPacket implements Packet{ + //snapshot meta + public int lastSnapshot; + public int snapid; + public long timeSent; + //player snapshot data + public float x, y, pointerX, pointerY, rotation, baseRotation, xv, yv; + public Tile mining; + public boolean boosting, shooting; + public BuildRequest currentRequest; + + @Override + public void write(ByteBuffer buffer){ + Player player = Vars.players[0]; + + buffer.putInt(lastSnapshot); + buffer.putInt(snapid); + buffer.putLong(TimeUtils.millis()); + + buffer.putFloat(player.x); + buffer.putFloat(player.y); + buffer.putFloat(player.pointerX); + buffer.putFloat(player.pointerY); + buffer.put(player.isBoosting ? (byte) 1 : 0); + buffer.put(player.isShooting ? (byte) 1 : 0); + + buffer.put((byte) (Mathf.clamp(player.getVelocity().x, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); + buffer.put((byte) (Mathf.clamp(player.getVelocity().y, -Unit.maxAbsVelocity, Unit.maxAbsVelocity) * Unit.velocityPercision)); + //saving 4 bytes, yay? + buffer.putShort((short) (player.rotation * 2)); + buffer.putShort((short) (player.baseRotation * 2)); + + buffer.putInt(player.getMineTile() == null ? -1 : player.getMineTile().packedPosition()); + + BuildRequest request = player.getCurrentRequest(); + + if(request != null){ + buffer.put(request.remove ? (byte) 1 : 0); + buffer.putInt(world.toPacked(request.x, request.y)); + if(!request.remove){ + buffer.put((byte) request.recipe.id); + buffer.put((byte) request.rotation); + } + }else{ + buffer.put((byte) -1); + } + } + + @Override + public void read(ByteBuffer buffer){ + lastSnapshot = buffer.getInt(); + snapid = buffer.getInt(); + timeSent = buffer.getLong(); + + x = buffer.getFloat(); + y = buffer.getFloat(); + pointerX = buffer.getFloat(); + pointerY = buffer.getFloat(); + boosting = buffer.get() == 1; + shooting = buffer.get() == 1; + xv = buffer.get() / Unit.velocityPercision; + yv = buffer.get() / Unit.velocityPercision; + rotation = buffer.getShort() / 2f; + baseRotation = buffer.getShort() / 2f; + mining = world.tile(buffer.getInt()); + + byte type = buffer.get(); + if(type != -1){ + int position = buffer.getInt(); + + if(type == 1){ //remove + currentRequest = new BuildRequest(position % world.width(), position / world.width()); + }else{ //place + byte recipe = buffer.get(); + byte rotation = buffer.get(); + currentRequest = new BuildRequest(position % world.width(), position / world.width(), rotation, Recipe.getByID(recipe)); + } + }else{ + currentRequest = null; + } + } + } + + /** + * Marks the beginning of a stream. + */ + public static class StreamBegin implements Packet{ + private static int lastid; + + public int id = lastid++; + public int total; + public Class type; + + @Override + public void write(ByteBuffer buffer){ + buffer.putInt(id); + buffer.putInt(total); + buffer.put(Registrator.getID(type)); + } + + @Override + public void read(ByteBuffer buffer){ + id = buffer.getInt(); + total = buffer.getInt(); + type = (Class) Registrator.getByID(buffer.get()); + } + } + + public static class StreamChunk implements Packet{ + public int id; + public byte[] data; + + @Override + public void write(ByteBuffer buffer){ + buffer.putInt(id); + buffer.putShort((short) data.length); buffer.put(data); } @Override - public void read(ByteBuffer buffer) { - position = buffer.getInt(); - data = buffer.get(); - } - } - - public static class EntityRequestPacket implements Packet{ - public int id; - public byte group; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(id); - buffer.put(group); - } - - @Override - public void read(ByteBuffer buffer) { + public void read(ByteBuffer buffer){ id = buffer.getInt(); - group = buffer.get(); - } - } - - public static class GameOverPacket implements Packet{ - @Override - public void write(ByteBuffer buffer) { } - - @Override - public void read(ByteBuffer buffer) { } - } - - public static class FriendlyFireChangePacket implements Packet{ - public boolean enabled; - - @Override - public void write(ByteBuffer buffer) { - buffer.put(enabled ? 1 : (byte)0); - } - - @Override - public void read(ByteBuffer buffer) { - enabled = buffer.get() == 1; - } - } - - public static class PlayerDeathPacket implements Packet{ - public int id; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(id); - } - - @Override - public void read(ByteBuffer buffer) { - id = buffer.getInt(); - } - } - - public static class CustomMapPacket extends Streamable{ - - } - - public static class MapAckPacket implements Packet{ - @Override - public void write(ByteBuffer buffer) { } - - @Override - public void read(ByteBuffer buffer) { } - } - - public static class ItemTransferPacket implements Packet, UnimportantPacket{ - public int position; - public byte rotation; - public byte itemid; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt((rotation) | (position << 2)); - buffer.put(itemid); - } - - @Override - public void read(ByteBuffer buffer) { - int i = buffer.getInt(); - rotation = (byte)(i & 0x3); - position = i >> 2; - itemid = buffer.get(); - } - } - - public static class ItemSetPacket implements Packet, UnimportantPacket{ - public int position; - public byte itemid, amount; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(position); - buffer.put(itemid); - buffer.put(amount); - } - - @Override - public void read(ByteBuffer buffer) { - position = buffer.getInt(); - itemid = buffer.get(); - amount = buffer.get(); - } - } - - public static class ItemOffloadPacket implements Packet{ - public int position; - public byte itemid; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(position); - buffer.put(itemid); - } - - @Override - public void read(ByteBuffer buffer) { - position = buffer.getInt(); - itemid = buffer.get(); - } - } - - public static class NetErrorPacket implements Packet{ - public String message; - - @Override - public void write(ByteBuffer buffer) { - buffer.putShort((short)message.getBytes().length); - buffer.put(message.getBytes()); - } - - @Override - public void read(ByteBuffer buffer) { - short length = buffer.getShort(); - byte[] bytes = new byte[length]; - buffer.get(bytes); - message = new String(bytes); - } - } - - public static class PlayerAdminPacket implements Packet{ - public boolean admin; - public int id; - - @Override - public void write(ByteBuffer buffer) { - buffer.put(admin ? (byte)1 : 0); - buffer.putInt(id); - } - - @Override - public void read(ByteBuffer buffer) { - admin = buffer.get() == 1; - id = buffer.getInt(); - } - } - - public static class AdministerRequestPacket implements Packet{ - public AdminAction action; - public int id; - - @Override - public void write(ByteBuffer buffer) { - buffer.put((byte)action.ordinal()); - buffer.putInt(id); - } - - @Override - public void read(ByteBuffer buffer) { - action = AdminAction.values()[buffer.get()]; - id = buffer.getInt(); - } - } - - public enum AdminAction{ - kick, ban, trace - } - - public static class TracePacket implements Packet{ - public TraceInfo info; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(info.playerid); - buffer.putShort((short)info.ip.getBytes().length); - buffer.put(info.ip.getBytes()); - buffer.put(info.modclient ? (byte)1 : 0); - buffer.put(info.android ? (byte)1 : 0); - - buffer.putInt(info.totalBlocksBroken); - buffer.putInt(info.structureBlocksBroken); - buffer.putInt(info.lastBlockBroken.id); - - buffer.putInt(info.totalBlocksPlaced); - buffer.putInt(info.lastBlockPlaced.id); - buffer.put(Base64Coder.decode(info.uuid)); - } - - @Override - public void read(ByteBuffer buffer) { - int id = buffer.getInt(); - short iplen = buffer.getShort(); - byte[] ipb = new byte[iplen]; - buffer.get(ipb); - - info = new TraceInfo(new String(ipb)); - - info.playerid = id; - info.modclient = buffer.get() == 1; - info.android = buffer.get() == 1; - info.totalBlocksBroken = buffer.getInt(); - info.structureBlocksBroken = buffer.getInt(); - info.lastBlockBroken = Block.getByID(buffer.getInt()); - info.totalBlocksPlaced = buffer.getInt(); - info.lastBlockPlaced = Block.getByID(buffer.getInt()); - byte[] uuid = new byte[8]; - buffer.get(uuid); - - info.uuid = new String(Base64Coder.encode(uuid)); + data = new byte[buffer.getShort()]; + buffer.get(data); } } } diff --git a/core/src/io/anuke/mindustry/net/Registrator.java b/core/src/io/anuke/mindustry/net/Registrator.java index 92d6eca4b8..6dbabdcc15 100644 --- a/core/src/io/anuke/mindustry/net/Registrator.java +++ b/core/src/io/anuke/mindustry/net/Registrator.java @@ -3,58 +3,24 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.ObjectIntMap; import com.badlogic.gdx.utils.reflect.ClassReflection; import io.anuke.mindustry.net.Packets.*; -import io.anuke.mindustry.net.Streamable.StreamBegin; -import io.anuke.mindustry.net.Streamable.StreamChunk; -public class Registrator { +public class Registrator{ private static Class[] classes = { StreamBegin.class, StreamChunk.class, - WorldData.class, - SyncPacket.class, - PositionPacket.class, - ShootPacket.class, - PlacePacket.class, - BreakPacket.class, - StateSyncPacket.class, - BlockLogRequestPacket.class, - RollbackRequestPacket.class, - BlockSyncPacket.class, - BulletPacket.class, - EnemyDeathPacket.class, - BlockUpdatePacket.class, - BlockDestroyPacket.class, + WorldStream.class, ConnectPacket.class, - DisconnectPacket.class, - ChatPacket.class, - KickPacket.class, - UpgradePacket.class, - WeaponSwitchPacket.class, - BlockTapPacket.class, - BlockConfigPacket.class, - EntityRequestPacket.class, - ConnectConfirmPacket.class, - GameOverPacket.class, - FriendlyFireChangePacket.class, - PlayerDeathPacket.class, - CustomMapPacket.class, - MapAckPacket.class, - EntitySpawnPacket.class, - ItemTransferPacket.class, - ItemSetPacket.class, - ItemOffloadPacket.class, - NetErrorPacket.class, - PlayerAdminPacket.class, - AdministerRequestPacket.class, - TracePacket.class + ClientSnapshotPacket.class, + InvokePacket.class }; private static ObjectIntMap> ids = new ObjectIntMap<>(); static{ if(classes.length > 127) throw new RuntimeException("Can't have more than 127 registered classes!"); - for(int i = 0; i < classes.length; i ++){ + for(int i = 0; i < classes.length; i++){ if(!ClassReflection.isAssignableFrom(Packet.class, classes[i]) && - !ClassReflection.isAssignableFrom(Streamable.class, classes[i])) throw new RuntimeException("Not a packet: " + classes[i]); + !ClassReflection.isAssignableFrom(Streamable.class, classes[i])) + throw new RuntimeException("Not a packet: " + classes[i]); ids.put(classes[i], i); } } @@ -64,7 +30,7 @@ public class Registrator { } public static byte getID(Class type){ - return (byte)ids.get(type, -1); + return (byte) ids.get(type, -1); } public static Class[] getClasses(){ diff --git a/core/src/io/anuke/mindustry/net/ServerDebug.java b/core/src/io/anuke/mindustry/net/ServerDebug.java deleted file mode 100644 index a34b05e7c1..0000000000 --- a/core/src/io/anuke/mindustry/net/ServerDebug.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.anuke.mindustry.net; - -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.OrderedMap; -import com.badlogic.gdx.utils.TimeUtils; -import com.badlogic.gdx.utils.reflect.ClassReflection; -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.net.Packets.Disconnect; -import io.anuke.ucore.util.Log; - -import static io.anuke.mindustry.Vars.playerGroup; - -public class ServerDebug { - private IntMap, Long>> last = new IntMap<>(); - - public void handle(int connection, Object packet){ - try { - if (!last.containsKey(connection)) - last.put(connection, new OrderedMap<>()); - if (packet instanceof Disconnect) - last.remove(connection); - else - last.get(connection).put(packet.getClass(), TimeUtils.millis()); - }catch (Exception e){ - Log.err(""); - } - } - - public String getOut(){ - StringBuilder build = new StringBuilder(); - for(Player player : playerGroup.all()){ - OrderedMap, Long> map = last.get(player.clientid, new OrderedMap<>()); - build.append("connection "); - build.append(player.clientid); - build.append(" / player '"); - build.append(player.name); - build.append(" android: "); - build.append(player.isAndroid); - build.append("'\n"); - - for(Class type : map.orderedKeys()){ - build.append(" "); - build.append(elapsed(type, map)); - build.append("\n"); - } - } - return build.toString(); - } - - private String elapsed(Class type, OrderedMap, Long> last) { - long t = last.get(type, -1L); - if (t == -1) { - return ClassReflection.getSimpleName(type) + ": "; - } else { - float el = TimeUtils.timeSinceMillis(t) / 1000f; - String tu; - if (el > 1f) { - tu = (int) el + "s"; - } else { - tu = (int) (el * 60) + "f"; - } - return ClassReflection.getSimpleName(type) + ": " + tu; - } - } -} diff --git a/core/src/io/anuke/mindustry/net/Streamable.java b/core/src/io/anuke/mindustry/net/Streamable.java index 1d700b2087..a27ea42ced 100644 --- a/core/src/io/anuke/mindustry/net/Streamable.java +++ b/core/src/io/anuke/mindustry/net/Streamable.java @@ -2,56 +2,18 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.reflect.ClassReflection; import com.badlogic.gdx.utils.reflect.ReflectionException; -import io.anuke.mindustry.net.Packet.ImportantPacket; +import io.anuke.mindustry.net.Packets.StreamBegin; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -public class Streamable implements ImportantPacket{ +public class Streamable implements Packet{ public transient ByteArrayInputStream stream; - /**Marks the beginning of a stream.*/ - public static class StreamBegin implements Packet{ - private static int lastid; - - public int id = lastid ++; - public int total; - public Class type; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(id); - buffer.putInt(total); - buffer.put(Registrator.getID(type)); - } - - @Override - public void read(ByteBuffer buffer) { - id = buffer.getInt(); - total = buffer.getInt(); - type = (Class)Registrator.getByID(buffer.get()); - } - } - - public static class StreamChunk implements Packet{ - public int id; - public byte[] data; - - @Override - public void write(ByteBuffer buffer) { - buffer.putInt(id); - buffer.putShort((short)data.length); - buffer.put(data); - } - - @Override - public void read(ByteBuffer buffer) { - id = buffer.getInt(); - data = new byte[buffer.getShort()]; - buffer.get(data); - } + @Override + public boolean isImportant(){ + return true; } public static class StreamBuilder{ @@ -68,15 +30,15 @@ public class Streamable implements ImportantPacket{ } public void add(byte[] bytes){ - try { + try{ stream.write(bytes); - }catch (IOException e){ + }catch(IOException e){ throw new RuntimeException(e); } } public Streamable build(){ - try { + try{ Streamable s = ClassReflection.newInstance(type); s.stream = new ByteArrayInputStream(stream.toByteArray()); return s; diff --git a/core/src/io/anuke/mindustry/net/TraceInfo.java b/core/src/io/anuke/mindustry/net/TraceInfo.java index 699e113e8e..512c8e85ff 100644 --- a/core/src/io/anuke/mindustry/net/TraceInfo.java +++ b/core/src/io/anuke/mindustry/net/TraceInfo.java @@ -1,10 +1,10 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.IntIntMap; +import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.blocks.Blocks; -public class TraceInfo { +public class TraceInfo{ public int playerid; public String ip; public boolean modclient; diff --git a/core/src/io/anuke/mindustry/net/ValidateException.java b/core/src/io/anuke/mindustry/net/ValidateException.java new file mode 100644 index 0000000000..89923702de --- /dev/null +++ b/core/src/io/anuke/mindustry/net/ValidateException.java @@ -0,0 +1,15 @@ +package io.anuke.mindustry.net; + +import io.anuke.mindustry.entities.Player; + +/** + * Thrown when a client sends invalid information. + */ +public class ValidateException extends RuntimeException{ + public final Player player; + + public ValidateException(Player player, String s){ + super(s); + this.player = player; + } +} diff --git a/core/src/io/anuke/mindustry/resource/Item.java b/core/src/io/anuke/mindustry/resource/Item.java deleted file mode 100644 index 87959c4b61..0000000000 --- a/core/src/io/anuke/mindustry/resource/Item.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.anuke.mindustry.resource; - -import com.badlogic.gdx.graphics.g2d.TextureRegion; -import com.badlogic.gdx.utils.Array; -import io.anuke.ucore.graphics.Draw; -import io.anuke.ucore.util.Bundles; - -public class Item{ - private static final Array items = new Array<>(); - - public static final Item - stone = new Item("stone"), - iron = new Item("iron"), - coal = new Item("coal"), - steel = new Item("steel"), - titanium = new Item("titanium"), - dirium = new Item("dirium"), - uranium = new Item("uranium"), - sand = new Item("sand"); - /*glass = new Item("glass"), - silicon = new Item("silicon");*/ - - public final int id; - public final String name; - public TextureRegion region; - public float explosiveness = 0f; - public float flammability = 0f; - - public Item(String name) { - this.id = items.size; - this.name = name; - - items.add(this); - } - - public void init(){ - this.region = Draw.region("icon-" + name); - } - - public String localizedName(){ - return Bundles.get("item." + this.name + ".name"); - } - - @Override - public String toString() { - return localizedName(); - } - - public static Array getAllItems() { - return Item.items; - } - - public static Item getByID(int id){ - return items.get(id); - } -} diff --git a/core/src/io/anuke/mindustry/resource/ItemStack.java b/core/src/io/anuke/mindustry/resource/ItemStack.java deleted file mode 100644 index 9d6bceb017..0000000000 --- a/core/src/io/anuke/mindustry/resource/ItemStack.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.anuke.mindustry.resource; - -public class ItemStack{ - public Item item; - public int amount; - public float pos; - - public ItemStack(Item item, int amount){ - this.item = item; - this.amount = amount; - } - - public boolean equals(ItemStack other){ - return other != null && other.item == item && other.amount == amount; - } -} diff --git a/core/src/io/anuke/mindustry/resource/Liquid.java b/core/src/io/anuke/mindustry/resource/Liquid.java deleted file mode 100644 index 5bf97660b3..0000000000 --- a/core/src/io/anuke/mindustry/resource/Liquid.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.anuke.mindustry.resource; - -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.Array; -import io.anuke.ucore.util.Bundles; - -public class Liquid { - - private static final Array liquids = new Array<>(); - - public static final Liquid - water = new Liquid("water", Color.ROYAL), - plasma = new Liquid("plasma", Color.CORAL), - lava = new Liquid("lava", Color.valueOf("ed5334")), - oil = new Liquid("oil", Color.valueOf("292929")), - cryofluid = new Liquid("cryofluid", Color.SKY); - - public final Color color; - public final String name; - public final int id; - - public Liquid(String name, Color color) { - this.name = name; - this.color = new Color(color); - - this.id = liquids.size; - - Liquid.liquids.add(this); - } - - public String localizedName(){ - return Bundles.get("liquid."+ this.name + ".name"); - } - - @Override - public String toString(){ - return localizedName(); - } - - public static Array getAllLiquids() { - return Liquid.liquids; - } - - public static Liquid getByID(int id){ - return liquids.get(id); - } -} diff --git a/core/src/io/anuke/mindustry/resource/Mech.java b/core/src/io/anuke/mindustry/resource/Mech.java deleted file mode 100644 index 57a9bf4800..0000000000 --- a/core/src/io/anuke/mindustry/resource/Mech.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.anuke.mindustry.resource; - -public class Mech extends Upgrade{ - public static final Mech - - standard = new Mech("standard"); - - public Mech(String name){ - super(name); - } -} diff --git a/core/src/io/anuke/mindustry/resource/Recipe.java b/core/src/io/anuke/mindustry/resource/Recipe.java deleted file mode 100644 index e7e1910028..0000000000 --- a/core/src/io/anuke/mindustry/resource/Recipe.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.anuke.mindustry.resource; - -import io.anuke.mindustry.world.Block; - -public class Recipe { - public Block result; - public ItemStack[] requirements; - public Section section; - public boolean desktopOnly = false; - - public Recipe(Section section, Block result, ItemStack... requirements){ - this.result = result; - this.requirements = requirements; - this.section = section; - } - - public Recipe setDesktop(){ - desktopOnly = true; - return this; - } -} diff --git a/core/src/io/anuke/mindustry/resource/Recipes.java b/core/src/io/anuke/mindustry/resource/Recipes.java deleted file mode 100644 index a9b750c734..0000000000 --- a/core/src/io/anuke/mindustry/resource/Recipes.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.anuke.mindustry.resource; - -import com.badlogic.gdx.utils.Array; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.world.Block; -import io.anuke.mindustry.world.blocks.DefenseBlocks; -import io.anuke.mindustry.world.blocks.DistributionBlocks; -import io.anuke.mindustry.world.blocks.ProductionBlocks; -import io.anuke.mindustry.world.blocks.WeaponBlocks; - -import static io.anuke.mindustry.resource.Section.*; - -public class Recipes { - private static final Array list = Array.with( - new Recipe(defense, DefenseBlocks.stonewall, stack(Item.stone, 12)), - new Recipe(defense, DefenseBlocks.ironwall, stack(Item.iron, 12)), - new Recipe(defense, DefenseBlocks.steelwall, stack(Item.steel, 12)), - new Recipe(defense, DefenseBlocks.titaniumwall, stack(Item.titanium, 12)), - new Recipe(defense, DefenseBlocks.diriumwall, stack(Item.dirium, 12)), - new Recipe(defense, DefenseBlocks.steelwalllarge, stack(Item.steel, 12*4)), - new Recipe(defense, DefenseBlocks.titaniumwalllarge, stack(Item.titanium, 12*4)), - new Recipe(defense, DefenseBlocks.diriumwalllarge, stack(Item.dirium, 12*4)), - new Recipe(defense, DefenseBlocks.door, stack(Item.steel, 3), stack(Item.iron, 3*4)).setDesktop(), - new Recipe(defense, DefenseBlocks.largedoor, stack(Item.steel, 3*4), stack(Item.iron, 3*4*4)).setDesktop(), - new Recipe(defense, DefenseBlocks.titaniumshieldwall, stack(Item.titanium, 16)), - - new Recipe(distribution, DistributionBlocks.conveyor, stack(Item.stone, 1)), - new Recipe(distribution, DistributionBlocks.steelconveyor, stack(Item.steel, 1)), - new Recipe(distribution, DistributionBlocks.pulseconveyor, stack(Item.dirium, 1)), - new Recipe(distribution, DistributionBlocks.router, stack(Item.stone, 2)), - new Recipe(distribution, DistributionBlocks.junction, stack(Item.iron, 2)), - new Recipe(distribution, DistributionBlocks.tunnel, stack(Item.iron, 2)), - new Recipe(distribution, DistributionBlocks.conduit, stack(Item.steel, 1)), - new Recipe(distribution, DistributionBlocks.pulseconduit, stack(Item.titanium, 1), stack(Item.steel, 1)), - new Recipe(distribution, DistributionBlocks.liquidrouter, stack(Item.steel, 2)), - new Recipe(distribution, DistributionBlocks.liquidjunction, stack(Item.steel, 2)), - new Recipe(distribution, DistributionBlocks.sorter, stack(Item.steel, 2)), - - new Recipe(weapon, WeaponBlocks.turret, stack(Item.stone, 4)), - new Recipe(weapon, WeaponBlocks.doubleturret, stack(Item.stone, 7)), - new Recipe(weapon, WeaponBlocks.machineturret, stack(Item.iron, 8), stack(Item.stone, 10)), - new Recipe(weapon, WeaponBlocks.shotgunturret, stack(Item.iron, 10), stack(Item.stone, 10)), - new Recipe(weapon, WeaponBlocks.flameturret, stack(Item.iron, 12), stack(Item.steel, 9)), - new Recipe(weapon, WeaponBlocks.sniperturret, stack(Item.iron, 15), stack(Item.steel, 10)), - new Recipe(weapon, WeaponBlocks.laserturret, stack(Item.steel, 12), stack(Item.titanium, 12)), - new Recipe(weapon, WeaponBlocks.mortarturret, stack(Item.steel, 25), stack(Item.titanium, 15)), - new Recipe(weapon, WeaponBlocks.teslaturret, stack(Item.steel, 20), stack(Item.titanium, 25), stack(Item.dirium, 15)), - new Recipe(weapon, WeaponBlocks.plasmaturret, stack(Item.steel, 10), stack(Item.titanium, 20), stack(Item.dirium, 15)), - new Recipe(weapon, WeaponBlocks.chainturret, stack(Item.steel, 50), stack(Item.titanium, 25), stack(Item.dirium, 40)), - new Recipe(weapon, WeaponBlocks.titanturret, stack(Item.steel, 70), stack(Item.titanium, 50), stack(Item.dirium, 55)), - - new Recipe(crafting, ProductionBlocks.smelter, stack(Item.stone, 40), stack(Item.iron, 40)), - new Recipe(crafting, ProductionBlocks.crucible, stack(Item.titanium, 50), stack(Item.steel, 50)), - new Recipe(crafting, ProductionBlocks.coalpurifier, stack(Item.steel, 10), stack(Item.iron, 10)), - new Recipe(crafting, ProductionBlocks.titaniumpurifier, stack(Item.steel, 30), stack(Item.iron, 30)), - new Recipe(crafting, ProductionBlocks.oilrefinery, stack(Item.steel, 15), stack(Item.iron, 15)), - new Recipe(crafting, ProductionBlocks.stoneformer, stack(Item.steel, 10), stack(Item.iron, 10)), - new Recipe(crafting, ProductionBlocks.lavasmelter, stack(Item.steel, 30), stack(Item.titanium, 15)), - new Recipe(crafting, ProductionBlocks.weaponFactory, stack(Item.steel, 60), stack(Item.iron, 60)).setDesktop(), - - new Recipe(production, ProductionBlocks.stonedrill, stack(Item.stone, 12)), - new Recipe(production, ProductionBlocks.irondrill, stack(Item.stone, 25)), - new Recipe(production, ProductionBlocks.coaldrill, stack(Item.stone, 25), stack(Item.iron, 40)), - new Recipe(production, ProductionBlocks.titaniumdrill, stack(Item.iron, 50), stack(Item.steel, 50)), - new Recipe(production, ProductionBlocks.uraniumdrill, stack(Item.iron, 40), stack(Item.steel, 40)), - new Recipe(production, ProductionBlocks.omnidrill, stack(Item.titanium, 40), stack(Item.dirium, 40)), - - new Recipe(power, ProductionBlocks.coalgenerator, stack(Item.iron, 30), stack(Item.stone, 20)), - new Recipe(power, ProductionBlocks.thermalgenerator, stack(Item.steel, 30), stack(Item.iron, 30)), - new Recipe(power, ProductionBlocks.combustiongenerator, stack(Item.iron, 30), stack(Item.stone, 20)), - new Recipe(power, ProductionBlocks.rtgenerator, stack(Item.titanium, 20), stack(Item.steel, 20)), - new Recipe(power, ProductionBlocks.nuclearReactor, stack(Item.titanium, 40), stack(Item.dirium, 40), stack(Item.steel, 50)), - new Recipe(power, DistributionBlocks.powerbooster, stack(Item.steel, 8), stack(Item.iron, 8)), - new Recipe(power, DistributionBlocks.powerlaser, stack(Item.steel, 3), stack(Item.iron, 3)), - new Recipe(power, DistributionBlocks.powerlasercorner, stack(Item.steel, 4), stack(Item.iron, 4)), - new Recipe(power, DistributionBlocks.powerlaserrouter, stack(Item.steel, 5), stack(Item.iron, 5)), - - new Recipe(power, DefenseBlocks.shieldgenerator, stack(Item.titanium, 30), stack(Item.dirium, 30)), - - new Recipe(distribution, DistributionBlocks.teleporter, stack(Item.steel, 30), stack(Item.dirium, 40)), - - new Recipe(power, DefenseBlocks.repairturret, stack(Item.iron, 30)), - new Recipe(power, DefenseBlocks.megarepairturret, stack(Item.iron, 20), stack(Item.steel, 30)), - - new Recipe(production, ProductionBlocks.pump, stack(Item.steel, 10)), - new Recipe(production, ProductionBlocks.fluxpump, stack(Item.steel, 10), stack(Item.dirium, 5)) - ); - - private static ItemStack stack(Item item, int amount){ - return new ItemStack(item, amount); - } - - public static Array all(){ - return list; - } - - public static Recipe getByResult(Block block){ - for(Recipe recipe : list){ - if(recipe.result == block){ - return recipe; - } - } - return null; - } - - public static Array getBy(Section section, Array r){ - for(Recipe recipe : list){ - if(recipe.section == section && !(Vars.mobile && recipe.desktopOnly)) - r.add(recipe); - } - - return r; - } -} diff --git a/core/src/io/anuke/mindustry/resource/Section.java b/core/src/io/anuke/mindustry/resource/Section.java deleted file mode 100644 index feb383eca8..0000000000 --- a/core/src/io/anuke/mindustry/resource/Section.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.anuke.mindustry.resource; - -public enum Section{ - weapon, production, distribution, power, defense, crafting; -} diff --git a/core/src/io/anuke/mindustry/resource/Upgrade.java b/core/src/io/anuke/mindustry/resource/Upgrade.java deleted file mode 100644 index 423528a483..0000000000 --- a/core/src/io/anuke/mindustry/resource/Upgrade.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.anuke.mindustry.resource; - -import com.badlogic.gdx.utils.Array; -import io.anuke.ucore.util.Bundles; - -public abstract class Upgrade { - private static Array upgrades = new Array<>(); - private static byte lastid; - - public final byte id; - public final String name; - public final String description; - - public Upgrade(String name){ - this.id = lastid ++; - this.name = name; - this.description = Bundles.getNotNull("upgrade."+name+".description"); - - upgrades.add(this); - } - - public String localized(){ - return Bundles.get("upgrade." + name + ".name"); - } - - public static Upgrade getByID(byte id){ - return upgrades.get(id); - } - - public static Array getAllUpgrades() { - return upgrades; - } -} diff --git a/core/src/io/anuke/mindustry/resource/UpgradeRecipes.java b/core/src/io/anuke/mindustry/resource/UpgradeRecipes.java deleted file mode 100644 index 03566c51ee..0000000000 --- a/core/src/io/anuke/mindustry/resource/UpgradeRecipes.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.anuke.mindustry.resource; - -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectMap.Entries; -import io.anuke.ucore.util.Mathf; - -public class UpgradeRecipes { - private static final ObjectMap recipes = Mathf.map( - Weapon.triblaster, list(stack(Item.iron, 60), stack(Item.steel, 80)), - Weapon.clustergun, list(stack(Item.iron, 300), stack(Item.steel, 80)), - Weapon.vulcan, list(stack(Item.iron, 100), stack(Item.steel, 150), stack(Item.titanium, 80)), - Weapon.beam, list(stack(Item.steel, 260), stack(Item.titanium, 160), stack(Item.dirium, 120)), - Weapon.shockgun, list(stack(Item.steel, 240), stack(Item.titanium, 160), stack(Item.dirium, 160)) - ); - - private static final ItemStack[] empty = {}; - - public static ItemStack[] get(Upgrade upgrade){ - return recipes.get(upgrade, empty); - } - - public static Entries getAllRecipes(){ - return recipes.entries(); - } - - private static ItemStack[] list(ItemStack... stacks){ - return stacks; - } - - private static ItemStack stack(Item item, int amount){ - return new ItemStack(item, amount); - } -} diff --git a/core/src/io/anuke/mindustry/resource/Weapon.java b/core/src/io/anuke/mindustry/resource/Weapon.java deleted file mode 100644 index e6f2c19e2f..0000000000 --- a/core/src/io/anuke/mindustry/resource/Weapon.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.anuke.mindustry.resource; - -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.entities.Bullet; -import io.anuke.mindustry.entities.BulletType; -import io.anuke.mindustry.entities.Player; -import io.anuke.mindustry.graphics.Fx; -import io.anuke.mindustry.net.Net; -import io.anuke.mindustry.net.NetEvents; -import io.anuke.ucore.core.Effects; -import io.anuke.ucore.core.Effects.Effect; -import io.anuke.ucore.entities.Entity; -import io.anuke.ucore.util.Angles; -import io.anuke.ucore.util.Mathf; -import io.anuke.ucore.util.Translator; - -public class Weapon extends Upgrade{ - public static final Weapon - - blaster = new Weapon("blaster", 12, BulletType.shot){ - { - effect = Fx.laserShoot; - length = 2f; - } - }, - triblaster = new Weapon("triblaster", 16, BulletType.spread){ - { - shots = 3; - effect = Fx.spreadShoot; - roundrobin = true; - } - }, - clustergun = new Weapon("clustergun", 26f, BulletType.cluster){ - { - effect = Fx.clusterShoot; - inaccuracy = 17f; - roundrobin = true; - shots = 2; - spacing = 0; - } - }, - beam = new Weapon("beam", 30f, BulletType.beamlaser){ - { - effect = Fx.beamShoot; - inaccuracy = 0; - roundrobin = true; - shake = 2f; - } - }, - vulcan = new Weapon("vulcan", 5, BulletType.vulcan){ - { - effect = Fx.vulcanShoot; - inaccuracy = 5; - roundrobin = true; - shake = 1f; - inaccuracy = 4f; - } - }, - shockgun = new Weapon("shockgun", 36, BulletType.shockshell){ - { - shootsound = "bigshot"; - effect = Fx.shockShoot; - shake = 2f; - roundrobin = true; - shots = 7; - inaccuracy = 15f; - length = 3.5f; - } - }; - /**weapon reload in frames*/ - float reload; - /**type of bullet shot*/ - BulletType type; - /**sound made when shooting*/ - String shootsound = "shoot"; - /**amount of shots per fire*/ - int shots = 1; - /**spacing in degrees between multiple shots, if applicable*/ - float spacing = 12f; - /**inaccuracy of degrees of each shot*/ - float inaccuracy = 0f; - /**intensity and duration of each shot's screen shake*/ - float shake = 0f; - /**effect displayed when shooting*/ - Effect effect; - /**shoot barrel length*/ - float length = 3f; - /**whether to shoot the weapons in different arms one after another, rather an all at once*/ - boolean roundrobin = false; - /**translator for vector calulations*/ - Translator tr = new Translator(); - - private Weapon(String name, float reload, BulletType type){ - super(name); - this.reload = reload; - this.type = type; - } - - public void update(Player p, boolean left){ - int t = left ? 1 : 2; - int t2 = !left ? 1 : 2; - if(p.timer.get(t, reload)){ - if(roundrobin){ - p.timer.reset(t2, reload/2f); - } - float ang = Angles.mouseAngle(p.x, p.y); - tr.trns(ang - 90, 3f * Mathf.sign(left), length); - shoot(p, p.x + tr.x, p.y + tr.y, Angles.mouseAngle(p.x + tr.x, p.y + tr.y)); - } - } - - void shootInternal(Player p, float x, float y, float rotation){ - Angles.shotgun(shots, spacing, rotation, f -> bullet(p, x, y, f + Mathf.range(inaccuracy))); - tr.trns(rotation, 3f); - if(effect != null) Effects.effect(effect, x + tr.x, y + tr.y, rotation); - Effects.shake(shake, shake, x, y); - Effects.sound(shootsound, x, y); - } - - public float getReload(){ - return reload; - } - - public void shoot(Player p, float x, float y, float angle){ - shootInternal(p, x, y, angle); - - if(Net.active() && p == Vars.player){ - NetEvents.handleShoot(this, x, y, angle); - } - } - - void bullet(Entity owner, float x, float y, float angle){ - tr.trns(angle, 3f); - new Bullet(type, owner, x + tr.x, y + tr.y, angle).add(); - } -} diff --git a/core/src/io/anuke/mindustry/type/AmmoEntry.java b/core/src/io/anuke/mindustry/type/AmmoEntry.java new file mode 100644 index 0000000000..a4e0559df1 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/AmmoEntry.java @@ -0,0 +1,14 @@ +package io.anuke.mindustry.type; + +/** + * Used to store ammo amounts in units and turrets. + */ +public class AmmoEntry{ + public AmmoType type; + public int amount; + + public AmmoEntry(AmmoType type, int amount){ + this.type = type; + this.amount = amount; + } +} diff --git a/core/src/io/anuke/mindustry/type/AmmoType.java b/core/src/io/anuke/mindustry/type/AmmoType.java new file mode 100644 index 0000000000..90a5eb0601 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/AmmoType.java @@ -0,0 +1,112 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.bullet.BulletType; +import io.anuke.mindustry.game.Content; +import io.anuke.ucore.core.Effects.Effect; + +public class AmmoType implements Content{ + private static int lastID = 0; + private static Array allTypes = new Array<>(32); + + public final byte id; + /** + * The item used. Always null if liquid isn't. + */ + public final Item item; + /** + * The liquid used. Always null if item isn't. + */ + public final Liquid liquid; + /** + * The resulting bullet. Never null. + */ + public final BulletType bullet; + /** + * For item ammo, this is amount given per ammo item. + * For liquid ammo, this is amount used per shot. + */ + public final float quantityMultiplier; + /** + * Reload speed multiplier. + */ + public float reloadMultiplier = 1f; + /** + * Bullet recoil strength. + */ + public float recoil = 0f; + /** + * Additional inaccuracy in degrees. + */ + public float inaccuracy; + /** + * Effect created when shooting. + */ + public Effect shootEffect = Fx.none; + /** + * Extra smoke effect created when shooting. + */ + public Effect smokeEffect = Fx.none; + + { + this.id = (byte) (lastID++); + allTypes.add(this); + } + + /** + * Creates an AmmoType with no liquid or item. Used for power-based ammo. + */ + public AmmoType(BulletType result){ + this.item = null; + this.liquid = null; + this.bullet = result; + this.quantityMultiplier = 1f; + this.reloadMultiplier = 1f; + } + + /** + * Creates an AmmoType with an item. + */ + public AmmoType(Item item, BulletType result, float multiplier){ + this.item = item; + this.liquid = null; + this.bullet = result; + this.quantityMultiplier = multiplier; + } + + /** + * Creates an AmmoType with a liquid. + */ + public AmmoType(Liquid liquid, BulletType result, float multiplier){ + this.item = null; + this.liquid = liquid; + this.bullet = result; + this.quantityMultiplier = multiplier; + } + + public static Array all(){ + return allTypes; + } + + public static AmmoType getByID(int id){ + return allTypes.get(id); + } + + /** + * Returns maximum distance the bullet this ammo type has can travel. + */ + public float getRange(){ + return bullet.speed * bullet.lifetime; + } + + @Override + public String getContentTypeName(){ + return "ammotype"; + } + + @Override + public Array getAll(){ + return allTypes; + } +} diff --git a/core/src/io/anuke/mindustry/type/Category.java b/core/src/io/anuke/mindustry/type/Category.java new file mode 100644 index 0000000000..6d75054b2f --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Category.java @@ -0,0 +1,5 @@ +package io.anuke.mindustry.type; + +public enum Category{ + weapon, production, distribution, liquid, power, defense, crafting, units +} diff --git a/core/src/io/anuke/mindustry/type/ContentList.java b/core/src/io/anuke/mindustry/type/ContentList.java new file mode 100644 index 0000000000..edeb482dca --- /dev/null +++ b/core/src/io/anuke/mindustry/type/ContentList.java @@ -0,0 +1,19 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; + +/** + * Interface for a list of content to be loaded in {@link io.anuke.mindustry.core.ContentLoader}. + */ +public interface ContentList{ + /** + * This method should create all the content. + */ + void load(); + + /** + * This method should return the list of the content of this type, for further loading. + */ + Array getAll(); +} diff --git a/core/src/io/anuke/mindustry/type/Item.java b/core/src/io/anuke/mindustry/type/Item.java new file mode 100644 index 0000000000..18653372c6 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Item.java @@ -0,0 +1,125 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.ui.ContentDisplay; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Strings; +import io.anuke.ucore.util.ThreadArray; + +public class Item implements Comparable, UnlockableContent{ + private static final ThreadArray items = new ThreadArray<>(); + + public final int id; + public final String name; + public final String description; + public final Color color; + public TextureRegion region; + + /** + * type of the item; used for tabs and core acceptance. default value is {@link ItemType#resource}. + */ + public ItemType type = ItemType.resource; + /** + * how explosive this item is. + */ + public float explosiveness = 0f; + /** + * flammability above 0.3 makes this eleigible for item burners. + */ + public float flammability = 0f; + /** + * how radioactive this item is. 0=none, 1=chernobyl ground zero + */ + public float radioactivity; + /** + * how effective this item is as flux for smelting. 0 = not a flux, 0.5 = normal flux, 1 = very good + */ + public float fluxiness = 0f; + /** + * drill hardness of the item + */ + public int hardness = 0; + /** + * the burning color of this item + */ + public Color flameColor = Palette.darkFlame.cpy(); + /** + * base material cost of this item, used for calculating place times + * 1 cost = 1 tick added to build time + */ + public float cost = 3f; + + public Item(String name, Color color){ + this.id = items.size; + this.name = name; + this.color = color; + this.description = Bundles.getOrNull("item." + this.name + ".description"); + + items.add(this); + + if(!Bundles.has("item." + this.name + ".name")){ + Log.err("Warning: item '" + name + "' is missing a localized name. Add the follow to bundle.properties:"); + Log.err("item." + this.name + ".name=" + Strings.capitalize(name.replace('-', '_'))); + } + } + + public static Array all(){ + return Item.items; + } + + public static Item getByID(int id){ + return items.get(id); + } + + public void load(){ + this.region = Draw.region("item-" + name); + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayItem(table, this); + } + + @Override + public String localizedName(){ + return Bundles.get("item." + this.name + ".name"); + } + + @Override + public TextureRegion getContentIcon(){ + return region; + } + + @Override + public String toString(){ + return localizedName(); + } + + @Override + public int compareTo(Item item){ + return Integer.compare(id, item.id); + } + + @Override + public String getContentName(){ + return name; + } + + @Override + public String getContentTypeName(){ + return "item"; + } + + @Override + public Array getAll(){ + return all(); + } +} diff --git a/core/src/io/anuke/mindustry/type/ItemStack.java b/core/src/io/anuke/mindustry/type/ItemStack.java new file mode 100644 index 0000000000..5714050602 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/ItemStack.java @@ -0,0 +1,15 @@ +package io.anuke.mindustry.type; + +public class ItemStack{ + public io.anuke.mindustry.type.Item item; + public int amount; + + public ItemStack(io.anuke.mindustry.type.Item item, int amount){ + this.item = item; + this.amount = amount; + } + + public boolean equals(ItemStack other){ + return other != null && other.item == item && other.amount == amount; + } +} diff --git a/core/src/io/anuke/mindustry/type/ItemType.java b/core/src/io/anuke/mindustry/type/ItemType.java new file mode 100644 index 0000000000..fb676443bb --- /dev/null +++ b/core/src/io/anuke/mindustry/type/ItemType.java @@ -0,0 +1,16 @@ +package io.anuke.mindustry.type; + +public enum ItemType{ + /** + * Not used for anything besides crafting inside blocks. + */ + resource, + /** + * Can be used for constructing blocks. Only materials are accepted into the core. + */ + material, + /** + * Only used as ammo for turrets. + */ + ammo +} diff --git a/core/src/io/anuke/mindustry/type/Liquid.java b/core/src/io/anuke/mindustry/type/Liquid.java new file mode 100644 index 0000000000..20410304c1 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Liquid.java @@ -0,0 +1,117 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.content.StatusEffects; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.ui.ContentDisplay; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.ThreadArray; + +public class Liquid implements UnlockableContent{ + private static final Array liquids = new ThreadArray<>(); + + public final Color color; + public final String name; + public final String description; + public final int id; + + /** + * 0-1, 0 is completely inflammable, anything above that may catch fire when exposed to heat, 0.5+ is very flammable. + */ + public float flammability; + /** + * temperature: 0.5 is 'room' temperature, 0 is very cold, 1 is molten hot + */ + public float temperature = 0.5f; + /** + * how much heat this liquid can store. 0.75=water (high), anything lower is probably less dense and bad at cooling. + */ + public float heatCapacity = 0.5f; + /** + * how thick this liquid is. 0.5=water (relatively viscous), 1 would be something like tar (very slow) + */ + public float viscosity = 0.5f; + /** + * how prone to exploding this liquid is, when heated. 0 = nothing, 1 = nuke + */ + public float explosiveness; + /** + * the burning color of this liquid + */ + public Color flameColor = Color.valueOf("ffb763"); + /** + * The associated status effect. + */ + public StatusEffect effect = StatusEffects.none; + /** + * Pump tier. Controls which pumps can use this liquid. + */ + public int tier; + /** + * Displayed icon. + */ + public TextureRegion iconRegion; + + public Liquid(String name, Color color){ + this.name = name; + this.color = new Color(color); + + this.id = liquids.size; + this.description = Bundles.getOrNull("liquid." + name + ".description"); + + Liquid.liquids.add(this); + } + + public static Array all(){ + return Liquid.liquids; + } + + public static Liquid getByID(int id){ + return liquids.get(id); + } + + @Override + public void load(){ + iconRegion = Draw.region("liquid-icon-" + name); + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayLiquid(table, this); + } + + @Override + public String localizedName(){ + return Bundles.get("liquid." + this.name + ".name"); + } + + @Override + public TextureRegion getContentIcon(){ + return iconRegion; + } + + @Override + public String toString(){ + return localizedName(); + } + + @Override + public String getContentName(){ + return name; + } + + @Override + public String getContentTypeName(){ + return "liquid"; + } + + @Override + public Array getAll(){ + return all(); + } +} diff --git a/core/src/io/anuke/mindustry/type/Mech.java b/core/src/io/anuke/mindustry/type/Mech.java new file mode 100644 index 0000000000..98640fe292 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Mech.java @@ -0,0 +1,73 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.content.Weapons; +import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.ui.ContentDisplay; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.layout.Table; + +//TODO merge unit type with mech +public class Mech extends Upgrade implements UnlockableContent{ + public boolean flying; + + public float speed = 1.1f; + public float maxSpeed = 1.1f; + public float boostSpeed = 0.75f; + public float drag = 0.4f; + public float mass = 1f; + public float armor = 1f; + + public float mineSpeed = 1f; + public int drillPower = -1; + public float carryWeight = 10f; + public float buildPower = 1f; + public boolean canRepair = false; + public Color trailColor = Color.valueOf("ffd37f"); + + public float weaponOffsetX, weaponOffsetY; + + public Weapon weapon = Weapons.blaster; + + public int itemCapacity = 30; + public int ammoCapacity = 100; + + public TextureRegion baseRegion, legRegion, region, iconRegion; + + public Mech(String name, boolean flying){ + super(name); + this.flying = flying; + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayMech(table, this); + } + + @Override + public TextureRegion getContentIcon(){ + return iconRegion; + } + + @Override + public String getContentName(){ + return name; + } + + @Override + public String getContentTypeName(){ + return "mech"; + } + + @Override + public void load(){ + if(!flying){ + legRegion = Draw.region(name + "-leg"); + baseRegion = Draw.region(name + "-base"); + } + + region = Draw.region(name); + iconRegion = Draw.region("mech-icon-" + name); + } +} diff --git a/core/src/io/anuke/mindustry/type/Recipe.java b/core/src/io/anuke/mindustry/type/Recipe.java new file mode 100644 index 0000000000..36b5e4bca6 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Recipe.java @@ -0,0 +1,191 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.OrderedMap; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.game.Content; +import io.anuke.mindustry.game.UnlockableContent; +import io.anuke.mindustry.ui.ContentDisplay; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.ContentStatValue; +import io.anuke.mindustry.world.meta.StatValue; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; +import io.anuke.ucore.util.Log; +import io.anuke.ucore.util.Strings; + +import java.util.Arrays; + +import static io.anuke.mindustry.Vars.*; + +public class Recipe implements UnlockableContent{ + private static int lastid; + private static Array allRecipes = new Array<>(); + private static ObjectMap recipeMap = new ObjectMap<>(); + + public final int id; + public final Block result; + public final ItemStack[] requirements; + public final Category category; + public final float cost; + + public boolean desktopOnly = false, debugOnly = false; + + private Block[] dependencies; + private Recipe[] recipeDependencies; + + public Recipe(Category category, Block result, ItemStack... requirements){ + this.id = lastid++; + this.result = result; + this.requirements = requirements; + this.category = category; + + Arrays.sort(requirements, (a, b) -> Integer.compare(a.item.id, b.item.id)); + + float timeToPlace = 0f; + for(ItemStack stack : requirements){ + timeToPlace += stack.amount * stack.item.cost; + } + + this.cost = timeToPlace; + + allRecipes.add(this); + recipeMap.put(result, this); + } + + /** + * Returns unlocked recipes in a category. + * Do not call on the server backend, as unlocking does not exist! + */ + public static void getUnlockedByCategory(Category category, Array r){ + if(headless){ + throw new RuntimeException("Not enabled on the headless backend!"); + } + + r.clear(); + for(Recipe recipe : allRecipes){ + if(recipe.category == category && (Vars.control.database().isUnlocked(recipe) || (debug && recipe.debugOnly))){ + r.add(recipe); + } + } + } + + /** + * Returns all recipes in a category. + */ + public static void getByCategory(Category category, Array r){ + r.clear(); + for(Recipe recipe : allRecipes){ + if(recipe.category == category){ + r.add(recipe); + } + } + } + + public static Array all(){ + return allRecipes; + } + + public static Recipe getByResult(Block block){ + return recipeMap.get(block); + } + + public static Recipe getByID(int id){ + if(id < 0 || id >= allRecipes.size){ + return null; + }else{ + return allRecipes.get(id); + } + } + + public Recipe setDesktop(){ + desktopOnly = true; + return this; + } + + public Recipe setDebug(){ + debugOnly = true; + return this; + } + + @Override + public boolean isHidden(){ + return debugOnly; + } + + @Override + public void displayInfo(Table table){ + ContentDisplay.displayRecipe(table, this); + } + + @Override + public String localizedName(){ + return result.formalName; + } + + @Override + public TextureRegion getContentIcon(){ + return result.getEditorIcon(); + } + + @Override + public void init(){ + if(!Bundles.has("block." + result.name + ".name")){ + Log.err("WARNING: Recipe block '{0}' does not have a formal name defined. Add the following to bundle.properties:", result.name); + Log.err("block.{0}.name={1}", result.name, Strings.capitalize(result.name.replace('-', '_'))); + }/*else if(result.fullDescription == null){ + Log.err("WARNING: Recipe block '{0}' does not have a description defined.", result.name); + }*/ + } + + @Override + public String getContentName(){ + return result.name; + } + + @Override + public String getContentTypeName(){ + return "recipe"; + } + + @Override + public void onUnlock(){ + for(OrderedMap map : result.stats.toMap().values()){ + for(StatValue value : map.values()){ + if(value instanceof ContentStatValue){ + ContentStatValue stat = (ContentStatValue) value; + UnlockableContent[] content = stat.getValueContent(); + for(UnlockableContent c : content){ + control.database().unlockContent(c); + } + } + } + } + } + + @Override + public UnlockableContent[] getDependencies(){ + if(dependencies == null){ + return null; + }else if(recipeDependencies == null){ + recipeDependencies = new Recipe[dependencies.length]; + for(int i = 0; i < recipeDependencies.length; i++){ + recipeDependencies[i] = Recipe.getByResult(dependencies[i]); + } + } + return recipeDependencies; + } + + public Recipe setDependencies(Block... blocks){ + this.dependencies = blocks; + return this; + } + + @Override + public Array getAll(){ + return allRecipes; + } +} diff --git a/core/src/io/anuke/mindustry/type/StatusEffect.java b/core/src/io/anuke/mindustry/type/StatusEffect.java new file mode 100644 index 0000000000..fa9bcab529 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/StatusEffect.java @@ -0,0 +1,96 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectSet; +import io.anuke.mindustry.entities.StatusController.StatusEntry; +import io.anuke.mindustry.entities.Unit; +import io.anuke.mindustry.game.Content; + +public class StatusEffect implements Content{ + private static final Array array = new Array<>(); + private static int lastid; + + /** + * Duration of this status effect in ticks at maximum power. + */ + public final float baseDuration; + public final int id; + + public float damageMultiplier = 1f; //damage dealt + public float armorMultiplier = 1f; //armor points + public float speedMultiplier = 1f; //speed + + /** + * Set of 'opposite' effects, which will decrease the duration of this effect when applied. + */ + protected ObjectSet opposites = new ObjectSet<>(); + /** + * The strength of time decrease when met with an opposite effect, as a fraction of the other's duration. + */ + protected float oppositeScale = 0.5f; + + public StatusEffect(float baseDuration){ + this.baseDuration = baseDuration; + + id = lastid++; + array.add(this); + } + + public static StatusEffect getByID(int id){ + return array.get(id); + } + + public static Array all(){ + return array; + } + + /** + * Runs every tick on the affected unit while time is greater than 0. + */ + public void update(Unit unit, float time){ + } + + /** + * Called when transitioning between two status effects. + * + * @param to The state to transition to + * @param time The current status effect time + * @param newTime The time that the new status effect will last + */ + public StatusEntry getTransition(Unit unit, StatusEffect to, float time, float newTime, StatusEntry result){ + if(opposites.contains(to)){ + time -= newTime * oppositeScale; + if(time > 0){ + return result.set(this, time); + } + } + + return result.set(to, newTime); + } + + /** + * Called when this effect transitions to a new status effect. + */ + public void onTransition(Unit unit, StatusEffect to){ + } + + public boolean isOpposite(StatusEffect other){ + return opposites.size > 0 && opposites.contains(other); + } + + public void setOpposites(StatusEffect... effects){ + for(StatusEffect e : effects){ + opposites.add(e); + } + } + + @Override + public String getContentTypeName(){ + return "statuseffect"; + } + + @Override + public Array getAll(){ + return null; + } +} diff --git a/core/src/io/anuke/mindustry/type/Upgrade.java b/core/src/io/anuke/mindustry/type/Upgrade.java new file mode 100644 index 0000000000..49157843ec --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Upgrade.java @@ -0,0 +1,54 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; +import io.anuke.ucore.function.Consumer; +import io.anuke.ucore.function.Predicate; +import io.anuke.ucore.util.Bundles; + +public abstract class Upgrade implements Content{ + private static Array upgrades = new Array<>(); + private static byte lastid; + + public final byte id; + public final String name; + public final String description; + + public Upgrade(String name){ + this.id = lastid++; + this.name = name; + this.description = Bundles.get("upgrade." + name + ".description"); + + upgrades.add(this); + } + + public static void forEach(Consumer type, Predicate pred){ + for(Upgrade u : upgrades){ + if(pred.test(u)){ + type.accept((T) u); + } + } + } + + public static Array all(){ + return upgrades; + } + + public static T getByID(byte id){ + return (T) upgrades.get(id); + } + + public String localizedName(){ + return Bundles.get("upgrade." + name + ".name"); + } + + @Override + public String toString(){ + return localizedName(); + } + + @Override + public Array getAll(){ + return all(); + } +} diff --git a/core/src/io/anuke/mindustry/type/Weapon.java b/core/src/io/anuke/mindustry/type/Weapon.java new file mode 100644 index 0000000000..a5d3610048 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/Weapon.java @@ -0,0 +1,209 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.OrderedMap; +import io.anuke.annotations.Annotations.Loc; +import io.anuke.annotations.Annotations.Remote; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.fx.Fx; +import io.anuke.mindustry.entities.Player; +import io.anuke.mindustry.entities.bullet.Bullet; +import io.anuke.mindustry.entities.traits.ShooterTrait; +import io.anuke.mindustry.gen.CallEntity; +import io.anuke.mindustry.net.In; +import io.anuke.mindustry.net.Net; +import io.anuke.ucore.core.Effects; +import io.anuke.ucore.core.Effects.Effect; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.util.Angles; +import io.anuke.ucore.util.Mathf; +import io.anuke.ucore.util.Translator; + +public class Weapon extends Upgrade{ + /** + * minimum cursor distance from player, fixes 'cross-eyed' shooting. + */ + protected static float minPlayerDist = 20f; + public TextureRegion equipRegion, region; + /** + * ammo type map. set with setAmmo() + */ + protected OrderedMap ammoMap = new OrderedMap<>(); + /** + * shell ejection effect + */ + protected Effect ejectEffect = Fx.none; + /** + * weapon reload in frames + */ + protected float reload; + /** + * amount of shots per fire + */ + protected int shots = 1; + /** + * spacing in degrees between multiple shots, if applicable + */ + protected float spacing = 12f; + /** + * inaccuracy of degrees of each shot + */ + protected float inaccuracy = 0f; + /** + * intensity and duration of each shot's screen shake + */ + protected float shake = 0f; + /** + * visual weapon knockback. + */ + protected float recoil = 1.5f; + /** + * shoot barrel y offset + */ + protected float length = 3f; + /** + * shoot barrel x offset. + */ + protected float width = 4f; + /** + * fraction of velocity that is random + */ + protected float velocityRnd = 0f; + /** + * whether to shoot the weapons in different arms one after another, rather than all at once + */ + protected boolean roundrobin = false; + /** + * translator for vector calulations + */ + protected Translator tr = new Translator(); + + protected Weapon(String name){ + super(name); + } + + @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) + public static void onPlayerShootWeapon(Player player, float x, float y, float rotation, boolean left){ + if(player == null) return; + //clients do not see their own shoot events: they are simulated completely clientside to prevent laggy visuals + //messing with the firerate or any other stats does not affect the server (take that, script kiddies!) + if(Net.client() && player == Vars.players[0]){ + return; + } + + shootDirect(player, x, y, rotation, left); + } + + @Remote(targets = Loc.server, called = Loc.both, in = In.entities, unreliable = true) + public static void onGenericShootWeapon(ShooterTrait shooter, float x, float y, float rotation, boolean left){ + if(shooter == null) return; + shootDirect(shooter, x, y, rotation, left); + } + + public static void shootDirect(ShooterTrait shooter, float x, float y, float rotation, boolean left){ + Weapon weapon = shooter.getWeapon(); + + Angles.shotgun(weapon.shots, weapon.spacing, rotation, f -> weapon.bullet(shooter, x, y, f + Mathf.range(weapon.inaccuracy))); + + AmmoType type = shooter.getInventory().getAmmo(); + + if(type == null) return; + + weapon.tr.trns(rotation + 180f, type.recoil); + + shooter.getVelocity().add(weapon.tr); + + weapon.tr.trns(rotation, 3f); + + Effects.shake(weapon.shake, weapon.shake, x, y); + Effects.effect(weapon.ejectEffect, x, y, rotation * -Mathf.sign(left)); + Effects.effect(type.shootEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); + Effects.effect(type.smokeEffect, x + weapon.tr.x, y + weapon.tr.y, rotation, shooter); + + //reset timer for remote players + shooter.getTimer().get(shooter.getShootTimer(left), weapon.reload); + } + + @Override + public void load(){ + equipRegion = Draw.region(name + "-equip"); + region = Draw.region(name); + } + + @Override + public String getContentTypeName(){ + return "weapon"; + } + + public void update(ShooterTrait shooter, float pointerX, float pointerY){ + update(shooter, true, pointerX, pointerY); + update(shooter, false, pointerX, pointerY); + } + + private void update(ShooterTrait shooter, boolean left, float pointerX, float pointerY){ + if(shooter.getInventory().hasAmmo() && shooter.getTimer().get(shooter.getShootTimer(left), reload)){ + if(roundrobin){ + shooter.getTimer().reset(shooter.getShootTimer(!left), reload / 2f); + } + + tr.set(pointerX, pointerY).sub(shooter.getX(), shooter.getY()); + if(tr.len() < minPlayerDist) tr.setLength(minPlayerDist); + + float cx = tr.x + shooter.getX(), cy = tr.y + shooter.getY(); + + float ang = tr.angle(); + tr.trns(ang - 90, width * Mathf.sign(left), length); + + shoot(shooter, shooter.getX() + tr.x, shooter.getY() + tr.y, Angles.angle(shooter.getX() + tr.x, shooter.getY() + tr.y, cx, cy), left); + } + } + + public float getRecoil(ShooterTrait player, boolean left){ + return (1f - Mathf.clamp(player.getTimer().getTime(player.getShootTimer(left)) / reload)) * recoil; + } + + public float getRecoil(){ + return recoil; + } + + public float getReload(){ + return reload; + } + + public void shoot(ShooterTrait p, float x, float y, float angle, boolean left){ + if(Net.client()){ + //call it directly, don't invoke on server + shootDirect(p, x, y, angle, left); + }else{ + if(p instanceof Player){ //players need special weapon handling logic + CallEntity.onPlayerShootWeapon((Player) p, x, y, angle, left); + }else{ + CallEntity.onGenericShootWeapon(p, x, y, angle, left); + } + } + + p.getInventory().useAmmo(); + } + + public Iterable getAcceptedItems(){ + return ammoMap.orderedKeys(); + } + + public AmmoType getAmmoType(Item item){ + return ammoMap.get(item); + } + + protected void setAmmo(AmmoType... types){ + for(AmmoType type : types){ + ammoMap.put(type.item, type); + } + } + + void bullet(ShooterTrait owner, float x, float y, float angle){ + if(owner == null || !owner.getInventory().hasAmmo()) return; + + tr.trns(angle, 3f); + Bullet.create(owner.getInventory().getAmmo().bullet, + owner, owner.getTeam(), x + tr.x, y + tr.y, angle, (1f - velocityRnd) + Mathf.random(velocityRnd)); + } +} diff --git a/core/src/io/anuke/mindustry/type/WeatherEvent.java b/core/src/io/anuke/mindustry/type/WeatherEvent.java new file mode 100644 index 0000000000..66871cc439 --- /dev/null +++ b/core/src/io/anuke/mindustry/type/WeatherEvent.java @@ -0,0 +1,38 @@ +package io.anuke.mindustry.type; + +import com.badlogic.gdx.utils.Array; +import io.anuke.mindustry.game.Content; + +//TODO implement this class +public class WeatherEvent implements Content{ + private static final Array all = new Array<>(); + private static int lastid; + + public final int id; + public final String name; + + public WeatherEvent(String name){ + this.id = lastid++; + this.name = name; + + all.add(this); + } + + public static Array all(){ + return all; + } + + public static WeatherEvent getByID(int id){ + return all.get(id); + } + + @Override + public String getContentTypeName(){ + return "weatherevent"; + } + + @Override + public Array getAll(){ + return all(); + } +} diff --git a/core/src/io/anuke/mindustry/ui/BorderImage.java b/core/src/io/anuke/mindustry/ui/BorderImage.java index 4dbfdc4809..bc9ed30002 100644 --- a/core/src/io/anuke/mindustry/ui/BorderImage.java +++ b/core/src/io/anuke/mindustry/ui/BorderImage.java @@ -1,44 +1,44 @@ package io.anuke.mindustry.ui; -import com.badlogic.gdx.graphics.Colors; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; - import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.graphics.Palette; import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.graphics.Lines; import io.anuke.ucore.scene.ui.Image; import io.anuke.ucore.scene.ui.layout.Unit; public class BorderImage extends Image{ - private float thickness = 3f; - - public BorderImage(){} - - public BorderImage(Texture texture){ - super(texture); - } - - public BorderImage(Texture texture, float thick){ - super(texture); - thickness = thick; - } + private float thickness = 3f; - public BorderImage(TextureRegion region, float thick){ - super(region); - thickness = thick; - } - - @Override - public void draw(Batch batch, float alpha){ - super.draw(batch, alpha); - - float scaleX = getScaleX(); - float scaleY = getScaleY(); - - Draw.color(Colors.get("accent")); - Lines.stroke(Unit.dp.scl(thickness)); - Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY); - Draw.reset(); - } + public BorderImage(){ + } + + public BorderImage(Texture texture){ + super(texture); + } + + public BorderImage(Texture texture, float thick){ + super(texture); + thickness = thick; + } + + public BorderImage(TextureRegion region, float thick){ + super(region); + thickness = thick; + } + + @Override + public void draw(Batch batch, float alpha){ + super.draw(batch, alpha); + + float scaleX = getScaleX(); + float scaleY = getScaleY(); + + Draw.color(Palette.accent); + Lines.stroke(Unit.dp.scl(thickness)); + Lines.rect(x + imageX, y + imageY, imageWidth * scaleX, imageHeight * scaleY); + Draw.reset(); + } } diff --git a/core/src/io/anuke/mindustry/ui/ContentDisplay.java b/core/src/io/anuke/mindustry/ui/ContentDisplay.java new file mode 100644 index 0000000000..181e5704f1 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/ContentDisplay.java @@ -0,0 +1,149 @@ +package io.anuke.mindustry.ui; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.OrderedMap; +import io.anuke.mindustry.entities.units.UnitType; +import io.anuke.mindustry.graphics.Palette; +import io.anuke.mindustry.type.Item; +import io.anuke.mindustry.type.Liquid; +import io.anuke.mindustry.type.Mech; +import io.anuke.mindustry.type.Recipe; +import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.blocks.defense.turrets.Turret; +import io.anuke.mindustry.world.meta.BlockStat; +import io.anuke.mindustry.world.meta.BlockStats; +import io.anuke.mindustry.world.meta.StatCategory; +import io.anuke.mindustry.world.meta.StatValue; +import io.anuke.ucore.graphics.Draw; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.util.Bundles; + +public class ContentDisplay{ + + public static void displayRecipe(Table table, Recipe recipe){ + Block block = recipe.result; + + table.table(title -> { + int size = 8 * 6; + + if(block instanceof Turret){ + size = (8 * block.size + 2) * (7 - block.size * 2); + } + + title.addImage(Draw.region("block-icon-" + block.name)).size(size); + title.add("[accent]" + block.formalName).padLeft(5); + }); + + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + + table.row(); + + if(block.fullDescription != null){ + table.add(block.fullDescription).padLeft(5).padRight(5).width(400f).wrap().fillX(); + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + table.row(); + } + + BlockStats stats = block.stats; + + for(StatCategory cat : stats.toMap().keys()){ + OrderedMap map = stats.toMap().get(cat); + + if(map.size == 0) continue; + + table.add("$text.category." + cat.name()).color(Palette.accent).fillX(); + table.row(); + + for(BlockStat stat : map.keys()){ + table.table(inset -> { + inset.left(); + inset.add("[LIGHT_GRAY]" + stat.localized() + ":[] "); + map.get(stat).display(inset); + }).fillX().padLeft(10); + table.row(); + } + } + } + + public static void displayItem(Table table, Item item){ + + table.table(title -> { + title.addImage(item.getContentIcon()).size(8 * 6); + title.add("[accent]" + item.localizedName()).padLeft(5); + }); + + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + + table.row(); + + if(item.description != null){ + table.add(item.description).padLeft(5).padRight(5).width(400f).wrap().fillX(); + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + table.row(); + } + + table.left().defaults().fillX(); + + table.add(Bundles.format("text.item.explosiveness", (int) (item.explosiveness * 100))); + table.row(); + table.add(Bundles.format("text.item.flammability", (int) (item.flammability * 100))); + table.row(); + table.add(Bundles.format("text.item.radioactivity", (int) (item.radioactivity * 100))); + table.row(); + table.add(Bundles.format("text.item.fluxiness", (int) (item.fluxiness * 100))); + table.row(); + table.add(Bundles.format("text.item.hardness", item.hardness)); + table.row(); + } + + public static void displayLiquid(Table table, Liquid liquid){ + + table.table(title -> { + title.addImage(liquid.getContentIcon()).size(8 * 6); + title.add("[accent]" + liquid.localizedName()).padLeft(5); + }); + + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + + table.row(); + + if(liquid.description != null){ + table.add(liquid.description).padLeft(5).padRight(5).width(400f).wrap().fillX(); + table.row(); + + table.addImage("white").height(3).color(Color.LIGHT_GRAY).pad(15).padLeft(0).padRight(0).fillX(); + table.row(); + } + + table.left().defaults().fillX(); + + table.add(Bundles.format("text.item.explosiveness", (int) (liquid.explosiveness * 100))); + table.row(); + table.add(Bundles.format("text.item.flammability", (int) (liquid.flammability * 100))); + table.row(); + table.add(Bundles.format("text.liquid.heatcapacity", (int) (liquid.heatCapacity * 100))); + table.row(); + table.add(Bundles.format("text.liquid.temperature", (int) (liquid.temperature * 100))); + table.row(); + table.add(Bundles.format("text.liquid.viscosity", (int) (liquid.viscosity * 100))); + table.row(); + } + + public static void displayMech(Table table, Mech mech){ + + } + + public static void displayUnit(Table table, UnitType unit){ + + } +} diff --git a/core/src/io/anuke/mindustry/ui/GridImage.java b/core/src/io/anuke/mindustry/ui/GridImage.java index b7c5ce6700..01792f5fbb 100644 --- a/core/src/io/anuke/mindustry/ui/GridImage.java +++ b/core/src/io/anuke/mindustry/ui/GridImage.java @@ -2,36 +2,40 @@ package io.anuke.mindustry.ui; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; - import io.anuke.ucore.graphics.Draw; import io.anuke.ucore.scene.Element; public class GridImage extends Element{ - private int imageWidth, imageHeight; - - public GridImage(int w, int h){ - this.imageWidth = w; - this.imageHeight = h; - } + private int imageWidth, imageHeight; - public void draw(Batch batch, float alpha){ - TextureRegion blank = Draw.region("white"); - - float xspace = (getWidth() / imageWidth); - float yspace = (getHeight() / imageHeight); - float s = 1f; - - for(int x = 0; x <= imageWidth; x ++){ - batch.draw(blank, (int)(getX() + xspace * x - s), getY() - s, 2, getHeight()+ (x == imageWidth ? 1: 0)); - } - - for(int y = 0; y <= imageHeight; y ++){ - batch.draw(blank, getX() - s, (int)(getY() + y * yspace - s), getWidth(), 2); - } - } - - public void setImageSize(int w, int h){ - this.imageWidth = w; - this.imageHeight = h; - } + public GridImage(int w, int h){ + this.imageWidth = w; + this.imageHeight = h; + } + + public void draw(Batch batch, float alpha){ + TextureRegion blank = Draw.region("white"); + + float xspace = (getWidth() / imageWidth); + float yspace = (getHeight() / imageHeight); + float s = 1f; + + int minspace = 10; + + int jumpx = (int) (Math.max(minspace, xspace) / xspace); + int jumpy = (int) (Math.max(minspace, yspace) / yspace); + + for(int x = 0; x <= imageWidth; x += jumpx){ + batch.draw(blank, (int) (getX() + xspace * x - s), getY() - s, 2, getHeight() + (x == imageWidth ? 1 : 0)); + } + + for(int y = 0; y <= imageHeight; y += jumpy){ + batch.draw(blank, getX() - s, (int) (getY() + y * yspace - s), getWidth(), 2); + } + } + + public void setImageSize(int w, int h){ + this.imageWidth = w; + this.imageHeight = h; + } } diff --git a/core/src/io/anuke/mindustry/ui/IntFormat.java b/core/src/io/anuke/mindustry/ui/IntFormat.java new file mode 100644 index 0000000000..6c4431a4b0 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/IntFormat.java @@ -0,0 +1,25 @@ +package io.anuke.mindustry.ui; + +import io.anuke.ucore.util.Bundles; + +/** + * A low-garbage way to format bundle strings. + */ +public class IntFormat{ + private final StringBuilder builder = new StringBuilder(); + private final String text; + private int lastValue = Integer.MIN_VALUE; + + public IntFormat(String text){ + this.text = text; + } + + public CharSequence get(int value){ + if(lastValue != value){ + builder.setLength(0); + builder.append(Bundles.format(text, value)); + } + lastValue = value; + return builder; + } +} diff --git a/core/src/io/anuke/mindustry/ui/ItemImage.java b/core/src/io/anuke/mindustry/ui/ItemImage.java new file mode 100644 index 0000000000..5dab85a67d --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/ItemImage.java @@ -0,0 +1,35 @@ +package io.anuke.mindustry.ui; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.type.ItemStack; +import io.anuke.ucore.function.Supplier; +import io.anuke.ucore.scene.ui.Image; +import io.anuke.ucore.scene.ui.layout.Stack; +import io.anuke.ucore.scene.ui.layout.Table; +import io.anuke.ucore.scene.ui.layout.Unit; + +public class ItemImage extends Stack{ + + public ItemImage(TextureRegion region, Supplier text){ + Table t = new Table().left().bottom(); + + t.label(text).color(Color.DARK_GRAY).padBottom(-22).get().setFontScale(Unit.dp.scl(0.5f)); + t.row(); + t.label(text).get().setFontScale(Unit.dp.scl(0.5f)); + + add(new Image(region)); + add(t); + } + + public ItemImage(ItemStack stack){ + Table t = new Table().left().bottom(); + + t.add(stack.amount + "").color(Color.DARK_GRAY).padBottom(-22).get().setFontScale(Unit.dp.scl(0.5f)); + t.row(); + t.add(stack.amount + "").get().setFontScale(Unit.dp.scl(0.5f)); + + add(new Image(stack.item.region)); + add(t); + } +} diff --git a/core/src/io/anuke/mindustry/ui/Links.java b/core/src/io/anuke/mindustry/ui/Links.java index facbcf45e7..09ebd501d8 100644 --- a/core/src/io/anuke/mindustry/ui/Links.java +++ b/core/src/io/anuke/mindustry/ui/Links.java @@ -3,18 +3,26 @@ package io.anuke.mindustry.ui; import com.badlogic.gdx.graphics.Color; import io.anuke.ucore.util.Bundles; -public class Links { - private static final LinkEntry[] links = { - new LinkEntry("discord", "https://discord.gg/BKADYds", Color.valueOf("7289da")), - new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")), - new LinkEntry("wiki", "http://mindustry.wikia.com/wiki/Mindustry_Wiki", Color.valueOf("0f142f")), - new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")), - new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")), - new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")), - new LinkEntry("dev-builds", "https://github.com/Anuken/Mindustry/wiki", Color.valueOf("fafbfc")), - }; +public class Links{ + private static LinkEntry[] links; + + private static void createLinks(){ + links = new LinkEntry[]{ + new LinkEntry("discord", "https://discord.gg/BKADYds", Color.valueOf("7289da")), + new LinkEntry("trello", "https://trello.com/b/aE2tcUwF", Color.valueOf("026aa7")), + new LinkEntry("wiki", "http://mindustry.wikia.com/wiki/Mindustry_Wiki", Color.valueOf("0f142f")), + new LinkEntry("itch.io", "https://anuke.itch.io/mindustry", Color.valueOf("fa5c5c")), + new LinkEntry("google-play", "https://play.google.com/store/apps/details?id=io.anuke.mindustry", Color.valueOf("689f38")), + new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Color.valueOf("24292e")), + new LinkEntry("dev-builds", "https://github.com/Anuken/Mindustry/wiki", Color.valueOf("fafbfc")) + }; + } public static LinkEntry[] getLinks(){ + if(links == null){ + createLinks(); + } + return links; } @@ -22,10 +30,10 @@ public class Links { public final String name, description, link; public final Color color; - public LinkEntry(String name, String link, Color color) { + public LinkEntry(String name, String link, Color color){ this.name = name; this.color = color; - this.description = Bundles.getNotNull("text.link." + name +".description"); + this.description = Bundles.getNotNull("text.link." + name + ".description"); this.link = link; } } diff --git a/core/src/io/anuke/mindustry/ui/MenuButton.java b/core/src/io/anuke/mindustry/ui/MenuButton.java index c489923e72..ec3b273d39 100644 --- a/core/src/io/anuke/mindustry/ui/MenuButton.java +++ b/core/src/io/anuke/mindustry/ui/MenuButton.java @@ -7,32 +7,32 @@ import io.anuke.ucore.scene.ui.TextButton; public class MenuButton extends TextButton{ - public MenuButton(String icon, String text, Listenable clicked){ - this(icon, text, null, clicked); - } - - public MenuButton(String icon, String text, String description, Listenable clicked){ - super("default"); - float s = 70f; + public MenuButton(String icon, String text, Listenable clicked){ + this(icon, text, null, clicked); + } - clicked(clicked); + public MenuButton(String icon, String text, String description, Listenable clicked){ + super("default"); + float s = 66f; - clearChildren(); + clicked(clicked); - margin(0); + clearChildren(); - table(t -> { - t.addImage(icon).size(14*3); - t.update(() -> t.setBackground(getClickListener().isOver() || getClickListener().isVisualPressed() ? "button-over" : "button")); - }).size(s - 5, s); + margin(0); + + table(t -> { + t.addImage(icon).size(14 * 3); + t.update(() -> t.setBackground(getClickListener().isOver() || getClickListener().isVisualPressed() ? "button-over" : "button")); + }).size(s - 5, s); - table(t -> { - t.add(text).wrap().growX().get().setAlignment(Align.center, Align.left); - if(description != null){ - t.row(); - t.add(description).color(Color.LIGHT_GRAY); - } - }).padLeft(5).growX(); - } + table(t -> { + t.add(text).wrap().growX().get().setAlignment(Align.center, Align.left); + if(description != null){ + t.row(); + t.add(description).color(Color.LIGHT_GRAY); + } + }).padLeft(5).growX(); + } } diff --git a/core/src/io/anuke/mindustry/ui/Minimap.java b/core/src/io/anuke/mindustry/ui/Minimap.java new file mode 100644 index 0000000000..1b0f563cf6 --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/Minimap.java @@ -0,0 +1,73 @@ +package io.anuke.mindustry.ui; + +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import io.anuke.mindustry.graphics.Shaders; +import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Graphics; +import io.anuke.ucore.scene.Element; +import io.anuke.ucore.scene.event.InputEvent; +import io.anuke.ucore.scene.event.InputListener; +import io.anuke.ucore.scene.style.TextureRegionDrawable; +import io.anuke.ucore.scene.ui.Image; +import io.anuke.ucore.scene.ui.layout.Table; + +import static io.anuke.mindustry.Vars.renderer; +import static io.anuke.mindustry.Vars.showFog; + +public class Minimap extends Table{ + + public Minimap(){ + super("button"); + + margin(5); + marginBottom(10); + + Image image = new Image(new TextureRegionDrawable(new TextureRegion())){ + @Override + public void draw(Batch batch, float parentAlpha){ + if(renderer.minimap().getRegion() == null) return; + + TextureRegionDrawable draw = (TextureRegionDrawable) getDrawable(); + draw.getRegion().setRegion(renderer.minimap().getRegion()); + super.draw(batch, parentAlpha); + if(renderer.minimap().getTexture() != null){ + renderer.minimap().drawEntities(x, y, width, height); + } + + if(showFog){ + renderer.fog().getTexture().setFilter(TextureFilter.Nearest, TextureFilter.Nearest); + + draw.getRegion().setTexture(renderer.fog().getTexture()); + draw.getRegion().setV(1f - draw.getRegion().getV()); + draw.getRegion().setV2(1f - draw.getRegion().getV2()); + + Graphics.shader(Shaders.fog); + super.draw(batch, parentAlpha); + Graphics.shader(); + + renderer.fog().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear); + } + } + }; + + addListener(new InputListener(){ + public boolean scrolled(InputEvent event, float x, float y, int amount){ + renderer.minimap().zoomBy(amount); + return true; + } + }); + + image.update(() -> { + + Element e = Core.scene.hit(Graphics.mouse().x, Graphics.mouse().y, true); + if(e != null && e.isDescendantOf(this)){ + Core.scene.setScrollFocus(this); + }else if(Core.scene.getScrollFocus() == this){ + Core.scene.setScrollFocus(null); + } + }); + add(image).size(140f, 140f); + } +} diff --git a/core/src/io/anuke/mindustry/ui/MobileButton.java b/core/src/io/anuke/mindustry/ui/MobileButton.java new file mode 100644 index 0000000000..dfbf1b161c --- /dev/null +++ b/core/src/io/anuke/mindustry/ui/MobileButton.java @@ -0,0 +1,16 @@ +package io.anuke.mindustry.ui; + +import com.badlogic.gdx.utils.Align; +import io.anuke.ucore.function.Listenable; +import io.anuke.ucore.scene.ui.ImageButton; + +public class MobileButton extends ImageButton{ + + public MobileButton(String icon, float isize, String text, Listenable listener){ + super(icon); + resizeImage(isize); + clicked(listener); + row(); + add(text).growX().wrap().center().get().setAlignment(Align.center, Align.center); + } +} diff --git a/core/src/io/anuke/mindustry/ui/PressGroup.java b/core/src/io/anuke/mindustry/ui/PressGroup.java deleted file mode 100644 index c2e7add8e2..0000000000 --- a/core/src/io/anuke/mindustry/ui/PressGroup.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.anuke.mindustry.ui; - -import com.badlogic.gdx.utils.Array; - -import io.anuke.ucore.scene.ui.Button; - -public class PressGroup{ - private Array