Merge branch 'master' of https://github.com/Anuken/Mindustry into 7.0-features

 Conflicts:
	core/src/mindustry/editor/MapInfoDialog.java
	core/src/mindustry/maps/filters/ClearFilter.java
	core/src/mindustry/mod/ClassMap.java
	gradle.properties
This commit is contained in:
Anuken 2021-06-17 10:44:51 -04:00
commit afe963ca5d
21 changed files with 871 additions and 47 deletions

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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());

View file

@ -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);

View file

@ -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){

View file

@ -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);

View file

@ -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;

View file

@ -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. */

View file

@ -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<CoreEdge> 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<Vec2> pos = new Seq<>();
Seq<CoreBuild> 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;
}
}
}

View file

@ -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<GraphEdge> allEdges;
float minDistanceBetweenSites = 1f;
public static Seq<GraphEdge> generate(Vec2[] values, float minX, float maxX, float minY, float maxY){
return new Voronoi().generateVoronoi(values, minX, maxX, minY, maxY);
}
Seq<GraphEdge> 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;
}
}

View file

@ -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<Map> maps = new Seq<>();
/** Serializer for meta. */

View file

@ -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;
}
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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.*/

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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