diff --git a/core/src/mindustry/ai/BlockIndexer.java b/core/src/mindustry/ai/BlockIndexer.java
index b010562595..9e72b7ef86 100644
--- a/core/src/mindustry/ai/BlockIndexer.java
+++ b/core/src/mindustry/ai/BlockIndexer.java
@@ -5,11 +5,11 @@ import arc.func.*;
 import arc.math.*;
 import arc.math.geom.*;
 import arc.struct.*;
+import arc.util.*;
 import mindustry.content.*;
 import mindustry.entities.type.*;
 import mindustry.game.EventType.*;
 import mindustry.game.*;
-import mindustry.game.Teams.*;
 import mindustry.type.*;
 import mindustry.world.*;
 import mindustry.world.blocks.*;
@@ -34,6 +34,8 @@ public class BlockIndexer{
     private ObjectSet<Tile>[] damagedTiles = new ObjectSet[Team.all().length];
     /**All ores available on this map.*/
     private ObjectSet<Item> allOres = new ObjectSet<>();
+    /**Stores teams that are present here as tiles.*/
+    private ObjectSet<Team> activeTeams = new ObjectSet<>();
 
     /** Maps teams to a map of flagged tiles by type. */
     private ObjectSet<Tile>[][] flagMap = new ObjectSet[Team.all().length][BlockFlag.all.length];
@@ -104,10 +106,11 @@ public class BlockIndexer{
     }
 
     private GridBits structQuadrant(Team t){
-        if(structQuadrants[t.id] == null){
-            structQuadrants[t.id] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize));
+        int id = Pack.u(t.id);
+        if(structQuadrants[id] == null){
+            structQuadrants[id] = new GridBits(Mathf.ceil(world.width() / (float)quadrantSize), Mathf.ceil(world.height() / (float)quadrantSize));
         }
-        return structQuadrants[t.id];
+        return structQuadrants[id];
     }
 
     /** Updates all the structure quadrants for a newly activated team. */
@@ -184,6 +187,19 @@ public class BlockIndexer{
         set.add(entity.tile);
     }
 
+    public TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
+        for(Team enemy : activeTeams){
+            if(!team.isEnemy(enemy)) continue;
+
+            TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
+            if(entity != null){
+                return entity;
+            }
+        }
+
+        return null;
+    }
+
     public TileEntity findTile(Team team, float x, float y, float range, Boolf<Tile> pred){
         return findTile(team, x, y, range, pred, false);
     }
@@ -263,6 +279,7 @@ public class BlockIndexer{
             }
             typeMap.put(tile.pos(), new TileIndex(tile.block().flags, tile.getTeam()));
         }
+        activeTeams.add(tile.getTeam());
 
         if(ores == null) return;
 
@@ -301,13 +318,12 @@ public class BlockIndexer{
         //this quadrant is now 'dirty', re-scan the whole thing
         int quadrantX = tile.x / quadrantSize;
         int quadrantY = tile.y / quadrantSize;
-        int index = quadrantX + quadrantY * quadWidth();
 
-        for(TeamData data : state.teams.getActive()){
-            GridBits bits = structQuadrant(data.team);
+        for(Team team : activeTeams){
+            GridBits bits = structQuadrant(team);
 
             //fast-set this quadrant to 'occupied' if the tile just placed is already of this team
-            if(tile.getTeam() == data.team && tile.entity != null && tile.block().targetable){
+            if(tile.getTeam() == team && tile.entity != null && tile.block().targetable){
                 bits.set(quadrantX, quadrantY);
                 continue; //no need to process futher
             }
@@ -319,7 +335,7 @@ public class BlockIndexer{
                 for(int y = quadrantY * quadrantSize; y < world.height() && y < (quadrantY + 1) * quadrantSize; y++){
                     Tile result = world.ltile(x, y);
                     //when a targetable block is found, mark this quadrant as occupied and stop searching
-                    if(result.entity != null && result.getTeam() == data.team){
+                    if(result.entity != null && result.getTeam() == team){
                         bits.set(quadrantX, quadrantY);
                         break outer;
                     }
diff --git a/core/src/mindustry/entities/Units.java b/core/src/mindustry/entities/Units.java
index fac56dcc0e..67fe01dc9e 100644
--- a/core/src/mindustry/entities/Units.java
+++ b/core/src/mindustry/entities/Units.java
@@ -83,13 +83,7 @@ public class Units{
     public static TileEntity findEnemyTile(Team team, float x, float y, float range, Boolf<Tile> pred){
         if(team == Team.derelict) return null;
 
-        for(Team enemy : team.enemies()){
-            TileEntity entity = indexer.findTile(enemy, x, y, range, pred, true);
-            if(entity != null){
-                return entity;
-            }
-        }
-        return null;
+        return indexer.findEnemyTile(team, x, y, range, pred);
     }
 
     /** Returns the closest target enemy. First, units are checked, then tile entities. */
diff --git a/core/src/mindustry/graphics/MinimapRenderer.java b/core/src/mindustry/graphics/MinimapRenderer.java
index 01ac9ab09a..c34bb246ec 100644
--- a/core/src/mindustry/graphics/MinimapRenderer.java
+++ b/core/src/mindustry/graphics/MinimapRenderer.java
@@ -93,8 +93,8 @@ public class MinimapRenderer implements Disposable{
             float ry = !withLabels ? (unit.y - rect.y) / rect.width * h : unit.y / (world.height() * tilesize) * h;
 
             Draw.mixcol(unit.getTeam().color, 1f);
-            float scale = Scl.scl(1f) / 2f * scaling;
-            Draw.rect(unit.getIconRegion(), x + rx, y + ry, unit.getIconRegion().getWidth() * scale, unit.getIconRegion().getHeight() * scale, unit.rotation - 90);
+            float scale = Scl.scl(1f) / 2f * scaling * 32f;
+            Draw.rect(unit.getIconRegion(), x + rx, y + ry, scale, scale, unit.rotation - 90);
             Draw.reset();
 
             if(withLabels && unit instanceof Player){
diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java
index c485b49ff2..8838dee9c9 100644
--- a/core/src/mindustry/input/DesktopInput.java
+++ b/core/src/mindustry/input/DesktopInput.java
@@ -136,7 +136,7 @@ public class DesktopInput extends InputHandler{
             ui.listfrag.toggle();
         }
 
-        if((player.getClosestCore() == null || state.isPaused()) && !ui.chatfrag.shown()){
+        if(((player.getClosestCore() == null && player.isDead()) || state.isPaused()) && !ui.chatfrag.shown()){
             //move camera around
             float camSpeed = !Core.input.keyDown(Binding.dash) ? 3f : 8f;
             Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(Time.delta() * camSpeed));
diff --git a/core/src/mindustry/ui/fragments/MinimapFragment.java b/core/src/mindustry/ui/fragments/MinimapFragment.java
index 7e93832cb4..d13fed8936 100644
--- a/core/src/mindustry/ui/fragments/MinimapFragment.java
+++ b/core/src/mindustry/ui/fragments/MinimapFragment.java
@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
 public class MinimapFragment extends Fragment{
     private boolean shown;
     private float panx, pany, zoom = 1f, lastZoom = -1;
-    private float baseSize = Scl.scl(1000f);
+    private float baseSize = Scl.scl(5f);
     private Element elem;
 
     @Override
@@ -25,16 +25,17 @@ public class MinimapFragment extends Fragment{
         elem = parent.fill((x, y, w, h) -> {
             w = Core.graphics.getWidth();
             h = Core.graphics.getHeight();
-            float size = baseSize * zoom;
+            float size = baseSize * zoom * world.width();
 
             Draw.color(Color.black);
             Fill.crect(x, y, w, h);
 
             if(renderer.minimap.getTexture() != null){
                 Draw.color();
+                float ratio = (float)renderer.minimap.getTexture().getHeight() / renderer.minimap.getTexture().getWidth();
                 TextureRegion reg = Draw.wrap(renderer.minimap.getTexture());
-                Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size);
-                renderer.minimap.drawEntities(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f, size, size, zoom, true);
+                Draw.rect(reg, w/2f + panx*zoom, h/2f + pany*zoom, size, size * ratio);
+                renderer.minimap.drawEntities(w/2f + panx*zoom - size/2f, h/2f + pany*zoom - size/2f * ratio, size, size * ratio, zoom, true);
             }
 
             Draw.reset();
diff --git a/fastlane/metadata/android/en-US/changelogs/29570.txt b/fastlane/metadata/android/en-US/changelogs/29570.txt
new file mode 100644
index 0000000000..f8244a4b0b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/29570.txt
@@ -0,0 +1,5 @@
+- Added new map view w/ panning and scrolling
+- Added block health rule
+- Added more internal teams for alternative gamemodes
+- Added features for improved server modding
+- Major internal change: package is now "mindustry" instead of "io.anuke.mindustry" (will break plugins)