Merge branch 'master' of https://github.com/Anuken/Mindustry into v105
BIN
core/assets-raw/sprites/blocks/campaign/core-silo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
core/assets-raw/sprites/blocks/campaign/data-processor.png
Normal file
|
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 |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/defense/segment.png
Normal file
|
After Width: | Height: | Size: 794 B |
|
After Width: | Height: | Size: 129 B |
|
After Width: | Height: | Size: 1000 B |
|
After Width: | Height: | Size: 720 B |
|
After Width: | Height: | Size: 513 B |
BIN
core/assets-raw/sprites/blocks/distribution/payload-router.png
Normal file
|
After Width: | Height: | Size: 307 B |
|
Before Width: | Height: | Size: 174 B |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 302 B |
|
Before Width: | Height: | Size: 474 B |
|
Before Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 339 B |
|
Before Width: | Height: | Size: 965 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 779 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 713 B After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 430 B |
|
After Width: | Height: | Size: 299 B |
BIN
core/assets-raw/sprites/blocks/production/disassembler.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 693 B |
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 418 B |
BIN
core/assets-raw/sprites/blocks/production/silicon-crucible.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 640 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 620 B |
BIN
core/assets-raw/sprites/units/cix-joint-base.png
Normal file
|
After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 660 B |
BIN
core/assets-raw/sprites/units/cix-old.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
core/assets-raw/sprites/units/eruptor-foot.png
Normal file
|
After Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
BIN
core/assets-raw/sprites/units/eruptor-leg-base.png
Normal file
|
After Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 400 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 950 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 467 B |
2
core/assets/baseparts/1591368647700.msch
Normal 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Ι
|
||||
2
core/assets/baseparts/1591368707268.msch
Normal file
@@ -0,0 +1,2 @@
|
||||
mschxś%LŃ
|
||||
Ă <›*…AÇ>ħ}‘kC+ŘV˘˛ßźYIrwÜ%Á„É`<ĂÁ0o‡×rĺĚâż!%ź‚lŚq1á+ľ\MĆ3źÜŽPăâW‰©§k,Uâ§ŐK0oňŢ÷}.ŔÜ<07>‚ąače”Fµµ€%ęĘuKAwB*ţ™˝?X=V"5ť~˙[ë
|
||||
BIN
core/assets/baseparts/1591369683871.msch
Normal file
BIN
core/assets/baseparts/1591369726023.msch
Normal file
BIN
core/assets/baseparts/1591378058396.msch
Normal file
2
core/assets/baseparts/1591380533871.msch
Normal 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´ZšşÚ%şú~ÄjęţZ<0B>zz]'fa苡/†ľřúňU<C588>ú\ą,Ä
|
||||
2
core/assets/baseparts/1591380739339.msch
Normal file
@@ -0,0 +1,2 @@
|
||||
mschxœŠA
|
||||
!Û…À^rÈ7|”q‡]A•|?cº(h‚ƒï©1üexœcéÍqÊ<71>Ÿ,í»Æ,ÒãÍ<C3A3>5MQ¼ç'õ’S<E28099>—–Zq–É-Yš@Àæ8Ó:L´Ëí"o—@ƒ4'
|
||||
2
core/assets/baseparts/1591381320600.msch
Normal file
@@ -0,0 +1,2 @@
|
||||
mschxœE<C593><45>
|
||||
ƒ0„¯¦þÀ`{Ž>T¦Aµ–*Œ½ûÀõg°@øÂå.<2E>Æ =o‚n)Õãv쎣 ìÅA¯lºMü,ý“ÏSâã&ÓÊÞNìÌs¸/‘ÃjO1!Êq`ûK¢ñû,&<26>[ÀÿR Œ¤(O€®»6EÖ *?»Ê».¡šëº>hRSÉ–-êTQBTP5PýAÙù–Ø(¢
|
||||
1
core/assets/baseparts/1591385128968.msch
Normal file
@@ -0,0 +1 @@
|
||||
mschxœ-Ïknƒ0à±<C3A0>]Ò$mZ”ªgàP!’Á/©·ï.S~ðY»;Æ ßŇ>>ãü¸üŒÝ:Ä¥o›ÇÔ§„²ÍãÖýæ ~ŽiË(×1åøè&TýÒ
|
||||
1
core/assets/baseparts/1591385266195.msch
Normal file
@@ -0,0 +1 @@
|
||||
mschx<>M־a‚0אַLDB׀sp¨)ֵ<>ְF&‰סלˆ³sׁר£i³ץֻ+NP ₪ׁAך¾ןsה£6rhnvװ®<D7B0>µ¡±ויJPg½,ה88<38>ץְ<D7A5>v0צ³½“k<E2809C>םשD¦c
|
||||
2
core/assets/baseparts/1591385293703.msch
Normal 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
|
||||
BIN
core/assets/baseparts/1591385372367.msch
Normal file
BIN
core/assets/baseparts/1591385575600.msch
Normal file
BIN
core/assets/baseparts/1591385624323.msch
Normal file
BIN
core/assets/baseparts/1591385989609.msch
Normal file
BIN
core/assets/baseparts/1591389341902.msch
Normal file
BIN
core/assets/baseparts/1591389353247.msch
Normal file
BIN
core/assets/baseparts/1591389407756.msch
Normal file
1
core/assets/baseparts/1591389457130.msch
Normal file
@@ -0,0 +1 @@
|
||||
mschxœ%ŠQƒ0C]RÐÄ>v>9Ê"µˆ¶S(HÜ~)X±_dƒƒÏKŒa•YëDݹ(¼¸äS®¢øü²i©‘§¯ÆmÃ;VIÓ^e0â–s-@
|
||||
3
core/assets/baseparts/1591389561116.msch
Normal 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
|
||||
1
core/assets/baseparts/1591389593326.msch
Normal 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¡ý÷5š¹RS´kJÌú‚3%rÉ6\b8Ìp‰á#…F”¾ Q‰’,Z¶[F¬˜G‰8iÐUC´„eÍÉ?<U<>vÏažÃ¼˜?·ì1
|
||||
2
core/assets/baseparts/1591389611174.msch
Normal file
@@ -0,0 +1,2 @@
|
||||
mschxś%ŚA<0E>07lh‘8đný
|
||||
ŠD’6%Eüľ öÁcÉŁ<C389>ĹĂŔĆ%žŰëüČvZp- <09>Kń'WĘý!aţ¦’ť`zG)a9Ľ›×ě÷Ŕ„{hę2ŠNA…Uô`WÓ7ŹőŞVC}7ő.PÔµ@-°ţµf
|
||||
BIN
core/assets/baseparts/core1.msch
Normal file
BIN
core/assets/baseparts/turret1.msch
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -88,3 +88,4 @@ Alicila
|
||||
Daniel Dusek
|
||||
DeltaNedas
|
||||
GioIacca9
|
||||
SnakkiZXZ
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 822 KiB After Width: | Height: | Size: 856 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 876 KiB After Width: | Height: | Size: 915 KiB |
@@ -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
core/src/mindustry/ai/Astar.java
Normal 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
core/src/mindustry/ai/BaseAI.java
Normal 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
core/src/mindustry/ai/BaseRegistry.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
83
core/src/mindustry/ai/types/BuilderAI.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
70
core/src/mindustry/ai/types/SuicideAI.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||