diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 650e4c293e..d816e2f14e 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -45,7 +45,6 @@ database = Core Database savegame = Save Game loadgame = Load Game joingame = Join Game -addplayers = Add/Remove Players customgame = Custom Game newgame = New Game none = @@ -164,7 +163,6 @@ server.port = Port: server.addressinuse = Address already in use! server.invalidport = Invalid port number! server.error = [crimson]Error hosting server. -save.old = This save is for an older version of the game, and can no longer be used.\n\n[lightgray]Save backwards compatibility will be implemented in the full 4.0 release. save.new = New Save save.overwrite = Are you sure you want to overwrite\nthis save slot? overwrite = Overwrite @@ -378,7 +376,6 @@ campaign = Campaign load = Load save = Save fps = FPS: {0} -tps = TPS: {0} ping = Ping: {0}ms language.restart = Please restart your game for the language settings to take effect. settings = Settings @@ -391,8 +388,10 @@ donate = Donate abandon = Abandon abandon.text = This zone and all its resources will be lost to the enemy. locked = Locked -complete = [lightgray]Reach: -zone.requirement = Wave {0} in zone {1} +complete = [lightgray]Complete: +requirement.wave = Reach Wave {0} in {1} +requirement.core = Destroy Enemy Core in {0} +requirement.unlock = Unlock {0} resume = Resume Zone:\n[lightgray]{0} bestwave = [lightgray]Best Wave: {0} launch = < LAUNCH > @@ -403,11 +402,13 @@ launch.confirm = This will launch all resources in your core.\nYou will not be a launch.skip.confirm = If you skip now, you will not be able to launch until later waves. uncover = Uncover configure = Configure Loadout -configure.locked = [lightgray]Unlock configuring loadout: Wave {0}. +bannedblocks = Banned Blocks +addall = Add All +configure.locked = [lightgray]Unlock configuring loadout: {0}. configure.invalid = Amount must be a number between 0 and {0}. zone.unlocked = [lightgray]{0} unlocked. -zone.requirement.complete = Wave {0} reached:\n{1} zone requirements met. -zone.config.complete = Wave {0} reached:\nLoadout config unlocked. +zone.requirement.complete = Requirement for {0} completed:[lightgray]\n{1} +zone.config.unlocked = Loadout unlocked:[lightgray]\n{0} zone.resources = [lightgray]Resources Detected: zone.objective = [lightgray]Objective: [accent]{0} zone.objective.survival = Survive @@ -468,12 +469,13 @@ settings.cleardata = Clear Game Data... settings.clear.confirm = Are you sure you want to clear this data?\nWhat is done cannot be undone! settings.clearall.confirm = [scarlet]WARNING![]\nThis will clear all data, including saves, maps, unlocks and keybinds.\nOnce you press 'ok' the game will wipe all data and automatically exit. paused = [accent]< Paused > +clear = Clear +banned = [scarlet]Banned yes = Yes no = No info.title = Info error.title = [crimson]An error has occured error.crashtitle = An error has occured -attackpvponly = [scarlet]Only available in Attack/PvP modes blocks.input = Input blocks.output = Output blocks.booster = Booster diff --git a/core/src/io/anuke/mindustry/content/Blocks.java b/core/src/io/anuke/mindustry/content/Blocks.java index 573ad8a832..0fa8f44e5c 100644 --- a/core/src/io/anuke/mindustry/content/Blocks.java +++ b/core/src/io/anuke/mindustry/content/Blocks.java @@ -965,12 +965,12 @@ public class Blocks implements ContentList{ //region liquid mechanicalPump = new Pump("mechanical-pump"){{ - requirements(Category.liquid, ItemStack.with(Items.copper, 15, Items.lead, 10)); + requirements(Category.liquid, ItemStack.with(Items.copper, 15, Items.metaglass, 10)); pumpAmount = 0.1f; }}; rotaryPump = new Pump("rotary-pump"){{ - requirements(Category.liquid, ItemStack.with(Items.copper, 70, Items.lead, 50, Items.silicon, 20, Items.titanium, 35)); + requirements(Category.liquid, ItemStack.with(Items.copper, 70, Items.metaglass, 50, Items.silicon, 20, Items.titanium, 35)); pumpAmount = 0.8f; consumes.power(0.15f); liquidCapacity = 30f; @@ -979,7 +979,7 @@ public class Blocks implements ContentList{ }}; thermalPump = new Pump("thermal-pump"){{ - requirements(Category.liquid, ItemStack.with(Items.copper, 80, Items.lead, 65, Items.silicon, 30, Items.titanium, 40, Items.thorium, 35)); + requirements(Category.liquid, ItemStack.with(Items.copper, 80, Items.metaglass, 70, Items.silicon, 30, Items.titanium, 40, Items.thorium, 35)); pumpAmount = 1.5f; consumes.power(0.30f); liquidCapacity = 40f; @@ -993,13 +993,13 @@ public class Blocks implements ContentList{ }}; pulseConduit = new Conduit("pulse-conduit"){{ - requirements(Category.liquid, ItemStack.with(Items.titanium, 1, Items.metaglass, 1)); + requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 1)); liquidCapacity = 16f; health = 90; }}; liquidRouter = new LiquidRouter("liquid-router"){{ - requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 2)); + requirements(Category.liquid, ItemStack.with(Items.graphite, 4, Items.metaglass, 2)); liquidCapacity = 20f; }}; @@ -1011,11 +1011,11 @@ public class Blocks implements ContentList{ }}; liquidJunction = new LiquidJunction("liquid-junction"){{ - requirements(Category.liquid, ItemStack.with(Items.titanium, 2, Items.metaglass, 2)); + requirements(Category.liquid, ItemStack.with(Items.graphite, 2, Items.metaglass, 2)); }}; bridgeConduit = new LiquidExtendingBridge("bridge-conduit"){{ - requirements(Category.liquid, ItemStack.with(Items.titanium, 4, Items.metaglass, 4)); + requirements(Category.liquid, ItemStack.with(Items.graphite, 4, Items.metaglass, 8)); range = 4; hasPower = false; }}; diff --git a/core/src/io/anuke/mindustry/content/Zones.java b/core/src/io/anuke/mindustry/content/Zones.java index ca7013fa3f..90794dd80e 100644 --- a/core/src/io/anuke/mindustry/content/Zones.java +++ b/core/src/io/anuke/mindustry/content/Zones.java @@ -1,13 +1,15 @@ package io.anuke.mindustry.content; -import io.anuke.arc.collection.Array; -import io.anuke.mindustry.game.ContentList; -import io.anuke.mindustry.game.SpawnGroup; -import io.anuke.mindustry.maps.generators.MapGenerator; -import io.anuke.mindustry.maps.generators.MapGenerator.Decoration; -import io.anuke.mindustry.maps.zonegen.DesertWastesGenerator; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Objectives.*; +import io.anuke.mindustry.maps.generators.*; +import io.anuke.mindustry.maps.generators.MapGenerator.*; +import io.anuke.mindustry.maps.zonegen.*; import io.anuke.mindustry.type.*; -import io.anuke.mindustry.world.Block; + +import static io.anuke.arc.collection.Array.with; +import static io.anuke.mindustry.content.Items.*; +import static io.anuke.mindustry.type.ItemStack.list; public class Zones implements ContentList{ public static Zone @@ -20,28 +22,26 @@ public class Zones implements ContentList{ public void load(){ groundZero = new Zone("groundZero", new MapGenerator("groundZero", 1)){{ - baseLaunchCost = ItemStack.with(Items.copper, -60); - startingItems = ItemStack.list(Items.copper, 60); + baseLaunchCost = list(copper, -60); + startingItems = list(copper, 60); alwaysUnlocked = true; conditionWave = 5; launchPeriod = 5; - resources = new Item[]{Items.copper, Items.scrap, Items.lead}; + resources = with(copper, scrap, lead); }}; desertWastes = new Zone("desertWastes", new DesertWastesGenerator(260, 260)){{ - startingItems = ItemStack.list(Items.copper, 120); + startingItems = list(copper, 120); conditionWave = 20; launchPeriod = 10; loadout = Loadouts.advancedShard; - zoneRequirements = ZoneRequirement.with(groundZero, 20); - blockRequirements = new Block[]{Blocks.combustionGenerator}; - resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand}; + resources = with(copper, lead, coal, sand); rules = r -> { r.waves = true; r.waveTimer = true; r.launchWaveMultiplier = 3f; r.waveSpacing = 60 * 50f; - r.spawns = Array.with( + r.spawns = with( new SpawnGroup(UnitTypes.crawler){{ unitScaling = 3f; }}, @@ -75,96 +75,140 @@ public class Zones implements ContentList{ }} ); }; + requirements = with( + new ZoneWave(groundZero, 20), + new Unlock(Blocks.combustionGenerator) + ); }}; saltFlats = new Zone("saltFlats", new MapGenerator("saltFlats")){{ - startingItems = ItemStack.list(Items.copper, 200, Items.silicon, 200, Items.lead, 200); + startingItems = list(copper, 200, Items.silicon, 200, lead, 200); loadout = Loadouts.basicFoundation; conditionWave = 10; launchPeriod = 5; - zoneRequirements = ZoneRequirement.with(desertWastes, 60); - blockRequirements = new Block[]{Blocks.daggerFactory, Blocks.draugFactory, Blocks.door, Blocks.waterExtractor}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand, Items.titanium}; + configureObjective = new Launched(this); + resources = with(copper, scrap, lead, coal, sand, titanium); + requirements = with( + new ZoneWave(desertWastes, 60), + new Unlock(Blocks.daggerFactory), + new Unlock(Blocks.draugFactory), + new Unlock(Blocks.door), + new Unlock(Blocks.waterExtractor) + ); }}; frozenForest = new Zone("frozenForest", new MapGenerator("frozenForest", 1) .decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.02))){{ loadout = Loadouts.basicFoundation; - baseLaunchCost = ItemStack.with(); - startingItems = ItemStack.list(Items.copper, 250); + startingItems = list(copper, 250); conditionWave = 10; - blockRequirements = new Block[]{Blocks.junction, Blocks.router}; - zoneRequirements = ZoneRequirement.with(groundZero, 10); - resources = new Item[]{Items.copper, Items.lead, Items.coal}; + resources = with(copper, lead, coal); + requirements = with( + new ZoneWave(groundZero, 10), + new Unlock(Blocks.junction), + new Unlock(Blocks.router) + ); }}; craters = new Zone("craters", new MapGenerator("craters", 1).decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.004))){{ - startingItems = ItemStack.list(Items.copper, 100); + startingItems = list(copper, 100); conditionWave = 10; - zoneRequirements = ZoneRequirement.with(frozenForest, 10); - blockRequirements = new Block[]{Blocks.mender, Blocks.combustionGenerator}; - resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.sand, Items.scrap}; + resources = with(copper, lead, coal, sand, scrap); + requirements = with( + new ZoneWave(frozenForest, 10), + new Unlock(Blocks.mender), + new Unlock(Blocks.combustionGenerator) + ); }}; ruinousShores = new Zone("ruinousShores", new MapGenerator("ruinousShores", 1)){{ loadout = Loadouts.basicFoundation; - baseLaunchCost = ItemStack.with(); - startingItems = ItemStack.list(Items.copper, 140, Items.lead, 50); + startingItems = list(copper, 140, lead, 50); conditionWave = 20; launchPeriod = 20; - zoneRequirements = ZoneRequirement.with(desertWastes, 20, craters, 15); - blockRequirements = new Block[]{Blocks.graphitePress, Blocks.combustionGenerator, Blocks.kiln, Blocks.mechanicalPump}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand}; + resources = with(copper, scrap, lead, coal, sand); + requirements = with( + new ZoneWave(desertWastes, 20), + new ZoneWave(craters, 15), + new Unlock(Blocks.graphitePress), + new Unlock(Blocks.combustionGenerator), + new Unlock(Blocks.kiln), + new Unlock(Blocks.mechanicalPump) + ); }}; stainedMountains = new Zone("stainedMountains", new MapGenerator("stainedMountains", 2) .decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{ loadout = Loadouts.basicFoundation; - startingItems = ItemStack.list(Items.copper, 200, Items.lead, 50); + startingItems = list(copper, 200, lead, 50); conditionWave = 10; launchPeriod = 10; - zoneRequirements = ZoneRequirement.with(frozenForest, 15); - blockRequirements = new Block[]{Blocks.pneumaticDrill, Blocks.powerNode, Blocks.turbineGenerator}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.sand}; + resources = with(copper, scrap, lead, coal, titanium, sand); + requirements = with( + new ZoneWave(frozenForest, 15), + new Unlock(Blocks.pneumaticDrill), + new Unlock(Blocks.powerNode), + new Unlock(Blocks.turbineGenerator) + ); }}; fungalPass = new Zone("fungalPass", new MapGenerator("fungalPass")){{ - startingItems = ItemStack.list(Items.copper, 250, Items.lead, 250, Items.metaglass, 100, Items.graphite, 100); - zoneRequirements = ZoneRequirement.with(stainedMountains, 15); - blockRequirements = new Block[]{Blocks.daggerFactory, Blocks.crawlerFactory, Blocks.door, Blocks.siliconSmelter}; - resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.titanium, Items.sand}; + startingItems = list(copper, 250, lead, 250, Items.metaglass, 100, Items.graphite, 100); + resources = with(copper, lead, coal, titanium, sand); + configureObjective = new Launched(this); + requirements = with( + new ZoneWave(stainedMountains, 15), + new Unlock(Blocks.daggerFactory), + new Unlock(Blocks.crawlerFactory), + new Unlock(Blocks.door), + new Unlock(Blocks.siliconSmelter) + ); }}; overgrowth = new Zone("overgrowth", new MapGenerator("overgrowth")){{ - startingItems = ItemStack.list(Items.copper, 1500, Items.lead, 1000, Items.silicon, 500, Items.metaglass, 250); + startingItems = list(copper, 1500, lead, 1000, Items.silicon, 500, Items.metaglass, 250); conditionWave = 12; launchPeriod = 4; loadout = Loadouts.basicNucleus; - zoneRequirements = ZoneRequirement.with(craters, 40, fungalPass, 10); - blockRequirements = new Block[]{Blocks.cultivator, Blocks.sporePress, Blocks.titanFactory, Blocks.wraithFactory}; - resources = new Item[]{Items.copper, Items.lead, Items.coal, Items.titanium, Items.sand, Items.thorium, Items.scrap}; + configureObjective = new Launched(this); + resources = with(copper, lead, coal, titanium, sand, thorium, scrap); + requirements = with( + new ZoneWave(craters, 40), + new Launched(fungalPass), + new Unlock(Blocks.cultivator), + new Unlock(Blocks.sporePress), + new Unlock(Blocks.titanFactory), + new Unlock(Blocks.wraithFactory) + ); }}; tarFields = new Zone("tarFields", new MapGenerator("tarFields") .decor(new Decoration(Blocks.shale, Blocks.shaleBoulder, 0.02))){{ loadout = Loadouts.basicFoundation; - startingItems = ItemStack.list(Items.copper, 250, Items.lead, 100); + startingItems = list(copper, 250, lead, 100); conditionWave = 15; launchPeriod = 10; - zoneRequirements = ZoneRequirement.with(ruinousShores, 20); - blockRequirements = new Block[]{Blocks.coalCentrifuge, Blocks.conduit, Blocks.wave}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium, Items.sand}; + requirements = with(new ZoneWave(ruinousShores, 20)); + resources = with(copper, scrap, lead, coal, titanium, thorium, sand); + requirements = with( + new ZoneWave(ruinousShores, 20), + new Unlock(Blocks.coalCentrifuge), + new Unlock(Blocks.conduit), + new Unlock(Blocks.wave) + ); }}; desolateRift = new Zone("desolateRift", new MapGenerator("desolateRift")){{ loadout = Loadouts.basicNucleus; - baseLaunchCost = ItemStack.with(); - startingItems = ItemStack.list(Items.copper, 1000, Items.lead, 1000, Items.graphite, 250, Items.titanium, 250, Items.silicon, 250); + startingItems = list(copper, 1000, lead, 1000, Items.graphite, 250, titanium, 250, Items.silicon, 250); conditionWave = 3; launchPeriod = 2; - zoneRequirements = ZoneRequirement.with(tarFields, 20); - blockRequirements = new Block[]{Blocks.thermalGenerator, Blocks.thoriumReactor}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.sand, Items.thorium}; + resources = with(copper, scrap, lead, coal, titanium, sand, thorium); + requirements = with( + new ZoneWave(tarFields, 20), + new Unlock(Blocks.thermalGenerator), + new Unlock(Blocks.thoriumReactor) + ); }}; /* @@ -174,21 +218,23 @@ public class Zones implements ContentList{ startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500); conditionWave = 3; launchPeriod = 2; - zoneRequirements = ZoneRequirement.with(stainedMountains, 40); + requirements = with(stainedMountains, 40); blockRequirements = new Block[]{Blocks.thermalGenerator}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand}; + resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand}; }};*/ nuclearComplex = new Zone("nuclearComplex", new MapGenerator("nuclearProductionComplex", 1) .decor(new Decoration(Blocks.snow, Blocks.sporeCluster, 0.01))){{ loadout = Loadouts.basicNucleus; - baseLaunchCost = ItemStack.with(); - startingItems = ItemStack.list(Items.copper, 1250, Items.lead, 1500, Items.silicon, 400, Items.metaglass, 250); + startingItems = list(copper, 1250, lead, 1500, Items.silicon, 400, Items.metaglass, 250); conditionWave = 30; launchPeriod = 15; - zoneRequirements = ZoneRequirement.with(fungalPass, 8); - blockRequirements = new Block[]{Blocks.thermalGenerator, Blocks.laserDrill}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium, Items.sand}; + resources = with(copper, scrap, lead, coal, titanium, thorium, sand); + requirements = with( + new Launched(fungalPass), + new Unlock(Blocks.thermalGenerator), + new Unlock(Blocks.laserDrill) + ); }}; /* @@ -198,9 +244,9 @@ public class Zones implements ContentList{ startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500); conditionWave = 3; launchPeriod = 2; - zoneRequirements = ZoneRequirement.with(nuclearComplex, 40); + requirements = with(nuclearComplex, 40); blockRequirements = new Block[]{Blocks.thermalGenerator}; - resources = new Item[]{Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium}; + resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium}; }};*/ } } diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index 686b6d9d72..ebcb9d1b56 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -151,11 +151,15 @@ public class Control implements ApplicationListener, Loadable{ }); Events.on(ZoneRequireCompleteEvent.class, e -> { - ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", state.wave, e.zone.localizedName)); + if(e.objective.display() != null){ + ui.hudfrag.showToast(Core.bundle.format("zone.requirement.complete", e.zoneForMet.localizedName, e.objective.display())); + } }); Events.on(ZoneConfigureCompleteEvent.class, e -> { - ui.hudfrag.showToast(Core.bundle.format("zone.config.complete", e.zone.configureWave)); + if(e.zone.configureObjective.display() != null){ + ui.hudfrag.showToast(Core.bundle.format("zone.config.unlocked", e.zone.configureObjective.display())); + } }); Events.on(Trigger.newGame, () -> { diff --git a/core/src/io/anuke/mindustry/core/Logic.java b/core/src/io/anuke/mindustry/core/Logic.java index c0544f70bf..460085c058 100644 --- a/core/src/io/anuke/mindustry/core/Logic.java +++ b/core/src/io/anuke/mindustry/core/Logic.java @@ -136,8 +136,7 @@ public class Logic implements ApplicationListener{ public void runWave(){ spawner.spawnEnemies(); state.wave++; - state.wavetime = world.isZone() && world.getZone().isBossWave(state.wave) ? state.rules.waveSpacing * state.rules.bossWaveMultiplier : - world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing; + state.wavetime = world.isZone() && world.getZone().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing; Events.fire(new WaveEvent()); } @@ -180,6 +179,10 @@ public class Logic implements ApplicationListener{ Effects.effect(Fx.launch, tile); } + if(world.getZone() != null){ + world.getZone().setLaunched(); + } + Time.runTask(30f, () -> { for(Tile tile : new ObjectSetIterator<>(state.teams.get(defaultTeam).cores)){ for(Item item : content.items()){ diff --git a/core/src/io/anuke/mindustry/game/Content.java b/core/src/io/anuke/mindustry/game/Content.java index 20d8612697..b0b4d03b1b 100644 --- a/core/src/io/anuke/mindustry/game/Content.java +++ b/core/src/io/anuke/mindustry/game/Content.java @@ -1,13 +1,13 @@ package io.anuke.mindustry.game; import io.anuke.arc.util.ArcAnnotate.*; -import io.anuke.mindustry.Vars; +import io.anuke.mindustry.*; import io.anuke.mindustry.mod.Mods.*; -import io.anuke.mindustry.type.ContentType; +import io.anuke.mindustry.type.*; /** Base class for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}. */ -public abstract class Content{ +public abstract class Content implements Comparable{ public final short id; /** The mod that loaded this piece of content. */ public @Nullable LoadedMod mod; @@ -34,6 +34,11 @@ public abstract class Content{ public void load(){ } + @Override + public int compareTo(Content c){ + return Integer.compare(id, c.id); + } + @Override public String toString(){ return getContentType().name() + "#" + id; diff --git a/core/src/io/anuke/mindustry/game/EventType.java b/core/src/io/anuke/mindustry/game/EventType.java index f26dc08970..b25c5faea0 100644 --- a/core/src/io/anuke/mindustry/game/EventType.java +++ b/core/src/io/anuke/mindustry/game/EventType.java @@ -61,11 +61,13 @@ public class EventType{ /** Called when a zone's requirements are met. */ public static class ZoneRequireCompleteEvent{ - public final Zone zone, required; + public final Zone zoneMet, zoneForMet; + public final Objective objective; - public ZoneRequireCompleteEvent(Zone zone, Zone required){ - this.zone = zone; - this.required = required; + public ZoneRequireCompleteEvent(Zone zoneMet, Zone zoneForMet, Objective objective){ + this.zoneMet = zoneMet; + this.zoneForMet = zoneForMet; + this.objective = objective; } } diff --git a/core/src/io/anuke/mindustry/game/GlobalData.java b/core/src/io/anuke/mindustry/game/GlobalData.java index a644c3eed8..5ef1ae09fb 100644 --- a/core/src/io/anuke/mindustry/game/GlobalData.java +++ b/core/src/io/anuke/mindustry/game/GlobalData.java @@ -91,12 +91,17 @@ public class GlobalData{ state.stats.itemsDelivered.getAndIncrement(item, 0, amount); } + public boolean hasItems(Array stacks){ + return !stacks.contains(s -> items.get(s.item, 0) < s.amount); + } + public boolean hasItems(ItemStack[] stacks){ for(ItemStack stack : stacks){ - if(items.get(stack.item, 0) < stack.amount){ + if(!has(stack.item, stack.amount)){ return false; } } + return true; } @@ -107,6 +112,13 @@ public class GlobalData{ modified = true; } + public void removeItems(Array stacks){ + for(ItemStack stack : stacks){ + items.getAndIncrement(stack.item, 0, -stack.amount); + } + modified = true; + } + public boolean has(Item item, int amount){ return items.get(item, 0) >= amount; } diff --git a/core/src/io/anuke/mindustry/game/Objective.java b/core/src/io/anuke/mindustry/game/Objective.java new file mode 100644 index 0000000000..dcd1146a10 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Objective.java @@ -0,0 +1,27 @@ +package io.anuke.mindustry.game; + +import io.anuke.arc.scene.ui.layout.*; +import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.game.Objectives.*; +import io.anuke.mindustry.type.*; + +/** Defines a specific objective for a game. */ +public interface Objective{ + + /** @return whether this objective is met. */ + boolean complete(); + + /** @return the string displayed when this objective is completed, in imperative form. + * e.g. when the objective is 'complete 10 waves', this would display "complete 10 waves". + * If this objective should not be displayed, should return null.*/ + @Nullable String display(); + + /** Build a display for this zone requirement.*/ + default void build(Table table){ + + } + + default Zone zone(){ + return this instanceof ZoneObjective ? ((ZoneObjective)this).zone : null; + } +} diff --git a/core/src/io/anuke/mindustry/game/Objectives.java b/core/src/io/anuke/mindustry/game/Objectives.java new file mode 100644 index 0000000000..340346bc13 --- /dev/null +++ b/core/src/io/anuke/mindustry/game/Objectives.java @@ -0,0 +1,96 @@ +package io.anuke.mindustry.game; + +import io.anuke.arc.*; +import io.anuke.arc.util.ArcAnnotate.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; + +/** Holds objective classes. */ +public class Objectives{ + + //TODO + public static class Wave implements Objective{ + public int wave; + + public Wave(int wave){ + this.wave = wave; + } + + protected Wave(){} + + @Override + public boolean complete(){ + return false; + } + + @Override + public String display(){ + //TODO + return null; + } + } + + public static class Unlock implements Objective{ + public @NonNull Block block; + + public Unlock(Block block){ + this.block = block; + } + + protected Unlock(){} + + @Override + public boolean complete(){ + return block.unlocked(); + } + + @Override + public String display(){ + return Core.bundle.format("requirement.unlock", block.localizedName); + } + } + + public static class ZoneWave extends ZoneObjective{ + public int wave; + + public ZoneWave(Zone zone, int wave){ + this.zone = zone; + this.wave = wave; + } + + protected ZoneWave(){} + + @Override + public boolean complete(){ + return zone.bestWave() >= wave; + } + + @Override + public String display(){ + return Core.bundle.format("requirement.wave", wave, zone.localizedName); + } + } + + public static class Launched extends ZoneObjective{ + + public Launched(Zone zone){ + this.zone = zone; + } + + protected Launched(){} + + @Override + public boolean complete(){ + return zone.hasLaunched(); + } + + @Override + public String display(){ + return Core.bundle.format("requirement.core", zone.localizedName); + } + } + + public abstract static class ZoneObjective implements Objective{ + public @NonNull Zone zone; + } +} diff --git a/core/src/io/anuke/mindustry/game/Rules.java b/core/src/io/anuke/mindustry/game/Rules.java index 0d85482976..94cf4225ad 100644 --- a/core/src/io/anuke/mindustry/game/Rules.java +++ b/core/src/io/anuke/mindustry/game/Rules.java @@ -1,11 +1,11 @@ package io.anuke.mindustry.game; -import io.anuke.annotations.Annotations.Serialize; -import io.anuke.arc.collection.Array; -import io.anuke.mindustry.content.Items; -import io.anuke.mindustry.io.JsonIO; -import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.type.Zone; +import io.anuke.annotations.Annotations.*; +import io.anuke.arc.collection.*; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.io.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; /** * Defines current rules on how the game should function. @@ -69,6 +69,8 @@ public class Rules{ public boolean tutorial = false; /** Starting items put in cores */ public Array loadout = Array.with(ItemStack.with(Items.copper, 100)); + /** Blocks that cannot be placed. */ + public ObjectSet bannedBlocks = new ObjectSet<>(); /** Copies this ruleset exactly. Not very efficient at all, do not use often. */ public Rules copy(){ diff --git a/core/src/io/anuke/mindustry/mod/ContentParser.java b/core/src/io/anuke/mindustry/mod/ContentParser.java index 62892e55d8..d83ce429ac 100644 --- a/core/src/io/anuke/mindustry/mod/ContentParser.java +++ b/core/src/io/anuke/mindustry/mod/ContentParser.java @@ -18,6 +18,7 @@ import io.anuke.mindustry.entities.Effects.*; import io.anuke.mindustry.entities.bullet.*; import io.anuke.mindustry.entities.type.*; import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Objectives.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.mod.Mods.*; import io.anuke.mindustry.type.*; @@ -39,7 +40,7 @@ public class ContentParser{ put(BulletType.class, (type, data) -> { Class bc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.entities.bullets") : BasicBulletType.class; data.remove("type"); - BulletType result = bc.getDeclaredConstructor().newInstance(); + BulletType result = make(bc); readFields(result, data); return result; }); @@ -60,6 +61,13 @@ public class ContentParser{ Log.info(Core.assets.get(path)); return Core.assets.get(path); }); + put(Objective.class, (type, data) -> { + Class oc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.game.Objectives") : ZoneWave.class; + data.remove("type"); + Objective obj = make(oc); + readFields(obj, data); + return obj; + }); }}; /** Stores things that need to be parsed fully, e.g. reading fields of content. * This is done to accomodate binding of content names first.*/ @@ -115,6 +123,7 @@ public class ContentParser{ ); Block block = type.getDeclaredConstructor(String.class).newInstance(mod + "-" + name); + currentContent = block; read(() -> { if(value.has("consumes")){ for(JsonValue child : value.get("consumes")){ @@ -159,6 +168,7 @@ public class ContentParser{ Class type = resolve(value.getString("type"), "io.anuke.mindustry.entities.type.base"); UnitType unit = new UnitType(mod + "-" + name, supply(type)); + currentContent = unit; read(() -> readFields(unit, value, true)); return unit; @@ -179,6 +189,7 @@ public class ContentParser{ item = constructor.get(mod + "-" + name); } + currentContent = item; read(() -> readFields(item, value)); return item; }; @@ -225,7 +236,11 @@ public class ContentParser{ } public void finishParsing(){ - reads.each(Runnable::run); + try{ + reads.each(Runnable::run); + }catch(Exception e){ + throw new RuntimeException("Error occurred parsing content: " + currentContent, e); + } reads.clear(); } @@ -253,6 +268,16 @@ public class ContentParser{ return c; } + private T make(Class type){ + try{ + java.lang.reflect.Constructor cons = type.getDeclaredConstructor(); + cons.setAccessible(true); + return cons.newInstance(); + }catch(Exception e){ + throw new RuntimeException(e); + } + } + private Supplier supply(Class type){ try{ java.lang.reflect.Constructor cons = type.getDeclaredConstructor(); @@ -366,9 +391,13 @@ public class ContentParser{ try{ return (Class)Class.forName(type + '.' + base); }catch(Exception ignored){ + try{ + return (Class)Class.forName(type + '$' + base); + }catch(Exception ignored2){ + } } } - throw new IllegalArgumentException("Type not found: " + potentials[0]); + throw new IllegalArgumentException("Types not found: " + base + "." + potentials[0]); } private interface FieldParser{ diff --git a/core/src/io/anuke/mindustry/type/Item.java b/core/src/io/anuke/mindustry/type/Item.java index fa766dc505..4950f32c10 100644 --- a/core/src/io/anuke/mindustry/type/Item.java +++ b/core/src/io/anuke/mindustry/type/Item.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.world.blocks.*; import static io.anuke.mindustry.Vars.content; -public class Item extends UnlockableContent implements Comparable{ +public class Item extends UnlockableContent{ public final Color color; /** type of the item; used for tabs and core acceptance. default value is {@link ItemType#resource}. */ @@ -61,11 +61,6 @@ public class Item extends UnlockableContent implements Comparable{ return localizedName(); } - @Override - public int compareTo(Item item){ - return Integer.compare(id, item.id); - } - @Override public ContentType getContentType(){ return ContentType.item; diff --git a/core/src/io/anuke/mindustry/type/ItemStack.java b/core/src/io/anuke/mindustry/type/ItemStack.java index 9e428716dc..4ea87f5605 100644 --- a/core/src/io/anuke/mindustry/type/ItemStack.java +++ b/core/src/io/anuke/mindustry/type/ItemStack.java @@ -19,6 +19,10 @@ public class ItemStack implements Comparable{ item = Items.copper; } + public ItemStack copy(){ + return new ItemStack(item, amount); + } + public boolean equals(ItemStack other){ return other != null && other.item == item && other.amount == amount; } diff --git a/core/src/io/anuke/mindustry/type/Zone.java b/core/src/io/anuke/mindustry/type/Zone.java index 3a499996c4..19e11aa3be 100644 --- a/core/src/io/anuke/mindustry/type/Zone.java +++ b/core/src/io/anuke/mindustry/type/Zone.java @@ -5,34 +5,32 @@ import io.anuke.arc.collection.*; import io.anuke.arc.function.*; import io.anuke.arc.graphics.g2d.*; import io.anuke.arc.scene.ui.layout.*; -import io.anuke.arc.util.*; import io.anuke.arc.util.ArcAnnotate.*; import io.anuke.mindustry.content.*; import io.anuke.mindustry.game.EventType.*; import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Objectives.*; import io.anuke.mindustry.maps.generators.*; -import io.anuke.mindustry.world.*; - -import java.util.*; import static io.anuke.mindustry.Vars.*; public class Zone extends UnlockableContent{ public @NonNull Generator generator; - public Block[] blockRequirements = {}; - public ZoneRequirement[] zoneRequirements = {}; - public Item[] resources = {}; + public @NonNull Objective configureObjective = new ZoneWave(this, 15); + public Array requirements = new Array<>(); + //TODO autogenerate + public Array resources = new Array<>(); + public Consumer rules = rules -> {}; public boolean alwaysUnlocked; public int conditionWave = Integer.MAX_VALUE; - public int configureWave = 15; public int launchPeriod = 10; public Loadout loadout = Loadouts.basicShard; public TextureRegion preview; - protected ItemStack[] baseLaunchCost = {}; + protected Array baseLaunchCost = new Array<>(); protected Array startingItems = new Array<>(); - protected ItemStack[] launchCost = null; + protected Array launchCost; private Array defaultStartingItems = new Array<>(); @@ -60,35 +58,15 @@ public class Zone extends UnlockableContent{ } } - public boolean isBossWave(int wave){ - return wave % configureWave == 0 && wave > 0; - } - public boolean isLaunchWave(int wave){ return metCondition() && wave % launchPeriod == 0; } public boolean canUnlock(){ - if(data.isUnlocked(this)){ - return true; - } - - for(ZoneRequirement other : zoneRequirements){ - if(other.zone.bestWave() < other.wave){ - return false; - } - } - - for(Block other : blockRequirements){ - if(!data.isUnlocked(other)){ - return false; - } - } - - return true; + return data.isUnlocked(this) || !requirements.contains(r -> !r.complete()); } - public ItemStack[] getLaunchCost(){ + public Array getLaunchCost(){ if(launchCost == null){ updateLaunchCost(); } @@ -104,30 +82,54 @@ public class Zone extends UnlockableContent{ defaultStartingItems.each(stack -> startingItems.add(new ItemStack(stack.item, stack.amount))); } + public boolean hasLaunched(){ + return Core.settings.getBool(name + "-launched", false); + } + + public void setLaunched(){ + updateObjectives(() -> { + Core.settings.put(name + "-launched", true); + data.modified(); + }); + } + public void updateWave(int wave){ int value = Core.settings.getInt(name + "-wave", 0); + if(value < wave){ - Core.settings.put(name + "-wave", wave); - data.modified(); + updateObjectives(() -> { + Core.settings.put(name + "-wave", wave); + data.modified(); + }); + } + } - for(Zone zone : content.zones()){ - ZoneRequirement req = Structs.find(zone.zoneRequirements, f -> f.zone == this); - if(req != null && wave == req.wave + 1){ - Events.fire(new ZoneRequireCompleteEvent(zone, this)); - } - } + public void updateObjectives(Runnable closure){ + Array incomplete = content.zones() + .map(z -> z.requirements).flatten() + .select(o -> o.zone() == this && !o.complete()) + .as(ZoneObjective.class); - if(wave == configureWave + 1){ - Events.fire(new ZoneConfigureCompleteEvent(this)); + boolean wasConfig = configureObjective.complete(); + + closure.run(); + for(ZoneObjective objective : incomplete){ + if(objective.complete()){ + Events.fire(new ZoneRequireCompleteEvent(objective.zone, content.zones().find(z -> z.requirements.contains(objective)), objective)); } } + + if(!wasConfig && configureObjective.complete()){ + Events.fire(new ZoneConfigureCompleteEvent(this)); + } } public int bestWave(){ return Core.settings.getInt(name + "-wave", 0); } - public boolean isCompleted(){ + /** @return whether initial conditions to launch are met. */ + public boolean isLaunchMet(){ return bestWave() >= conditionWave; } @@ -152,7 +154,7 @@ public class Zone extends UnlockableContent{ } stacks.sort(); - launchCost = stacks.toArray(ItemStack.class); + launchCost = stacks; Core.settings.putObject(name + "-starting-items", startingItems); data.modified(); } @@ -164,13 +166,13 @@ public class Zone extends UnlockableContent{ } public boolean canConfigure(){ - return bestWave() >= configureWave; + return configureObjective.complete(); } @Override public void init(){ generator.init(loadout); - Arrays.sort(resources); + resources.sort(); for(ItemStack stack : startingItems){ defaultStartingItems.add(new ItemStack(stack.item, stack.amount)); @@ -208,26 +210,4 @@ public class Zone extends UnlockableContent{ return ContentType.zone; } - public static class ZoneRequirement{ - public @NonNull Zone zone; - public @NonNull int wave; - - public ZoneRequirement(Zone zone, int wave){ - this.zone = zone; - this.wave = wave; - } - - protected ZoneRequirement(){ - - } - - public static ZoneRequirement[] with(Object... objects){ - ZoneRequirement[] out = new ZoneRequirement[objects.length / 2]; - for(int i = 0; i < objects.length; i += 2){ - out[i / 2] = new ZoneRequirement((Zone)objects[i], (Integer)objects[i + 1]); - } - return out; - } - } - } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java index 10f418c1de..00d9db0cd0 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/CustomRulesDialog.java @@ -1,34 +1,112 @@ package io.anuke.mindustry.ui.dialogs; +import io.anuke.arc.*; +import io.anuke.arc.collection.*; import io.anuke.arc.function.*; -import io.anuke.arc.graphics.Color; -import io.anuke.arc.scene.ui.layout.Table; +import io.anuke.arc.graphics.*; +import io.anuke.arc.scene.style.*; +import io.anuke.arc.scene.ui.*; +import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; import io.anuke.mindustry.*; -import io.anuke.mindustry.content.Blocks; -import io.anuke.mindustry.content.Items; -import io.anuke.mindustry.game.Rules; -import io.anuke.mindustry.graphics.Pal; -import io.anuke.mindustry.type.ItemStack; -import io.anuke.mindustry.type.ItemType; +import io.anuke.mindustry.content.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.gen.*; +import io.anuke.mindustry.graphics.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.ui.*; +import io.anuke.mindustry.world.*; -import static io.anuke.mindustry.Vars.tilesize; +import static io.anuke.mindustry.Vars.*; public class CustomRulesDialog extends FloatingDialog{ private Table main; private Rules rules; private Supplier resetter; private LoadoutDialog loadoutDialog; + private FloatingDialog banDialog; public CustomRulesDialog(){ super("$mode.custom"); loadoutDialog = new LoadoutDialog(); + banDialog = new FloatingDialog("$bannedblocks"); + banDialog.addCloseButton(); + + banDialog.shown(this::rebuildBanned); + banDialog.buttons.addImageTextButton("$addall", Icon.arrow16Small, () -> { + rules.bannedBlocks.addAll(content.blocks().select(Block::isBuildable)); + rebuildBanned(); + }).size(180, 64f); + + banDialog.buttons.addImageTextButton("$clear", Icon.trash16Small, () -> { + rules.bannedBlocks.clear(); + rebuildBanned(); + }).size(180, 64f); + setFillParent(true); shown(this::setup); addCloseButton(); } + private void rebuildBanned(){ + float previousScroll = banDialog.cont.getChildren().isEmpty() ? 0f : ((ScrollPane)banDialog.cont.getChildren().first()).getScrollY(); + banDialog.cont.clear(); + banDialog.cont.pane(t -> { + t.margin(10f); + + if(rules.bannedBlocks.isEmpty()){ + t.add("$empty"); + } + + Array array = Array.with(rules.bannedBlocks); + array.sort(); + + int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3; + int i = 0; + + for(Block block : array){ + t.table(Tex.underline, b -> { + b.left().margin(4f); + b.addImage(block.icon(Cicon.medium)).size(Cicon.medium.size).padRight(3); + b.add(block.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap(); + + b.addImageButton(Icon.cancelSmall, Styles.clearPartiali, () -> { + rules.bannedBlocks.remove(block); + rebuildBanned(); + }).size(70f).pad(-4f).padLeft(0f); + }).size(300f, 70f).padRight(5); + + if(++i % cols == 0){ + t.row(); + } + } + }).get().setScrollYForce(previousScroll); + banDialog.cont.row(); + banDialog.cont.addImageTextButton("$add", Icon.addSmall, () -> { + FloatingDialog dialog = new FloatingDialog("$add"); + dialog.cont.pane(t -> { + t.left().margin(14f); + int[] i = {0}; + content.blocks().each(b -> !rules.bannedBlocks.contains(b) && b.isBuildable(), b -> { + int cols = mobile && Core.graphics.isPortrait() ? 4 : 12; + t.addImageButton(new TextureRegionDrawable(b.icon(Cicon.medium)), Styles.cleari, () -> { + rules.bannedBlocks.add(b); + rebuildBanned(); + dialog.hide(); + }).size(60f).get().resizeImage(Cicon.medium.size); + + if(++i[0] % cols == 0){ + t.row(); + } + }); + }); + + dialog.addCloseButton(); + dialog.show(); + }).size(300f, 64f); + } + public void show(Rules rules, Supplier resetter){ this.rules = rules; this.resetter = resetter; @@ -67,18 +145,17 @@ public class CustomRulesDialog extends FloatingDialog{ number("$rules.buildspeedmultiplier", f -> rules.buildSpeedMultiplier = f, () -> rules.buildSpeedMultiplier); main.addButton("$configure", - () -> loadoutDialog.show( - Blocks.coreShard.itemCapacity, - () -> rules.loadout, - () -> { - rules.loadout.clear(); - rules.loadout.add(new ItemStack(Items.copper, 100)); - }, - () -> {}, () -> {}, - item -> item.type == ItemType.material + () -> loadoutDialog.show(Blocks.coreShard.itemCapacity, rules.loadout, + () -> { + rules.loadout.clear(); + rules.loadout.add(new ItemStack(Items.copper, 100)); + }, () -> {}, () -> {} )).left().width(300f); main.row(); + main.addButton("$bannedblocks", banDialog::show).left().width(300f); + main.row(); + title("$rules.title.player"); number("$rules.playerdamagemultiplier", f -> rules.playerDamageMultiplier = f, () -> rules.playerDamageMultiplier); number("$rules.playerhealthmultiplier", f -> rules.playerHealthMultiplier = f, () -> rules.playerHealthMultiplier); diff --git a/core/src/io/anuke/mindustry/ui/dialogs/DeployDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/DeployDialog.java index 86841152e8..2f8a1d804b 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/DeployDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/DeployDialog.java @@ -21,9 +21,7 @@ import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.io.SaveIO.*; import io.anuke.mindustry.type.*; -import io.anuke.mindustry.type.Zone.*; import io.anuke.mindustry.ui.*; -import io.anuke.mindustry.ui.Styles; import io.anuke.mindustry.ui.TreeLayout.*; import static io.anuke.mindustry.Vars.*; @@ -129,7 +127,7 @@ public class DeployDialog extends FloatingDialog{ button.defaults().colspan(2); button.row(); - button.add(Core.bundle.format("save.wave", color + slot.getWave())); + button.add(Core.bundle.format("save", color + slot.getWave())); button.row(); button.label(() -> Core.bundle.format("save.playtime", color + slot.getPlayTime())); button.row(); @@ -156,7 +154,7 @@ public class DeployDialog extends FloatingDialog{ node.allChildren.clear(); node.allChildren.addAll(node.children); for(ZoneNode other : new ObjectSetIterator<>(nodes)){ - if(Structs.contains(other.zone.zoneRequirements, req -> req.zone == node.zone)){ + if(other.zone.requirements.contains(req -> req.zone() == node.zone)){ node.allChildren.add(other); } } @@ -164,12 +162,7 @@ public class DeployDialog extends FloatingDialog{ } boolean hidden(Zone zone){ - for(ZoneRequirement other : zone.zoneRequirements){ - if(!data.isUnlocked(other.zone)){ - return true; - } - } - return false; + return zone.requirements.contains(o -> o.zone() != null && o.zone().locked()); } void buildButton(Zone zone, Button button){ @@ -258,7 +251,7 @@ public class DeployDialog extends FloatingDialog{ //this.height /= 2f; nodes.add(this); - arr.selectFrom(content.zones(), other -> other.zoneRequirements.length > 0 && other.zoneRequirements[0].zone == zone); + arr.selectFrom(content.zones(), other -> other.requirements.size > 0 && other.requirements.first().zone() == zone); children = new ZoneNode[arr.size]; for(int i = 0; i < children.length; i++){ diff --git a/core/src/io/anuke/mindustry/ui/dialogs/LoadoutDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/LoadoutDialog.java index 9e541990e6..fc5b560678 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/LoadoutDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/LoadoutDialog.java @@ -2,9 +2,7 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.arc.*; import io.anuke.arc.collection.*; -import io.anuke.arc.function.*; import io.anuke.arc.input.*; -import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.arc.util.*; import io.anuke.mindustry.game.*; @@ -16,10 +14,12 @@ import static io.anuke.mindustry.Vars.*; public class LoadoutDialog extends FloatingDialog{ private Runnable hider; - private Supplier> supplier; + //private Supplier> supplier; private Runnable resetter; private Runnable updater; - private Predicate filter; + private Array stacks = new Array<>(); + private Array originalStacks = new Array<>(); + //private Predicate filter; private Table items; private int capacity; @@ -33,54 +33,37 @@ public class LoadoutDialog extends FloatingDialog{ } }); - cont.add(items = new Table()).left(); + cont.pane(t -> items = t.margin(10f)).left(); shown(this::setup); hidden(() -> { + originalStacks.selectFrom(stacks, s -> s.amount > 0); + updater.run(); if(hider != null){ hider.run(); } }); - cont.row(); + buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f); - cont.addButton("$add", () -> { - FloatingDialog dialog = new FloatingDialog(""); - dialog.setFillParent(false); - for(Item item : content.items().select(item -> filter.test(item) && item.type == ItemType.material && supplier.get().find(stack -> stack.item == item) == null)){ - TextButton button = dialog.cont.addButton("", Styles.cleart, () -> { - dialog.hide(); - supplier.get().add(new ItemStack(item, 0)); - updater.run(); - setup(); - }).size(300f, 36f).get(); - button.clearChildren(); - button.left(); - button.addImage(item.icon(Cicon.small)).size(8 * 3).pad(4); - button.add(item.localizedName); - dialog.cont.row(); - } - dialog.show(); - }).size(100f, 40).left().disabled(b -> !content.items().contains(item -> filter.test(item) && !supplier.get().contains(stack -> stack.item == item))); - - cont.row(); - cont.addButton("$settings.reset", () -> { + buttons.addImageTextButton("$settings.reset", Icon.refreshSmall, () -> { resetter.run(); updater.run(); setup(); }).size(210f, 64f); - - cont.row(); - cont.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f); } - public void show(int capacity, Supplier> supplier, Runnable reseter, Runnable updater, Runnable hider, Predicate filter){ + public void show(int capacity, Array stacks, Runnable reseter, Runnable updater, Runnable hider){ + this.originalStacks = stacks; + this.stacks = stacks.map(ItemStack::copy); + this.stacks.addAll(content.items().select(i -> i.type == ItemType.material && + !stacks.contains(stack -> stack.item == i)).map(i -> new ItemStack(i, 0))); + this.stacks.sort(Structs.comparingInt(s -> s.item.id)); this.resetter = reseter; - this.supplier = supplier; this.updater = updater; this.capacity = capacity; this.hider = hider; - this.filter = filter; + //this.filter = filter; show(); } @@ -88,41 +71,54 @@ public class LoadoutDialog extends FloatingDialog{ items.clearChildren(); items.left(); float bsize = 40f; - int step = 50; - for(ItemStack stack : supplier.get()){ - items.addButton("x", Styles.clearPartialt, () -> { - supplier.get().remove(stack); - updater.run(); - setup(); - }).size(bsize); + int i = 0; - items.addButton("-", Styles.clearPartialt, () -> { - stack.amount = Math.max(stack.amount - step, 0); - updater.run(); - }).size(bsize); + for(ItemStack stack : stacks){ + items.table(Tex.pane, t -> { + t.margin(4).marginRight(8).left(); + t.addButton("-", Styles.cleart, () -> { + stack.amount = Math.max(stack.amount - step(stack.amount), 0); + updater.run(); + }).size(bsize); - items.addButton("+", Styles.clearPartialt, () -> { - stack.amount = Math.min(stack.amount + step, capacity); - updater.run(); - }).size(bsize); + t.addButton("+", Styles.cleart, () -> { + stack.amount = Math.min(stack.amount + step(stack.amount), capacity); + updater.run(); + }).size(bsize); - items.addImageButton(Icon.pencilSmaller, Styles.clearPartial2i, () -> ui.showTextInput("$configure", stack.item.localizedName, 10, stack.amount + "", true, str -> { - if(Strings.canParsePostiveInt(str)){ - int amount = Strings.parseInt(str); - if(amount >= 0 && amount <= capacity){ - stack.amount = amount; - updater.run(); - return; + t.addImageButton(Icon.pencilSmaller, Styles.cleari, () -> ui.showTextInput("$configure", stack.item.localizedName, 10, stack.amount + "", true, str -> { + if(Strings.canParsePostiveInt(str)){ + int amount = Strings.parseInt(str); + if(amount >= 0 && amount <= capacity){ + stack.amount = amount; + updater.run(); + return; + } } - } - ui.showInfo(Core.bundle.format("configure.invalid", capacity)); - })).size(bsize); + ui.showInfo(Core.bundle.format("configure.invalid", capacity)); + })).size(bsize); - items.addImage(stack.item.icon(Cicon.small)).size(8 * 3).padRight(4).padLeft(4); - items.label(() -> stack.amount + "").left(); + t.addImage(stack.item.icon(Cicon.small)).size(8 * 3).padRight(4).padLeft(4); + t.label(() -> stack.amount + "").left().width(90f); + }).pad(2).left().fillX(); - items.row(); + + if(++i % 2 == 0 || (mobile && Core.graphics.isPortrait())){ + items.row(); + } + } + } + + private int step(int amount){ + if(amount < 1000){ + return 100; + }else if(amount < 2000){ + return 200; + }else if(amount < 5000){ + return 500; + }else{ + return 1000; } } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java index ab943f8f5b..7a7d716294 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/ZoneInfoDialog.java @@ -1,15 +1,15 @@ package io.anuke.mindustry.ui.dialogs; import io.anuke.arc.*; +import io.anuke.arc.collection.*; import io.anuke.arc.graphics.*; import io.anuke.arc.scene.ui.*; import io.anuke.arc.scene.ui.layout.*; import io.anuke.mindustry.game.*; +import io.anuke.mindustry.game.Objectives.*; import io.anuke.mindustry.gen.*; import io.anuke.mindustry.graphics.*; import io.anuke.mindustry.type.*; -import io.anuke.mindustry.type.Zone.*; -import io.anuke.mindustry.world.*; import static io.anuke.mindustry.Vars.*; @@ -38,8 +38,7 @@ public class ZoneInfoDialog extends FloatingDialog{ if(!zone.unlocked()) return; - ItemStack[] stacks = zone.getLaunchCost(); - for(ItemStack stack : stacks){ + for(ItemStack stack : zone.getLaunchCost()){ if(stack.amount == 0) continue; if(i++ % 2 == 0){ @@ -62,29 +61,32 @@ public class ZoneInfoDialog extends FloatingDialog{ cont.table(req -> { req.defaults().left(); - if(zone.zoneRequirements.length > 0){ + Array zones = zone.requirements.select(o -> !(o instanceof Unlock)); + + if(!zones.isEmpty()){ req.table(r -> { r.add("$complete").colspan(2).left(); r.row(); - for(ZoneRequirement other : zone.zoneRequirements){ + for(Objective o : zones){ r.addImage(Icon.terrain).padRight(4); - r.add(Core.bundle.format("zone.requirement", other.wave, other.zone.localizedName())).color(Color.lightGray); - r.addImage(other.zone.bestWave() >= other.wave ? Icon.checkSmall : Icon.cancelSmall, other.zone.bestWave() >= other.wave ? Color.lightGray : Color.scarlet).padLeft(3); + r.add(o.display()).color(Color.lightGray); + r.addImage(o.complete() ? Icon.checkSmall : Icon.cancelSmall, o.complete() ? Color.lightGray : Color.scarlet).padLeft(3); r.row(); } }); } req.row(); + Array blocks = zone.requirements.select(o -> o instanceof Unlock).as(Unlock.class); - if(zone.blockRequirements.length > 0){ + if(!blocks.isEmpty()){ req.table(r -> { r.add("$research.list").colspan(2).left(); r.row(); - for(Block block : zone.blockRequirements){ - r.addImage(block.icon(Cicon.small)).size(8 * 3).padRight(4); - r.add(block.localizedName).color(Color.lightGray); - r.addImage(data.isUnlocked(block) ? Icon.checkSmall : Icon.cancelSmall, data.isUnlocked(block) ? Color.lightGray : Color.scarlet).padLeft(3); + for(Unlock blocko : blocks){ + r.addImage(blocko.block.icon(Cicon.small)).size(8 * 3).padRight(5); + r.add(blocko.block.localizedName).color(Color.lightGray).left(); + r.addImage(blocko.block.unlocked() ? Icon.checkSmall : Icon.cancelSmall, blocko.block.unlocked() ? Color.lightGray : Color.scarlet).padLeft(3); r.row(); } @@ -106,7 +108,7 @@ public class ZoneInfoDialog extends FloatingDialog{ t.left(); t.add("$zone.resources").padRight(6); - if(zone.resources.length > 0){ + if(zone.resources.size > 0){ t.table(r -> { t.left(); int i = 0; @@ -135,8 +137,8 @@ public class ZoneInfoDialog extends FloatingDialog{ cont.row(); - cont.addButton(zone.canConfigure() ? "$configure" : Core.bundle.format("configure.locked", zone.configureWave), - () -> loadout.show(zone.loadout.core().itemCapacity, zone::getStartingItems, zone::resetStartingItems, zone::updateLaunchCost, rebuildItems, item -> data.getItem(item) > 0 && item.type == ItemType.material) + cont.addButton(zone.canConfigure() ? "$configure" : Core.bundle.format("configure.locked", zone.configureObjective.display()), + () -> loadout.show(zone.loadout.core().itemCapacity, zone.getStartingItems(), zone::resetStartingItems, zone::updateLaunchCost, rebuildItems) ).fillX().pad(3).disabled(b -> !zone.canConfigure()); } cont.marginRight(12f); diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index 98ce9f2159..c21f08a4e2 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -151,6 +151,10 @@ public class PlacementFragment extends Fragment{ Color color = state.rules.infiniteResources || (core != null && (core.items.has(block.requirements, state.rules.buildCostMultiplier) || state.rules.infiniteResources)) ? Color.white : Color.gray; button.forEach(elem -> elem.setColor(color)); button.setChecked(control.input.block == block); + + if(state.rules.bannedBlocks.contains(block)){ + button.forEach(elem -> elem.setColor(Color.darkGray)); + } }); button.hovered(() -> hovered = block); @@ -226,6 +230,15 @@ public class PlacementFragment extends Fragment{ } }).growX().left().margin(3); + if(state.rules.bannedBlocks.contains(lastDisplay)){ + topTable.row(); + topTable.table(b -> { + b.addImage(Icon.cancelSmall).padRight(2).color(Color.scarlet); + b.add("$banned"); + b.left(); + }).padTop(2).left(); + } + }else if(tileDisplayBlock() != null){ //show selected tile lastDisplay = tileDisplayBlock(); topTable.table(t -> { @@ -301,7 +314,11 @@ public class PlacementFragment extends Fragment{ returnArray.add(block); } } - returnArray.sort((b1, b2) -> -Boolean.compare(unlocked(b1), unlocked(b2))); + returnArray.sort((b1, b2) -> { + int locked = -Boolean.compare(unlocked(b1), unlocked(b2)); + if(locked != 0) return locked; + return Boolean.compare(state.rules.bannedBlocks.contains(b1), state.rules.bannedBlocks.contains(b2)); + }); return returnArray; } diff --git a/core/src/io/anuke/mindustry/world/Build.java b/core/src/io/anuke/mindustry/world/Build.java index bb08372000..7d1441314f 100644 --- a/core/src/io/anuke/mindustry/world/Build.java +++ b/core/src/io/anuke/mindustry/world/Build.java @@ -73,6 +73,10 @@ public class Build{ return false; } + if(state.rules.bannedBlocks.contains(type) && !(state.rules.waves && team == waveTeam)){ + return false; + } + if((type.solid || type.solidifes) && Units.anyEntities(x * tilesize + type.offset() - type.size*tilesize/2f, y * tilesize + type.offset() - type.size*tilesize/2f, type.size * tilesize, type.size*tilesize)){ return false; } diff --git a/gradle.properties b/gradle.properties index baf26c820e..dcfbb32c6f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=9f567f04a005ea8b4132b31d64f325ec105def5e +archash=59cff366c4d46577b659e153183eb824eaafd7f7 diff --git a/tests/src/test/java/ZoneTests.java b/tests/src/test/java/ZoneTests.java index f86cf714a2..ce5dedc053 100644 --- a/tests/src/test/java/ZoneTests.java +++ b/tests/src/test/java/ZoneTests.java @@ -1,18 +1,14 @@ -import io.anuke.arc.collection.Array; -import io.anuke.arc.collection.ObjectSet; -import io.anuke.arc.util.Structs; -import io.anuke.arc.util.Time; -import io.anuke.mindustry.core.GameState.State; -import io.anuke.mindustry.game.SpawnGroup; -import io.anuke.mindustry.type.Item; -import io.anuke.mindustry.type.Zone; -import io.anuke.mindustry.world.Tile; -import io.anuke.mindustry.world.blocks.storage.CoreBlock; +import io.anuke.arc.collection.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.core.GameState.*; +import io.anuke.mindustry.game.*; +import io.anuke.mindustry.type.*; +import io.anuke.mindustry.world.*; +import io.anuke.mindustry.world.blocks.storage.*; import org.junit.jupiter.api.*; import static io.anuke.mindustry.Vars.*; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.DynamicTest.dynamicTest; public class ZoneTests{ @@ -68,7 +64,7 @@ public class ZoneTests{ assertTrue(spawner.countSpawns() > 0 || (state.rules.attackMode && !state.teams.get(waveTeam).cores.isEmpty()), "Zone \"" + zone.name + "\" has no enemy spawn points: " + spawner.countSpawns()); for(Item item : resources){ - assertTrue(Structs.contains(zone.resources, item), "Zone \"" + zone.name + "\" is missing item in resource list: \"" + item.name + "\""); + assertTrue(zone.resources.contains(item), "Zone \"" + zone.name + "\" is missing item in resource list: \"" + item.name + "\""); } for(Item item : zone.resources){