diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index c1c89aa4e7..3561a3f20c 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -492,6 +492,7 @@ filter.option.block = Block filter.option.floor = Floor filter.option.flooronto = Target Floor filter.option.target = Target +filter.option.replacement = Replacement filter.option.wall = Wall filter.option.ore = Ore filter.option.floor2 = Secondary Floor @@ -994,6 +995,7 @@ rules.waves = Waves rules.attack = Attack Mode rules.buildai = AI Building rules.corecapture = Capture Core On Destruction +rules.polygoncoreprotection = Polygonal Core Protection rules.enemyCheat = Infinite AI (Red Team) Resources rules.blockhealthmultiplier = Block Health Multiplier rules.blockdamagemultiplier = Block Damage Multiplier diff --git a/core/assets/maps/glacier.msav b/core/assets/maps/glacier.msav index 73796f7643..efe240108d 100644 Binary files a/core/assets/maps/glacier.msav and b/core/assets/maps/glacier.msav differ diff --git a/core/assets/maps/passage.msav b/core/assets/maps/passage.msav new file mode 100644 index 0000000000..b6816fba7b Binary files /dev/null and b/core/assets/maps/passage.msav differ diff --git a/core/assets/maps/veins.msav b/core/assets/maps/veins.msav index 3273feb74b..037d3f0b75 100644 Binary files a/core/assets/maps/veins.msav and b/core/assets/maps/veins.msav differ diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java index 56246bc02f..d5b9ffcf7b 100644 --- a/core/src/mindustry/content/Fx.java +++ b/core/src/mindustry/content/Fx.java @@ -1113,6 +1113,47 @@ public class Fx{ }); }), + impactReactorExplosion = new Effect(30, 500f, b -> { + float intensity = 8f; + float baseLifetime = 25f + intensity * 15f; + b.lifetime = 50f + intensity * 64f; + + color(Pal.lighterOrange); + alpha(0.8f); + for(int i = 0; i < 5; i++){ + rand.setSeed(b.id*2 + i); + float lenScl = rand.random(0.25f, 1f); + int fi = i; + b.scaled(b.lifetime * lenScl, e -> { + randLenVectors(e.id + fi - 1, e.fin(Interp.pow10Out), (int)(2.8f * intensity), 25f * intensity, (x, y, in, out) -> { + float fout = e.fout(Interp.pow5Out) * rand.random(0.5f, 1f); + float rad = fout * ((2f + intensity) * 2.35f); + + Fill.circle(e.x + x, e.y + y, rad); + Drawf.light(e.x + x, e.y + y, rad * 2.6f, Pal.lighterOrange, 0.7f); + }); + }); + } + + b.scaled(baseLifetime, e -> { + Draw.color(); + e.scaled(5 + intensity * 2f, i -> { + stroke((3.1f + intensity/5f) * i.fout()); + Lines.circle(e.x, e.y, (3f + i.fin() * 14f) * intensity); + Drawf.light(e.x, e.y, i.fin() * 14f * 2f * intensity, Color.white, 0.9f * e.fout()); + }); + + color(Color.white, Pal.lighterOrange, e.fin()); + stroke((2f * e.fout())); + + Draw.z(Layer.effect + 0.001f); + randLenVectors(e.id + 1, e.finpow() + 0.001f, (int)(8 * intensity), 30f * intensity, (x, y, in, out) -> { + lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + out * 4 * (4f + intensity)); + Drawf.light(e.x + x, e.y + y, (out * 4 * (3f + intensity)) * 3.5f, Draw.getColor(), 0.8f); + }); + }); + }), + blockExplosion = new Effect(30, e -> { e.scaled(7, i -> { stroke(3.1f * i.fout()); diff --git a/core/src/mindustry/editor/MapInfoDialog.java b/core/src/mindustry/editor/MapInfoDialog.java index da4bf5a4b3..288f7224c3 100644 --- a/core/src/mindustry/editor/MapInfoDialog.java +++ b/core/src/mindustry/editor/MapInfoDialog.java @@ -7,6 +7,7 @@ import arc.util.*; import mindustry.*; import mindustry.game.*; import mindustry.io.*; +import mindustry.maps.filters.*; import mindustry.ui.*; import mindustry.ui.dialogs.*; @@ -74,7 +75,11 @@ public class MapInfoDialog extends BaseDialog{ t.row(); t.add("@editor.generation").padRight(8).left(); t.button("@edit", () -> { - generate.show(maps.readFilters(editor.tags.get("genfilters", "")), + //randomize so they're not all the same seed + var res = maps.readFilters(editor.tags.get("genfilters", "")); + res.each(GenerateFilter::randomize); + + generate.show(res, filters -> { //reset seed to 0 so it is not written filters.each(f -> f.seed = 0); diff --git a/core/src/mindustry/entities/Damage.java b/core/src/mindustry/entities/Damage.java index 176761c349..7c69b06cb2 100644 --- a/core/src/mindustry/entities/Damage.java +++ b/core/src/mindustry/entities/Damage.java @@ -34,13 +34,18 @@ public class Damage{ dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, Fx.dynamicExplosion); } + /** Creates a dynamic explosion based on specified parameters. */ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, Effect explosionFx){ + dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null, explosionFx); + } + /** Creates a dynamic explosion based on specified parameters. */ public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam){ dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, fire, ignoreTeam, Fx.dynamicExplosion); } /** Creates a dynamic explosion based on specified parameters. */ - public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosion){ + public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam, Effect explosionFx){ if(damage){ for(int i = 0; i < Mathf.clamp(power / 700, 0, 8); i++){ int length = 5 + Mathf.clamp((int)(power / 500), 1, 20); @@ -74,7 +79,7 @@ public class Damage{ float shake = Math.min(explosiveness / 4f + 3f, 9f); Effect.shake(shake, shake, x, y); - explosion.at(x, y, radius / 8f); + explosionFx.at(x, y, radius / 8f); } public static void createIncend(float x, float y, float range, int amount){ diff --git a/core/src/mindustry/entities/comp/BuildingComp.java b/core/src/mindustry/entities/comp/BuildingComp.java index 6c6bf1250d..83c20b40ab 100644 --- a/core/src/mindustry/entities/comp/BuildingComp.java +++ b/core/src/mindustry/entities/comp/BuildingComp.java @@ -1059,7 +1059,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, }); } - Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions); + Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions, block.destroyEffect); if(!floor().solid && !floor().isLiquid){ Effect.rubble(x, y, block.size); diff --git a/core/src/mindustry/game/EventType.java b/core/src/mindustry/game/EventType.java index 015eac0c97..9d590de52c 100644 --- a/core/src/mindustry/game/EventType.java +++ b/core/src/mindustry/game/EventType.java @@ -9,6 +9,7 @@ import mindustry.net.*; import mindustry.net.Packets.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.storage.CoreBlock.*; public class EventType{ @@ -283,6 +284,15 @@ public class EventType{ } } + /** Called when a core block is placed/removed or its team is changed. */ + public static class CoreChangeEvent{ + public CoreBuild core; + + public CoreChangeEvent(CoreBuild core){ + this.core = core; + } + } + public static class StateChangeEvent{ public final State from, to; diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index 093a09f722..b0dd8d3b4f 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -68,6 +68,8 @@ public class Rules{ public float deconstructRefundMultiplier = 0.5f; /** No-build zone around enemy core radius. */ public float enemyCoreBuildRadius = 400f; + /** If true, no-build zones are calculated based on the closest core. */ + public boolean polygonCoreProtection = false; /** Radius around enemy wave drop zones.*/ public float dropZoneRadius = 300f; /** Time between waves in ticks. */ diff --git a/core/src/mindustry/graphics/OverlayRenderer.java b/core/src/mindustry/graphics/OverlayRenderer.java index 5279de9e4d..a75555b60b 100644 --- a/core/src/mindustry/graphics/OverlayRenderer.java +++ b/core/src/mindustry/graphics/OverlayRenderer.java @@ -5,14 +5,18 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.math.geom.*; +import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.ai.types.*; import mindustry.entities.*; +import mindustry.game.EventType.*; +import mindustry.game.*; +import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.input.*; -import mindustry.ui.*; import mindustry.world.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; @@ -23,6 +27,42 @@ public class OverlayRenderer{ private float buildFade, unitFade; private Sized lastSelect; + private Seq cedges = new Seq<>(); + private boolean updatedCores; + + public OverlayRenderer(){ + Events.on(WorldLoadEvent.class, e -> { + updatedCores = true; + }); + + Events.on(CoreChangeEvent.class, e -> { + updatedCores = true; + }); + } + + private void updateCoreEdges(){ + if(!updatedCores){ + return; + } + + updatedCores = false; + cedges.clear(); + + Seq pos = new Seq<>(); + Seq teams = new Seq<>(); + for(TeamData team : state.teams.active){ + for(CoreBuild b : team.cores){ + teams.add(b); + pos.add(new Vec2(b.x, b.y)); + } + } + + //if this is laggy, it could be shoved in another thread. + var result = Voronoi.generate(pos.toArray(Vec2.class), 0, world.unitWidth(), 0, world.unitHeight()); + for(var edge : result){ + cedges.add(new CoreEdge(edge.x1, edge.y1, edge.x2, edge.y2, teams.get(edge.site1).team, teams.get(edge.site2).team)); + } + } public void drawBottom(){ InputHandler input = control.input; @@ -117,15 +157,32 @@ public class OverlayRenderer{ Lines.stroke(buildFade * 2f); if(buildFade > 0.005f){ - state.teams.eachEnemyCore(player.team(), core -> { - float dst = core.dst(player); - if(dst < state.rules.enemyCoreBuildRadius * 2.2f){ - Draw.color(Color.darkGray); - Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius); - Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time, 10f, 0.5f)); - Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius); + if(state.rules.polygonCoreProtection){ + updateCoreEdges(); + Draw.color(Pal.accent); + + for(int i = 0; i < 2; i++){ + float offset = (i == 0 ? -2f : 0f); + for(CoreEdge edge : cedges){ + Team displayed = edge.displayed(); + if(displayed != null){ + Draw.color(i == 0 ? Color.darkGray : Tmp.c1.set(displayed.color).lerp(Pal.accent, Mathf.absin(Time.time, 10f, 0.2f))); + Lines.line(edge.x1, edge.y1 + offset, edge.x2, edge.y2 + offset); + } + } } - }); + + Draw.color(); + }else{ + state.teams.eachEnemyCore(player.team(), core -> { + if(Core.camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(core.x, core.y, state.rules.enemyCoreBuildRadius * 2f))){ + Draw.color(Color.darkGray); + Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius); + Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time, 10f, 0.5f)); + Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius); + } + }); + } } Lines.stroke(2f); @@ -192,4 +249,28 @@ public class OverlayRenderer{ } } } + + private static class CoreEdge{ + float x1, y1, x2, y2; + Team t1, t2; + + public CoreEdge(float x1, float y1, float x2, float y2, Team t1, Team t2){ + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + this.t1 = t1; + this.t2 = t2; + } + + @Nullable + Team displayed(){ + return + t1 == t2 ? null : + t1 == player.team() ? t2 : + t2 == player.team() ? t1 : + t2.id == 0 ? t1 : + t1.id < t2.id && t1.id != 0 ? t1 : t2; + } + } } diff --git a/core/src/mindustry/graphics/Voronoi.java b/core/src/mindustry/graphics/Voronoi.java new file mode 100755 index 0000000000..7b10e497e2 --- /dev/null +++ b/core/src/mindustry/graphics/Voronoi.java @@ -0,0 +1,643 @@ +package mindustry.graphics; + +import arc.math.geom.*; +import arc.struct.*; + +import java.util.*; + +//TODO in dire need of cleanup +public class Voronoi{ + private final static int LE = 0; + private final static int RE = 1; + + //TODO make local + int siteidx; + Site[] sites; + int nsites; + float borderMinX, borderMaxX, borderMinY, borderMaxY; + float ymin; + float deltay; + int nvertices = 0; + int nedges; + Site bottomsite; + int PQcount; + int PQmin; + int PQhashsize; + Halfedge[] PQhash; + int ELhashsize; + Halfedge[] ELhash; + Seq allEdges; + float minDistanceBetweenSites = 1f; + + public static Seq generate(Vec2[] values, float minX, float maxX, float minY, float maxY){ + return new Voronoi().generateVoronoi(values, minX, maxX, minY, maxY); + } + + Seq generateVoronoi(Vec2[] values, float minX, float maxX, float minY, float maxY){ + allEdges = new Seq<>(); + + nsites = values.length; + + float sn = (float)nsites + 4; + int rtsites = (int)Math.sqrt(sn); + + sites = new Site[nsites]; + Vec2 first = values[0]; + float xmin = first.x; + ymin = first.y; + float xmax = first.x; + float ymax = first.y; + for(int i = 0; i < nsites; i++){ + sites[i] = new Site(); + sites[i].coord.set(values[i]); + sites[i].sitenbr = i; + + if(values[i].x < xmin){ + xmin = values[i].x; + }else if(values[i].x > xmax){ + xmax = values[i].x; + } + + if(values[i].y < ymin){ + ymin = values[i].y; + }else if(values[i].y > ymax){ + ymax = values[i].y; + } + } + + Arrays.sort(sites, (p1, p2) -> { + Vec2 s1 = p1.coord, s2 = p2.coord; + if(s1.y < s2.y){ + return (-1); + } + if(s1.y > s2.y){ + return (1); + } + return Float.compare(s1.x, s2.x); + }); + + deltay = ymax - ymin; + float deltax = xmax - xmin; + + // Check bounding box inputs - if mins are bigger than maxes, swap them + float temp; + if(minX > maxX){ + temp = minX; + minX = maxX; + maxX = temp; + } + if(minY > maxY){ + temp = minY; + minY = maxY; + maxY = temp; + } + borderMinX = minX; + borderMinY = minY; + borderMaxX = maxX; + borderMaxY = maxY; + + siteidx = 0; + + PQcount = 0; + PQmin = 0; + PQhashsize = 4 * rtsites; + PQhash = new Halfedge[PQhashsize]; + + for(int i2 = 0; i2 < PQhashsize; i2 += 1){ + PQhash[i2] = new Halfedge(); + } + int i1; + ELhashsize = 2 * rtsites; + ELhash = new Halfedge[ELhashsize]; + + for(i1 = 0; i1 < ELhashsize; i1 += 1){ + ELhash[i1] = null; + } + Halfedge ELleftend = newHe(null, 0); + Halfedge ELrightend = newHe(null, 0); + ELleftend.ELleft = null; + ELleftend.ELright = ELrightend; + ELrightend.ELleft = ELleftend; + ELrightend.ELright = null; + ELhash[0] = ELleftend; + ELhash[ELhashsize - 1] = ELrightend; + + bottomsite = next(); + Site newsite = next(); + Halfedge lbnd; + Vec2 newintstar = null; + Edge e; + while(true){ + if(PQcount != 0){ + Vec2 answer = new Vec2(); + + while(PQhash[PQmin].PQnext == null){ + PQmin += 1; + } + answer.x = PQhash[PQmin].PQnext.vertex.coord.x; + answer.y = PQhash[PQmin].PQnext.ystar; + newintstar = (answer); + } + + Halfedge rbnd; + Halfedge bisector; + Site p; + Site bot; + + if(newsite != null && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ + int bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); + + if(bucket < 0){ + bucket = 0; + } + if(bucket >= ELhashsize){ + bucket = ELhashsize - 1; + } + + Halfedge he = getHash(bucket); + if(he == null){ + for(int i = 1; i < ELhashsize; i += 1){ + if((he = getHash(bucket - i)) != null){ + break; + } + if((he = getHash(bucket + i)) != null){ + break; + } + } + } + if(he == ELleftend || (he != ELrightend && right(he, (newsite.coord)))){ + do{ + he = he.ELright; + }while(he != ELrightend && right(he, (newsite.coord))); + he = he.ELleft; + }else{ + do{ + he = he.ELleft; + }while(he != ELleftend && !right(he, (newsite.coord))); + } + + if(bucket > 0 && bucket < ELhashsize - 1){ + ELhash[bucket] = he; + } + lbnd = he; + rbnd = lbnd.ELright; + + bot = rightreg(lbnd); + e = bisect(bot, newsite); + + bisector = newHe(e, LE); + insert(lbnd, bisector); + + if((p = intersect(lbnd, bisector)) != null){ + pqdelete(lbnd); + pqinsert(lbnd, p, p.coord.dst(newsite.coord)); + } + lbnd = bisector; + bisector = newHe(e, RE); + insert(lbnd, bisector); + + if((p = intersect(bisector, rbnd)) != null){ + pqinsert(bisector, p, p.coord.dst(newsite.coord)); + } + newsite = next(); + }else if(!(PQcount == 0)){ + Halfedge curr; + + curr = PQhash[PQmin].PQnext; + PQhash[PQmin].PQnext = curr.PQnext; + PQcount -= 1; + lbnd = (curr); + Halfedge llbnd = lbnd.ELleft; + rbnd = lbnd.ELright; + Halfedge rrbnd = (rbnd.ELright); + bot = leftReg(lbnd); + Site top = rightreg(rbnd); + + Site v = lbnd.vertex; + v.sitenbr = nvertices; + nvertices += 1; + endpoint(lbnd.ELedge, lbnd.ELpm, v); + endpoint(rbnd.ELedge, rbnd.ELpm, v); + delete(lbnd); + pqdelete(rbnd); + delete(rbnd); + int pm = LE; + + if(bot.coord.y > top.coord.y){ + Site temp1 = bot; + bot = top; + top = temp1; + pm = RE; + } + + e = bisect(bot, top); + bisector = newHe(e, pm); + insert(llbnd, bisector); + endpoint(e, RE - pm, v); + + if((p = intersect(llbnd, bisector)) != null){ + pqdelete(llbnd); + pqinsert(llbnd, p, p.coord.dst(bot.coord)); + } + + if((p = intersect(bisector, rrbnd)) != null){ + pqinsert(bisector, p, p.coord.dst(bot.coord)); + } + }else{ + break; + } + } + + for(lbnd = (ELleftend.ELright); lbnd != ELrightend; lbnd = (lbnd.ELright)){ + e = lbnd.ELedge; + clipLine(e); + } + + return allEdges; + } + + private Site next(){ + return siteidx < nsites ? sites[siteidx ++] : null; + } + + private Edge bisect(Site s1, Site s2){ + Edge newedge = new Edge(); + + // store the sites that this edge is bisecting + newedge.reg[0] = s1; + newedge.reg[1] = s2; + // to begin with, there are no endpoints on the bisector - it goes to + // infinity + newedge.ep[0] = null; + newedge.ep[1] = null; + + // get the difference in x dist between the sites + float dx = s2.coord.x - s1.coord.x; + float dy = s2.coord.y - s1.coord.y; + // make sure that the difference in positive + float adx = dx > 0 ? dx : -dx; + float ady = dy > 0 ? dy : -dy; + newedge.c = s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy * dy) * 0.5f;// get the slope of the line + + if(adx > ady){ + newedge.a = 1.0f; + newedge.b = dy / dx; + newedge.c /= dx;// set formula of line, with x fixed to 1 + }else{ + newedge.b = 1.0f; + newedge.a = dx / dy; + newedge.c /= dy;// set formula of line, with y fixed to 1 + } + + newedge.edgenbr = nedges; + + nedges += 1; + return newedge; + } + + private int pqbucket(Halfedge he){ + int bucket; + + bucket = (int)((he.ystar - ymin) / deltay * PQhashsize); + if(bucket < 0){ + bucket = 0; + } + if(bucket >= PQhashsize){ + bucket = PQhashsize - 1; + } + if(bucket < PQmin){ + PQmin = bucket; + } + return bucket; + } + + // push the HalfEdge into the ordered linked list of vertices + private void pqinsert(Halfedge he, Site v, float offset){ + Halfedge last, next; + + he.vertex = v; + he.ystar = v.coord.y + offset; + last = PQhash[pqbucket(he)]; + while((next = last.PQnext) != null + && (he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))){ + last = next; + } + he.PQnext = last.PQnext; + last.PQnext = he; + PQcount += 1; + } + + // remove the HalfEdge from the list of vertices + private void pqdelete(Halfedge he){ + Halfedge last; + + if(he.vertex != null){ + last = PQhash[pqbucket(he)]; + while(last.PQnext != he){ + last = last.PQnext; + } + + last.PQnext = he.PQnext; + PQcount -= 1; + he.vertex = null; + } + } + + private Halfedge newHe(Edge e, int pm){ + Halfedge answer = new Halfedge(); + answer.ELedge = e; + answer.ELpm = pm; + answer.PQnext = null; + answer.vertex = null; + return answer; + } + + private Site leftReg(Halfedge he){ + if(he.ELedge == null){ + return bottomsite; + } + return he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]; + } + + private void insert(Halfedge lb, Halfedge newHe){ + newHe.ELleft = lb; + newHe.ELright = lb.ELright; + lb.ELright.ELleft = newHe; + lb.ELright = newHe; + } + + /* + * This delete routine can't reclaim node, since pointers from hash table + * may be present. + */ + private void delete(Halfedge he){ + he.ELleft.ELright = he.ELright; + he.ELright.ELleft = he.ELleft; + he.deleted = true; + } + + /* Get entry from hash table, pruning any deleted nodes */ + private Halfedge getHash(int b){ + Halfedge he; + + if(b < 0 || b >= ELhashsize){ + return (null); + } + he = ELhash[b]; + if(he == null || !he.deleted){ + return (he); + } + + /* Hash table points to deleted half edge. Patch as necessary. */ + ELhash[b] = null; + return (null); + } + + private void clipLine(Edge e){ + float pxmin, pxmax, pymin, pymax; + Site s1, s2; + float x1 = 0, x2 = 0, y1 = 0, y2 = 0; + + x1 = e.reg[0].coord.x; + x2 = e.reg[1].coord.x; + y1 = e.reg[0].coord.y; + y2 = e.reg[1].coord.y; + + // if the distance between the two points this line was created from is + // less than the square root of 2, then ignore it + if(Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))) < minDistanceBetweenSites){ + return; + } + pxmin = borderMinX; + pxmax = borderMaxX; + pymin = borderMinY; + pymax = borderMaxY; + + if(e.a == 1.0 && e.b >= 0.0){ + s1 = e.ep[1]; + s2 = e.ep[0]; + }else{ + s1 = e.ep[0]; + s2 = e.ep[1]; + } + + if(e.a == 1.0){ + y1 = pymin; + if(s1 != null && s1.coord.y > pymin){ + y1 = s1.coord.y; + } + if(y1 > pymax){ + y1 = pymax; + } + x1 = e.c - e.b * y1; + y2 = pymax; + if(s2 != null && s2.coord.y < pymax){ + y2 = s2.coord.y; + } + + if(y2 < pymin){ + y2 = pymin; + } + x2 = (e.c) - (e.b) * y2; + if(((x1 > pxmax) & (x2 > pxmax)) | ((x1 < pxmin) & (x2 < pxmin))){ + return; + } + if(x1 > pxmax){ + x1 = pxmax; + y1 = (e.c - x1) / e.b; + } + if(x1 < pxmin){ + x1 = pxmin; + y1 = (e.c - x1) / e.b; + } + if(x2 > pxmax){ + x2 = pxmax; + y2 = (e.c - x2) / e.b; + } + if(x2 < pxmin){ + x2 = pxmin; + y2 = (e.c - x2) / e.b; + } + }else{ + x1 = pxmin; + if(s1 != null && s1.coord.x > pxmin){ + x1 = s1.coord.x; + } + if(x1 > pxmax){ + x1 = pxmax; + } + y1 = e.c - e.a * x1; + x2 = pxmax; + if(s2 != null && s2.coord.x < pxmax){ + x2 = s2.coord.x; + } + if(x2 < pxmin){ + x2 = pxmin; + } + y2 = e.c - e.a * x2; + if(((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin))){ + return; + } + if(y1 > pymax){ + y1 = pymax; + x1 = (e.c - y1) / e.a; + } + if(y1 < pymin){ + y1 = pymin; + x1 = (e.c - y1) / e.a; + } + if(y2 > pymax){ + y2 = pymax; + x2 = (e.c - y2) / e.a; + } + if(y2 < pymin){ + y2 = pymin; + x2 = (e.c - y2) / e.a; + } + } + + GraphEdge newEdge = new GraphEdge(); + allEdges.add(newEdge); + newEdge.x1 = x1; + newEdge.y1 = y1; + newEdge.x2 = x2; + newEdge.y2 = y2; + + newEdge.site1 = e.reg[0].sitenbr; + newEdge.site2 = e.reg[1].sitenbr; + } + + private void endpoint(Edge e, int lr, Site s){ + e.ep[lr] = s; + if(e.ep[RE - lr] == null){ + return; + } + clipLine(e); + } + + private boolean right(Halfedge el, Vec2 p){ + Edge e = el.ELedge; + Site topsite = e.reg[1]; + boolean rightOf = p.x > topsite.coord.x; + if(rightOf && el.ELpm == LE){ + return true; + } + if(!rightOf && el.ELpm == RE){ + return false; + } + + boolean above; + if(e.a == 1.0){ + float dyp = p.y - topsite.coord.y; + float dxp = p.x - topsite.coord.x; + boolean fast = false; + if((!rightOf & (e.b < 0.0)) | (rightOf & (e.b >= 0.0))){ + above = dyp >= e.b * dxp; + fast = above; + }else{ + above = p.x + p.y * e.b > e.c; + if(e.b < 0.0){ + above = !above; + } + if(!above){ + fast = true; + } + } + if(!fast){ + float dxs = topsite.coord.x - (e.reg[0]).coord.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp + * (1.0 + 2.0 * dxp / dxs + e.b * e.b); + if(e.b < 0.0){ + above = !above; + } + } + }else{ + float yl = e.c - e.a * p.x; + float t1 = p.y - yl; + float t2 = p.x - topsite.coord.x; + float t3 = yl - topsite.coord.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return ((el.ELpm == LE) == above); + } + + private Site rightreg(Halfedge he){ + if(he.ELedge == null) return bottomsite; + + return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]); + } + + private Site intersect(Halfedge el1, Halfedge el2){ + Edge e1, e2, e; + Halfedge el; + float d, xint, yint; + boolean right_of_site; + Site v; + + e1 = el1.ELedge; + e2 = el2.ELedge; + if(e1 == null || e2 == null){ + return null; + } + + if(e1.reg[1] == e2.reg[1]){ + return null; + } + + d = e1.a * e2.b - e1.b * e2.a; + if(-1.0e-10 < d && d < 1.0e-10){ + return null; + } + + xint = (e1.c * e2.b - e2.c * e1.b) / d; + yint = (e2.c * e1.a - e1.c * e2.a) / d; + + if((e1.reg[1].coord.y < e2.reg[1].coord.y) + || (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x)){ + el = el1; + e = e1; + }else{ + el = el2; + e = e2; + } + + right_of_site = xint >= e.reg[1].coord.x; + if((right_of_site && el.ELpm == LE) + || (!right_of_site && el.ELpm == RE)){ + return null; + } + + v = new Site(); + v.coord.x = xint; + v.coord.y = yint; + return (v); + } + + static class Site{ + Vec2 coord = new Vec2(); + int sitenbr; + } + + static class Halfedge{ + Halfedge ELleft, ELright; + Edge ELedge; + boolean deleted; + int ELpm; + Site vertex; + float ystar; + Halfedge PQnext; + } + + public static class GraphEdge{ + public float x1, y1, x2, y2; + + public int site1, site2; + } + + static class Edge{ + float a = 0, b = 0, c = 0; + Site[] ep = new Site[2]; + Site[] reg = new Site[2]; + int edgenbr; + } +} diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index d7ea3b392d..72fcc09aa1 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -29,9 +29,9 @@ import static mindustry.Vars.*; public class Maps{ /** List of all built-in maps. Filenames only. */ - private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "mudFlats", "moltenLake", "archipelago", "debrisField", "veins", "glacier"}; + private static String[] defaultMapNames = {"maze", "fortress", "labyrinth", "islands", "tendrils", "caldera", "wasteland", "shattered", "fork", "triad", "mudFlats", "moltenLake", "archipelago", "debrisField", "veins", "glacier", "passage"}; /** Maps tagged as PvP */ - static final String[] pvpMaps = {"veins", "glacier"}; + static final String[] pvpMaps = {"veins", "glacier", "passage"}; /** All maps stored in an ordered array. */ private Seq maps = new Seq<>(); /** Serializer for meta. */ diff --git a/core/src/mindustry/maps/filters/ClearFilter.java b/core/src/mindustry/maps/filters/ClearFilter.java index 3b2684ef7e..59e4512a82 100644 --- a/core/src/mindustry/maps/filters/ClearFilter.java +++ b/core/src/mindustry/maps/filters/ClearFilter.java @@ -7,12 +7,14 @@ import mindustry.world.*; import static mindustry.maps.filters.FilterOption.*; public class ClearFilter extends GenerateFilter{ - protected Block block = Blocks.air; + protected Block target = Blocks.stone; + protected Block replace = Blocks.air; @Override public FilterOption[] options(){ return new BlockOption[]{ - new BlockOption("block", () -> block, b -> block = b, b -> oresOnly.get(b) || wallsOnly.get(b)) + new BlockOption("target", () -> target, b -> target = b, anyOptional), + new BlockOption("replacement", () -> replace, b -> replace = b, anyOptional) }; } @@ -24,12 +26,21 @@ public class ClearFilter extends GenerateFilter{ @Override public void apply(GenerateInput in){ - if(in.block == block){ - in.block = Blocks.air; - } - - if(in.overlay == block){ - in.overlay = Blocks.air; + if(in.block == target || in.floor == target || (target.isOverlay() && in.overlay == target)){ + //special case: when air is the result, replace only the overlay or wall + if(replace == Blocks.air){ + if(in.overlay == target){ + in.overlay = Blocks.air; + }else{ + in.block = Blocks.air; + } + }else if(replace.isOverlay()){ //replace the best match based on type + in.overlay = replace; + }else if(replace.isFloor()){ + in.floor = replace; + }else{ + in.block = replace; + } } } } diff --git a/core/src/mindustry/mod/ClassMap.java b/core/src/mindustry/mod/ClassMap.java index 2b34e5bbb5..de033c0e9a 100644 --- a/core/src/mindustry/mod/ClassMap.java +++ b/core/src/mindustry/mod/ClassMap.java @@ -1,6 +1,7 @@ package mindustry.mod; import arc.struct.*; +import mindustry.world.blocks.environment.*; import mindustry.world.blocks.payloads.*; /** Generated class. Maps simple class names to concrete classes. For use in JSON mods. */ @@ -173,8 +174,11 @@ public class ClassMap{ classes.put("OreBlock", mindustry.world.blocks.environment.OreBlock.class); classes.put("OverlayFloor", mindustry.world.blocks.environment.OverlayFloor.class); classes.put("Prop", mindustry.world.blocks.environment.Prop.class); + classes.put("Bush", Bush.class); + classes.put("WavingProp", WavingProp.class); classes.put("ShallowLiquid", mindustry.world.blocks.environment.ShallowLiquid.class); classes.put("SpawnBlock", mindustry.world.blocks.environment.SpawnBlock.class); + classes.put("StaticClusterWall", StaticClusterWall.class); classes.put("StaticTree", mindustry.world.blocks.environment.StaticTree.class); classes.put("StaticWall", mindustry.world.blocks.environment.StaticWall.class); classes.put("TreeBlock", mindustry.world.blocks.environment.TreeBlock.class); diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index 6c763e840b..aa19947d54 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -170,7 +170,8 @@ public class CustomRulesDialog extends BaseDialog{ check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode); check("@rules.buildai", b -> rules.teams.get(rules.waveTeam).ai = rules.teams.get(rules.waveTeam).infiniteResources = b, () -> rules.teams.get(rules.waveTeam).ai); check("@rules.corecapture", b -> rules.coreCapture = b, () -> rules.coreCapture); - number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200)); + check("@rules.polygoncoreprotection", b -> rules.polygonCoreProtection = b, () -> rules.polygonCoreProtection); + number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection); title("@rules.title.environment"); check("@rules.explosions", b -> rules.damageExplosions = b, () -> rules.damageExplosions); diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 11eb6b3bef..6e2e2c1afc 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -229,6 +229,8 @@ public class Block extends UnlockableContent{ public boolean instantDeconstruct = false; /** Effect for breaking the block. Passes size as rotation. */ public Effect breakEffect = Fx.breakBlock; + /** Effect for destroying the block. */ + public Effect destroyEffect = Fx.dynamicExplosion; /** Multiplier for cost of research in tech tree. */ public float researchCostMultiplier = 1; /** Whether this block has instant transfer.*/ diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java index b873546340..0069ea0f56 100644 --- a/core/src/mindustry/world/Build.java +++ b/core/src/mindustry/world/Build.java @@ -10,9 +10,11 @@ import mindustry.content.*; import mindustry.entities.*; import mindustry.game.EventType.*; import mindustry.game.*; +import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.world.blocks.*; import mindustry.world.blocks.ConstructBlock.*; +import mindustry.world.blocks.storage.CoreBlock.*; import static mindustry.Vars.*; @@ -132,7 +134,23 @@ public class Build{ return false; } - if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset, y * tilesize + type.offset, core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){ + //find closest core, if it doesn't match the team, placing is not legal + if(state.rules.polygonCoreProtection){ + float mindst = Float.MAX_VALUE; + CoreBuild closest = null; + for(TeamData data : state.teams.active){ + for(CoreBuild tile : data.cores){ + float dst = tile.dst2(x * tilesize + type.offset, y * tilesize + type.offset); + if(dst < mindst){ + closest = tile; + mindst = dst; + } + } + } + if(closest != null && closest.team != team){ + return false; + } + }else if(state.teams.eachEnemyCore(team, core -> Mathf.dst(x * tilesize + type.offset, y * tilesize + type.offset, core.x, core.y) < state.rules.enemyCoreBuildRadius + type.size * tilesize / 2f)){ return false; } diff --git a/core/src/mindustry/world/blocks/power/ImpactReactor.java b/core/src/mindustry/world/blocks/power/ImpactReactor.java index d692699100..64e94d8c14 100644 --- a/core/src/mindustry/world/blocks/power/ImpactReactor.java +++ b/core/src/mindustry/world/blocks/power/ImpactReactor.java @@ -26,6 +26,7 @@ public class ImpactReactor extends PowerGenerator{ public float itemDuration = 60f; public int explosionRadius = 23; public int explosionDamage = 1900; + public Effect explodeEffect = Fx.impactReactorExplosion; public Color plasma1 = Color.valueOf("ffd06b"), plasma2 = Color.valueOf("ff361b"); @@ -137,32 +138,14 @@ public class ImpactReactor extends PowerGenerator{ public void onDestroyed(){ super.onDestroyed(); - if(warmup < 0.4f || !state.rules.reactorExplosions) return; + if(warmup < 0.3f || !state.rules.reactorExplosions) return; Sounds.explosionbig.at(tile); - Effect.shake(6f, 16f, x, y); - Fx.impactShockwave.at(x, y); - for(int i = 0; i < 6; i++){ - Time.run(Mathf.random(80), () -> Fx.impactcloud.at(x, y)); - } - Damage.damage(x, y, explosionRadius * tilesize, explosionDamage * 4); - - for(int i = 0; i < 20; i++){ - Time.run(Mathf.random(80), () -> { - Tmp.v1.rnd(Mathf.random(40f)); - Fx.explosion.at(Tmp.v1.x + x, Tmp.v1.y + y); - }); - } - - for(int i = 0; i < 70; i++){ - Time.run(Mathf.random(90), () -> { - Tmp.v1.rnd(Mathf.random(120f)); - Fx.impactsmoke.at(Tmp.v1.x + x, Tmp.v1.y + y); - }); - } + Effect.shake(6f, 16f, x, y); + explodeEffect.at(x, y); } @Override diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index a5d61237e3..52844719d7 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -194,6 +194,20 @@ public class CoreBlock extends StorageBlock{ super.damage(source, damage); } + @Override + public void created(){ + super.created(); + + Events.fire(new CoreChangeEvent(this)); + } + + @Override + public void changeTeam(Team next){ + super.changeTeam(next); + + Events.fire(new CoreChangeEvent(this)); + } + @Override public double sense(LAccess sensor){ if(sensor == LAccess.itemCapacity) return storageCapacity; @@ -260,6 +274,8 @@ public class CoreBlock extends StorageBlock{ spawner.getSpawns().add(tile); } } + + Events.fire(new CoreChangeEvent(this)); } @Override diff --git a/gradle.properties b/gradle.properties index 15947e3eac..133a6a5351 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ kapt.include.compile.classpath=false kotlin.stdlib.default.dependency=false #needed for android compilation android.useAndroidX=true -archash=07ced971f4c8b8b5a61aa3a84b29c90aa497cb48 +archash=df2786dd8de1ebc1b70c453d6dcac62c2ecf9e18