1
0
mirror of https://github.com/Anuken/Mindustry.git synced 2024-09-20 21:08:42 +03:00

Many various campaign mechanic changes

This commit is contained in:
Anuken 2020-07-07 21:13:03 -04:00
parent b95206cf87
commit 26e70fa585
11 changed files with 220 additions and 31 deletions

View File

@ -80,6 +80,8 @@ public class Vars implements Loadable{
public static final float buildingRange = 220f;
/** duration of time between turns in ticks */
public static final float turnDuration = 20 * Time.toMinutes;
/** turns needed to destroy a sector completely */
public static final float sectorDestructionTurns = 3f;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.05f;
/** launch animation duration */

View File

@ -11,6 +11,7 @@ import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.type.Weather.*;
import mindustry.world.*;
@ -87,14 +88,16 @@ public class Logic implements ApplicationListener{
//when loading a 'damaged' sector, propagate the damage
Events.on(WorldLoadEvent.class, e -> {
if(state.isCampaign() && state.rules.sector.getSecondsPassed() > 0){
if(state.isCampaign() && state.rules.sector.getSecondsPassed() > 0 && state.rules.sector.hasBase()){
long seconds = state.rules.sector.getSecondsPassed();
CoreEntity core = state.rules.defaultTeam.core();
//TODO figure out how to apply damage properly
// if(state.rules.sector.hasWaves()){
//SectorDamage.apply(seconds);
//}
//apply fractional damage based on how many turns have passed for this sector
float turnsPassed = seconds / (turnDuration / 60f);
if(state.rules.sector.hasWaves()){
SectorDamage.apply(turnsPassed / sectorDestructionTurns);
}
//add resources based on turns passed
if(state.rules.sector.save != null && core != null){

View File

@ -59,6 +59,15 @@ public class EventType{
/** Called when a game begins and the world is loaded. */
public static class WorldLoadEvent{}
/** Called when a sector is destroyed by waves when you're not there. */
public static class SectorLoseEvent{
public final Sector sector;
public SectorLoseEvent(Sector sector){
this.sector = sector;
}
}
public static class LaunchItemEvent{
public final ItemStack stack;

View File

@ -23,6 +23,13 @@ public class Universe{
public Universe(){
load();
//update base coverage on capture
Events.on(SectorCaptureEvent.class, e -> {
if(state.isCampaign()){
state.getSector().planet.updateBaseCoverage();
}
});
}
/** Update regardless of whether the player is in the campaign. */
@ -131,6 +138,17 @@ public class Universe{
//increment seconds passed for this sector by the time that just passed with this turn
if(!sector.isBeingPlayed()){
sector.setSecondsPassed(sector.getSecondsPassed() + actuallyPassed);
//check if the sector has been attacked too many times...
if(sector.hasBase() && sector.getSecondsPassed() * 60f > turnDuration * sectorDestructionTurns){
//fire event for losing the sector
Events.fire(new SectorLoseEvent(sector));
//if so, just delete the save for now. it's lost.
//TODO don't delete it later maybe
sector.save.delete();
sector.save = null;
}
}
//reset time spent to 0

View File

@ -3,7 +3,10 @@ package mindustry.maps;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.ai.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.*;
@ -11,15 +14,14 @@ import static mindustry.Vars.*;
public class SectorDamage{
//direct damage is for testing only
private static final boolean direct = false;
private static final boolean direct = false, rubble = true;
//TODO amount of damage could be related to wave spacing
public static void apply(float turns){
public static void apply(float fraction){
Tiles tiles = world.tiles;
Queue<Tile> frontier = new Queue<>();
float[][] values = new float[tiles.width][tiles.height];
float damage = turns*50;
float damage = fraction*80; //arbitrary damage value
//phase one: find all spawnpoints
for(Tile tile : tiles){
@ -29,6 +31,29 @@ public class SectorDamage{
}
}
Building core = state.rules.defaultTeam.core();
if(core != null && !frontier.isEmpty()){
for(Tile spawner : frontier){
//find path from spawn to core
Seq<Tile> path = Astar.pathfind(spawner, core.tile, t -> t.cost, t -> !(t.block().isStatic() && t.solid()));
int amount = (int)(path.size * fraction);
for(int i = 0; i < amount; i++){
Tile t = path.get(i);
Geometry.circle(t.x, t.y, tiles.width, tiles.height, 5, (cx, cy) -> {
Tile other = tiles.getn(cx, cy);
//just remove all the buildings in the way - as long as they're not cores!
if(other.build != null && other.team() == state.rules.defaultTeam && !(other.block() instanceof CoreBlock)){
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
Effects.rubble(other.build.x, other.build.y, other.block().size);
}
other.remove();
}
});
}
}
}
float falloff = (damage) / (Math.max(tiles.width, tiles.height) * Mathf.sqrt2);
int peak = 0;
@ -53,14 +78,16 @@ public class SectorDamage{
if(direct){
other.build.damage(currDamage);
}else{ //indirect damage happens at game load time
other.build.health(other.build.health() - currDamage);
other.build.health -= currDamage;
//don't kill the core!
if(other.block() instanceof CoreBlock) other.build.health = Math.max(other.build.health, 1f);
//remove the block when destroyed
if(other.build.health() < 0){
//rubble currently disabled
//if(!other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
// Effects.rubble(other.entity.x(), other.entity.y(), other.block().size);
//}
if(other.build.health < 0){
//rubble
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
Effects.rubble(other.build.x, other.build.y, other.block().size);
}
other.remove();
}
@ -78,5 +105,7 @@ public class SectorDamage{
}
}
}
}

View File

@ -1,13 +1,41 @@
package mindustry.maps.generators;
import arc.math.geom.*;
import arc.util.noise.*;
import mindustry.graphics.g3d.*;
import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.type.*;
import mindustry.type.Sector.*;
import mindustry.world.*;
public abstract class PlanetGenerator extends BasicGenerator implements HexMesher{
protected Sector sector;
/** Should generate sector bases for a planet. */
public void generateSector(Sector sector){
Ptile tile = sector.tile;
boolean any = false;
float noise = Noise.snoise3(tile.v.x, tile.v.y, tile.v.z, 0.001f, 0.5f);
if(noise > 0.028){
any = true;
}
if(noise < 0.15){
for(Ptile other : tile.tiles){
if(sector.planet.getSector(other).is(SectorAttribute.base)){
any = false;
break;
}
}
}
if(any){
sector.data.attributes |= (1 << SectorAttribute.base.ordinal());
}
}
protected void genTile(Vec3 position, TileGen tile){
}

View File

@ -288,6 +288,16 @@ public class TODOPlanetGenerator extends PlanetGenerator{
}
state.rules.waves = true;
float difficulty = sector.baseCoverage;
//scale up the spawning base on difficulty (this is just for testing)
for(SpawnGroup group : state.rules.spawns){
group.unitAmount *= difficulty;
if(group.unitScaling != SpawnGroup.never){
group.unitScaling *= difficulty;
}
}
}
@Override

View File

@ -10,6 +10,7 @@ import arc.struct.*;
import arc.util.ArcAnnotate.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.noise.*;
import mindustry.*;
import mindustry.ctype.*;
import mindustry.graphics.*;
@ -18,7 +19,7 @@ import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.maps.generators.*;
import mindustry.type.Sector.*;
import static mindustry.Vars.universe;
import static mindustry.Vars.*;
public class Planet extends UnlockableContent{
/** Default spacing between planet orbits in world units. */
@ -98,10 +99,6 @@ public class Planet extends UnlockableContent{
t.printStackTrace();
}
}
for(Sector sector : sectors){
sector.generate();
}
}else{
sectors = new Seq<>();
}
@ -183,6 +180,24 @@ public class Planet extends UnlockableContent{
return in;
}
/** Updates wave coverage of bases. */
public void updateBaseCoverage(){
for(Sector sector : sectors){
float sum = 1f;
for(Sector other : sector.inRange(2)){
if(other.is(SectorAttribute.base)){
sum += 1f;
}
}
if(sector.hasEnemyBase()){
sum += 2f;
}
sector.baseCoverage = sum;
}
}
/** @return the supplied matrix with transformation applied. */
public Mat3D getTransform(Mat3D mat){
return mat.setToTranslation(position).rotate(Vec3.Y, getRotation());
@ -193,6 +208,21 @@ public class Planet extends UnlockableContent{
mesh = meshLoader.get();
}
@Override
public void init(){
if(generator != null){
Noise.setSeed(id + 1);
for(Sector sector : sectors){
generator.generateSector(sector);
}
updateBaseCoverage();
}
}
@Override
public void dispose(){
if(mesh != null){

View File

@ -1,6 +1,7 @@
package mindustry.type;
import arc.*;
import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.ArcAnnotate.*;
@ -16,6 +17,9 @@ import static mindustry.Vars.*;
/** A small section of a planet. */
public class Sector{
private static final Seq<Sector> tmpSeq1 = new Seq<>(), tmpSeq2 = new Seq<>(), tmpSeq3 = new Seq<>();
private static final ObjectSet<Sector> tmpSet = new ObjectSet<>();
public final SectorRect rect;
public final Plane plane;
public final Planet planet;
@ -27,8 +31,7 @@ public class Sector{
public @Nullable SaveSlot save;
public @Nullable SectorPreset preset;
/** Sector enemy hostility from 0 to 1 */
//public float hostility;
public float baseCoverage;
//TODO implement a dynamic launch period
public int launchPeriod = 10;
@ -42,6 +45,45 @@ public class Sector{
this.data = data;
}
public Seq<Sector> inRange(int range){
//TODO cleanup/remove
if(true){
tmpSeq1.clear();
neighbors(tmpSeq1::add);
return tmpSeq1;
}
tmpSeq1.clear();
tmpSeq2.clear();
tmpSet.clear();
tmpSeq1.add(this);
tmpSet.add(this);
for(int i = 0; i < range; i++){
while(!tmpSeq1.isEmpty()){
Sector sec = tmpSeq1.pop();
tmpSet.add(sec);
sec.neighbors(other -> {
if(tmpSet.add(other)){
tmpSeq2.add(other);
}
});
}
tmpSeq1.clear();
tmpSeq1.addAll(tmpSeq2);
}
tmpSeq3.clear().addAll(tmpSeq2);
return tmpSeq3;
}
public void neighbors(Cons<Sector> cons){
for(Ptile tile : tile.tiles){
cons.get(planet.getSector(tile));
}
}
/** @return whether this sector can be landed on at all.
* Only sectors adjacent to non-wave sectors can be landed on.
* TODO also preset sectors*/
@ -77,11 +119,6 @@ public class Sector{
return save != null;
}
public void generate(){
//TODO use simplex and a seed
//hostility = Math.max(Noise.snoise3(tile.v.x, tile.v.y, tile.v.z, 0.5f, 0.4f), 0);
}
public boolean locked(){
return !unlocked();
}

View File

@ -122,7 +122,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
//draw all sector stuff
for(Sector sec : planet.sectors){
if(selectAlpha > 0.01f){
if(canLaunch(sec) || sec.unlocked()){
if(/*canLaunch(sec) || sec.unlocked()*/true){
if(sec.baseCoverage > 0){
planets.fill(sec, Tmp.c1.set(Team.crux.color).a(0.1f * sec.baseCoverage * selectAlpha), -0.002f);
}
Color color =
sec.hasBase() ? Team.sharded.color :
sec.preset != null ? Team.derelict.color :
@ -220,6 +224,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
@Override
public void draw(){
planets.render(PlanetDialog.this);
Core.scene.setScrollFocus(PlanetDialog.this);
}
},
new Table(t -> {
@ -277,7 +282,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
selectAlpha = Mathf.lerpDelta(selectAlpha, Mathf.num(planets.zoom < 1.9f), 0.1f);
}
//TODO add strings to bundle after prototyping is done
//TODO localize
private void updateSelected(){
Sector sector = selected;
@ -294,6 +299,13 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
stable.image().color(Pal.accent).fillX().height(3f).pad(3f).row();
stable.add(sector.save != null ? sector.save.getPlayTime() : "[lightgray]Unexplored").row();
if(sector.hasBase() && sector.hasWaves()){
stable.add("[scarlet]Under attack!");
stable.row();
stable.add("[accent]" + Mathf.ceil(sectorDestructionTurns - (sector.getSecondsPassed() * 60) / turnDuration) + " turn(s) until destruction");
stable.row();
}
stable.add("Resources:").row();
stable.table(t -> {
t.left();

View File

@ -7,6 +7,7 @@ import arc.math.*;
import arc.scene.*;
import arc.scene.actions.*;
import arc.scene.event.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.ImageButton.*;
import arc.scene.ui.layout.*;
@ -28,13 +29,14 @@ import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class HudFragment extends Fragment{
private static final float dsize = 47.2f;
public final PlacementFragment blockfrag = new PlacementFragment();
private ImageButton flip;
private Table lastUnlockTable;
private Table lastUnlockLayout;
private boolean shown = true;
private float dsize = 47.2f;
private CoreItemsDisplay coreItems = new CoreItemsDisplay();
private String hudText = "";
@ -51,6 +53,11 @@ public class HudFragment extends Fragment{
showToast("Sector[accent] captured[]!");
});
//TODO localize
Events.on(SectorLoseEvent.class, e -> {
showToast(Icon.warning, "Sector " + e.sector.id + " [scarlet]lost!");
});
//TODO full implementation
Events.on(ResetEvent.class, e -> {
coreItems.resetUsed();
@ -393,6 +400,10 @@ public class HudFragment extends Fragment{
}
public void showToast(String text){
showToast(Icon.ok, text);
}
public void showToast(Drawable icon, String text){
if(state.isMenu()) return;
scheduleToast(() -> {
@ -405,7 +416,7 @@ public class HudFragment extends Fragment{
}
});
table.margin(12);
table.image(Icon.ok).pad(3);
table.image(icon).pad(3);
table.add(text).wrap().width(280f).get().setAlignment(Align.center, Align.center);
table.pack();