From 865ee952c9b03a55b1f4dc1610f4c9e9f8ef2ae2 Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 16 Jun 2021 13:26:18 -0400 Subject: [PATCH] WIP polygonal core protection --- core/assets/bundles/bundle.properties | 1 + core/src/mindustry/game/Rules.java | 2 + .../mindustry/graphics/OverlayRenderer.java | 96 ++- core/src/mindustry/graphics/Voronoi.java | 731 ++++++++++++++++++ .../ui/dialogs/CustomRulesDialog.java | 3 +- core/src/mindustry/world/Build.java | 20 +- 6 files changed, 842 insertions(+), 11 deletions(-) create mode 100755 core/src/mindustry/graphics/Voronoi.java diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 3e2304b765..280d6624e6 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -995,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/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..fe7309621e 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,39 @@ public class OverlayRenderer{ private float buildFade, unitFade; private Sized lastSelect; + private Seq cedges = new Seq<>(); + private int lastCores = -1; + + public OverlayRenderer(){ + Events.on(WorldLoadEvent.class, e -> { + cedges.clear(); + lastCores = -1; + }); + } + + private void updateCoreEdges(){ + int count = state.teams.active.sum(t -> t.cores.size); + if(count == lastCores){ + return; + } + + lastCores = count; + 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)); + } + } + + 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 +154,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 +246,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..7053ac20ee --- /dev/null +++ b/core/src/mindustry/graphics/Voronoi.java @@ -0,0 +1,731 @@ +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; + Site newsite, bot, top, temp1, p; + Site v; + Vec2 newintstar = null; + int pm; + Halfedge lbnd, rbnd, llbnd, rrbnd, bisector; + Edge e; + + 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(); + newsite = next(); + 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); + } + + // if the lowest site has a smaller y value than the lowest vector + // intersection, + // process the site otherwise process the vector intersection + + if(newsite != null + && (PQcount == 0 || newsite.coord.y < newintstar.y || (newsite.coord.y == newintstar.y && newsite.coord.x < newintstar.x))){ + /* new site is smallest -this is a site event */ + // get the first HalfEdge to the LEFT of the new site + int i, bucket; + Halfedge he; + + /* Use hash table to get close to desired halfedge */ + // use the hash function to find the place in the hash map that this + // HalfEdge should be + bucket = (int)(((newsite.coord).x - xmin) / deltax * ELhashsize); + + // make sure that the bucket position in within the range of the hash + // array + if(bucket < 0){ + bucket = 0; + } + if(bucket >= ELhashsize){ + bucket = ELhashsize - 1; + } + + he = getHash(bucket); + if(he == null) + // if the HE isn't found, search backwards and forwards in the hash map + // for the first non-null entry + { + for(i = 1; i < ELhashsize; i += 1){ + if((he = getHash(bucket - i)) != null){ + break; + } + if((he = getHash(bucket + i)) != null){ + break; + } + } + } + /* Now search linear list of halfedges for the correct one */ + if(he == ELleftend || (he != ELrightend && right(he, (newsite.coord)))){ + // keep going right on the list until either the end is reached, or + // you find the 1st edge which the point isn't to the right of + do{ + he = he.ELright; + }while(he != ELrightend && right(he, (newsite.coord))); + he = he.ELleft; + }else + // if the point is to the left of the HalfEdge, then search left for + // the HE just to the left of the point + { + do{ + he = he.ELleft; + }while(he != ELleftend && !right(he, (newsite.coord))); + } + + /* Update hash table and reference counts */ + if(bucket > 0 && bucket < ELhashsize - 1){ + ELhash[bucket] = he; + } + lbnd = (he); + // get the first HalfEdge to the RIGHT of the new site + rbnd = (lbnd.ELright); + // if this halfedge has no edge,bot =bottom site (whatever that + // is) + bot = rightreg(lbnd); + // create a new edge that bisects + e = bisect(bot, newsite); + + // create a new HalfEdge, setting its ELpm field to 0 + bisector = newHe(e, LE); + // insert this new bisector edge between the left and right + // vectors in a linked list + insert(lbnd, bisector); + + // if the new bisector intersects with the left edge, + // remove the left edge's vertex, and put in the new one + if((p = intersect(lbnd, bisector)) != null){ + pqdelete(lbnd); + pqinsert(lbnd, p, p.coord.dst(newsite.coord)); + } + lbnd = bisector; + // create a new HalfEdge, setting its ELpm field to 1 + bisector = newHe(e, RE); + // insert the new HE to the right of the original bisector + // earlier in the IF stmt + insert(lbnd, bisector); + + // if this new bisector intersects with the new HalfEdge + if((p = intersect(bisector, rbnd)) != null){ + // push the HE into the ordered linked list of vertices + pqinsert(bisector, p, p.coord.dst(newsite.coord)); + } + newsite = next(); + }else if(!(PQcount == 0)) + /* intersection is smallest - this is a vector event */{ + // pop the HalfEdge with the lowest vector off the ordered list + // of vectors + Halfedge curr; + + curr = PQhash[PQmin].PQnext; + PQhash[PQmin].PQnext = curr.PQnext; + PQcount -= 1; + lbnd = (curr); + // get the HalfEdge to the left of the above HE + llbnd = (lbnd.ELleft); + // get the HalfEdge to the right of the above HE + rbnd = (lbnd.ELright); + // get the HalfEdge to the right of the HE to the right of the + // lowest HE + rrbnd = (rbnd.ELright); + // get the Site to the left of the left HE which it bisects + bot = leftReg(lbnd); + // get the Site to the right of the right HE which it bisects + top = rightreg(rbnd); + + v = lbnd.vertex; // get the vertex that caused this event + // set the vertex number - couldn't do this + v.sitenbr = nvertices; + nvertices += 1; + // earlier since we didn't know when it would be processed + endpoint(lbnd.ELedge, lbnd.ELpm, v); + // set the endpoint of + // the left HalfEdge to be this vector + endpoint(rbnd.ELedge, rbnd.ELpm, v); + // set the endpoint of the right HalfEdge to + // be this vector + delete(lbnd); // mark the lowest HE for + // deletion - can't delete yet because there might be pointers + // to it in Hash Map + pqdelete(rbnd); + // remove all vertex events to do with the right HE + delete(rbnd); // mark the right HE for + // deletion - can't delete yet because there might be pointers + // to it in Hash Map + pm = LE; // set the pm variable to zero + + if(bot.coord.y > top.coord.y) + // if the site to the left of the event is higher than the + // Site + { // to the right of it, then swap them and set the 'pm' + // variable to 1 + temp1 = bot; + bot = top; + top = temp1; + pm = RE; + } + e = bisect(bot, top); // create an Edge (or line) + // that is between the two Sites. This creates the formula of + // the line, and assigns a line number to it + bisector = newHe(e, pm); // create a HE from the Edge 'e', + // and make it point to that edge + // with its ELedge field + insert(llbnd, bisector); // insert the new bisector to the + // right of the left HE + endpoint(e, RE - pm, v); // set one endpoint to the new edge + // to be the vector point 'v'. + // If the site to the left of this bisector is higher than the + // right Site, then this endpoint + // is put in position 0; otherwise in pos 1 + + // if left HE and the new bisector intersect, then delete + // the left HE, and reinsert it + if((p = intersect(llbnd, bisector)) != null){ + pqdelete(llbnd); + pqinsert(llbnd, p, p.coord.dst(bot.coord)); + } + + // if right HE and the new bisector intersect, then + // reinsert it + 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){ + float dx, dy, adx, ady; + Edge newedge; + + 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 + dx = s2.coord.x - s1.coord.x; + dy = s2.coord.y - s1.coord.y; + // make sure that the difference in positive + adx = dx > 0 ? dx : -dx; + 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; + Site topsite; + boolean right_of_site; + boolean above, fast; + float dxp, dyp, dxs, t1, t2, t3, yl; + + e = el.ELedge; + topsite = e.reg[1]; + right_of_site = p.x > topsite.coord.x; + if(right_of_site && el.ELpm == LE){ + return (true); + } + if(!right_of_site && el.ELpm == RE){ + return (false); + } + + if(e.a == 1.0){ + dyp = p.y - topsite.coord.y; + dxp = p.x - topsite.coord.x; + fast = false; + if((!right_of_site & (e.b < 0.0)) | (right_of_site & (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){ + 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 /* e.b==1.0 */{ + yl = e.c - e.a * p.x; + t1 = p.y - yl; + t2 = p.x - topsite.coord.x; + 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; + + // if the ELpm field is zero, return the site 0 that this edge bisects, + // otherwise return site number 1 + return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]); + } + + // create a new site where the HalfEdges el1 and el2 intersect - note that + // the Vec2 in the argument list is not used, don't know why it's there + 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 the two edges bisect the same parent, 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; + } + + // create a new site at the point of intersection - this is a new vector + // event waiting to happen + v = new Site(); + v.coord.x = xint; + v.coord.y = yint; + return (v); + } + + // used both for sites and for vertices + 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]; // JH: End points? + Site[] reg = new Site[2]; // JH: Sites this edge bisects? + int edgenbr; + } +} 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/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; }