mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-09-22 13:01:12 +03:00
Merge remote-tracking branch 'origin/v9' into v9
This commit is contained in:
4
.github/workflows/push.yml
vendored
4
.github/workflows/push.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Update bundles
|
||||
if: ${{ github.repository == 'Anuken/Mindustry' }}
|
||||
run: |
|
||||
./gradlew updateBundles
|
||||
./gradlew -DcompileMethodParams updateBundles
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git config --global user.name "Github Actions"
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Update scripts, class mappings and map names
|
||||
if: ${{ github.repository == 'Anuken/Mindustry' }}
|
||||
run: |
|
||||
./gradlew updateScripts fixMaps
|
||||
./gradlew -DcompileMethodParams updateScripts fixMaps
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git config --global user.name "Github Actions"
|
||||
|
||||
@@ -20,6 +20,7 @@ import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
@SupportedAnnotationTypes({
|
||||
"mindustry.annotations.Annotations.EntityDef",
|
||||
@@ -29,6 +30,11 @@ import java.util.*;
|
||||
"mindustry.annotations.Annotations.TypeIOHandler"
|
||||
})
|
||||
public class EntityProcess extends BaseProcessor{
|
||||
static final Pattern selfParamPattern = Pattern.compile("this\\.<(.*)>self\\(\\)");
|
||||
static final Pattern selfPattern = Pattern.compile("self\\(\\)");
|
||||
static final Pattern yieldPattern = Pattern.compile(" yield ");
|
||||
static final Pattern missingPattern = Pattern.compile("\\/\\*missing\\*\\/");
|
||||
|
||||
Seq<EntityDefinition> definitions = new Seq<>();
|
||||
Seq<GroupDefinition> groupDefs = new Seq<>();
|
||||
Seq<Stype> baseComponents;
|
||||
@@ -77,12 +83,12 @@ public class EntityProcess extends BaseProcessor{
|
||||
for(Smethod elem : component.methods()){
|
||||
if(elem.is(Modifier.ABSTRACT) || elem.is(Modifier.NATIVE)) continue;
|
||||
//get all statements in the method, store them
|
||||
methodBlocks.put(elem.descString(), elem.tree().getBody().toString()
|
||||
.replaceAll("this\\.<(.*)>self\\(\\)", "this") //fix parameterized self() calls
|
||||
.replaceAll("self\\(\\)", "this") //fix self() calls
|
||||
.replaceAll(" yield ", "") //fix enchanced switch
|
||||
.replaceAll("\\/\\*missing\\*\\/", "var") //fix vars
|
||||
);
|
||||
String body = elem.tree().getBody().toString();
|
||||
body = selfParamPattern.matcher(body).replaceAll("this"); //fix parameterized self() calls
|
||||
body = selfPattern.matcher(body).replaceAll("this"); //fix self() calls
|
||||
body = yieldPattern.matcher(body).replaceAll(""); //fix enhanced switch
|
||||
body = missingPattern.matcher(body).replaceAll("var"); //fix vars
|
||||
methodBlocks.put(elem.descString(), body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +572,15 @@ public class EntityProcess extends BaseProcessor{
|
||||
String blockName = elem.up().getSimpleName().toString().toLowerCase().replace("comp", "");
|
||||
|
||||
//skip empty blocks
|
||||
if(str.replace("{", "").replace("\n", "").replace("}", "").replace("\t", "").replace(" ", "").isEmpty()){
|
||||
boolean empty = true;
|
||||
for(int i = 0; i < str.length(); i++){
|
||||
char c = str.charAt(i);
|
||||
if(c != '{' && c != '}' && c != '\n' && c != '\t' && c != ' '){
|
||||
empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(empty){
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ allprojects{
|
||||
if(!project.hasProperty("versionType")) versionType = 'official'
|
||||
appName = 'Mindustry'
|
||||
steamworksVersion = 'da41c05b7a2a44c8625ca0ba7d6ca1a2e73f621a'
|
||||
rhinoVersion = 'e74ac129d2bff72c1deb536d7d99a0dbb7046d2e'
|
||||
rhinoVersion = '32395f942976a6d1d21ec9877664554cd5762306'
|
||||
|
||||
loadVersionProps = {
|
||||
return new Properties().with{p -> p.load(file('../core/assets/version.properties').newReader()); return p }
|
||||
@@ -529,3 +529,9 @@ task resolveDependencies{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(System.getProperty("compileMethodParams") != null){
|
||||
project(":core").tasks.withType(JavaCompile).configureEach{
|
||||
options.compilerArgs << '-parameters'
|
||||
}
|
||||
}
|
||||
@@ -321,8 +321,8 @@ server.admins.none = No admins found!
|
||||
server.add = Add Server
|
||||
server.delete = Are you sure you want to delete this server?
|
||||
server.edit = Edit Server
|
||||
server.outdated = [scarlet]Outdated Server![]
|
||||
server.outdated.client = [scarlet]Outdated Client![]
|
||||
server.outdated = [red]⚠ Outdated Server![]
|
||||
server.outdated.client = [red]⚠ Outdated Client![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Custom Build
|
||||
confirmban = Are you sure you want to ban "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Няма намерени администратори!
|
||||
server.add = Добави Сървър
|
||||
server.delete = Сигурни ли сте, че искате да изтриете този сървър?
|
||||
server.edit = Редактирай Сървър
|
||||
server.outdated = [scarlet]Остарял Сървър![]
|
||||
server.outdated.client = [scarlet]Остарял Клиент![]
|
||||
server.outdated = [red]⚠ Остарял Сървър![]
|
||||
server.outdated.client = [red]⚠ Остарял Клиент![]
|
||||
server.version = [gray]в{0} {1}
|
||||
server.custombuild = [accent]Персонализирана компилация
|
||||
confirmban = Сигурни ли сте, че искате да баннете "{0}[white]"?
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -319,8 +319,8 @@ server.admins.none = Žádní správci nebyli nalezeni.
|
||||
server.add = Přidat server
|
||||
server.delete = Jsi si jistý, že chceš smazat tento server?
|
||||
server.edit = Upravit server
|
||||
server.outdated = [scarlet]Zastaralá verze serveru![]
|
||||
server.outdated.client = [scarlet]Zastaralá verze klienta![]
|
||||
server.outdated = [red]⚠ Zastaralá verze serveru![]
|
||||
server.outdated.client = [red]⚠ Zastaralá verze klienta![]
|
||||
server.version = [gray]Verze: {0} {1}[]
|
||||
server.custombuild = [accent]Upravená verze hry[]
|
||||
confirmban = Jsi si jistý, že chceš zakázat hráči "{0}[white]" přístup na server?[]
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = ¡No hay ningún administrador!
|
||||
server.add = Agregar servidor
|
||||
server.delete = ¿Realmente quieres borrar este servidor?
|
||||
server.edit = Editar Servidor
|
||||
server.outdated = [scarlet]¡Servidor Desactualizado![]
|
||||
server.outdated.client = [scarlet]¡Cliente Desactualizado![]
|
||||
server.outdated = [red]⚠ ¡Servidor Desactualizado![]
|
||||
server.outdated.client = [red]⚠ ¡Cliente Desactualizado![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Versión personalizada
|
||||
confirmban = ¿Quieres vetar a "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Walang nahnap na admin!
|
||||
server.add = Maglagay ng Server
|
||||
server.delete = Sigurado ka bang gusto mong tanggalin ang server na ito?
|
||||
server.edit = Ipalit ang Server
|
||||
server.outdated = [scarlet]Di-bagong Server![]
|
||||
server.outdated.client = [scarlet]Di-bagong Client![]
|
||||
server.outdated = [red]⚠ Di-bagong Server![]
|
||||
server.outdated.client = [red]⚠ Di-bagong Client![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Di-karaniwang Build
|
||||
confirmban = Sigurado ka bang gusto mong itanggal si "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Aucun administrateur trouvé !
|
||||
server.add = Ajouter un serveur
|
||||
server.delete = Êtes-vous sûr de vouloir supprimer ce serveur ?
|
||||
server.edit = Modifier le serveur
|
||||
server.outdated = [scarlet]Serveur obsolète ![]
|
||||
server.outdated.client = [scarlet]Client obsolète ![]
|
||||
server.outdated = [red]⚠ Serveur obsolète ![]
|
||||
server.outdated.client = [red]⚠ Client obsolète ![]
|
||||
server.version = [gray]Version : {0} {1}
|
||||
server.custombuild = [accent]Version personnalisée
|
||||
confirmban = Êtes-vous sûr de vouloir bannir "{0}[white]" ?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Nem találhatók adminisztrátorok!
|
||||
server.add = Kiszolgáló hozzáadása
|
||||
server.delete = Biztosan törlöd ezt a kiszolgálót?
|
||||
server.edit = Kiszolgáló szerkesztése
|
||||
server.outdated = [scarlet]Elavult kiszolgáló![]
|
||||
server.outdated.client = [scarlet]Elavult kliens![]
|
||||
server.outdated = [red]⚠ Elavult kiszolgáló![]
|
||||
server.outdated.client = [red]⚠ Elavult kliens![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Egyéni összeállítás
|
||||
confirmban = Biztosan kitiltod a(z) „{0}[white]” nevű játékost?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Tidak ada admin ditemukan!
|
||||
server.add = Tambah Server
|
||||
server.delete = Anda yakin ingin menghapus server ini?
|
||||
server.edit = Sunting Server
|
||||
server.outdated = [scarlet]Server Kedaluwarsa![]
|
||||
server.outdated.client = [scarlet]Klien Kedaluwarsa![]
|
||||
server.outdated = [red]⚠ Server Kedaluwarsa![]
|
||||
server.outdated.client = [red]⚠ Klien Kedaluwarsa![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Bentuk Kustom
|
||||
confirmban = Anda yakin ingin melarang pemain ini untuk masuk lagi?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Nessun amministratore trovato!
|
||||
server.add = Aggiungi Server
|
||||
server.delete = Sei sicuro di voler eliminare questo server?
|
||||
server.edit = Modifica Server
|
||||
server.outdated = [scarlet]Server Obsoleto![]
|
||||
server.outdated.client = [scarlet]Client Obsoleto![]
|
||||
server.outdated = [red]⚠ Server Obsoleto![]
|
||||
server.outdated.client = [red]⚠ Client Obsoleto![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Build Personalizzata
|
||||
confirmban = Sei sicuro di voler bandire "{0}[white]"?
|
||||
|
||||
@@ -187,7 +187,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にエラーがあります。[] Modを無効化するか、エラーを修正してください。
|
||||
mod.enable = 有効化
|
||||
mod.requiresrestart = このModをインストールするためにはゲームの再起動が必要です。
|
||||
@@ -285,7 +285,7 @@ servers.remote = リモートサーバー
|
||||
servers.global = コミュニティサーバー
|
||||
|
||||
servers.disclaimer = コミュニティサーバーは、[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 = 非表示
|
||||
@@ -319,8 +319,8 @@ server.admins.none = 管理者はいません!
|
||||
server.add = サーバーを追加
|
||||
server.delete = サーバーを削除してもよろしいですか?
|
||||
server.edit = サーバーを編集
|
||||
server.outdated = [crimson]古いサーバーです![]
|
||||
server.outdated.client = [crimson]古いクライアントです![]
|
||||
server.outdated = [red]⚠ 古いサーバーです![]
|
||||
server.outdated.client = [red]⚠ 古いクライアントです![]
|
||||
server.version = [lightgray]バージョン: {0} {1}
|
||||
server.custombuild = [accent]カスタムビルド
|
||||
confirmban = {0} をBanしてもよろしいですか?
|
||||
@@ -524,7 +524,7 @@ editor.filters.description = 説明
|
||||
editor.filters.modname = Modの名前
|
||||
editor.filters.prioritizemod = Modの優先順位
|
||||
editor.filters.prioritizecustom = カスタムの優先度
|
||||
editor.filters.planetselect = Planet Selection
|
||||
editor.filters.planetselect = 惑星の選択
|
||||
editor.shiftx = Shift X
|
||||
editor.shifty = Shift Y
|
||||
|
||||
@@ -576,7 +576,7 @@ asset.content.badname = [red]無効な名前です!ほとんどの符号は許
|
||||
asset.content.import.file = ファイルをインポート
|
||||
asset.content.import.clipboard = クリップボードからインポート
|
||||
asset.exists = そのタイプのアセットで、同じパス/名前のものが既に存在します:\n[lightgray]{0}
|
||||
asset.exists.audio = A sound/music asset with that name is already loaded:\n[lightgray]{0}
|
||||
asset.exists.audio = その名前の音声/音楽アセットは同じ名前のものが既に存在します:\n[lightgray]{0}
|
||||
asset.broken = アセットファイルが見つかりませんでした。\nデータの不適切なインポート、または正しくエクスポートされていない保存ファイルが原因である可能性があります。
|
||||
asset.audio.invalid = 音声ファイルを読み込めませんでした。\n有効な OGG または MP3 ファイルであることを確認してください。
|
||||
asset.delete.confirm = このアセットを削除してもよろしいですか?
|
||||
@@ -1440,10 +1440,10 @@ setting.milliseconds = {0} ミリ秒
|
||||
setting.fullscreen.name = フルスクリーン
|
||||
setting.fps.name = FPSを表示
|
||||
setting.console.name = コンソールを有効にする
|
||||
setting.drawhitboxes.name = Display Hitboxes
|
||||
setting.showperformance.name = Display Performance Metrics
|
||||
setting.buildingtimestep.name = Fixed Building Timestep
|
||||
setting.buildingtimestep.description = Runs buildings on a fixed timestep with constant delta,\nas opposed to every frame with variable delta.\nIf buildings cannot be updated fast enough, the game will slow down.\nIf you don't know what that means, don't touch this option.
|
||||
setting.drawhitboxes.name = 当たり判定を表示する
|
||||
setting.showperformance.name = パフォーマンスを表示する
|
||||
setting.buildingtimestep.name = 固定建築更新間隔
|
||||
setting.buildingtimestep.description = 建築物を一定のデルタ時間で固定された更新間隔で動作させます。\nフレームごとの可変デルタ時間での更新とは異なります。\n建築物を十分な速度で更新できない場合、ゲームの進行が遅くなります。\nこの説明の意味が分からない場合は、この設定を変更しないでください。
|
||||
setting.smoothcamera.name = スムーズなカメラ
|
||||
setting.detach-camera.name = フリーカメラ
|
||||
setting.detach-camera.description = 有効にすると、カメラがプレイヤーユニットから独立して移動します。\nこの設定はホットキーに割り当て可能です。
|
||||
@@ -1586,9 +1586,9 @@ keybind.chat_mode.name = チャットモードの変更
|
||||
keybind.drop_unit.name = ユニット投下
|
||||
keybind.zoom_minimap.name = ミニマップのズーム
|
||||
keybind.detach_camera.name = フリーカメラの切り替え
|
||||
keybind.teleport_cursor.name = Teleport To Cursor (Sandbox)
|
||||
keybind.debug_hitboxes.name = デバッグヒットボックスの切り替え
|
||||
keybind.performance_metrics.name = Toggle Performance Metrics
|
||||
keybind.teleport_cursor.name = カーソル位置にテレポート (Sandbox)
|
||||
keybind.debug_hitboxes.name = 当たり判定のデバッグ切り替え
|
||||
keybind.performance_metrics.name = パフォーマンス表示の切り替え
|
||||
mode.help.title = モード説明
|
||||
mode.survival.name = サバイバル
|
||||
mode.survival.description = 通常のモードです。資源も限られる中、自動的にウェーブが進行していきます。\n[gray]マップに敵の出現設定が必要です。
|
||||
@@ -1646,7 +1646,7 @@ rules.enemyCheat = 敵(赤チーム)の資源の無限化
|
||||
rules.blockhealthmultiplier = ブロック体力倍率
|
||||
rules.blockdamagemultiplier = ブロックダメージ倍率
|
||||
rules.unitfactoryactivation = ユニット工場の起動遅延
|
||||
rules.unitfactoryactivation.objective = Unit Factories Activating: {0}
|
||||
rules.unitfactoryactivation.objective = ユニット工場の有効化: {0}
|
||||
rules.unitbuildspeedmultiplier = ユニット製造速度倍率
|
||||
rules.unitcostmultiplier = ユニット製造コスト倍率
|
||||
rules.unithealthmultiplier = ユニット体力倍率
|
||||
@@ -1682,22 +1682,22 @@ rules.title.experimental = 実験的なゲームプレイ
|
||||
rules.title.environment = 環境
|
||||
rules.title.teams = チーム
|
||||
rules.title.planet = 惑星
|
||||
rules.title.music = Music
|
||||
rules.alwaysplaymusic = Always Play Music
|
||||
rules.alwaysplaymusic.info = Overrides the 'always play music' audio setting.
|
||||
rules.ambientmusic = Ambient Music
|
||||
rules.ambientmusic.info = Comma-separated music tracks that randomly play under normal circumstances.\nIf empty, default music will be used.
|
||||
rules.darkmusic = Conflict Music
|
||||
rules.darkmusic.info = Comma-separated music tracks that are more likely to play during battle.\nIf empty, default music will be used.
|
||||
rules.nomusic = Disable Automatic Music
|
||||
rules.nomusic.info = Turns off all automatic music playback.\nUseful for maps that have custom music played using processors.
|
||||
rules.title.music = 音楽
|
||||
rules.alwaysplaymusic = 常に音楽を再生する
|
||||
rules.alwaysplaymusic.info = '常に音楽を再生する'音楽設定を上書きします。
|
||||
rules.ambientmusic = 環境音楽
|
||||
rules.ambientmusic.info = 通常時にランダム再生される音楽トラック(カンマ区切り)。\n空欄の場合はデフォルトの音楽が使用されます。
|
||||
rules.darkmusic = 戦闘音楽
|
||||
rules.darkmusic.info = 戦闘中に再生されやすい音楽トラック(カンマ区切り)。\n空欄の場合はデフォルトの音楽が使用されます。
|
||||
rules.nomusic = 自動音楽再生を無効化する
|
||||
rules.nomusic.info = すべての自動音楽再生を無効にします。\nプロセッサでカスタム音楽を再生するマップに便利です。
|
||||
rules.lighting = 霧
|
||||
rules.fog = 戦場の霧
|
||||
rules.invasions = 敵セクター侵攻
|
||||
rules.legacylaunchpads = 旧発射台仕様
|
||||
rules.legacylaunchpads.info = 着陸台なしで発射台を使用できるようにします(v7.0仕様)。
|
||||
landingpad.legacy.disabled = [scarlet]\ue815 無効[lightgray](旧発射台は有効)
|
||||
rules.hidespawns = Hide Enemy Spawns
|
||||
rules.hidespawns = 敵の出現地点を非表示にする
|
||||
rules.randomwaveai = 不規則なウェーブAI
|
||||
rules.pauseDisabled = 一時停止の無効化
|
||||
rules.fire = 火災
|
||||
@@ -2447,11 +2447,11 @@ block.illuminator.description = 電力が必要な光源です。
|
||||
block.message.description = メッセージを保存し、仲間間の通信に使用します。
|
||||
block.reinforced-message.description = メッセージを保存し、仲間間の通信に使用します。
|
||||
block.world-message.description = マップ作成時に使用するメッセージブロックです。破壊することはできません。
|
||||
block.world-processor.description = A processor for use in mapmaking. Cannot be destroyed. Capable of using privileged instructions, such as those that spawn units or change tiles.
|
||||
block.world-cell.description = A memory cell for use in mapmaking. Cannot be destroyed.
|
||||
block.world-switch.description = A switch block for use in mapmaking. Cannot be destroyed. Can only be interacted by players on the same team.
|
||||
block.shield-projector.description = An unbreakable shield projector for use in mapmaking.
|
||||
block.large-shield-projector.description = An unbreakable shield projector for use in mapmaking.
|
||||
block.world-processor.description = マップ作成に使用するプロセッサーです。破壊できません。ユニットの生成やタイルの変更など、特権命令を使用できます。
|
||||
block.world-cell.description = マップ作成に使用するメモリセルです。破壊できません。
|
||||
block.world-switch.description = マップ作成に使用するスイッチブロックです。破壊できません。同じチームのプレイヤーのみ操作できます。
|
||||
block.shield-projector.description = マップ作成に使用する破壊不可能なシールドプロジェクターです。
|
||||
block.large-shield-projector.description = マップ作成に使用する破壊不可能な大型シールドプロジェクターです。
|
||||
block.graphite-press.description = 石炭を圧縮し、黒鉛を生成します。
|
||||
block.multi-press.description = 黒鉛圧縮機のアップグレード版です。水と電力を使用して、より効率的に石炭を圧縮します。
|
||||
block.silicon-smelter.description = 石炭と砂からシリコンを製造します。
|
||||
@@ -2775,7 +2775,7 @@ lst.radar = 射程内のユニットを探します。
|
||||
lst.sensor = 建物やユニットのデータを取得します。
|
||||
lst.set = 変数を定義し、数値を代入します。
|
||||
lst.operation = 一つから二つの変数を使って演算をします。
|
||||
lst.select = Conditionally set a variable to one of two values.
|
||||
lst.select = 条件に応じて、2つの値のうち1つを変数に設定します。
|
||||
lst.end = 命令スタックの先頭にジャンプします。
|
||||
lst.wait = 一定の秒数待ちます。
|
||||
lst.stop = このプロセッサの実行を停止します。
|
||||
@@ -2788,7 +2788,7 @@ lst.unitlocate = マップ上の任意の座標にある、特定のタイプの
|
||||
lst.getblock = 任意の座標のタイルの情報を取得します。
|
||||
lst.setblock = 任意の座標のタイルの情報を変更します。
|
||||
lst.spawnunit = 任意の座標にユニットをスポーンさせます。
|
||||
lst.spawnbullet = Spawns a bullet at a location.
|
||||
lst.spawnbullet = 指定された位置に弾丸を生成します。
|
||||
lst.applystatus = ユニットからステータス効果を適用または削除する。
|
||||
lst.weathersense = 天候の種類が有効かどうかを確認します。
|
||||
lst.weatherset = 天候の種類の現在の状態を設定します。
|
||||
@@ -2855,8 +2855,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 = 指定した座標に向かって撃ちます。
|
||||
@@ -3054,4 +3054,4 @@ lenum.solarmultiplier = ソーラーパネルの発電量倍率。
|
||||
lenum.dragmultiplier = 環境による抵抗倍率。
|
||||
lenum.ban = 配置または建造できないブロックやユニット。
|
||||
lenum.unban = ユニットまたはブロックの禁止を解除する。
|
||||
lenum.musicvolume = Global music volume multiplier.\nClamped to [0, 1].
|
||||
lenum.musicvolume = 音楽の音量倍率。\n [0, 1]の範囲に制限されます。
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = 관리자를 찾을 수 없습니다!
|
||||
server.add = 서버 추가
|
||||
server.delete = 정말로 이 서버를 삭제하시겠습니까?
|
||||
server.edit = 서버 편집
|
||||
server.outdated = [scarlet]서버 버전이 낮습니다![]
|
||||
server.outdated.client = [scarlet]클라이언트 버전이 낮습니다![]
|
||||
server.outdated = [red]⚠ 서버 버전이 낮습니다![]
|
||||
server.outdated.client = [red]⚠ 클라이언트 버전이 낮습니다![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]사용자 정의 서버
|
||||
confirmban = 정말로 "{0}[white]" 을(를) 차단하시겠습니까?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Nenhum administrador encontrado!
|
||||
server.add = Adicionar servidor
|
||||
server.delete = Você tem certeza que quer deletar esse servidor?
|
||||
server.edit = Editar Servidor
|
||||
server.outdated = [scarlet]Servidor Desatualizado![]
|
||||
server.outdated.client = [scarlet]Cliente Desatualizado![]
|
||||
server.outdated = [red]⚠ Servidor Desatualizado![]
|
||||
server.outdated.client = [red]⚠ Cliente Desatualizado![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Versão Customizada
|
||||
confirmban = Você tem certeza que quer banir "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Nu s-au găsit admini!
|
||||
server.add = Adăugă server
|
||||
server.delete = Sigur vrei să ștergi acest server?
|
||||
server.edit = Editează Server
|
||||
server.outdated = [scarlet]Server Învechit![]
|
||||
server.outdated.client = [scarlet]Client Învechit![]
|
||||
server.outdated = [red]⚠ Server Învechit![]
|
||||
server.outdated.client = [red]⚠ Client Învechit![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Build Personalizat
|
||||
confirmban = Sigur vrei să interzici jucătorul "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Администраторов нет!
|
||||
server.add = Добавить сервер
|
||||
server.delete = Вы действительно хотите удалить этот сервер?
|
||||
server.edit = Редактировать сервер
|
||||
server.outdated = [scarlet]Устаревший сервер![]
|
||||
server.outdated.client = [scarlet]Устаревший клиент![]
|
||||
server.outdated = [red]⚠ Устаревший сервер![]
|
||||
server.outdated.client = [red]⚠ Устаревший клиент![]
|
||||
server.version = [gray]Версия: {0} {1}
|
||||
server.custombuild = [accent]Пользовательская сборка
|
||||
confirmban = Вы действительно хотите заблокировать игрока «{0}[white]»?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Nema pronađenih administratora!
|
||||
server.add = Dodaj Server
|
||||
server.delete = Da li želite da obrišete ovaj server?
|
||||
server.edit = Izmeni Server
|
||||
server.outdated = [scarlet]Zastareli Server![]
|
||||
server.outdated.client = [scarlet]Zastareli Klijent![]
|
||||
server.outdated = [red]⚠ Zastareli Server![]
|
||||
server.outdated.client = [red]⚠ Zastareli Klijent![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Svjojehodna Verzija
|
||||
confirmban = Da li ste sigurni da želite da [scarlet]trajno[] izbacite "{0}[white]"?
|
||||
|
||||
@@ -154,10 +154,10 @@ mods.update.available = [lightgray]Cập Nhật Có Sẵn: {0}
|
||||
mods.update.all = Cập Nhật Tất Cả Bản Mod
|
||||
mods.downloading = [accent]Đang Tải Xuống Mod: {0}
|
||||
|
||||
mod.warning = ⚠ Mods can execute arbitrary code.\n\nNever download mods from sources you don't trust - they could steal data or compromise your computer.
|
||||
mod.warning.read = I actually read this
|
||||
mod.warning.accept = I accept the responsibility
|
||||
mod.warning.deny = Disable my mods
|
||||
mod.warning = ⚠ Các bản mod có thể thực thi mã tùy ý.\n\nKhông bao giờ tải mod từ nguồn bạn không tin tưởng - họ có thể ăn cắp dữ liệu hoặc gây hại đến máy tính của bạn.
|
||||
mod.warning.read = Tôi thực sự đã đọc diều này
|
||||
mod.warning.accept = Tôi nhận trách nhiệm
|
||||
mod.warning.deny = Tắt các mod của tôi
|
||||
mod.installed = [[Đã cài đặt]
|
||||
mod.display = [gray]Mod:[accent] {0}
|
||||
mod.enabled = [lightgray]Đã bật
|
||||
@@ -319,8 +319,8 @@ server.admins.none = Không tìm thấy quản trị viên nào!
|
||||
server.add = Thêm máy chủ
|
||||
server.delete = Bạn có chắc chắn muốn xóa máy chủ này không?
|
||||
server.edit = Chỉnh sửa máy chủ
|
||||
server.outdated = [scarlet]Máy chủ lỗi thời![]
|
||||
server.outdated.client = [scarlet]Máy khách lỗi thời![]
|
||||
server.outdated = [red]⚠ Máy chủ lỗi thời![]
|
||||
server.outdated.client = [red]⚠ Máy khách lỗi thời![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]Bản dựng tùy chỉnh
|
||||
confirmban = Bạn có chắc chắn muốn cấm "{0}[white]"?
|
||||
|
||||
@@ -319,8 +319,8 @@ server.admins.none = 找不到管理员!
|
||||
server.add = 添加服务器
|
||||
server.delete = 确定要删除这个服务器吗?
|
||||
server.edit = 编辑服务器
|
||||
server.outdated = [scarlet]服务器版本过低![]
|
||||
server.outdated.client = [scarlet]客户端版本过低![]
|
||||
server.outdated = [red]⚠ 服务器版本过低![]
|
||||
server.outdated.client = [red]⚠ 客户端版本过低![]
|
||||
server.version = [gray]v{0} {1}
|
||||
server.custombuild = [accent]自行编译
|
||||
confirmban = 确定要封禁“{0}[white]”吗?
|
||||
@@ -3016,7 +3016,7 @@ lenum.payenter = 进入/降落到单位下方的荷载方块中
|
||||
lenum.flag = 赋予单位数字形式的标记
|
||||
lenum.mine = 从某个位置采集矿物
|
||||
lenum.build = 建造建筑
|
||||
lenum.getblock = 根据坐标获取建筑物、环境块和环境墙体类型。\n单位必须在位置范围内,否则返回空值。
|
||||
lenum.getblock = 根据坐标获取建筑物、环境块和环境墙体类型。\n坐标必须在单位的雷达范围内,否则返回空值。
|
||||
lenum.within = 检查单位是否接近了某个位置
|
||||
lenum.boost = 开始/停止助推
|
||||
|
||||
|
||||
@@ -48,15 +48,28 @@ const Arrays = Packages.java.util.Arrays
|
||||
const TextureFilter = Packages.arc.graphics.TextureFilter
|
||||
const TextureWrap = Packages.arc.graphics.TextureWrap
|
||||
importPackage(Packages.arc)
|
||||
importPackage(Packages.arc.assets)
|
||||
importPackage(Packages.arc.assets.loaders)
|
||||
importPackage(Packages.arc.audio)
|
||||
importPackage(Packages.arc.backend.headless)
|
||||
importPackage(Packages.arc.files)
|
||||
importPackage(Packages.arc.flabel)
|
||||
importPackage(Packages.arc.freetype)
|
||||
importPackage(Packages.arc.func)
|
||||
importPackage(Packages.arc.fx)
|
||||
importPackage(Packages.arc.fx.filters)
|
||||
importPackage(Packages.arc.fx.util)
|
||||
importPackage(Packages.arc.graphics)
|
||||
importPackage(Packages.arc.graphics.g2d)
|
||||
importPackage(Packages.arc.graphics.g3d)
|
||||
importPackage(Packages.arc.graphics.gl)
|
||||
importPackage(Packages.arc.input)
|
||||
importPackage(Packages.arc.math)
|
||||
importPackage(Packages.arc.math.geom)
|
||||
importPackage(Packages.arc.mock)
|
||||
importPackage(Packages.arc.net)
|
||||
importPackage(Packages.arc.net.dns)
|
||||
importPackage(Packages.arc.packer)
|
||||
importPackage(Packages.arc.scene)
|
||||
importPackage(Packages.arc.scene.actions)
|
||||
importPackage(Packages.arc.scene.event)
|
||||
@@ -194,6 +207,7 @@ const LaunchItemEvent = Packages.mindustry.game.EventType.LaunchItemEvent
|
||||
const SectorInvasionEvent = Packages.mindustry.game.EventType.SectorInvasionEvent
|
||||
const SectorLoseEvent = Packages.mindustry.game.EventType.SectorLoseEvent
|
||||
const SaveLoadEvent = Packages.mindustry.game.EventType.SaveLoadEvent
|
||||
const TextureStreamEvent = Packages.mindustry.game.EventType.TextureStreamEvent
|
||||
const DataPatchLoadEvent = Packages.mindustry.game.EventType.DataPatchLoadEvent
|
||||
const WorldLoadEndEvent = Packages.mindustry.game.EventType.WorldLoadEndEvent
|
||||
const WorldLoadBeginEvent = Packages.mindustry.game.EventType.WorldLoadBeginEvent
|
||||
|
||||
@@ -13,6 +13,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.defense.*;
|
||||
import mindustry.world.blocks.units.UnitAssembler.*;
|
||||
|
||||
import static arc.graphics.g2d.Draw.rect;
|
||||
@@ -1933,7 +1934,7 @@ public class Fx{
|
||||
float angle = rand.random(360f);
|
||||
float lenRand = rand.random(0.5f, 1.2f);
|
||||
Tmp.v1.trns(angle, circleRad);
|
||||
|
||||
|
||||
for(int s : Mathf.signs){
|
||||
Drawf.tri(e.x + Tmp.v1.x, e.y + Tmp.v1.y, e.fout() * 10f, e.fout() * 10f * lenRand + 8f, angle + 90f + s * 90f);
|
||||
}
|
||||
@@ -2807,12 +2808,10 @@ public class Fx{
|
||||
shieldBreak = new Effect(40, e -> {
|
||||
color(e.color);
|
||||
stroke(3f * e.fout());
|
||||
if(e.data instanceof ForceFieldAbility ab){
|
||||
Lines.poly(e.x, e.y, ab.sides, e.rotation + e.fin(), ab.rotation);
|
||||
return;
|
||||
}
|
||||
int sides = e.data instanceof ForceProjector f ? f.sides : e.data instanceof ForceFieldAbility a ? a.sides : 6;
|
||||
float rotation = e.data instanceof ForceProjector f ? f.shieldRotation : e.data instanceof ForceFieldAbility a ? a.rotation : 6;
|
||||
|
||||
Lines.poly(e.x, e.y, 6, e.rotation + e.fin());
|
||||
Lines.poly(e.x, e.y, sides, e.rotation + e.fin(), rotation);
|
||||
}).followParent(true),
|
||||
|
||||
arcShieldBreak = new Effect(40, e -> {
|
||||
|
||||
@@ -476,7 +476,7 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
PerfCounter.unitUpdate.begin();
|
||||
if(editor){
|
||||
Groups.unit.update(Unitc::isPlayer);
|
||||
Groups.unit.update(u -> u.isPlayer() || u.spawnedByCore);
|
||||
}else{
|
||||
Groups.unit.update();
|
||||
}
|
||||
|
||||
@@ -164,6 +164,20 @@ public class NetClient implements ApplicationListener{
|
||||
Call.requestAssets(missing.toArray());
|
||||
});
|
||||
|
||||
net.handleClient(TextureStream.class, data -> {
|
||||
try(DataInputStream in = new DataInputStream(data.stream)){
|
||||
String name = in.readUTF();
|
||||
byte[] pngData = in.readAllBytes();
|
||||
if(!headless){
|
||||
//empty image data means we're removing the texture instead. see NetServer.removeTexture()
|
||||
if(pngData.length == 0) state.data.removeTexture(name);
|
||||
else state.data.addTexture(name, pngData);
|
||||
}
|
||||
}catch(IOException e){
|
||||
Log.err("Failed to read server texture stream", e);
|
||||
}
|
||||
});
|
||||
|
||||
net.handleClient(StreamBegin.class, data -> {
|
||||
boolean isWorld = data.type == Net.packetIdWorldStream, isAssets = data.type == Net.packetIdAssetStream;
|
||||
|
||||
|
||||
@@ -552,6 +552,40 @@ public class NetServer implements ApplicationListener{
|
||||
debug("Packed @ of world data to @ (@ / @)", Strings.formatByteCount(stream.size()), player.name, player.con.address, player.uuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams a texture to a single connected client. This may take some time if the image is large or if the connection is poor.
|
||||
* Make sure to call {@link #removeTexture(NetConnection, String)} when the image is no longer needed to prevent resource leaks.
|
||||
* Use {@link PixmapIO#writePngBytes} to get Pixmap bytes. Respect {@link mindustry.mod.DataPatcher#maxImageSize}.
|
||||
* Should be called on main thread to ensure correct ordering of sends (multiple with same name), and removals (remove called right after adding). */
|
||||
public void sendTexture(NetConnection con, String name, byte[] pngData){
|
||||
var stream = new ByteArrayOutputStream();
|
||||
NetworkIO.packTexture(stream, name, pngData);
|
||||
con.sendStreamAsync(new TextureStream(), stream);
|
||||
}
|
||||
|
||||
/** Streams a texture to every connected client.
|
||||
* See {@link #sendTexture(NetConnection, String, byte[])} for more info. */
|
||||
public void sendTexture(String name, byte[] pngData){
|
||||
var stream = new ByteArrayOutputStream();
|
||||
NetworkIO.packTexture(stream, name, pngData);
|
||||
for(NetConnection con : net.getConnections()){
|
||||
con.sendStreamAsync(new TextureStream(), stream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes a texture previously sent with {@link #sendTexture(NetConnection, String, byte[])} from a single client.
|
||||
* If called while the texture is in use, it will be replaced with a black rectangle. Do not do this.
|
||||
* Should be called on main thread for the same reasons as sendTexture. */
|
||||
public void removeTexture(NetConnection con, String name){
|
||||
sendTexture(con, name, Streams.emptyBytes);
|
||||
}
|
||||
|
||||
/** Removes a texture previously sent with {@link #sendTexture(String, byte[])} from every connected client.
|
||||
* See {@link #removeTexture(NetConnection, String)} for more info. */
|
||||
public void removeTexture(String name){
|
||||
sendTexture(name, Streams.emptyBytes);
|
||||
}
|
||||
|
||||
public void addPacketHandler(String type, Cons2<Player, String> handler){
|
||||
customPacketHandlers.get(type, Seq::new).add(handler);
|
||||
}
|
||||
|
||||
@@ -329,9 +329,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
}});
|
||||
}else{
|
||||
new Dialog(titleText){{
|
||||
cont.margin(30).add(text).padRight(6f);
|
||||
cont.image().width(400f).pad(2).height(4f).color(Pal.accent);
|
||||
cont.row();
|
||||
cont.add(text).row();
|
||||
TextFieldFilter filter = numbers ? TextFieldFilter.digitsOnly : (f, c) -> true;
|
||||
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
|
||||
TextField field = cont.field(def, t -> {}).size(400f, 50f).get();
|
||||
field.setMaxLength(textLength);
|
||||
field.setFilter(filter);
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
|
||||
@@ -171,7 +171,7 @@ public class MapContentView implements AssetView{
|
||||
}
|
||||
|
||||
if(list.getChildren().isEmpty()){
|
||||
list.add("@patch.none");
|
||||
list.add("@none.found");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ public class ForceFieldAbility extends Ability{
|
||||
|
||||
//self-destructing units can have a shield on death
|
||||
if(unit.shield > 0f && !wasBroken){
|
||||
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), this);
|
||||
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.type.shieldColor(unit), sides);
|
||||
breakSound.at(unit.x, unit.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,22 @@ public class ShieldArcAbility extends Ability{
|
||||
//translate bullet back to where it was upon collision
|
||||
b.trns(-b.vel.x, -b.vel.y);
|
||||
|
||||
float penX = Math.abs(paramPos.x - b.x), penY = Math.abs(paramPos.y - b.y);
|
||||
|
||||
if(penX > penY){
|
||||
b.vel.x *= -1;
|
||||
b.vel.y *= paramField.reflectVel;
|
||||
}else{
|
||||
b.vel.y *= -1;
|
||||
b.vel.x *= paramField.reflectVel;
|
||||
float nx = b.x - paramPos.x, ny = b.y - paramPos.y;
|
||||
float nlen = Mathf.len(nx, ny);
|
||||
if(nlen > 0.0001f){
|
||||
nx /= nlen;
|
||||
ny /= nlen;
|
||||
}
|
||||
|
||||
float dot = b.vel.x * nx + b.vel.y * ny;
|
||||
float rx = b.vel.x - 2f * dot * nx;
|
||||
float ry = b.vel.y - 2f * dot * ny;
|
||||
float outDot = rx * nx + ry * ny;
|
||||
float normalX = outDot * nx, normalY = outDot * ny;
|
||||
float tangX = rx - normalX, tangY = ry - normalY;
|
||||
|
||||
b.vel.set(normalX + tangX * paramField.reflectVel, normalY + tangY * paramField.reflectVel);
|
||||
|
||||
b.owner = paramUnit;
|
||||
b.team = paramUnit.team;
|
||||
b.time = b.lifetime * paramField.reflectTime;
|
||||
|
||||
@@ -119,6 +119,15 @@ public class EventType{
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when a new texture is received from the server via {@link mindustry.core.NetServer#sendTexture}. */
|
||||
public static class TextureStreamEvent {
|
||||
public final String name;
|
||||
|
||||
public TextureStreamEvent(String name){
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveLoadEvent{
|
||||
public final boolean isMap;
|
||||
|
||||
|
||||
@@ -10,8 +10,67 @@ import mindustry.entities.units.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.distribution.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
interface BridgePlacer{
|
||||
boolean unlockedNow();
|
||||
|
||||
boolean positionsValid(int x1, int y1, int x2, int y2);
|
||||
|
||||
void applyToPlans(BuildPlan cur, BuildPlan other);
|
||||
}
|
||||
|
||||
class ItemBridgePlacer implements BridgePlacer{
|
||||
private final ItemBridge bridge;
|
||||
|
||||
ItemBridgePlacer(ItemBridge bridge){
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlockedNow(){
|
||||
return bridge.unlockedNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positionsValid(int x1, int y1, int x2, int y2){
|
||||
return bridge.positionsValid(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToPlans(BuildPlan cur, BuildPlan other){
|
||||
cur.block = bridge;
|
||||
other.block = bridge;
|
||||
other.config = new Point2(cur.x - other.x, cur.y - other.y);
|
||||
}
|
||||
}
|
||||
|
||||
class DirectionBridgePlacer implements BridgePlacer{
|
||||
private final DirectionBridge bridge;
|
||||
|
||||
DirectionBridgePlacer(DirectionBridge bridge){
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlockedNow(){
|
||||
return bridge.unlockedNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positionsValid(int x1, int y1, int x2, int y2){
|
||||
return bridge.positionsValid(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToPlans(BuildPlan cur, BuildPlan other){
|
||||
cur.block = bridge;
|
||||
other.block = bridge;
|
||||
}
|
||||
}
|
||||
|
||||
public class Placement{
|
||||
private static final Seq<BuildPlan> plans1 = new Seq<>();
|
||||
private static final Seq<Point2> tmpPoints = new Seq<>(), tmpPoints2 = new Seq<>();
|
||||
@@ -19,6 +78,7 @@ public class Placement{
|
||||
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
|
||||
private static final Bresenham2 bres = new Bresenham2();
|
||||
private static final Seq<Point2> points = new Seq<>();
|
||||
private static final IntSeq tmpInts = new IntSeq(), tmpInts2 = new IntSeq();
|
||||
|
||||
//for pathfinding
|
||||
private static final IntFloatMap costs = new IntFloatMap();
|
||||
@@ -97,7 +157,7 @@ public class Placement{
|
||||
boolean addedLast = false;
|
||||
|
||||
outer:
|
||||
for(int i = 0; i < base.size;){
|
||||
for(int i = 0; i < base.size; ){
|
||||
var point = base.get(i);
|
||||
result.add(point);
|
||||
if(i == base.size - 1) addedLast = true;
|
||||
@@ -115,7 +175,7 @@ public class Placement{
|
||||
}
|
||||
|
||||
//if it got here, that means nothing was found. try to proceed to the next node anyway
|
||||
i ++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if(!addedLast && !base.isEmpty()) result.add(base.peek());
|
||||
@@ -132,7 +192,8 @@ public class Placement{
|
||||
calculateBridges(plans, bridge, false, t -> false);
|
||||
}
|
||||
|
||||
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge, boolean hasJunction, Boolf<Block> avoid){
|
||||
private static void calculateBridges(Seq<BuildPlan> plans, BridgePlacer bridge, boolean hasJunction, Boolf<Block> avoid){
|
||||
//common checks
|
||||
if(isSidePlace(plans) || plans.size == 0) return;
|
||||
|
||||
//check for orthogonal placement + unlocked state
|
||||
@@ -140,139 +201,119 @@ public class Placement{
|
||||
return;
|
||||
}
|
||||
|
||||
smartCalculateBridges(plans, bridge, hasJunction, avoid);
|
||||
}
|
||||
|
||||
private static void smartCalculateBridges(Seq<BuildPlan> plans, BridgePlacer bridge, boolean hasJunction, Boolf<Block> avoid){
|
||||
Boolf<BuildPlan> placeable = plan ->
|
||||
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
|
||||
!(plan != plans.first() && plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
|
||||
!(plan != plans.first() && plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
|
||||
var result = plans1.clear();
|
||||
var rotated = plans.first().tile() != null && plans.first().tile().absoluteRelativeTo(plans.peek().x, plans.peek().y) == Mathf.mod(plans.first().rotation + 2, 4);
|
||||
|
||||
outer:
|
||||
for(int i = 0; i < plans.size;){
|
||||
// Use DP for smarter bridge placement
|
||||
final int conveyorCost = 3;
|
||||
final int junctionCost = 30;
|
||||
final int bridgeCost = 200;
|
||||
final int bridgeOverEmptyPenalty = 5;
|
||||
final int infCost = Integer.MAX_VALUE / 2; // Avoid overflow when adding
|
||||
|
||||
int N = plans.size;
|
||||
var dp = tmpInts.setSize(2 * N);
|
||||
var parent = tmpInts2.setSize(2 * N);
|
||||
Arrays.fill(dp, 0, 2 * N, infCost);
|
||||
Arrays.fill(parent, 0, 2 * N, -1);
|
||||
dp[0] = 0;
|
||||
dp[N] = bridgeCost;
|
||||
|
||||
for(int i = 1; i < N; i++){
|
||||
var cur = plans.get(i);
|
||||
result.add(cur);
|
||||
boolean canPlace = placeable.get(cur);
|
||||
boolean needJunction = hasJunction && (cur.tile() == null || avoid.get(cur.tile().block()));
|
||||
|
||||
//gap found
|
||||
if(i < plans.size - 1 && placeable.get(cur) && !placeable.get(plans.get(i + 1))){
|
||||
boolean wereSame = true;
|
||||
if(!canPlace && !needJunction){
|
||||
continue;
|
||||
}
|
||||
|
||||
//find the closest valid position within range
|
||||
for(int j = i + 1; j < plans.size; j++){
|
||||
var other = plans.get(j);
|
||||
|
||||
//out of range now, set to current position and keep scanning forward for next occurrence
|
||||
if(!bridge.positionsValid(cur.x, cur.y, other.x, other.y)){
|
||||
//add 'missed' conveyors
|
||||
for(int k = i + 1; k < j; k++){
|
||||
result.add(plans.get(k));
|
||||
}
|
||||
i = j;
|
||||
continue outer;
|
||||
}else if(placeable.get(other)){
|
||||
|
||||
if(wereSame && hasJunction){
|
||||
//the gap is fake, it's just conveyors that can be replaced with junctions
|
||||
i ++;
|
||||
continue outer;
|
||||
}else{
|
||||
//found a link, assign bridges
|
||||
cur.block = bridge;
|
||||
other.block = bridge;
|
||||
if(rotated){
|
||||
other.config = new Point2(cur.x - other.x, cur.y - other.y);
|
||||
}else{
|
||||
cur.config = new Point2(other.x - cur.x, other.y - cur.y);
|
||||
}
|
||||
|
||||
i = j;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
if(other.tile() != null && !avoid.get(other.tile().block())){
|
||||
wereSame = false;
|
||||
}
|
||||
}
|
||||
|
||||
//if it got here, that means nothing was found. this likely means there's a bunch of stuff at the end; add it and bail out
|
||||
for(int j = i + 1; j < plans.size; j++){
|
||||
result.add(plans.get(j));
|
||||
}
|
||||
break;
|
||||
if(canPlace){
|
||||
dp[i] = dp[i - 1] + conveyorCost;
|
||||
}else{
|
||||
i ++;
|
||||
dp[i] = dp[i - 1] + junctionCost;
|
||||
}
|
||||
parent[i] = i - 1;
|
||||
|
||||
if(dp[i] < infCost && canPlace){
|
||||
dp[N + i] = dp[i] + bridgeCost;
|
||||
parent[N + i] = i - 1;
|
||||
}
|
||||
|
||||
// Consider bridges from all previous positions
|
||||
if(i >= 2 && canPlace){
|
||||
int emptyPenalty = 0;
|
||||
if(placeable.get(plans.get(i - 1))){
|
||||
emptyPenalty += bridgeOverEmptyPenalty;
|
||||
}
|
||||
|
||||
for(int j = i - 2; j >= 0; j--){
|
||||
var other = plans.get(j);
|
||||
if(!bridge.positionsValid(cur.x, cur.y, other.x, other.y)){
|
||||
break; // No need to check further back if this one is out of range
|
||||
}
|
||||
|
||||
if(placeable.get(other)){
|
||||
int cost = dp[N + j] + bridgeCost + emptyPenalty;
|
||||
if(dp[N + i] > cost){
|
||||
dp[N + i] = cost;
|
||||
parent[N + i] = j;
|
||||
}
|
||||
emptyPenalty += bridgeOverEmptyPenalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dp[N + i] < dp[i]){
|
||||
dp[i] = dp[N + i];
|
||||
parent[i] = parent[N + i];
|
||||
}
|
||||
|
||||
if(canPlace && dp[i] >= infCost){
|
||||
// Unable to connect, restart a new segment
|
||||
dp[i] = 0;
|
||||
dp[N + i] = bridgeCost;
|
||||
}
|
||||
}
|
||||
|
||||
// Backtrack to assign bridges
|
||||
int bridgeMode = 0;
|
||||
for(int i = N - 1; i >= 0; ){
|
||||
var cur = plans.get(i);
|
||||
int p = parent[bridgeMode + i];
|
||||
|
||||
if(p == -1 || p == i - 1){
|
||||
// No connection, connected by conveyor, or junction, no bridge needed
|
||||
result.add(cur);
|
||||
bridgeMode = 0;
|
||||
i--;
|
||||
}else{
|
||||
// Connected by bridge, assign it
|
||||
var other = plans.get(p);
|
||||
bridge.applyToPlans(cur, other);
|
||||
result.add(cur);
|
||||
i = p;
|
||||
bridgeMode = N;
|
||||
}
|
||||
}
|
||||
|
||||
result.reverse();
|
||||
plans.set(result);
|
||||
}
|
||||
|
||||
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge, boolean hasJunction, Boolf<Block> avoid){
|
||||
calculateBridges(plans, new ItemBridgePlacer(bridge), hasJunction, avoid);
|
||||
}
|
||||
|
||||
public static void calculateBridges(Seq<BuildPlan> plans, DirectionBridge bridge, boolean hasJunction, Boolf<Block> avoid){
|
||||
if(isSidePlace(plans) || plans.size == 0) return;
|
||||
|
||||
//check for orthogonal placement + unlocked state
|
||||
if(!(plans.first().x == plans.peek().x || plans.first().y == plans.peek().y) || !bridge.unlockedNow()){
|
||||
return;
|
||||
}
|
||||
|
||||
Boolf<BuildPlan> placeable = plan ->
|
||||
(plan.placeable(player.team()) || (plan.tile() != null && plan.tile().block() == plan.block)) && //don't count the same block as inaccessible
|
||||
!(plan != plans.first() && plan.build() != null && plan.build().rotation != plan.rotation && avoid.get(plan.tile().block()));
|
||||
|
||||
var result = plans1.clear();
|
||||
|
||||
outer:
|
||||
for(int i = 0; i < plans.size;){
|
||||
var cur = plans.get(i);
|
||||
result.add(cur);
|
||||
|
||||
//gap found
|
||||
if(i < plans.size - 1 && placeable.get(cur) && !placeable.get(plans.get(i + 1))){
|
||||
boolean wereSame = true;
|
||||
|
||||
//find the closest valid position within range
|
||||
for(int j = i + 1; j < plans.size; j++){
|
||||
var other = plans.get(j);
|
||||
|
||||
//out of range now, set to current position and keep scanning forward for next occurrence
|
||||
if(!bridge.positionsValid(cur.x, cur.y, other.x, other.y)){
|
||||
//add 'missed' conveyors
|
||||
for(int k = i + 1; k < j; k++){
|
||||
result.add(plans.get(k));
|
||||
}
|
||||
i = j;
|
||||
continue outer;
|
||||
}else if(placeable.get(other)){
|
||||
|
||||
if(wereSame && hasJunction){
|
||||
//the gap is fake, it's just conveyors that can be replaced with junctions
|
||||
i ++;
|
||||
continue outer;
|
||||
}else{
|
||||
//found a link, assign bridges
|
||||
cur.block = bridge;
|
||||
other.block = bridge;
|
||||
i = j;
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
if(other.tile() != null && !avoid.get(other.tile().block())){
|
||||
wereSame = false;
|
||||
}
|
||||
}
|
||||
|
||||
//if it got here, that means nothing was found. this likely means there's a bunch of stuff at the end; add it and bail out
|
||||
for(int j = i + 1; j < plans.size; j++){
|
||||
result.add(plans.get(j));
|
||||
}
|
||||
break;
|
||||
}else{
|
||||
i ++;
|
||||
}
|
||||
}
|
||||
|
||||
plans.set(result);
|
||||
calculateBridges(plans, new DirectionBridgePlacer(bridge), hasJunction, avoid);
|
||||
}
|
||||
|
||||
private static float tileHeuristic(Tile tile, Tile other){
|
||||
|
||||
8
core/src/mindustry/logic/LDrawable.java
Normal file
8
core/src/mindustry/logic/LDrawable.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
public interface LDrawable {
|
||||
boolean drawable(LExecutor exec);
|
||||
void draw(LongSeq buffer);
|
||||
}
|
||||
@@ -1040,8 +1040,8 @@ public class LExecutor{
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
if(target.building() instanceof LogicDisplayBuild d && d.isValid() && (d.team == exec.team || exec.privileged)){
|
||||
d.flushCommands(exec.graphicsBuffer);
|
||||
if(target.building() instanceof LDrawable d && d.drawable(exec)){
|
||||
d.draw(exec.graphicsBuffer);
|
||||
}
|
||||
exec.graphicsBuffer.clear();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.Log.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.mod.data.*;
|
||||
|
||||
import java.io.*;
|
||||
@@ -21,8 +22,14 @@ public class DataImagePacker{
|
||||
private static int defaultAtlasDepth = -1;
|
||||
|
||||
public static final String regionPrefix = "dp-";
|
||||
/** Prefix for images pushed dynamically by the server via {@link #addTexture(String, byte[])}, as opposed to map/mod data patches. */
|
||||
public static final String serverRegionPrefix = "net-";
|
||||
|
||||
private @Nullable Seq<AtlasRegion> addedRegions;
|
||||
/** Textures added at runtime via addTexture(), keyed by their unprefixed name. Tracked separately from patchAtlas so they can be added/removed individually. */
|
||||
private final ObjectMap<String, Texture> serverImages = new ObjectMap<>();
|
||||
/** Single threaded executor so that concurrent calls always complete in FIFO order. */
|
||||
private final ExecutorService textureExecutor = Threads.executor("Server Texture Streamer", 1);
|
||||
|
||||
/** Packs a new set of images. If images are already packed, disposes of the old ones. */
|
||||
public void pack(Seq<ImageAsset> images){
|
||||
@@ -159,6 +166,63 @@ public class DataImagePacker{
|
||||
}
|
||||
addedRegions = null;
|
||||
}
|
||||
|
||||
serverImages.each((name, texture) -> {
|
||||
Core.atlas.getRegionMap().remove(serverRegionPrefix + name);
|
||||
Core.atlas.getDrawables().remove(serverRegionPrefix + name);
|
||||
texture.dispose();
|
||||
});
|
||||
serverImages.clear();
|
||||
}
|
||||
|
||||
/** Decodes PNG bytes and registers them into the atlas under "netRegionPrefix + name", replacing any existing image with the same name. Safe to call from any thread. */
|
||||
public void addTexture(String name, byte[] pngData){
|
||||
textureExecutor.execute(() -> {
|
||||
Pixmap pixmap;
|
||||
try{
|
||||
pixmap = new Pixmap(pngData);
|
||||
}catch(Throwable e){
|
||||
Log.err("Invalid server-provided image data for '" + name + "'", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if(pixmap.width > DataPatcher.maxImageSize || pixmap.height > DataPatcher.maxImageSize){
|
||||
Log.err("Server-provided image '" + name + "' exceeds max size of " + DataPatcher.maxImageSize);
|
||||
pixmap.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
Core.app.post(() -> {
|
||||
removeTexture(name);
|
||||
|
||||
Texture texture = new Texture(pixmap);
|
||||
pixmap.dispose();
|
||||
|
||||
var region = new TextureAtlas.AtlasRegion(texture, 0, 0, texture.width, texture.height);
|
||||
region.name = serverRegionPrefix + name;
|
||||
|
||||
serverImages.put(name, texture);
|
||||
Core.atlas.getRegionMap().put(serverRegionPrefix + name, region);
|
||||
|
||||
//fired with the full atlas region name
|
||||
Events.fire(new TextureStreamEvent(serverRegionPrefix + name));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Removes a texture previously added with {@link #addTexture}. Must be called on the main thread. */
|
||||
private void removeTexture(String name){
|
||||
Texture texture = serverImages.remove(name);
|
||||
if(texture != null){
|
||||
Core.atlas.getRegionMap().remove(serverRegionPrefix + name);
|
||||
Core.atlas.getDrawables().remove(serverRegionPrefix + name);
|
||||
texture.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/** Queues a texture for removal. Will run after any pending {@link #addTexture} calls so that races do not occur. */
|
||||
public void removeTextureQueued(String name){
|
||||
textureExecutor.execute(() -> Core.app.post(() -> removeTexture(name)));
|
||||
}
|
||||
|
||||
public void printStats(PixmapPacker packer){
|
||||
|
||||
@@ -10,6 +10,7 @@ import mindustry.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.mod.data.*;
|
||||
import mindustry.net.*;
|
||||
|
||||
public class DataManager{
|
||||
private DataPatcher patcher = new DataPatcher();
|
||||
@@ -28,7 +29,6 @@ public class DataManager{
|
||||
|
||||
public void reloadContent(boolean reloadArrays){
|
||||
|
||||
patcher.unapply(reloadArrays);
|
||||
patcher.apply(getPatches(), getContent(), reloadArrays);
|
||||
|
||||
rebuildOrderedAssets();
|
||||
@@ -184,6 +184,18 @@ public class DataManager{
|
||||
reloadImages();
|
||||
}
|
||||
|
||||
/** Adds/replaces a single image pushed by the server at runtime, independent of map/mod data patches.
|
||||
* Use {@link mindustry.core.NetServer#sendTexture(NetConnection, String, byte[])} to send a texture to connected clients. */
|
||||
public void addTexture(String name, byte[] pngData){
|
||||
if(!Vars.headless) packer.addTexture(name, pngData);
|
||||
}
|
||||
|
||||
/** Removes a texture previously added with {@link #addTexture}.
|
||||
* Use {@link mindustry.core.NetServer#removeTexture(NetConnection, String)} to remove a texture from connected clients. */
|
||||
public void removeTexture(String name){
|
||||
if(!Vars.headless) packer.removeTextureQueued(name);
|
||||
}
|
||||
|
||||
public void reloadAudio(){
|
||||
soundLoader.unload();
|
||||
soundLoader.load(getSounds(), getMusic());
|
||||
|
||||
@@ -8,6 +8,7 @@ import arc.util.serialization.Json.*;
|
||||
import arc.util.serialization.*;
|
||||
import arc.util.serialization.Jval.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.part.*;
|
||||
@@ -92,7 +93,7 @@ public class DataPatcher{
|
||||
public void apply(Seq<PatchAsset> patches, Seq<ContentAsset> content, boolean reloadContentWorld){
|
||||
//if you're un-applying data patches, and it throws an error, just crash. this is not recoverable.
|
||||
if(applied){
|
||||
unapply();
|
||||
unapply(reloadContentWorld);
|
||||
applied = false;
|
||||
}
|
||||
|
||||
@@ -130,7 +131,8 @@ public class DataPatcher{
|
||||
Fi file = new Fi(asset.path);
|
||||
|
||||
//this is very important for resizing various arrays used in the game
|
||||
if((asset.type == ContentType.item || asset.type == ContentType.liquid)){
|
||||
//checking for blocks is also important, as those can be added/removed, and corresponding blocks need to be updated
|
||||
if(asset.type == ContentType.item || asset.type == ContentType.liquid || asset.type == ContentType.block){
|
||||
needsArrayFix = true;
|
||||
}
|
||||
|
||||
@@ -333,9 +335,27 @@ public class DataPatcher{
|
||||
if(!Vars.headless && Vars.ui != null && Vars.ui.editor != null && Vars.ui.editor.isShown()){
|
||||
int wh = Vars.world.width() * Vars.world.height();
|
||||
for(int i = 0; i < wh; i++){
|
||||
var b = Vars.world.tiles.geti(i).build;
|
||||
if(b != null && b.items != null) b.items.checkArrayCapacity(items);
|
||||
if(b != null && b.liquids != null) b.liquids.checkArrayCapacity(items);
|
||||
Tile tile = Vars.world.tiles.geti(i);
|
||||
|
||||
//stale checks for floor/overlay
|
||||
if(tile.floor().removed) tile.setFloor(getReplacementBlock(tile.floor()).asFloor());
|
||||
if(tile.overlay().removed) tile.setOverlay(getReplacementBlock(tile.overlay()).asFloor());
|
||||
|
||||
if(tile.block().removed){
|
||||
Block mapped = getReplacementBlock(tile.block());
|
||||
//tile refers to stale content; get rid of it.
|
||||
if(mapped == Blocks.air){
|
||||
tile.remove();
|
||||
}else{
|
||||
//update internal reference of block to point to the new one with correct ID
|
||||
tile.updateBlockReference(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
var b = tile.build;
|
||||
if(b == null || !tile.isCenter()) continue;
|
||||
if(b.items != null) b.items.checkArrayCapacity(items);
|
||||
if(b.liquids != null) b.liquids.checkArrayCapacity(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +364,17 @@ public class DataPatcher{
|
||||
needsArrayFix = false;
|
||||
}
|
||||
|
||||
private static Block getReplacementBlock(Block existing){
|
||||
Block other = Vars.content.block(existing.name);
|
||||
if(other == null) return Blocks.air;
|
||||
//make sure they are type compatible
|
||||
if(other.getClass() == existing.getClass()){
|
||||
return other;
|
||||
}
|
||||
//could not find an equivalent, clear it
|
||||
return Blocks.air;
|
||||
}
|
||||
|
||||
void visit(Object object){
|
||||
visitStack.add(object);
|
||||
if(object instanceof Content c && usedpatches.add(c)){
|
||||
|
||||
@@ -93,7 +93,7 @@ public class Scripts implements Disposable{
|
||||
|
||||
private boolean run(String script, String file, boolean wrap){
|
||||
try{
|
||||
if(currentMod != null){
|
||||
if(currentMod != null && wrap){
|
||||
//inject script info into file
|
||||
context.evaluateString(scope, "modName = \"" + currentMod.name + "\"\nscriptName = \"" + file + "\"", "initscript.js", 1);
|
||||
}
|
||||
|
||||
@@ -426,6 +426,7 @@ public class ArcNetProvider implements NetProvider{
|
||||
|
||||
@Override
|
||||
public void sendStream(Streamable stream){
|
||||
//listeners are processed in the order they're added and each reads into the buffer greedily before the next gets a turn, so concurrent streams are sent in FIFO order
|
||||
connection.addListener(new InputStreamSender(stream.stream, 1024){
|
||||
int id;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Net{
|
||||
public static final int packetIdAssetStream, packetIdWorldStream;
|
||||
public static final int packetIdAssetStream, packetIdWorldStream, packetIdTextureStream;
|
||||
|
||||
private static Seq<Prov<? extends Packet>> packetProvs = new Seq<>();
|
||||
private static Seq<Class<? extends Packet>> packetClasses = new Seq<>();
|
||||
@@ -50,6 +50,7 @@ public class Net{
|
||||
registerPacket(ConnectPacket::new);
|
||||
registerPacket(AssetRequirementStream::new);
|
||||
packetIdAssetStream = registerPacket(AssetStream::new);
|
||||
packetIdTextureStream = registerPacket(TextureStream::new);
|
||||
|
||||
//register generated packet classes
|
||||
Call.registerPackets();
|
||||
|
||||
@@ -163,6 +163,15 @@ public class NetworkIO{
|
||||
}
|
||||
}
|
||||
|
||||
public static void packTexture(OutputStream os, String name, byte[] pngData){
|
||||
try(DataOutputStream stream = new DataOutputStream(os)){
|
||||
stream.writeUTF(name);
|
||||
stream.write(pngData);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuffer writeServerData(){
|
||||
String name = (headless ? Config.serverName.string() : player.name);
|
||||
String description = headless && !Config.desc.string().equals("off") ? Config.desc.string() : "";
|
||||
|
||||
@@ -71,6 +71,10 @@ public class Packets{
|
||||
|
||||
}
|
||||
|
||||
public static class TextureStream extends Streamable{
|
||||
|
||||
}
|
||||
|
||||
public static class AssetRequirementStream extends Streamable{
|
||||
|
||||
}
|
||||
|
||||
@@ -806,7 +806,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
stats.add(Stat.targetsAir, targetAir);
|
||||
stats.add(Stat.targetsGround, targetGround);
|
||||
|
||||
if(abilities.any()){
|
||||
if(abilities.contains(a -> a.display)){
|
||||
stats.add(Stat.abilities, StatValues.abilities(abilities));
|
||||
}
|
||||
|
||||
@@ -2000,6 +2000,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
public static class UnitEngine implements Cloneable{
|
||||
public float x, y, radius, rotation;
|
||||
public @Nullable Color color;
|
||||
|
||||
public UnitEngine(float x, float y, float radius, float rotation){
|
||||
this.x = x;
|
||||
@@ -2018,7 +2019,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
if(scale <= 0.0001f) return;
|
||||
|
||||
float rot = unit.rotation - 90;
|
||||
Color color = type.engineColor == null ? unit.team.color : type.engineColor;
|
||||
Color color = this.color != null ? this.color : type.engineColor == null ? unit.team.color : type.engineColor;
|
||||
|
||||
Tmp.v1.set(x, y).rotate(rot);
|
||||
float ex = Tmp.v1.x, ey = Tmp.v1.y;
|
||||
|
||||
40
core/src/mindustry/ui/Elems.java
Normal file
40
core/src/mindustry/ui/Elems.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package mindustry.ui;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class Elems{
|
||||
|
||||
/** Fancy check box with a background. Not a real check box. */
|
||||
public static Button check(String text, Boolp checked, Boolc listener){
|
||||
return check(text, checked != null && checked.get(), checked, listener);
|
||||
}
|
||||
|
||||
/** Fancy check box with a background. Not a real check box. */
|
||||
public static Button check(String text, boolean checked, Boolc listener){
|
||||
return check(text, checked, null, listener);
|
||||
}
|
||||
|
||||
/** Fancy check box with a background. Not a real check box. */
|
||||
public static Button check(String text, boolean currentChecked, @Nullable Boolp checked, Boolc listener){
|
||||
Button box = new Button(Styles.grayt);
|
||||
box.background(Styles.grayPanel);
|
||||
box.margin(10f);
|
||||
|
||||
box.add(new Image()).update(i -> i.setDrawable(box.isOver() ? (box.isChecked() ? Tex.checkOnOver : Tex.checkOver) : box.isChecked() ? Tex.checkOn : Tex.checkOff))
|
||||
.size(32f).padRight(8f).padLeft(-4f);
|
||||
|
||||
box.add(text);
|
||||
box.setChecked(currentChecked);
|
||||
|
||||
if(checked != null) box.update(() -> box.setChecked(checked.get()));
|
||||
|
||||
box.clicked(() -> listener.get(box.isChecked()));
|
||||
|
||||
box.left();
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,9 @@ public class Menus{
|
||||
}
|
||||
};
|
||||
|
||||
dialog.idToElement = UiTreeBuilder.build(dialog.cont, builder, dialog.originalListener);
|
||||
var result = UiTreeBuilder.build(dialog.cont, builder, dialog.originalListener);
|
||||
dialog.idToElement = result.idElements;
|
||||
dialog.addImages(result.images);
|
||||
|
||||
var oldDialog = menuDialogs.remove(id);
|
||||
|
||||
@@ -108,12 +110,13 @@ public class Menus{
|
||||
if(table == null) return;
|
||||
|
||||
table.clear();
|
||||
var newElements = UiTreeBuilder.build(table, builder, result -> {
|
||||
var result = UiTreeBuilder.build(table, builder, res -> {
|
||||
//forward results to original listener
|
||||
dialog.originalListener.get(result);
|
||||
dialog.originalListener.get(res);
|
||||
});
|
||||
//save new element IDs
|
||||
dialog.idToElement.putAll(newElements);
|
||||
//save new element IDs and images
|
||||
dialog.idToElement.putAll(result.idElements);
|
||||
dialog.addImages(result.images);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
@@ -388,12 +391,31 @@ public class Menus{
|
||||
static class MenuDialog extends BaseDialog{
|
||||
//retained for easy lookup if an element needs to be replaced
|
||||
ObjectMap<String, Element> idToElement;
|
||||
//every streamed image built into this dialog, keyed by the region name
|
||||
ObjectMap<String, Seq<Image>> images = new ObjectMap<>();
|
||||
Cons<MenuResult> originalListener;
|
||||
boolean wasHidden;
|
||||
|
||||
static{
|
||||
// refresh images that are received after the dialog is already opened
|
||||
Events.on(TextureStreamEvent.class, e -> {
|
||||
for(var dialog : menuDialogs.values()){
|
||||
var imgs = dialog.images.get(e.name);
|
||||
if(imgs != null){
|
||||
var drawable = Core.atlas.drawable(e.name);
|
||||
imgs.each(img -> img.setDrawable(drawable));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MenuDialog(){
|
||||
super("");
|
||||
}
|
||||
|
||||
void addImages(ObjectMap<String, Seq<Image>> images){
|
||||
images.each((region, imgs) -> this.images.get(region, Seq::new).addAll(imgs));
|
||||
}
|
||||
}
|
||||
|
||||
public interface MenuListener{
|
||||
|
||||
@@ -253,6 +253,7 @@ public class UiBuilder{
|
||||
ImageBuilder(){ super(UiKey.image); }
|
||||
|
||||
public ImageBuilder region(String region){ return prop(UiKey.region, region); }
|
||||
public ImageBuilder placeholder(String placeholder){ return prop(UiKey.placeholder, placeholder); }
|
||||
public ImageBuilder scaling(Scaling scaling){ return prop(UiKey.scaling, scaling.name()); }
|
||||
}
|
||||
|
||||
@@ -261,6 +262,7 @@ public class UiBuilder{
|
||||
|
||||
public ButtonBuilder text(String text){ return prop(UiKey.text, text); }
|
||||
public ButtonBuilder icon(String icon){ return prop(UiKey.icon, icon); }
|
||||
public ButtonBuilder placeholder(String placeholder){ return prop(UiKey.placeholder, placeholder); }
|
||||
public ButtonBuilder style(String style){ return prop(UiKey.style, style); }
|
||||
public ButtonBuilder clicked(String result){ return prop(UiKey.clicked, result); }
|
||||
public ButtonBuilder disabled(boolean disabled){ return prop(UiKey.disabled, disabled); }
|
||||
@@ -272,6 +274,7 @@ public class UiBuilder{
|
||||
ImageButtonBuilder(){ super(UiKey.imageButton); }
|
||||
|
||||
public ImageButtonBuilder icon(String icon){ return prop(UiKey.icon, icon); }
|
||||
public ImageButtonBuilder placeholder(String placeholder){ return prop(UiKey.placeholder, placeholder); }
|
||||
public ImageButtonBuilder style(String style){ return prop(UiKey.style, style); }
|
||||
public ImageButtonBuilder clicked(String result){ return prop(UiKey.clicked, result); }
|
||||
public ImageButtonBuilder disabled(boolean disabled){ return prop(UiKey.disabled, disabled); }
|
||||
|
||||
@@ -6,7 +6,7 @@ public enum UiKey{
|
||||
table, pane, stack, label, image, button, imageButton, field, check, slider, space, defaults, buttonTable, row, //NOTE: all node types must be before 'row'
|
||||
|
||||
// node-specific properties
|
||||
text, wrap, region, icon, scaling, background, margin, id, hint, maxLength, checked, min, max, step,
|
||||
text, wrap, region, icon, placeholder, scaling, background, margin, id, hint, maxLength, checked, min, max, step,
|
||||
defaultValue, clicked, enter, style, group, condition, color, disabled,
|
||||
|
||||
// cell properties
|
||||
|
||||
@@ -20,20 +20,21 @@ import arc.scene.utils.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.mod.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.builder.UiBuilder.*;
|
||||
|
||||
/** Builds a UI into an existing Table from a NodeBuilder tree. */
|
||||
public class UiTreeBuilder{
|
||||
|
||||
public static ObjectMap<String, Element> build(Table root, NodeBuilder<?> builder){
|
||||
public static BuildResult build(Table root, NodeBuilder<?> builder){
|
||||
return build(root, builder, null);
|
||||
}
|
||||
|
||||
public static ObjectMap<String, Element> build(Table root, NodeBuilder<?> builder, @Nullable Cons<MenuResult> resultListener){
|
||||
public static BuildResult build(Table root, NodeBuilder<?> builder, @Nullable Cons<MenuResult> resultListener){
|
||||
BuildContext ctx = new BuildContext(resultListener);
|
||||
buildInto(root, builder.entries, ctx);
|
||||
return ctx.idElements;
|
||||
return new BuildResult(ctx.idElements, ctx.images);
|
||||
}
|
||||
|
||||
private static void buildInto(Table table, Seq<Entry> entries, BuildContext ctx){
|
||||
@@ -98,12 +99,15 @@ public class UiTreeBuilder{
|
||||
|
||||
var element = addNode(entry.key, child, ctx);
|
||||
if(element == null) continue; // unknown/unsupported node - skip for forward compat
|
||||
String id = node.str(UiKey.id);
|
||||
String id = child.str(UiKey.id);
|
||||
|
||||
if(id != null){
|
||||
element.name = id;
|
||||
ctx.idElements.put(id, element);
|
||||
}
|
||||
String colorStr = child.str(UiKey.color); // Apply color if provided
|
||||
if(colorStr != null) element.setColor(Strings.parseColor(colorStr, Color.white));
|
||||
|
||||
stack.add(element);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +129,10 @@ public class UiTreeBuilder{
|
||||
yield label;
|
||||
}
|
||||
case image -> {
|
||||
Image image = new Image(findDrawable(node.str(UiKey.region, node.str(UiKey.icon, "error"))));
|
||||
String region = node.str(UiKey.region, node.str(UiKey.icon, "error"));
|
||||
Image image = new Image(findDrawable(region, node.str(UiKey.placeholder)));
|
||||
// track server-streamed images so that they can be reloaded when streaming finishes
|
||||
if(region.startsWith(DataImagePacker.serverRegionPrefix)) ctx.images.get(region, Seq::new).add(image);
|
||||
String scl = node.str(UiKey.scaling);
|
||||
if(scl != null){
|
||||
try{
|
||||
@@ -141,7 +148,10 @@ public class UiTreeBuilder{
|
||||
|
||||
String icon = node.str(UiKey.icon);
|
||||
if(icon != null){
|
||||
btn.add(new Image(findDrawable(icon)).setScaling(Scaling.fit)).size(32f);
|
||||
Image iconImage = new Image(findDrawable(icon, node.str(UiKey.placeholder))).setScaling(Scaling.fit);
|
||||
// track server-streamed images so that they can be reloaded when streaming finishes
|
||||
if(icon.startsWith(DataImagePacker.serverRegionPrefix)) ctx.images.get(icon, Seq::new).add(iconImage);
|
||||
btn.add(iconImage).size(32f);
|
||||
btn.getCells().reverse();
|
||||
}
|
||||
wireButton(btn, node, ctx);
|
||||
@@ -149,7 +159,9 @@ public class UiTreeBuilder{
|
||||
}
|
||||
case imageButton -> {
|
||||
String icon = node.str(UiKey.icon);
|
||||
ImageButton btn = new ImageButton(icon != null ? findDrawable(icon) : null);
|
||||
ImageButton btn = new ImageButton(icon != null ? findDrawable(icon, node.str(UiKey.placeholder)) : null);
|
||||
// track server-streamed images so that they can be reloaded when streaming finishes
|
||||
if(icon != null && icon.startsWith(DataImagePacker.serverRegionPrefix)) ctx.images.get(icon, Seq::new).add(btn.getImage());
|
||||
applyStyle(node, ImageButtonStyle.class, btn::setStyle);
|
||||
wireButton(btn, node, ctx);
|
||||
yield btn;
|
||||
@@ -308,13 +320,20 @@ public class UiTreeBuilder{
|
||||
}
|
||||
|
||||
private static Drawable findDrawable(String name){
|
||||
return findDrawable(name, "error");
|
||||
}
|
||||
|
||||
/** @param placeholder shown instead of the error sprite if {@code name} isn't streamed from the server yet */
|
||||
private static Drawable findDrawable(String name, @Nullable String placeholder){
|
||||
if(Core.atlas.has(name)){
|
||||
return Core.atlas.drawable(name);
|
||||
}
|
||||
//icons are a fallback (TODO: bad idea?)
|
||||
var result = Icon.icons.get(name);
|
||||
if(result == null) return Core.atlas.drawable("error");
|
||||
return result;
|
||||
if(result != null) return result;
|
||||
|
||||
if(placeholder != null) return findDrawable(placeholder, null);
|
||||
return Core.atlas.drawable("nomap"); // no placeholder provided, use the default of the map preview texture
|
||||
}
|
||||
|
||||
/** Evaluates a condition string: "portrait", "landscape", or "width|height >=|>|<|<= number". */
|
||||
@@ -363,6 +382,7 @@ public class UiTreeBuilder{
|
||||
final @Nullable Cons<MenuResult> resultListener;
|
||||
final ObjectMap<String, Element> idElements = new ObjectMap<>();
|
||||
final ObjectMap<String, ButtonGroup<Button>> buttonGroups = new ObjectMap<>();
|
||||
final ObjectMap<String, Seq<Image>> images = new ObjectMap<>();
|
||||
|
||||
BuildContext(@Nullable Cons<MenuResult> resultListener){
|
||||
this.resultListener = resultListener;
|
||||
@@ -372,4 +392,15 @@ public class UiTreeBuilder{
|
||||
return buttonGroups.get(name, ButtonGroup::new);
|
||||
}
|
||||
}
|
||||
|
||||
/** Result of building a NodeBuilder: named elements (those with an explicit id), plus any server-streamed images, keyed by region name */
|
||||
public static class BuildResult{
|
||||
public final ObjectMap<String, Element> idElements;
|
||||
public final ObjectMap<String, Seq<Image>> images;
|
||||
|
||||
BuildResult(ObjectMap<String, Element> idElements, ObjectMap<String, Seq<Image>> images){
|
||||
this.idElements = idElements;
|
||||
this.images = images;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -779,11 +779,9 @@ public class JoinDialog extends BaseDialog{
|
||||
}else if(host.version == 0){
|
||||
return Core.bundle.get("server.outdated");
|
||||
}else if(host.version < Version.build && Version.build != -1){
|
||||
return Core.bundle.get("server.outdated") + "\n" +
|
||||
Core.bundle.format("server.version", host.version, host.versionType);
|
||||
return "\n" + Core.bundle.format("server.version", host.version, host.versionType) + " " + Core.bundle.get("server.outdated");
|
||||
}else if(host.version > Version.build && Version.build != -1){
|
||||
return Core.bundle.get("server.outdated.client") + "\n" +
|
||||
Core.bundle.format("server.version", host.version, host.versionType);
|
||||
return "\n" + Core.bundle.format("server.version", host.version, host.versionType) + " " + Core.bundle.get("server.outdated.client");
|
||||
}else if(host.version == Version.build && Version.type.equals(host.versionType)){
|
||||
//not important
|
||||
return "";
|
||||
|
||||
@@ -206,14 +206,22 @@ public class ModsDialog extends BaseDialog{
|
||||
t.table(title1 -> {
|
||||
title1.left();
|
||||
|
||||
title1.add(new BorderImage(){{
|
||||
title1.add(new BorderImage()).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f).with(b -> {
|
||||
if(mod.iconTexture != null){
|
||||
setDrawable(new TextureRegion(mod.iconTexture));
|
||||
b.setDrawable(new TextureRegion(mod.iconTexture));
|
||||
}else{
|
||||
setDrawable(Tex.nomap);
|
||||
b.setDrawable(Tex.nomap);
|
||||
//if the texture gets loaded with a delay, update it
|
||||
b.update(() -> {
|
||||
if(mod.iconTexture != null){
|
||||
b.setDrawable(new TextureRegion(mod.iconTexture));
|
||||
//stop updating
|
||||
b.update(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
border(Pal.accent);
|
||||
}}).size(h - 8f).padTop(-8f).padLeft(-8f).padRight(8f);
|
||||
b.border(Pal.accent);
|
||||
});
|
||||
|
||||
title1.table(text -> {
|
||||
boolean hideDisabled = !mod.isSupported() || mod.hasUnmetDependencies() || mod.hasContentErrors();
|
||||
|
||||
@@ -58,12 +58,16 @@ public class PausedDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
if(!mobile){
|
||||
float tpad = -60f;
|
||||
|
||||
if(steam){
|
||||
tpad = 0f;
|
||||
|
||||
cont.check("@steam.friendsonly", !Core.settings.getBool("steampublichost2"), val -> {
|
||||
Core.settings.put("steampublichost2", !val);
|
||||
platform.updateLobby();
|
||||
}).colspan(2).left().with(c -> ui.addDescTooltip(c, "@steam.friendsonly.tooltip")).width(440f)
|
||||
.visible(() -> net.server()).center().colspan(2).fillX().padBottom(10f).row();
|
||||
.visible(() -> net.server()).center().colspan(2).fillX().padBottom(10f).padTop(-60f).row();
|
||||
}
|
||||
|
||||
float dw = 220f;
|
||||
@@ -72,10 +76,10 @@ public class PausedDialog extends BaseDialog{
|
||||
boolean showObjective = state.rules.sector != null && state.rules.sector.preset != null && state.rules.sector.preset.description != null;
|
||||
|
||||
if(showObjective){
|
||||
cont.button("@objective", Icon.info, () -> ui.fullText.show("@objective", state.rules.sector != null && state.rules.sector.preset != null ? state.rules.sector.preset.description : "oh dear")).padTop(-60f);
|
||||
cont.button("@objective", Icon.info, () -> ui.fullText.show("@objective", state.rules.sector != null && state.rules.sector.preset != null ? state.rules.sector.preset.description : "oh dear")).padTop(tpad);
|
||||
}
|
||||
|
||||
cont.button("@abandon", Icon.cancel, () -> ui.planet.abandonSectorConfirm(state.rules.sector, this::hide)).padTop(-60f)
|
||||
cont.button("@abandon", Icon.cancel, () -> ui.planet.abandonSectorConfirm(state.rules.sector, this::hide)).padTop(tpad)
|
||||
.colspan(showObjective ? 1 : 2).width(showObjective ? dw : dw * 2 + 10f)
|
||||
.disabled(b -> net.client() || state.gameOver).visible(() -> state.rules.sector != null).row();
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
|
||||
graphics.sliderPref("screenshake", 4, 0, 8, i -> (i / 4f) + "x");
|
||||
|
||||
graphics.sliderPref("bloomintensity", 6, 0, 16, i -> (int)(i/4f * 100f) + "%");
|
||||
graphics.sliderPref("bloomintensity", 6, 0, 16, i -> (int)(i / 4f * 100f) + "%");
|
||||
graphics.sliderPref("bloomblur", 2, 1, 16, i -> i + "x");
|
||||
|
||||
graphics.sliderPref("fpscap", 240, 10, 245, 5, s -> {
|
||||
@@ -810,25 +810,13 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
|
||||
@Override
|
||||
public void add(SettingsTable table){
|
||||
Button box = new Button(Styles.grayt);
|
||||
box.background(Styles.grayPanel);
|
||||
box.margin(10f);
|
||||
|
||||
box.add(new Image()).update(i -> i.setDrawable(box.isOver() ? (box.isChecked() ? Tex.checkOnOver : Tex.checkOver) : box.isChecked() ? Tex.checkOn : Tex.checkOff))
|
||||
.size(32f).padRight(8f).padLeft(-4f);
|
||||
|
||||
box.add(title);
|
||||
|
||||
box.update(() -> box.setChecked(settings.getBool(name)));
|
||||
|
||||
box.clicked(() -> {
|
||||
settings.put(name, box.isChecked());
|
||||
Table box = Elems.check(title, () -> settings.getBool(name), value -> {
|
||||
settings.put(name, value);
|
||||
if(changed != null){
|
||||
changed.get(box.isChecked());
|
||||
changed.get(value);
|
||||
}
|
||||
});
|
||||
|
||||
box.left();
|
||||
addDesc(table.add(box).minWidth(Math.min(500f, Core.graphics.getWidth() / 1.2f / Scl.scl(1f))).fillX().height(45f).left().padTop(7f).get());
|
||||
table.row();
|
||||
}
|
||||
|
||||
@@ -196,6 +196,11 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/** Internal method for data patches - do not use!! */
|
||||
public void updateBlockReference(Block block){
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Block> T cblock(){
|
||||
return (T)block;
|
||||
@@ -785,6 +790,44 @@ public class Tile implements Position, QuadTreeObject, Displayable{
|
||||
}
|
||||
}
|
||||
|
||||
/** Fills an area. x2/y2 are inclusive.*/
|
||||
@Remote(called = Loc.server)
|
||||
public static void fillTileBlocks(int x, int y, int x2, int y2, Block block, Team team){
|
||||
for(int tx = x; tx <= x2; tx++){
|
||||
for(int ty = y; ty <= y2; ty++){
|
||||
Tile tile = world.tile(tx, ty);
|
||||
if(tile == null) return; //out of bounds; return instead of breaking since it's a rectangle
|
||||
tile.setBlock(block, team, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fills an area. x2/y2 are inclusive.*/
|
||||
@Remote(called = Loc.server)
|
||||
public static void fillTileFloors(int x, int y, int x2, int y2, Block block){
|
||||
if(!(block instanceof Floor floor)) return;
|
||||
for(int tx = x; tx <= x2; tx++){
|
||||
for(int ty = y; ty <= y2; ty++){
|
||||
Tile tile = world.tile(tx, ty);
|
||||
if(tile == null) return; //out of bounds; return instead of breaking since it's a rectangle
|
||||
tile.setFloor(floor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fills an area. x2/y2 are inclusive.*/
|
||||
@Remote(called = Loc.server)
|
||||
public static void fillTileOverlays(int x, int y, int x2, int y2, Block block){
|
||||
if(!(block instanceof OverlayFloor floor)) return;
|
||||
for(int tx = x; tx <= x2; tx++){
|
||||
for(int ty = y; ty <= y2; ty++){
|
||||
Tile tile = world.tile(tx, ty);
|
||||
if(tile == null) return; //out of bounds; return instead of breaking since it's a rectangle
|
||||
tile.setOverlay(floor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void setFloor(Tile tile, Block floor, Block overlay){
|
||||
tile.setFloor(floor.asFloor());
|
||||
|
||||
@@ -242,7 +242,7 @@ public class ForceProjector extends Block{
|
||||
if(buildup >= shieldHealth + phaseShieldBoost * phaseHeat && !broken){
|
||||
broken = true;
|
||||
buildup = shieldHealth;
|
||||
shieldBreakEffect.at(x, y, realRadius(), team.color);
|
||||
shieldBreakEffect.at(x, y, realRadius(), team.color, block);
|
||||
breakSound.at(x, y);
|
||||
if(team != state.rules.defaultTeam){
|
||||
Events.fire(Trigger.forceProjectorBreak);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ShockwaveTower extends Block{
|
||||
if(potentialEfficiency > 0 && (reloadCounter += edelta()) >= reload && timer(timerCheck, checkInterval)){
|
||||
targets.clear();
|
||||
Groups.bullet.intersect(x - range, y - range, range * 2, range * 2, b -> {
|
||||
if(b.team != team && b.type.hittable){
|
||||
if(b.team != team && b.type.hittable && b.within(x, y, range + 1f)){
|
||||
targets.add(b);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ public class LogicDisplay extends Block{
|
||||
clipSize = Math.max(clipSize, scaleFactor * Draw.scl * displaySize);
|
||||
}
|
||||
|
||||
public class LogicDisplayBuild extends Building{
|
||||
public class LogicDisplayBuild extends Building implements LDrawable{
|
||||
//The root display (bottom left corner of display for tileable displays)
|
||||
public LogicDisplayBuild rootDisplay = this;
|
||||
public @Nullable FrameBuffer buffer;
|
||||
@@ -121,7 +121,13 @@ public class LogicDisplay extends Block{
|
||||
};
|
||||
}
|
||||
|
||||
public void flushCommands(LongSeq graphicsBuffer){
|
||||
@Override
|
||||
public boolean drawable(LExecutor exec){
|
||||
return isValid() && (exec.privileged || (team == exec.team && !privileged));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(LongSeq graphicsBuffer){
|
||||
int added = Math.min(graphicsBuffer.size, LExecutor.maxDisplayBuffer - commands.size);
|
||||
|
||||
for(int i = 0; i < added; i++){
|
||||
|
||||
@@ -258,11 +258,11 @@ public class TileableLogicDisplay extends LogicDisplay{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushCommands(LongSeq graphicsBuffer){
|
||||
public void draw(LongSeq graphicsBuffer){
|
||||
if(isRoot()){
|
||||
super.flushCommands(graphicsBuffer);
|
||||
super.draw(graphicsBuffer);
|
||||
}else{
|
||||
rootDisplay.flushCommands(graphicsBuffer);
|
||||
rootDisplay.draw(graphicsBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class LiquidModule extends BlockModule{
|
||||
flow = cacheFlow;
|
||||
}
|
||||
|
||||
boolean updateFlow = flowTimer.get(15);
|
||||
boolean updateFlow = flowTimer.get(flowVisualRefreshInterval);
|
||||
|
||||
for(int i = 0; i < liquids.length; i++){
|
||||
flow[i].add(cacheSums[i]);
|
||||
@@ -63,7 +63,7 @@ public class LiquidModule extends BlockModule{
|
||||
cacheSums[i] = 0;
|
||||
|
||||
if(updateFlow){
|
||||
displayFlow[i] = flow[i].hasEnoughData() ? flow[i].mean() / flowVisualRefreshInterval : -1;
|
||||
displayFlow[i] = flow[i].hasEnoughData() ? flow[i].mean() / flowPollInterval : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript{
|
||||
}
|
||||
|
||||
dependencies{
|
||||
classpath "com.mobidevelop.robovm:robovm-gradle-plugin:2.3.24"
|
||||
classpath "com.mobidevelop.robovm:robovm-gradle-plugin:2.3.26"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"doc": "Tech tree placement for this content. Also accepts a plain string, treated as 'parent'.",
|
||||
"parent": {"type": "mindustry.ctype.UnlockableContent", "doc": "Name of the parent tech tree node; required unless 'root' is true."},
|
||||
"requirements": {"type": "mindustry.type.ItemStack[]", "doc": "Overrides the default research cost, which is this content's build cost."},
|
||||
"objectives": {"type": "mindustry.type.Objective[]", "doc": "Extra objectives to complete; items/liquids get a Produce objective automatically."},
|
||||
"objectives": {"type": "mindustry.game.Objectives.Objective[]", "doc": "Extra objectives to complete; items/liquids get a Produce objective automatically."},
|
||||
"planet": {"type": "mindustry.type.Planet", "doc": "Name of the planet this node belongs to; inherited from the parent otherwise."},
|
||||
"root": {"type": "boolean", "doc": "If true, this is a root node and 'parent' is not required.", "default": "false"},
|
||||
"name": {"type": "java.lang.String", "doc": "Display name for a root node; ignored for non-root nodes."},
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"abstract": true,
|
||||
"doc": "Completion objective."
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"abstract": true,
|
||||
"doc": "Planet mesh."
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"superclass": "java.lang.Object",
|
||||
"superclass": "mindustry.graphics.g3d.GenericMesh",
|
||||
"mesh": {
|
||||
"type": "mindustry.graphics.g3d.GenericMesh",
|
||||
"doc": "Wrapped mesh to render with the extra transform applied. Required - no default."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"superclass": "java.lang.Object",
|
||||
"superclass": "mindustry.graphics.g3d.GenericMesh",
|
||||
"meshes": {
|
||||
"type": "mindustry.graphics.g3d.GenericMesh[]",
|
||||
"doc": "Meshes to render together, in order. Required - no default."
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"doc": "Planet mesh.",
|
||||
"abstract": true,
|
||||
"superclass": "mindustry.graphics.g3d.GenericMesh"
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@@ -65,7 +66,17 @@ public class SchemaGenerator{
|
||||
return;
|
||||
}
|
||||
|
||||
if(type.getSuperclass() != null) val.put("superclass", type.getSuperclass().getCanonicalName());
|
||||
if(Modifier.isAbstract(type.getModifiers())){
|
||||
val.put("abstract", true);
|
||||
}
|
||||
|
||||
if(type.getSuperclass() != null){
|
||||
val.put("superclass", type.getSuperclass().getCanonicalName());
|
||||
}
|
||||
|
||||
if(Objective.class.isAssignableFrom(type)){
|
||||
val.put("superclass", Objective.class.getCanonicalName());
|
||||
}
|
||||
|
||||
if(typeDec.getJavadoc().isPresent()){
|
||||
val.put("doc", typeDec.getJavadoc().get().toText());
|
||||
@@ -74,7 +85,7 @@ public class SchemaGenerator{
|
||||
for(var field : type.getFields()){
|
||||
if(Modifier.isStatic(field.getModifiers()) || field.getDeclaringClass() != type) continue;
|
||||
Jval inner = Jval.newObject();
|
||||
inner.put("type", field.getGenericType().getTypeName());
|
||||
inner.put("type", field.getGenericType().getTypeName().replace('$', '.'));
|
||||
|
||||
if(field.isAnnotationPresent(Nullable.class)) inner.put("nullable", true);
|
||||
|
||||
@@ -150,6 +161,14 @@ public class SchemaGenerator{
|
||||
}
|
||||
""".replace("%TYPES%", allClasses.select(UnitType.class::isAssignableFrom).toString(", ", t -> "\"" + t.getSimpleName() + "\"")));
|
||||
|
||||
writeSchema(BuildVisibility.class.getCanonicalName(), """
|
||||
{
|
||||
"doc": "Types of block build visibility.",
|
||||
"superclass": "Enum",
|
||||
"values": [%VALUES%]
|
||||
}
|
||||
""".replace("%VALUES%", Seq.with(BuildVisibility.class.getFields()).toString(", ", t -> "\"" + t.getName() + "\"")));
|
||||
|
||||
writeSchema(Interp.class.getCanonicalName(), """
|
||||
{
|
||||
"doc": "All types of interpolation.",
|
||||
@@ -158,6 +177,14 @@ public class SchemaGenerator{
|
||||
}
|
||||
""".replace("%VALUES%", Seq.with(Interp.class.getFields()).toString(", ", t -> "\"" + t.getName() + "\"")));
|
||||
|
||||
writeSchema(AIController.class.getCanonicalName(), """
|
||||
{
|
||||
"doc": "Type of unit AI",
|
||||
"superclass": "Enum",
|
||||
"values": [%VALUES%]
|
||||
}
|
||||
""".replace("%VALUES%", decls.keys().toSeq().select(AIController.class::isAssignableFrom).toString(", ", t -> "\"" + t.getSimpleName() + "\"")));
|
||||
|
||||
writeSchema("allTeams", """
|
||||
{
|
||||
"doc": "All team names.",
|
||||
@@ -186,6 +213,7 @@ public class SchemaGenerator{
|
||||
}
|
||||
""".replace("%VALUES%", Seq.with(Sounds.class.getFields()).select(f -> f.getType() == Sound.class).toString(", ", t -> "\"" + t.getName() + "\"")));
|
||||
|
||||
|
||||
new Fi("../../tools/extra-schemas").copyFilesTo(outputDir);
|
||||
}
|
||||
|
||||
@@ -194,6 +222,7 @@ public class SchemaGenerator{
|
||||
injectCustomField(UnitType.class, "template", field("TemplateUnitType", "UnitType template class.", false));
|
||||
injectCustomField(UnitType.class, "defaultController", field(AIController.class.getCanonicalName(), "Unconditional controller; always assigned, even if on the player team. This overwrites RTS AI.", false));
|
||||
injectCustomField(UnitType.class, "aiController", field(AIController.class.getCanonicalName(), "Controller used when the unit is not on the player team.", false));
|
||||
injectCustomField(UnitType.class, "controller", field(AIController.class.getCanonicalName(), "Controller used when the unit is not on the player team. Equivalent to aiController.", false));
|
||||
|
||||
injectCustomField(Planet.class, "sectorSize", field("int", "Planet sector subdivisions.", false));
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ public class ScriptMainGenerator{
|
||||
|
||||
Seq<Class<?>> classes = Seq.withArrays(
|
||||
getClasses("mindustry"),
|
||||
getClasses("arc"),
|
||||
getClasses("arc.func"),
|
||||
getClasses("arc.struct"),
|
||||
getClasses("arc.scene"),
|
||||
|
||||
Reference in New Issue
Block a user