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

This commit is contained in:
Petr Gašparík
2020-06-07 23:56:42 +02:00
262 changed files with 9460 additions and 7181 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 186 B

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 347 B

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 678 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 820 B

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 467 B

+2
View File
@@ -0,0 +1,2 @@
mschxœ-ŒÑ
à E¯U»BÊ>ć}’³aZ‹Õýý’v ¹9¹$Á„›‚Ù|&¨Çˆ{(ûNÕ}|J.ùú"èµ̱QvGé5–}£ž}‹Á­5¦„yG«ñÙ[©˜BÙÞôe. "ê’SIÓPÂÆŠ¯™þÀaΙ␍-hY¸ Ÿ+özdå‡:—øh”ƒAM
+2
View File
@@ -0,0 +1,2 @@
mschxś%LŃ
Ă <*…AÇ>ħ}kC+ŘV˘˛ßźYIrwÜ%Á„É`<ĂÁ0o‡×rĺĚâż!%ź‚lŚq+ľ\MĆ3źÜŽPăâW‰©§k,Uâ§ŐK0oňŢ÷}.ŔÜ‚ąače”Fµµ€%ęĘuKAwB*ţ™˝?X=V"5ť~˙[ë
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
mschxś%Ś] „GD4ńˇ>ôjĹŤ5A0@Űxů¶KÍţfgöi íŚ~ÉTs@KÉaÚŮ=(lŽĽ]Ňć=ú™JátbĚŃS˛ö\ />cÂtÄ7'âÂVô•qwqźźąl1Ř•'*â·Â»Íń™¸IAA52Z4
uQ˛I´ZšşÚ%şú~ÄjęţZ zz]'fa苡/†ľřúňUú\ą,Ä
+2
View File
@@ -0,0 +1,2 @@
mschŠA
!Û…À^rÈ7|”q‡]A•|?cº(h‚ƒï©1üexœcéÍqÊŸ,í»Æ,ÒãÍ5MQ¼ç'õ’S—–Zq–É-@Àæ:L´Ëí"o—@ƒ4'
+2
View File
@@ -0,0 +1,2 @@
mschxœE
ƒ0 „¯¦þÀ`{Ž>T¦A µ–*Œ½ûÀõg°@øÂå.Æ  =o‚n)Õãv쎣 ìÅA¯lºMü,ý“ÏSâã&ÓÊÞNìÌ­s¸/‘ÃjO1!Êq` ûK¢ñû,&[ÀÿR Œ¤(O€®»6 EÖ * ?»Ê».¡šëº>hRSÉ–-êTQBTP5PýAÙù–Ø(¢
+1
View File
@@ -0,0 +1 @@
mschxœ-Ïknƒ0à±]Ò$mZ”ªgàP!’Á/©·ï.S~ðY »;Æ ßÅ>>ãü ¸üŒÝ:Ä¥o›ÇÔ§„²ÍãÖýæ ~ŽiË(×1åøè&TýÒ␍͜ש•[\Ó ÁþX}X³ŸŒbˆ%ŽÄ“@^HIä¨G;ŽvZ”+¯è¨VùÒ_ûÎÚpƒÕæ»×AF6ÉçB¨´à¼ì¼ëºRê;9‘Wr&r%oäÜÈù$µ¤”ëìQEoUk¢ã"-Hî?¤±|
+1
View File
@@ -0,0 +1 @@
mschxM־a0 אַLDB׀sp¨)ְֵF&‰סלˆ³sׁר£i³ץֻ+NP ₪ׁAך¾ןsה£6rhnvװ®µ¡±ויJPg½,ה88ץְv0 צ³½“kםשD¦c␍¹ )2₪‚›ְg„ה–ם₪ש␍ֲo~ץנkxEFESDS°ֹ¾&ל'¯J®eeUױה/₪!u4u0@®}wO<
+2
View File
@@ -0,0 +1,2 @@
mschxœ%ŒA
Ã0 'VKÝB ~ˆeãKjçÐÇ—’J HbwV,0p«éy”´/%ARŸymí“{¬mÉ„½­©Ç-Õ¼FU%O]<n@ðÞq‡ãâzez°Tµ¦\¹˜Í_er¾Mö6ZÉÏ™¬ûÐâe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
mschxœ%ŠQƒ0 C]RÐÄ>v>9Ê"µˆ¶S(HÜ~)X±_dƒƒÏKŒa•YëD­Ý¹(¼¸äS®¢øü²i©‘§¯ÆmÃ;VIÓ^e0â–s-@␍ÝuV™É½­Úƒx›z¸?Œk¤
+3
View File
@@ -0,0 +1,3 @@
mschxœ=Љnƒ0 `çpKK·Òco±>TшÄ!¨Ô·¯_*‘ø‚c'Á´£C~
c¤Csˆ¹ÿLU;OÏøš35clû0¥6 .§a ×m3Õ]ZÖœþ¶Urê´Æñ±Ì[n#ýRy ^ ðŸ¸b1ñ ÒCp58/ð␍ÎØßƒJ·³´2nd”;#g—ýÎs(p(pZàdÜôËË0
ƒØ)Є—ódvF¼pU]`ü-£ Œ.°$Y+”;²Þñ␍Ú.
+1
View File
@@ -0,0 +1 @@
mschxœ=Ž]ƒ „Gå§IoÑ7e”¨I•†jMo_—IÊ3;» nôÖ¯vžòyæi6°CÚ>ñ›2î¯-k¿/C7æåùDXö¸vïtä!BGB“ àr*^ý÷5š¹RS´kJÌú‚3%rÉ6\b8Ìp‰á#…F”¾ Q‰’,Z¶[F¬˜G‰8iÐ UC´„eÍÉ?<UvÏažÃ¼˜?·ì1
+2
View File
@@ -0,0 +1,2 @@
mschxś%ŚA0 7lh
ŠD’6%Eüľ öÁcÉŁĹĂŔĆ%žŰëüČvZp- Kń'WĘý!aţ¦’ť`zG)a9Ľ›×ě÷Ŕ„{hę2ŠNA…Uô`WÓ7ŹőŞVC}7ő.P Ôµ@-°ţµf
Binary file not shown.
Binary file not shown.
+4 -5
View File
@@ -14,9 +14,8 @@ uniform float OriginalIntensity;
varying MED vec2 v_texCoords;
void main(){
vec3 original = texture2D(u_texture0, v_texCoords).rgb;
vec3 bloom = texture2D(u_texture1, v_texCoords).rgb * BloomIntensity;
original = OriginalIntensity * (original - original * bloom);
gl_FragColor.rgb = original + bloom;
vec4 original = texture2D(u_texture0, v_texCoords) * OriginalIntensity;
vec4 bloom = texture2D(u_texture1, v_texCoords) * BloomIntensity;
original = original * (vec4(1.0) - bloom);
gl_FragColor = original + bloom;
}
+2 -6
View File
@@ -496,7 +496,6 @@ error.bloom = Failed to initialize bloom.\nYour device may not support it.
#NOTE TO TRANSLATORS: don't bother editing these, they'll be removed and/or rewritten anyway
sector.groundZero.name = Ground Zero
sector.desertWastes.name = Desert Wastes
sector.craters.name = The Craters
sector.frozenForest.name = Frozen Forest
sector.ruinousShores.name = Ruinous Shores
@@ -514,7 +513,6 @@ sector.fungalPass.name = Fungal Pass
sector.groundZero.description = The optimal location to begin once more. Low enemy threat. Few resources.\nGather as much lead and copper as possible.\nMove on.
sector.frozenForest.description = Even here, closer to mountains, the spores have spread. The frigid temperatures cannot contain them forever.\n\nBegin the venture into power. Build combustion generators. Learn to use menders.
sector.desertWastes.description = These wastes are vast, unpredictable, and criss-crossed with derelict sector structures.\nCoal is present in the region. Burn it for power, or synthesize graphite.\n\n[lightgray]This landing location cannot be guaranteed.
sector.saltFlats.description = On the outskirts of the desert lie the Salt Flats. Few resources can be found in this location.\n\nThe enemy has erected a resource storage complex here. Eradicate their core. Leave nothing standing.
sector.craters.description = Water has accumulated in this crater, relic of the old wars. Reclaim the area. Collect sand. Smelt metaglass. Pump water to cool turrets and drills.
sector.ruinousShores.description = Past the wastes, is the shoreline. Once, this location housed a coastal defense array. Not much of it remains. Only the most basic defense structures have remained unscathed, everything else reduced to scrap.\nContinue the expansion outwards. Rediscover the technology.
@@ -540,6 +538,7 @@ settings.clearall.confirm = [scarlet]WARNING![]\nThis will clear all data, inclu
paused = [accent]< Paused >
clear = Clear
banned = [scarlet]Banned
unplaceable.sectorcaptured = [scarlet]Requires captured sector
yes = Yes
no = No
info.title = Info
@@ -785,8 +784,6 @@ rules.unitdrops = Unit Drops
rules.unitbuildspeedmultiplier = Unit Production Speed Multiplier
rules.unithealthmultiplier = Unit Health Multiplier
rules.blockhealthmultiplier = Block Health Multiplier
rules.playerhealthmultiplier = Player Health Multiplier
rules.playerdamagemultiplier = Player Damage Multiplier
rules.unitdamagemultiplier = Unit Damage Multiplier
rules.enemycorebuildradius = Enemy Core No-Build Radius:[lightgray] (tiles)
rules.wavespacing = Wave Spacing:[lightgray] (sec)
@@ -796,12 +793,11 @@ rules.deconstructrefundmultiplier = Deconstruct Refund Multiplier
rules.waitForWaveToEnd = Waves Wait for Enemies
rules.dropzoneradius = Drop Zone Radius:[lightgray] (tiles)
rules.title.waves = Waves
rules.title.respawns = Respawns
rules.title.resourcesbuilding = Resources & Building
rules.title.player = Players
rules.title.enemy = Enemies
rules.title.unit = Units
rules.title.experimental = Experimental
rules.title.environment = Environment
rules.lighting = Lighting
rules.ambientlight = Ambient Light
rules.solarpowermultiplier = Solar Power Multiplier
+1
View File
@@ -88,3 +88,4 @@ Alicila
Daniel Dusek
DeltaNedas
GioIacca9
SnakkiZXZ
+7
View File
@@ -235,3 +235,10 @@
63509=basic-reconstructor|block-basic-reconstructor-medium
63508=block-loader|block-block-loader-medium
63507=block-unloader|block-block-unloader-medium
63506=core-silo|block-core-silo-medium
63505=data-processor|block-data-processor-medium
63504=payload-router|block-payload-router-medium
63503=silicon-crucible|block-silicon-crucible-medium
63502=segment|block-segment-medium
63501=large-overdrive-projector|block-large-overdrive-projector-medium
63500=disassembler|block-disassembler-medium
Binary file not shown.
+67 -63
View File
@@ -24,70 +24,72 @@ const extend = function(classType, params){
const newEffect = (lifetime, renderer) => new Effects.Effect(lifetime, new Effects.EffectRenderer({render: renderer}))
Call = Packages.mindustry.gen.Call
importPackage(Packages.mindustry.game)
importPackage(Packages.arc.func)
importPackage(Packages.mindustry.entities)
importPackage(Packages.mindustry.graphics.g3d)
importPackage(Packages.mindustry.ctype)
importPackage(Packages.mindustry.gen)
importPackage(Packages.mindustry.core)
importPackage(Packages.mindustry.world.blocks.storage)
importPackage(Packages.mindustry.ui.dialogs)
importPackage(Packages.arc.scene.ui)
importPackage(Packages.mindustry.world.blocks.defense.turrets)
importPackage(Packages.mindustry.world.blocks.distribution)
importPackage(Packages.mindustry.ui)
importPackage(Packages.mindustry.content)
importPackage(Packages.mindustry.world.blocks.liquid)
importPackage(Packages.arc.struct)
importPackage(Packages.arc.scene.ui.layout)
importPackage(Packages.mindustry.world.modules)
importPackage(Packages.arc.util)
importPackage(Packages.arc.graphics)
importPackage(Packages.mindustry.entities.def)
importPackage(Packages.mindustry.maps.generators)
importPackage(Packages.arc.scene.actions)
importPackage(Packages.mindustry.graphics)
importPackage(Packages.mindustry.entities.bullet)
importPackage(Packages.mindustry.world.blocks.legacy)
importPackage(Packages.mindustry.world.blocks.experimental)
importPackage(Packages.mindustry.editor)
importPackage(Packages.mindustry.type)
importPackage(Packages.arc.scene.ui)
importPackage(Packages.arc.math.geom)
importPackage(Packages.mindustry.game)
importPackage(Packages.mindustry.maps.filters)
importPackage(Packages.arc.struct)
importPackage(Packages.arc.scene.style)
importPackage(Packages.mindustry.ui.dialogs)
importPackage(Packages.mindustry.entities.comp)
importPackage(Packages.mindustry.world.blocks.defense.turrets)
importPackage(Packages.mindustry.async)
importPackage(Packages.mindustry.world.blocks.distribution)
importPackage(Packages.mindustry.world.blocks.environment)
importPackage(Packages.mindustry.world.blocks.campaign)
importPackage(Packages.mindustry.world.blocks.liquid)
importPackage(Packages.mindustry.ui)
importPackage(Packages.mindustry.world.blocks.production)
importPackage(Packages.mindustry.ai)
importPackage(Packages.mindustry.world.blocks.defense)
importPackage(Packages.mindustry.world.meta)
importPackage(Packages.mindustry.world.blocks.legacy)
importPackage(Packages.mindustry.world.blocks.units)
importPackage(Packages.arc.graphics)
importPackage(Packages.arc.func)
importPackage(Packages.mindustry.content)
importPackage(Packages.mindustry.world.blocks.power)
importPackage(Packages.mindustry.ui.layout)
importPackage(Packages.mindustry.world.blocks)
importPackage(Packages.arc.scene.event)
importPackage(Packages.mindustry.logic)
importPackage(Packages.arc.math)
importPackage(Packages.mindustry.world)
importPackage(Packages.mindustry.maps)
importPackage(Packages.mindustry.maps.generators)
importPackage(Packages.mindustry.world.meta.values)
importPackage(Packages.mindustry.entities)
importPackage(Packages.arc.util)
importPackage(Packages.mindustry.graphics)
importPackage(Packages.mindustry.world.modules)
importPackage(Packages.mindustry.world.blocks.sandbox)
importPackage(Packages.mindustry.input)
importPackage(Packages.mindustry.world.consumers)
importPackage(Packages.mindustry.ui.fragments)
importPackage(Packages.mindustry.ai.formations)
importPackage(Packages.mindustry.type)
importPackage(Packages.mindustry.world.blocks.production)
importPackage(Packages.arc.scene.event)
importPackage(Packages.arc.math)
importPackage(Packages.arc.scene.utils)
importPackage(Packages.mindustry.world.blocks.defense)
importPackage(Packages.mindustry.graphics.g3d)
importPackage(Packages.mindustry.world.meta)
importPackage(Packages.mindustry.world.blocks.payloads)
importPackage(Packages.mindustry.world)
importPackage(Packages.mindustry.async)
importPackage(Packages.arc.scene.style)
importPackage(Packages.mindustry.world.blocks)
importPackage(Packages.arc.math.geom)
importPackage(Packages.mindustry.ai)
importPackage(Packages.mindustry.maps.filters)
importPackage(Packages.arc.graphics.g2d)
importPackage(Packages.mindustry.ai.formations.patterns)
importPackage(Packages.mindustry.world.blocks.environment)
importPackage(Packages.mindustry)
importPackage(Packages.mindustry.entities.units)
importPackage(Packages.mindustry.ctype)
importPackage(Packages.arc.scene.ui.layout)
importPackage(Packages.mindustry.ai.types)
importPackage(Packages.mindustry.maps)
importPackage(Packages.mindustry.world.meta.values)
importPackage(Packages.mindustry.world.producers)
importPackage(Packages.mindustry.world.blocks.units)
importPackage(Packages.mindustry.ai.formations.patterns)
importPackage(Packages.arc.scene.utils)
importPackage(Packages.mindustry.ai.formations)
importPackage(Packages.mindustry.ui.fragments)
importPackage(Packages.mindustry.world.blocks.experimental)
importPackage(Packages.mindustry.world.blocks.storage)
importPackage(Packages.mindustry.audio)
importPackage(Packages.mindustry.ui.layout)
importPackage(Packages.mindustry.entities.bullet)
importPackage(Packages.mindustry.world.consumers)
importPackage(Packages.mindustry.core)
importPackage(Packages.arc.scene)
importPackage(Packages.mindustry.maps.planet)
importPackage(Packages.mindustry)
importPackage(Packages.arc)
importPackage(Packages.mindustry.world.blocks.logic)
importPackage(Packages.mindustry.world.blocks.payloads)
importPackage(Packages.mindustry.world.producers)
importPackage(Packages.arc.graphics.g2d)
importPackage(Packages.mindustry.maps.planet)
const PlayerIpUnbanEvent = Packages.mindustry.game.EventType.PlayerIpUnbanEvent
const PlayerIpBanEvent = Packages.mindustry.game.EventType.PlayerIpBanEvent
const PlayerUnbanEvent = Packages.mindustry.game.EventType.PlayerUnbanEvent
@@ -95,8 +97,7 @@ const PlayerBanEvent = Packages.mindustry.game.EventType.PlayerBanEvent
const PlayerLeave = Packages.mindustry.game.EventType.PlayerLeave
const PlayerConnect = Packages.mindustry.game.EventType.PlayerConnect
const PlayerJoin = Packages.mindustry.game.EventType.PlayerJoin
const MechChangeEvent = Packages.mindustry.game.EventType.MechChangeEvent
const ResizeEvent = Packages.mindustry.game.EventType.ResizeEvent
const UnitChangeEvent = Packages.mindustry.game.EventType.UnitChangeEvent
const UnitCreateEvent = Packages.mindustry.game.EventType.UnitCreateEvent
const UnitDestroyEvent = Packages.mindustry.game.EventType.UnitDestroyEvent
const BlockDestroyEvent = Packages.mindustry.game.EventType.BlockDestroyEvent
@@ -107,12 +108,19 @@ const ResearchEvent = Packages.mindustry.game.EventType.ResearchEvent
const UnlockEvent = Packages.mindustry.game.EventType.UnlockEvent
const StateChangeEvent = Packages.mindustry.game.EventType.StateChangeEvent
const TileChangeEvent = Packages.mindustry.game.EventType.TileChangeEvent
const WorldLoadEvent = Packages.mindustry.game.EventType.WorldLoadEvent
const GameOverEvent = Packages.mindustry.game.EventType.GameOverEvent
const TapConfigEvent = Packages.mindustry.game.EventType.TapConfigEvent
const TapEvent = Packages.mindustry.game.EventType.TapEvent
const DepositEvent = Packages.mindustry.game.EventType.DepositEvent
const WithdrawEvent = Packages.mindustry.game.EventType.WithdrawEvent
const SectorCaptureEvent = Packages.mindustry.game.EventType.SectorCaptureEvent
const ZoneConfigureCompleteEvent = Packages.mindustry.game.EventType.ZoneConfigureCompleteEvent
const ZoneRequireCompleteEvent = Packages.mindustry.game.EventType.ZoneRequireCompleteEvent
const PlayerChatEvent = Packages.mindustry.game.EventType.PlayerChatEvent
const CommandIssueEvent = Packages.mindustry.game.EventType.CommandIssueEvent
const LaunchItemEvent = Packages.mindustry.game.EventType.LaunchItemEvent
const WorldLoadEvent = Packages.mindustry.game.EventType.WorldLoadEvent
const ClientLoadEvent = Packages.mindustry.game.EventType.ClientLoadEvent
const BlockInfoEvent = Packages.mindustry.game.EventType.BlockInfoEvent
const CoreItemDeliverEvent = Packages.mindustry.game.EventType.CoreItemDeliverEvent
const TurretAmmoDeliverEvent = Packages.mindustry.game.EventType.TurretAmmoDeliverEvent
@@ -123,17 +131,13 @@ const PlayEvent = Packages.mindustry.game.EventType.PlayEvent
const DisposeEvent = Packages.mindustry.game.EventType.DisposeEvent
const ContentReloadEvent = Packages.mindustry.game.EventType.ContentReloadEvent
const ServerLoadEvent = Packages.mindustry.game.EventType.ServerLoadEvent
const ClientLoadEvent = Packages.mindustry.game.EventType.ClientLoadEvent
const ClientCreateEvent = Packages.mindustry.game.EventType.ClientCreateEvent
const SaveLoadEvent = Packages.mindustry.game.EventType.SaveLoadEvent
const ZoneConfigureCompleteEvent = Packages.mindustry.game.EventType.ZoneConfigureCompleteEvent
const ZoneRequireCompleteEvent = Packages.mindustry.game.EventType.ZoneRequireCompleteEvent
const PlayerChatEvent = Packages.mindustry.game.EventType.PlayerChatEvent
const CommandIssueEvent = Packages.mindustry.game.EventType.CommandIssueEvent
const MapPublishEvent = Packages.mindustry.game.EventType.MapPublishEvent
const MapMakeEvent = Packages.mindustry.game.EventType.MapMakeEvent
const LaunchItemEvent = Packages.mindustry.game.EventType.LaunchItemEvent
const ResizeEvent = Packages.mindustry.game.EventType.ResizeEvent
const LaunchEvent = Packages.mindustry.game.EventType.LaunchEvent
const LoseEvent = Packages.mindustry.game.EventType.LoseEvent
const WinEvent = Packages.mindustry.game.EventType.WinEvent
const TurnEvent = Packages.mindustry.game.EventType.TurnEvent
const Trigger = Packages.mindustry.game.EventType.Trigger
Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 B

After

Width:  |  Height:  |  Size: 761 B

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 KiB

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 KiB

After

Width:  |  Height:  |  Size: 915 KiB

+7 -7
View File
@@ -65,18 +65,14 @@ public class Vars implements Loadable{
public static final Array<String> defaultServers = Array.with();
/** maximum distance between mine and core that supports automatic transferring */
public static final float mineTransferRange = 220f;
/** whether to enable editing of units in the editor */
public static final boolean enableUnitEditing = false;
/** max chat message length */
public static final int maxTextLength = 150;
/** max player name length in bytes */
public static final int maxNameLength = 40;
/** displayed item size when ingame, TODO remove. */
/** displayed item size when ingame. */
public static final float itemSize = 5f;
/** extra padding around the world; units outside this bound will begin to self-destruct. */
public static final float worldBounds = 100f;
/** units outside of this bound will simply die instantly */
public static final float finalWorldBounds = worldBounds + 500;
/** units outside of this bound will die instantly */
public static final float finalWorldBounds = 500;
/** mining range for manual miners */
public static final float miningRange = 70f;
/** range for building */
@@ -85,6 +81,8 @@ public class Vars implements Loadable{
public static final float turnDuration = 5 * Time.toMinutes;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.05f;
/** launch animation duration */
public static final float launchDuration = 140f;
/** tile used in certain situations, instead of null */
public static Tile emptyTile;
/** for map generator dialog */
@@ -180,6 +178,7 @@ public class Vars implements Loadable{
public static BeControl becontrol;
public static AsyncCore asyncCore;
public static TeamIndexProcess teamIndex;
public static BaseRegistry bases;
public static Universe universe;
public static World world;
@@ -252,6 +251,7 @@ public class Vars implements Loadable{
spawner = new WaveSpawner();
indexer = new BlockIndexer();
pathfinder = new Pathfinder();
bases = new BaseRegistry();
state = new GameState();
data = new GlobalData();
+97
View File
@@ -0,0 +1,97 @@
package mindustry.ai;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.world.*;
import static mindustry.Vars.world;
public class Astar{
public static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);
private static final Array<Tile> out = new Array<>();
private static final PQueue<Tile> queue = new PQueue<>(200 * 200 / 4, (a, b) -> 0);
private static final IntFloatMap costs = new IntFloatMap();
private static byte[][] rotations;
public static Array<Tile> pathfind(Tile from, Tile to, TileHueristic th, Boolf<Tile> passable){
return pathfind(from.x, from.y, to.x, to.y, th, manhattan, passable);
}
public static Array<Tile> pathfind(int startX, int startY, int endX, int endY, TileHueristic th, Boolf<Tile> passable){
return pathfind(startX, startY, endX, endY, th, manhattan, passable);
}
public static Array<Tile> pathfind(int startX, int startY, int endX, int endY, TileHueristic th, DistanceHeuristic dh, Boolf<Tile> passable){
Tiles tiles = world.tiles;
Tile start = tiles.getn(startX, startY);
Tile end = tiles.getn(endX, endY);
GridBits closed = new GridBits(tiles.width, tiles.height);
costs.clear();
queue.clear();
queue.comparator = Structs.comparingFloat(a -> costs.get(a.pos(), 0f) + dh.cost(a.x, a.y, end.x, end.y));
queue.add(start);
if(rotations == null || rotations.length != world.width() || rotations[0].length != world.height()){
rotations = new byte[world.width()][world.height()];
}
boolean found = false;
while(!queue.empty()){
Tile next = queue.poll();
float baseCost = costs.get(next.pos(), 0f);
if(next == end){
found = true;
break;
}
closed.set(next.x, next.y);
for(Point2 point : Geometry.d4){
int newx = next.x + point.x, newy = next.y + point.y;
if(Structs.inBounds(newx, newy, tiles.width, tiles.height)){
Tile child = tiles.getn(newx, newy);
if(passable.get(child)){
float newCost = th.cost(next, child) + baseCost;
if(!closed.get(child.x, child.y)){
closed.set(child.x, child.y);
rotations[child.x][child.y] = child.relativeTo(next.x, next.y);
costs.put(child.pos(), newCost);
queue.add(child);
}
}
}
}
}
out.clear();
if(!found) return out;
Tile current = end;
while(current != start){
out.add(current);
byte rot = rotations[current.x][current.y];
current = tiles.getn(current.x + Geometry.d4x[rot], current.y + Geometry.d4y[rot]);
}
out.reverse();
return out;
}
public interface DistanceHeuristic{
float cost(int x1, int y1, int x2, int y2);
}
public interface TileHueristic{
float cost(Tile tile);
default float cost(Tile from, Tile tile){
return cost(tile);
}
}
}
+164
View File
@@ -0,0 +1,164 @@
package mindustry.ai;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.BaseRegistry.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.game.Schematic.*;
import mindustry.game.Teams.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static mindustry.Vars.*;
public class BaseAI{
private static final Vec2 axis = new Vec2(), rotator = new Vec2();
private static final float correctPercent = 0.5f;
private static final float step = 5;
private static final int attempts = 5;
private static final float emptyChance = 0.01f;
private static int correct = 0, incorrect = 0;
private int lastX, lastY, lastW, lastH;
private boolean triedWalls;
TeamData data;
Interval timer = new Interval();
public BaseAI(TeamData data){
this.data = data;
}
public void update(){
//only schedule when there's something to build.
if(data.blocks.isEmpty() && timer.get(step)){
if(!triedWalls){
tryWalls();
triedWalls = true;
}
for(int i = 0; i < attempts; i++){
int range = 150;
CoreEntity core = data.cores.random();
Tmp.v1.rnd(Mathf.random(range));
int wx = (int)(core.tileX() + Tmp.v1.x), wy = (int)(core.tileY() + Tmp.v1.y);
Tile tile = world.tiles.getc(wx, wy);
Array<BasePart> parts = null;
//pick a completely random base part, and place it a random location
//((yes, very intelligent))
if(tile.drop() != null && Vars.bases.forResource(tile.drop()).any()){
parts = Vars.bases.forResource(tile.drop());
}else if(Mathf.chance(emptyChance)){
parts = Vars.bases.parts;
}
if(parts != null){
BasePart part = parts.random();
if(tryPlace(part, tile.x, tile.y)){
break;
}
}
}
}
}
boolean tryPlace(BasePart part, int x, int y){
int rotation = Mathf.range(2);
axis.set((int)(part.schematic.width / 2f), (int)(part.schematic.height / 2f));
Schematic result = Schematics.rotate(part.schematic, rotation);
int rotdeg = rotation*90;
rotator.set(part.centerX, part.centerY).rotateAround(axis, rotdeg);
//bottom left schematic corner
int cx = x - (int)rotator.x;
int cy = y - (int)rotator.y;
//chekc valid placeability
for(Stile tile : result.tiles){
int realX = tile.x + cx, realY = tile.y + cy;
if(!Build.validPlace(tile.block, data.team, realX, realY, tile.rotation)){
return false;
}
}
//make sure at least X% of resource requirements are met
correct = incorrect = 0;
if(part.required instanceof Item){
for(Stile tile : result.tiles){
if(tile.block instanceof Drill){
tile.block.iterateTaken(tile.x + cx, tile.y + cy, (ex, ey) -> {
Tile res = world.rawTile(ex, ey);
if(res.drop() == part.required){
correct ++;
}else{
incorrect ++;
}
});
}
}
}
//fail if not enough fit requirements
if((float)correct / incorrect < correctPercent){
return false;
}
//queue it
for(Stile tile : result.tiles){
data.blocks.add(new BlockPlan(cx + tile.x, cy + tile.y, tile.rotation, tile.block.id, tile.config));
}
lastX = cx - 1;
lastY = cy - 1;
lastW = result.width + 2;
lastH = result.height + 2;
triedWalls = false;
return true;
}
void tryWalls(){
Block wall = Blocks.copperWall;
Tile spawn = state.rules.defaultTeam.core() != null ? state.rules.defaultTeam.core().tile : data.team.core().tile;
for(int wx = lastX; wx <= lastX + lastW; wx++){
for(int wy = lastY; wy <= lastY + lastH; wy++){
Tile tile = world.tile(wx, wy);
if(tile == null || !tile.block().alwaysReplace) continue;
boolean any = false;
for(Point2 p : Geometry.d8){
if(Angles.angleDist(Angles.angle(p.x, p.y), spawn.angleTo(tile)) > 70){
continue;
}
Tile o = world.tile(tile.x + p.x, tile.y + p.y);
if(o != null && o.team() == data.team && !(o.block() instanceof Wall)){
any = true;
break;
}
}
if(any && Build.validPlace(wall, data.team, tile.x, tile.y, 0)){
data.blocks.add(new BlockPlan(tile.x, tile.y, (short)0, wall.id, null));
}
}
}
}
}
+121
View File
@@ -0,0 +1,121 @@
package mindustry.ai;
import arc.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.game.*;
import mindustry.game.Schematic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.production.*;
import mindustry.world.blocks.sandbox.*;
import mindustry.world.blocks.storage.*;
import mindustry.world.meta.*;
import java.io.*;
import static mindustry.Vars.tilesize;
public class BaseRegistry{
public Array<BasePart> cores = new Array<>();
public Array<BasePart> parts = new Array<>();
public ObjectMap<Content, Array<BasePart>> reqParts = new ObjectMap<>();
public Array<BasePart> forResource(Content item){
return reqParts.get(item, Array::new);
}
public void load(){
cores.clear();
parts.clear();
reqParts.clear();
String[] names = Core.files.internal("basepartnames").readString().split("\n");
for(String name : names){
try{
Schematic schem = Schematics.read(Core.files.internal("baseparts/" + name));
BasePart part = new BasePart(schem);
Tmp.v1.setZero();
int drills = 0;
for(Stile tile : schem.tiles){
//keep track of core type
if(tile.block instanceof CoreBlock){
part.core = tile.block;
}
//save the required resource based on item source - multiple sources are not allowed
if(tile.block instanceof ItemSource){
Item config = (Item)tile.config;
if(config != null) part.required = config;
}
//same for liquids - this is not used yet
if(tile.block instanceof LiquidSource){
Liquid config = (Liquid)tile.config;
if(config != null) part.required = config;
}
//calculate averages
if(tile.block instanceof Drill || tile.block instanceof Pump){
Tmp.v1.add(tile.x*tilesize + tile.block.offset(), tile.y*tilesize + tile.block.offset());
drills ++;
}
}
schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly);
part.tier = schem.tiles.sumf(s -> s.block.buildCost / s.block.buildCostMultiplier);
if(part.core != null){
cores.add(part);
}else if(part.required == null){
parts.add(part);
}
if(drills > 0){
Tmp.v1.scl(1f / drills).scl(1f / tilesize);
part.centerX = (int)Tmp.v1.x;
part.centerY = (int)Tmp.v1.y;
}else{
part.centerX = part.schematic.width/2;
part.centerY = part.schematic.height/2;
}
if(part.required != null) reqParts.get(part.required, Array::new).add(part);
}catch(IOException e){
throw new RuntimeException(e);
}
}
cores.sort(Structs.comps(Structs.comparingFloat(b -> b.core.health), Structs.comparingFloat(b -> b.tier)));
parts.sort();
reqParts.each((key, arr) -> arr.sort());
}
public static class BasePart implements Comparable<BasePart>{
public final Schematic schematic;
//offsets for drills
public int centerX, centerY;
public @Nullable Content required;
public @Nullable Block core;
//total build cost
public float tier;
public BasePart(Schematic schematic){
this.schematic = schematic;
}
@Override
public int compareTo(BasePart other){
return Float.compare(tier, other.tier);
}
}
}
+8 -10
View File
@@ -34,15 +34,15 @@ public class BlockIndexer{
/** Maps each team ID to a quarant. A quadrant is a grid of bits, where each bit is set if and only if there is a block of that team in that quadrant. */
private GridBits[] structQuadrants;
/** Stores all damaged tile entities by team. */
private TileArray[] damagedTiles = new TileArray[Team.all().length];
private TileArray[] damagedTiles = new TileArray[Team.all.length];
/** All ores available on this map. */
private ObjectSet<Item> allOres = new ObjectSet<>();
/** Stores teams that are present here as tiles. */
private Array<Team> activeTeams = new Array<>();
/** Maps teams to a map of flagged tiles by flag. */
private TileArray[][] flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
private TileArray[][] flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
/** Max units by team. */
private int[] unitCaps = new int[Team.all().length];
private int[] unitCaps = new int[Team.all.length];
/** Maps tile positions to their last known tile index data. */
private IntMap<TileIndex> typeMap = new IntMap<>();
/** Empty set used for returning. */
@@ -69,9 +69,9 @@ public class BlockIndexer{
Events.on(WorldLoadEvent.class, event -> {
scanOres.clear();
scanOres.addAll(Item.getAllOres());
damagedTiles = new TileArray[Team.all().length];
flagMap = new TileArray[Team.all().length][BlockFlag.all.length];
unitCaps = new int[Team.all().length];
damagedTiles = new TileArray[Team.all.length];
flagMap = new TileArray[Team.all.length][BlockFlag.all.length];
unitCaps = new int[Team.all.length];
for(int i = 0; i < flagMap.length; i++){
for(int j = 0; j < BlockFlag.all.length; j++){
@@ -84,7 +84,7 @@ public class BlockIndexer{
ores = null;
//create bitset for each team type that contains each quadrant
structQuadrants = new GridBits[Team.all().length];
structQuadrants = new GridBits[Team.all.length];
for(Tile tile : world.tiles){
process(tile);
@@ -175,7 +175,6 @@ public class BlockIndexer{
int ty = world.toTile(wy);
int tileRange = (int)(range / tilesize + 1);
intSet.clear();
boolean any = false;
for(int x = -tileRange + tx; x <= tileRange + tx; x++){
@@ -186,10 +185,9 @@ public class BlockIndexer{
if(other == null) continue;
if(other.team() == team && !intSet.contains(other.pos()) && pred.get(other)){
if(other.team() == team && pred.get(other) && intSet.add(other.pos())){
cons.get(other);
any = true;
intSet.add(other.pos());
}
}
}
+161 -53
View File
@@ -17,23 +17,27 @@ import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Pathfinder implements Runnable{
private static final long maxUpdate = Time.millisToNanos(4);
private static final long maxUpdate = Time.millisToNanos(6);
private static final int updateFPS = 60;
private static final int updateInterval = 1000 / updateFPS;
private static final int impassable = -1;
private static final int fieldTimeout = 1000 * 60 * 2;
/** tile data, see PathTileStruct */
private int[][] tiles;
/** unordered array of path data for iteration only. DO NOT iterate ot access this in the main thread. */
private Array<PathData> list = new Array<>();
/** Maps teams + flags to a valid path to get to that flag for that team. */
private PathData[][] pathMap = new PathData[Team.all().length][PathTarget.all.length];
/** Grid map of created path data that should not be queued again. */
private GridBits created = new GridBits(Team.all().length, PathTarget.all.length);
/** unordered array of path data for iteration only. DO NOT iterate or access this in the main thread. */
private Array<Flowfield> threadList = new Array<>(), mainList = new Array<>();
/** Maps team ID and target to to a flowfield.*/
private ObjectMap<PathTarget, Flowfield>[] fieldMap = new ObjectMap[Team.all.length];
/** Used field maps. */
private ObjectSet<PathTarget>[] fieldMapUsed = new ObjectSet[Team.all.length];
/** handles task scheduling on the update thread. */
private TaskQueue queue = new TaskQueue();
/** current pathfinding thread */
/** Stores path target for a position. Main thread only.*/
private ObjectMap<Position, PathTarget> targetCache = new ObjectMap<>();
/** Current pathfinding thread */
private @Nullable Thread thread;
private IntArray tmpArray = new IntArray();
public Pathfinder(){
Events.on(WorldLoadEvent.class, event -> {
@@ -41,16 +45,18 @@ public class Pathfinder implements Runnable{
//reset and update internal tile array
tiles = new int[world.width()][world.height()];
pathMap = new PathData[Team.all().length][PathTarget.all.length];
created = new GridBits(Team.all().length, PathTarget.all.length);
list = new Array<>();
fieldMap = new ObjectMap[Team.all.length];
fieldMapUsed = new ObjectSet[Team.all.length];
targetCache = new ObjectMap<>();
threadList = new Array<>();
mainList = new Array<>();
for(Tile tile : world.tiles){
tiles[tile.x][tile.y] = packTile(tile);
}
//special preset which may help speed things up; this is optional
preloadPath(state.rules.waveTeam, PathTarget.enemyCores);
preloadPath(state.rules.waveTeam, FlagTarget.enemyCores);
start();
});
@@ -62,7 +68,7 @@ public class Pathfinder implements Runnable{
/** Packs a tile into its internal representation. */
private int packTile(Tile tile){
return PathTile.get(tile.cost, tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f);
return PathTile.get(tile.cost, tile.getTeamID(), !tile.solid() && tile.floor().drownTime <= 0f, !tile.solid() && tile.floor().isLiquid);
}
/** Starts or restarts the pathfinding thread. */
@@ -80,12 +86,13 @@ public class Pathfinder implements Runnable{
queue.clear();
}
public int debugValue(Team team, int x, int y){
if(pathMap[team.id][PathTarget.enemyCores.ordinal()] == null) return 0;
return pathMap[team.id][PathTarget.enemyCores.ordinal()].weights[x][y];
}
//public int debugValue(Team team, int x, int y){
// if(pathMap[team.id][FlagTarget.enemyCores.ordinal()] == null) return 0;
// return pathMap[team.id][FlagTarget.enemyCores.ordinal()].weights[x][y];
//}
/** Update a tile in the internal pathfinding grid. Causes a complete pathfinding reclaculation. */
/** Update a tile in the internal pathfinding grid.
* Causes a complete pathfinding reclaculation. Main thread only. */
public void updateTile(Tile tile){
if(net.client()) return;
@@ -98,19 +105,17 @@ public class Pathfinder implements Runnable{
});
//can't iterate through array so use the map, which should not lead to problems
for(PathData[] arr : pathMap){
for(PathData path : arr){
if(path != null){
synchronized(path.targets){
path.targets.clear();
path.target.getTargets(path.team, path.targets);
}
for(Flowfield path : mainList){
if(path != null){
synchronized(path.targets){
path.targets.clear();
path.target.getPositions(path.team, path.targets);
}
}
}
queue.post(() -> {
for(PathData data : list){
for(Flowfield data : threadList){
updateTargets(data, x, y);
}
});
@@ -126,8 +131,31 @@ public class Pathfinder implements Runnable{
queue.run();
//total update time no longer than maxUpdate
for(PathData data : list){
updateFrontier(data, maxUpdate / list.size);
for(Flowfield data : threadList){
updateFrontier(data, maxUpdate / threadList.size);
//remove flowfields that have 'timed out' so they can be garbage collected and no longer waste space
if(data.target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
//make sure it doesn't get removed twice
data.lastUpdateTime = Time.millis();
Team team = data.team;
Core.app.post(() -> {
//remove its used state
if(fieldMap[team.uid] != null){
fieldMap[team.uid].remove(data.target);
fieldMapUsed[team.uid].remove(data.target);
}
//remove from main thread list
mainList.remove(data);
});
queue.post(() -> {
//remove from this thread list with a delay
threadList.remove(data);
});
}
}
try{
@@ -142,23 +170,50 @@ public class Pathfinder implements Runnable{
}
}
public Tile getTargetTile(Tile tile, Team team, Position target){
return getTargetTile(tile, team, getTarget(target));
}
/** Gets next tile to travel to. Main thread only. */
public Tile getTargetTile(Tile tile, Team team, PathTarget target){
if(tile == null) return null;
PathData data = pathMap[team.id][target.ordinal()];
if(fieldMap[team.uid] == null){
fieldMap[team.uid] = new ObjectMap<>();
fieldMapUsed[team.uid] = new ObjectSet<>();
}
Flowfield data = fieldMap[team.uid].get(target);
if(data == null){
//if this combination is not found, create it on request
if(!created.get(team.id, target.ordinal())){
created.set(team.id, target.ordinal());
if(fieldMapUsed[team.uid].add(target)){
//grab targets since this is run on main thread
IntArray targets = target.getTargets(team, new IntArray());
IntArray targets = target.getPositions(team, new IntArray());
queue.post(() -> createPath(team, target, targets));
}
return tile;
}
//if refresh rate is positive, queue a refresh
if(target.refreshRate() > 0 && Time.timeSinceMillis(data.lastUpdateTime) > target.refreshRate()){
data.lastUpdateTime = Time.millis();
tmpArray.clear();
data.target.getPositions(data.team, tmpArray);
synchronized(data.targets){
//make sure the position actually changed
if(!(data.targets.size == 1 && tmpArray.size == 1 && data.targets.first() == tmpArray.first())){
data.targets.clear();
data.target.getPositions(data.team, data.targets);
//queue an update
queue.post(() -> updateTargets(data));
}
}
}
int[][] values = data.weights;
int value = values[tile.x][tile.y];
@@ -182,6 +237,10 @@ public class Pathfinder implements Runnable{
return current;
}
private PathTarget getTarget(Position position){
return targetCache.get(position, () -> new PositionTarget(position));
}
/** @return whether a tile can be passed through by this team. Pathfinding thread only. */
private boolean passable(int x, int y, Team team){
int tile = tiles[x][y];
@@ -192,7 +251,7 @@ public class Pathfinder implements Runnable{
* Clears the frontier, increments the search and sets up all flow sources.
* This only occurs for active teams.
*/
private void updateTargets(PathData path, int x, int y){
private void updateTargets(Flowfield path, int x, int y){
if(!Structs.inBounds(x, y, path.weights)) return;
if(path.weights[x][y] == 0){
@@ -208,10 +267,18 @@ public class Pathfinder implements Runnable{
path.weights[x][y] = impassable;
}
//increment search, clear frontier
path.search++;
//clear frontier to prevent contamination
path.frontier.clear();
updateTargets(path);
}
/** Increments the search and sets up flow sources. Does not change the frontier. */
private void updateTargets(Flowfield path){
//increment search, but do not clear the frontier
path.search++;
synchronized(path.targets){
//add targets
for(int i = 0; i < path.targets.size; i++){
@@ -219,25 +286,33 @@ public class Pathfinder implements Runnable{
int tx = Point2.x(pos), ty = Point2.y(pos);
path.weights[tx][ty] = 0;
path.searches[tx][ty] = (short)path.search;
path.searches[tx][ty] = path.search;
path.frontier.addFirst(pos);
}
}
}
private void preloadPath(Team team, PathTarget target){
updateFrontier(createPath(team, target, target.getTargets(team, new IntArray())), -1);
updateFrontier(createPath(team, target, target.getPositions(team, new IntArray())), -1);
}
/**
* Created a new flowfield that aims to get to a certain target for a certain team.
* Pathfinding thread only.
*/
private PathData createPath(Team team, PathTarget target, IntArray targets){
PathData path = new PathData(team, target, world.width(), world.height());
private Flowfield createPath(Team team, PathTarget target, IntArray targets){
Flowfield path = new Flowfield(team, target, world.width(), world.height());
path.lastUpdateTime = Time.millis();
list.add(path);
pathMap[team.id][target.ordinal()] = path;
threadList.add(path);
//add to main thread's list of paths
Core.app.post(() -> {
mainList.add(path);
if(fieldMap[team.uid] != null){
fieldMap[team.uid].put(target, path);
}
});
//grab targets from passed array
synchronized(path.targets){
@@ -263,7 +338,7 @@ public class Pathfinder implements Runnable{
}
/** Update the frontier for a path. Pathfinding thread only. */
private void updateFrontier(PathData path, long nsToRun){
private void updateFrontier(Flowfield path, long nsToRun){
long start = Time.nanos();
while(path.frontier.size > 0 && (nsToRun < 0 || Time.timeSinceNanos(start) <= nsToRun)){
@@ -295,7 +370,7 @@ public class Pathfinder implements Runnable{
}
/** A path target defines a set of targets for a path. */
public enum PathTarget{
public enum FlagTarget implements PathTarget{
enemyCores((team, out) -> {
for(Tile other : indexer.getEnemy(team, BlockFlag.core)){
out.add(other.pos());
@@ -314,23 +389,54 @@ public class Pathfinder implements Runnable{
}
});
public static final PathTarget[] all = values();
public static final FlagTarget[] all = values();
private final Cons2<Team, IntArray> targeter;
PathTarget(Cons2<Team, IntArray> targeter){
FlagTarget(Cons2<Team, IntArray> targeter){
this.targeter = targeter;
}
/** Get targets. This must run on the main thread. */
public IntArray getTargets(Team team, IntArray out){
@Override
public IntArray getPositions(Team team, IntArray out){
targeter.get(team, out);
return out;
}
@Override
public int refreshRate(){
return 0;
}
}
public static class PositionTarget implements PathTarget{
public final Position position;
public PositionTarget(Position position){
this.position = position;
}
@Override
public IntArray getPositions(Team team, IntArray out){
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
return out;
}
@Override
public int refreshRate(){
return 900;
}
}
public interface PathTarget{
/** Gets targets to pathfind towards. This must run on the main thread. */
IntArray getPositions(Team team, IntArray out);
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
int refreshRate();
}
/** Data for a specific flow field to some set of destinations. */
static class PathData{
static class Flowfield{
/** Team this path is for. */
final Team team;
/** Flag that is being targeted. */
@@ -338,20 +444,22 @@ public class Pathfinder implements Runnable{
/** costs of getting to a specific tile */
final int[][] weights;
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
final short[][] searches;
final int[][] searches;
/** search frontier, these are Pos objects */
final IntQueue frontier = new IntQueue();
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
final IntArray targets = new IntArray();
/** current search ID */
int search = 1;
/** last updated time */
long lastUpdateTime;
PathData(Team team, PathTarget target, int width, int height){
Flowfield(Team team, PathTarget target, int width, int height){
this.team = team;
this.target = target;
this.weights = new int[width][height];
this.searches = new short[width][height];
this.searches = new int[width][height];
this.frontier.ensureCapacity((width + height) * 3);
}
}
@@ -363,9 +471,9 @@ public class Pathfinder implements Runnable{
short cost;
//team of block, if applicable (0 by default)
byte team;
//type of target; TODO remove
//byte type;
//whether it's viable to pass this block
boolean passable;
//whether it's viable to pass this block through water
boolean passableWater;
}
}
@@ -0,0 +1,83 @@
package mindustry.ai.types;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.entities.units.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.BuildBlock.*;
import static mindustry.Vars.*;
public class BuilderAI extends AIController{
@Override
public void update(){
Builderc builder = (Builderc)unit;
if(builder.moving()){
builder.lookAt(builder.vel().angle());
}
//approach request if building
if(builder.buildRequest() != null){
BuildRequest req = builder.buildRequest();
boolean valid =
(req.tile().entity instanceof BuildEntity && req.tile().<BuildEntity>ent().cblock == req.block) ||
(req.breaking ?
Build.validBreak(unit.team(), req.x, req.y) :
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));
if(valid){
//move toward the request
moveTo(req.tile(), buildingRange - 20f);
}else{
//discard invalid request
builder.requests().removeFirst();
}
}else{
//find new request
if(!unit.team().data().blocks.isEmpty()){
Queue<BlockPlan> blocks = unit.team().data().blocks;
BlockPlan block = blocks.first();
//check if it's already been placed
if(world.tile(block.x, block.y) != null && world.tile(block.x, block.y).block().id == block.block){
blocks.removeFirst();
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
//add build request.
BuildRequest req = new BuildRequest(block.x, block.y, block.rotation, content.block(block.block));
if(block.config != null){
req.configure(block.config);
}
builder.addBuild(req);
}else{
//shift head of queue to tail, try something else next time
blocks.removeFirst();
blocks.addLast(block);
}
}else{
//TODO implement AI base building
}
}
}
protected void moveTo(Position target, float circleLength){
vec.set(target).sub(unit);
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
vec.setLength(unit.type().speed * Time.delta() * length);
if(length < -0.5f){
vec.rotate(180f);
}else if(length < 0){
vec.setZero();
}
unit.moveAt(vec);
}
}
+3 -1
View File
@@ -11,7 +11,7 @@ public class FlyingAI extends AIController{
@Override
public void update(){
if(!unit.vel().isZero(0.01f)){
if(unit.moving()){
unit.rotation(unit.vel().angle());
}
@@ -46,6 +46,8 @@ public class FlyingAI extends AIController{
unit.controlWeapons(shoot, shoot);
}
//TODO clean up
protected void circle(float circleLength){
circle(circleLength, unit.type().speed);
}
+26 -6
View File
@@ -1,6 +1,8 @@
package mindustry.ai.types;
import arc.math.geom.*;
import arc.util.ArcAnnotate.*;
import mindustry.*;
import mindustry.ai.formations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -9,7 +11,7 @@ public class FormationAI extends AIController implements FormationMember{
public Unitc leader;
private Vec3 target = new Vec3();
private Formation formation;
private @Nullable Formation formation;
public FormationAI(Unitc leader, Formation formation){
this.leader = leader;
@@ -23,19 +25,37 @@ public class FormationAI extends AIController implements FormationMember{
@Override
public void update(){
if(leader.dead()){
unit.resetController();
return;
}
unit.controlWeapons(leader.isRotate(), leader.isShooting());
// unit.moveAt(Tmp.v1.set(deltaX, deltaY).limit(unit.type().speed));
if(leader.isShooting()){
unit.aimLook(leader.aimX(), leader.aimY());
}else{
unit.lookAt(leader.rotation());
if(!unit.vel().isZero(0.001f)){
// unit.lookAt(unit.vel().angle());
if(!unit.moving()){
unit.lookAt(unit.vel().angle());
}else{
unit.lookAt(leader.rotation());
}
}
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
Vec2 realtarget = vec.set(target);
if(unit.isGrounded() && Vars.world.raycast(unit.tileX(), unit.tileY(), leader.tileX(), leader.tileY(), Vars.world::solid)){
realtarget.set(Vars.pathfinder.getTargetTile(unit.tileOn(), unit.team(), leader));
}
unit.moveAt(realtarget.sub(unit).limit(unit.type().speed));
}
@Override
public void removed(Unitc unit){
if(formation != null){
formation.removeMember(this);
}
}
@Override
+9 -15
View File
@@ -1,6 +1,5 @@
package mindustry.ai.types;
import arc.util.*;
import mindustry.ai.Pathfinder.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
@@ -17,11 +16,6 @@ public class GroundAI extends AIController{
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
//TODO this is hacky, cleanup
if(unit instanceof Mechc && unit.moving()){
unit.lookAt(((Mechc)unit).baseRotation());
}
}
if(retarget()){
@@ -31,14 +25,12 @@ public class GroundAI extends AIController{
Tilec core = unit.closestEnemyCore();
if(core != null){
float dst = unit.dst(core);
if(dst < unit.range() / 1.1f){
if(unit.within(core,unit.range() / 1.1f)){
target = core;
}
if(dst > unit.range() * 0.5f){
moveToCore(PathTarget.enemyCores);
if(!unit.within(core, unit.range() * 0.5f)){
moveToCore(FlagTarget.enemyCores);
}
}
@@ -51,19 +43,21 @@ public class GroundAI extends AIController{
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
}else if(unit.moving()){
unit.lookAt(unit.vel().angle());
}
unit.controlWeapons(rotate, shoot);
}
protected void moveToCore(PathTarget path){
protected void moveToCore(FlagTarget path){
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, unit.team(), path);
if(tile == targetTile) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed * Time.delta()));
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
protected void moveAwayFromCore(){
@@ -86,11 +80,11 @@ public class GroundAI extends AIController{
Tile tile = unit.tileOn();
if(tile == null) return;
Tile targetTile = pathfinder.getTargetTile(tile, enemy, PathTarget.enemyCores);
Tile targetTile = pathfinder.getTargetTile(tile, enemy, FlagTarget.enemyCores);
Tilec core = unit.closestCore();
if(tile == targetTile || core == null || unit.within(core, 120f)) return;
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed * Time.delta()));
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
}
}
@@ -0,0 +1,70 @@
package mindustry.ai.types;
import mindustry.*;
import mindustry.ai.Pathfinder.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.*;
public class SuicideAI extends GroundAI{
static boolean blockedByBlock;
@Override
public void update(){
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
target = null;
}
if(retarget()){
targetClosest();
}
Tilec core = unit.closestEnemyCore();
boolean rotate = false, shoot = false;
if(!Units.invalidateTarget(target, unit, unit.range())){
rotate = true;
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
(target instanceof Tilec ? ((Tilec)target).block().size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
if(unit.type().hasWeapons()){
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
}
blockedByBlock = false;
//raycast for target
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.entity == target) return false;
if(tile != null && tile.entity != null && tile.entity.team() != unit.team()){
blockedByBlock = true;
return true;
}else{
return tile == null || tile.solid();
}
});
//shoot when there's an enemy block in the way
if(blockedByBlock){
shoot = true;
}
if(!blocked){
//move towards target directly
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
}
}else{
if(core != null){
moveToCore(FlagTarget.enemyCores);
}
if(unit.moving()) unit.lookAt(unit.vel().angle());
}
unit.controlWeapons(rotate, shoot);
}
}
@@ -9,8 +9,8 @@ import java.util.*;
/** Creates quadtrees per unit team. */
public class TeamIndexProcess implements AsyncProcess{
private QuadTree<Unitc>[] trees = new QuadTree[Team.all().length];
private int[] counts = new int[Team.all().length];
private QuadTree<Unitc>[] trees = new QuadTree[Team.all.length];
private int[] counts = new int[Team.all.length];
public QuadTree<Unitc> tree(Team team){
if(trees[team.uid] == null) trees[team.uid] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
@@ -28,14 +28,14 @@ public class TeamIndexProcess implements AsyncProcess{
@Override
public void reset(){
counts = new int[Team.all().length];
trees = new QuadTree[Team.all().length];
counts = new int[Team.all.length];
trees = new QuadTree[Team.all.length];
}
@Override
public void begin(){
for(Team team : Team.all()){
for(Team team : Team.all){
if(trees[team.uid] != null){
trees[team.uid].clear();
}
+1 -1
View File
@@ -16,7 +16,7 @@ public class LoopControl{
float baseVol = sound.calcFalloff(pos.getX(), pos.getY());
float vol = baseVol * volume;
SoundData data = sounds.getOr(sound, SoundData::new);
SoundData data = sounds.get(sound, SoundData::new);
data.volume += vol;
data.volume = Mathf.clamp(data.volume, 0f, 1f);
data.total += baseVol;

Some files were not shown because too many files have changed in this diff Show More