Merge branch 'master' of https://github.com/Anuken/Mindustry into v9

# Conflicts:
#	gradle.properties
This commit is contained in:
Anuken
2026-07-22 10:03:28 -04:00
9 changed files with 187 additions and 36 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
name: Bug report
description: The type of device you were playing on
description: An issue with the game.
labels: ["bug"]
body:
+10 -10
View File
@@ -62,7 +62,7 @@ export.file = Экспортировать файл
import.file = Импортировать файл
copy.clipboard = Скопировать в буфер обмена
load.clipboard = Вставить из буфера обмена
workshop.browse = Мастерская
workshop.browse = Искать в Мастерской
workshop.share = Поделиться в Мастерской
schematic = Схема
@@ -74,7 +74,7 @@ schematic.exists = Схема с таким названием уже сущес
schematic.import = Импортировать схему...
schematic.flip = [accent][[{0}][]/[accent][[{1}][]: Отразить схему
schematic.saved = Схема сохранена.
schematic.delete.confirm = Эта схема будет поджарена Испепелителем.
schematic.delete.confirm = Эта схема будет разнесена в металлолом.
schematic.edit = Редактировать схему
schematic.info = {0}x{1}, {2} блоков
schematic.disabled = [scarlet]Схемы отключены[]\nНа этой [accent]карте[] или [accent]сервере[] запрещено использование схем.
@@ -183,7 +183,7 @@ mod.incompletedependencies.details = Этот мод не может быть з
mod.requiresversion = Требуется версия игры: [red]{0}
mod.errors = Ошибки были вызваны загружаемым содержимым.
mod.errors.map = [scarlet]Errors have occurred loading map content. All custom content has been disabled.[]\nThis map will likely not function correctly.
mod.errors.map = [scarlet]При загрузке ресурсов карты произошли ошибки. Весь пользовательский контент был отключен.[]\nЭта карта, вероятно, не будет работать правильно.
mod.noerrorplay = [scarlet]У Вас есть модификации с ошибками.[] Выключите проблемные модификации или исправьте ошибки перед игрой.
mod.enable = Вкл.
mod.requiresrestart = Теперь игра закроется, чтобы применить изменения в модификациях.
@@ -281,7 +281,7 @@ servers.remote = Удалённые серверы
servers.global = Серверы сообщества
servers.disclaimer = Серверы сообщества [accent]не[] принадлежат разработчику и [accent]не[] контролируются им.\n\nСерверы могут содержать пользовательский контент, который не подходит для всех возрастов.
servers.connect.confirm = Join the following server?\n\n[accent]{0}[]\n\nOnly connect to servers you trust!
servers.connect.confirm = Присоединиться к этому серверу?\n\n[accent]{0}[]\n\nНе подключайтесь к серверам, которым не доверяете!
servers.showhidden = Отображать скрытые серверы
server.shown = Отображается
server.hidden = Скрыт
@@ -333,7 +333,7 @@ disconnect.timeout = Время ожидания истекло.
disconnect.data = Ошибка при загрузке данных мира!
disconnect.snapshottimeout = Превышено время ожидания при получении UDP-снапшотов.\nЭто может быть вызвано нестабильной сетью или соединением.
cantconnect = Не удаётся присоединиться к игре ([accent]{0}[]).
disconnect.reason = Disconnected: {0}
disconnect.reason = Отключение: {0}
connecting = [accent]Подключение...
reconnecting = [accent]Переподключение...
@@ -443,7 +443,7 @@ commandmode.nounits = [нет единиц]
wave = [accent]Волна {0}
wave.cap = [accent]Волна {0}/{1}
wave.waiting = [lightgray]Волна через {0}
wave.waveInProgress = [lightgray]Волна продолжается
wave.waveInProgress = [lightgray]Волна в процессе
waiting = [lightgray]Ожидание...
waiting.players = Ожидание игроков...
wave.enemies = [lightgray]Враги: {0}
@@ -1122,7 +1122,7 @@ settings.clearplanetcampaignsaves.confirm = Вы уверены, что хоти
paused = [accent]< Пауза >
clear = Очистить
banned = [scarlet]Запрещено
block.limit = [scarlet]Limit: {0} Buildings
block.limit = [scarlet]Лимит: {0} блоков
unsupported.environment = [scarlet]Неподдерживаемые условия
yes = Да
no = Нет
@@ -2841,8 +2841,8 @@ lglobal.@clientUnit = Боевая единица клиента, выполня
lglobal.@clientName = Имя игрока клиента, выполняющего код
lglobal.@clientTeam = ID команды клиента, выполняющего код.
lglobal.@clientMobile = True, если код работает на мобильном клиенте, иначе False
lglobal.@clientMusicPlaying = True if any music is currently playing
lglobal.@clientCurrentMusic = Name of the current track being played, or null
lglobal.@clientMusicPlaying = True, если в данный момент играет любая музыка
lglobal.@clientCurrentMusic = Название текущей проигрываемой музыки, или null
lenum.type = Тип постройки/единицы. \nНапример, для маршрутизатора это будет [accent]@router[].\nНе строка.
lenum.shoot = Стрельба в определённую позицию.
@@ -3040,4 +3040,4 @@ lenum.solarmultiplier = Устанавливает множитель получ
lenum.dragmultiplier = Устанавливает множитель скорости торможения боевых единиц.
lenum.ban = Устанавливает запрещённые блоки и боевые единицы.
lenum.unban = Снимает запрет на ранее запрещённые блоки и боевые единицы.
lenum.musicvolume = Global music volume multiplier.\nClamped to [0, 1].
lenum.musicvolume = Глобальный множитель громкости музыки.\nСокращается до диапазона [0, 1].
+2 -1
View File
@@ -195,4 +195,5 @@ Jovinull
Alon
硫缺铅/PyratiteNoLead
Space
NeilGreenFly
NeilGreenFly
Sunky.MP3G
+5 -2
View File
@@ -691,6 +691,7 @@ public class LExecutor{
if(target instanceof Senseable se){
if(sense instanceof Content co){
to.setnum(se.sense(co));
return;
}else if(sense instanceof LAccess la){
Object objOut = se.senseObject(la);
@@ -701,6 +702,7 @@ public class LExecutor{
//object output
to.setobj(objOut);
}
return;
}
}else{
if(sense == LAccess.size || sense == LAccess.bufferSize){
@@ -712,9 +714,10 @@ public class LExecutor{
return;
}
}
to.setobj(null);
}
//unrecognized or unhandled property
to.setobj(null);
}
}
+6
View File
@@ -224,6 +224,10 @@ public abstract class LStatement{
}
protected static void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
showSelectTable(b, hideCons, () -> {});
}
protected static void showSelectTable(Button b, Cons2<Table, Runnable> hideCons, Runnable hideCallback){
Table t = new Table(Tex.paneSolid){
@Override
public float getPrefHeight(){
@@ -243,6 +247,7 @@ public abstract class LStatement{
Runnable hide = () -> {
Core.app.post(hitter::remove);
t.actions(Actions.fadeOut(0.3f, Interp.fade), Actions.remove());
hideCallback.run();
};
hitter.fillParent = true;
@@ -256,6 +261,7 @@ public abstract class LStatement{
Core.app.post(() -> {
hitter.remove();
t.remove();
hideCallback.run();
});
return;
}
+152 -15
View File
@@ -2390,21 +2390,7 @@ public class LStatements{
table.button(b -> {
b.image(Icon.pencilSmall);
Seq<String> soundNames = new Seq<>();
for(var entry : Core.assets.getAllEntries(Sound.class, new Seq<>())){
if(entry.value != Sounds.none && entry.value.file != null){
soundNames.add(Strings.getFileNameWithoutExtension(entry.key));
}
}
soundNames.sort();
String soundName = id.startsWith("@sfx-") ? id.substring(5) : id;
b.clicked(() -> showSelect(b, soundNames.toArray(String.class), soundName, t -> {
id = "@sfx-" + t;
build(table);
}, 3, cell -> cell.size(170, 50)));
b.clicked(() -> showSoundSelect(b, table));
}, Styles.logict, () -> {}).size(40).color(table.color).left().padLeft(-1);
row(table);
@@ -2441,6 +2427,157 @@ public class LStatements{
public LCategory category(){
return LCategory.world;
}
private static @Nullable Sound lastPreview;
private static class SoundChoice{
final String category;
final String name;
final Sound sound;
SoundChoice(String category, String name, Sound sound){
this.category = category;
this.name = name;
this.sound = sound;
}
}
protected void showSoundSelect(Button button, Table table){
Seq<SoundChoice> choices = new Seq<>();
ObjectMap<String, Seq<SoundChoice>> categories = new ObjectMap<>();
for(var entry : Core.assets.getAllEntries(Sound.class, new Seq<>())){
Sound sound = entry.value;
if(sound == Sounds.none || sound == null || sound.file == null) continue;
String name = Strings.getFileNameWithoutExtension(entry.key);
String category = soundCategory(entry.key);
SoundChoice choice = new SoundChoice(category, name, sound);
choices.add(choice);
categories.get(category, Seq::new).add(choice);
}
if(choices.isEmpty()) return;
choices.sort((a, b) -> {
int cmp = a.category.compareTo(b.category);
return cmp == 0 ? a.name.compareTo(b.name) : cmp;
});
Seq<String> categoryNames = categories.keys().toSeq().sort();
categoryNames.insert(0, "all");
for(var seq : categories.values()){
seq.sortComparing(a -> a.name);
}
String current = id.startsWith("@sfx-") ? id.substring(5) : id;
String currentCategory = "all";
for(var choice : choices){
if(choice.name.equals(current)){
currentCategory = choice.category;
break;
}
}
final String selectedCurrentCategory = currentCategory;
showSelectTable(button, (root, hide) -> {
root.left().top();
String[] selectedCategory = {categories.containsKey(selectedCurrentCategory) ? selectedCurrentCategory : "all"};
Table soundList = new Table();
ButtonGroup<Button> tabGroup = new ButtonGroup<>();
Runnable rebuild = () -> {
soundList.clearChildren();
soundList.defaults().left().pad(2f);
soundList.top();
Seq<SoundChoice> visible = new Seq<>();
if("all".equals(selectedCategory[0])){
visible.addAll(choices);
}else{
visible.addAll(categories.get(selectedCategory[0], Seq::new));
}
if(visible.isEmpty()){
soundList.add("@none.found").pad(8f);
return;
}
for(var choice : visible){
soundList.table(row -> {
row.left().top();
row.defaults().left();
row.button(Icon.play, Styles.cleari, 28f, () -> previewSound(choice.sound))
.update(b -> b.getStyle().imageUp = choice.sound != null && choice.sound == lastPreview && choice.sound.countPlaying() > 0 ? Icon.pause : Icon.play)
.size(40f).padRight(6f);
String label = "all".equals(selectedCategory[0]) ? choice.category + "/" + choice.name : choice.name;
row.button(label, Styles.logicTogglet, () -> {
id = "@sfx-" + choice.name;
build(table);
hide.run();
}).growX().height(40f).left().checked(choice.name.equals(current)).with(t -> {
t.getLabelCell().growX().left().labelAlign(Align.left).padLeft(10f);
}).padRight(4f);
}).growX().height(40f).padBottom(3f).row();
}
};
root.table(tabs -> {
tabs.left().top();
tabs.defaults().size(140f, 34f).left();
for(String category : categoryNames){
tabs.button(Strings.capitalize(category), Styles.logicTogglet, () -> {
selectedCategory[0] = category;
rebuild.run();
//fixes flickering
var parent = (Table)root.parent.parent;
parent.pack();
parent.act(0f);
}).checked(selectedCategory[0].equals(category)).group(tabGroup).growX().row();
}
}).top().left().width(160f);
root.add(soundList).top().width(Math.min(Core.graphics.getWidth() / Scl.scl(1f) * 0.9f, 450f));
rebuild.run();
}, () -> {
if(lastPreview != null){
lastPreview.stop();
}
});
}
private static String soundCategory(String entryName){
String normalized = entryName.replace('\\', '/');
int end = normalized.lastIndexOf('/');
if(end < 0) return "Data Patch";
int start = normalized.lastIndexOf('/', end - 1);
return normalized.substring(start + 1, end);
}
private static void previewSound(Sound sound){
if(sound == null || sound == Sounds.none) return;
if(lastPreview != null && lastPreview.countPlaying() > 0 && lastPreview != Sounds.uiButton){
lastPreview.stop();
if(lastPreview == sound) return; //double tap = stop
}
lastPreview = sound;
//don't play the button sound every time
if(sound != Sounds.uiButton) Sounds.uiButton.stop();
//play sound on the UI bus as the main one is paused
sound.play(1, 1, 0, false, true, control.sound.uiBus);
}
}
@RegisterStatement("playmusic")
+5 -1
View File
@@ -218,7 +218,10 @@ public class DataPatcher{
if(reloadContentWorld) fixContentArrays();
}
for(var set : patches){
var patchIt = patches.iterator();
while(patchIt.hasNext()){
PatchAsset set = patchIt.next();
set.warnings.clear();
set.error = false;
@@ -234,6 +237,7 @@ public class DataPatcher{
if(Vars.headless){
String[] planets = req.isArray() ? req.asStringArray() : new String[]{req.asString()};
if(!Structs.contains(planets, Vars.state.rules.planet.name)){
patchIt.remove();
continue;
}
}
+2 -2
View File
@@ -41,8 +41,8 @@ public class CrashHandler{
+ "Cores: " + OS.cores + "\n"
+ (cause == null ? "" : "Likely Cause: " + cause.meta.displayName + " (" + cause.name + " v" + cause.meta.version + ")\n")
+ (enabledMods == null ? "<no mod init>" : "Mods: " + (enabledMods.isEmpty() ? "none (vanilla)" : enabledMods.toString(", ", mod -> mod.name + ":" + mod.meta.version))) + "\n"
+ (state != null && state.data != null && state.data.getPatches().size > 0 ? "Patches: \n" + state.data.getPatches().toString("\n---\n", p -> p.patch) + "\n" : "")
+ "\n\n" + error;
+ "\n\n" + error
+ (state != null && state.data != null && state.data.getPatches().size > 0 ? "\n\nPatches: \n" + state.data.getPatches().toString("\n---\n", p -> p.patch) + "\n" : "");
}
public static void log(Throwable exception){
+4 -4
View File
@@ -1469,16 +1469,16 @@ public class UnitType extends UnlockableContent implements Senseable{
float scl = xscl;
if(unit.inFogTo(Vars.player.team())) return;
if(buildSpeed > 0f){
boolean isPayload = !unit.isAdded();
if(buildSpeed > 0f && !isPayload){
unit.drawBuilding();
}
if(unit.mining()){
if(unit.mining() && !isPayload){
drawMining(unit);
}
boolean isPayload = !unit.isAdded();
Mechc mech = unit instanceof Mechc m ? m : null;
Segmentc seg = unit instanceof Segmentc c ? c : null;
float z =