diff --git a/.travis.yml b/.travis.yml index 6b1dfd72ac..11dbe8cd1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ script: - git clone --depth=1 --branch=master https://github.com/Anuken/MindustryBuilds ../MindustryBuilds - cd ../MindustryBuilds - echo ${TRAVIS_TAG} -- if [ -n "$TRAVIS_TAG" ]; then echo versionName=5-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi +- if [ -n "$TRAVIS_TAG" ]; then echo versionName=6-fdroid-${TRAVIS_TAG:1}$'\n'versionCode=${TRAVIS_TAG:1} > version_fdroid.txt; git add .; git commit -m "Updating to build ${TRAVIS_TAG}"; fi - git tag ${TRAVIS_BUILD_NUMBER} - git config --global user.name "Build Uploader" - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds ${TRAVIS_BUILD_NUMBER}; git push https://Anuken:${GH_PUSH_TOKEN}@github.com/Anuken/MindustryBuilds; fi diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 6d1aafb54f..651576c3de 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -20,7 +20,7 @@ gameover = Game Over gameover.pvp = The[accent] {0}[] team is victorious! highscore = [accent]New highscore! copied = Copied. -indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- Content is missing\n - Most [scarlet]Unit AI[] does not work properly\n- Many units are unfinished\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[]. +indev.popup = [accent]v6[] is currently in [accent]alpha[].\n[lightgray]This means:[]\n[scarlet]- The campaign is completely unfinished[]\n- SFX and music are unfinished/missing\n- Everything you see is subject to change or removal.\n\nReport bugs or crashes on [accent]Github[]. indev.notready = This part of the game isn't ready yet load.sound = Sounds @@ -291,6 +291,8 @@ waiting = [lightgray]Waiting... waiting.players = Waiting for players... wave.enemies = [lightgray]{0} Enemies Remaining wave.enemy = [lightgray]{0} Enemy Remaining +wave.guardianwarn = Guardian approaching in [accent]{0}[] waves. +wave.guardianwarn.one = Guardian approaching in [accent]{0}[] wave. loadimage = Load Image saveimage = Save Image unknown = Unknown @@ -573,6 +575,7 @@ info.title = Info error.title = [scarlet]An error has occured error.crashtitle = An error has occured unit.nobuild = [scarlet]Unit can't build +lastaccessed = [lightgray]Last Accessed: {0} blocks.input = Input blocks.output = Output blocks.booster = Booster @@ -628,6 +631,7 @@ bar.powerbalance = Power: {0}/s bar.powerstored = Stored: {0}/{1} bar.poweramount = Power: {0} bar.poweroutput = Power Output: {0} +bar.powerlines = Connections: {0}/{1} bar.items = Items: {0} bar.capacity = Capacity: {0} bar.unitcap = {0} {1}/{2} @@ -990,6 +994,7 @@ block.darksand-water.name = Dark Sand Water block.char.name = Char block.dacite.name = Dacite block.dacite-wall.name = Dacite Wall +block.dacite-boulder.name = Dacite Boulder block.ice-snow.name = Ice Snow block.stone-wall.name = Stone Wall block.ice-wall.name = Ice Wall diff --git a/core/assets/maps/craters.msav b/core/assets/maps/craters.msav index b879701ae6..b10b354bf0 100644 Binary files a/core/assets/maps/craters.msav and b/core/assets/maps/craters.msav differ diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index e0871c74ac..37ba22d3cb 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -85,9 +85,9 @@ public class Vars implements Loadable{ /** range for moving items for logic units */ public static final float logicItemTransferRange = 45f; /** duration of time between turns in ticks */ - public static final float turnDuration = 20 * Time.toMinutes; + public static final float turnDuration = 2 * Time.toMinutes; /** turns needed to destroy a sector completely */ - public static final float sectorDestructionTurns = 3f; + public static final float sectorDestructionTurns = 2f; /** min armor fraction damage; e.g. 0.05 = at least 5% damage */ public static final float minArmorDamage = 0.1f; /** launch animation duration */ @@ -184,7 +184,7 @@ public class Vars implements Loadable{ public static GameState state; public static EntityCollisions collisions; public static DefaultWaves defaultWaves; - public static mindustry.audio.LoopControl loops; + public static LoopControl loops; public static Platform platform = new Platform(){}; public static Mods mods; public static Schematics schematics; diff --git a/core/src/mindustry/ai/WaveSpawner.java b/core/src/mindustry/ai/WaveSpawner.java index 417909a92b..9cecf32ac1 100644 --- a/core/src/mindustry/ai/WaveSpawner.java +++ b/core/src/mindustry/ai/WaveSpawner.java @@ -77,14 +77,18 @@ public class WaveSpawner{ eachGroundSpawn((spawnX, spawnY, doShockwave) -> { if(doShockwave){ - Time.run(20f, () -> Fx.spawnShockwave.at(spawnX, spawnY, state.rules.dropZoneRadius)); - Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true)); + doShockwave(spawnX, spawnY); } }); Time.runTask(121f, () -> spawning = false); } + public void doShockwave(float x, float y){ + Time.run(20f, () -> Fx.spawnShockwave.at(x, y, state.rules.dropZoneRadius)); + Time.run(40f, () -> Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true)); + } + private void eachGroundSpawn(SpawnConsumer cons){ for(Tile spawn : spawns){ cons.accept(spawn.worldx(), spawn.worldy(), true); diff --git a/core/src/mindustry/ai/types/BuilderAI.java b/core/src/mindustry/ai/types/BuilderAI.java index 1e1875e176..8e8c74f986 100644 --- a/core/src/mindustry/ai/types/BuilderAI.java +++ b/core/src/mindustry/ai/types/BuilderAI.java @@ -17,13 +17,17 @@ public class BuilderAI extends AIController{ @Nullable Builderc following; @Override - public void updateUnit(){ + public void updateMovement(){ Builderc builder = (Builderc)unit; if(builder.moving()){ builder.lookAt(builder.vel().angle()); } + if(target != null && shouldShoot()){ + unit.lookAt(target); + } + builder.updateBuilding(true); if(following != null){ @@ -95,13 +99,29 @@ public class BuilderAI extends AIController{ }else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid. //add build request. builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config)); + //shift build plan to tail so next unit builds something else. + blocks.addLast(blocks.removeFirst()); }else{ //shift head of queue to tail, try something else next time blocks.removeFirst(); blocks.addLast(block); } } - } } + + @Override + public AIController fallback(){ + return unit.type().flying ? new FlyingAI() : new GroundAI(); + } + + @Override + public boolean useFallback(){ + return state.rules.waves && unit.team == state.rules.waveTeam && !unit.team.rules().ai; + } + + @Override + public boolean shouldShoot(){ + return !((Builderc)unit).isBuilding(); + } } diff --git a/core/src/mindustry/ai/types/LogicAI.java b/core/src/mindustry/ai/types/LogicAI.java index df856d6688..a7b606b932 100644 --- a/core/src/mindustry/ai/types/LogicAI.java +++ b/core/src/mindustry/ai/types/LogicAI.java @@ -21,6 +21,7 @@ public class LogicAI extends AIController{ public LUnitControl control = LUnitControl.stop; public float moveX, moveY, moveRad; public float itemTimer, payTimer, controlTimer = logicControlTimeout, targetTimer; + @Nullable public Building controller; public BuildPlan plan = new BuildPlan(); diff --git a/core/src/mindustry/ai/types/RepairAI.java b/core/src/mindustry/ai/types/RepairAI.java index 84baf1c555..5f021ae828 100644 --- a/core/src/mindustry/ai/types/RepairAI.java +++ b/core/src/mindustry/ai/types/RepairAI.java @@ -2,27 +2,33 @@ package mindustry.ai.types; import mindustry.entities.*; import mindustry.entities.units.*; +import mindustry.gen.*; import mindustry.world.blocks.ConstructBlock.*; -//note that repair AI doesn't attack anything even if it theoretically can public class RepairAI extends AIController{ @Override protected void updateMovement(){ - boolean shoot = false; - - if(target != null){ - if(!target.within(unit, unit.type().range * 0.8f)){ - moveTo(target, unit.type().range * 0.8f); - } + if(target instanceof Building){ + boolean shoot = false; if(target.within(unit, unit.type().range)){ unit.aim(target); shoot = true; } + + unit.controlWeapons(shoot); + }else if(target == null){ + unit.controlWeapons(false); } - unit.controlWeapons(shoot); + if(target != null){ + if(!target.within(unit, unit.type().range * 0.65f)){ + moveTo(target, unit.type().range * 0.65f); + } + + unit.lookAt(target); + } } @Override @@ -30,5 +36,10 @@ public class RepairAI extends AIController{ target = Units.findDamagedTile(unit.team, unit.x, unit.y); if(target instanceof ConstructBuild) target = null; + + if(target == null){ + super.updateTargeting(); + } } + } diff --git a/core/src/mindustry/ai/types/SuicideAI.java b/core/src/mindustry/ai/types/SuicideAI.java index 7278062605..2eae6e409f 100644 --- a/core/src/mindustry/ai/types/SuicideAI.java +++ b/core/src/mindustry/ai/types/SuicideAI.java @@ -6,6 +6,8 @@ import mindustry.entities.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.world.*; +import mindustry.world.blocks.distribution.*; +import mindustry.world.blocks.liquid.*; import mindustry.world.meta.*; public class SuicideAI extends GroundAI{ @@ -14,7 +16,7 @@ public class SuicideAI extends GroundAI{ @Override public void updateUnit(){ - if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){ + if(Units.invalidateTarget(target, unit.team, unit.x, unit.y, Float.MAX_VALUE)){ target = null; } @@ -26,7 +28,7 @@ public class SuicideAI extends GroundAI{ boolean rotate = false, shoot = false, moveToTarget = false; - if(!Units.invalidateTarget(target, unit, unit.range())){ + if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){ rotate = true; shoot = unit.within(target, unit.type().weapons.first().bullet.range() + (target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f)); @@ -35,29 +37,36 @@ public class SuicideAI extends GroundAI{ unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed)); } - blockedByBlock = false; + //do not move toward walls or transport blocks + if(!(target instanceof Building build && ( + build.block.group == BlockGroup.walls || + build.block.group == BlockGroup.liquids || + build.block.group == BlockGroup.transportation + ))){ + 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.build == target) return false; - if(tile != null && tile.build != null && tile.build.team != unit.team()){ - blockedByBlock = true; - return true; - }else{ - return tile == null || tile.solid(); + //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.build == target) return false; + if(tile != null && tile.build != null && tile.build.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; } - }); - //shoot when there's an enemy block in the way - if(blockedByBlock){ - shoot = true; - } - - if(!blocked){ - moveToTarget = true; - //move towards target directly - unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed)); + if(!blocked){ + moveToTarget = true; + //move towards target directly + unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed)); + } } } @@ -78,4 +87,10 @@ public class SuicideAI extends GroundAI{ unit.controlWeapons(rotate, shoot); } + + @Override + protected Teamc target(float x, float y, float range, boolean air, boolean ground){ + return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground && + !(t.block instanceof Conveyor || t.block instanceof Conduit)); //do not target conveyors/conduits + } } diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index f04da239bb..05ed572f7e 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -702,7 +702,7 @@ public class Blocks implements ContentList{ drawer = new DrawAnimation(); consumes.item(Items.sporePod, 1); - consumes.power(0.60f); + consumes.power(0.7f); }}; pulverizer = new GenericCrafter("pulverizer"){{ @@ -727,7 +727,7 @@ public class Blocks implements ContentList{ hasPower = hasItems = hasLiquids = true; consumes.liquid(Liquids.oil, 0.1f); - consumes.power(0.5f); + consumes.power(0.7f); }}; incinerator = new Incinerator("incinerator"){{ @@ -829,27 +829,27 @@ public class Blocks implements ContentList{ }}; scrapWall = new Wall("scrap-wall"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, with()); + requirements(Category.defense, BuildVisibility.sandboxOnly, with(Items.scrap, 6)); health = 60 * wallHealthMultiplier; variants = 5; }}; scrapWallLarge = new Wall("scrap-wall-large"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, with()); + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 4)); health = 60 * 4 * wallHealthMultiplier; size = 2; variants = 4; }}; scrapWallHuge = new Wall("scrap-wall-huge"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, with()); + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 9)); health = 60 * 9 * wallHealthMultiplier; size = 3; variants = 3; }}; scrapWallGigantic = new Wall("scrap-wall-gigantic"){{ - requirements(Category.defense, BuildVisibility.sandboxOnly, with()); + requirements(Category.defense, BuildVisibility.sandboxOnly, ItemStack.mult(scrapWall.requirements, 16)); health = 60 * 16 * wallHealthMultiplier; size = 4; }}; @@ -994,7 +994,7 @@ public class Blocks implements ContentList{ router = new Router("router"){{ requirements(Category.distribution, with(Items.copper, 3)); - buildCostMultiplier = 2f; + buildCostMultiplier = 4f; }}; distributor = new Router("distributor"){{ @@ -1287,7 +1287,7 @@ public class Blocks implements ContentList{ }}; cultivator = new Cultivator("cultivator"){{ - requirements(Category.production, with(Items.copper, 10, Items.lead, 25, Items.silicon, 10)); + requirements(Category.production, with(Items.copper, 25, Items.lead, 25, Items.silicon, 10)); outputItem = new ItemStack(Items.sporePod, 1); craftTime = 140; size = 2; @@ -1295,7 +1295,7 @@ public class Blocks implements ContentList{ hasPower = true; hasItems = true; - consumes.power(0.80f); + consumes.power(0.9f); consumes.liquid(Liquids.water, 0.2f); }}; @@ -1341,6 +1341,7 @@ public class Blocks implements ContentList{ size = 4; unitCapModifier = 14; + researchCostMultiplier = 0.04f; }}; coreNucleus = new CoreBlock("core-nucleus"){{ @@ -1352,6 +1353,7 @@ public class Blocks implements ContentList{ size = 5; unitCapModifier = 20; + researchCostMultiplier = 0.06f; }}; vault = new StorageBlock("vault"){{ diff --git a/core/src/mindustry/content/StatusEffects.java b/core/src/mindustry/content/StatusEffects.java index 6752cf68e6..4cdfadec76 100644 --- a/core/src/mindustry/content/StatusEffects.java +++ b/core/src/mindustry/content/StatusEffects.java @@ -33,7 +33,7 @@ public class StatusEffects implements ContentList{ freezing = new StatusEffect("freezing"){{ speedMultiplier = 0.6f; - armorMultiplier = 0.8f; + healthMultiplier = 0.8f; effect = Fx.freezing; init(() -> { @@ -81,7 +81,7 @@ public class StatusEffects implements ContentList{ melting = new StatusEffect("melting"){{ speedMultiplier = 0.8f; - armorMultiplier = 0.8f; + healthMultiplier = 0.8f; damage = 0.3f; effect = Fx.melting; @@ -93,7 +93,7 @@ public class StatusEffects implements ContentList{ sapped = new StatusEffect("sapped"){{ speedMultiplier = 0.7f; - armorMultiplier = 0.8f; + healthMultiplier = 0.8f; effect = Fx.sapped; effectChance = 0.1f; }}; @@ -115,7 +115,7 @@ public class StatusEffects implements ContentList{ }}; overdrive = new StatusEffect("overdrive"){{ - armorMultiplier = 0.95f; + healthMultiplier = 0.95f; speedMultiplier = 1.15f; damageMultiplier = 1.4f; damage = -0.01f; @@ -132,13 +132,13 @@ public class StatusEffects implements ContentList{ }}; shielded = new StatusEffect("shielded"){{ - armorMultiplier = 3f; + healthMultiplier = 3f; }}; boss = new StatusEffect("boss"){{ permanent = true; - damageMultiplier = 1.5f; - armorMultiplier = 1.5f; + damageMultiplier = 2f; + healthMultiplier = 2f; }}; shocked = new StatusEffect("shocked"); diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index d01be43902..9a1b851118 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -514,7 +514,7 @@ public class UnitTypes implements ContentList{ crawler = new UnitType("crawler"){{ defaultController = SuicideAI::new; - speed = 0.92f; + speed = 1f; hitSize = 8f; health = 180; mechSideSway = 0.25f; @@ -529,9 +529,9 @@ public class UnitTypes implements ContentList{ hitEffect = Fx.pulverize; lifetime = 10f; speed = 1f; - splashDamageRadius = 55f; + splashDamageRadius = 70f; instantDisappear = true; - splashDamage = 60f; + splashDamage = 80f; killShooter = true; hittable = false; collidesAir = true; @@ -1145,7 +1145,7 @@ public class UnitTypes implements ContentList{ speed = 1.9f; rotateSpeed = 15f; accel = 0.1f; - range = 70f; + range = 130f; health = 400; buildSpeed = 0.5f; engineOffset = 6.5f; @@ -1187,8 +1187,6 @@ public class UnitTypes implements ContentList{ healPercent = 5.5f; collidesTeam = true; backColor = Pal.heal; - frontColor = Color.white; - backColor = Pal.heal; trailColor = Pal.heal; }}; }}); @@ -1233,7 +1231,7 @@ public class UnitTypes implements ContentList{ }}; quad = new UnitType("quad"){{ - armor = 4f; + armor = 8f; health = 6000; speed = 1.2f; rotateSpeed = 2f; @@ -1256,7 +1254,7 @@ public class UnitTypes implements ContentList{ new Weapon(){{ x = y = 0f; mirror = false; - reload = 60f; + reload = 55f; minShootVelocity = 0.01f; bullet = new BasicBulletType(){{ @@ -1289,9 +1287,9 @@ public class UnitTypes implements ContentList{ speed = 0.001f; collides = false; - healPercent = 10f; - splashDamage = 240f; - splashDamageRadius = 115f; + healPercent = 15f; + splashDamage = 320f; + splashDamageRadius = 120f; }}; }}); }}; @@ -1546,7 +1544,7 @@ public class UnitTypes implements ContentList{ xRand = 8f; shotDelay = 1f; - bullet = new MissileBulletType(4.2f, 25){{ + bullet = new MissileBulletType(4.2f, 30){{ homingPower = 0.12f; width = 8f; height = 8f; @@ -1554,8 +1552,8 @@ public class UnitTypes implements ContentList{ drag = -0.003f; homingRange = 80f; keepVelocity = false; - splashDamageRadius = 25f; - splashDamage = 25f; + splashDamageRadius = 30f; + splashDamage = 35f; lifetime = 56f; trailColor = Pal.bulletYellowBack; backColor = Pal.bulletYellowBack; diff --git a/core/src/mindustry/content/Weathers.java b/core/src/mindustry/content/Weathers.java index a2ffbb098f..273fdb5899 100644 --- a/core/src/mindustry/content/Weathers.java +++ b/core/src/mindustry/content/Weathers.java @@ -152,7 +152,7 @@ public class Weathers implements ContentList{ if(tile != null && tile.floor().liquidDrop == Liquids.water){ Draw.color(Tmp.c1.set(tile.floor().mapColor).mul(1.5f).a(state.opacity())); Draw.rect(splashes[(int)(life * (splashes.length - 1))], x, y); - }else{ + }else if(tile != null && tile.floor().liquidDrop == null && !tile.floor().solid){ Draw.color(Color.royal, Color.white, 0.3f); Draw.alpha(Mathf.slope(life) * state.opacity()); diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index dd54bcc59d..df536552df 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -9,7 +9,6 @@ import mindustry.game.EventType.*; import mindustry.game.*; import mindustry.game.Teams.*; import mindustry.gen.*; -import mindustry.maps.*; import mindustry.type.*; import mindustry.type.Weather.*; import mindustry.world.*; @@ -89,13 +88,16 @@ public class Logic implements ApplicationListener{ if(state.isCampaign()){ long seconds = state.rules.sector.getSecondsPassed(); CoreBuild core = state.rules.defaultTeam.core(); + //THE WAVES NEVER END + state.rules.waves = true; //apply fractional damage based on how many turns have passed for this sector - float turnsPassed = seconds / (turnDuration / 60f); + //float turnsPassed = seconds / (turnDuration / 60f); - if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){ - SectorDamage.apply(turnsPassed / sectorDestructionTurns); - } + //TODO sector damage disabled for now + //if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){ + // SectorDamage.apply(turnsPassed / sectorDestructionTurns); + //} //add resources based on turns passed if(state.rules.sector.save != null && core != null){ @@ -197,6 +199,8 @@ public class Logic implements ApplicationListener{ state.rules.waves = false; } + //TODO capturing is disabled + /* //if there's a "win" wave and no enemies are present, win automatically if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){ //the sector has been conquered - waves get disabled @@ -209,7 +213,7 @@ public class Logic implements ApplicationListener{ if(!headless){ control.saves.saveSector(state.rules.sector); } - } + }*/ }else{ if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){ state.gameOver = true; diff --git a/core/src/mindustry/ctype/UnlockableContent.java b/core/src/mindustry/ctype/UnlockableContent.java index 56e9c31146..f6e6d0a2dc 100644 --- a/core/src/mindustry/ctype/UnlockableContent.java +++ b/core/src/mindustry/ctype/UnlockableContent.java @@ -22,7 +22,7 @@ public abstract class UnlockableContent extends MappableContent{ /** Whether this content is always unlocked in the tech tree. */ public boolean alwaysUnlocked = false; /** Icons by Cicon ID.*/ - protected TextureRegion[] cicons = new TextureRegion[mindustry.ui.Cicon.all.length]; + protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length]; /** Unlock state. Loaded from settings. Do not modify outside of the constructor. */ protected boolean unlocked; diff --git a/core/src/mindustry/entities/comp/BuilderComp.java b/core/src/mindustry/entities/comp/BuilderComp.java index d57b1b5b6d..0aafd4d0c0 100644 --- a/core/src/mindustry/entities/comp/BuilderComp.java +++ b/core/src/mindustry/entities/comp/BuilderComp.java @@ -116,7 +116,6 @@ abstract class BuilderComp implements Unitc{ current.progress = entity.progress; } - /** Draw all current build requests. Does not draw the beam effect, only the positions. */ void drawBuildRequests(){ diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 4a8e23e6cf..15d79a8942 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -57,6 +57,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, transient int rotation; transient boolean enabled = true; transient float enabledControlTime; + transient String lastAccessed; PowerModule power; ItemModule items; @@ -348,6 +349,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, //endregion //region handler methods + /** Called when an unloader takes an item. */ + public void itemTaken(Item item){ + + } + /** Called when this block is dropped as a payload. */ public void dropped(){ @@ -874,6 +880,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, //null is of type void.class; anonymous classes use their superclass. Class type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass(); + if(builder != null && builder.isPlayer()){ + lastAccessed = builder.getPlayer().name; + } + if(block.configurations.containsKey(type)){ block.configurations.get(type).get(this, value); } @@ -1038,6 +1048,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, } } + if(net.active() && lastAccessed != null){ + table.row(); + table.add(Core.bundle.format("lastaccessed", lastAccessed)).growX().wrap().left(); + } + table.marginBottom(-5); } } diff --git a/core/src/mindustry/entities/comp/PayloadComp.java b/core/src/mindustry/entities/comp/PayloadComp.java index a27c7721be..ac1f4fa323 100644 --- a/core/src/mindustry/entities/comp/PayloadComp.java +++ b/core/src/mindustry/entities/comp/PayloadComp.java @@ -86,10 +86,10 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{ return true; } - if(payload instanceof BuildPayload){ - return dropBlock((BuildPayload)payload); - }else if(payload instanceof UnitPayload){ - return dropUnit((UnitPayload)payload); + if(payload instanceof BuildPayload b){ + return dropBlock(b); + }else if(payload instanceof UnitPayload p){ + return dropUnit(p); } return false; } @@ -126,6 +126,8 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{ int rot = (int)((rotation + 45f) / 90f) % 4; payload.place(on, rot); + if(isPlayer()) payload.build.lastAccessed = getPlayer().name; + Fx.unitDrop.at(tile); Fx.placeBlock.at(on.drawx(), on.drawy(), on.block().size); return true; diff --git a/core/src/mindustry/entities/comp/ShieldComp.java b/core/src/mindustry/entities/comp/ShieldComp.java index 359637414e..274286b801 100644 --- a/core/src/mindustry/entities/comp/ShieldComp.java +++ b/core/src/mindustry/entities/comp/ShieldComp.java @@ -9,7 +9,7 @@ import static mindustry.Vars.*; @Component abstract class ShieldComp implements Healthc, Posc{ - @Import float health, hitTime, x, y; + @Import float health, hitTime, x, y, healthMultiplier; @Import boolean dead; /** Absorbs health damage. */ @@ -22,6 +22,7 @@ abstract class ShieldComp implements Healthc, Posc{ @Replace @Override public void damage(float amount){ + amount /= healthMultiplier; //apply armor amount = Math.max(amount - armor, minArmorDamage * amount); diff --git a/core/src/mindustry/entities/comp/StatusComp.java b/core/src/mindustry/entities/comp/StatusComp.java index 3a31b13b95..fcce0d0a02 100644 --- a/core/src/mindustry/entities/comp/StatusComp.java +++ b/core/src/mindustry/entities/comp/StatusComp.java @@ -19,7 +19,7 @@ abstract class StatusComp implements Posc, Flyingc{ private Seq statuses = new Seq<>(); private transient Bits applied = new Bits(content.getBy(ContentType.status).size); - @ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, armorMultiplier = 1, reloadMultiplier = 1; + @ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1; @Import UnitType type; @@ -104,7 +104,7 @@ abstract class StatusComp implements Posc, Flyingc{ } applied.clear(); - speedMultiplier = damageMultiplier = armorMultiplier = reloadMultiplier = 1f; + speedMultiplier = damageMultiplier = healthMultiplier = reloadMultiplier = 1f; if(statuses.isEmpty()) return; @@ -122,7 +122,7 @@ abstract class StatusComp implements Posc, Flyingc{ statuses.remove(index); }else{ speedMultiplier *= entry.effect.speedMultiplier; - armorMultiplier *= entry.effect.armorMultiplier; + healthMultiplier *= entry.effect.healthMultiplier; damageMultiplier *= entry.effect.damageMultiplier; reloadMultiplier *= entry.effect.reloadMultiplier; entry.effect.update(self(), entry.time); diff --git a/core/src/mindustry/entities/units/AIController.java b/core/src/mindustry/entities/units/AIController.java index d235972212..b02fa5dc00 100644 --- a/core/src/mindustry/entities/units/AIController.java +++ b/core/src/mindustry/entities/units/AIController.java @@ -21,6 +21,7 @@ public class AIController implements UnitController{ protected Unit unit; protected Interval timer = new Interval(4); + protected AIController fallback; /** main target that is being faced */ protected Teamc target; @@ -34,11 +35,27 @@ public class AIController implements UnitController{ @Override public void updateUnit(){ + //use fallback AI when possible + if(useFallback() && (fallback != null || (fallback = fallback()) != null)){ + if(fallback.unit != unit) fallback.unit(unit); + fallback.updateUnit(); + return; + } + updateVisuals(); updateTargeting(); updateMovement(); } + @Nullable + protected AIController fallback(){ + return null; + } + + protected boolean useFallback(){ + return false; + } + protected UnitCommand command(){ return unit.team.data().command; } diff --git a/core/src/mindustry/game/DefaultWaves.java b/core/src/mindustry/game/DefaultWaves.java index e21b13ca8c..53a0bfa99c 100644 --- a/core/src/mindustry/game/DefaultWaves.java +++ b/core/src/mindustry/game/DefaultWaves.java @@ -119,6 +119,7 @@ public class DefaultWaves{ unitAmount = 2; unitScaling = 2; max = 20; + shieldScaling = 30; }}, new SpawnGroup(dagger){{ @@ -144,6 +145,7 @@ public class DefaultWaves{ unitAmount = 2; spacing = 2; unitScaling = 2; + shieldScaling = 20; }}, new SpawnGroup(flare){{ @@ -162,6 +164,7 @@ public class DefaultWaves{ unitScaling = 3; spacing = 5; max = 16; + shieldScaling = 30; }}, new SpawnGroup(nova){{ @@ -169,6 +172,7 @@ public class DefaultWaves{ unitAmount = 2; unitScaling = 3; spacing = 4; + shieldScaling = 20; }}, new SpawnGroup(atrax){{ @@ -192,15 +196,15 @@ public class DefaultWaves{ unitAmount = 1; unitScaling = 1; spacing = 40; - shieldScaling = 10f; + shieldScaling = 20f; }}, new SpawnGroup(antumbra){{ - begin = 131; + begin = 120; unitAmount = 1; unitScaling = 1; spacing = 40; - shieldScaling = 10f; + shieldScaling = 20f; }}, new SpawnGroup(vela){{ @@ -211,6 +215,15 @@ public class DefaultWaves{ shieldScaling = 20f; }}, + new SpawnGroup(corvus){{ + begin = 145; + unitAmount = 1; + unitScaling = 1; + spacing = 35; + shieldScaling = 30f; + shields = 100; + }}, + new SpawnGroup(horizon){{ begin = 90; unitAmount = 2; @@ -252,7 +265,7 @@ public class DefaultWaves{ //max reasonable wave, after which everything gets boring int cap = 200; - float shieldStart = 30, shieldsPerWave = 15 + difficulty*20f; + float shieldStart = 30, shieldsPerWave = 20 + difficulty*30f; Intc createProgression = start -> { //main sequence @@ -314,8 +327,8 @@ public class DefaultWaves{ step += (int)(Mathf.random(12, 25) * Mathf.lerp(1f, 0.4f, difficulty)); } - int bossWave = Mathf.random(30, 60); - int bossSpacing = Mathf.random(30, 50); + int bossWave = (int)(Mathf.random(30, 60) * Mathf.lerp(1f, 0.7f, difficulty)); + int bossSpacing = (int)(Mathf.random(25, 40) * Mathf.lerp(1f, 0.6f, difficulty)); //main boss progression out.add(new SpawnGroup(Structs.random(species)[4]){{ @@ -339,6 +352,14 @@ public class DefaultWaves{ shieldScaling = shieldsPerWave; }}); + //shift back waves on higher difficulty for a harder start + int shift = Math.max((int)(difficulty * 15 - 5), 0); + + for(SpawnGroup group : out){ + group.begin -= shift; + group.end -= shift; + } + return out; } } diff --git a/core/src/mindustry/game/Objectives.java b/core/src/mindustry/game/Objectives.java index 535f1d3e13..a4097c51fc 100644 --- a/core/src/mindustry/game/Objectives.java +++ b/core/src/mindustry/game/Objectives.java @@ -28,6 +28,7 @@ public class Objectives{ } } + //TODO fix public static class SectorComplete extends SectorObjective{ public SectorComplete(SectorPreset zone){ @@ -38,12 +39,12 @@ public class Objectives{ @Override public boolean complete(){ - return preset.sector.isCaptured(); + return preset.sector.save != null && preset.sector.save.meta.wave >= preset.sector.save.meta.rules.winWave; } @Override public String display(){ - return Core.bundle.format("requirement.capture", preset.localizedName); + return Core.bundle.format("requirement.wave", preset.sector.save == null ? "" : preset.sector.save.meta.rules.winWave, preset.localizedName); } } diff --git a/core/src/mindustry/game/Schematic.java b/core/src/mindustry/game/Schematic.java index 3aeb222d29..e539ae69a2 100644 --- a/core/src/mindustry/game/Schematic.java +++ b/core/src/mindustry/game/Schematic.java @@ -62,6 +62,10 @@ public class Schematic implements Publishable, Comparable{ return tags.get("name", "unknown"); } + public String description(){ + return tags.get("description", ""); + } + public void save(){ schematics.saveChanges(this); } @@ -90,7 +94,7 @@ public class Schematic implements Publishable, Comparable{ @Override public String steamDescription(){ - return null; + return description(); } @Override diff --git a/core/src/mindustry/game/SectorInfo.java b/core/src/mindustry/game/SectorInfo.java index dc56902c60..b66dc5bf95 100644 --- a/core/src/mindustry/game/SectorInfo.java +++ b/core/src/mindustry/game/SectorInfo.java @@ -10,6 +10,8 @@ import mindustry.world.*; import mindustry.world.blocks.storage.CoreBlock.*; import mindustry.world.modules.*; +import java.util.*; + import static mindustry.Vars.*; public class SectorInfo{ @@ -42,7 +44,15 @@ public class SectorInfo{ /** Counter refresh state. */ private transient Interval time = new Interval(); /** Core item storage to prevent spoofing. */ - private transient int[] lastCoreItems; + private transient int[] coreItemCounts; + + /** Handles core item changes. */ + public void handleCoreItem(Item item, int amount){ + if(coreItemCounts == null){ + coreItemCounts = new int[content.items().size]; + } + coreItemCounts[item.id] += amount; + } /** @return the real location items go when launched on this sector */ public Sector getRealDestination(){ @@ -105,12 +115,6 @@ public class SectorInfo{ universe.runTurn(); } - //create last stored core items - if(lastCoreItems == null){ - lastCoreItems = new int[content.items().size]; - updateCoreDeltas(); - } - CoreBuild ent = state.rules.defaultTeam.core(); //refresh throughput @@ -124,15 +128,16 @@ public class SectorInfo{ stat.loaded = true; } - //how the resources changed - only interested in negative deltas, since that's what happens during spoofing - int coreDelta = Math.min(ent == null ? 0 : ent.items.get(item) - lastCoreItems[item.id], 0); - //add counter, subtract how many items were taken from the core during this time - stat.means.add(Math.max(stat.counter + coreDelta, 0)); + stat.means.add(Math.max(stat.counter, 0)); stat.counter = 0; stat.mean = stat.means.rawMean(); }); + if(coreItemCounts == null){ + coreItemCounts = new int[content.items().size]; + } + //refresh core items for(Item item : content.items()){ ExportStat stat = production.get(item, ExportStat::new); @@ -143,21 +148,14 @@ public class SectorInfo{ //get item delta //TODO is preventing negative production a good idea? - int delta = Math.max((ent == null ? 0 : ent.items.get(item)) - lastCoreItems[item.id], 0); + int delta = Math.max(ent == null ? 0 : coreItemCounts[item.id], 0); //store means stat.means.add(delta); stat.mean = stat.means.rawMean(); } - updateCoreDeltas(); - } - } - - private void updateCoreDeltas(){ - CoreBuild ent = state.rules.defaultTeam.core(); - for(int i = 0; i < lastCoreItems.length; i++){ - lastCoreItems[i] = ent == null ? 0 : ent.items.get(i); + Arrays.fill(coreItemCounts, 0); } } diff --git a/core/src/mindustry/game/SpawnGroup.java b/core/src/mindustry/game/SpawnGroup.java index 28edfd0a1a..f9ae7c74d3 100644 --- a/core/src/mindustry/game/SpawnGroup.java +++ b/core/src/mindustry/game/SpawnGroup.java @@ -28,7 +28,7 @@ public class SpawnGroup implements Serializable{ /** The spacing, in waves, of spawns. For example, 2 = spawns every other wave */ public int spacing = 1; /** Maximum amount of units that spawn */ - public int max = 100; + public int max = 40; /** How many waves need to pass before the amount of units spawned increases by 1 */ public float unitScaling = never; /** Shield points that this unit has. */ @@ -88,7 +88,7 @@ public class SpawnGroup implements Serializable{ if(begin != 0) json.writeValue("begin", begin); if(end != never) json.writeValue("end", end); if(spacing != 1) json.writeValue("spacing", spacing); - //if(max != 40) json.writeValue("max", max); + if(max != 40) json.writeValue("max", max); if(unitScaling != never) json.writeValue("scaling", unitScaling); if(shields != 0) json.writeValue("shields", shields); if(shieldScaling != 0) json.writeValue("shieldScaling", shieldScaling); @@ -105,12 +105,18 @@ public class SpawnGroup implements Serializable{ begin = data.getInt("begin", 0); end = data.getInt("end", never); spacing = data.getInt("spacing", 1); - //max = data.getInt("max", 40); + max = data.getInt("max", 40); unitScaling = data.getFloat("scaling", never); shields = data.getFloat("shields", 0); shieldScaling = data.getFloat("shieldScaling", 0); unitAmount = data.getInt("amount", 1); - effect = content.getByName(ContentType.status, data.hasChild("effect") && data.getChild("effect").isString() ? data.getString("effect", "none") : "none"); + + //old boss effect ID + if(data.has("effect") && data.get("effect").isNumber() && data.getInt("effect", -1) == 8){ + effect = StatusEffects.boss; + }else{ + effect = content.getByName(ContentType.status, data.has("effect") && data.get("effect").isString() ? data.getString("effect", "none") : "none"); + } } @Override diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index b2c9c7d039..892b93b731 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -140,8 +140,9 @@ public class Universe{ if(!sector.isBeingPlayed()){ sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed); + //TODO sector damage disabled for now //check if the sector has been attacked too many times... - if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){ + /*if(sector.hasBase() && sector.hasWaves() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){ //fire event for losing the sector Events.fire(new SectorLoseEvent(sector)); @@ -151,17 +152,17 @@ public class Universe{ //clear recieved sector.setExtraItems(new ItemSeq()); sector.save = null; - } + }*/ } //export to another sector if(sector.save != null && sector.save.meta != null && sector.save.meta.secinfo != null && sector.save.meta.secinfo.destination != null){ Sector to = sector.save.meta.secinfo.destination; if(to.save != null){ - ItemSeq items = to.getExtraItems(); + ItemSeq items = new ItemSeq(); //calculated exported items to this sector sector.save.meta.secinfo.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed))); - to.setExtraItems(items); + to.addItems(items); } } diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index ea988e5e78..19569ed1a3 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -7,6 +7,7 @@ import arc.math.*; import arc.math.geom.*; import arc.util.*; import mindustry.*; +import mindustry.ai.types.*; import mindustry.gen.*; import mindustry.input.*; import mindustry.ui.*; @@ -151,6 +152,13 @@ public class OverlayRenderer{ input.drawOverSelect(); + if(ui.hudfrag.blockfrag.hover() instanceof Unit unit && unit.controller() instanceof LogicAI ai && ai.controller instanceof Building build){ + Drawf.square(build.x, build.y, build.block.size * tilesize/2f + 2f); + if(!unit.within(build, unit.hitSize * 2f)){ + Drawf.arrow(unit.x, unit.y, build.x, build.y, unit.hitSize *2f, 4f); + } + } + //draw selection overlay when dropping item if(input.isDroppingItem()){ Vec2 v = Core.input.mouseWorld(input.getMouseX(), input.getMouseY()); diff --git a/core/src/mindustry/input/DesktopInput.java b/core/src/mindustry/input/DesktopInput.java index 0b21a2cea4..7099f254dd 100644 --- a/core/src/mindustry/input/DesktopInput.java +++ b/core/src/mindustry/input/DesktopInput.java @@ -176,7 +176,7 @@ public class DesktopInput extends InputHandler{ public void update(){ super.update(); - if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content))){ + if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content) || scene.getKeyboardFocus().isDescendantOf(ui.minimapfrag.elem))){ ui.listfrag.toggle(); } diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index 3f4d4caecd..eebe40e15b 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -256,7 +256,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ } @Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true) - public static void rotateBlock(Player player, Building tile, boolean direction){ + public static void rotateBlock(@Nullable Player player, Building tile, boolean direction){ if(tile == null) return; if(net.server() && (!Units.canInteract(player, tile) || @@ -264,6 +264,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ throw new ValidateException(player, "Player cannot rotate a block."); } + if(player != null) tile.lastAccessed = player.name; tile.rotation = Mathf.mod(tile.rotation + Mathf.sign(direction), 4); tile.updateProximity(); tile.noSleep(); @@ -531,6 +532,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ }); }else{ lastSchematic.tags.put("name", text); + lastSchematic.tags.put("description", ""); schematics.add(lastSchematic); ui.showInfoFade("@schematic.saved"); ui.schematics.showInfo(lastSchematic); diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index 066ddc0efe..669cd65d53 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -290,12 +290,15 @@ public class TypeIO{ public static void writeController(Writes write, UnitController control){ //no real unit controller state is written, only the type - if(control instanceof Player){ + if(control instanceof Player p){ write.b(0); - write.i(((Player)control).id); - }else if(control instanceof FormationAI){ + write.i(p.id); + }else if(control instanceof FormationAI form){ write.b(1); - write.i(((FormationAI)control).leader.id); + write.i(form.leader.id); + }else if(control instanceof LogicAI logic && logic.controller != null){ + write.b(3); + write.i(logic.controller.pos()); }else{ write.b(2); } @@ -312,6 +315,19 @@ public class TypeIO{ }else if(type == 1){ //formation controller int id = read.i(); return prev instanceof FormationAI ? prev : new FormationAI(Groups.unit.getByID(id), null); + }else if(type == 3){ + int pos = read.i(); + if(prev instanceof LogicAI pai){ + pai.controller = world.build(pos); + return pai; + }else{ + //create new AI for assignment + LogicAI out = new LogicAI(); + //instantly time out when updated. + out.controlTimer = LogicAI.logicControlTimeout; + out.controller = world.build(pos); + return out; + } }else{ //there are two cases here: //1: prev controller was not a player, carry on diff --git a/core/src/mindustry/logic/LAssembler.java b/core/src/mindustry/logic/LAssembler.java index e355416427..4f72d121aa 100644 --- a/core/src/mindustry/logic/LAssembler.java +++ b/core/src/mindustry/logic/LAssembler.java @@ -52,6 +52,9 @@ public class LAssembler{ } } + //used as a special value for any environmental solid block + putConst("@solid", Blocks.stoneWall); + putConst("@air", Blocks.air); for(UnitType type : Vars.content.units()){ diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 93c5263a9b..3bcfaf46ee 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -6,6 +6,7 @@ import arc.util.*; import arc.util.noise.*; import mindustry.*; import mindustry.ai.types.*; +import mindustry.content.*; import mindustry.ctype.*; import mindustry.entities.*; import mindustry.game.*; @@ -251,6 +252,11 @@ public class LExecutor{ case spawn -> { res = Geometry.findClosest(unit.x, unit.y, Vars.spawner.getSpawns()); } + case damaged -> { + Building b = Units.findDamagedTile(unit.team, unit.x, unit.y); + res = b == null ? null : b.tile; + build = true; + } } if(res != null && (!build || res.build != null)){ @@ -435,12 +441,16 @@ public class LExecutor{ } case getBlock -> { float x = exec.numf(p1), y = exec.numf(p2); - if(unit.within(x, y, unit.range())){ + float range = Math.max(unit.range(), buildingRange); + if(!unit.within(x, y, range)){ exec.setobj(p3, null); + exec.setnum(p4, 0); }else{ Tile tile = world.tileWorld(x, y); - Block block = tile == null || !tile.synthetic() ? null : tile.block(); + //any environmental solid block is returned as StoneWall, aka "@solid" + Block block = tile == null ? null : !tile.synthetic() ? (tile.solid() ? Blocks.stoneWall : Blocks.air) : tile.block(); exec.setobj(p3, block); + exec.setnum(p4, tile != null && tile.build != null ? tile.build.rotation : 0); } } case itemDrop -> { diff --git a/core/src/mindustry/logic/LLocate.java b/core/src/mindustry/logic/LLocate.java index 679136e74f..84da2321f9 100644 --- a/core/src/mindustry/logic/LLocate.java +++ b/core/src/mindustry/logic/LLocate.java @@ -3,7 +3,8 @@ package mindustry.logic; public enum LLocate{ ore, building, - spawn; + spawn, + damaged; public static final LLocate[] all = values(); } diff --git a/core/src/mindustry/logic/LStatements.java b/core/src/mindustry/logic/LStatements.java index a96adaf9d9..ba4d31e9a1 100644 --- a/core/src/mindustry/logic/LStatements.java +++ b/core/src/mindustry/logic/LStatements.java @@ -889,7 +889,7 @@ public class LStatements{ table.row(); } - case spawn -> { + case spawn, damaged -> { table.row(); } } diff --git a/core/src/mindustry/logic/LUnitControl.java b/core/src/mindustry/logic/LUnitControl.java index 879578e59b..2166b00690 100644 --- a/core/src/mindustry/logic/LUnitControl.java +++ b/core/src/mindustry/logic/LUnitControl.java @@ -15,7 +15,7 @@ public enum LUnitControl{ mine("x", "y"), flag("value"), build("x", "y", "block", "rotation"), - getBlock("x", "y", "result"), + getBlock("x", "y", "result", "resRot"), within("x", "y", "radius", "result"); public final String[] params; diff --git a/core/src/mindustry/maps/Map.java b/core/src/mindustry/maps/Map.java index edef90cea7..0fdc818139 100644 --- a/core/src/mindustry/maps/Map.java +++ b/core/src/mindustry/maps/Map.java @@ -98,7 +98,9 @@ public class Map implements Comparable, Publishable{ public Rules rules(Rules base){ try{ - Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}")); + //this replacement is a MASSIVE hack but it fixes some incorrect overwriting of team-specific rules. + //may need to be tweaked later + Rules result = JsonIO.read(Rules.class, base, tags.get("rules", "{}").replace("teams:{2:{infiniteAmmo:true}},", "")); if(result.spawns.isEmpty()) result.spawns = Vars.defaultWaves.get(); return result; }catch(Exception e){ diff --git a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java index 781bbdb7b5..ee2f6b437c 100644 --- a/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java +++ b/core/src/mindustry/maps/planet/SerpuloPlanetGenerator.java @@ -145,7 +145,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ float constraint = 1.3f; float radius = width / 2f / Mathf.sqrt3; int rooms = rand.random(2, 5); - Seq array = new Seq<>(); + Seq roomseq = new Seq<>(); for(int i = 0; i < rooms; i++){ Tmp.v1.trns(rand.random(360f), rand.random(radius / constraint)); @@ -153,7 +153,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ float ry = (height/2f + Tmp.v1.y); float maxrad = radius - Tmp.v1.len(); float rrad = Math.min(rand.random(9f, maxrad / 2f), 30f); - array.add(new Room((int)rx, (int)ry, (int)rrad)); + roomseq.add(new Room((int)rx, (int)ry, (int)rrad)); } //check positions on the map to place the player spawn. this needs to be in the corner of the map @@ -182,13 +182,13 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } if(waterTiles <= 4 || (i + angleStep >= 360)){ - array.add(spawn = new Room(cx, cy, rand.random(8, 15))); + roomseq.add(spawn = new Room(cx, cy, rand.random(8, 15))); for(int j = 0; j < enemySpawns; j++){ float enemyOffset = rand.range(60f); Tmp.v1.set(cx - width/2, cy - height/2).rotate(180f + enemyOffset).add(width/2, height/2); Room espawn = new Room((int)Tmp.v1.x, (int)Tmp.v1.y, rand.random(8, 15)); - array.add(espawn); + roomseq.add(espawn); enemies.add(espawn); } @@ -196,16 +196,16 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } } - for(Room room : array){ + for(Room room : roomseq){ erase(room.x, room.y, room.radius); } int connections = rand.random(Math.max(rooms - 1, 1), rooms + 3); for(int i = 0; i < connections; i++){ - array.random(rand).connect(array.random(rand)); + roomseq.random(rand).connect(roomseq.random(rand)); } - for(Room room : array){ + for(Room room : roomseq){ spawn.connect(room); } @@ -252,10 +252,6 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } }); - for(Room espawn : enemies){ - tiles.getn(espawn.x, espawn.y).setOverlay(Blocks.spawn); - } - trimDark(); median(2); @@ -273,7 +269,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ //tar if(floor == Blocks.darksand){ if(Math.abs(0.5f - noise(x - 40, y, 2, 0.7, 80)) > 0.25f && - Math.abs(0.5f - noise(x, y + sector.id*10, 1, 1, 60)) > 0.41f){ + Math.abs(0.5f - noise(x, y + sector.id*10, 1, 1, 60)) > 0.41f && !(roomseq.contains(r -> Mathf.within(x, y, r.x, r.y, 15)))){ floor = Blocks.tar; ore = Blocks.air; } @@ -298,6 +294,23 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ } } + if(rand.chance(0.0075)){ + //random spore trees + boolean any = false; + boolean all = true; + for(Point2 p : Geometry.d4){ + Tile other = tiles.get(x + p.x, y + p.y); + if(other != null && other.block() == Blocks.air){ + any = true; + }else{ + all = false; + } + } + if(any && ((block == Blocks.snowWall || block == Blocks.iceWall) || (all && block == Blocks.air && floor == Blocks.snow && rand.chance(0.03)))){ + block = rand.chance(0.5) ? Blocks.whiteTree : Blocks.whiteTreeDead; + } + } + //random stuff dec: { for(int i = 0; i < 4; i++){ @@ -392,6 +405,10 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{ Schematics.placeLaunchLoadout(spawn.x, spawn.y); + for(Room espawn : enemies){ + tiles.getn(espawn.x, espawn.y).setOverlay(Blocks.spawn); + } + if(sector.hasEnemyBase()){ basegen.generate(tiles, enemies.map(r -> tiles.getn(r.x, r.y)), tiles.get(spawn.x, spawn.y), state.rules.waveTeam, sector, difficulty); diff --git a/core/src/mindustry/mod/ContentParser.java b/core/src/mindustry/mod/ContentParser.java index 38f4db6a82..b9c119ee32 100644 --- a/core/src/mindustry/mod/ContentParser.java +++ b/core/src/mindustry/mod/ContentParser.java @@ -603,7 +603,7 @@ public class ContentParser{ customRequirements = null; }else{ researchName = research.getString("parent"); - customRequirements = research.hasChild("requirements") ? parser.readValue(ItemStack[].class, research.getChild("requirements")) : null; + customRequirements = research.has("requirements") ? parser.readValue(ItemStack[].class, research.get("requirements")) : null; } //remove old node diff --git a/core/src/mindustry/type/Sector.java b/core/src/mindustry/type/Sector.java index 6d042296d1..4c1df28945 100644 --- a/core/src/mindustry/type/Sector.java +++ b/core/src/mindustry/type/Sector.java @@ -8,6 +8,7 @@ import arc.util.*; import mindustry.*; import mindustry.game.Saves.*; import mindustry.graphics.g3d.PlanetGrid.*; +import mindustry.world.modules.*; import static mindustry.Vars.*; @@ -143,9 +144,17 @@ public class Sector{ } public void removeItem(Item item, int amount){ + ItemSeq seq = new ItemSeq(); + seq.add(item, -amount); + addItems(seq); + } + + public void addItems(ItemSeq items){ if(isBeingPlayed()){ if(state.rules.defaultTeam.core() != null){ - state.rules.defaultTeam.items().remove(item, amount); + ItemModule storage = state.rules.defaultTeam.items(); + int cap = state.rules.defaultTeam.core().storageCapacity; + items.each((item, amount) -> storage.add(item, Math.min(cap - storage.get(item), amount))); } }else{ ItemSeq recv = getExtraItems(); @@ -170,7 +179,7 @@ public class Sector{ }); } - recv.remove(item, amount); + recv.add(items); setExtraItems(recv); } diff --git a/core/src/mindustry/type/StatusEffect.java b/core/src/mindustry/type/StatusEffect.java index fda8dbbbf2..eb0bedb60c 100644 --- a/core/src/mindustry/type/StatusEffect.java +++ b/core/src/mindustry/type/StatusEffect.java @@ -13,8 +13,8 @@ import mindustry.gen.*; public class StatusEffect extends MappableContent{ /** Damage dealt by the unit with the effect. */ public float damageMultiplier = 1f; - /** Unit armor multiplier. */ - public float armorMultiplier = 1f; + /** Unit health multiplier. */ + public float healthMultiplier = 1f; /** Unit speed multiplier */ public float speedMultiplier = 1f; /** Unit speed multiplier */ diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index e14629b594..42fac7be72 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -24,8 +24,8 @@ import static arc.util.Time.*; import static mindustry.Vars.*; public class CustomRulesDialog extends BaseDialog{ - private Table main; Rules rules; + private Table main; private Prov resetter; private LoadoutDialog loadoutDialog; private BaseDialog banDialog; @@ -166,7 +166,7 @@ public class CustomRulesDialog extends BaseDialog{ title("@rules.title.enemy"); check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode); - check("@rules.buildai", b -> rules.waveTeam.rules().ai = b, () -> rules.waveTeam.rules().ai); + check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai); number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200)); title("@rules.title.environment"); diff --git a/core/src/mindustry/ui/dialogs/PausedDialog.java b/core/src/mindustry/ui/dialogs/PausedDialog.java index 559450a591..f09e2fe185 100644 --- a/core/src/mindustry/ui/dialogs/PausedDialog.java +++ b/core/src/mindustry/ui/dialogs/PausedDialog.java @@ -35,17 +35,18 @@ public class PausedDialog extends BaseDialog{ if(!mobile){ //TODO localize - cont.label(() -> state.getSector() == null ? "" : - ("[lightgray]Next turn in [accent]" + state.getSector().displayTimeRemaining() + - (state.rules.winWave > 0 && !state.getSector().isCaptured() ? "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture" : ""))) - .visible(() -> state.getSector() != null).colspan(2); + //TODO capturing is disabled, remove? + //cont.label(() -> state.getSector() == null ? "" : + //("[lightgray]Next turn in [accent]" + state.getSector().displayTimeRemaining() + + // (state.rules.winWave > 0 && !state.getSector().isCaptured() ? "\n[lightgray]Reach wave[accent] " + state.rules.winWave + "[] to capture" : ""))) + // .visible(() -> state.getSector() != null).colspan(2); cont.row(); float dw = 220f; cont.defaults().width(dw).height(55).pad(5f); - cont.button("@back", Icon.left, this::hide); - cont.button("@settings", Icon.settings, ui.settings::show); + cont.button("@back", Icon.left, this::hide).name("back"); + cont.button("@settings", Icon.settings, ui.settings::show).name("settings"); if(!state.rules.tutorial){ if(!state.isCampaign() && !state.isEditor()){ diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index a7b667dcd8..dd63a1afeb 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -32,18 +32,18 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //if true, enables launching anywhere for testing public static boolean debugSelect = false; - final FrameBuffer buffer = new FrameBuffer(2, 2, true); - final PlanetRenderer planets = renderer.planets; - final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog(); - final Table stable = new Table().background(Styles.black3); + public final FrameBuffer buffer = new FrameBuffer(2, 2, true); + public final PlanetRenderer planets = renderer.planets; + public final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog(); + public final Table stable = new Table().background(Styles.black3); - int launchRange; - float zoom = 1f, selectAlpha = 1f; - @Nullable Sector selected, hovered, launchSector; - CoreBuild launcher; - Mode mode = look; - boolean launching; - Cons listener = s -> {}; + public int launchRange; + public float zoom = 1f, selectAlpha = 1f; + public @Nullable Sector selected, hovered, launchSector; + public CoreBuild launcher; + public Mode mode = look; + public boolean launching; + public Cons listener = s -> {}; public PlanetDialog(){ super("", Styles.fullDialog); @@ -237,7 +237,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ cont.clear(); titleTable.remove(); - cont.stack( new Element(){ { @@ -263,9 +262,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } }, new Table(t -> { + t.touchable = Touchable.disabled; //TODO localize t.top(); - t.label(() -> mode == select ? "@sectors.select" : mode == launch ? "Select Launch Sector" : "Turn " + universe.turn()).style(Styles.outlineLabel).color(Pal.accent); + t.label(() -> mode == select ? "@sectors.select" : mode == launch ? "Select Launch Sector" : "").style(Styles.outlineLabel).color(Pal.accent); })).grow(); } @@ -338,6 +338,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ stable.add("[accent]Difficulty: " + (int)(sector.baseCoverage * 10)).row(); } + //TODO sector damage is disabled, remove when finalized + /* if(sector.hasBase() && sector.hasWaves()){ //TODO localize when finalized //these mechanics are likely to change and as such are not added to the bundle @@ -345,7 +347,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ stable.row(); stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s)\nuntil destruction"); stable.row(); - } + }*/ if(sector.save != null){ stable.add("@sectors.resources").row(); @@ -468,7 +470,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ stable.act(0f); } - enum Mode{ + public enum Mode{ /** Look around for existing sectors. Can only deploy. */ look, /** Launch to a new location. */ diff --git a/core/src/mindustry/ui/dialogs/ResearchDialog.java b/core/src/mindustry/ui/dialogs/ResearchDialog.java index a87aa2d55c..9c5e6fd3ed 100644 --- a/core/src/mindustry/ui/dialogs/ResearchDialog.java +++ b/core/src/mindustry/ui/dialogs/ResearchDialog.java @@ -31,14 +31,14 @@ import java.util.*; import static mindustry.Vars.*; public class ResearchDialog extends BaseDialog{ - final float nodeSize = Scl.scl(60f); - ObjectSet nodes = new ObjectSet<>(); - TechTreeNode root = new TechTreeNode(TechTree.root, null); - Rect bounds = new Rect(); - ItemsDisplay itemDisplay; - View view; + public final float nodeSize = Scl.scl(60f); + public ObjectSet nodes = new ObjectSet<>(); + public TechTreeNode root = new TechTreeNode(TechTree.root, null); + public Rect bounds = new Rect(); + public ItemsDisplay itemDisplay; + public View view; - ItemSeq items; + public ItemSeq items; public ResearchDialog(){ super(""); @@ -114,7 +114,7 @@ public class ResearchDialog extends BaseDialog{ buttons.button("@database", Icon.book, () -> { hide(); ui.database.show(); - }).size(210f, 64f); + }).size(210f, 64f).name("database"); //scaling/drag input addListener(new InputListener(){ @@ -250,7 +250,7 @@ public class ResearchDialog extends BaseDialog{ return node.content.unlocked() || !node.objectives.contains(i -> !i.complete()); } - void showToast(String info){ + public void showToast(String info){ Table table = new Table(); table.actions(Actions.fadeOut(0.5f, Interp.fade), Actions.remove()); table.top().add(info); @@ -279,11 +279,11 @@ public class ResearchDialog extends BaseDialog{ } } - class TechTreeNode extends TreeNode{ - final TechNode node; - boolean visible = true, selectable = true; + public class TechTreeNode extends TreeNode{ + public final TechNode node; + public boolean visible = true, selectable = true; - TechTreeNode(TechNode node, TechTreeNode parent){ + public TechTreeNode(TechNode node, TechTreeNode parent){ this.node = node; this.parent = parent; this.width = this.height = nodeSize; @@ -297,11 +297,11 @@ public class ResearchDialog extends BaseDialog{ } } - class View extends Group{ - float panX = 0, panY = -200, lastZoom = -1; - boolean moved = false; - ImageButton hoverNode; - Table infoTable = new Table(); + public class View extends Group{ + public float panX = 0, panY = -200, lastZoom = -1; + public boolean moved = false; + public ImageButton hoverNode; + public Table infoTable = new Table(); { infoTable.touchable = Touchable.enabled; @@ -400,7 +400,8 @@ public class ResearchDialog extends BaseDialog{ } } - return false; + //can always spend when locked + return node.content.locked(); } void spend(TechNode node){ @@ -414,7 +415,7 @@ public class ResearchDialog extends BaseDialog{ ItemStack completed = node.finishedRequirements[i]; //amount actually taken from inventory - int used = Math.min(req.amount - completed.amount, items.get(req.item)); + int used = Math.max(Math.min(req.amount - completed.amount, items.get(req.item)), 0); items.remove(req.item, used); completed.amount += used; diff --git a/core/src/mindustry/ui/dialogs/SchematicsDialog.java b/core/src/mindustry/ui/dialogs/SchematicsDialog.java index 99a087d39f..8c99272e89 100644 --- a/core/src/mindustry/ui/dialogs/SchematicsDialog.java +++ b/core/src/mindustry/ui/dialogs/SchematicsDialog.java @@ -4,6 +4,7 @@ import arc.*; import arc.graphics.*; import arc.graphics.Texture.*; import arc.graphics.g2d.*; +import arc.input.*; import arc.math.*; import arc.scene.style.*; import arc.scene.ui.*; @@ -106,18 +107,36 @@ public class SchematicsDialog extends BaseDialog{ }); buttons.button(Icon.pencil, style, () -> { - ui.showTextInput("@schematic.rename", "@name", s.name(), res -> { - Schematic replacement = schematics.all().find(other -> other.name().equals(res) && other != s); - if(replacement != null){ - //renaming to an existing schematic is not allowed, as it is not clear how the tags would be merged, and which one should be removed - ui.showErrorMessage("@schematic.exists"); - return; - } + new Dialog("@schematic.rename"){{ + cont.margin(30).add("@name").padRight(6f); + TextField nameField = cont.field(s.name(), null).size(400f, 55f).addInputDialog().get(); - s.tags.put("name", res); - s.save(); - rebuildPane[0].run(); - }); + cont.row(); + + cont.margin(30).add("@editor.description").padRight(6f); + TextField descripionField = cont.area(s.description(), Styles.areaField, t -> {}).size(400f, 140f).addInputDialog().get(); + + Runnable accept = () -> { + s.tags.put("name", nameField.getText()); + s.tags.put("description", descripionField.getText()); + s.save(); + hide(); + rebuildPane[0].run(); + }; + + buttons.defaults().size(120, 54).pad(4); + buttons.button("@ok", accept).disabled(b -> nameField.getText().isEmpty()); + buttons.button("@cancel", this::hide); + + keyDown(KeyCode.enter, () -> { + if(!nameField.getText().isEmpty() && Core.scene.getKeyboardFocus() != descripionField){ + accept.run(); + } + }); + keyDown(KeyCode.escape, this::hide); + keyDown(KeyCode.back, this::hide); + show(); + }}; }); if(s.hasSteamID()){ diff --git a/core/src/mindustry/ui/fragments/HudFragment.java b/core/src/mindustry/ui/fragments/HudFragment.java index 38ae0cd1f2..f56d507c4d 100644 --- a/core/src/mindustry/ui/fragments/HudFragment.java +++ b/core/src/mindustry/ui/fragments/HudFragment.java @@ -15,6 +15,7 @@ import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import mindustry.annotations.Annotations.*; +import mindustry.content.*; import mindustry.core.GameState.*; import mindustry.ctype.*; import mindustry.game.EventType.*; @@ -47,6 +48,26 @@ public class HudFragment extends Fragment{ @Override public void build(Group parent){ + //warn about guardian/boss waves + Events.on(WaveEvent.class, e -> { + int max = 10; + outer: + for(int i = state.wave - 1; i <= state.wave + max; i++){ + for(SpawnGroup group : state.rules.spawns){ + if(group.effect == StatusEffects.boss && group.getUnitsSpawned(i) > 0){ + int diff = (i + 2) - state.wave; + + //increments at which to warn about incoming guardian + if(diff == 1 || diff == 2 || diff == 5 || diff == 10){ + showToast(Icon.warning, Core.bundle.format("wave.guardianwarn" + (diff == 1 ? ".one" : ""), diff)); + } + + break outer; + } + } + } + }); + //TODO details and stuff Events.on(SectorCaptureEvent.class, e ->{ //TODO localize @@ -63,36 +84,25 @@ public class HudFragment extends Fragment{ coreItems.clear(); }); - Events.on(TurnEvent.class, e -> { - Seq attacked = universe.getAttacked(state.getSector().planet); - - if(attacked.any()){ - - //TODO localize - String text = attacked.size > 1 ? attacked.size + " sectors attacked." : "Sector " + attacked.first().id + " under attack."; - - showToast(Icon.warning, text); - } - - //ui.announce("[accent][[ Turn " + universe.turn() + " ]\n[scarlet]" + attackedSectors.size + "[lightgray] sector(s) attacked."); - }); - //paused table parent.fill(t -> { + t.name = "paused"; t.top().visible(() -> state.isPaused()).touchable = Touchable.disabled; t.table(Styles.black5, top -> top.add("@paused").style(Styles.outlineLabel).pad(8f)).growX(); }); //minimap + position parent.fill(t -> { + t.name = "minimap/position"; t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial && shown); //minimap - t.add(new Minimap()); + t.add(new Minimap()).name("minimap"); t.row(); //position t.label(() -> player.tileX() + "," + player.tileY()) .visible(() -> Core.settings.getBool("position") && !state.rules.tutorial) - .touchable(Touchable.disabled); + .touchable(Touchable.disabled) + .name("position"); t.top().right(); }); @@ -104,15 +114,18 @@ public class HudFragment extends Fragment{ if(mobile){ cont.table(select -> { + select.name = "mobile buttons"; select.left(); select.defaults().size(dsize).left(); ImageButtonStyle style = Styles.clearTransi; - select.button(Icon.menu, style, ui.paused::show); + select.button(Icon.menu, style, ui.paused::show).name("menu"); flip = select.button(Icon.upOpen, style, this::toggleMenus).get(); + flip.name = "flip"; - select.button(Icon.paste, style, ui.schematics::show); + select.button(Icon.paste, style, ui.schematics::show) + .name("schematics"); select.button(Icon.pause, style, () -> { if(net.active()){ @@ -141,7 +154,7 @@ public class HudFragment extends Fragment{ }else{ ui.database.show(); } - }).update(i -> { + }).name("chat").update(i -> { if(net.active() && mobile){ i.getStyle().imageUp = Icon.chat; }else if(state.isCampaign()){ @@ -167,14 +180,15 @@ public class HudFragment extends Fragment{ Table wavesMain, editorMain; - cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight()); + cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight()) + .name("waves/editor"); wavesMain.visible(() -> shown && !state.isEditor()); - wavesMain.top().left(); + wavesMain.top().left().name = "waves"; wavesMain.table(s -> { //wave info button with text - s.add(makeStatusTable()).grow(); + s.add(makeStatusTable()).grow().name("status"); //table with button to skip wave s.button(Icon.play, Styles.righti, 30f, () -> { @@ -183,18 +197,22 @@ public class HudFragment extends Fragment{ }else{ logic.skipWave(); } - }).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()).visible(() -> state.rules.waves); + }).growY().fillX().right().width(40f).disabled(b -> !canSkipWave()) + .visible(() -> state.rules.waves).name("skip"); }).width(dsize * 5 + 4f); wavesMain.row(); wavesMain.table(Tex.button, t -> t.margin(10f).add(new Bar("boss.health", Pal.health, () -> state.boss() == null ? 0f : state.boss().healthf()).blink(Color.white)) - .grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get(); + .grow()).fillX().visible(() -> state.rules.waves && state.boss() != null).height(60f).get() + .name = "boss"; wavesMain.row(); + editorMain.name = "editor"; editorMain.table(Tex.buttonEdge4, t -> { //t.margin(0f); + t.name = "teams"; t.add("@editor.teams").growX().left(); t.row(); t.table(teams -> { @@ -215,29 +233,33 @@ public class HudFragment extends Fragment{ }).width(dsize * 5 + 4f); editorMain.visible(() -> shown && state.isEditor()); - //fps display cont.table(info -> { + info.name = "fps/ping"; info.touchable = Touchable.disabled; info.top().left().margin(4).visible(() -> Core.settings.getBool("fps") && shown); info.update(() -> info.setTranslation(state.rules.waves || state.isEditor() ? 0f : -Scl.scl(dsize * 4 + 3), 0)); IntFormat fps = new IntFormat("fps"); IntFormat ping = new IntFormat("ping"); - info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left().style(Styles.outlineLabel); + info.label(() -> fps.get(Core.graphics.getFramesPerSecond())).left() + .style(Styles.outlineLabel).name("fps"); info.row(); - info.label(() -> ping.get(netClient.getPing())).visible(net::client).left().style(Styles.outlineLabel); + info.label(() -> ping.get(netClient.getPing())).visible(net::client).left() + .style(Styles.outlineLabel).name("ping"); }).top().left(); }); //core items parent.fill(t -> { + t.name = "coreitems"; t.top().add(coreItems); t.visible(() -> Core.settings.getBool("coreitems") && !mobile && !state.isPaused() && shown); }); //spawner warning parent.fill(t -> { + t.name = "nearpoint"; t.touchable = Touchable.disabled; t.table(Styles.black, c -> c.add("@nearpoint") .update(l -> l.setColor(Tmp.c1.set(Color.white).lerp(Color.scarlet, Mathf.absin(Time.time(), 10f, 1f)))) @@ -246,12 +268,14 @@ public class HudFragment extends Fragment{ }); parent.fill(t -> { + t.name = "waiting"; t.visible(() -> netServer.isWaitingForPlayers()); t.table(Tex.button, c -> c.add("@waiting.players")); }); //'core is under attack' table parent.fill(t -> { + t.name = "coreattack"; t.touchable = Touchable.disabled; float notifDuration = 240f; float[] coreAttackTime = {0}; @@ -285,6 +309,7 @@ public class HudFragment extends Fragment{ //tutorial text parent.fill(t -> { + t.name = "tutorial"; Runnable resize = () -> { t.clearChildren(); t.top().right().visible(() -> state.rules.tutorial); @@ -307,11 +332,13 @@ public class HudFragment extends Fragment{ //'saving' indicator parent.fill(t -> { + t.name = "saving"; t.bottom().visible(() -> control.saves.isSaving()); t.add("@saving").style(Styles.outlineLabel); }); parent.fill(p -> { + p.name = "hudtext"; p.top().table(Styles.black3, t -> t.margin(4).label(() -> hudText) .style(Styles.outlineLabel)).padTop(10).visible(p.color.a >= 0.001f); p.update(() -> { @@ -327,6 +354,7 @@ public class HudFragment extends Fragment{ //TODO DEBUG: rate table if(false) parent.fill(t -> { + t.name = "rates"; t.bottom().left(); t.table(Styles.black6, c -> { Bits used = new Bits(content.items().size); diff --git a/core/src/mindustry/ui/fragments/MenuFragment.java b/core/src/mindustry/ui/fragments/MenuFragment.java index 45faa9bf10..cd8d13be0d 100644 --- a/core/src/mindustry/ui/fragments/MenuFragment.java +++ b/core/src/mindustry/ui/fragments/MenuFragment.java @@ -45,6 +45,7 @@ public class MenuFragment extends Fragment{ parent.fill(c -> { container = c; + c.name = "menu container"; if(!mobile){ buildDesktop(); @@ -57,8 +58,8 @@ public class MenuFragment extends Fragment{ //info icon if(mobile){ - parent.fill(c -> c.bottom().left().button("", Styles.infot, ui.about::show).size(84, 45)); - parent.fill(c -> c.bottom().right().button("", Styles.discordt, ui.discord::show).size(84, 45)); + parent.fill(c -> c.bottom().left().button("", Styles.infot, ui.about::show).size(84, 45).name("info")); + parent.fill(c -> c.bottom().right().button("", Styles.discordt, ui.discord::show).size(84, 45).name("discord")); }else if(becontrol.active()){ parent.fill(c -> c.bottom().right().button("@be.check", Icon.refresh, () -> { ui.loadfrag.show(); @@ -68,7 +69,7 @@ public class MenuFragment extends Fragment{ ui.showInfo("@be.noupdates"); } }); - }).size(200, 60).update(t -> { + }).size(200, 60).name("becheck").update(t -> { t.getLabel().setColor(becontrol.isUpdateAvailable() ? Tmp.c1.set(Color.white).lerp(Pal.accent, Mathf.absin(5f, 1f)) : Color.white); })); } @@ -93,6 +94,7 @@ public class MenuFragment extends Fragment{ private void buildMobile(){ container.clear(); + container.name = "buttons"; container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight()); float size = 120f; @@ -153,7 +155,6 @@ public class MenuFragment extends Fragment{ container.clear(); container.setSize(Core.graphics.getWidth(), Core.graphics.getHeight()); - float width = 230f; Drawable background = Styles.black6; @@ -161,6 +162,7 @@ public class MenuFragment extends Fragment{ container.add().width(Core.graphics.getWidth()/10f); container.table(background, t -> { t.defaults().width(width).height(70f); + t.name = "buttons"; buttons(t, new Buttoni("@play", Icon.play, @@ -183,6 +185,7 @@ public class MenuFragment extends Fragment{ container.table(background, t -> { submenu = t; + t.name = "submenu"; t.color.a = 0f; t.top(); t.defaults().width(width).height(70f); diff --git a/core/src/mindustry/ui/fragments/MinimapFragment.java b/core/src/mindustry/ui/fragments/MinimapFragment.java index 7c9d3b5994..43ad3163b0 100644 --- a/core/src/mindustry/ui/fragments/MinimapFragment.java +++ b/core/src/mindustry/ui/fragments/MinimapFragment.java @@ -18,7 +18,7 @@ public class MinimapFragment extends Fragment{ private boolean shown; float panx, pany, zoom = 1f, lastZoom = -1; private float baseSize = Scl.scl(5f); - private Element elem; + public Element elem; @Override public void build(Group parent){ diff --git a/core/src/mindustry/ui/fragments/PlayerListFragment.java b/core/src/mindustry/ui/fragments/PlayerListFragment.java index eae9407131..c006dcf3ff 100644 --- a/core/src/mindustry/ui/fragments/PlayerListFragment.java +++ b/core/src/mindustry/ui/fragments/PlayerListFragment.java @@ -24,7 +24,9 @@ public class PlayerListFragment extends Fragment{ @Override public void build(Group parent){ + content.name = "players"; parent.fill(cont -> { + cont.name = "playerlist"; cont.visible(() -> visible); cont.update(() -> { if(!(net.active() && state.isGame())){ @@ -47,6 +49,7 @@ public class PlayerListFragment extends Fragment{ sField = pane.field(null, text -> { rebuild(); }).grow().pad(8).get(); + sField.name = "search"; sField.setMaxLength(maxNameLength); sField.setMessageText(Core.bundle.format("players.search")); @@ -56,6 +59,7 @@ public class PlayerListFragment extends Fragment{ pane.table(menu -> { menu.defaults().growX().height(50f).fillY(); + menu.name = "menu"; menu.button("@server.bans", ui.bans::show).disabled(b -> net.client()); menu.button("@server.admins", ui.admins::show).disabled(b -> net.client()); @@ -99,6 +103,7 @@ public class PlayerListFragment extends Fragment{ }; table.margin(8); table.add(new Image(user.icon()).setScaling(Scaling.bounded)).grow(); + table.name = user.name(); button.add(table).size(h); button.labelWrap("[#" + user.color().toString().toUpperCase() + "]" + user.name()).width(170f).pad(10); diff --git a/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java b/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java index 73861538e3..3f33dd1b4b 100644 --- a/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java +++ b/core/src/mindustry/ui/fragments/ScriptConsoleFragment.java @@ -39,7 +39,6 @@ public class ScriptConsoleFragment extends Table{ }; public ScriptConsoleFragment(){ - setFillParent(true); font = Fonts.def; diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 189a9c21c1..95d9a426e0 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -193,6 +193,8 @@ public class Block extends UnlockableContent{ public BuildPlaceability buildPlaceability = BuildPlaceability.always; /** Multiplier for speed of building this block. */ public float buildCostMultiplier = 1f; + /** Multiplier for cost of research in tech tree. */ + public float researchCostMultiplier = 1; /** Whether this block has instant transfer.*/ public boolean instantTransfer = false; /** Whether you can rotate this block with Keybind rotateplaced + Scroll Wheel. */ @@ -619,7 +621,7 @@ public class Block extends UnlockableContent{ public ItemStack[] researchRequirements(){ ItemStack[] out = new ItemStack[requirements.length]; for(int i = 0; i < out.length; i++){ - int quantity = 40 + Mathf.round(Mathf.pow(requirements[i].amount, 1.25f) * 20, 10); + int quantity = 40 + Mathf.round(Mathf.pow(requirements[i].amount, 1.25f) * 20 * researchCostMultiplier, 10); out[i] = new ItemStack(requirements[i].item, UI.roundAmount(quantity)); } diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java index 8bd9a77c47..c228397a32 100644 --- a/core/src/mindustry/world/blocks/ConstructBlock.java +++ b/core/src/mindustry/world/blocks/ConstructBlock.java @@ -58,7 +58,7 @@ public class ConstructBlock extends Block{ } @Remote(called = Loc.server) - public static void constructFinish(Tile tile, Block block, Unit builder, byte rotation, Team team, Object config){ + public static void constructFinish(Tile tile, Block block, @Nullable Unit builder, byte rotation, Team team, Object config){ if(tile == null) return; float healthf = tile.build == null ? 1f : tile.build.healthf(); @@ -76,6 +76,10 @@ public class ConstructBlock extends Block{ if(prev != null && prev.size > 0){ tile.build.overwrote(prev); } + + if(builder != null && builder.isPlayer()){ + tile.build.lastAccessed = builder.getPlayer().name; + } } //last builder was this local client player, call placed() @@ -142,6 +146,9 @@ public class ConstructBlock extends Block{ public Block previous; public Object lastConfig; + @Nullable + public Unit lastBuilder; + private float[] accumulator; private float[] totalAccumulator; @@ -214,6 +221,10 @@ public class ConstructBlock extends Block{ return; } + if(builder.isPlayer()){ + lastBuilder = builder; + } + lastConfig = config; if(cblock.requirements.length != accumulator.length || totalAccumulator.length != cblock.requirements.length){ @@ -233,13 +244,18 @@ public class ConstructBlock extends Block{ progress = Mathf.clamp(progress + maxProgress); if(progress >= 1f || state.rules.infiniteResources){ - constructed(tile, cblock, builder, (byte)rotation, builder.team, config); + if(lastBuilder == null) lastBuilder = builder; + constructed(tile, cblock, lastBuilder, (byte)rotation, builder.team, config); } } public void deconstruct(Unit builder, @Nullable Building core, float amount){ float deconstructMultiplier = state.rules.deconstructRefundMultiplier; + if(builder.isPlayer()){ + lastBuilder = builder; + } + if(cblock != null){ ItemStack[] requirements = cblock.requirements; if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){ @@ -271,7 +287,8 @@ public class ConstructBlock extends Block{ progress = Mathf.clamp(progress - amount); if(progress <= 0 || state.rules.infiniteResources){ - Call.deconstructFinish(tile, this.cblock == null ? previous : this.cblock, builder); + if(lastBuilder == null) lastBuilder = builder; + Call.deconstructFinish(tile, this.cblock == null ? previous : this.cblock, lastBuilder); } } diff --git a/core/src/mindustry/world/blocks/defense/Wall.java b/core/src/mindustry/world/blocks/defense/Wall.java index 873cbaea78..abb941b899 100644 --- a/core/src/mindustry/world/blocks/defense/Wall.java +++ b/core/src/mindustry/world/blocks/defense/Wall.java @@ -34,7 +34,7 @@ public class Wall extends Block{ solid = true; destructible = true; group = BlockGroup.walls; - buildCostMultiplier = 5f; + buildCostMultiplier = 6f; canOverdrive = false; } diff --git a/core/src/mindustry/world/blocks/defense/turrets/ChargeTurret.java b/core/src/mindustry/world/blocks/defense/turrets/ChargeTurret.java index 3680cce446..15feaf0379 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/ChargeTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/ChargeTurret.java @@ -1,10 +1,13 @@ package mindustry.world.blocks.defense.turrets; +import arc.audio.*; import arc.math.*; import arc.util.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.bullet.*; +import mindustry.type.*; +import mindustry.gen.*; import static mindustry.Vars.*; @@ -14,6 +17,7 @@ public class ChargeTurret extends PowerTurret{ public float chargeMaxDelay = 10f; public Effect chargeEffect = Fx.none; public Effect chargeBeginEffect = Fx.none; + public Sound chargeSound = Sounds.none; public ChargeTurret(String name){ super(name); @@ -28,7 +32,8 @@ public class ChargeTurret extends PowerTurret{ tr.trns(rotation, size * tilesize / 2f); chargeBeginEffect.at(x + tr.x, y + tr.y, rotation); - + chargeSound.at(x + tr.x, y + tr.y, 1); + for(int i = 0; i < chargeEffects; i++){ Time.run(Mathf.random(chargeMaxDelay), () -> { if(!isValid()) return; diff --git a/core/src/mindustry/world/blocks/defense/turrets/Turret.java b/core/src/mindustry/world/blocks/defense/turrets/Turret.java index c535f27135..411ee782ec 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/Turret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/Turret.java @@ -434,7 +434,7 @@ public abstract class Turret extends Block{ fshootEffect.at(x + tr.x, y + tr.y, rotation); fsmokeEffect.at(x + tr.x, y + tr.y, rotation); - shootSound.at(tile, Mathf.random(0.9f, 1.1f)); + shootSound.at(x + tr.x, y + tr.y, Mathf.random(0.9f, 1.1f)); if(shootShake > 0){ Effect.shake(shootShake, shootShake, this); diff --git a/core/src/mindustry/world/blocks/logic/LogicBlock.java b/core/src/mindustry/world/blocks/logic/LogicBlock.java index f3962a5448..aecf5d4149 100644 --- a/core/src/mindustry/world/blocks/logic/LogicBlock.java +++ b/core/src/mindustry/world/blocks/logic/LogicBlock.java @@ -1,7 +1,6 @@ package mindustry.world.blocks.logic; import arc.func.*; -import arc.graphics.g2d.*; import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.struct.Bits; @@ -9,7 +8,6 @@ import arc.struct.*; import arc.util.*; import arc.util.io.*; import mindustry.*; -import mindustry.ai.types.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.io.*; @@ -425,7 +423,6 @@ public class LogicBlock extends Block{ @Override public void buildConfiguration(Table table){ - table.button(Icon.pencil, Styles.clearTransi, () -> { Vars.ui.logic.show(code, code -> { configure(compress(code, relativeConnections())); @@ -433,19 +430,6 @@ public class LogicBlock extends Block{ }).size(40); } - @Override - public void draw(){ - super.draw(); - - if(ui.hudfrag.blockfrag.hover() instanceof Unit unit && unit.controller() instanceof LogicAI ai && ai.controller == this){ - Draw.z(Layer.overlayUI); - Drawf.square(x, y, size * tilesize/2f + 2f); - if(!unit.within(this, unit.hitSize * 2f)){ - Drawf.arrow(unit.x, unit.y, x, y, unit.hitSize *2f, 4f); - } - } - } - @Override public boolean onConfigureTileTapped(Building other){ if(this == other){ diff --git a/core/src/mindustry/world/blocks/power/PowerNode.java b/core/src/mindustry/world/blocks/power/PowerNode.java index 245c3d4f7e..5d033c99b7 100644 --- a/core/src/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/mindustry/world/blocks/power/PowerNode.java @@ -112,6 +112,12 @@ public class PowerNode extends PowerBlock{ (UI.formatAmount((int)entity.power.graph.getLastPowerStored())), UI.formatAmount((int)entity.power.graph.getLastCapacity())), () -> Pal.powerBar, () -> Mathf.clamp(entity.power.graph.getLastPowerStored() / entity.power.graph.getLastCapacity()))); + + bars.add("connections", entity -> new Bar(() -> + Core.bundle.format("bar.powerlines", entity.power.links.size, maxNodes), + () -> Pal.items, + () -> (float)entity.power.links.size / (float)maxNodes + )); } @Override diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index 295a127f2b..bdb16eb674 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -14,6 +14,7 @@ import mindustry.game.EventType.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.logic.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.*; @@ -157,6 +158,13 @@ public class CoreBlock extends StorageBlock{ public int storageCapacity; //note that this unit is never actually used for control; the possession handler makes the player respawn when this unit is controlled public BlockUnitc unit = Nulls.blockUnit; + public boolean noEffect = false; + + @Override + public double sense(LAccess sensor){ + if(sensor == LAccess.itemCapacity) return storageCapacity; + return super.sense(sensor); + } @Override public void created(){ @@ -195,7 +203,7 @@ public class CoreBlock extends StorageBlock{ @Override public boolean acceptItem(Building source, Item item){ - return items.get(item) < getMaximumAccepted(item); + return items.get(item) < getMaximumAccepted(item) || incinerate(); } @Override @@ -264,6 +272,10 @@ public class CoreBlock extends StorageBlock{ return tile instanceof StorageBuild && (((StorageBuild)tile).linkedCore == core || ((StorageBuild)tile).linkedCore == null); } + public boolean incinerate(){ + return state.isCampaign(); + } + @Override public float handleDamage(float amount){ if(player != null && team == player.team()){ @@ -298,16 +310,51 @@ public class CoreBlock extends StorageBlock{ } } + @Override + public void onDestroyed(){ + super.onDestroyed(); + + if(state.isCampaign() && team == state.rules.waveTeam){ + //do not recache + world.setGenerating(true); + tile.setOverlay(Blocks.spawn); + world.setGenerating(false); + + if(!spawner.getSpawns().contains(tile)){ + spawner.getSpawns().add(tile); + } + spawner.doShockwave(x, y); + } + } + @Override public void placed(){ super.placed(); state.teams.registerCore(this); } + @Override + public void itemTaken(Item item){ + if(state.isCampaign()){ + //update item taken amount + state.secinfo.handleCoreItem(item, -1); + } + } + @Override public void handleItem(Building source, Item item){ if(net.server() || !net.active()){ - super.handleItem(source, item); + + if(items.get(item) >= getMaximumAccepted(item)){ + //create item incineration effect at random intervals + if(!noEffect){ + incinerateEffect(this, source); + } + noEffect = false; + }else{ + super.handleItem(source, item); + } + if(state.rules.tutorial){ Events.fire(new CoreItemDeliverEvent()); } diff --git a/core/src/mindustry/world/blocks/storage/StorageBlock.java b/core/src/mindustry/world/blocks/storage/StorageBlock.java index 320d70868a..50eb65a270 100644 --- a/core/src/mindustry/world/blocks/storage/StorageBlock.java +++ b/core/src/mindustry/world/blocks/storage/StorageBlock.java @@ -1,10 +1,13 @@ package mindustry.world.blocks.storage; +import arc.math.*; import arc.struct.*; import arc.util.*; +import mindustry.content.*; import mindustry.gen.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.storage.CoreBlock.*; import mindustry.world.meta.*; public class StorageBlock extends Block{ @@ -22,6 +25,16 @@ public class StorageBlock extends Block{ return false; } + public static void incinerateEffect(Building self, Building source){ + if(Mathf.chance(0.1)){ + Tile edge = Edges.getFacingEdge(source, self); + Tile edge2 = Edges.getFacingEdge(self, source); + if(edge != null && edge2 != null){ + Fx.fuelburn.at((edge.worldx() + edge2.worldx())/2f, (edge.worldy() + edge2.worldy())/2f); + } + } + } + public class StorageBuild extends Building{ protected @Nullable Building linkedCore; @@ -30,6 +43,17 @@ public class StorageBlock extends Block{ return linkedCore != null ? linkedCore.acceptItem(source, item) : items.get(item) < getMaximumAccepted(item); } + @Override + public void handleItem(Building source, Item item){ + if(linkedCore != null){ + incinerateEffect(this, source); + ((CoreBuild)linkedCore).noEffect = true; + linkedCore.handleItem(source, item); + }else{ + super.handleItem(source, item); + } + } + @Override public int getMaximumAccepted(Item item){ return itemCapacity; diff --git a/core/src/mindustry/world/blocks/storage/Unloader.java b/core/src/mindustry/world/blocks/storage/Unloader.java index 86ce15956e..524d6eb939 100644 --- a/core/src/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/mindustry/world/blocks/storage/Unloader.java @@ -66,6 +66,7 @@ public class Unloader extends Block{ }else{ other.items.remove(item, 1); } + other.itemTaken(item); } } } diff --git a/servers.json b/servers.json index 177f25f676..5990afd487 100644 --- a/servers.json +++ b/servers.json @@ -35,6 +35,9 @@ { "address": "aamindustry.play.ai:6568" }, + { + "address": "aamindustry.play.ai:6569" + }, { "address": "aamindustry.play.ai:6570" }, @@ -74,6 +77,9 @@ { "address": "pandorum.su:9999" }, + { + "address": "mindustryranked.ddns.net" + }, { "address": "attack.pearkin.net" } diff --git a/servers_be.json b/servers_be.json index 5ebfae37df..ae667423c5 100644 --- a/servers_be.json +++ b/servers_be.json @@ -7,5 +7,14 @@ }, { "address": "md.surrealment.com" + }, + { + "address": "routerchain.ddns.net" + }, + { + "address": "mindustry.pl:6660" + }, + { + "address": "be.wayzer.cf" } ]