From d9e05907afec896cbe48bb6548faafbe412bfc39 Mon Sep 17 00:00:00 2001 From: Anuken Date: Fri, 5 Jun 2020 10:45:57 -0400 Subject: [PATCH] Basic schematic based generation --- .gitignore | 1 + build.gradle | 1 + core/assets/baseparts/core1.msch | Bin 0 -> 273 bytes core/assets/baseparts/turret1.msch | Bin 0 -> 102 bytes core/src/mindustry/Vars.java | 2 + core/src/mindustry/ai/BaseRegistry.java | 100 +++++++++++ core/src/mindustry/content/Blocks.java | 10 +- core/src/mindustry/content/Bullets.java | 4 +- core/src/mindustry/game/Schematics.java | 13 +- core/src/mindustry/io/SaveFileReader.java | 8 +- core/src/mindustry/io/SaveVersion.java | 2 +- .../io/legacy/LegacySaveVersion.java | 2 +- .../maps/generators/BaseGenerator.java | 169 ++++++++++++------ core/src/mindustry/world/Block.java | 17 ++ .../blocks/defense/OverdriveProjector.java | 12 +- .../src/mindustry/server/ServerLauncher.java | 2 + 16 files changed, 272 insertions(+), 71 deletions(-) create mode 100644 core/assets/baseparts/core1.msch create mode 100644 core/assets/baseparts/turret1.msch create mode 100644 core/src/mindustry/ai/BaseRegistry.java diff --git a/.gitignore b/.gitignore index 868e2a4281..1a50ef27f2 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ packr-out/ config/ *.gif +/core/assets/basepartnames version.properties .attach_* diff --git a/build.gradle b/build.gradle index 7fee921c61..2e66aff6e0 100644 --- a/build.gradle +++ b/build.gradle @@ -106,6 +106,7 @@ allprojects{ output += other.name.substring("bundle".length() + 1, other.name.lastIndexOf('.')) + "\n" } new File(project(':core').projectDir, 'assets/locales').text = output + new File(project(':core').projectDir, 'assets/basepartnames').text = new File(project(':core').projectDir, 'assets/baseparts/').list().join("\n") } writeVersion = { diff --git a/core/assets/baseparts/core1.msch b/core/assets/baseparts/core1.msch new file mode 100644 index 0000000000000000000000000000000000000000..43439698d7b307b263d6e4122ada2ee7f39bfe10 GIT binary patch literal 273 zcmc~TPR?Mgn4=runRmp1XK}dY%Q^ z#s5r}tgqU}-#90_*5;PO&0in0_SY)REv+w&*;coIdtvSMT6ej>nQNYJ{9bkW_O7oN z*F7(nHD{RrOPR?}@vtDvIc<@O{LJ&=)^mbt0t9}nSb5@vvai#;i4$ILm0^5h)ii%W z;U(oLR_DD26Gg?nGq(Dj3U6}v;5s;ObI@g<4o;W9r@rhL5aQ2@GJhq-!L(4prAkGA z_L9xBXIQvv&#^aFSTkdH!_G^BD@6YHo+{CMb|=#5hOdT`f#5=qgmk~q9qDeIX?OOB isNUQrwr;jvN#~md+gn-Rq}ztQYhUpD2Q&8rIWGX?UV*Uy literal 0 HcmV?d00001 diff --git a/core/assets/baseparts/turret1.msch b/core/assets/baseparts/turret1.msch new file mode 100644 index 0000000000000000000000000000000000000000..40c49f142c38bbc4f2407aa575cd0026cfe94676 GIT binary patch literal 102 zcmc~TPR?Mgn4{Yn&DEeF!rI?&@M3xImy45J^i_T|%w~IQxB1+G#DoP4SxdHcCS)Zq z+m)?xpDQi=lWTe6n$A0k#RX=0^PGDkoR)5xyL6*616y*?7vX?Ur5;yfed>R0`t#`l J cores = new Array<>(); + public Array parts = new Array<>(); + public ObjectMap> itemParts = new ObjectMap<>(); + + public Array forItem(Item item){ + return itemParts.get(item, Array::new); + } + + public void load(){ + cores.clear(); + parts.clear(); + itemParts.clear(); + + String[] names = Core.files.internal("basepartnames").readString().split("\n"); + + for(String name : names){ + try{ + Schematic schem = Schematics.read(Core.files.internal("baseparts/" + name)); + + BasePart part = new BasePart(schem); + + for(Stile tile : schem.tiles){ + //make note of occupied positions + tile.block.iterateTaken(tile.x, tile.y, part.occupied::set); + + //keep track of core type + if(tile.block instanceof CoreBlock){ + part.core = tile.block; + } + + //save the required resource based on item source - multiple sources are not allowed + if(tile.block instanceof ItemSource){ + Item config = (Item)tile.config; + if(config != null) part.requiredItem = config; + } + + //same for liquids - this is not used yet + if(tile.block instanceof LiquidSource){ + Liquid config = (Liquid)tile.config; + if(config != null) part.requiredLiquid = config; + } + } + schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly); + + part.tier = schem.tiles.sumf(s -> s.block.buildCost / s.block.buildCostMultiplier); + + (part.core != null ? cores : parts).add(part); + + if(part.requiredItem != null){ + itemParts.get(part.requiredItem, Array::new).add(part); + } + }catch(IOException e){ + throw new RuntimeException(e); + } + } + + cores.sort(Structs.comps(Structs.comparingFloat(b -> b.core.health), Structs.comparingFloat(b -> b.tier))); + parts.sort(); + itemParts.each((key, arr) -> arr.sort()); + } + + public static class BasePart implements Comparable{ + public final Schematic schematic; + public final GridBits occupied; + + public @Nullable Liquid requiredLiquid; + public @Nullable Item requiredItem; + public @Nullable Block core; + + //total build cost + public float tier; + + public BasePart(Schematic schematic){ + this.schematic = schematic; + this.occupied = new GridBits(schematic.width, schematic.height); + } + + @Override + public int compareTo(BasePart other){ + return Float.compare(tier, other.tier); + } + } +} diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 45bc23aad9..c895cabf8a 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -689,7 +689,7 @@ public class Blocks implements ContentList{ consumes.power(4f); consumes.item(Items.scrap); - consumes.liquid(Liquids.slag, 0.1f); + consumes.liquid(Liquids.slag, 0.12f); }}; sporePress = new GenericCrafter("spore-press"){{ @@ -917,11 +917,11 @@ public class Blocks implements ContentList{ requirements(Category.effect, ItemStack.with(Items.lead, 200, Items.titanium, 130, Items.silicon, 130, Items.plastanium, 80, Items.surgealloy, 120)); consumes.power(10f); size = 3; - range = 180f; - speedBoost = 1.5f; - speedBoostPhase = 1f; + range = 200f; + speedBoost = 2.5f; useTime = 250f; - consumes.item(Items.phasefabric).boost(); + hasBoost = false; + consumes.item(Items.phasefabric); }}; forceProjector = new ForceProjector("force-projector"){{ diff --git a/core/src/mindustry/content/Bullets.java b/core/src/mindustry/content/Bullets.java index 819c459347..391237aa71 100644 --- a/core/src/mindustry/content/Bullets.java +++ b/core/src/mindustry/content/Bullets.java @@ -191,10 +191,10 @@ public class Bullets implements ContentList{ }}; flakSurge = new FlakBulletType(4.5f, 13){{ - splashDamage = 40f; + splashDamage = 45f; splashDamageRadius = 40f; lightning = 2; - lightningLength = 12; + lightningLength = 7; shootEffect = Fx.shootBig; }}; diff --git a/core/src/mindustry/game/Schematics.java b/core/src/mindustry/game/Schematics.java index 0ecb7ab20e..2b92574490 100644 --- a/core/src/mindustry/game/Schematics.java +++ b/core/src/mindustry/game/Schematics.java @@ -106,6 +106,9 @@ public class Schematics implements Loadable{ if(shadowBuffer == null){ Core.app.post(() -> shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 8, maxSchematicSize + padding + 8)); } + + //load base schematics + bases.load(); } public void overwrite(Schematic target, Schematic newSchematic){ @@ -372,14 +375,18 @@ public class Schematics implements Loadable{ } public static void placeLoadout(Schematic schem, int x, int y){ + placeLoadout(schem, x, y, state.rules.defaultTeam, Blocks.oreCopper); + } + + public static void placeLoadout(Schematic schem, int x, int y, Team team, Block resource){ Stile coreTile = schem.tiles.find(s -> s.block instanceof CoreBlock); - if(coreTile == null) throw new IllegalArgumentException("Schematic has no core tile. Exiting."); + if(coreTile == null) throw new IllegalArgumentException("Loadout schematic has no core tile!"); int ox = x - coreTile.x, oy = y - coreTile.y; schem.tiles.each(st -> { Tile tile = world.tile(st.x + ox, st.y + oy); if(tile == null) return; - tile.setBlock(st.block, state.rules.defaultTeam, 0); + tile.setBlock(st.block, team, 0); tile.rotation(st.rotation); Object config = st.config; @@ -388,7 +395,7 @@ public class Schematics implements Loadable{ } if(st.block instanceof Drill){ - tile.getLinkedTiles(t -> t.setOverlay(Blocks.oreCopper)); + tile.getLinkedTiles(t -> t.setOverlay(resource)); } }); } diff --git a/core/src/mindustry/io/SaveFileReader.java b/core/src/mindustry/io/SaveFileReader.java index 16938dc869..857551860f 100644 --- a/core/src/mindustry/io/SaveFileReader.java +++ b/core/src/mindustry/io/SaveFileReader.java @@ -94,12 +94,12 @@ public abstract class SaveFileReader{ return length; } - public void skipRegion(DataInput input) throws IOException{ - skipRegion(input, false); + public void skipChunk(DataInput input) throws IOException{ + skipChunk(input, false); } - /** Skip a region completely. */ - public void skipRegion(DataInput input, boolean isByte) throws IOException{ + /** Skip a chunk completely, discarding the bytes. */ + public void skipChunk(DataInput input, boolean isByte) throws IOException{ int length = readChunk(input, isByte, t -> {}); int skipped = input.skipBytes(length); if(length != skipped){ diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java index 4f81cd903c..8637d6ef86 100644 --- a/core/src/mindustry/io/SaveVersion.java +++ b/core/src/mindustry/io/SaveVersion.java @@ -245,7 +245,7 @@ public abstract class SaveVersion extends SaveFileReader{ } }else{ //skip the entity region, as the entity and its IO code are now gone - skipRegion(stream, true); + skipChunk(stream, true); } } }else{ diff --git a/core/src/mindustry/io/legacy/LegacySaveVersion.java b/core/src/mindustry/io/legacy/LegacySaveVersion.java index 47f7e26955..4e543c7be6 100644 --- a/core/src/mindustry/io/legacy/LegacySaveVersion.java +++ b/core/src/mindustry/io/legacy/LegacySaveVersion.java @@ -110,7 +110,7 @@ public abstract class LegacySaveVersion extends SaveVersion{ int amount = stream.readInt(); for(int j = 0; j < amount; j++){ //simply skip all the entities - skipRegion(stream, true); + skipChunk(stream, true); } } } diff --git a/core/src/mindustry/maps/generators/BaseGenerator.java b/core/src/mindustry/maps/generators/BaseGenerator.java index da56c3117d..5607036a1b 100644 --- a/core/src/mindustry/maps/generators/BaseGenerator.java +++ b/core/src/mindustry/maps/generators/BaseGenerator.java @@ -1,78 +1,143 @@ package mindustry.maps.generators; -import arc.*; import arc.math.*; +import arc.math.geom.*; import arc.struct.*; -import arc.util.*; -import mindustry.*; +import mindustry.ai.BaseRegistry.*; import mindustry.content.*; import mindustry.game.*; +import mindustry.game.Schematic.*; +import mindustry.gen.*; import mindustry.type.*; import mindustry.world.*; -import mindustry.world.blocks.defense.turrets.*; -import mindustry.world.blocks.defense.turrets.ItemTurret.*; +import mindustry.world.blocks.defense.*; import mindustry.world.blocks.environment.*; +import mindustry.world.blocks.production.*; -import java.io.*; +import static mindustry.Vars.*; public class BaseGenerator{ - static Array schematics = new Array<>(); + private Tiles tiles; + private Team team; + private ObjectMap ores = new ObjectMap<>(); public void generate(Tiles tiles, Array cores, Tile spawn, Team team, Sector sector){ + this.tiles = tiles; + this.team = team; + + for(Block block : content.blocks()){ + if(block instanceof OreBlock && block.asFloor().itemDrop != null){ + ores.put(block.asFloor().itemDrop, (OreBlock)block); + } + } + + Array wallsSmall = content.blocks().select(b -> b instanceof Wall && b.size == 1); + Array wallsLarge = content.blocks().select(b -> b instanceof Wall && b.size == 2); + + float bracket = 0.1f; + int range = 200; + int wallAngle = 180; + BasePart coreschem = bases.cores.getFrac(bracket); + + Block wall = wallsSmall.getFrac(bracket), wallLarge = wallsLarge.getFrac(bracket); + + //TODO random flipping and rotation + for(Tile tile : cores){ tile.clearOverlay(); - tile.setBlock(Blocks.coreShard, team); - } + Schematics.placeLoadout(coreschem.schematic, tile.x, tile.y, team, coreschem.requiredItem == null ? Blocks.oreCopper : ores.get(coreschem.requiredItem)); - //TODO remove this, it's just a test - if(!Core.files.external("SCHEMATICOUTPUT").exists()){ - Log.err("no schematics"); - return; - } - - if(schematics.isEmpty()){ - schematics.addAll(Array.with(Core.files.external("SCHEMATICOUTPUT").list()).map(s -> { - try{ - return Schematics.read(s); - }catch(IOException e){ - throw new RuntimeException(); - } - })); - } - - Vars.state.rules.enemyCheat = true; - - int range = 180; - int attempts = 3000; - int cx = cores.first().x, cy = cores.first().y; - - outer: - for(int i = 0; i < attempts; i++){ - Tmp.v1.rnd(Mathf.random(range)); - int x = (int)(cx + Tmp.v1.x), y = (int)(cy + Tmp.v1.y); - - Schematic res = schematics.random(); - int ex = x - res.width/2, ey = y - res.height/2; - - for(int rx = ex; rx <= ex + res.width; rx++){ - for(int ry = ey; ry <= ey + res.height; ry++){ - Tile tile = tiles.get(rx, ry); - if(tile == null || Vars.world.getDarkness(rx, ry) > 0 || tile.floor().isDeep() || (!tile.block().isAir() && !(tile.block() instanceof Rock && tile.block().destructible))) continue outer; - } + //fill core with every type of item (even non-material) + Tilec entity = tile.entity; + for(Item item : content.items()){ + entity.items().add(item, entity.block().itemCapacity); } + } - Schematics.place(res, x, y, team); - - //add ammo - for(int rx = ex; rx <= ex + res.width; rx++){ - for(int ry = ey; ry <= ey + res.height; ry++){ - Tile tile = tiles.get(rx, ry); - if(tile != null && tile.entity instanceof ItemTurretEntity){ - tile.entity.handleItem(tile.entity, ((ItemTurret)tile.block()).ammoTypes.keys().toArray().first()); + //first pass: random schematics + for(Tile core : cores){ + core.circle(range, (x, y) -> { + Tile tile = tiles.getn(x, y); + if(tile.overlay().itemDrop != null){ + Array parts = bases.forItem(tile.overlay().itemDrop); + if(!parts.isEmpty()){ + tryPlace(parts.random(), x, y); } } + }); + } + + //second pass: small walls + for(Tile core : cores){ + core.circle(range, (x, y) -> { + Tile tile = tiles.getn(x, y); + if(tile.block().alwaysReplace){ + boolean any = false; + + for(Point2 p : Geometry.d8){ + if(Angles.angleDist(Angles.angle(p.x, p.y), spawn.angleTo(core)) > wallAngle){ + continue; + } + + Tile o = tiles.get(tile.x + p.x, tile.y + p.y); + if(o != null && o.team() == team && !(o.block() instanceof Wall)){ + any = true; + break; + } + } + + if(any){ + tile.setBlock(wall, team); + } + } + }); + } + + //third pass: large walls + for(Tile core : cores){ + core.circle(range, (x, y) -> { + int walls = 0; + for(int cx = 0; cx < 2; cx++){ + for(int cy = 0; cy < 2; cy++){ + Tile tile = tiles.get(x + cx, y + cy); + if(tile == null || tile.block().size != 1 || (tile.block() != wall && !tile.block().alwaysReplace)) return; + + if(tile.block() == wall){ + walls ++; + } + } + } + + if(walls >= 3){ + tiles.getn(x, y).setBlock(wallLarge, team); + } + }); + } + } + + boolean tryPlace(BasePart part, int x, int y){ + int cx = x - part.schematic.width/2, cy = y - part.schematic.height/2; + for(int rx = cx; rx <= cx + part.schematic.width; rx++){ + for(int ry = cy; ry <= cy + part.schematic.height; ry++){ + Tile tile = tiles.get(rx, ry); + if(tile == null || ((!tile.block().alwaysReplace || world.getDarkness(rx, ry) > 0) && part.occupied.get(rx - cx, ry - cy))){ + return false; + } } } + if(part.requiredItem != null){ + for(Stile tile : part.schematic.tiles){ + if(tile.block instanceof Drill){ + tile.block.iterateTaken(tile.x + cx, tile.y + cy, (ex, ey) -> { + tiles.getn(ex, ey).setOverlay(ores.get(part.requiredItem)); + }); + } + } + } + + Schematics.place(part.schematic, x, y, team); + + return true; } } diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index d328690b89..1f4eadd391 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -405,6 +405,23 @@ public class Block extends UnlockableContent{ return (hasItems && itemCapacity > 0); } + /** Iterate through ever grid position taken up by this block. */ + public void iterateTaken(int x, int y, Intc2 placer){ + if(isMultiblock()){ + int offsetx = -(size - 1) / 2; + int offsety = -(size - 1) / 2; + + for(int dx = 0; dx < size; dx++){ + for(int dy = 0; dy < size; dy++){ + placer.get(dx + offsetx + x, dy + offsety + y); + } + } + + }else{ + placer.get(x, y); + } + } + /** Never use outside of the editor! */ public TextureRegion editorIcon(){ if(editorIcon == null) editorIcon = Core.atlas.find(name + "-icon-editor"); diff --git a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java index f2fee4133f..a6f174eaf3 100644 --- a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java @@ -23,6 +23,7 @@ public class OverdriveProjector extends Block{ public float speedBoostPhase = 0.75f; public float useTime = 400f; public float phaseRangeBoost = 20f; + public boolean hasBoost = true; public Color baseColor = Color.valueOf("feb380"); public Color phaseColor = Color.valueOf("ffd59e"); @@ -51,9 +52,12 @@ public class OverdriveProjector extends Block{ stats.add(BlockStat.speedIncrease, (int)(100f * speedBoost), StatUnit.percent); stats.add(BlockStat.range, range / tilesize, StatUnit.blocks); + stats.add(BlockStat.productionTime, useTime / 60f, StatUnit.seconds); - stats.add(BlockStat.boostEffect, phaseRangeBoost / tilesize, StatUnit.blocks); - stats.add(BlockStat.boostEffect, (int)((speedBoost + speedBoostPhase) * 100f), StatUnit.percent); + if(hasBoost){ + stats.add(BlockStat.boostEffect, phaseRangeBoost / tilesize, StatUnit.blocks); + stats.add(BlockStat.boostEffect, (int)((speedBoost + speedBoostPhase) * 100f), StatUnit.percent); + } } public class OverdriveEntity extends TileEntity{ @@ -71,7 +75,9 @@ public class OverdriveProjector extends Block{ heat = Mathf.lerpDelta(heat, consValid() ? 1f : 0f, 0.08f); charge += heat * Time.delta(); - phaseHeat = Mathf.lerpDelta(phaseHeat, Mathf.num(cons().optionalValid()), 0.1f); + if(hasBoost){ + phaseHeat = Mathf.lerpDelta(phaseHeat, Mathf.num(cons().optionalValid()), 0.1f); + } if(timer(timerUse, useTime) && efficiency() > 0){ consume(); diff --git a/server/src/mindustry/server/ServerLauncher.java b/server/src/mindustry/server/ServerLauncher.java index 760d480fc4..e7146b4db8 100644 --- a/server/src/mindustry/server/ServerLauncher.java +++ b/server/src/mindustry/server/ServerLauncher.java @@ -74,6 +74,8 @@ public class ServerLauncher implements ApplicationListener{ System.exit(1); } + bases.load(); + Core.app.addListener(new ApplicationListener(){public void update(){ asyncCore.begin(); }}); Core.app.addListener(logic = new Logic()); Core.app.addListener(netServer = new NetServer());