mirror of
https://github.com/Anuken/Mindustry.git
synced 2024-12-22 16:03:59 +07:00
WIP campaign difficulty dialog
This commit is contained in:
parent
efb86724a1
commit
d8eabece7c
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
- name: Run unit tests and build JAR
|
||||
run: ./gradlew desktop:dist
|
||||
- name: Upload desktop JAR for testing
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Desktop JAR (zipped)
|
||||
path: desktop/build/libs/Mindustry.jar
|
||||
|
@ -197,6 +197,7 @@ campaign.select = Select Starting Campaign
|
||||
campaign.none = [lightgray]Select a planet to start on.\nThis can be switched at any time.
|
||||
campaign.erekir = Newer, more polished content. Mostly linear campaign progression.\n\nMore difficult. Higher quality maps and overall experience.
|
||||
campaign.serpulo = Older content; the classic experience. More open-ended, more content.\n\nPotentially unbalanced maps and campaign mechanics. Less polished.
|
||||
campaign.difficulty = Difficulty
|
||||
completed = [accent]Researched
|
||||
techtree = Tech Tree
|
||||
techtree.select = Tech Tree Selection
|
||||
@ -800,6 +801,11 @@ threat.high = High
|
||||
threat.extreme = Extreme
|
||||
threat.eradication = Eradication
|
||||
|
||||
difficulty.easy = Easy
|
||||
difficulty.normal = Normal
|
||||
difficulty.hard = Hard
|
||||
difficulty.eradication = Eradication
|
||||
|
||||
planets = Planets
|
||||
|
||||
planet.serpulo.name = Serpulo
|
||||
@ -1172,12 +1178,6 @@ setting.fpscap.text = {0} FPS
|
||||
setting.uiscale.name = UI Scaling
|
||||
setting.uiscale.description = Restart required to apply changes.
|
||||
setting.swapdiagonal.name = Always Diagonal Placement
|
||||
setting.difficulty.training = Training
|
||||
setting.difficulty.easy = Easy
|
||||
setting.difficulty.normal = Normal
|
||||
setting.difficulty.hard = Hard
|
||||
setting.difficulty.insane = Insane
|
||||
setting.difficulty.name = Difficulty:
|
||||
setting.screenshake.name = Screen Shake
|
||||
setting.bloomintensity.name = Bloom Intensity
|
||||
setting.bloomblur.name = Bloom Blur
|
||||
@ -1397,6 +1397,8 @@ rules.title.teams = Teams
|
||||
rules.title.planet = Planet
|
||||
rules.lighting = Lighting
|
||||
rules.fog = Fog of War
|
||||
rules.invasions = Enemy Sector Invasions
|
||||
rules.showspawns = Show Enemy Spawns
|
||||
rules.fire = Fire
|
||||
rules.anyenv = <Any>
|
||||
rules.explosions = Block/Unit Explosion Damage
|
||||
|
@ -66,12 +66,19 @@ public class WaveSpawner{
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getSpawned(state.wave - 1);
|
||||
if(spawned == 0) continue;
|
||||
|
||||
if(state.isCampaign()){
|
||||
spawned = Math.max(1, Mathf.round(spawned * state.getPlanet().campaignRules.difficulty.enemySpawnMultiplier));
|
||||
}
|
||||
|
||||
int spawnedf = spawned;
|
||||
|
||||
if(group.type.flying){
|
||||
float spread = margin / 1.5f;
|
||||
|
||||
eachFlyerSpawn(group.spawn, (spawnX, spawnY) -> {
|
||||
for(int i = 0; i < spawned; i++){
|
||||
for(int i = 0; i < spawnedf; i++){
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
|
||||
spawnEffect(unit);
|
||||
@ -82,7 +89,7 @@ public class WaveSpawner{
|
||||
|
||||
eachGroundSpawn(group.spawn, (spawnX, spawnY, doShockwave) -> {
|
||||
|
||||
for(int i = 0; i < spawned; i++){
|
||||
for(int i = 0; i < spawnedf; i++){
|
||||
Tmp.v1.rnd(spread);
|
||||
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
|
@ -5326,7 +5326,7 @@ public class Blocks{
|
||||
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
|
||||
plans = Seq.with(
|
||||
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
|
||||
new UnitPlan(UnitTypes.retusa, 60f * 50f, with(Items.silicon, 15, Items.metaglass, 25, Items.titanium, 20))
|
||||
new UnitPlan(UnitTypes.retusa, 60f * 35f, with(Items.silicon, 15, Items.titanium, 20))
|
||||
);
|
||||
size = 3;
|
||||
consumePower(1.2f);
|
||||
|
@ -85,6 +85,8 @@ public class Planets{
|
||||
r.coreDestroyClear = true;
|
||||
r.onlyDepositCore = true;
|
||||
};
|
||||
campaignRuleDefaults.fog = true;
|
||||
campaignRuleDefaults.showSpawns = true;
|
||||
|
||||
unlockedOnLand.add(Blocks.coreBastion);
|
||||
}};
|
||||
|
@ -16,9 +16,9 @@ import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
@ -30,7 +30,6 @@ import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.io.*;
|
||||
@ -441,6 +440,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
state.wave = 1;
|
||||
//set up default wave time
|
||||
state.wavetime = state.rules.initialWaveSpacing <= 0f ? (state.rules.waveSpacing * (sector.preset == null ? 2f : sector.preset.startWaveTimeMultiplier)) : state.rules.initialWaveSpacing;
|
||||
state.wavetime *= sector.planet.campaignRules.difficulty.waveTimeMultiplier;
|
||||
//reset captured state
|
||||
sector.info.wasCaptured = false;
|
||||
|
||||
|
@ -92,7 +92,7 @@ public class Logic implements ApplicationListener{
|
||||
if(wavesPassed > 0){
|
||||
//simulate wave counter moving forward
|
||||
state.wave += wavesPassed;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing * state.getPlanet().campaignRules.difficulty.waveTimeMultiplier;
|
||||
|
||||
SectorDamage.applyCalculatedDamage();
|
||||
}
|
||||
@ -221,7 +221,7 @@ public class Logic implements ApplicationListener{
|
||||
public void play(){
|
||||
state.set(State.playing);
|
||||
//grace period of 2x wave time before game starts
|
||||
state.wavetime = state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing;
|
||||
state.wavetime = (state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing) * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);;
|
||||
Events.fire(new PlayEvent());
|
||||
|
||||
//add starting items
|
||||
@ -270,7 +270,7 @@ public class Logic implements ApplicationListener{
|
||||
public void runWave(){
|
||||
spawner.spawnEnemies();
|
||||
state.wave++;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);
|
||||
|
||||
Events.fire(new WaveEvent());
|
||||
}
|
||||
|
15
core/src/mindustry/game/CampaignRules.java
Normal file
15
core/src/mindustry/game/CampaignRules.java
Normal file
@ -0,0 +1,15 @@
|
||||
package mindustry.game;
|
||||
|
||||
public class CampaignRules{
|
||||
public Difficulty difficulty = Difficulty.normal;
|
||||
public boolean fog;
|
||||
public boolean showSpawns;
|
||||
public boolean sectorInvasion;
|
||||
|
||||
public void apply(Rules rules){
|
||||
rules.staticFog = rules.fog = fog;
|
||||
rules.showSpawns = showSpawns;
|
||||
rules.teams.get(rules.waveTeam).blockHealthMultiplier = difficulty.enemyHealthMultiplier;
|
||||
rules.teams.get(rules.waveTeam).unitHealthMultiplier = difficulty.enemyHealthMultiplier;
|
||||
}
|
||||
}
|
26
core/src/mindustry/game/Difficulty.java
Normal file
26
core/src/mindustry/game/Difficulty.java
Normal file
@ -0,0 +1,26 @@
|
||||
package mindustry.game;
|
||||
|
||||
import arc.*;
|
||||
|
||||
public enum Difficulty{
|
||||
//TODO these need tweaks
|
||||
easy(1f, 0.75f, 1.5f),
|
||||
normal(1f, 1f, 1f),
|
||||
hard(1.25f, 1.5f, 0.6f),
|
||||
eradication(1.5f, 2f, 0.4f);
|
||||
|
||||
public static final Difficulty[] all = values();
|
||||
|
||||
//TODO add more fields
|
||||
public float enemyHealthMultiplier, enemySpawnMultiplier, waveTimeMultiplier;
|
||||
|
||||
Difficulty(float enemyHealthMultiplier, float enemySpawnMultiplier, float waveTimeMultiplier){
|
||||
this.enemySpawnMultiplier = enemySpawnMultiplier;
|
||||
this.waveTimeMultiplier = waveTimeMultiplier;
|
||||
this.enemyHealthMultiplier = enemyHealthMultiplier;
|
||||
}
|
||||
|
||||
public String localized(){
|
||||
return Core.bundle.get("difficulty." + name());
|
||||
}
|
||||
}
|
@ -252,7 +252,7 @@ public class Universe{
|
||||
}
|
||||
|
||||
//queue random invasions
|
||||
if(!sector.isAttacked() && sector.planet.allowSectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
||||
if(!sector.isAttacked() && sector.planet.campaignRules.sectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
|
||||
int count = sector.near().count(s -> s.hasEnemyBase() && !s.hasBase());
|
||||
|
||||
//invasion chance depends on # of nearby bases
|
||||
|
@ -19,6 +19,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.graphics.g3d.*;
|
||||
import mindustry.graphics.g3d.PlanetGrid.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.maps.generators.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
@ -127,15 +128,21 @@ public class Planet extends UnlockableContent{
|
||||
public boolean allowWaves = false;
|
||||
/** If false, players are unable to land on this planet's numbered sectors. */
|
||||
public boolean allowLaunchToNumbered = true;
|
||||
/** If true, the player is allowed to change the difficulty/rules in the planet UI. */
|
||||
public boolean allowCampaignRules = false;
|
||||
/** Icon as displayed in the planet selection dialog. This is a string, as drawables are null at load time. */
|
||||
public String icon = "planet";
|
||||
/** Plays in the planet dialog when this planet is selected. */
|
||||
public Music launchMusic = Musics.launch;
|
||||
/** Default core block for launching. */
|
||||
public Block defaultCore = Blocks.coreShard;
|
||||
/** Global difficulty/modifier settings for this planet's campaign. */
|
||||
public CampaignRules campaignRules = new CampaignRules();
|
||||
/** Defaults applied to the rules. */
|
||||
public CampaignRules campaignRuleDefaults = new CampaignRules();
|
||||
/** Sets up rules on game load for any sector on this planet. */
|
||||
public Cons<Rules> ruleSetter = r -> {};
|
||||
/** Parent body that this planet orbits around. If null, this planet is considered to be in the middle of the solar system.*/
|
||||
/** Parent body that this planet orbits around. If null, this planet is considered to be in the middle of the solar system. */
|
||||
public @Nullable Planet parent;
|
||||
/** The root parent of the whole solar system this planet is in. */
|
||||
public Planet solarSystem;
|
||||
@ -183,6 +190,7 @@ public class Planet extends UnlockableContent{
|
||||
|
||||
//calculate solar system
|
||||
for(solarSystem = this; solarSystem.parent != null; solarSystem = solarSystem.parent);
|
||||
allowCampaignRules = isVanilla();
|
||||
}
|
||||
|
||||
public Planet(String name, Planet parent, float radius, int sectorSize){
|
||||
@ -200,17 +208,38 @@ public class Planet extends UnlockableContent{
|
||||
}
|
||||
}
|
||||
|
||||
public void saveRules(){
|
||||
Core.settings.putJson(name + "-campaign-rules", campaignRules);
|
||||
}
|
||||
|
||||
public void loadRules(){
|
||||
campaignRules = Core.settings.getJson(name + "-campaign-rules", CampaignRules.class, () -> campaignRules);
|
||||
}
|
||||
|
||||
public @Nullable Sector getStartSector(){
|
||||
return sectors.size == 0 ? null : sectors.get(startSector);
|
||||
}
|
||||
|
||||
public void applyRules(Rules rules){
|
||||
applyRules(rules, false);
|
||||
}
|
||||
|
||||
public void applyRules(Rules rules, boolean customGame){
|
||||
ruleSetter.get(rules);
|
||||
|
||||
rules.attributes.clear();
|
||||
rules.attributes.add(defaultAttributes);
|
||||
rules.env = defaultEnv;
|
||||
rules.planet = this;
|
||||
|
||||
if(!customGame){
|
||||
campaignRules.apply(rules);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyDefaultRules(CampaignRules rules){
|
||||
JsonIO.copy(campaignRuleDefaults, rules);
|
||||
rules.sectorInvasion = allowSectorInvasion;
|
||||
}
|
||||
|
||||
public @Nullable Sector getLastSector(){
|
||||
@ -327,6 +356,9 @@ public class Planet extends UnlockableContent{
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
applyDefaultRules(campaignRules);
|
||||
loadRules();
|
||||
|
||||
if(techTree == null){
|
||||
techTree = TechTree.roots.find(n -> n.planet == this);
|
||||
}
|
||||
|
86
core/src/mindustry/ui/dialogs/CampaignRulesDialog.java
Normal file
86
core/src/mindustry/ui/dialogs/CampaignRulesDialog.java
Normal file
@ -0,0 +1,86 @@
|
||||
package mindustry.ui.dialogs;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class CampaignRulesDialog extends BaseDialog{
|
||||
Planet planet;
|
||||
Table current;
|
||||
|
||||
public CampaignRulesDialog(){
|
||||
super("@campaign.difficulty");
|
||||
|
||||
addCloseButton();
|
||||
|
||||
hidden(() -> {
|
||||
if(planet != null){
|
||||
planet.saveRules();
|
||||
|
||||
if(Vars.state.isGame() && Vars.state.isCampaign() && Vars.state.getPlanet() == planet){
|
||||
planet.campaignRules.apply(Vars.state.rules);
|
||||
Call.setRules(Vars.state.rules);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void rebuild(){
|
||||
CampaignRules rules = planet.campaignRules;
|
||||
cont.clear();
|
||||
|
||||
cont.top().pane(inner -> {
|
||||
inner.top().left().defaults().fillX().left().pad(5);
|
||||
current = inner;
|
||||
|
||||
current.table(Tex.button, t -> {
|
||||
t.margin(10f);
|
||||
var group = new ButtonGroup<>();
|
||||
var style = Styles.flatTogglet;
|
||||
|
||||
t.defaults().size(140f, 50f);
|
||||
|
||||
for(Difficulty diff : Difficulty.all){
|
||||
t.button(diff.localized(), style, () -> {
|
||||
rules.difficulty = diff;
|
||||
}).group(group).checked(b -> rules.difficulty == diff);
|
||||
}
|
||||
}).left().fill(false).expand(false, false).row();
|
||||
|
||||
if(planet.allowSectorInvasion){
|
||||
check("@rules.invasions", b -> rules.sectorInvasion = b, () -> rules.sectorInvasion);
|
||||
}
|
||||
|
||||
check("@rules.fog", b -> rules.fog = b, () -> rules.fog);
|
||||
check("@rules.showspawns", b -> rules.showSpawns = b, () -> rules.showSpawns);
|
||||
}).growY();
|
||||
}
|
||||
|
||||
public void show(Planet planet){
|
||||
this.planet = planet;
|
||||
|
||||
rebuild();
|
||||
show();
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov){
|
||||
check(text, cons, prov, () -> true);
|
||||
}
|
||||
|
||||
void check(String text, Boolc cons, Boolp prov, Boolp condition){
|
||||
String infoText = text.substring(1) + ".info";
|
||||
var cell = current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get()));
|
||||
if(Core.bundle.has(infoText)){
|
||||
cell.tooltip(text + ".info");
|
||||
}
|
||||
cell.get().left();
|
||||
current.row();
|
||||
}
|
||||
|
||||
}
|
@ -329,7 +329,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
|
||||
for(Planet planet : content.planets().select(p -> p.accessible && p.visible && p.isLandable())){
|
||||
t.button(planet.localizedName, style, () -> {
|
||||
planet.applyRules(rules);
|
||||
planet.applyRules(rules, true);
|
||||
}).group(group).checked(b -> rules.planet == planet);
|
||||
|
||||
if(t.getChildren().size % 3 == 0){
|
||||
|
@ -67,6 +67,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
public Label hoverLabel = new Label("");
|
||||
|
||||
private Texture[] planetTextures;
|
||||
private CampaignRulesDialog campaignRules = new CampaignRulesDialog();
|
||||
|
||||
public PlanetDialog(){
|
||||
super("", Styles.fullDialog);
|
||||
@ -387,7 +388,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
//preset sectors can only be selected once unlocked
|
||||
if(sector.preset != null){
|
||||
TechNode node = sector.preset.techNode;
|
||||
return node == null || node.parent == null || (node.parent.content.unlocked() && (!(node.parent.content instanceof SectorPreset preset) || preset.sector.hasBase()));
|
||||
return sector.preset.unlocked() || node == null || node.parent == null || (node.parent.content.unlocked() && (!(node.parent.content instanceof SectorPreset preset) || preset.sector.hasBase()));
|
||||
}
|
||||
|
||||
return sector.planet.generator != null ?
|
||||
@ -474,7 +475,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
if(state.uiAlpha > 0.001f){
|
||||
for(Sector sec : planet.sectors){
|
||||
if(sec.hasBase()){
|
||||
if(planet.allowSectorInvasion){
|
||||
if(planet.campaignRules.sectorInvasion){
|
||||
for(Sector enemy : sec.near()){
|
||||
if(enemy.hasEnemyBase()){
|
||||
planets.drawArc(planet, enemy.tile.v, sec.tile.v, Team.crux.color.write(Tmp.c2).a(state.uiAlpha), Color.clear, 0.24f, 110f, 25);
|
||||
@ -612,6 +613,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
t.top().left();
|
||||
ScrollPane pane = new ScrollPane(null, Styles.smallPane);
|
||||
t.add(pane).colspan(2).row();
|
||||
t.button("@campaign.difficulty", Icon.bookSmall, () -> {
|
||||
campaignRules.show(state.planet);
|
||||
}).margin(12f).size(208f, 40f).padTop(12f).visible(() -> state.planet.allowCampaignRules).row();
|
||||
t.add().height(64f); //padding for close button
|
||||
Table starsTable = new Table(Styles.black);
|
||||
pane.setWidget(starsTable);
|
||||
pane.setScrollingDisabled(true, false);
|
||||
@ -1133,7 +1138,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
if(sector.isAttacked()){
|
||||
addSurvivedInfo(sector, stable, false);
|
||||
}else if(sector.hasBase() && sector.planet.allowSectorInvasion && sector.near().contains(Sector::hasEnemyBase)){
|
||||
}else if(sector.hasBase() && sector.planet.campaignRules.sectorInvasion && sector.near().contains(Sector::hasEnemyBase)){
|
||||
stable.add("@sectors.vulnerable");
|
||||
stable.row();
|
||||
}else if(!sector.hasBase() && sector.hasEnemyBase()){
|
||||
|
@ -32,5 +32,5 @@ public enum BlockFlag{
|
||||
public final static BlockFlag[] all = values();
|
||||
|
||||
/** Values for logic only. Filters out some internal flags. */
|
||||
public final static BlockFlag[] allLogic = {core, storage, generator, turret, factory, repair, battery, reactor};
|
||||
public final static BlockFlag[] allLogic = {core, storage, generator, turret, factory, repair, battery, reactor, drill};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user