diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 4ef2ab73ee..dd111ee17f 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -33,6 +33,7 @@ client.stopsendbuildplans = [accent][[{0}][] to stop dispatching build plans client.sendbuildplans = [accent][[{0}][] to start dispatching build plans client.freezequeueing = [scarlet][SHIFT + {0}][] to toggle freeze queueing client.configured = configured +client.configurednodelink = link added client.built = built client.broke = broke client.destroyed = destroyed diff --git a/core/assets/bundles/bundle_ja.properties b/core/assets/bundles/bundle_ja.properties index f23464cb9c..becba3cb3c 100644 --- a/core/assets/bundles/bundle_ja.properties +++ b/core/assets/bundles/bundle_ja.properties @@ -33,6 +33,7 @@ client.stopsendbuildplans = 建設計画の送信を停止 [accent][[{0}][] client.sendbuildplans = 建設計画の送信を開始 [accent][[{0}][] client.freezequeueing = [scarlet][SHIFT + {0}][] で凍結キューイングを切り替える client.configured = 設定済み +client.configurednodelink = リンクを追加 client.built = 建設済み client.broke = 破壊済み client.destroyed = 破壊されました diff --git a/core/assets/bundles/bundle_zh_CN.properties b/core/assets/bundles/bundle_zh_CN.properties index 8b2469a7ac..179c3c1424 100644 --- a/core/assets/bundles/bundle_zh_CN.properties +++ b/core/assets/bundles/bundle_zh_CN.properties @@ -33,6 +33,7 @@ client.stopsendbuildplans = [accent][[{0}][]停止派遣建筑规划 client.sendbuildplans = [accent][[{0}][]开始派遣建筑规划 client.freezequeueing = [scarlet][SHIFT + {0}][]切换冻结队列 client.configured = 配置/更改了 +client.configurednodelink = 链接增添了 client.built = 建造了 client.broke = 拆除了 client.destroyed = 被摧毁 @@ -102,6 +103,8 @@ client.claj.portnotfound = CLaJ链接缺失端口 client.claj.colonnotfound = CLaJ链接缺失冒号 client.claj.wrongkeylength = CLaJ链接长度错误 client.claj.missingprefix = CLaJ链接缺失前缀 +client.claj.ping.error = 连接服务器时出错. +client.claj.ping.pending = 尚未连接. # Editor client.editor.autosaved = 自动保存完成! diff --git a/core/src/mindustry/ai/BaseRegistry.java b/core/src/mindustry/ai/BaseRegistry.java index 79f71974bb..f6cf8ff35a 100644 --- a/core/src/mindustry/ai/BaseRegistry.java +++ b/core/src/mindustry/ai/BaseRegistry.java @@ -4,6 +4,7 @@ import arc.math.*; import arc.struct.*; import arc.util.*; +import arc.util.pooling.*; import mindustry.ctype.*; import mindustry.game.*; import mindustry.game.Schematic.*; @@ -81,7 +82,13 @@ public void load(){ drills ++; } } - schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly); + schem.tiles.removeAll(s -> { + if(s.block.buildVisibility == BuildVisibility.sandboxOnly){ + Pools.free(s); + return true; + } + return false; + }); part.tier = schem.tiles.sumf(s -> Mathf.pow(s.block.buildCost / s.block.buildCostMultiplier, 1.4f)); diff --git a/core/src/mindustry/client/antigrief/Interactor.kt b/core/src/mindustry/client/antigrief/Interactor.kt index 1f7897bba0..074ea0e5e6 100644 --- a/core/src/mindustry/client/antigrief/Interactor.kt +++ b/core/src/mindustry/client/antigrief/Interactor.kt @@ -1,8 +1,11 @@ package mindustry.client.antigrief +import arc.* import mindustry.ai.types.* +import mindustry.entities.units.* import mindustry.gen.* import mindustry.gen.Unit +import mindustry.ui.* interface Interactor { val name: String @@ -13,20 +16,34 @@ interface Interactor { } open class UnitInteractor(unit: Unit?) : Interactor { - override val name = when { + override val name = when { // FINISHME: bundle unit?.isPlayer == true -> "${unit.type.localizedName} controlled by ${unit.player.coloredName()}" -// (unit?.controller() as? FormationAI)?.leader?.isPlayer == true -> "${unit.type.localizedName} controlled by ${(unit.controller() as FormationAI).leader.playerNonNull().coloredName()}" FINISHME: commanding exists + unit?.controller() is CommandAI -> "${unit.type.localizedName} commanded by ${unit.lastCommanded ?: "unknown"} " + + "to ${(unit.controller() as CommandAI).currentCommand()?.name ?: "unknown"}" unit?.controller() is LogicAI -> { val lcontrol = (unit.controller() as? LogicAI)?.controller "${unit.type.localizedName} logic-controlled by ${lcontrol?.block()?.localizedName} (${lcontrol?.tileX()}, ${lcontrol?.tileY()}) accessed by ${lcontrol?.lastAccessed}" } + unit?.controller() is AIController -> { + val controllerName = when (unit.controller()) { + is MinerAI -> Core.bundle.get("command.mine") + is FlyingAI -> "fly" + is GroundAI -> "walk" + is BuilderAI -> Core.bundle.get("command.${if ((unit.controller() as BuilderAI).onlyAssist) "rebuild" else "assist"}") + is RepairAI -> Core.bundle.get("command.repair") + else -> "control" + } + "AI-${controllerName} ${unit.type.localizedName}" + } else -> unit?.type?.localizedName ?: "null unit" } override val shortName: String = when { unit?.isPlayer == true -> unit.player.coloredName() -// (unit?.controller() as? FormationAI)?.leader?.isPlayer == true -> (unit.controller() as FormationAI).leader.playerNonNull().name FINISHME: Commanding exists - unit?.controller() is LogicAI -> "logic-controlled ${unit.type.localizedName}" + unit?.controller() is CommandAI -> if (Core.settings.getBool("useiconslogs")) Iconc.codes.get("units").toChar().toString() + Fonts.getUnicodeStr(unit.type.name) + else "player-commanded ${unit.type.localizedName}" + unit?.controller() is LogicAI -> if (Core.settings.getBool("useiconslogs")) Iconc.codes.get("logic").toChar().toString() + Fonts.getUnicodeStr(unit.type.name) + else "logic-controlled ${unit.type.localizedName}" else -> unit?.type?.localizedName ?: "null unit" } @@ -34,9 +51,9 @@ open class UnitInteractor(unit: Unit?) : Interactor { } object NullUnitInteractor : UnitInteractor(Nulls.unit) { - override val name = "null unit" // FINISHME: Dont use this when nodes are automatically configured + override val name = "null unit" - override val shortName = "null unit" // FINISHME: Dont use this when nodes are automatically configured + override val shortName = "null unit" } class NoInteractor : Interactor { diff --git a/core/src/mindustry/client/antigrief/TileLog.kt b/core/src/mindustry/client/antigrief/TileLog.kt index 92b713f705..91d4848673 100644 --- a/core/src/mindustry/client/antigrief/TileLog.kt +++ b/core/src/mindustry/client/antigrief/TileLog.kt @@ -190,7 +190,7 @@ abstract class AbstractTileLog(tile: Tile, cause: Interactor, val block: Block) protected val eventTarget: String = if (Core.settings.getBool("useiconslogs")) Fonts.getUnicodeStr(block.name) else block.localizedName } -class ConfigureTileLog(tile: Tile, cause: Interactor, block: Block, val rotation: Int, var configuration: Any?) : AbstractTileLog(tile, cause, block) { +open class ConfigureTileLog(tile: Tile, cause: Interactor, block: Block, val rotation: Int, var configuration: Any?) : AbstractTileLog(tile, cause, block) { override fun apply(previous: TileState) { previous.rotation = rotation previous.configuration = configuration @@ -204,11 +204,17 @@ class ConfigureTileLog(tile: Tile, cause: Interactor, block: Block, val rotation } } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[accent]${Core.bundle.get("client.configured")}[]" else Core.bundle.get("client.configured") + private val eventName: String = Core.bundle.get("client.configured").let { if(Core.settings.getBool("colorizelogs")) "[accent]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } +class NodeLinkAddedTileLog(tile: Tile, cause: Interactor, block: Block, rotation: Int, configuration: Any?) : ConfigureTileLog(tile, cause, block, rotation, configuration) { + override fun toString() = "$eventTarget ${Core.bundle.get("client.configurednodelink")}" + private val eventName: String = Core.bundle.get("client.configurednodelink").let { if(Core.settings.getBool("colorizelogs")) "[accent]$it[]" else it } + override fun toShortString() = "$eventTarget $eventName" +} + open class TilePlacedLog(tile: Tile, cause: Interactor, block: Block, var rotation: Int = tile.build?.rotation ?: 0, var configuration: Any?, val isRootTile: Boolean) : AbstractTileLog(tile, cause, block) { override fun apply(previous: TileState) { previous.block = block @@ -226,7 +232,7 @@ open class TilePlacedLog(tile: Tile, cause: Interactor, block: Block, var rotati return "${cause.name.stripColors()} ${Core.bundle.get("client.built")} ${block.localizedName}" } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[green]${Core.bundle.get("client.built")}[]" else Core.bundle.get("client.built") + private val eventName: String = Core.bundle.get("client.built").let { if(Core.settings.getBool("colorizelogs")) "[green]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } @@ -236,7 +242,7 @@ class BlockPayloadDropLog(tile: Tile, cause: Interactor, block: Block, rotation: return "${cause.name.stripColors()} ${Core.bundle.get("client.putdown")} ${block.localizedName}" } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[accent]${Core.bundle.get("client.putdown")}[]" else Core.bundle.get("client.putdown") + private val eventName: String = Core.bundle.get("client.putdown").let { if(Core.settings.getBool("colorizelogs")) "[accent]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } @@ -253,7 +259,7 @@ open class TileBreakLog(tile: Tile, cause: Interactor, block: Block) : AbstractT return "${cause.name.stripColors()} ${Core.bundle.get("client.broke")} ${block.localizedName}" } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[red]${Core.bundle.get("client.broke")}[]" else Core.bundle.get("client.broke") + private val eventName: String = Core.bundle.get("client.broke").let { if(Core.settings.getBool("colorizelogs")) "[red]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } @@ -263,7 +269,7 @@ class BlockPayloadPickupLog(tile: Tile, cause: Interactor, block: Block) : TileB return "${cause.name.stripColors()} ${Core.bundle.get("client.pickedup")} ${block.localizedName}" } - private val eventName: String = if (Core.settings.getBool("colorizelogs")) "[accent]${Core.bundle.get("client.pickedup")}[]" else Core.bundle.get("client.pickedup") + private val eventName: String = Core.bundle.get("client.pickedup").let { if(Core.settings.getBool("colorizelogs")) "[accent]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } @@ -272,7 +278,7 @@ class TileDestroyedLog(tile: Tile, block: Block) : TileBreakLog(tile, NoInteract return "${block.localizedName} ${Core.bundle.get("client.destroyed")}" } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[red]${Core.bundle.get("client.destroyed")}[]" else Core.bundle.get("client.destroyed") + private val eventName: String = Core.bundle.get("client.destroyed").let { if(Core.settings.getBool("colorizelogs")) "[red]$it[]" else it } override fun toShortString() = "$eventTarget $eventName" } @@ -287,8 +293,8 @@ class UnitDestroyedLog(val tile: Tile, cause: Interactor, val unit: Unit, val is } private val eventController: String = "${cause.shortName.stripColors().take(16)}${if (cause.shortName.stripColors().length > 16) "..." else ""}" - private val eventNamePlayer: String = if(Core.settings.getBool("colorizelogs")) "[red]${Core.bundle.get("client.playerunitdeath")}[]" else Core.bundle.get("client.playerunitdeath") - private val eventNameLogic: String = if(Core.settings.getBool("colorizelogs")) "[red]${Core.bundle.get("client.unitdeath")}[]" else Core.bundle.get("client.unitdeath") + private val eventNamePlayer: String = Core.bundle.get("client.playerunitdeath").let { if(Core.settings.getBool("colorizelogs")) "[red]$it[]" else it } + private val eventNameLogic: String = Core.bundle.get("client.unitdeath").let { if(Core.settings.getBool("colorizelogs")) "[red]$it[]" else it } private val eventUnit: String = if(Core.settings.getBool("useiconslogs") && unit.type.name.isNotEmpty()) Fonts.getUnicodeStr(unit.type.name) else unit.type?.localizedName ?: "null unit" override fun toShortString(): String { @@ -305,7 +311,7 @@ class RotateTileLog(tile: Tile, cause: Interactor, block: Block, val rotation: I return "${cause.name.stripColors()} ${Core.bundle.get("client.rotated")} ${block.localizedName} ${Core.bundle.get(if (direction) "client.counterclockwise" else "client.clockwise")}" } - private val eventName: String = if(Core.settings.getBool("colorizelogs")) "[accent]${Core.bundle.get("client.rotated")}[]" else Core.bundle.get("client.rotated") + private val eventName: String = Core.bundle.get("client.rotated").let { if(Core.settings.getBool("colorizelogs")) "[accent]$it[]" else it } override fun toShortString() = "$eventPlayer $eventName $eventTarget" } diff --git a/core/src/mindustry/client/antigrief/TileRecords.kt b/core/src/mindustry/client/antigrief/TileRecords.kt index e09cfae6c7..7b3679e45b 100644 --- a/core/src/mindustry/client/antigrief/TileRecords.kt +++ b/core/src/mindustry/client/antigrief/TileRecords.kt @@ -12,6 +12,7 @@ import mindustry.game.* import mindustry.gen.* import mindustry.world.* import mindustry.world.blocks.* +import mindustry.world.blocks.power.* import java.time.* import kotlin.math.* @@ -61,8 +62,9 @@ object TileRecords { Events.on(EventType.ConfigEventBefore::class.java) { if (it.player != null) Seer.blockConfig(it.player, it.tile.tile, it.value) + val constructor = if ((it.player == null) && it.tile.tile.block() is PowerNode) ::NodeLinkAddedTileLog else ::ConfigureTileLog it.tile.tile.getLinkedTiles { tile -> - addLog(tile, ConfigureTileLog(tile, it.player.toInteractor(), tile.block(), it.tile.rotation, it.value)) + addLog(tile, constructor(tile, it.player.toInteractor(), tile.block(), it.tile.rotation, it.value)) } } @@ -90,14 +92,14 @@ object TileRecords { Events.on(EventType.UnitDeadEvent::class.java) { if(it.unit == null || it.unit.team() != Vars.player.team() || it.unit.tileOn() == null) return@on - val controller = it.unit.controller() - if(controller !is LogicAI && controller !is Player) return@on + + if(it.unit.controller() is MissileAI) return@on val threshold = it.unit.type.hitSize * it.unit.type.hitSize + 0.01f for (point in TileLog.linkedArea(it.unit.tileOn(), Mathf.ceil(it.unit.type.hitSize / Vars.tilesize))) { if (point in Vars.world && it.unit.within(Vars.world[point], threshold)) { val tile = Vars.world[point] - addLog(tile, UnitDestroyedLog(tile, it.unit.toInteractor(), it.unit, controller is Player)) + addLog(tile, UnitDestroyedLog(tile, it.unit.toInteractor(), it.unit, it.unit.controller() is Player)) } } } diff --git a/core/src/mindustry/client/communication/CommandTransmission.kt b/core/src/mindustry/client/communication/CommandTransmission.kt index 6bb36ee456..77a79cbe81 100644 --- a/core/src/mindustry/client/communication/CommandTransmission.kt +++ b/core/src/mindustry/client/communication/CommandTransmission.kt @@ -44,20 +44,22 @@ class CommandTransmission : Transmission { STOP_PATH(false, { val cert = Main.keyStorage.findTrusted(BigInteger(it.certSN))!! if (Navigation.currentlyFollowing != null) { - lastStopTime = Time.millis() val oldPath = Navigation.currentlyFollowing if (Main.keyStorage.builtInCerts.contains(cert)) { val dialog = BaseDialog("@client.stoppath.stopped") dialog.cont.add(Core.bundle.format("client.stoppath.bydev", cert.readableName)) dialog.buttons.button("@close", Icon.menu) { dialog.hide() } .size(210f, 64f) - } else if (Time.timeSinceMillis(lastStopTime) > Time.toMinutes * (1 + numStopIgnores)) { + dialog.show() + } else if (Time.timeSinceMillis(lastStopTime) > Time.toMinutes.toLong() * (1 + numStopIgnores)) { + numStopIgnores -= Math.min(numStopIgnores, (Time.timeSinceMillis(lastStopTime) / Time.toMinutes.toLong()).toInt() - numStopIgnores - 1) Vars.ui.showCustomConfirm("@client.stoppath.stopped", Core.bundle.format("client.stoppath.by", Main.keyStorage.aliasOrName(cert)), Core.bundle.get("client.stoppath.continue"), Core.bundle.get("client.stoppath.stop"), { Navigation.follow(oldPath); numStopIgnores ++; }, {} ) } + lastStopTime = Time.millis() Navigation.stopFollowing() } }), diff --git a/core/src/mindustry/client/utils/AutoShoot.kt b/core/src/mindustry/client/utils/AutoShoot.kt index ecb0af3078..270ea6709d 100644 --- a/core/src/mindustry/client/utils/AutoShoot.kt +++ b/core/src/mindustry/client/utils/AutoShoot.kt @@ -54,7 +54,7 @@ fun autoShoot() { if (target == null || Client.timer.get(2, 6f)) { // Acquire target FINISHME: Heal allied units? if (type.canAttack) { - val ignoreDisarmed = Server.io() + val ignoreDisarmed = Server.io() && !CustomMode.defense(); target = Units.closestEnemy(unit.team, unit.x, unit.y, unit.range()) { u -> !(ignoreDisarmed && u.disarmed) && u.checkTarget(type.targetAir, unit.type.targetGround) } } if (type.canHeal && target == null) { diff --git a/core/src/mindustry/entities/comp/PlayerComp.java b/core/src/mindustry/entities/comp/PlayerComp.java index f109d4d1b2..bc961359c9 100644 --- a/core/src/mindustry/entities/comp/PlayerComp.java +++ b/core/src/mindustry/entities/comp/PlayerComp.java @@ -239,7 +239,7 @@ public void unit(Unit unit){ if(this.unit != Nulls.unit){ //un-control the old unit this.unit.resetController(); - if(!headless && isLocal()) { // Plan persistence is client side only FINISHME: Move this to some other class + if(!headless && isLocal() && (justSwitchFrom == null || this.unit == justSwitchFrom)) { // Plan persistence is client side only FINISHME: Move this to some other class if(Navigation.currentlyFollowing instanceof BuildPath bp) bp.clearQueues(); persistPlans.clear(); // Don't want to stack multiple sets of plans... persistPlans.ensureCapacity(this.unit.plans.size); @@ -262,6 +262,8 @@ public void unit(Unit unit){ if(!headless && isLocal() && !persistPlans.isEmpty()){ // Persist plans through unit swaps if(!ClientVars.syncing && Time.timeSinceMillis(ClientVars.lastJoinTime) < 3000) persistPlans.clear(); // I can't find a more reliable way to not persist through map changes + control.input.playerPlanTree.clear(); + player.unit().plans.each(control.input.playerPlanTree::insert); persistPlans.each(unit::addBuild); persistPlans.clear(); persistPlans.shrink(); // Don't want an array hanging around in memory, replace it with a 0 element array diff --git a/core/src/mindustry/game/Schematic.java b/core/src/mindustry/game/Schematic.java index b90f59fbbb..5a3b0380a1 100644 --- a/core/src/mindustry/game/Schematic.java +++ b/core/src/mindustry/game/Schematic.java @@ -3,6 +3,7 @@ import arc.files.*; import arc.struct.*; import arc.util.*; +import arc.util.pooling.*; import mindustry.content.*; import mindustry.mod.Mods.*; import mindustry.type.*; @@ -154,7 +155,7 @@ public Stile set(Stile other){ } public Stile copy(){ - return new Stile(block, x, y, config, rotation); + return Pools.obtain(Stile.class, Stile::new).set(this); } } } diff --git a/core/src/mindustry/game/Schematics.java b/core/src/mindustry/game/Schematics.java index 78573032eb..8073e57239 100644 --- a/core/src/mindustry/game/Schematics.java +++ b/core/src/mindustry/game/Schematics.java @@ -112,6 +112,7 @@ public void overwrite(Schematic target, Schematic newSchematic){ previews.remove(target); } + Pools.freeAll(target.tiles, true); target.tiles.clear(); target.tiles.addAll(newSchematic.tiles); target.width = newSchematic.width; diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index 643e8131a0..4db80cbf03 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -1675,8 +1675,35 @@ protected void freezeSelection(int x1, int y1, int x2, int y2, boolean flush, in } } + private void updateWallLine(int x1, int y1, int x2, int y2){ + String blockType = block.name.split("-wall")[0]; + Seq equivalents = Vars.content.blocks().select(block -> block.name.startsWith(blockType + "-wall")); + + int xmin = Math.min(x1, x2), xmax = Math.max(x1, x2), ymin = Math.min(y1, y2), ymax = Math.max(y1, y2); + for(int y = ymin; y <= ymax; ++y){ + for(int x = xmin; x <= xmax; ++x){ + var tile = world.tile(x, y); + if(tile == null) continue; + var otherBlock = tile.block(); + if(otherBlock == null || otherBlock == block || otherBlock.group != BlockGroup.walls) continue; + Tile otherTile; + if(tile.build == null || (otherTile = tile.build.tile) == null || (otherTile != tile && + // include blocks that overlap with but not necessarily originate within the rect + (x != xmin || otherTile.y != y) && (y != ymin && otherTile.x != x))) continue; + if(equivalents.contains(otherBlock, true)) continue; + var replacement = equivalents.find(candidate -> candidate.size == otherBlock.size); + if(replacement == null) continue; + var plan = new BuildPlan(otherTile.x, otherTile.y, 0, replacement, null); + plan.animScale = 1f; + linePlans.add(plan); + } + } + } + protected void updateLine(int x1, int y1, int x2, int y2){ linePlans.clear(); + if(block.group == BlockGroup.walls && Core.input.shift()) updateWallLine(x1, y1, x2, y2); + else iterateLine(x1, y1, x2, y2, l -> { rotation = l.rotation; var plan = new BuildPlan(l.x, l.y, l.rotation, block, block.nextConfig());