WIP campaign stat tracking (not displayed yet)

This commit is contained in:
Anuken
2026-03-07 20:55:24 -05:00
parent 2b74317b34
commit b85056a493
8 changed files with 127 additions and 10 deletions

View File

@@ -187,7 +187,7 @@ public class Control implements ApplicationListener, Loadable{
Events.on(GameOverEvent.class, e -> {
if(state.isCampaign() && !net.client() && !headless){
//save gameover sate immediately
//save gameover state immediately
if(saves.getCurrent() != null){
saves.getCurrent().save();
}
@@ -285,7 +285,12 @@ public class Control implements ApplicationListener, Loadable{
}
});
Events.on(SaveWriteEvent.class, e -> forcePlaceAll());
Events.on(SaveWriteEvent.class, e -> {
if(!net.client() && state.isCampaign()){
state.getPlanet().saveStats();
}
forcePlaceAll();
});
Events.on(HostEvent.class, e -> forcePlaceAll());
Events.on(HostEvent.class, e -> {
state.set(State.playing);

View File

@@ -135,6 +135,10 @@ public class Logic implements ApplicationListener{
if(!net.client() && e.sector.planet.generator != null){
e.sector.planet.generator.onSectorCaptured(e.sector);
}
if(checkCampaignStats()){
state.getPlanet().stats().sectorsCaptured ++;
}
});
Events.on(SectorLoseEvent.class, e -> {
@@ -157,38 +161,71 @@ public class Logic implements ApplicationListener{
}));
Events.on(BlockBuildEndEvent.class, e -> {
if(e.team == state.rules.defaultTeam){
if((e.team == state.rules.defaultTeam || e.unit != null && e.unit.team == state.rules.defaultTeam)){
if(e.breaking){
state.stats.buildingsDeconstructed++;
}else{
state.stats.buildingsBuilt++;
}
if(checkCampaignStats()){
(e.breaking ? state.getPlanet().stats().buildingsDeconstructed : state.getPlanet().stats().buildingsBuilt).increment(e.tile.block());
}
}
});
Events.on(BlockDestroyEvent.class, e -> {
if(e.tile.team() == state.rules.defaultTeam){
state.stats.buildingsDestroyed ++;
}
});
Events.on(BlockDestroyEvent.class, e -> {
if(e.tile.team() != state.rules.defaultTeam){
if(checkCampaignStats()){
state.getPlanet().stats().buildingsDestroyed.increment(e.tile.block());
}
}else{ //...should derelict blocks count as 'destroyed'? technically, they could be destroyed by the enemy, but that is very rare
state.stats.destroyedBlockCount.increment(e.tile.block());
if(checkCampaignStats()){
state.getPlanet().stats().enemyBuildingsDestroyed.increment(e.tile.block());
}
}
});
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit.team() != state.rules.defaultTeam){
state.stats.enemyUnitsDestroyed ++;
if(checkCampaignStats()){
state.getPlanet().stats().enemyUnitsDestroyed.increment(e.unit.type);
}
}
});
Events.on(UnitCreateEvent.class, e -> {
if(e.unit.team == state.rules.defaultTeam){
state.stats.unitsCreated++;
if(checkCampaignStats()){
state.getPlanet().stats().unitsProduced.increment(e.unit.type);
}
}
});
Events.on(WaveEvent.class, e -> {
if(checkCampaignStats()){
state.getPlanet().stats().wavesLasted ++;
}
});
Events.on(GameOverEvent.class, e -> {
if(checkCampaignStats()){
state.getPlanet().stats().sectorsLost ++;
}
});
}
private boolean checkCampaignStats(){
return state.isCampaign() && !net.client();
}
private void checkOverlappingPlans(Team team, Tile tile){

View File

@@ -0,0 +1,29 @@
package mindustry.game;
import arc.struct.*;
import mindustry.type.*;
import mindustry.world.*;
/** Statistics for a specific planet's campaign. */
public class CampaignStats{
/** Enemy units destroyed by type. */
public ObjectIntMap<UnitType> enemyUnitsDestroyed = new ObjectIntMap<>();
/** Record of enemy blocks that have been destroyed (from any source) by count. */
public ObjectIntMap<Block> enemyBuildingsDestroyed = new ObjectIntMap<>();
/** Player team units produced by type. */
public ObjectIntMap<UnitType> unitsProduced = new ObjectIntMap<>();
/** Record of blocks that have been placed by count. */
public ObjectIntMap<Block> buildingsBuilt = new ObjectIntMap<>();
/** Record of blocks that have been placed by count. */
public ObjectIntMap<Block> buildingsDeconstructed = new ObjectIntMap<>();
/** Record of blocks that have been placed by count. */
public ObjectIntMap<Block> buildingsDestroyed = new ObjectIntMap<>();
/** Total campaign playtime in milliseconds. */
public long playtime;
/** Total game-overs. */
public int sectorsLost;
/** Total times a sector has been captured. If you lose (or get invaded) and re-capture something, this still counts. */
public int sectorsCaptured;
/** Total waves lasted. */
public int wavesLasted;
}

View File

@@ -22,7 +22,7 @@ public class GameStats{
/** Record of enemy blocks that have been destroyed (from any source) by count. */
public ObjectIntMap<Block> destroyedBlockCount = new ObjectIntMap<>();
/**
* Record of items that have entered the core through transport blocks. Used for objectives only.
* Record of items that have entered the core through transport blocks. Used for tutorial objectives only.
* This can easily be ""spoofed"" with unloaders, so don't use it for anything remotely important.
* */
public ObjectIntMap<Item> coreItemCount = new ObjectIntMap<>();

View File

@@ -187,7 +187,11 @@ public class Saves{
if(current != null && state.isGame()
&& !(state.isPaused() && Core.scene.hasDialog())){
if(lastTimestamp != 0){
totalPlaytime += Time.timeSinceMillis(lastTimestamp);
long change = Time.timeSinceMillis(lastTimestamp);
totalPlaytime += change;
if(state.isCampaign()){
state.getPlanet().stats().playtime += change;
}
}
lastTimestamp = Time.millis();
}

View File

@@ -162,6 +162,9 @@ public class Planet extends UnlockableContent{
/** Loads the planet grid outline mesh. Clientside only. */
public Prov<Mesh> gridMeshLoader = () -> MeshBuilder.buildPlanetGrid(grid, outlineColor, outlineRad * radius);
/** If set, this planet will have the same stats as its parent. Use for shared campaigns. */
public @Nullable Planet statParent;
/** Planets that are allowed to update at the same time as this one for background calculations. */
public ObjectSet<Planet> updateGroup = new ObjectSet<>();
/** Global difficulty/modifier settings for this planet's campaign. */
@@ -178,6 +181,9 @@ public class Planet extends UnlockableContent{
/** Data indicating attack sector positions and sector mappings. */
public @Nullable PlanetData data;
/** Statistics of this planet campaign. If statParent is not null, this planet shares the same stats as the parent. */
private CampaignStats campaignStats = new CampaignStats();
public Planet(String name, Planet parent, float radius){
super(name);
@@ -228,6 +234,34 @@ public class Planet extends UnlockableContent{
campaignRules = Core.settings.getJson(name + "-campaign-rules", CampaignRules.class, () -> campaignRules);
}
public CampaignStats stats(){
return statParent != null ? statParent.campaignStats : campaignStats;
}
public void loadStats(){
//there is no need to load stats if the parent's ones are used
if(statParent == null){
campaignStats = Core.settings.getJson(name + "-campaign-stats", CampaignStats.class, CampaignStats::new);
}
}
public void saveStats(){
if(statParent != null && statParent != this){
statParent.saveStats();
}else{
Core.settings.putJson(name + "-campaign-stats", campaignStats);
}
}
public void clearStats(){
if(statParent != null && statParent != this){
statParent.clearStats();
}else{
campaignStats = new CampaignStats();
saveStats();
}
}
public @Nullable Sector getStartSector(){
return sectors.size == 0 ? null : sectors.get(startSector);
}
@@ -391,6 +425,7 @@ public class Planet extends UnlockableContent{
public void init(){
applyDefaultRules(campaignRules);
loadRules();
loadStats();
if(techTree == null){
techTree = TechTree.roots.find(n -> n.planet == this);

View File

@@ -36,7 +36,13 @@ public class PausedDialog extends BaseDialog{
}).size(70f).tooltip("@customize").visible(() -> state.rules.allowEditRules && (net.server() || !net.active()));
})).grow().row();
shown(this::rebuild);
shown(() -> {
rebuild();
if(state.isCampaign()){
state.getPlanet().saveStats();
}
});
addCloseListener();
}

View File

@@ -134,6 +134,7 @@ public class SettingsMenuDialog extends BaseDialog{
t.button("@settings.clearcampaignsaves", Icon.trash, style, () -> {
ui.showConfirm("@confirm", "@settings.clearcampaignsaves.confirm", () -> {
for(var planet : content.planets()){
planet.clearStats();
for(var sec : planet.sectors){
sec.clearInfo();
if(sec.save != null){