Compare commits

...
61 Commits
Author SHA1 Message Date
Anuken 69ed90bb9d Directional unloader read fix 2026-09-30 18:02:13 -04:00
Anuken e337452d46 Reverted #12722 on master branch (will be back on v9)
This reverts commit 9f70386466.
2026-09-30 17:59:01 -04:00
Twcash 9f70386466 fix Copper ore color to match item color (#12722) 2026-09-30 17:56:38 -04:00
Cardillan e7de4e1a10 Fix empty statement fields shifting the remaining parameters (#12715)
* Fix empty statement fields shifting remaining parameters

* Fixed unit tests
2026-09-30 17:55:18 -04:00
WayZer 4cc15b84f6 Do not let content JSON set Effect#initialized (#12724) 2026-09-29 14:29:43 -04:00
Anuken f3bfa418a5 Fixed rare physics crash 2026-09-27 18:45:23 -04:00
Anuken 663b0b8737 Backported patch loading in mods from v9 for future compatibility 2026-09-25 16:47:42 -04:00
Anuken 3a54813513 Fixed some schema generation bugs 2026-09-20 18:40:01 -04:00
Anuken 3e43d88374 WIP re-generation of regions on server 2026-09-20 17:27:51 -04:00
Anuken 580f8f5217 Fixed #12697 2026-09-20 13:55:06 -04:00
Github Actions 067c720a88 Automatic bundle update 2026-09-20 13:22:06 +00:00
Kevin Vilyan 2541682bee Update Bundle ID (#12693) 2026-09-20 09:21:29 -04:00
Vector 78921a064e Update bundle_es.properties (#12672)
* Update bundle_es.properties

* Update bundle_es.properties

* Update bundle_es.properties

* sensor descriptions

* Update bundle_es.properties

* Update bundle_es.properties

* Update bundle_es.properties

* Update bundle_es.properties
2026-09-20 09:21:19 -04:00
Sunky.MP3G 92d53b01fc More RU translations (#12639)
* New RU translations and proper processor ones

* Finished

* Typo
2026-09-20 09:21:11 -04:00
Anuken 8f1a4c5541 Merge remote-tracking branch 'origin/master' 2026-09-20 09:21:00 -04:00
Anuken e2ccb6ea96 Fixed #12691 2026-09-20 09:20:54 -04:00
EggleEgg 2f3a3979a4 Bugfix lightning chain distance (again) (#12695) 2026-09-20 09:19:40 -04:00
Anuken 9f065cea59 Do not add liquid bars to blocks with 0 liquid capacity 2026-09-19 23:24:54 -04:00
Anuken d2c5e09413 Fixed popups not respecting hidden UI state 2026-09-19 11:43:05 -04:00
Anuken 1cc5d54fde Fixed #12690 2026-09-19 10:53:14 -04:00
Anuken 4ef2a6d81e CADisableMinimumFrameDurationOnPhone on iOS 2026-09-18 18:04:25 -04:00
Anuken be14e4173e Null check for displayFlow 2026-09-18 13:58:24 -04:00
Anuken c58aac5f67 Version-specific release fetching for mods 2026-09-18 12:47:14 -04:00
Anuken cada4b2543 Logic parsing tests 2026-09-18 11:11:56 -04:00
summoner ccc5bebc3b Translation: Update bundle_hu.properties (#12679)
Follow the changes of the english bundle
Minor fixes
2026-09-18 10:47:37 -04:00
Anuken 8b80186320 Merge remote-tracking branch 'origin/master' 2026-09-18 10:47:15 -04:00
Anuken 1e8317fbac Fixed #12683 2026-09-18 10:47:09 -04:00
Cardillan 6500b5b6de Improve hex and binary literal parses (#12678)
* Improve hex and binary literal parses

* Moved the specialized unsigned long parsing routine from Strings directly to LAssembler
2026-09-18 10:46:33 -04:00
Anuken eff677e112 Merge remote-tracking branch 'origin/master' 2026-09-18 10:17:56 -04:00
Anuken 543dae701c Updated PR template 2026-09-18 10:17:43 -04:00
Anuken ec2bc54339 Fixed #12677 2026-09-18 10:17:43 -04:00
EggleEgg ca2c51896d Bugfix: scaled crafters unable to dump fast enough (#12669) 2026-09-17 11:10:16 -04:00
Anuken c640ae21ad Merge remote-tracking branch 'origin/master' 2026-09-17 10:54:56 -04:00
Anuken b8f392a116 Fixed #12674 2026-09-17 10:54:49 -04:00
EggleEgg bfa31bf184 Bugfix: attributecrafter yield string format (#12675)
* Bugfix: attributecrafter yield string format

* imports
2026-09-17 10:51:59 -04:00
Anuken 088486c1ad Fixed #12673 2026-09-17 10:51:38 -04:00
Anuken acf891e419 Mod matching release WIP 2026-09-16 15:40:41 -04:00
Anuken ca90af5f56 Updated steamworks 2026-09-16 12:42:44 -04:00
Anuken 45b82b9fa2 Merge remote-tracking branch 'origin/master' 2026-09-16 10:18:06 -04:00
Anuken 22cf431170 Fixed incorrect texture removal 2026-09-16 10:17:59 -04:00
Acts1631 b513d92456 Deduplicate requested assets during handshake (#12667)
* Deduplicate requested assets during handshake

* Use Bits for asset request deduplication
2026-09-15 20:34:09 -04:00
Anuken b3be63437e Better player name wrapping in player list 2026-09-15 12:13:12 -04:00
Anuken c801b625ac Merge remote-tracking branch 'origin/master' 2026-09-15 00:39:53 -04:00
Anuken 0bbc4c6777 Fixed #12660 2026-09-15 00:39:46 -04:00
sora54 4eed142ccc Fix cluster array dimension to use costTypes size (#12658) 2026-09-15 00:19:59 -04:00
EggleEgg 42ec5c9685 Bugfix: cap lightning distance (#12659) 2026-09-15 00:03:44 -04:00
summoner ca9876544e Translation: Update bundle_hu.properties (#12661)
* Translation: Update bundle_hu.properties

Fixing sentences
Fixing wording

* Translation: Update bundle_hu.properties

More fixes
2026-09-15 00:03:23 -04:00
Cardillan 73d5865908 Fix processor text/graphics buffer holding old content on restart (#12663) 2026-09-15 00:03:16 -04:00
NiTiSonandNiTiSon f474eab926 fix: Renderer update cause blocks to lose their extra and floor data (#12664)
Co-authored-by: NiTiSon <NiTiSon@users.noreply.github.com>
2026-09-15 00:03:02 -04:00
Anuken b76147ec50 Antialias + linear filter bleed on data patch images 2026-09-14 10:48:08 -04:00
Anuken 22ca0ce33f Avoidance radius clamp 2026-09-13 22:53:38 -04:00
Anuken 4368c6e60e Fixed #12657 2026-09-13 21:20:47 -04:00
Anuken 7591c51b60 More dummy validation + Pathfinder fixes 2026-09-13 21:15:29 -04:00
Anuken 93c88fea8c Merge remote-tracking branch 'origin/master' 2026-09-13 09:15:04 -04:00
Anuken 9de4deed37 Fixed redundant mod browser mod icons being cached 2026-09-13 09:14:58 -04:00
Thinker8832 254fd3e28d Fix typo in split.container description (#12651) 2026-09-12 23:46:25 -04:00
EggleEgg 9a886aa582 Bugfix: overdriable dummy sddeq3e (#12653) 2026-09-12 23:46:09 -04:00
Anuken a51047a80d Merge remote-tracking branch 'origin/master' 2026-09-12 23:45:59 -04:00
Anuken 55dba0c156 Fixed #12650 2026-09-12 23:45:53 -04:00
WMF a98a7ba227 yet another requiredPlanets fix (#12649) 2026-09-12 23:28:09 -04:00
Anuken 56348b3e31 Fixed #12652 2026-09-12 23:21:40 -04:00
54 changed files with 806 additions and 454 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
If your pull request is **not** translation or serverlist-related, read the list of requirements below and check each box:
If your pull request is **not** translation-related, read the list of requirements below and check each box:
- [ ] I have read the [contribution guidelines](https://github.com/Anuken/Mindustry/blob/master/CONTRIBUTING.md).
- [ ] I have ensured that my code compiles, if applicable.
- [ ] I have ensured that any new features in this PR function correctly in-game, if applicable.
- [ ] I affirm that this code was not written using generative AI/LLMs.
+1 -3
View File
@@ -22,7 +22,7 @@ I **especially** do not want to see PRs that apply any kind of automated analysi
### Do not make AI "contributions".
If I see a PR with significant amounts of code that's obviously written by AI, I will reject your PR, and you will be blocked. Don't waste my time with slop.
If I see a PR with code that's obviously written by AI, I will reject your PR, and you will be blocked. Don't waste my time with slop.
Asking AI questions, and using that information to help you write code? Fine. Using it to actually write code? No.
@@ -67,7 +67,6 @@ What you'll usually need to change:
### Avoid boxed types (Integer, Boolean)
Never create variables or collections with boxed types `Seq<Integer>` or `ObjectMap<Integer, ...>`. Use the collections specialized for this task, e.g. `IntSeq` and `IntMap`.
### Do not allocate anything if possible.
Never allocate `new` objects in the main loop. If you absolutely require new objects, use `Pools` to obtain and free object instances.
Otherwise, use the `Tmp` variables for things like vector/shape operations, or create `static` variables for re-use.
@@ -77,7 +76,6 @@ If using a list, make it a static variable and clear it every time it is used. R
This is situational, but in essence, what it means is to avoid using any sort of getters and setters unless absolutely necessary. Public or protected fields should suffice for most things.
If something needs to be encapsulated in the future, IntelliJ can handle it with a few clicks.
### Do not create methods unless necessary.
Unless a block of code is very large or used in more than 1-2 places, don't split it up into a separate method. Making unnecessary methods only creates confusion, and may slightly decrease performance.
-3
View File
@@ -1,3 +0,0 @@
# Note: The v7 server list is frozen. No new servers will be accepted. All v8 server PRs should be made [here](https://github.com/Anuken/MindustryServerList).
*PRs to edit addresses of existing servers will still be accepted, although very infrequently.*
@@ -211,10 +211,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
if(round++ >= rounds) return false; //only process 1 round
if(rootDirectory == null){
try{
String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no")
.toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length()))
.parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " ");
rootDirectory = Fi.get(path).parent();
rootDirectory = new Fi(new File(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no").toUri())).parent().parent().parent().parent().parent().parent().parent().parent();
}catch(IOException e){
throw new RuntimeException(e);
}
+1 -1
View File
@@ -42,7 +42,7 @@ allprojects{
if(!project.hasProperty("versionModifier")) versionModifier = 'release'
if(!project.hasProperty("versionType")) versionType = 'official'
appName = 'Mindustry'
steamworksVersion = 'da41c05b7a2a44c8625ca0ba7d6ca1a2e73f621a'
steamworksVersion = '185e76cfe630a6f0e1dd7b9bf57be41f6064a1f2'
rhinoVersion = '32395f942976a6d1d21ec9877664554cd5762306'
loadVersionProps = {
+2 -2
View File
@@ -1304,7 +1304,7 @@ bar.cargounitcap = Cargo Unit Cap Reached
bar.drillspeed = Drill Speed: {0}/s
bar.pumpspeed = Pump Speed: {0}/s
bar.efficiency = Efficiency: {0}%
bar.yield = Yield: +{0}%
bar.yield = Yield: {0}%
bar.boost = Boost: +{0}%
bar.powerbuffer = Batteries: {0}/{1}
bar.powerbalance = Power: {0}/s
@@ -3396,7 +3396,7 @@ logicrule.label.wavetimer = wave timer?
logicrule.label.waves = waves
logicrule.label.wave = wave
logicrule.label.wavespacing = wave spacing
logicrule.label.wavesending = waves ending?
logicrule.label.wavesending = waves sending?
logicrule.label.attackmode = attack mode?
logicrule.label.enemycorebuildradius = enemy core build radius
logicrule.label.dropzoneradius = drop zone radius
+179 -179
View File
@@ -838,7 +838,7 @@ objective.destroyblock.name = Destruir bloque
objective.destroyblocks.name = Destruir bloques
objective.destroycore.name = Destruir núcleo
objective.commandmode.name = Modo comando
objective.flag.name = Etiqueta
objective.flag.name = Flag
marker.shapetext.name = Forma del texto
marker.point.name = Punto
@@ -1238,7 +1238,7 @@ stat.reactive = Reacciona con
stat.immunities = Inmune a
stat.healing = Curación
stat.status = Efecto de estado
stat.efficiency = [stat]{0}% Eficiencia
stat.efficiency = {0}% Eficiencia
stat.chance = [stat]{0}%[lightgray] probabilidad de [white]
stat.hitsize = Tamaño de Unidad
stat.resettime = Tiempo de Reinicio
@@ -1299,7 +1299,7 @@ bar.cargounitcap = Se alcanzó el límite de carga de unidades
bar.drillspeed = Velocidad del taladro: {0}/s
bar.pumpspeed = Velocidad de bombeado: {0}/s
bar.efficiency = Eficiencia: {0}%
bar.yield = Yield: +{0}%
bar.yield = Producción: {0}%
bar.boost = Aceleración: +{0}%
bar.powerbuffer = Poder en baterías: {0}/{1}
bar.powerbalance = Energía: {0}/s
@@ -1319,7 +1319,7 @@ bar.nevermelts = nunca hace fusión
bar.heatamount = Calor: {0}
bar.heatpercent = Calor: {0} ({1}%)
bar.power = Energía
bar.progress = Construyendo...
bar.progress = {0}% Construido
bar.loadprogress = Progreso
bar.launchcooldown = Recarga de lanzamiento
bar.input = Entrada
@@ -1990,8 +1990,8 @@ block.metal-tiles-13.name = Casilla de metal 13
block.metal-wall-1.name = Muro de metal 1
block.metal-wall-2.name = Muro de metal 2
block.metal-wall-3.name = Muro de metal 3
block.colored-floor.name = Piso coloreado
block.colored-wall.name = Muro coloreado
block.colored-floor.name = Suelo de Color
block.colored-wall.name = Muro de Color
block.character-overlay.name = Caracter
block.character-overlay-white.name = Caracter (Blanco)
block.rune-overlay.name = Runa
@@ -2757,7 +2757,7 @@ block.ship-refabricator.description = Mejora al segundo nivel la nave selecciona
block.mech-refabricator.description = Mejora al segundo nivel el mech seleccionado.
block.prime-refabricator.description = Mejora al tercer nivel las unidades entrantes.
block.basic-assembler-module.description = Incrementa el nivel del ensamblador si se coloca junto a un área de construcción. Requiere energía. Se puede usar como entrada de un cargador.
block.small-deconstructor.description = Deconstruye las estructuras y unidades entrantes. Devuelve el 100% de su coste de construcción.
block.small-deconstructor.description = Deconstruye las estructuras y unidades entrantes. Devuelve el 100% de su coste de construcción. Guarda los objetos en diferentes capacidades de objetos.
block.reinforced-payload-conveyor.description = Mueve los lotes de carga en una dirección.
block.reinforced-payload-router.description = Distribuye las cargas a los bloques adyacentes. Funcionará como un clasificador si se le establece un filtro.
block.payload-mass-driver.description = Estructura de transporte de carga de largo alcance. Dispara las cargas entrantes a otras catapultas enlazadas.
@@ -2945,63 +2945,63 @@ laccess.displayheight = Alto de un bloque de pantalla, en pixeles.
laccess.buffersize = Número de comandos sin ejecutar en el buffer gráfico de una pantalla.
laccess.operations = Número de operaciones ejecutadas en el bloque.\nPara pantallas, retorna el número de operaciones drawflush.
laccess.maxunits = Unidades máximas que un equipo puede tener.\nSolo puede ser obtenido de los núcleos.
laccess.totalitems = Total number of items contained.
laccess.firstitem = First item contained, or null if empty.
laccess.totalliquids = Total amount of liquid contained.
laccess.totalpower = For blocks with a buffered power store, the amount of power stored; for other blocks, buffer fullness from 0 to 1.
laccess.itemcapacity = Maximum number of items this building can hold.
laccess.liquidcapacity = Maximum amount of liquid this building can hold.
laccess.powercapacity = Maximum amount of power this building can store.
laccess.powernetstored = Total power stored in the connected power grid.
laccess.powernetcapacity = Total power storage capacity of the connected power grid.
laccess.powernetin = Power entering the connected power grid, per second.
laccess.powernetout = Power leaving the connected power grid, per second.
laccess.ammo = Current ammo count of a turret.
laccess.ammocapacity = Maximum ammo count of a turret.
laccess.health = Current health.\nFor bullets, their damage.
laccess.maxhealth = Maximum health.\nFor bullets, the base damage.
laccess.heat = Current heat of a turret that consumes heat, 0 to 1.
laccess.shield = Current shield amount of a unit.
laccess.armor = Armor value; flat damage reduction per hit.
laccess.efficiency = Operating efficiency of a building, where 1 is nominal.
laccess.timescale = Time scale multiplier affecting a building, where 1 is normal speed.
laccess.rotation = Facing angle of a unit or turret, in degrees.\nFor most other buildings, the 0 to 3 orientation.
laccess.x = X position, in tiles.
laccess.y = Y position, in tiles.
laccess.velocityx = X velocity of a unit, in tiles/sec.
laccess.velocityy = Y velocity of a unit, in tiles/sec.
laccess.shootx = X coordinate a unit or turret is aiming at.
laccess.shooty = Y coordinate a unit or turret is aiming at.
laccess.camerax = X position of the controlling player's camera.
laccess.cameray = Y position of the controlling player's camera.
laccess.camerawidth = Width of the controlling player's view, in tiles.
laccess.cameraheight = Height of the controlling player's view, in tiles.
laccess.solid = Whether this building blocks movement.
laccess.range = Range of a unit or turret, in tiles.
laccess.shooting = Whether a unit or turret is currently shooting.
laccess.boosting = Whether a unit is currently boosting or flying over terrain.
laccess.minex = X tile coordinate a unit is mining, or -1 if not mining.
laccess.miney = Y tile coordinate a unit is mining, or -1 if not mining.
laccess.mining = Whether a unit is currently mining.
laccess.buildx = X tile coordinate a unit is building at, or -1 if not building.
laccess.buildy = Y tile coordinate a unit is building at, or -1 if not building.
laccess.pingx = X coordinate a player is pinging, or null if not pinging.
laccess.pingy = Y coordinate a player is pinging, or null if not pinging.
laccess.pingtext = Text of a player's current ping, or null if not pinging.
laccess.building = Building a unit is currently constructing, or null.
laccess.breaking = Building a unit is currently deconstructing, or null.
laccess.team = Team ID of a unit or building.
laccess.flag = Numeric flag value stored on a unit.
laccess.flying = Whether a unit is currently airborne.
laccess.name = Name of the player controlling this unit, or null.
laccess.payloadcount = Number of payloads currently held.
laccess.payloadtype = Type of the last payload picked up, or null.
laccess.totalpayload = Combined size of all held payloads.
laccess.payloadcapacity = Maximum combined payload size a unit or building can carry.
laccess.selectedblock = Block currently selected by the controlling player, or null.
laccess.selectedrotation = Build rotation currently selected by the controlling player, 0 to 3.
laccess.bulletlifetime = Total lifetime of a bullet, in ticks.
laccess.bullettime = Time a bullet has existed, in ticks.
laccess.totalitems = Número total de items dentro.
laccess.firstitem = Primer item dentro, Devuelve null si se encuentra vacío.
laccess.totalliquids = Cantidad total de líquido contenido.
laccess.totalpower = Para bloques con la capacidad de almacenar energía, la cantidad de energía almacenada; para otros bloques, de 0 a 1.
laccess.itemcapacity = Número total de items que esta construcción o unidad puede almacenar.
laccess.liquidcapacity = Tamaño máximo de liquido que esta construcción puede almacenar.
laccess.powercapacity = Cantidad máxima de energía que esta construcción puede almacenar.
laccess.powernetstored = Total de energía almacenada en la red de energía conectada.
laccess.powernetcapacity = Capacidad de almacenamiento total en la red de energía conectada.
laccess.powernetin = Energía que entra en la red de energía conectada, por segundo.
laccess.powernetout = Energía que abandona la red de energía conectada, por segundo.
laccess.ammo = Cantidad de munición actual de una torreta.
laccess.ammocapacity = Cantidad de munición máxima de una torreta.
laccess.health = Salud actual.\nPara balas, su daño.
laccess.maxhealth = Salud máxima.\nPara balas, su daño base.
laccess.heat = Calor actual de una torreta que puede usarlo, 0 a 1.
laccess.shield = Cantidad de escudo actual de una unidad.
laccess.armor = Valor de armadura; reducción plana de daño por golpe.
laccess.efficiency = Eficiencia operativa de la construcción, donde 1 es el valor nominal.
laccess.timescale = Multiplicador de escala de tiempo afectando a la construcción, donde 1 es la velocidad normal.
laccess.rotation = El ángulo al que está mirando una unidad o torreta, en grados.\nPara muchas de las otras construcciones, la orientación de 0 a 3.
laccess.x = Posición X, en casillas.
laccess.y = Posición Y, en casillas.
laccess.velocityx = Velocidad horizontal de una unidad, en casillas/segundo.
laccess.velocityy = Velocidad vertical de una unidad, en casillas/segundo.
laccess.shootx = Coordenada X a la cual una unidad o torreta está apuntando.
laccess.shooty = Coordenada Y a la cual una unidad o torreta está apuntando.
laccess.camerax = Posición X de la camara del jugador que está controlando.
laccess.cameray = Posición Y de la camara del jugador que está controlando.
laccess.camerawidth = Ancho de la vista del jugador que está controlando, en casillas.
laccess.cameraheight = Largo de la vista del jugador que está controlando, en casillas.
laccess.solid = Si una construcción bloquea el movimiento sobre de ella.
laccess.range = Rango de una unidad o torreta, en casillas.
laccess.shooting = Si una unidad o torreta se encuentra disparando.
laccess.boosting = Si una unidad está volando sobrevolando o volando sobre el terreno.
laccess.minex = Coordenada X de la casilla en la cual una unidad está minando, o -1 si no está minando.
laccess.miney = Coordenada Y de la casilla en la cual una unidad está minando, o -1 si no está minando.
laccess.mining = Si una unidad se encuentra minando.
laccess.buildx = Coordenada X de la casilla en la cual una unidad está construyendo, o -1 si no está construyendo.
laccess.buildy = Coordenada Y de la casilla en la cual una unidad está construyendo, o -1 si no está construyendo.
laccess.pingx = Coordenada X en la cual un jugador esta pingeando, o null si no está pingeando.
laccess.pingy = Coordenada Y en la cual un jugador está pingeando, o null si no está pingeando.
laccess.pingtext = Texto del ping actual de un jugador, o null si no está pingeando.
laccess.building = Construcción que la unidad está construyendo actualmente, o null.
laccess.breaking = Construcción que la unidad está deconstruyendo actualmente, o null.
laccess.team = Identificador de equipo de una unidad o construcción.
laccess.flag = Valor numérico o flag almacenada en una unidad.
laccess.flying = Si la unidad se encuentra volando.
laccess.name = Nombre del jugador controlando esta unidad, o null.
laccess.payloadcount = Numero de carga que se lleva actualmente.
laccess.payloadtype = Tipo de la última carga recogida, o null.
laccess.totalpayload = Tamaño combinado de toda la carga llevada.
laccess.payloadcapacity = Tamaño máximo combinado que una unidad o construcción pueden cargar.
laccess.selectedblock = Bloque seleccionado actualmente por el jugador que está controlando, o null.
laccess.selectedrotation = Rotación del bloque actualmente seleccionado por el jugador que está controland, de 0 a 3.
laccess.bulletlifetime = Tiempo de vida total de una bala, en ticks.
laccess.bullettime = Tiempo que una bala ha existido, en ticks.
lcategory.unknown = Desconocido
lcategory.unknown.description = Instrucciones no clasificadas.
@@ -3186,8 +3186,8 @@ instruction.print = Agregar texto
instruction.printchar = Agregar Carácter
instruction.format = Reemplazar Formato
instruction.drawflush = Dibujar Gráficos
instruction.printflush = Imprimir
instruction.getlink = Obtener Enlazados
instruction.printflush = Imprimir Texto
instruction.getlink = Obtener Enlazado
instruction.control = Controlar
instruction.radar = Radar
instruction.sensor = Sensor
@@ -3195,9 +3195,9 @@ instruction.set = Asignar
instruction.operation = Operación
instruction.select = Seleccionar
instruction.wait = Esperar
instruction.stop = Detener
instruction.stop = Detener Ejecución
instruction.lookup = Buscar
instruction.jump = Salto Condicional
instruction.jump = Jump
instruction.end = Terminar
instruction.unitbind = Vincular Unidad
instruction.unitcontrol = Controlar Unidad
@@ -3206,31 +3206,31 @@ instruction.unitlocate = Localizar con Unidad
instruction.query = Query
instruction.getblock = Obtener Bloque
instruction.setblock = Crear Bloque
instruction.spawnunit = Aparecer Unidad
instruction.spawnbullet = Aparecer Bala
instruction.spawnunit = Crear Unidad
instruction.spawnbullet = Crear Bala
instruction.applystatus = Aplicar Estado
instruction.weathersense = Detectar Clima
instruction.weathersense = Obtener Clima
instruction.weatherset = Establecer Clima
instruction.spawnwave = Aparecer Oleada
instruction.spawnwave = Generar Oleada
instruction.setrule = Cambiar Regla
instruction.flushmessage = Flush Message
instruction.flushmessage = Mostrar Mensaje
instruction.cutscene = Cinematica
instruction.effect = Efecto
instruction.explosion = Explosion
instruction.setrate = Set Rate
instruction.setrate = Establecer Velocidad
instruction.fetch = Buscar
instruction.sync = Sincronizar
instruction.clientdata = Datos del Cliente
instruction.getflag = Get Flag
instruction.setflag = Set Flag
instruction.setprop = Set Prop
instruction.getflag = Obtener Flag Global
instruction.setflag = Asignar Flag Global
instruction.setprop = Asignar Propiedad
instruction.playsound = Reproducir Sonido
instruction.playmusic = Reproducir Musica
instruction.setmarker = Set Marker
instruction.makemarker = Make Marker
instruction.localeprint = Locale Print
instruction.packcolor = Desagrupar Color
instruction.unpackcolor = Agrupar Color
instruction.packcolor = Agrupar Color
instruction.unpackcolor = Desagrupar Color
instruction.invalid = Instrucción Invalida
# Implemented via the enumText() method
blockflag.label.core = núcleos
@@ -3263,22 +3263,22 @@ fetchtype.label.unitcount = cantidad de unidades
fetchtype.label.playercount = cantidad de jugadores
fetchtype.label.corecount = cantidad de núcleos
fetchtype.label.buildcount = cantidad de construcciones
graphicstype.label.clear = clear
graphicstype.label.clear = llenar
graphicstype.label.color = color
graphicstype.label.col = col
graphicstype.label.stroke = stroke
graphicstype.label.line = line
graphicstype.label.rect = rect
graphicstype.label.linerect = line rect
graphicstype.label.poly = poly
graphicstype.label.linepoly = line poly
graphicstype.label.triangle = triangle
graphicstype.label.image = image
graphicstype.label.print = print
graphicstype.label.stroke = ancho
graphicstype.label.line = linea
graphicstype.label.rect = rectangulo
graphicstype.label.linerect = lineas rect
graphicstype.label.poly = poligono
graphicstype.label.linepoly = lineas poly
graphicstype.label.triangle = triangulo
graphicstype.label.image = imágen
graphicstype.label.print = texto
graphicstype.label.translate = translate
graphicstype.label.scale = scale
graphicstype.label.rotate = rotate
graphicstype.label.reset = reset
graphicstype.label.scale = escalar
graphicstype.label.rotate = rotar
graphicstype.label.reset = reiniciar
laccess.label.totalitems = total de objetos
laccess.label.firstitem = primer objeto
laccess.label.totalliquids = liquido total
@@ -3318,27 +3318,27 @@ laccess.label.displayheight = largo de pantalla
laccess.label.buffersize = tamaño de buffer
laccess.label.operations = operaciones
laccess.label.size = tamaño
laccess.label.solid = ¿solido?
laccess.label.dead = ¿muerto?
laccess.label.solid = solido?
laccess.label.dead = muerto?
laccess.label.range = rango
laccess.label.shooting = ¿disparando?
laccess.label.boosting = ¿sobrevolando?
laccess.label.shooting = disparando?
laccess.label.boosting = sobrevolando?
laccess.label.minex = minando x
laccess.label.miney = minando y
laccess.label.mining = ¿minando?
laccess.label.mining = minando?
laccess.label.buildx = construyendo x
laccess.label.buildy = construyendo y
laccess.label.pingx = ping x
laccess.label.pingy = ping y
laccess.label.pingtext = texto en ping
laccess.label.building = ¿construyendo?
laccess.label.breaking = ¿deconstruyendo?
laccess.label.building = construyendo?
laccess.label.breaking = deconstruyendo?
laccess.label.speed = velocidad
laccess.label.team = equipo
laccess.label.type = tipo
laccess.label.flag = flag
laccess.label.flying = ¿puede volar?
laccess.label.controlled = ¿controlada?
laccess.label.flying = puede volar?
laccess.label.controlled = controlada?
laccess.label.controller = controlador
laccess.label.name = nombre
laccess.label.payloadcount = conteo de carga
@@ -3351,7 +3351,7 @@ laccess.label.selectedblock = bloque seleccionado
laccess.label.selectedrotation = rotación seleccionada
laccess.label.bulletlifetime = tiempo de vida de la bala
laccess.label.bullettime = tiempo de bala
laccess.label.enabled = ¿activado?
laccess.label.enabled = activado?
laccess.label.shoot = disparo
laccess.label.shootp = disparo con predicción
laccess.label.config = configuración
@@ -3387,17 +3387,17 @@ lmarkercontrol.label.posi = posi
lmarkercontrol.label.uvi = uvi
lmarkercontrol.label.colori = colori
logicrule.label.currentwavetime = tiempo restante oleada actual
logicrule.label.wavetimer = ¿temporizador de oleada?
logicrule.label.waves = ¿oleadas activas?
logicrule.label.wavetimer = temporizador de oleada?
logicrule.label.waves = oleadas activas?
logicrule.label.wave = oleada
logicrule.label.wavespacing = tiempo entre oleadas
logicrule.label.wavesending = ¿adelantar oleada?
logicrule.label.attackmode = ¿modo de juego ataque?
logicrule.label.wavesending = enviar oleadas?
logicrule.label.attackmode = modo de juego ataque?
logicrule.label.enemycorebuildradius = radio de construccion de núcleo enemiga
logicrule.label.dropzoneradius = radio de zona de aparición
logicrule.label.unitcap = capacidad de unidades
logicrule.label.maparea = area del mapa
logicrule.label.lighting = ¿iluminación?
logicrule.label.lighting = iluminación?
logicrule.label.cangameover = se puede perder
logicrule.label.ambientlight = iluminación ambiental
logicrule.label.unitlight = iluminación de unidad
@@ -3405,7 +3405,7 @@ logicrule.label.solarmultiplier = multiplicador solar
logicrule.label.dragmultiplier = multiplicador de arrastre
logicrule.label.ban = banear
logicrule.label.unban = desbanear
logicrule.label.pausedisabled = ¿desactivar pausa?
logicrule.label.pausedisabled = desactivar pausa?
logicrule.label.musicvolume = volumen de la música
logicrule.label.buildspeed = velocidad de construcción
logicrule.label.unithealth = salud de unidad
@@ -3421,29 +3421,29 @@ lunitcontrol.label.idle = inactiva
lunitcontrol.label.stop = detenerse
lunitcontrol.label.unbind = desvincular
lunitcontrol.label.move = mover
lunitcontrol.label.approach = aproximar
lunitcontrol.label.pathfind = encontrarRuta
lunitcontrol.label.autopathfind = encontrarRuta auto
lunitcontrol.label.target = target
lunitcontrol.label.targetp = target predict
lunitcontrol.label.approach = acercar
lunitcontrol.label.pathfind = seguir ruta
lunitcontrol.label.autopathfind = auto-seguir ruta
lunitcontrol.label.target = disparar
lunitcontrol.label.targetp = disparar con predicción
lunitcontrol.label.itemdrop = soltar objeto
lunitcontrol.label.itemtake = tomar objeto
lunitcontrol.label.paydrop = soltar carga
lunitcontrol.label.paytake = tomar carga
lunitcontrol.label.payenter = entrar como carga
lunitcontrol.label.flag = etiqueta
lunitcontrol.label.flag = flag
lunitcontrol.label.mine = minar
lunitcontrol.label.build = construir bloque
lunitcontrol.label.getblock = obtener bloque
lunitcontrol.label.deconstruct = deconstruir
lunitcontrol.label.within = ¿dentro de?
lunitcontrol.label.boost = ¿potenciar?
lunitcontrol.label.within = dentro de?
lunitcontrol.label.boost = sobrevolar?
messagetype.label.notify = notify
messagetype.label.announce = announce
messagetype.label.toast = toast
messagetype.label.mission = mission
querytype.label.unit = unit
querytype.label.building = building
querytype.label.unit = unidad
querytype.label.building = construcción
radarsort.label.distance = distancia
radarsort.label.health = salud
radarsort.label.maxhealth = salud máxima
@@ -3457,40 +3457,40 @@ radartarget.label.attacker = unidad armada
radartarget.label.flying = unidad aérea
radartarget.label.ground = unidad terrestre
radartarget.label.boss = boss
tilelayer.label.floor = floor
tilelayer.label.ore = ore
tilelayer.label.block = block
tilelayer.label.building = building
tilelayer.label.floor = suelo
tilelayer.label.ore = mineral
tilelayer.label.block = bloque
tilelayer.label.building = construcción
name.token.color = color
name.token.width = width
name.token.height = height
name.token.sides = sides
name.token.radius = radius
name.token.rotation = rotation
name.token.image = image
name.token.size = size
name.token.align = align
name.token.set = crear
name.token.of = of
name.token.width = ancho
name.token.height = largo
name.token.sides = lados
name.token.radius = radio
name.token.rotation = rotación
name.token.image = imagen
name.token.size = tamaño
name.token.align = alinear
name.token.set = asigna
name.token.of = de
name.token.to = a
name.token.at = en
name.token.in = en
name.token.damage = daño
name.token.velocityScl = velocity scale
name.token.lifeScl = life scale
name.token.aimX = aim X
name.token.aimY = aim Y
name.token.read = read
name.token.write = write
name.token.from = from
name.token.char = char
name.token.velocityScl = escala de velocidad
name.token.lifeScl = escala de salud
name.token.aimX = apuntar X
name.token.aimY = apuntar Y
name.token.read = lee
name.token.write = escribe
name.token.from = de
name.token.char = carácter
name.token.linknum = link#
name.token.draw = draw
name.token.clear = clear
name.token.apply = apply
name.token.circle = circle
name.token.rect = rect
name.token.draw = dibujar
name.token.clear = limpiar
name.token.apply = aplicar
name.token.circle = circulo
name.token.rect = recta
name.token.positional = posicional
name.token.global = global
name.token.target = objetivo
@@ -3507,43 +3507,43 @@ name.token.then = then
name.token.else = else
name.token.find = localizar
name.token.group = grupo
name.token.enemy = ¿enemigo?
name.token.enemy = enemigo?
name.token.ore = mineral
name.token.outX = X obtenida
name.token.outY = Y obtenida
name.token.owner = owner
name.token.found = ¿encontrado?
name.token.found = encontrado?
name.token.building = construcción
name.token.always = always
name.token.not = not
name.token.pack = pack
name.token.unpack = unpack
name.token.-lookup = = lookup
name.token.type = type
name.token.pack = agrupar
name.token.unpack = desagrupar
name.token.-lookup = = bucar
name.token.type = tipo
name.token.get = get
name.token.team = team
name.token.angle = angle
name.token.spawn = spawn
name.token.-bullet = = bullet
name.token.unit = unit
name.token.weather = weather
name.token.state = state
name.token.team = equipo
name.token.angle = ángulo
name.token.spawn = crear
name.token.-bullet = = bala
name.token.unit = unidad
name.token.weather = clima
name.token.state = estado
name.token.natural = natural
name.token.speed = speed
name.token.level = level
name.token.duration = duration
name.token.seconds = seconds
name.token.duration = duración
name.token.seconds = segundos
name.token.shown = shown
name.token.data = data
name.token.effect = effect
name.token.pierce = pierce
name.token.ground = ground
name.token.effect = efecto
name.token.pierce = atraviesa
name.token.ground = terrestre
name.token.air = air
name.token.flag = flag
name.token.volume = volume
name.token.volume = volumen
name.token.pitch = pitch
name.token.pan = pan
name.token.limit = limit
name.token.limit = limite
name.token.id = id #
name.token.replace = replace?
name.token.for = for
@@ -3562,13 +3562,13 @@ name.token.name = name
name.token.index = index
name.token.block-unit = block/unit
name.token.enable = enable
name.token.amount = amount
name.token.amount = cantidad
name.token.item = item
name.token.takeUnits = take units
name.token.takeUnits = tomar unidades?
name.token.value = value
name.token.floor = floor
name.token.result = resultado
name.token.config = config
name.token.config = configuración
name.token.shapeText = shape text
name.token.point = point
name.token.shape = shape
@@ -3577,12 +3577,12 @@ name.token.line = line
name.token.texture = texture
name.token.quad = quad
name.token.light = light
name.token.snowing = snowing
name.token.rain = rain
name.token.sandstorm = sand storm
name.token.sporestorm = spore storm
name.token.fog = fog
name.token.suspend-particles = suspended particles
name.token.snowing = nevando
name.token.rain = lloviendo
name.token.sandstorm = tormenta de arena
name.token.sporestorm = tormenta de esporas
name.token.fog = neblina
name.token.suspend-particles = particulas suspendidas
name.token.topLeft = top left
name.token.top = top
name.token.topRight = top right
@@ -3626,15 +3626,15 @@ name.token.sparkExplosion = explosión con chispas
name.token.crossExplosion = explosión con cruz
name.token.wave = oleada
name.token.bubble = burbuja
name.token.music = music
name.token.interrupt = interrupt
name.token.music = musica
name.token.interrupt = interrumpe
name.token.all = Todos
name.token.beams = Rayos
name.token.block = Bloques
name.token.block = bloque
name.token.charge = Carga
name.token.environment = Clima
name.token.explosions = Explosiones
name.token.loops = Bucles
name.token.movement = Movimiento
name.token.shoot = Disparos
name.token.ui = Interfaz Usuario
name.token.shoot = disparar
name.token.ui = Interfaz de Usuario
+9 -9
View File
@@ -1019,7 +1019,7 @@ sector.crateredBattleground.description = Víz gyűlt össze ebben a kráterben,
sector.ruinousShores.description = A pusztaság mögött a partvonal húzódik. Valaha ezen a helyen egy partvédelmi rendszer állt. Nem sok minden maradt belőle. Csak a legalapvetőbb védelmi szerkezetek maradtak érintetlenül, minden más csak törmelék lett.\nFolytasd a terjeszkedést. Fedezd fel újra a technológiát.
sector.stainedMountains.description = Mélyebben a szárazföldön fekszenek a hegyek, a spóráktól még érintetlenül.\nTermeld ki a bőséges titán készleteket a körzetben. Tanuld meg felhasználni.\n\nAz ellenség itt nagyobb létszámban van jelen. Ne hagyj nekik időt, hogy a legerősebb egységeiket hadba állíthassák.
sector.overgrowth.description = Ez a terület közelebb esik a spórák forrásához, a spórák már kinőtték.\nAz ellenség egy helyőrséget létesített itt. Építs Mace egységeket. Pusztítsd el a bázist.
sector.tarFields.description = Egy olajtermelő övezet peremvidéke a hegyek és a sivatag határán. Egy azon kevés szektorok közül, ahol még hasznosítható kátránykészletek találhatók.\nBár a terület elhagyatott, veszélyes ellenséges erők fészkelnek a közelben. Ne becsüld alá őket.\n\n[lightgray]Fedezd fel az olajfeldolgozási lehetőségeket, ha tudod.
sector.tarFields.description = Egy olajtermelő övezet peremvidéke a hegyek és a sivatag határán. Azon kevés szektorok egyike, ahol még hasznosítható kátránykészletek találhatók.\nBár a terület elhagyatott, veszélyes ellenséges erők fészkelnek a közelben. Ne becsüld alá őket.\n\n[lightgray]Fedezd fel az olajfeldolgozási lehetőségeket, ha tudod.
sector.desolateRift.description = Ez egy rendkívül veszélyes zóna. Bár nyersanyagokban gazdag, kevés hely áll rendelkezésre. Magas a kockázat. Építs szárazföldi és légvédelmet, amint csak tudsz. Ne tévesszen meg a hosszú szünet az ellenség támadásai között.
sector.nuclearComplex.description = Egy néhai tóriumkitermelő és feldolgozó létesítmény, romokban.\n[lightgray]Fedezd fel a tóriumot és a sokrétű felhasználását.\n\nAz ellenség nagy létszámban van jelen, és folyamatosan megfigyelés alatt tartják a környéket.
sector.fungalPass.description = Átmeneti terület a magas hegyek és a mélyebben fekvő, spórák uralta lapály között. Egy kisebb ellenséges megfigyelő állomás található itt.\nSemmisítsd meg.\nHasználj Dagger egységeket. Pusztítsd el a támaszpontot.
@@ -1299,7 +1299,7 @@ bar.cargounitcap = Az egység raktere megtelt
bar.drillspeed = Termelés: {0}/mp
bar.pumpspeed = Termelés: {0}/mp
bar.efficiency = Hatásfok: {0}%
bar.yield = Hozam: +{0}%
bar.yield = Hozam: {0}%
bar.boost = Erősítés: +{0}%
bar.powerbuffer = Akkumulátorok: {0}/{1}
bar.powerbalance = Áram: {0}/mp
@@ -1414,8 +1414,8 @@ category.function = Funkció
category.optional = Lehetséges fejlesztések
setting.alwaysmusic.name = Folyamatos zenelejátszás
setting.alwaysmusic.description = Amikor engedélyezve van, akkor a zene folyamatosan szól a játékban.\nHa ki van kapcsolva, akkor csak véletlenszerű időközönként szólal meg.
setting.skipcoreanimation.name = Támaszpont animációjának kihagyása kilövéskor és leszálláskor
setting.skipcoreanimation.description = Ha engedélyezve van, akkor a támaszpont fel- és leszállásának\nanimációja nem lesz megjelenítve.
setting.skipcoreanimation.name = Támaszpont animációinak kihagyása
setting.skipcoreanimation.description = Ha engedélyezve van, akkor a támaszpont fel- és leszállási\nanimációja nem lesz megjelenítve.
setting.landscape.name = Fekvő mód zárolása
setting.landscape.description = Ha engedélyezve van, akkor a képernyő tájolása fekvő módban lesz rögzítve.
setting.blockreplace.name = Automatikus blokkjavaslatok
@@ -1468,8 +1468,8 @@ setting.destroyedblocks.name = Lerombolt építmények megjelenítése
setting.destroyedblocks.description = Ha engedélyezve van, akkor a megsemmisített blokkok\ntörmeléket hagynak maguk után.
setting.blockstatus.name = Blokkok állapotának megjelenítése
setting.blockstatus.description = A blokkok felett színes állapotikonok jelennek meg.
setting.conveyorpathfinding.name = Szállítószalag nyomvonalkeresése építéskor
setting.conveyorpathfinding.description = Ha engedélyezve van, akkor a szállítószalagok elhelyezésekor a rendszer\nautomatikusan megpróbálja megtalálni az útvonalat a célállomásig.
setting.conveyorpathfinding.name = Nyomvonalkeresés szállítószalag-építéskor
setting.conveyorpathfinding.description = Ha engedélyezve van, akkor a szállítószalagok elhelyezésekor a rendszer\nautomatikusan megpróbálja megtalálni azok nyomvonalát a célállomásig.
setting.sensitivity.name = Játékvezérlők érzékenysége
setting.sensitivity.description = A játékvezérlők (joystick) érzékenységét lehet vele finomhangolni.
setting.saveinterval.name = Mentési időköz
@@ -1478,7 +1478,7 @@ setting.seconds = {0} másodperc
setting.milliseconds = {0} ezredmásodperc
setting.fullscreen.name = Teljes képernyő
setting.fps.name = FPS, memóriahasználat és ping megjelenítése
setting.fps.description = Megjeleníti a jelenlegi képkockasebességet (FPS), a memóriahasználatot MB/GB-ban\nés a hálózati pinget a képernyő sarkában.
setting.fps.description = Megjeleníti a jelenlegi képkockasebességet, a memóriahasználatot MB/GB-ban\nés a hálózati pinget a képernyő sarkában.
setting.console.name = Konzol engedélyezése
setting.console.description = Engedélyezi a játékon belüli JavaScript-konzolt.
setting.drawhitboxes.name = Ütközési dobozok megjelenítése
@@ -3391,14 +3391,14 @@ logicrule.label.wavetimer = hullámidőzítő?
logicrule.label.waves = hullámok
logicrule.label.wave = hullám
logicrule.label.wavespacing = hullámok közötti idő
logicrule.label.wavesending = hullámok véget érnek?
logicrule.label.wavesending = hullámok küldése?
logicrule.label.attackmode = támadó mód?
logicrule.label.enemycorebuildradius = ellenséges támaszpont építési sugara
logicrule.label.dropzoneradius = ledobási zóna sugara
logicrule.label.unitcap = egységkorlát
logicrule.label.maparea = térkép területe
logicrule.label.lighting = világítás?
logicrule.label.cangameover = lehetséges játék vége
logicrule.label.cangameover = befejeződhet a játék
logicrule.label.ambientlight = környezeti fény
logicrule.label.unitlight = egység fénye
logicrule.label.solarmultiplier = napelemszorzó
+68 -68
View File
@@ -882,7 +882,7 @@ bannedunits.whitelist = Unit yang Dilarang Sebagai Whitelist
bannedblocks.whitelist = Blok yang Dilarang Sebagai Whitelist
addall = Tambah Semua
launch.from = Meluncurkan Dari: [accent]{0}
launch.capacity = Kapasitas item yang Diluncurkan: [accent]{0}
launch.capacity = Kapasitas Item yang Diluncurkan: [accent]{0}
launch.destination = Destinasi: {0}
landing.sources = Total Sumber Sektor: [accent]{0}[]
landing.import = Total Impor Maksimum: {0}[accent]{1}[lightgray]/mnt
@@ -1074,7 +1074,7 @@ sector.caldera-erekir.description = Sumber daya yang terdeteksi di sektor ini te
sector.stronghold.description = Markas musuh yang besar di sektor ini menjaga simpanan [accent]torium[] dalam jumlah besar.\nGunakan itu untuk mengembangkan unit dan menara ke tingkat yang lebih tinggi.
sector.crevice.description = Musuh akan mengirimkan pasukan serangan yang hebat untuk menghancurkan markasmu di sektor ini.\nKembangkan [accent]karbida[] dan [accent]Generator Pirolisis[] mungkin imperatif untuk bertahan hidup.
sector.siege.description = Sektor ini memiliki dua ngarai paralel yang akan memaksa serangan dari dua arah.\nRiset [accent]sianogen[] untuk mendapatkan kemampuan untuk memproduksi unit tank yang lebih kuat.\nPeringatan: Rudal jarak jauh milik musuh telah terdeteksi. Rudal tersebut mungkin ditembak jatuh sebelum terjadi benturan.
sector.crossroads.description = Markas musuh di sektor ini telah didirikan di berbagai medan. Riset unit yang berbeda untuk beradaptasi.\nSelain itu, beberapa markas telah dilindungi oleh perisai. Cari tahu bagaimana mereka diberi daya.
sector.crossroads.description = Markas musuh di sektor ini telah didirikan di berbagai medan. Riset unit yang berbeda untuk beradaptasi.\nSelain itu, beberapa markas telah dilindungi oleh perisai. Cari tahu bagaimana mereka diberi tenaga.
sector.karst.description = Sektor ini kaya akan sumber daya, namun akan diserang oleh musuh begitu inti baru mendarat.\nManfaatkan sumber daya dan riset [accent]fabrik phase[].
sector.origin.description = Sektor terakhir dengan kehadiran musuh yang signifikan.\nTidak ada peluang riset yang tersisa - fokus menghancurkan semua inti musuh.
@@ -1299,7 +1299,7 @@ bar.cargounitcap = Kapasitas Unit Kargo Telah Mencapai Batas
bar.drillspeed = Kecepatan Bor: {0}/d
bar.pumpspeed = Kecepatan Pompa: {0}/d
bar.efficiency = Efisiensi: {0}%
bar.yield = Hasil: +{0}%
bar.yield = Hasil: {0}%
bar.boost = Pendorongan: +{0}%
bar.powerbuffer = Tenaga Baterai: {0}/{1}
bar.powerbalance = Tenaga: {0}/d
@@ -1447,7 +1447,7 @@ setting.autotarget.name = Bidik Musuh Secara Otomatis
setting.autotarget.description = Ketika diaktifkan, unit Anda akan secara otomatis membidik dan menembaki musuh di sekitar.
setting.keyboard.name = Kontrol Tetikus+Papan Ketik
setting.fpscap.name = Batas FPS
setting.fpscap.description = Mengatur jumlah maksimum bingkai per detik (FPS) saat permainan berjalan.\nNilai yang lebih tinggi dapat meningkatkan konsumsi daya baterai.
setting.fpscap.description = Mengatur jumlah maksimum bingkai per detik (FPS) saat permainan berjalan.\nNilai yang lebih tinggi dapat meningkatkan konsumsi baterai.
setting.fpscap.none = Tidak Ada
setting.fpscap.text = {0} FPS
setting.uiscale.name = Skala UI
@@ -2453,7 +2453,7 @@ onset.defenses = [accent]Siapkan pertahanan:[lightgray] {0}
onset.attack = Musuh dalam keadaan rentan diserang. Lancarkan serangan balasan.
onset.cores = Inti baru dapat ditempatkan di [accent]ubin inti[].\nInti baru berfungsi sebagai pangkalan depan dan berbagi sumber daya dengan inti lainnya.\nTempatkan sebuah :core-bastion: inti.
onset.detect = Musuh akan dapat mendeteksi Anda dalam 2 menit.\nSiapkan pertahanan, penambangan, dan produksi.
onset.commandmode = Tahan [accent]shift[] untuk masuk ke[accent]mode perintah[].\n[accent]Klik kiri dan seret[] untuk memilih unit.\n[accent]Klik kanan[] untuk memerintahkan unit yang dipilih untuk bergerak atau menyerang.
onset.commandmode = Tahan [accent]shift[] untuk masuk ke [accent]mode perintah[].\n[accent]Klik kiri dan seret[] untuk memilih unit.\n[accent]Klik kanan[] untuk memerintahkan unit yang dipilih untuk bergerak atau menyerang.
onset.commandmode.mobile = Tekan tombol [accent]perintah[] untuk masuk ke [accent]mode perintah[].\nTekan dan tahan jari Anda, lalu [accent]seret[] untuk memilih unit.\n[accent]Ketuk[] untuk memerintahkan unit yang dipilih untuk bergerak atau menyerang.
aegis.tungsten = Tungsten dapat ditambang menggunakan [accent]bor tumbukan[].\nBangunan ini memerlukan [accent]air[] dan [accent]tenaga[].
@@ -2686,7 +2686,7 @@ block.smite.description = Menembakan semburan peluru yang menusuk dan menyambar.
block.malign.description = Menembakkan rentetan muatan laser pelacak ke arah musuh. Memerlukan pemanasan ekstensif.
block.silicon-arc-furnace.description = Memurnikan silikon dari pasir dan grafit.
block.oxidation-chamber.description = Mengubah berilium dan ozon menjadi oksida. Memancarkan panas sebagai produk sampingan.
block.electric-heater.description = Pemanas yang menghadap ke arah blok. Memerlukan daya yang besar.
block.electric-heater.description = Pemanas yang menghadap ke arah blok. Memerlukan tenaga yang besar.
block.slag-heater.description = Pemanas yang menghadap ke arah blok. Memerlukan lava.
block.phase-heater.description = Pemanas yang menghadap ke arah blok. Memerlukan fabrik phase.
block.heat-redirector.description = Mengalihkan akumulasi panas ke blok lain.
@@ -2700,7 +2700,7 @@ block.carbide-crucible.description = Memadukan grafit dan tungsten menjadi karbi
block.cyanogen-synthesizer.description = Mensintesis sianogen dari arkisit dan grafit. Memerlukan panas.
block.slag-incinerator.description = Membakar benda atau cairan yang tidak mudah menguap. Memerlukan lava.
block.vent-condenser.description = Mengondensasi gas ventilasi menjadi air. Memerlukan tenaga.
block.plasma-bore.description = Saat ditempatkan menghadap dinding bijih, mengeluarkan bahan tanpa batas. Memerlukan daya dalam jumlah kecil.
block.plasma-bore.description = Saat ditempatkan menghadap dinding bijih, mengeluarkan bahan tanpa batas. Memerlukan tenaga dalam jumlah kecil.
block.large-plasma-bore.description = Bor plasma yang lebih besar. Mampu menambang tungsten dan torium. Memerlukan hidrogen dan tenaga.
block.cliff-crusher.description = Menghancurkan dinding, mengeluarkan pasir tanpa batas. Memerlukan tenaga. Efisiensi bervariasi berdasarkan jenis dinding.
block.large-cliff-crusher.description = Menghancurkan dinding, mengeluarkan pasir tanpa batas. Memerlukan tenaga dan hidrogen. Efisiensi bervariasi berdasarkan jenis dinding. Dapat menggunakan grafit untuk meningkatkan efisiensi.
@@ -2741,7 +2741,7 @@ block.turbine-condenser.description = Menghasilkan tenaga ketika ditempatkan pad
block.chemical-combustion-chamber.description = Menghasilkan tenaga dari arkisit dan ozon.
block.pyrolysis-generator.description = Menghasilkan tenaga dalam jumlah besar dari arkisit dan lava. Memproduksi air sebagai produk sampingan.
block.flux-reactor.description = Menghasilkan tenaga dalam jumlah besar ketika dipanaskan. Memerlukan sianogen sebagai penstabil. Tenaga yang dihasilkan dan kebutuhan sianogen sebanding dengan panas yang masuk.\nMeledak jika sianogen yang disediakan tidak mencukupi.
block.neoplasia-reactor.description = Menggunakan arkisit, air, dan fabrik phase untuk menghasilkan daya dalam jumlah besar. Menghasilkan panas dan neoplasma yang berbahaya sebagai produk sampingan.\nMeledak jika neoplasma tidak dikeluarkan dari reaktor melalui saluran.
block.neoplasia-reactor.description = Menggunakan arkisit, air, dan fabrik phase untuk menghasilkan tenaga dalam jumlah besar. Menghasilkan panas dan neoplasma yang berbahaya sebagai produk sampingan.\nMeledak jika neoplasma tidak dikeluarkan dari reaktor melalui saluran.
block.build-tower.description = Secara otomatis membangun kembali bangunan dalam jangkauan dan membantu unit lain dalam konstruksi.
block.regen-projector.description = Perlahan memperbaiki bangunan sekutu di perimeter persegi. Memerlukan hidrogen. Dapat menggunakan fabrik phase untuk meningkatkan efisiensi.
block.reinforced-container.description = Menyimpan sejumlah kecil item. Isi kontainer dapat dibongkar melalui pembongkar muatan. Tidak dapat meningkatkan kapasitas penyimpanan inti.
@@ -2945,63 +2945,63 @@ laccess.displayheight = Tinggi blok tampilan logika dalam piksel.
laccess.buffersize = Untuk pesan: Panjang dari isi pesan.\nUntuk tampilan: Jumlah dari perintah grafis mentah.
laccess.operations = Jumlah operasi yang dilakukan pada blok.\nUntuk tampilan, mengembalikan jumlah dari operasi drawflush.
laccess.maxunits = Jumlah unit maksimum yang dapat dimiliki sebuah tim.\nHanya dapat dideteksi dari inti.
laccess.totalitems = Total number of items contained.
laccess.firstitem = First item contained, or null if empty.
laccess.totalliquids = Total amount of liquid contained.
laccess.totalpower = For blocks with a buffered power store, the amount of power stored; for other blocks, buffer fullness from 0 to 1.
laccess.itemcapacity = Maximum number of items this building can hold.
laccess.liquidcapacity = Maximum amount of liquid this building can hold.
laccess.powercapacity = Maximum amount of power this building can store.
laccess.powernetstored = Total power stored in the connected power grid.
laccess.powernetcapacity = Total power storage capacity of the connected power grid.
laccess.powernetin = Power entering the connected power grid, per second.
laccess.powernetout = Power leaving the connected power grid, per second.
laccess.ammo = Current ammo count of a turret.
laccess.ammocapacity = Maximum ammo count of a turret.
laccess.health = Current health.\nFor bullets, their damage.
laccess.maxhealth = Maximum health.\nFor bullets, the base damage.
laccess.heat = Current heat of a turret that consumes heat, 0 to 1.
laccess.shield = Current shield amount of a unit.
laccess.armor = Armor value; flat damage reduction per hit.
laccess.efficiency = Operating efficiency of a building, where 1 is nominal.
laccess.timescale = Time scale multiplier affecting a building, where 1 is normal speed.
laccess.rotation = Facing angle of a unit or turret, in degrees.\nFor most other buildings, the 0 to 3 orientation.
laccess.x = X position, in tiles.
laccess.y = Y position, in tiles.
laccess.velocityx = X velocity of a unit, in tiles/sec.
laccess.velocityy = Y velocity of a unit, in tiles/sec.
laccess.shootx = X coordinate a unit or turret is aiming at.
laccess.shooty = Y coordinate a unit or turret is aiming at.
laccess.camerax = X position of the controlling player's camera.
laccess.cameray = Y position of the controlling player's camera.
laccess.camerawidth = Width of the controlling player's view, in tiles.
laccess.cameraheight = Height of the controlling player's view, in tiles.
laccess.solid = Whether this building blocks movement.
laccess.range = Range of a unit or turret, in tiles.
laccess.shooting = Whether a unit or turret is currently shooting.
laccess.boosting = Whether a unit is currently boosting or flying over terrain.
laccess.minex = X tile coordinate a unit is mining, or -1 if not mining.
laccess.miney = Y tile coordinate a unit is mining, or -1 if not mining.
laccess.mining = Whether a unit is currently mining.
laccess.buildx = X tile coordinate a unit is building at, or -1 if not building.
laccess.buildy = Y tile coordinate a unit is building at, or -1 if not building.
laccess.pingx = X coordinate a player is pinging, or null if not pinging.
laccess.pingy = Y coordinate a player is pinging, or null if not pinging.
laccess.pingtext = Text of a player's current ping, or null if not pinging.
laccess.building = Building a unit is currently constructing, or null.
laccess.breaking = Building a unit is currently deconstructing, or null.
laccess.team = Team ID of a unit or building.
laccess.flag = Numeric flag value stored on a unit.
laccess.flying = Whether a unit is currently airborne.
laccess.name = Name of the player controlling this unit, or null.
laccess.payloadcount = Number of payloads currently held.
laccess.payloadtype = Type of the last payload picked up, or null.
laccess.totalpayload = Combined size of all held payloads.
laccess.payloadcapacity = Maximum combined payload size a unit or building can carry.
laccess.selectedblock = Block currently selected by the controlling player, or null.
laccess.selectedrotation = Build rotation currently selected by the controlling player, 0 to 3.
laccess.bulletlifetime = Total lifetime of a bullet, in ticks.
laccess.bullettime = Time a bullet has existed, in ticks.
laccess.totalitems = Jumlah total item yang tersimpan.
laccess.firstitem = Item pertama yang dimuat, atau null jika kosong.
laccess.totalliquids = Jumlah total cairan yang tersimpan.
laccess.totalpower = Untuk blok dengan penyimpanan tenaga berpenyangga (dioda), jumlah tenaga yang tersimpan; untuk blok lainnya, tingkat keterisian penyangga dari 0 hingga 1.
laccess.itemcapacity = Jumlah maksimum item yang dapat disimpan oleh bangunan ini.
laccess.liquidcapacity = Jumlah maksimum cairan yang dapat disimpan oleh bangunan ini.
laccess.powercapacity = Jumlah tenaga maksimum yang dapat disimpan oleh bangunan ini.
laccess.powernetstored = Total Tenaga yang tersimpan dalam jaringan listrik yang terhubung.
laccess.powernetcapacity = Total kapasitas penyimpanan tenaga dari jaringan listrik yang terhubung.
laccess.powernetin = Tenaga yang masuk ke jaringan listrik yang terhubung, per detik.
laccess.powernetout = Tenaga yang keluar dari jaringan listrik yang terhubung, per detik.
laccess.ammo = Jumlah amunisi menara saat ini.
laccess.ammocapacity = Jumlah amunisi maksimum menara.
laccess.health = Nyawa saat ini.\nUntuk peluru, besaran damage-nya.
laccess.maxhealth = Nyawa maksimum.\nUntuk peluru, damage dasar.
laccess.heat = Unit panas saat ini dari menara yang mengonsumsi unit panas, 0 hingga 1.
laccess.shield = Jumlah perisai yang dimiliki oleh unit saat ini.
laccess.armor = Nilai armor; pengurangan damage tetap per serangan.
laccess.efficiency = Efisiensi operasional bangunan, dengan nilai nominal 1.
laccess.timescale = Pengali skala waktu yang memengaruhi bangunan, dengan nilai 1 berarti kecepatan normal.
laccess.rotation = Sudut hadap unit atau turret, dalam derajat.\nUntuk sebagian besar bangunan lainnya, orientasi berkisar antara 0 hingga 3.
laccess.x = Posisi X, dalam satuan ubin.
laccess.y = Posisi Y, dalam satuan ubin.
laccess.velocityx = Kecepatan X suatu unit, dalam satuan ubin/detik.
laccess.velocityy = Kecepatan Y suatu unit, dalam satuan ubin/detik.
laccess.shootx = Koordinat X yang dituju oleh unit atau turret.
laccess.shooty = Koordinat Y yang dituju oleh unit atau turret.
laccess.camerax = Posisi X kamera pemain yang memegang kendali.
laccess.cameray = Posisi Y kamera pemain yang memegang kendali.
laccess.camerawidth = Lebar pandangan pemain yang memegang kendali, dalam satuan ubin.
laccess.cameraheight = Tinggi pandangan pemain yang memegang kendali, dalam satuan ubin.
laccess.solid = Apakah bangunan ini menghalangi pergerakan.
laccess.range = Jangkauan unit atau turret, dalam satuan ubin.
laccess.shooting = Apakah unit atau turret sedang menembak.
laccess.boosting = Apakah unit saat ini sedang mengaktifkan pendorong atau terbang melintasi medan.
laccess.minex = Koordinat ubin X tempat unit sedang menambang, atau -1 jika tidak sedang menambang.
laccess.miney = Koordinat ubin Y tempat unit sedang menambang, atau -1 jika tidak sedang menambang.
laccess.mining = Apakah suatu unit sedang menambang.
laccess.buildx = Koordinat ubin X tempat unit sedang membangun, atau -1 jika tidak sedang membangun.
laccess.buildy = Koordinat ubin Y tempat unit sedang membangun, atau -1 jika tidak sedang membangun.
laccess.pingx = Koordinat X yang sedang ditandai oleh pemain, atau null jika tidak sedang menandai.
laccess.pingy = Koordinat Y yang sedang ditandai oleh pemain, atau null jika tidak sedang menandai.
laccess.pingtext = Teks yang menunjukkan ping pemain saat ini, atau null jika tidak sedang melakukan ping.
laccess.building = Unit yang sedang membangun saat ini, atau null.
laccess.breaking = Unit yang sedang mendekonstruksi saat ini, atau null.
laccess.team = ID Tim dari unit atau bangunan.
laccess.flag = Nilai flag numerik yang disimpan pada unit.
laccess.flying = Apakah suatu unit saat ini sedang berada di udara.
laccess.name = Nama pemain yang mengendalikan unit ini, atau null.
laccess.payloadcount = Jumlah muatan yang saat ini dimiliki.
laccess.payloadtype = Jenis muatan terakhir yang diambil, atau null.
laccess.totalpayload = Ukuran gabungan dari semua muatan yang disimpan.
laccess.payloadcapacity = Ukuran muatan gabungan maksimum yang dapat dibawa oleh unit atau bangunan.
laccess.selectedblock = Blok yang saat ini dipilih oleh pemain yang memegang kendali, atau null.
laccess.selectedrotation = Rotasi bangunan yang saat ini dipilih oleh pemain yang memegang kendali, 0 hingga 3.
laccess.bulletlifetime = Total masa hidup peluru, dalam satuan tick.
laccess.bullettime = Durasi peluru aktif, dalam satuan tick.
lcategory.unknown = Tak Diketahui
lcategory.unknown.description = Instruksi tanpa kategori.
@@ -3282,7 +3282,7 @@ graphicstype.label.reset = atur ulang
laccess.label.totalitems = total item
laccess.label.firstitem = item pertama
laccess.label.totalliquids = total cairan
laccess.label.totalpower = total daya
laccess.label.totalpower = total tenaga
laccess.label.itemcapacity = kapasitas item
laccess.label.liquidcapacity = kapasitas cairan
laccess.label.powercapacity = kapasitas tenaga
@@ -3351,7 +3351,7 @@ laccess.label.selectedblock = blok terpilih
laccess.label.selectedrotation = rotasi terpilih
laccess.label.bulletlifetime = masa aktif peluru
laccess.label.bullettime = waktu peluru
laccess.label.enabled = diaktifkan
laccess.label.enabled = aktifkan
laccess.label.shoot = tembak
laccess.label.shootp = prediksi tembak
laccess.label.config = konfig
@@ -3456,7 +3456,7 @@ radartarget.label.player = pemain
radartarget.label.attacker = penyerang
radartarget.label.flying = terbang
radartarget.label.ground = darat
radartarget.label.boss = boss
radartarget.label.boss = penjaga
tilelayer.label.floor = lantai
tilelayer.label.ore = bijih
tilelayer.label.block = blok
+65 -65
View File
@@ -1299,7 +1299,7 @@ bar.cargounitcap = Достигнут предел грузовой единиц
bar.drillspeed = Скорость бурения: {0}/с
bar.pumpspeed = Скорость выкачивания: {0}/с
bar.efficiency = Эффективность: {0}%
bar.yield = Получение: +{0}%
bar.yield = Получение: {0}%
bar.boost = Ускорение: +{0}%
bar.powerbuffer = Энергия в аккумуляторах: {0}/{1}
bar.powerbalance = Энергия: {0}/с
@@ -2945,63 +2945,63 @@ laccess.displayheight = Высота дисплея, в пикселях.
laccess.buffersize = Количество необработанных команд в графическом буфере дисплея.
laccess.operations = Количество операций, выполненных в блоке.\nДля дисплеев, возвращает количество выполненных операций Вывод рисования/drawflush.
laccess.maxunits = Максимальное количество единиц, которое может быть у команды.\nОпределяется с помощью ядер.
laccess.totalitems = Total number of items contained.
laccess.firstitem = First item contained, or null if empty.
laccess.totalliquids = Total amount of liquid contained.
laccess.totalpower = For blocks with a buffered power store, the amount of power stored; for other blocks, buffer fullness from 0 to 1.
laccess.itemcapacity = Maximum number of items this building can hold.
laccess.liquidcapacity = Maximum amount of liquid this building can hold.
laccess.powercapacity = Maximum amount of power this building can store.
laccess.powernetstored = Total power stored in the connected power grid.
laccess.powernetcapacity = Total power storage capacity of the connected power grid.
laccess.powernetin = Power entering the connected power grid, per second.
laccess.powernetout = Power leaving the connected power grid, per second.
laccess.ammo = Current ammo count of a turret.
laccess.ammocapacity = Maximum ammo count of a turret.
laccess.health = Current health.\nFor bullets, their damage.
laccess.maxhealth = Maximum health.\nFor bullets, the base damage.
laccess.heat = Current heat of a turret that consumes heat, 0 to 1.
laccess.shield = Current shield amount of a unit.
laccess.armor = Armor value; flat damage reduction per hit.
laccess.efficiency = Operating efficiency of a building, where 1 is nominal.
laccess.timescale = Time scale multiplier affecting a building, where 1 is normal speed.
laccess.rotation = Facing angle of a unit or turret, in degrees.\nFor most other buildings, the 0 to 3 orientation.
laccess.x = X position, in tiles.
laccess.y = Y position, in tiles.
laccess.velocityx = X velocity of a unit, in tiles/sec.
laccess.velocityy = Y velocity of a unit, in tiles/sec.
laccess.shootx = X coordinate a unit or turret is aiming at.
laccess.shooty = Y coordinate a unit or turret is aiming at.
laccess.camerax = X position of the controlling player's camera.
laccess.cameray = Y position of the controlling player's camera.
laccess.camerawidth = Width of the controlling player's view, in tiles.
laccess.cameraheight = Height of the controlling player's view, in tiles.
laccess.solid = Whether this building blocks movement.
laccess.range = Range of a unit or turret, in tiles.
laccess.shooting = Whether a unit or turret is currently shooting.
laccess.boosting = Whether a unit is currently boosting or flying over terrain.
laccess.minex = X tile coordinate a unit is mining, or -1 if not mining.
laccess.miney = Y tile coordinate a unit is mining, or -1 if not mining.
laccess.mining = Whether a unit is currently mining.
laccess.buildx = X tile coordinate a unit is building at, or -1 if not building.
laccess.buildy = Y tile coordinate a unit is building at, or -1 if not building.
laccess.pingx = X coordinate a player is pinging, or null if not pinging.
laccess.pingy = Y coordinate a player is pinging, or null if not pinging.
laccess.pingtext = Text of a player's current ping, or null if not pinging.
laccess.building = Building a unit is currently constructing, or null.
laccess.breaking = Building a unit is currently deconstructing, or null.
laccess.team = Team ID of a unit or building.
laccess.flag = Numeric flag value stored on a unit.
laccess.flying = Whether a unit is currently airborne.
laccess.name = Name of the player controlling this unit, or null.
laccess.payloadcount = Number of payloads currently held.
laccess.payloadtype = Type of the last payload picked up, or null.
laccess.totalpayload = Combined size of all held payloads.
laccess.payloadcapacity = Maximum combined payload size a unit or building can carry.
laccess.selectedblock = Block currently selected by the controlling player, or null.
laccess.selectedrotation = Build rotation currently selected by the controlling player, 0 to 3.
laccess.bulletlifetime = Total lifetime of a bullet, in ticks.
laccess.bullettime = Time a bullet has existed, in ticks.
laccess.totalitems = Общее количество хранимых предметов.
laccess.firstitem = Первый хранимый предмет. Если пусто - возвращает null.
laccess.totalliquids = Общее количество хранимой жидкости.
laccess.totalpower = Для блоков, хранящих энергию, возвращает её количество; для остальных блоков, их уровень энергии от 0 до 1.
laccess.itemcapacity = Максимальное количество предметов, хранимых этой постройкой.
laccess.liquidcapacity = Максимальное количество жидкости, хранимой этой постройкой.
laccess.powercapacity = Максимальное количество энергии, хранимой этой постройкой.
laccess.powernetstored = Общее количество энергии, хранимой в сети.
laccess.powernetcapacity = Максимальное количество энергии, хранимой в сети.
laccess.powernetin = Энергия, входящая в сеть за секунду.
laccess.powernetout = Энергия, выходящая из сети за секунду.
laccess.ammo = Текущее количество боеприпасов в турели.
laccess.ammocapacity = Максимальное количество боеприпасов в турели.
laccess.health = Текущее здоровье.\nДля пуль возвращает их урон.
laccess.maxhealth = Максимальное здоровье.\nДля пуль возвращает их базовый урон.
laccess.heat = Текущий нагрев турели, от 0 до 1.
laccess.shield = Общее количество щита у юнита.
laccess.armor = Значение брони; на сколько снизится получаемый урон за удар.
laccess.efficiency = Эффективность работы постройки. 1 - обычная эффективность.
laccess.timescale = Множитель скорости, применённый на эту постройку. 1 - обычная скорость.
laccess.rotation = Угол направления юнита или турели в градусах.\nДля большинства других построек возвращает 0-3 в зависимости от их направления.
laccess.x = Позиция X блока/юнита в блоках.
laccess.y = Позиция Y блока/юнита в блоках.
laccess.velocityx = Скорость юнита по X в блоках/сек.
laccess.velocityy = Скорость юнита по Y в блоках/сек.
laccess.shootx = Позиция X, в которую нацелен юнит или турель.
laccess.shooty = Позиция Y, в которую нацелен юнит или турель.
laccess.camerax = Позиция X камеры управляющего игрока.
laccess.cameray = Позиция Y камеры управляющего игрока.
laccess.camerawidth = Ширина камеры управляющего игрока в блоках.
laccess.cameraheight = Высота камеры управляющего игрока в блоках.
laccess.solid = Блокирует ли этот блок движение.
laccess.range = Радиус юнита или турели в блоках.
laccess.shooting = Стреляет ли юнит/турель в данный момент.
laccess.boosting = Летает ли этот наземный юнит.
laccess.minex = Позиция X руды, которую сейчас добывает юнит, или -1, если не добывает.
laccess.miney = Позиция Y руды, которую сейчас добывает юнит, или -1, если не добывает.
laccess.mining = Добывает ли юнит руду.
laccess.buildx = Позиция X, на которой сейчас строит юнит, или -1, если не строит.
laccess.buildy = Позиция Y, на которой сейчас строит юнит, или -1, если не строит.
laccess.pingx = Позиция X отметки, поставленной игроком, или null, если отметки нет.
laccess.pingy = Позиция Y отметки, поставленной игроком, или null, если отметки нет.
laccess.pingtext = Текст отметки, поставленной игроком, или null, если отметки нет.
laccess.building = Блок, который сейчас строится юнитом, или null.
laccess.breaking = Блок, который сейчас разбирается юнитом, или null.
laccess.team = ID команды юнита/блока.
laccess.flag = Числовой флаг, назначенный юниту.
laccess.flying = Летает ли этот юнит.
laccess.name = Имя управляющего игрока, или null.
laccess.payloadcount = Количество перевозимых грузов.
laccess.payloadtype = Тип последнего подобранного груза, или null.
laccess.totalpayload = Суммарный размер всех перевозимых грузов.
laccess.payloadcapacity = Максимальный суммарный размер всех перевозимых грузов юнита.
laccess.selectedblock = Блок, выбранный управляющим игроком, или null.
laccess.selectedrotation = Вращение блока, выбранного управляющим игроком, от 0 до 3.
laccess.bulletlifetime = Максимальное время жизни пули в тиках.
laccess.bullettime = Время существования пули в тиках.
lcategory.unknown = Неизвестно
lcategory.unknown.description = Нет категории.
@@ -3496,9 +3496,9 @@ name.token.global = глобальный
name.token.target = цель
name.token.and = и
name.token.or = или
name.token.b-and = бит-и
name.token.xor = ис-или
name.token.flip = бит-не
name.token.b-and = b-and
name.token.xor = xor
name.token.flip = flip
name.token.order = порядок
name.token.sort = сортировка
name.token.output = выход
@@ -3509,8 +3509,8 @@ name.token.find = найти
name.token.group = группа
name.token.enemy = враг?
name.token.ore = руда
name.token.outX = X
name.token.outY = Y
name.token.outX = X цели
name.token.outY = Y цели
name.token.owner = владелец
name.token.found = найден?
name.token.building = постройка
@@ -3532,7 +3532,7 @@ name.token.natural = естественная
name.token.speed = скорость
name.token.level = уровень
name.token.duration = длительность
name.token.seconds = seconds
name.token.seconds = секунд
name.token.shown = отображается
name.token.data = данные
name.token.effect = эффект
@@ -3599,7 +3599,7 @@ name.token.blockFall = падение блока
name.token.placeBlock = размещение блока
name.token.placeBlockSpark = размещение блока (искры)
name.token.breakBlock = разрушение блока
name.token.-spawn = = spawn
name.token.-spawn = = создать
name.token.trail = след
name.token.breakProp = разрушение валуна
name.token.smokeCloud = облако дыма
+1 -1
View File
@@ -2461,7 +2461,7 @@ split.pickup = 某些物品可以被核心單位撿起。\n撿起這個[accent]
split.pickup.mobile = 某些物品可以被核心單位撿起。\n撿起這個[accent]容器[],然後放到 [accent]重物裝載機[] 上。\n(要撿起或放下東西,請長按該物品。)
split.acquire = 您必須獲取一些鎢才能建造單位。
split.build = 單位必須被運送到牆的另一邊。\n在牆的兩側各放一個[accent]重物質量驅動器[]。\n通過按壓其中一個來設置連結,然後選擇另一個。
split.container = 類似於容器,單位也可以使用[accent]重物質量驅動器[]進行運輸。\n將一個單位兵工廠放置在重物裝載機旁邊以裝載它們,然後將它們送到牆的另一側以進攻敵方基地。
split.container = 類似於容器,單位也可以使用[accent]重物質量驅動器[]進行運輸。\n將一個單位兵工廠放置在重物質量驅動器旁邊以裝載它們,然後將它們送到牆的另一側以進攻敵方基地。
item.copper.description = 最基本的結構材料。在各種類型的方塊中廣泛使用。
item.copper.details = 銅,蕈孢星上異常常見的金屬。加工前結構脆弱。
+4 -1
View File
@@ -183,7 +183,10 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> {
content.load();
mods.loadModPatches();
});
assets.loadRun("baseparts", BaseRegistry.class, () -> {}, () -> bases.load());
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> t.setWrap(TextureWrap.repeat);
+6 -2
View File
@@ -164,6 +164,7 @@ public class ControlPathfinder implements Runnable{
//main thread only!
long lastUpdateId = state.updateId;
long lastRecomputeTime;
//both threads
volatile boolean notFound = false;
@@ -456,7 +457,7 @@ public class ControlPathfinder implements Runnable{
Cluster[][] dim1 = clusters[team];
if(dim1 == null){
dim1 = clusters[team] = new Cluster[Team.all.length][];
dim1 = clusters[team] = new Cluster[costTypes.size][];
}
Cluster[] dim2 = dim1[pathCost];
@@ -1192,7 +1193,9 @@ public class ControlPathfinder implements Runnable{
long fieldKey = FieldIndex.get(destPos, costId, team);
//use existing request if it exists.
if(request != null && request.destination == destPos){
if(request != null && (request.destination == destPos ||
//can only recompute path only twice a second, unless it's far away
(Time.timeSinceMillis(request.lastRecomputeTime) < 1000 && Mathf.dst(destX, destY, request.destination % wwidth, request.destination / wwidth) <= 4f))){
request.lastUpdateId = state.updateId;
Tile initialTileOn = tileOn;
@@ -1313,6 +1316,7 @@ public class ControlPathfinder implements Runnable{
unitRequests.put(unit, request = new PathRequest(unit, team, costId, destPos));
PathRequest f = request;
request.lastRecomputeTime = Time.millis();
//on the pathfinding thread: initialize the request
queue.post(() -> {
+1 -2
View File
@@ -8,7 +8,6 @@ import arc.struct.*;
import arc.util.TaskQueue;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
import mindustry.game.*;
@@ -263,7 +262,7 @@ public class Pathfinder implements Runnable{
tile.build == null || !solid || tile.block() instanceof CoreBlock ? 0 : Math.min((int)(tile.build.health / 40), 80),
tid == 0 && tile.build != null && state.rules.coreCapture ? 255 : tid, //use teamid = 255 when core capture is enabled to mark out derelict structures
solid,
tile.floor().isLiquid && tile.block() == Blocks.air,
tile.floor().isLiquid && !tile.block().solid,
tile.legSolid(),
nearLiquid,
nearGround,
+2 -1
View File
@@ -259,7 +259,8 @@ public class CommandAI extends AIController{
targetPos = new Vec2();
lastTargetPos = targetPos;
}
targetPos.set(attackTarget);
//don't target where target dummies are right now, target their block
targetPos.set(attackTarget instanceof TargetDummyUnit t && t.building != null ? t.building : attackTarget);
if(unit.isGrounded() && attackTarget instanceof Building build && build.tile.solid() && unit.type.pathCostId != ControlPathfinder.costIdLegs && !ramming){
Tile best = build.findClosestEdge(unit, Tile::solid);
@@ -92,7 +92,7 @@ public class AvoidanceProcess implements AsyncProcess{
float rad = Float.intBitsToFloat(items[i + 1]);
float rad2 = rad * rad;
int r = Math.max(1, Mathf.ceil(rad));
int r = Mathf.clamp(1, Mathf.ceil(rad), 20);
for(int dx = -r; dx <= r; dx++){
for(int dy = -r; dy <= r; dy++){
+10 -1
View File
@@ -26,6 +26,7 @@ public class PhysicsProcess implements AsyncProcess{
private PhysicsWorld[] physics;
private Seq<PhysicRef> refs = new Seq<>(false, 20, PhysicRef.class);
private Seq<Future<?>> futures = new Seq<>(false, layers, Future.class);
private Seq<PhysicRef> pendingAdds = new Seq<>(false, 20, PhysicRef.class);
private static volatile long maxPhysicsTime = 0;
@@ -44,7 +45,8 @@ public class PhysicsProcess implements AsyncProcess{
PhysicRef ref = new PhysicRef(unit, body);
refs.add(ref);
if(ref.lastLayer >= 0) physics[ref.lastLayer].add(body);
//defer adds until physics update is done
pendingAdds.add(ref);
}
@Override
@@ -68,6 +70,13 @@ public class PhysicsProcess implements AsyncProcess{
PerfCounter.unitPhysicsAsync.add(maxPhysicsTime);
maxPhysicsTime = 0;
//flush newly added entities
for(int i = 0; i < pendingAdds.size; i++){
PhysicRef ref = pendingAdds.items[i];
if(ref.lastLayer >= 0) physics[ref.lastLayer].add(ref.body);
}
pendingAdds.clear();
//move entities
for(PhysicRef ref : refs){
Physicsc entity = ref.entity;
+4 -5
View File
@@ -6484,7 +6484,7 @@ public class Blocks{
regionSuffix = "-dark";
size = 3;
configurable = false;
showAllCommands = false;
consumePower(3f);
consumeLiquid(Liquids.hydrogen, 3f / 60f);
consumeItems(with(Items.silicon, 40, Items.tungsten, 30));
@@ -6502,7 +6502,7 @@ public class Blocks{
regionSuffix = "-dark";
size = 3;
configurable = false;
showAllCommands = false;
consumePower(2.5f);
consumeLiquid(Liquids.hydrogen, 3f / 60f);
consumeItems(with(Items.silicon, 60, Items.tungsten, 40));
@@ -6521,7 +6521,7 @@ public class Blocks{
regionSuffix = "-dark";
size = 3;
configurable = false;
showAllCommands = false;
consumePower(2.5f);
consumeLiquid(Liquids.hydrogen, 3f / 60f);
consumeItems(with(Items.silicon, 50, Items.tungsten, 40));
@@ -6534,7 +6534,6 @@ public class Blocks{
);
}};
//yes very silly name
primeRefabricator = new Reconstructor("prime-refabricator"){{
requirements(Category.units, with(Items.thorium, 250, Items.oxide, 200, Items.tungsten, 200, Items.silicon, 400));
regionSuffix = "-dark";
@@ -6542,7 +6541,7 @@ public class Blocks{
researchCostMultipliers.put(Items.thorium, 0.2f);
size = 5;
configurable = false;
showAllCommands = false;
consumePower(4.5f);
consumeLiquid(Liquids.nitrogen, 10f / 60f);
consumeItems(with(Items.thorium, 80, Items.silicon, 100));
+3 -1
View File
@@ -972,8 +972,10 @@ public class NetServer implements ApplicationListener{
}else{
Seq<DataAsset> res = new Seq<>();
Seq<DataAsset> allAssets = state.data.getAllExternalAssets();
Bits requestedIds = new Bits(allAssets.size);
for(short id : ids){
if(id >= allAssets.size || id < 0) continue;
if(id >= allAssets.size || id < 0 || requestedIds.get(id)) continue;
requestedIds.set(id);
res.add(allAssets.get(id));
}
+4 -2
View File
@@ -408,8 +408,9 @@ public class UI implements ApplicationListener, Loadable{
table.touchable = Touchable.disabled;
table.setFillParent(true);
if(cinfo.visible && !state.isMenu()) table.marginTop(cinfo.getPrefHeight() / Scl.scl() / 2);
table.update(() -> {
table.visible(() -> {
if(state.isMenu()) table.remove();
return ui.hudfrag.shown;
});
table.actions(Actions.delay(duration * 0.9f), Actions.fadeOut(duration * 0.1f, Interp.fade), Actions.remove());
table.top().table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).padTop(10);
@@ -431,11 +432,12 @@ public class UI implements ApplicationListener, Loadable{
}
table.setFillParent(true);
table.touchable = Touchable.disabled;
table.update(() -> {
table.visible(() -> {
if(state.isMenu()){
table.remove();
if(id != null) popups.remove(id);
}
return ui.hudfrag.shown;
});
table.actions(Actions.delay(duration), Actions.remove(), Actions.run(() -> { if(id != null) popups.remove(id); }));
table.align(align).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).pad(top, left, bottom, right);
+5 -1
View File
@@ -78,7 +78,11 @@ public class MapEditor{
if(build != null && tile.isCenter()){
builds.add(build);
}
tiles.seti(i, new EditorTile(tile.x, tile.y, tile.floorID(), tile.overlayID(), build == null ? tile.blockID() : 0));
var newTile = new EditorTile(tile.x, tile.y, tile.floorID(), tile.overlayID(), build == null ? tile.blockID() : 0);
newTile.floorData = tile.floorData;
newTile.extraData = tile.extraData;
tiles.seti(i, newTile);
}
for(var build : builds){
+1 -1
View File
@@ -23,7 +23,7 @@ public class Effect{
public static final Seq<Effect> all = new Seq<>();
private boolean initialized;
private transient boolean initialized;
public final int id;
+1 -1
View File
@@ -84,7 +84,7 @@ public class Lightning{
Unit furthest = Geometry.findFurthest(x, y, entities);
if(furthest != null){
if(furthest != null && furthest.within(x, y, hitRange * 2f)){
hit.add(furthest.id());
x = furthest.x();
y = furthest.y();
@@ -16,10 +16,18 @@ abstract class TargetDummyComp implements Unitc, Healthc{
}
}
@Replace
@Override
public void rawDamage(float amount){
if(building instanceof TargetDummyBuild td){
td.dummyHit(amount);
}
}
@Override
@Replace
public boolean canLand(){
//dummies should always respect the boost config even when the landing area is obstructed (usually by other dummies)
return true;
}
}
@@ -174,7 +174,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return whether there is solid, un-occupied ground under this unit. */
public boolean canLand(){
return !onSolid() && Units.count(x, y, physicSize(), f -> f != self() && f.isGrounded()) == 0;
float psize = physicSize();
return !onSolid() && !Units.any(x - psize/2f, y - psize/2f, psize, psize, f -> f != self() && f.isGrounded());
}
public boolean inRange(Position other){
+9
View File
@@ -6,6 +6,7 @@ import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.serialization.*;
import mindustry.content.*;
import mindustry.content.TechTree.*;
import mindustry.core.*;
@@ -554,6 +555,14 @@ public abstract class SaveVersion extends SaveFileReader{
public void readDataPatches(DataInput stream, SaveReadState saveState) throws IOException{
stream.readInt(); //version - ignored for now
//the requiredPlanets filter needs this, since the rules aren't read yet
if(headless){
try{
Planet planet = content.planet(new JsonReader().parse(saveState.ruleString).getString("planet", Planets.serpulo.name));
state.rules.planet = planet == null ? Planets.serpulo : planet;
}catch(Exception ignored){}
}
int total = stream.readInt();
Seq<DataAsset> assets = new Seq<>(total);
+36 -14
View File
@@ -11,9 +11,6 @@ import mindustry.logic.LExecutor.*;
public class LAssembler{
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
private static final long invalidNumNegative = Long.MIN_VALUE;
private static final long invalidNumPositive = Long.MAX_VALUE;
public boolean privileged;
/** Maps names to variable. */
public OrderedMap<String, LVar> vars = new OrderedMap<>();
@@ -110,23 +107,48 @@ public class LAssembler{
}
double parseDouble(String symbol){
//fail fast for obvious non-numbers
if(symbol.isEmpty() || !isNumStart(symbol.charAt(0))) return Double.NaN;
//parse hex/binary syntax
if(symbol.startsWith("0b")) return parseLong(false, symbol, 2, 2, symbol.length());
if(symbol.startsWith("+0b")) return parseLong(false, symbol, 2, 3, symbol.length());
if(symbol.startsWith("-0b")) return parseLong(true,symbol, 2, 3, symbol.length());
if(symbol.startsWith("0x")) return parseLong(false,symbol, 16, 2, symbol.length());
if(symbol.startsWith("+0x")) return parseLong(false,symbol, 16, 3, symbol.length());
if(symbol.startsWith("-0x")) return parseLong(true,symbol, 16, 3, symbol.length());
if(symbol.startsWith("0b")) return parseHexOrBin(false, symbol, true, 2);
if(symbol.startsWith("+0b")) return parseHexOrBin(false, symbol, true, 3);
if(symbol.startsWith("-0b")) return parseHexOrBin(true, symbol, true, 3);
if(symbol.startsWith("0x")) return parseHexOrBin(false, symbol, false, 2);
if(symbol.startsWith("+0x")) return parseHexOrBin(false, symbol, false, 3);
if(symbol.startsWith("-0x")) return parseHexOrBin(true, symbol, false, 3);
if(symbol.startsWith("%[") && symbol.endsWith("]") && symbol.length() > 3) return parseNamedColor(symbol);
if(symbol.startsWith("%") && (symbol.length() == 7 || symbol.length() == 9)) return parseColor(symbol);
return Strings.parseDouble(symbol, Double.NaN);
}
double parseLong(boolean negative, String s, int radix, int start, int end) {
long usedInvalidNum = negative ? invalidNumPositive : invalidNumNegative;
long l = Strings.parseLong(s, radix, start, end, usedInvalidNum);
return l == usedInvalidNum ? Double.NaN : negative ? -l : l;
boolean isNumStart(char c){
//note that 'e10' isn't a valid number; '%ffffff' is. Hex numbers start with '0x'.
return c >= '0' && c <= '9' || c == '.' || c == '-' || c == '+' || c == '%';
}
//parses *unsigned* hex or bin number, including negative ones (0xffffffffffffffff as -1)
//detects overflow by input length and uses bit manipulation to avoid signed arithmetics
double parseHexOrBin(boolean negative, String s, boolean binary, int offset){
int end = s.length();
if(offset >= end) return Double.NaN;
int pos = offset;
while(pos < end && s.charAt(pos) == '0') pos ++; //skip leading zeros to avoid incorrect overflow detection
int shift = binary ? 1 : 4;
if(end - pos > 64 / shift) return Double.NaN;
long acc = 0;
int radix = 1 << shift;
while(pos < end){
int digit = Character.digit(s.charAt(pos), radix);
if(digit < 0) return Double.NaN;
acc = acc << shift | digit;
pos ++;
}
return negative ? -acc : acc;
}
double parseColor(String symbol){
@@ -178,4 +200,4 @@ public class LAssembler{
return vars.get(name);
}
}
}
+2
View File
@@ -122,6 +122,8 @@ public class LExecutor{
/** Loads with a specified assembler. Resets all variables. */
public void load(LAssembler builder){
stop = false;
textBuffer.setLength(0);
graphicsBuffer.clear();
nameMap = null;
//retain constants that are links, which, by convention, don't start with @ (builtin) or _ (numeric constant)
vars = builder.vars.values().toSeq().retainAll(var -> !var.constant || var.name.charAt(0) != '_' && var.name.charAt(0) != '@').toArray(LVar.class);
+1 -1
View File
@@ -90,7 +90,7 @@ public abstract class LStatement{
/** Sanitizes variable input strings from text fields into values that will not break logic parsing. */
public static String sanitize(String value){
if(value.length() == 0){
return "";
return "null";
}else if(value.length() == 1){
if(value.charAt(0) == '"' || value.charAt(0) == ';' || value.charAt(0) == ' ' ||
value.charAt(0) == '\n' || value.charAt(0) == '\t' || value.charAt(0) == '#'){
+1 -1
View File
@@ -2541,7 +2541,7 @@ public class LStatements{
b.label(() -> bundle(type));
b.clicked(() -> showSelect(b, MapObjectives.allMarkerTypeNames.toArray(String.class), type, t -> {
type = bundle(t);
type = t;
build(table);
}, 2, cell -> cell.size(160, 50)));
}, Styles.logict, () -> {}).size(180, 40).color(table.color).left().padLeft(2);
+4 -1
View File
@@ -12,6 +12,7 @@ import arc.util.*;
import arc.util.Log.*;
import mindustry.*;
import mindustry.game.EventType.*;
import mindustry.graphics.*;
import mindustry.mod.data.*;
import java.io.*;
@@ -106,6 +107,8 @@ public class DataImagePacker{
try{
Pixmap pixmap = new Pixmap(cacheFile);
Pixmaps.antialias(pixmap);
Drawf.checkBleed(pixmap);
//don't add the double dp prefix, only add it if it's not already present
String name = (image.isGenerated() && image.name.contains("-dp-") ? "" : regionPrefix) + image.name;
@@ -178,7 +181,7 @@ public class DataImagePacker{
public void unload(){
if(patchAtlas != null){
for(var texture : patchAtlas.getTextures()){
if(texture != null) patchAtlas.getTextures().remove(texture);
if(texture != null) Core.atlas.getTextures().remove(texture);
}
for(var region : patchAtlas.getRegions()){
if(region != null){
+18 -12
View File
@@ -78,6 +78,7 @@ public class DataManager{
PixmapRegion error = new PixmapRegion(Pixmaps.blankPixmap());
UnlockableContent[] currentContent = {null};
String[] currentHash = {null};
Fi serverGeneratedDir = Vars.dataDirectory.child("assets/sprites/generated");
MultiPacker saver = new MultiPacker(false){
@Override
@@ -89,10 +90,17 @@ public class DataManager{
if(name.startsWith(DataImagePacker.regionPrefix)) name = name.substring(DataImagePacker.regionPrefix.length());
String path = "generated/" + currentHash[0] + "/" + name + ".png";
ImageAsset newImage = new ImageAsset();
newImage.setPath(path);
//it would be nice to do this async, but the pixmap typically gets disposed right after add() exist
//it would be nice to do this async, but the pixmap typically gets disposed right after add()
byte[] bytes = PixmapIO.writePngBytes(region.pixmap);
newImage.updateData(bytes);
if(Vars.headless){ //TODO: doesn't actually work on the server yet
Fi file = serverGeneratedDir.child(name + ".png");
file.writeBytes(bytes);
newImage.readOverride(path, file);
}else{
newImage.setPath(path);
newImage.updateData(bytes);
}
images.add(newImage);
packed[0] ++;
@@ -150,17 +158,15 @@ public class DataManager{
imagePixmaps.each((key, val) -> val.pixmap.dispose());
error.pixmap.dispose();
if(packed[0] > 0 || forcePack){
if(!Vars.headless && (packed[0] > 0 || forcePack)){
reloadImages();
if(!Vars.headless){
for(var cont : contentToPack){
try{
cont.loadIcon();
cont.load();
}catch(Exception e){
Log.err("Failed to load icons for " + cont, e);
}
for(var cont : contentToPack){
try{
cont.loadIcon();
cont.load();
}catch(Exception e){
Log.err("Failed to load icons for " + cont, e);
}
}
}
+2 -2
View File
@@ -195,8 +195,8 @@ public class DataPatcher{
//register global variables
for(var cont : all){
if(!cont.hasErrored() && cont instanceof UnlockableContent u && Vars.logicVars.get("@" + u.name) == null){
addedVars.add(Vars.logicVars.put("@" + u.name, u, false));
if(!cont.hasErrored() && cont instanceof UnlockableContent u && Vars.logicVars.get("@" + (u instanceof StatusEffect ? "status-" : "") + u.name) == null){
addedVars.add(Vars.logicVars.put("@" + (u instanceof StatusEffect ? "status-" : "") + u.name, u, false));
}
}
+34 -2
View File
@@ -1,10 +1,16 @@
package mindustry.mod;
import arc.struct.*;
import arc.util.*;
import mindustry.ui.dialogs.*;
/** Mod listing as a data class. */
public class ModListing{
public String repo, name, internalName, author, lastUpdated, description, minGameVersion, version;
public String repo, name, internalName, author, lastUpdated, description, minGameVersion, version, iconHash = "";
public boolean hasScripts, hasJava, iosCompatible, legacyCompatible, hasIcon;
public String[] contentTypes = {};
/** game build -> release ID + mod version */
public @Nullable ArrayMap<String, ModRelease> releases;
public String[] tags = {};
public int stars;
@Override
@@ -22,4 +28,30 @@ public class ModListing{
", stars=" + stars +
'}';
}
/** @return the specific release that matches the current game version, or null if no specific match is found (i.e. /latest should be used) */
public @Nullable ModRelease getMatchingRelease(){
if(releases == null) return null;
for(var entry : releases){
if(ModsDialog.matchesGameVersion(ModsDialog.parseVersion(entry.key))){
return entry.value;
}
}
return null;
}
public static class ModRelease{
/** Github release ID ($API/releases/$ID) */
public String id = "";
/** Actual mod version string in release's mod.json */
public String version = "";
@Override
public String toString(){
return "ModRelease{" +
"id='" + id + '\'' +
", version='" + version + '\'' +
'}';
}
}
}
+19
View File
@@ -22,6 +22,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.mod.ContentParser.*;
import mindustry.mod.data.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -936,6 +937,24 @@ public class Mods implements Loadable{
Events.fire(new ModContentLoadEvent());
}
public void loadModPatches(){
for(LoadedMod mod : orderedMods()){
if(mod.root.child("patches").exists()){
try{
Seq<Fi> patches = mod.root.child("patches").findAll(f -> f.extEquals("json") || f.extEquals("hjson"));
if(patches.size > 0){
//create a new patcher per mod so the fields don't get reset
DataPatcher patcher = new DataPatcher();
patches.sort();
patcher.apply(patches.map(f -> new PatchAsset(f.readString())), new Seq<>());
}
}catch(Exception e){
Log.err("Failed to apply patches from mod " + mod.meta.name, e);
}
}
}
}
public void handleContentError(Content content, Throwable error){
parser.markError(content, error);
}
@@ -130,7 +130,7 @@ public class EditorMapsDialog extends MapListDialog{
if(!map.tags.get("description", "").isEmpty()){
t.add("@editor.description").padRight(10).color(Color.gray).top();
t.row();
t.add(map.description()).growX().wrap().padTop(2);
t.add(map.description()).growX().wrap(true).padTop(2);
}
}).height(mapsize).width(mapsize);
@@ -65,6 +65,11 @@ public class ModBrowserDialog extends BaseDialog{
shown(this::rebuildBrowser);
}
public @Nullable ModListing getCachedMod(String repo){
if(modList == null) return null;
return modList.find(m -> m.repo.equalsIgnoreCase(repo));
}
public void getModList(Cons<Seq<ModListing>> listener){
//mods already fetched, use that
if(modList != null){
@@ -247,9 +252,9 @@ public class ModBrowserDialog extends BaseDialog{
textureCache.put(repo, last = Core.atlas.find("nomap"));
if(mod.hasIcon){
Fi cacheFolder = Vars.mobile ? Core.files.cache("modIconCache"): dataDirectory.child("modIconCache");
Fi cacheFolder = mobile ? Core.files.cache("modIconCache"): dataDirectory.child("modIconCache");
cacheFolder.mkdirs();
Fi cacheFile = cacheFolder.child(Strings.sanitizeFilename(mod.repo + mod.lastUpdated) + ".png");
Fi cacheFile = cacheFolder.child(Strings.sanitizeFilename(mod.repo + "_" + mod.iconHash) + ".png");
if(!cacheFile.exists()){ //fetch from Github
ConsT<HttpResponse, Exception> fetch = res -> {
@@ -337,7 +342,7 @@ public class ModBrowserDialog extends BaseDialog{
var found = mods.list().find(l -> mod.repo != null && mod.repo.equals(l.getRepo()));
sel.buttons.button(found == null ? "@mods.browser.add" : "@mods.browser.reinstall", Icon.download, () -> {
sel.hide();
ui.mods.githubImportMod(mod.repo, mod.hasJava, null, true);
ui.mods.githubImportMod(mod);
});
if(Core.graphics.isPortrait()){
+72 -4
View File
@@ -17,6 +17,7 @@ import arc.util.Http.*;
import arc.util.io.*;
import arc.util.serialization.*;
import arc.util.serialization.Jval.*;
import mindustry.core.*;
import mindustry.ctype.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
@@ -26,10 +27,13 @@ import mindustry.mod.Mods.*;
import mindustry.ui.*;
import java.util.*;
import java.util.regex.*;
import static mindustry.Vars.*;
public class ModsDialog extends BaseDialog{
private static final Pattern modVersionPattern = Pattern.compile("\\[[vb](\\d+)(?:\\.(\\d+))?\\]");
public ModBrowserDialog browser;
protected float modImportProgress;
@@ -105,7 +109,11 @@ public class ModsDialog extends BaseDialog{
var mod = repoToMod.get(entry.repo);
if(mod != null){
modToListing.put(mod, entry);
if(Strings.checkNewerSemver(entry.version, mod.meta.version)) withUpdates.add(mod);
var release = entry.getMatchingRelease();
//Only compare to the release that fits the current version the client is using, if one exists; don't look for updates that don't match the version
if(Strings.checkNewerSemver(entry.version, release == null ? mod.meta.version : release.version)){
withUpdates.add(mod);
}
}
}
@@ -171,8 +179,15 @@ public class ModsDialog extends BaseDialog{
if(text.startsWith("https://github.com/")) text = text.substring("https://github.com/".length());
Core.settings.put("lastmod", text);
//there's no good way to know if it's a java mod here, so assume it's not
githubImportMod(text, false, null, true);
var listing = browser.getCachedMod(text);
if(listing != null){
//auto-choose release when a listing is found
githubImportMod(listing);
}else{
//this will auto-detect whether it's java, then grab latest release unconditionally
//TODO: would be nice to grab version-appropriate release but I don't want to copy-paste browser logic for this
githubImportMod(text, false, null, true);
}
});
}).margin(12f);
});
@@ -526,7 +541,7 @@ public class ModsDialog extends BaseDialog{
}
}
public void viewReleases(String repo, boolean isJava, boolean reinstall) {
public void viewReleases(String repo, boolean isJava, boolean reinstall){
BaseDialog load = new BaseDialog("");
load.cont.add("[accent]" + Core.bundle.get("mods.browser.fetching"));
load.show();
@@ -596,6 +611,11 @@ public class ModsDialog extends BaseDialog{
}));
}
public void githubImportMod(ModListing mod){
var matchingRelease = mod.getMatchingRelease();
githubImportMod(mod.repo, mod.hasJava, matchingRelease == null ? null : matchingRelease.id, true);
}
public void githubImportMod(String repo, boolean isJava, boolean forceEnable){
githubImportMod(repo, isJava, null, forceEnable);
}
@@ -686,4 +706,52 @@ public class ModsDialog extends BaseDialog{
}, this::importFail);
}
}
/** @return {major, minor} parsed from a leading/trailing "[v{major}]" or "[v{major}.{minor}]" tag, or null if none is found at either end (or the numbers are unparseable). */
public static @Nullable int[] parseVersionTag(String str){
if(str == null) return null;
Matcher m = modVersionPattern.matcher(str);
while(m.find()){
if(m.start() == 0 || m.end() == str.length()){
int major = Strings.parseInt(m.group(1));
//any major version below 15 is likely a major-version tag like [v7] and should be ignored
if(major == Integer.MIN_VALUE || major < 15) return null;
int minor = m.group(2) != null ? Strings.parseInt(m.group(2)) : 0;
if(minor == Integer.MIN_VALUE) return null;
return new int[]{major, minor};
}
}
return null;
}
public static @Nullable int[] parseVersion(String str){
if(str == null || str.isEmpty()) return null;
int dot = str.indexOf('.');
String majorStr = dot == -1 ? str : str.substring(0, dot);
String minorStr = dot == -1 ? null : str.substring(dot + 1);
if(majorStr.isEmpty() || (dot != -1 && (minorStr.isEmpty() || str.indexOf('.', dot + 1) != -1))) return null;
int major = Strings.parseInt(majorStr);
if(major == Integer.MIN_VALUE) return null;
int minor = minorStr != null ? Strings.parseInt(minorStr) : 0;
if(minor == Integer.MIN_VALUE) return null;
return new int[]{major, minor};
}
/** @return whether the specified release name and its tags match the current game build (e.g. "Bingus Mod [v160]" should only match build 160) */
public static boolean matchesGameVersion(String releaseTitle){
return matchesGameVersion(parseVersionTag(releaseTitle));
}
public static boolean matchesGameVersion(@Nullable int[] tag){
//version not specified, matches anything.
if(tag == null) return true;
//must match exactly; custom builds (-1) do not match.
//for revisions, either match if the revision is unspecified (v160 -> matches ALL 160.x builds), OR, if it is specified, match the exact version (the mod author must have had a good reason for it)
return Version.build == tag[0] && (tag[1] == 0 || Version.revision == tag[1]);
}
}
@@ -132,8 +132,7 @@ public class PlayerListFragment{
});
button.add(iconTable).size(h);
button.labelWrap("[#" + user.color().toString().toUpperCase() + "]" + user.name()).style(Styles.outlineLabel).width(170f).pad(10);
button.add().grow();
button.add("[#" + user.color().toString().toUpperCase() + "]" + user.name()).style(Styles.outlineLabel).growX().wrap(true).pad(10);
button.background(Tex.underline);
@@ -236,8 +235,6 @@ public class PlayerListFragment{
dialog.cont.button("@back", Icon.left, dialog::hide).padTop(-1f).size(220f, 55f);
dialog.show();
}).size(h);
}
}else if(!user.isLocal() && !user.admin && net.client() && Groups.player.size() >= 3 && player.team() == user.team()){ //votekick
@@ -249,7 +246,7 @@ public class PlayerListFragment{
.size(h);
}
content.add(button).width(350f).height(h + 14);
content.add(button).width(mobile ? 350f : 470f).height(h + 14);
content.row();
}
+1 -1
View File
@@ -760,7 +760,7 @@ public class Block extends UnlockableContent implements Senseable{
}
//nothing was added, so it's safe to add a dynamic liquid bar (probably?)
if(!added){
if(!added && liquidCapacity > 0f){
addLiquidBar(build -> build.liquids.current());
}
}
+1 -1
View File
@@ -186,7 +186,7 @@ public class Build{
return false;
}
if(!state.rules.editor && checkCoreRadius){
if(!state.rules.editor && checkCoreRadius && !(!checkVisible && team == Team.derelict)){
//find closest core, if it doesn't match the team, placing is not legal
if(state.rules.polygonCoreProtection){
float mindst = Float.MAX_VALUE;
@@ -5,15 +5,14 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.TextField.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.graphics.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -23,6 +22,8 @@ import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class TargetDummy extends Block{
static final float maxDummySize = 30f;
public final int dpsUpdateTime = timers++;
public UnitType unitType = UnitTypes.dummy;
public float pullScale = 0.33f;
@@ -39,18 +40,18 @@ public class TargetDummy extends Block{
underBullets = true;
targetable = false;
allowedInPayloads = false;
canOverdrive = false;
saveConfig = true;
config(FloatSeq.class, (TargetDummyBuild tile, FloatSeq config) -> {
if(config.size > 0){
tile.unitTeam = tile.team;
if(config.get(0) == 1) tile.unitTeam = Team.get(tile.dummyTeam());
}
if(config.size > 1) tile.boosting = config.get(1) == 1;
if(config.size > 2) tile.unitArmor = config.get(2);
if(config.size > 3) tile.resetTime = config.get(3);
if(config.size > 4) tile.dummySize = config.get(4);
config(int[].class, (TargetDummyBuild tile, int[] config) -> {
if(config.length != 5) return; //invalid, must have 5 values exactly
tile.unitTeam = tile.team;
if(config[0] == 1) tile.unitTeam = Team.get(tile.dummyTeam());
tile.boosting = config[1] == 1;
tile.unitArmor = Mathf.clamp(config[2], 0, 100_000);
tile.resetTime = config[3];
tile.dummySize = Mathf.clamp(Float.intBitsToFloat(config[4]), 0.1f, maxDummySize * tilesize);
if(Float.isNaN(tile.dummySize) || Float.isInfinite(tile.dummySize)) tile.dummySize = 8f;
});
}
@@ -241,28 +242,28 @@ public class TargetDummy extends Block{
t.background(Styles.black6);
t.defaults().left();
t.check(Core.bundle.get("rules.enemyteam"), unitTeam != team, b -> configureFloat(0, Mathf.num(b))).colspan(3).row();
t.check(Core.bundle.get("stat.flying"), boosting, b -> configureFloat(1, Mathf.num(b))).colspan(3).row();
t.check(Core.bundle.get("rules.enemyteam"), unitTeam != team, b -> configureInt(0, Mathf.num(b))).colspan(3).row();
t.check(Core.bundle.get("stat.flying"), boosting, b -> configureInt(1, Mathf.num(b))).colspan(3).row();
t.add(Core.bundle.get("stat.armor"));
t.field("" + (int)unitArmor, TextFieldFilter.digitsOnly, s -> configureFloat(2, Strings.parseInt(s))).valid(val -> Strings.parseInt(val, Integer.MAX_VALUE) < 100_000).width(200f).padLeft(8f).colspan(2).row();
t.field("" + (int)unitArmor, TextFieldFilter.digitsOnly, s -> configureInt(2, Strings.parseInt(s))).valid(val -> Strings.parseInt(val, Integer.MAX_VALUE) < 100_000).width(200f).padLeft(8f).colspan(2).row();
t.add(Core.bundle.get("stat.resettime"));
t.field(Strings.autoFixed(resetTime / 60f, 2), TextFieldFilter.floatsOnly, s -> configureFloat(3, Strings.parseFloat(s) * 60f)).valid(Strings::canParsePositiveFloat).padLeft(8f).growX();
t.field(Strings.autoFixed(resetTime / 60f, 2), TextFieldFilter.floatsOnly, s -> configureInt(3, (int)(Strings.parseFloat(s) * 60f))).valid(Strings::canParsePositiveFloat).padLeft(8f).growX();
t.add(StatUnit.seconds.localized()).padLeft(8).row();
t.add(Core.bundle.get("stat.hitsize"));
t.field("" + (dummySize/tilesize), TextFieldFilter.floatsOnly, s -> configureFloat(4, Strings.parseFloat(s) * tilesize))
t.field("" + (dummySize/tilesize), TextFieldFilter.floatsOnly, s -> configureInt(4, Float.floatToIntBits(Strings.parseFloat(s) * tilesize)))
.valid(val -> {
float parsed = Strings.parseFloat(val, Float.MAX_VALUE);
return parsed >= 0.1f && parsed <= 50f;
return parsed >= 0.1f && parsed <= maxDummySize;
}).padLeft(8f).growX();
t.add(StatUnit.blocks.localized()).padLeft(8f);
}).top().grow().margin(8f);
}
//helper to send the rest of values, since config does not support arrays
public void configureFloat(int index, float value){
FloatSeq seq = FloatSeq.with(Mathf.num(unitTeam != team), Mathf.num(boosting), unitArmor, resetTime, dummySize);
seq.set(index, value);
configure(seq);
public void configureInt(int index, int value){
int[] values = config();
values[index] = value;
configure(values);
}
public int dummyTeam(){
@@ -271,8 +272,9 @@ public class TargetDummy extends Block{
}
@Override
public Object config(){
return FloatSeq.with(Mathf.num(unitTeam != team), Mathf.num(boosting), unitArmor, resetTime, dummySize);
public int[] config(){
//note: float[] is not supported in build 160
return new int[]{Mathf.num(unitTeam != team), Mathf.num(boosting), (int)unitArmor, (int)resetTime, Float.floatToIntBits(dummySize)};
}
@Override
@@ -162,7 +162,7 @@ public class DirectionalUnloader extends Block{
public void read(Reads read, byte revision){
super.read(read, revision);
int id = read.s();
unloadItem = id == -1 ? null : content.items().get(id);
unloadItem = id == -1 ? null : content.item(id);
offset = read.s();
}
}
@@ -43,6 +43,15 @@ public class ThermalGenerator extends PowerGenerator{
lightClipSize = Math.max(lightClipSize, 45f * size * 2f * 2f);
}
@Override
public void afterPatch(){
super.afterPatch();
if(outputLiquid != null){
outputsLiquid = true;
hasLiquids = true;
}
}
@Override
public void setStats(){
super.setStats();
@@ -82,7 +91,7 @@ public class ThermalGenerator extends PowerGenerator{
generateEffect.at(x + Mathf.range(3f), y + Mathf.range(3f));
}
if(outputLiquid != null){
if(outputLiquid != null && hasLiquids){
float added = Math.min(productionEfficiency * delta() * outputLiquid.amount, liquidCapacity - liquids.get(outputLiquid.liquid));
liquids.add(outputLiquid.liquid, added);
dumpLiquid(outputLiquid.liquid);
@@ -11,11 +11,15 @@ import mindustry.world.meta.*;
/** A crafter that gains efficiency from attribute tiles. */
public class AttributeCrafter extends GenericCrafter{
public Attribute attribute = Attribute.heat;
/** Base efficiency of the crafter. */
public float baseEfficiency = 1f;
/** Maximum efficiency/output boost from attributes. */
public float maxBoost = 1f;
/** Minimum efficiency required to place this block. */
public float minEfficiency = -1f;
public boolean displayEfficiency = true;
public boolean displayScaledOutput = true;
/** Whether to show this bar in the UI. */
public boolean displayEfficiency = true, displayScaledOutput = true;
/** Whether liquid consumption scales with efficiency. */
public boolean scaleLiquidConsumption = false;
/** Scaled output (yield) multiplier, scales with attribute. <=0 to disable. */
public float outputScale = 0f;
@@ -34,12 +38,10 @@ public class AttributeCrafter extends GenericCrafter{
drawPlaceText(
(displayEfficiency && boostScale > 0f ?
Core.bundle.format("bar.efficiency",
(int)((baseEfficiency + Math.min(maxBoost, boostScale * sumAttribute(attribute, x, y))) * 100f))
Core.bundle.format("bar.efficiency", (int)((baseEfficiency + Math.min(maxBoost, boostScale * sumAttribute(attribute, x, y))) * 100f))
: "") +
(displayScaledOutput && outputScale > 0f ? "\n" +
Core.bundle.format("bar.yield",
(int)(Math.min(maxBoost, outputScale * sumAttribute(attribute, x, y)) * 100f))
(displayScaledOutput && outputScale > 0f ?
"\n" + Core.bundle.format("bar.yield", (int)((1f + Math.min(maxBoost, outputScale * sumAttribute(attribute, x, y))) * 100f))
: ""), x, y, valid);
}
@@ -59,7 +61,7 @@ public class AttributeCrafter extends GenericCrafter{
if(displayScaledOutput && outputScale > 0f){
addBar("yield", (AttributeCrafterBuild entity) ->
new Bar(
() -> Core.bundle.format("bar.yield", (int)((entity.outputMultiplier() - baseEfficiency) * 100)),
() -> Core.bundle.format("bar.yield", (int)(entity.outputMultiplier() * 100)),
() -> Pal.lightOrange,
entity::outputMultiplier));
}
@@ -335,7 +335,10 @@ public class GenericCrafter extends Block{
public void dumpOutputs(){
if(outputItems != null && timer(timerDump, dumpTime / timeScale)){
for(ItemStack output : outputItems){
dump(output.item);
int amount = Math.max(1, Mathf.round(scaleOutput(output.amount)));
for(int i = 0; i < amount; i++){
if(!dump(output.item)) break;
}
}
}
@@ -39,6 +39,7 @@ public class Reconstructor extends UnitBlock{
public Sound createSound = Sounds.unitCreate;
public float createSoundVolume = 1f;
public boolean showAllCommands = true;
public Reconstructor(String name){
super(name);
@@ -187,7 +188,7 @@ public class Reconstructor extends UnitBlock{
if(build != null && build.team == this.team){
commandPos.set(build);
}
}
}
}
@Override
@@ -197,7 +198,7 @@ public class Reconstructor extends UnitBlock{
public boolean canSetCommand(){
var output = unit();
return output == null || output.allowChangeCommands;
return showAllCommands ? (output == null || output.allowChangeCommands) : output != null && output.commands.size > 1;
}
@Override
@@ -214,6 +215,8 @@ public class Reconstructor extends UnitBlock{
public void buildConfiguration(Table table){
var unit = unit();
if(!showAllCommands && unit == null) return;
var group = new ButtonGroup<ImageButton>();
group.setMinCheckCount(0);
int i = 0, columns = 5;
@@ -47,8 +47,10 @@ public class ItemModule extends BlockModule{
public void updateFlow(){
//update the flow at N fps at most
if(flowTimer.get(1, flowPollInterval)){
int len = content.items().size;
if(items.length != len) items = Arrays.copyOf(items, len);
if(flow == null){
if(flow == null || flow.length != len || cacheSums == null || cacheFlow == null){
if(cacheFlow == null || cacheFlow.length != items.length){
cacheFlow = new WindowedMean[items.length];
for(int i = 0; i < items.length; i++){
@@ -95,7 +97,7 @@ public class ItemModule extends BlockModule{
/** @return a specific item's flow rate in items/s; any value < 0 means not ready.*/
public float getFlowRate(Item item){
return flow == null ? -1f : displayFlow[item.id] * 60;
return flow == null ? -1f : displayFlow != null ? displayFlow[item.id] * 60 : -1f;
}
public boolean hasFlowItem(Item item){
@@ -290,6 +292,10 @@ public class ItemModule extends BlockModule{
public void checkArrayCapacity(int size){
if(items.length != size) items = Arrays.copyOf(items, size);
cacheFlow = null;
cacheSums = null;
displayFlow = null;
flow = null;
}
@Override
@@ -20,9 +20,9 @@ public class LiquidModule extends BlockModule{
public static float flowVisualRefreshInterval = 15f;
private static final Interval flowTimer = new Interval(2);
private static WindowedMean[] cacheFlow;
private static float[] cacheSums;
private static float[] displayFlow;
private static @Nullable WindowedMean[] cacheFlow;
private static @Nullable float[] cacheSums;
private static @Nullable float[] displayFlow;
private static final Bits cacheBits = new Bits();
private float[] liquids = new float[content.liquids().size];
@@ -32,7 +32,10 @@ public class LiquidModule extends BlockModule{
public void updateFlow(){
if(flowTimer.get(1, flowPollInterval)){
if(flow == null){
int len = content.liquids().size;
if(liquids.length != len) liquids = Arrays.copyOf(liquids, len);
if(flow == null || flow.length != len || cacheSums == null || cacheFlow == null){
if(cacheFlow == null || cacheFlow.length != liquids.length){
cacheFlow = new WindowedMean[liquids.length];
for(int i = 0; i < liquids.length; i++){
@@ -75,7 +78,7 @@ public class LiquidModule extends BlockModule{
/** @return current liquid's flow rate in u/s; any value < 0 means 'not ready'. */
public float getFlowRate(Liquid liquid){
return flow == null ? -1f : displayFlow[liquid.id] * 60;
return flow == null ? -1f : displayFlow != null ? displayFlow[liquid.id] * 60 : -1f;
}
public boolean hasFlowLiquid(Liquid liquid){
@@ -152,6 +155,10 @@ public class LiquidModule extends BlockModule{
public void checkArrayCapacity(int size){
if(liquids.length != size) liquids = Arrays.copyOf(liquids, size);
cacheFlow = null;
cacheSums = null;
displayFlow = null;
flow = null;
}
@Override
+1 -1
View File
@@ -26,4 +26,4 @@ org.gradle.caching=true
org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000
android.enableR8.fullMode=false
archash=68a04fab6e
archash=7445105cd2
+2
View File
@@ -38,6 +38,8 @@
<true/>
<key>UIRequiresFullScreen</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>NSPhotoLibraryAddUsageDescription</key>
@@ -54,6 +54,7 @@ public class ServerLauncher implements ApplicationListener{
mods.loadScripts();
content.createModContent();
content.init();
mods.loadModPatches();
if(mods.hasContentErrors()){
err("Error occurred loading mod content:");
+120 -2
View File
@@ -1,3 +1,4 @@
import arc.graphics.*;
import mindustry.logic.*;
import mindustry.logic.LExecutor.*;
import org.junit.jupiter.api.*;
@@ -122,7 +123,7 @@ public class LogicTests{
Arguments.of("a bare single semicolon is invalid", ";", "invalid"),
Arguments.of("a bare single space is invalid", " ", "invalid"),
Arguments.of("a single ordinary character passes through unchanged", "a", "a"),
Arguments.of("empty input stays empty", "", ""),
Arguments.of("empty input gets converted to null", "", "null"),
Arguments.of("a plain already-quoted value is untouched", "\"hello\"", "\"hello\""),
Arguments.of("a unescaped quote at the end gets doubled", "\"hello\\\"", "\"hello\\\\\""),
Arguments.of(
@@ -243,6 +244,123 @@ public class LogicTests{
);
}
/** Values that must resolve to a numeric constant (isobj == false) or an object constant. */
static Stream<Arguments> parseValCases(){
return Stream.of(
// name, symbol, expected (Double => numeric, anything else => object)
Arguments.of("parse null", "null", null),
//decimal
Arguments.of("decimal: zero", "0", 0.0),
Arguments.of("decimal: positive integer", "42", 42.0),
Arguments.of("decimal: negative integer", "-42", -42.0),
Arguments.of("decimal: explicit plus sign", "+42", 42.0),
Arguments.of("decimal: fraction", "3.14", 3.14),
Arguments.of("decimal: negative fraction", "-3.14", -3.14),
Arguments.of("decimal: leading dot", ".5", 0.5),
Arguments.of("decimal: large integer", "123456789012", 123456789012.0),
Arguments.of("decimal: scientific", "1e3", 1000.0),
Arguments.of("decimal: scientific negative exponent", "1.5e-2", 0.015),
//hex
Arguments.of("hex: zero", "0x0", 0.0),
Arguments.of("hex: uppercase digits", "0xFF", 255.0),
Arguments.of("hex: lowercase digits", "0xff", 255.0),
Arguments.of("hex: mixed case", "0xDeadBeef", 3735928559.0),
Arguments.of("hex: explicit plus", "+0xFF", 255.0),
Arguments.of("hex: negative", "-0xFF", -255.0),
Arguments.of("hex: many leading zeros aren't overflow", "0x0000000000000000000000FF", 255.0),
Arguments.of("hex: only zeros", "0x" + "0".repeat(100), 0.0),
Arguments.of("hex: Long.MAX_VALUE", "0x7FFFFFFFFFFFFFFF", (double)Long.MAX_VALUE),
Arguments.of("hex: Long.MIN_VALUE (0x8000...)", "0x8000000000000000", (double)Long.MIN_VALUE),
Arguments.of("hex: unsigned long max wraps to -1", "0xFFFFFFFFFFFFFFFF", -1.0),
Arguments.of("hex: unsigned long max - 1 wraps to -2", "0xFFFFFFFFFFFFFFFE", -2.0),
Arguments.of("hex: negated -1 wrap gives 1", "-0xFFFFFFFFFFFFFFFF", 1.0),
Arguments.of("hex: negative Long.MAX_VALUE", "-0x7FFFFFFFFFFFFFFF", -(double)Long.MAX_VALUE),
//binary
Arguments.of("bin: zero", "0b0", 0.0),
Arguments.of("bin: one", "0b1", 1.0),
Arguments.of("bin: ten", "0b1010", 10.0),
Arguments.of("bin: explicit plus", "+0b1010", 10.0),
Arguments.of("bin: negative", "-0b1010", -10.0),
Arguments.of("bin: many leading zeros aren't overflow", "0b" + "0".repeat(70) + "1", 1.0),
Arguments.of("bin: 63 ones is Long.MAX_VALUE", "0b0" + "1".repeat(63), (double)Long.MAX_VALUE),
Arguments.of("bin: 1 followed by 63 zeros is Long.MIN_VALUE", "0b1" + "0".repeat(63), (double)Long.MIN_VALUE),
Arguments.of("bin: 64 ones wraps to -1", "0b" + "1".repeat(64), -1.0)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("parseValCases")
void parseVarValues(String name, String symbol, Object expected){
LAssembler asm = new LAssembler();
LVar v = asm.var(symbol);
if(expected instanceof Double d){
assertFalse(v.isobj, "should be numeric: " + symbol);
assertEquals(d, v.numval, 0.00001f);
}else{
assertTrue(v.isobj);
assertEquals(expected, v.objval);
}
}
/** Colors are packed into the bits of a (tiny, denormal) double, so they need an exact comparison. */
static Stream<Arguments> parseColorCases(){
return Stream.of(
Arguments.of("color: rgb white", "%ffffff", Color.toDoubleBits(255, 255, 255, 255)),
Arguments.of("color: rgba", "%ff000080", Color.toDoubleBits(255, 0, 0, 128)),
Arguments.of("color: named", "%[red]", Colors.get("red").toDoubleBits())
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("parseColorCases")
void parseColorValues(String name, String symbol, double expected){
LAssembler asm = new LAssembler();
LVar v = asm.var(symbol);
assertFalse(v.isobj);
assertEquals(expected, v.numval, 0.0);
}
/** Anything here must NOT be parsed as a number (i.e. it becomes a plain variable name). */
static Stream<String> invalidNumberCases(){
return Stream.of(
//fail-fast: first char can't start a number
"",
"abc",
"e10",
"NaN",
"Infinity",
//prefix with no digits
"0x", "0b", "+0x", "-0b",
//invalid digits
"0xG", "0x12G4", "0b2", "0b102", "0x-1", "0x1.5",
//overflow: more than 64 significant bits
"0x1" + "0".repeat(16), //17 hex digits
"-0x1" + "0".repeat(16),
"0x" + "F".repeat(17),
"0b1" + "0".repeat(64), //65 binary digits
"0b" + "1".repeat(65),
//malformed colors
"%fff",
"%[nosuchcolor]"
);
}
@ParameterizedTest(name = "invalid: [{0}]")
@MethodSource("invalidNumberCases")
void parseInvalidNumbers(String symbol){
LAssembler asm = new LAssembler();
LVar v = asm.var(symbol);
assertTrue(v.isobj, "should not parse as a number: " + symbol);
assertNull(v.objval);
}
//unterminated / malformed string literals: these must fail loudly, never silently misparse
@ParameterizedTest(name = "{0}")
@@ -336,4 +454,4 @@ public class LogicTests{
void crlfLabelsResolveToTheSameJumpLocationAsLfLabels(){
assertDoesNotThrow(() -> load("loop:\r\njump loop always\r\n"));
}
}
}
+14 -3
View File
@@ -95,7 +95,12 @@ public class SchemaGenerator{
var root = getTypeDecl(field.getDeclaringClass());
if(root != null) root.getFieldByName(field.getName()).ifPresent(fdec -> {
String[] docAndDefault = determineJavadocAndDefault(field, fdec, fdec.getVariables().getFirst().orElseThrow());
var variable = fdec.getVariables().stream()
.filter(v -> v.getNameAsString().equals(field.getName()))
.findFirst().orElse(null);
if(variable == null) return;
String[] docAndDefault = determineJavadocAndDefault(field, fdec, variable);
if(docAndDefault[0] != null) inner.put("doc", docAndDefault[0]);
if(docAndDefault[1] != null) inner.put("default", docAndDefault[1]);
});
@@ -302,8 +307,14 @@ public class SchemaGenerator{
initValue = "[]";
}
//field
if(initValue.contains(".") && !(baseField.getType().isArray())){
//numeric literal: drop the Java-only suffix (f/F/d/D/L), keep the decimal point for JSON
boolean numeric = initValue.matches("-?(\\d+\\.?\\d*|\\.\\d+)([eE][-+]?\\d+)?[fFdDlL]?");
if(numeric){
initValue = initValue.replaceAll("[fFdDlL]$", "");
}
//field reference, e.g. Category.turret -> turret (never applied to numbers)
if(!numeric && initValue.contains(".") && !(baseField.getType().isArray())){
var split = initValue.split("\\.");
initValue = split[split.length - 1];
}