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

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 186 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
mschxœ-ŒÑ
à E¯U»BÊ>ć}³aZÕ<E280B9>ýýv ¹9¹$Á„›‚Ù|&¨Çˆ{(ûNÕ}|J.ùú"èµ̱QvGé5}£ž}Á­5¦„y<E2809E>G«ñÙ[©˜BÙÞôe. <C3AA>SIÓPÂÆŠ¯™þÀaΙ

View File

@@ -0,0 +1,2 @@
mschxś%LŃ
Ă <*…AÇ>ħ}kC+ŘV˘˛ßźYIrwÜ%Á„É`<ĂÁ0o‡×rĺĚâż!%źlŚq+ľ\MĆ3źÜŽPăâW‰©§k,Uâ§ŐK0oňŢ÷}.ŔÜ<07>ąače”Fµµ€%ęĘuKAwB*ţ™˝?X=V"5ť~˙[ë

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
mschxś%Ś]<0E> „GD4ńˇ>ôjĹŤ5A0@Űxů¶KÍţfgö<67><C3B6>i íŚ~ÉTs@KÉaÚŮ=(lŽĽ]Ňć=ú™JátbĚŃS˛ö\ />cÂtÄ7'âÂVô•qwqźźąl1Ř•'*â·Â»Íń™¸IAA52Z4
uQ˛I´şÚ%şú~ÄjęţZ <0B>zz]'fačˇ/†ľřúňU<C588>ú\ą,Ä

View File

@@ -0,0 +1,2 @@
mschŠA
!Û…À^rÈ7|”q‡]A•|?cº(hƒï©1üexœcéÍqÊ<71>Ÿ,í»Æ,ÒãÍ<C3A3>5MQ¼ç'õS<E28099>ZqÉ-@Àæ:L´Ëí"o—@ƒ4'

View File

@@ -0,0 +1,2 @@
mschxœE<C593><45>
ƒ0 „¯¦þÀ`{Ž>T¦A µ–*Œ½ûÀõg°@øÂå.<2E>Æ  =on)Õãv쎣 ìÅA¯lºMü,ý“ÏSâã&ÓÊÞNìÌ­s¸/ÃjO1!Êq` ûK¢ñû,&<26>[ÀÿR Œ¤(O€®»6 EÖ * ?»Ê».¡šëº>hRSÉ-êTQBTP5PýAÙù–Ø(¢

View File

@@ -0,0 +1 @@
mschxœ-Ïknƒ0à±<C3A0>]Ò$mZ”ªgàP!’Á/©·ï.S~ðY »;Æ ßÅ>>ãü ¸üŒÝ:Ä¥oÇÔ§„²ÍãÖýæ ~ŽiË(×1åøè&TýÒ

View File

@@ -0,0 +1 @@
mschx<>M־a0 אַLDB׀sp¨)ֵ<>ְF&‰סלˆ³sׁר£i³ץֻ+NP ₪ׁAך¾ןsה£6rhnvװ®<D7B0>µ¡±ויJPg½,ה88<38>ץְ<D7A5>v0 צ³½“k<E2809C>םשD¦c

View File

@@ -0,0 +1,2 @@
mschxœ%ŒA
Ã0 'VKÝB ~ˆeãKjçÐÇ—J HbwV,<2C>0p«é<C2AB>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.

View File

@@ -0,0 +1 @@
mschxœ%ŠQƒ0 C]RÐÄ>v>9Ê"µˆ¶S(HÜ~)X±_dƒƒÏKŒa•YëD­Ý¹(¼¸äS®¢øü²§¯ÆmÃ;VIÓ^es-@

View File

@@ -0,0 +1,3 @@
mschxœ=Љnƒ0 `çpKK·Òco±>TшÄ!¨Ô·¯<C2B7>_*øc'Á´£C~
c¤Csˆ¹ÿLU;OÏøš35clû0¥6 <0C>.§a ×m3Õ]ZÖœþ¶Urê´Æñ±Ì[n#ýRy ^ ðŸ¸b1ñ ÒCp58<35>
ÎØßƒJ·³´2nd”;#g—ýÎs(p(pZàdÜôËË0

View File

@@ -0,0 +1 @@
mschxœ=Ž]ƒ „Gå§IoÑ7e”¨I•†jMo_—IÊ3;» nôÖ¯vžòyæi6°CÚ>ñ2î¯-k¿/C7æåùDXö¸vïtä!B<>GB“ à<>r*^ý÷5š¹RS´kJÌú3%rÉ6\b8Ìp‰á#…F”¾ Q‰,Z¶[F¬˜G‰8iÐ UC´„eÍÉ?<U<>vÏažÃ¼˜?·ì1

View File

@@ -0,0 +1,2 @@
mschxś%ŚA<0E>0 7lh
ŠD6%Eüľ öÁcÉŁ<C389>ĹĂŔĆ%žŰëüČvZp- <09>Kń'WĘý!aţ¦ť`zG)a9Ľ×ě÷Ŕ„{hę2ŠNA…Uô`WÓ7ŹőŞVC}7ő.P Ôµ@-°ţµf

Binary file not shown.

Binary file not shown.

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;
}

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

View File

@@ -88,3 +88,4 @@ Alicila
Daniel Dusek
DeltaNedas
GioIacca9
SnakkiZXZ

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.

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

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();

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);
}
}
}

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));
}
}
}
}
}

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);
}
}
}

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());
}
}
}

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;
}
}

View File

@@ -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);
}
}

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);
}

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

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));
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

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