Skip to content

Commit d8eabec

Browse files
committed
WIP campaign difficulty dialog
1 parent efb8672 commit d8eabec

15 files changed

+204
-29
lines changed

.github/workflows/pr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Run unit tests and build JAR
2323
run: ./gradlew desktop:dist
2424
- name: Upload desktop JAR for testing
25-
uses: actions/upload-artifact@v2
25+
uses: actions/upload-artifact@v4
2626
with:
2727
name: Desktop JAR (zipped)
2828
path: desktop/build/libs/Mindustry.jar

core/assets/bundles/bundle.properties

+8-6
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ campaign.select = Select Starting Campaign
197197
campaign.none = [lightgray]Select a planet to start on.\nThis can be switched at any time.
198198
campaign.erekir = Newer, more polished content. Mostly linear campaign progression.\n\nMore difficult. Higher quality maps and overall experience.
199199
campaign.serpulo = Older content; the classic experience. More open-ended, more content.\n\nPotentially unbalanced maps and campaign mechanics. Less polished.
200+
campaign.difficulty = Difficulty
200201
completed = [accent]Researched
201202
techtree = Tech Tree
202203
techtree.select = Tech Tree Selection
@@ -800,6 +801,11 @@ threat.high = High
800801
threat.extreme = Extreme
801802
threat.eradication = Eradication
802803

804+
difficulty.easy = Easy
805+
difficulty.normal = Normal
806+
difficulty.hard = Hard
807+
difficulty.eradication = Eradication
808+
803809
planets = Planets
804810

805811
planet.serpulo.name = Serpulo
@@ -1172,12 +1178,6 @@ setting.fpscap.text = {0} FPS
11721178
setting.uiscale.name = UI Scaling
11731179
setting.uiscale.description = Restart required to apply changes.
11741180
setting.swapdiagonal.name = Always Diagonal Placement
1175-
setting.difficulty.training = Training
1176-
setting.difficulty.easy = Easy
1177-
setting.difficulty.normal = Normal
1178-
setting.difficulty.hard = Hard
1179-
setting.difficulty.insane = Insane
1180-
setting.difficulty.name = Difficulty:
11811181
setting.screenshake.name = Screen Shake
11821182
setting.bloomintensity.name = Bloom Intensity
11831183
setting.bloomblur.name = Bloom Blur
@@ -1397,6 +1397,8 @@ rules.title.teams = Teams
13971397
rules.title.planet = Planet
13981398
rules.lighting = Lighting
13991399
rules.fog = Fog of War
1400+
rules.invasions = Enemy Sector Invasions
1401+
rules.showspawns = Show Enemy Spawns
14001402
rules.fire = Fire
14011403
rules.anyenv = <Any>
14021404
rules.explosions = Block/Unit Explosion Damage

core/src/mindustry/ai/WaveSpawner.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,19 @@ public void spawnEnemies(){
6666
if(group.type == null) continue;
6767

6868
int spawned = group.getSpawned(state.wave - 1);
69+
if(spawned == 0) continue;
70+
71+
if(state.isCampaign()){
72+
spawned = Math.max(1, Mathf.round(spawned * state.getPlanet().campaignRules.difficulty.enemySpawnMultiplier));
73+
}
74+
75+
int spawnedf = spawned;
6976

7077
if(group.type.flying){
7178
float spread = margin / 1.5f;
7279

7380
eachFlyerSpawn(group.spawn, (spawnX, spawnY) -> {
74-
for(int i = 0; i < spawned; i++){
81+
for(int i = 0; i < spawnedf; i++){
7582
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
7683
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
7784
spawnEffect(unit);
@@ -82,7 +89,7 @@ public void spawnEnemies(){
8289

8390
eachGroundSpawn(group.spawn, (spawnX, spawnY, doShockwave) -> {
8491

85-
for(int i = 0; i < spawned; i++){
92+
for(int i = 0; i < spawnedf; i++){
8693
Tmp.v1.rnd(spread);
8794

8895
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
@@ -153,7 +160,7 @@ private void eachGroundSpawn(int filterPos, SpawnConsumer cons){
153160

154161
private void eachFlyerSpawn(int filterPos, Floatc2 cons){
155162
boolean airUseSpawns = state.rules.airUseSpawns;
156-
163+
157164
for(Tile tile : spawns){
158165
if(filterPos != -1 && filterPos != tile.pos()) continue;
159166

core/src/mindustry/content/Blocks.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public class Blocks{
156156

157157
//payloads
158158
payloadConveyor, payloadRouter, reinforcedPayloadConveyor, reinforcedPayloadRouter, payloadMassDriver, largePayloadMassDriver, smallDeconstructor, deconstructor, constructor, largeConstructor, payloadLoader, payloadUnloader,
159-
159+
160160
//logic
161161
message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell, memoryBank,
162162
canvas, reinforcedMessage,
@@ -1282,7 +1282,7 @@ public static void load(){
12821282
itemCapacity = 0;
12831283
consumePower(100f / 60f);
12841284
}};
1285-
1285+
12861286
slagHeater = new HeatProducer("slag-heater"){{
12871287
requirements(Category.crafting, with(Items.tungsten, 50, Items.oxide, 20, Items.beryllium, 20));
12881288

@@ -3405,7 +3405,7 @@ Items.surgeAlloy, new MissileBulletType(3.7f, 18){{
34053405
lightningLength = 10;
34063406
}}
34073407
);
3408-
3408+
34093409
shoot = new ShootBarrel(){{
34103410
barrels = new float[]{
34113411
-4, -1.25f, 0,
@@ -5326,7 +5326,7 @@ Items.surgeAlloy, new BasicBulletType(7f, 250){{
53265326
requirements(Category.units, with(Items.copper, 150, Items.lead, 130, Items.metaglass, 120));
53275327
plans = Seq.with(
53285328
new UnitPlan(UnitTypes.risso, 60f * 45f, with(Items.silicon, 20, Items.metaglass, 35)),
5329-
new UnitPlan(UnitTypes.retusa, 60f * 50f, with(Items.silicon, 15, Items.metaglass, 25, Items.titanium, 20))
5329+
new UnitPlan(UnitTypes.retusa, 60f * 35f, with(Items.silicon, 15, Items.titanium, 20))
53305330
);
53315331
size = 3;
53325332
consumePower(1.2f);
@@ -5930,7 +5930,7 @@ Items.surgeAlloy, new BasicBulletType(7f, 250){{
59305930

59315931
worldCell = new MemoryBlock("world-cell"){{
59325932
requirements(Category.logic, BuildVisibility.worldProcessorOnly, with());
5933-
5933+
59345934
targetable = false;
59355935
privileged = true;
59365936
memoryCapacity = 128;
@@ -5939,7 +5939,7 @@ Items.surgeAlloy, new BasicBulletType(7f, 250){{
59395939

59405940
worldMessage = new MessageBlock("world-message"){{
59415941
requirements(Category.logic, BuildVisibility.worldProcessorOnly, with());
5942-
5942+
59435943
targetable = false;
59445944
privileged = true;
59455945
}};

core/src/mindustry/content/Planets.java

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public static void load(){
8585
r.coreDestroyClear = true;
8686
r.onlyDepositCore = true;
8787
};
88+
campaignRuleDefaults.fog = true;
89+
campaignRuleDefaults.showSpawns = true;
8890

8991
unlockedOnLand.add(Blocks.coreBastion);
9092
}};

core/src/mindustry/core/Control.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import mindustry.content.TechTree.*;
1717
import mindustry.core.GameState.*;
1818
import mindustry.entities.*;
19+
import mindustry.game.*;
1920
import mindustry.game.EventType.*;
2021
import mindustry.game.Objectives.*;
21-
import mindustry.game.*;
2222
import mindustry.game.Saves.*;
2323
import mindustry.gen.*;
2424
import mindustry.input.*;
@@ -30,7 +30,6 @@
3030
import mindustry.type.*;
3131
import mindustry.ui.dialogs.*;
3232
import mindustry.world.*;
33-
import mindustry.world.blocks.storage.*;
3433
import mindustry.world.blocks.storage.CoreBlock.*;
3534

3635
import java.io.*;
@@ -441,6 +440,7 @@ void playSector(@Nullable Sector origin, Sector sector, WorldReloader reloader){
441440
state.wave = 1;
442441
//set up default wave time
443442
state.wavetime = state.rules.initialWaveSpacing <= 0f ? (state.rules.waveSpacing * (sector.preset == null ? 2f : sector.preset.startWaveTimeMultiplier)) : state.rules.initialWaveSpacing;
443+
state.wavetime *= sector.planet.campaignRules.difficulty.waveTimeMultiplier;
444444
//reset captured state
445445
sector.info.wasCaptured = false;
446446

core/src/mindustry/core/Logic.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public Logic(){
9292
if(wavesPassed > 0){
9393
//simulate wave counter moving forward
9494
state.wave += wavesPassed;
95-
state.wavetime = state.rules.waveSpacing;
95+
state.wavetime = state.rules.waveSpacing * state.getPlanet().campaignRules.difficulty.waveTimeMultiplier;
9696

9797
SectorDamage.applyCalculatedDamage();
9898
}
@@ -221,7 +221,7 @@ private void checkOverlappingPlans(Team team, Tile tile){
221221
public void play(){
222222
state.set(State.playing);
223223
//grace period of 2x wave time before game starts
224-
state.wavetime = state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing;
224+
state.wavetime = (state.rules.initialWaveSpacing <= 0 ? state.rules.waveSpacing * 2 : state.rules.initialWaveSpacing) * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);;
225225
Events.fire(new PlayEvent());
226226

227227
//add starting items
@@ -270,7 +270,7 @@ public void skipWave(){
270270
public void runWave(){
271271
spawner.spawnEnemies();
272272
state.wave++;
273-
state.wavetime = state.rules.waveSpacing;
273+
state.wavetime = state.rules.waveSpacing * (state.isCampaign() ? state.getPlanet().campaignRules.difficulty.waveTimeMultiplier : 1f);
274274

275275
Events.fire(new WaveEvent());
276276
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package mindustry.game;
2+
3+
public class CampaignRules{
4+
public Difficulty difficulty = Difficulty.normal;
5+
public boolean fog;
6+
public boolean showSpawns;
7+
public boolean sectorInvasion;
8+
9+
public void apply(Rules rules){
10+
rules.staticFog = rules.fog = fog;
11+
rules.showSpawns = showSpawns;
12+
rules.teams.get(rules.waveTeam).blockHealthMultiplier = difficulty.enemyHealthMultiplier;
13+
rules.teams.get(rules.waveTeam).unitHealthMultiplier = difficulty.enemyHealthMultiplier;
14+
}
15+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package mindustry.game;
2+
3+
import arc.*;
4+
5+
public enum Difficulty{
6+
//TODO these need tweaks
7+
easy(1f, 0.75f, 1.5f),
8+
normal(1f, 1f, 1f),
9+
hard(1.25f, 1.5f, 0.6f),
10+
eradication(1.5f, 2f, 0.4f);
11+
12+
public static final Difficulty[] all = values();
13+
14+
//TODO add more fields
15+
public float enemyHealthMultiplier, enemySpawnMultiplier, waveTimeMultiplier;
16+
17+
Difficulty(float enemyHealthMultiplier, float enemySpawnMultiplier, float waveTimeMultiplier){
18+
this.enemySpawnMultiplier = enemySpawnMultiplier;
19+
this.waveTimeMultiplier = waveTimeMultiplier;
20+
this.enemyHealthMultiplier = enemyHealthMultiplier;
21+
}
22+
23+
public String localized(){
24+
return Core.bundle.get("difficulty." + name());
25+
}
26+
}

core/src/mindustry/game/Universe.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public void runTurn(){
252252
}
253253

254254
//queue random invasions
255-
if(!sector.isAttacked() && sector.planet.allowSectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
255+
if(!sector.isAttacked() && sector.planet.campaignRules.sectorInvasion && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
256256
int count = sector.near().count(s -> s.hasEnemyBase() && !s.hasBase());
257257

258258
//invasion chance depends on # of nearby bases

core/src/mindustry/type/Planet.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import mindustry.graphics.*;
2020
import mindustry.graphics.g3d.*;
2121
import mindustry.graphics.g3d.PlanetGrid.*;
22+
import mindustry.io.*;
2223
import mindustry.maps.generators.*;
2324
import mindustry.world.*;
2425
import mindustry.world.blocks.*;
@@ -127,15 +128,21 @@ public class Planet extends UnlockableContent{
127128
public boolean allowWaves = false;
128129
/** If false, players are unable to land on this planet's numbered sectors. */
129130
public boolean allowLaunchToNumbered = true;
131+
/** If true, the player is allowed to change the difficulty/rules in the planet UI. */
132+
public boolean allowCampaignRules = false;
130133
/** Icon as displayed in the planet selection dialog. This is a string, as drawables are null at load time. */
131134
public String icon = "planet";
132135
/** Plays in the planet dialog when this planet is selected. */
133136
public Music launchMusic = Musics.launch;
134137
/** Default core block for launching. */
135138
public Block defaultCore = Blocks.coreShard;
139+
/** Global difficulty/modifier settings for this planet's campaign. */
140+
public CampaignRules campaignRules = new CampaignRules();
141+
/** Defaults applied to the rules. */
142+
public CampaignRules campaignRuleDefaults = new CampaignRules();
136143
/** Sets up rules on game load for any sector on this planet. */
137144
public Cons<Rules> ruleSetter = r -> {};
138-
/** Parent body that this planet orbits around. If null, this planet is considered to be in the middle of the solar system.*/
145+
/** Parent body that this planet orbits around. If null, this planet is considered to be in the middle of the solar system. */
139146
public @Nullable Planet parent;
140147
/** The root parent of the whole solar system this planet is in. */
141148
public Planet solarSystem;
@@ -183,6 +190,7 @@ public Planet(String name, Planet parent, float radius){
183190

184191
//calculate solar system
185192
for(solarSystem = this; solarSystem.parent != null; solarSystem = solarSystem.parent);
193+
allowCampaignRules = isVanilla();
186194
}
187195

188196
public Planet(String name, Planet parent, float radius, int sectorSize){
@@ -200,17 +208,38 @@ public Planet(String name, Planet parent, float radius, int sectorSize){
200208
}
201209
}
202210

211+
public void saveRules(){
212+
Core.settings.putJson(name + "-campaign-rules", campaignRules);
213+
}
214+
215+
public void loadRules(){
216+
campaignRules = Core.settings.getJson(name + "-campaign-rules", CampaignRules.class, () -> campaignRules);
217+
}
218+
203219
public @Nullable Sector getStartSector(){
204220
return sectors.size == 0 ? null : sectors.get(startSector);
205221
}
206222

207223
public void applyRules(Rules rules){
224+
applyRules(rules, false);
225+
}
226+
227+
public void applyRules(Rules rules, boolean customGame){
208228
ruleSetter.get(rules);
209229

210230
rules.attributes.clear();
211231
rules.attributes.add(defaultAttributes);
212232
rules.env = defaultEnv;
213233
rules.planet = this;
234+
235+
if(!customGame){
236+
campaignRules.apply(rules);
237+
}
238+
}
239+
240+
public void applyDefaultRules(CampaignRules rules){
241+
JsonIO.copy(campaignRuleDefaults, rules);
242+
rules.sectorInvasion = allowSectorInvasion;
214243
}
215244

216245
public @Nullable Sector getLastSector(){
@@ -327,6 +356,9 @@ public void load(){
327356

328357
@Override
329358
public void init(){
359+
applyDefaultRules(campaignRules);
360+
loadRules();
361+
330362
if(techTree == null){
331363
techTree = TechTree.roots.find(n -> n.planet == this);
332364
}

0 commit comments

Comments
 (0)