From 6bf5e8ae1e58d6ef9c170e4662b6d4a1c1120caf Mon Sep 17 00:00:00 2001 From: Anuken Date: Wed, 2 Aug 2023 22:15:44 -0400 Subject: [PATCH] WIP logic particle effect instruction --- .../sprites/statuses/status-fast.png | Bin 0 -> 218 bytes core/assets/bundles/bundle.properties | 2 + core/src/mindustry/ClientLauncher.java | 2 + core/src/mindustry/content/Blocks.java | 5 +- core/src/mindustry/content/Fx.java | 33 ++- core/src/mindustry/core/Logic.java | 1 - core/src/mindustry/core/UI.java | 2 + core/src/mindustry/logic/GlobalVars.java | 8 + core/src/mindustry/logic/LExecutor.java | 97 +++++++- core/src/mindustry/logic/LStatements.java | 102 ++++++++ core/src/mindustry/logic/LogicFx.java | 106 ++++++++ core/src/mindustry/type/UnitType.java | 2 + core/src/mindustry/type/Weapon.java | 2 +- core/src/mindustry/ui/BorderImage.java | 5 + .../ui/dialogs/CustomRulesDialog.java | 7 - .../mindustry/ui/dialogs/EffectsDialog.java | 235 ++++++++++++++++++ core/src/mindustry/world/Block.java | 2 +- .../world/blocks/storage/CoreBlock.java | 6 +- .../world/blocks/units/Reconstructor.java | 2 +- 19 files changed, 596 insertions(+), 23 deletions(-) create mode 100644 core/assets-raw/sprites/statuses/status-fast.png create mode 100644 core/src/mindustry/logic/LogicFx.java create mode 100644 core/src/mindustry/ui/dialogs/EffectsDialog.java diff --git a/core/assets-raw/sprites/statuses/status-fast.png b/core/assets-raw/sprites/statuses/status-fast.png new file mode 100644 index 0000000000000000000000000000000000000000..0de6eda95b46d74003ff90f48de113ce952cc9d5 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Q#@T9Ln2z= zPV?q#P~dUte)vya?I5o<^VkH{O9I;x6$wV(C(}Bzopr0ECWHJ^%m! literal 0 HcmV?d00001 diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 73f8093a15..e2bd35db05 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -2258,6 +2258,8 @@ lst.cutscene = Manipulate the player camera. lst.setflag = Set a global flag that can be read by all processors. lst.getflag = Check if a global flag is set. lst.setprop = Sets a property of a unit or building. +lst.effect = Create a particle effect. +lst.sync = Sync a variable across the network.\nOnly invoked 10 times a second at most. logic.nounitbuild = [red]Unit building logic is not allowed here. diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 3f50c85003..ad77dd6bcc 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -204,6 +204,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform if(limitFps){ nextFrame += (1000 * 1000000) / targetfps; + }else{ + nextFrame = Time.nanos(); } if(!finished){ diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index 364b8e7c30..08a157dd9b 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -2937,6 +2937,7 @@ public class Blocks{ armor = 5f; alwaysUnlocked = true; incinerateNonBuildable = true; + requiresCoreZone = true; //TODO should this be higher? buildCostMultiplier = 0.7f; @@ -2956,6 +2957,7 @@ public class Blocks{ armor = 10f; incinerateNonBuildable = true; buildCostMultiplier = 0.7f; + requiresCoreZone = true; unitCapModifier = 15; researchCostMultipliers.put(Items.silicon, 0.5f); @@ -2973,6 +2975,7 @@ public class Blocks{ armor = 15f; incinerateNonBuildable = true; buildCostMultiplier = 0.7f; + requiresCoreZone = true; unitCapModifier = 15; researchCostMultipliers.put(Items.silicon, 0.4f); @@ -4571,7 +4574,7 @@ public class Blocks{ deathExplosionEffect = Fx.massiveExplosion; shootOnDeath = true; shake = 10f; - bullet = new ExplosionBulletType(640f, 65f){{ + bullet = new ExplosionBulletType(700f, 65f){{ hitColor = Pal.redLight; shootEffect = new MultiEffect(Fx.massiveExplosion, Fx.scatheExplosion, Fx.scatheLight, new WaveEffect(){{ lifetime = 10f; diff --git a/core/src/mindustry/content/Fx.java b/core/src/mindustry/content/Fx.java index 90de9a73f2..1d1301abaf 100644 --- a/core/src/mindustry/content/Fx.java +++ b/core/src/mindustry/content/Fx.java @@ -29,11 +29,11 @@ public class Fx{ none = new Effect(0, 0f, e -> {}), - blockCrash = new Effect(100f, e -> { + blockCrash = new Effect(90f, e -> { if(!(e.data instanceof Block block)) return; alpha(e.fin() + 0.5f); - float offset = Mathf.lerp(0f, 200f, e.fout()); + float offset = Mathf.lerp(0f, 180f, e.fout()); color(0f, 0f, 0f, 0.44f); rect(block.fullIcon, e.x - offset * 4f, e.y, (float)block.size * 8f, (float)block.size * 8f); color(Color.white); @@ -417,6 +417,20 @@ public class Fx{ Lines.spikes(e.x, e.y, 1f + e.fin() * 6f, e.fout() * 4f, 6); }), + sparkExplosion = new Effect(30f, 160f, e -> { + color(e.color); + stroke(e.fout() * 3f); + float circleRad = 6f + e.finpow() * e.rotation; + Lines.circle(e.x, e.y, circleRad); + + rand.setSeed(e.id); + for(int i = 0; i < 16; i++){ + float angle = rand.random(360f); + float lenRand = rand.random(0.5f, 1f); + Lines.lineAngle(e.x, e.y, angle, e.foutpow() * e.rotation * 0.8f * rand.random(1f, 0.6f) + 2f, e.finpow() * e.rotation * 1.2f * lenRand + 6f); + } + }), + titanExplosion = new Effect(30f, 160f, e -> { color(e.color); stroke(e.fout() * 3f); @@ -609,6 +623,12 @@ public class Fx{ Lines.circle(e.x, e.y, 2f + e.finpow() * 7f); }), + dynamicWave = new Effect(22, e -> { + color(e.color, 0.7f); + stroke(e.fout() * 2f); + Lines.circle(e.x, e.y, 4f + e.finpow() * e.rotation); + }), + shieldWave = new Effect(22, e -> { color(e.color, 0.7f); stroke(e.fout() * 2f); @@ -1517,6 +1537,15 @@ public class Fx{ }); }), + smokePuff = new Effect(30, e -> { + color(e.color); + + randLenVectors(e.id, 6, 4f + 30f * e.finpow(), (x, y) -> { + Fill.circle(e.x + x, e.y + y, e.fout() * 3f); + Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout()); + }); + }), + shootSmall = new Effect(8, e -> { color(Pal.lighterOrange, Pal.lightOrange, e.fin()); float w = 1f + 5 * e.fout(); diff --git a/core/src/mindustry/core/Logic.java b/core/src/mindustry/core/Logic.java index b312e93374..e722afb8f1 100644 --- a/core/src/mindustry/core/Logic.java +++ b/core/src/mindustry/core/Logic.java @@ -458,7 +458,6 @@ public class Logic implements ApplicationListener{ } } - //TODO objectives clientside??? if(!state.isEditor()){ state.rules.objectives.update(); if(state.rules.objectives.checkChanged() && net.server()){ diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index 3851d4880a..92d8552618 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -71,6 +71,7 @@ public class UI implements ApplicationListener, Loadable{ public SchematicsDialog schematics; public ModsDialog mods; public ColorPicker picker; + public EffectsDialog effects; public LogicDialog logic; public FullTextDialog fullText; public CampaignCompleteDialog campaignComplete; @@ -183,6 +184,7 @@ public class UI implements ApplicationListener, Loadable{ consolefrag = new ConsoleFragment(); picker = new ColorPicker(); + effects = new EffectsDialog(); editor = new MapEditorDialog(); controls = new KeybindDialog(); restart = new GameOverDialog(); diff --git a/core/src/mindustry/logic/GlobalVars.java b/core/src/mindustry/logic/GlobalVars.java index 7a0556f78c..3354277b13 100644 --- a/core/src/mindustry/logic/GlobalVars.java +++ b/core/src/mindustry/logic/GlobalVars.java @@ -2,6 +2,7 @@ package mindustry.logic; import arc.*; import arc.files.*; +import arc.graphics.*; import arc.math.*; import arc.struct.*; import arc.util.*; @@ -81,6 +82,13 @@ public class GlobalVars{ } } + for(var entry : Colors.getColors().entries()){ + //ignore uppercase variants, they are duplicates + if(Character.isUpperCase(entry.key.charAt(0))) continue; + + put("@color" + Strings.capitalize(entry.key), entry.value.toDoubleBits()); + } + //used as a special value for any environmental solid block put("@solid", Blocks.stoneWall); diff --git a/core/src/mindustry/logic/LExecutor.java b/core/src/mindustry/logic/LExecutor.java index 83bfb8c77a..3a2fe7cf10 100644 --- a/core/src/mindustry/logic/LExecutor.java +++ b/core/src/mindustry/logic/LExecutor.java @@ -16,6 +16,7 @@ import mindustry.entities.*; import mindustry.game.*; import mindustry.game.Teams.*; import mindustry.gen.*; +import mindustry.logic.LogicFx.*; import mindustry.type.*; import mindustry.world.*; import mindustry.world.blocks.environment.*; @@ -122,6 +123,11 @@ public class LExecutor{ return index < 0 ? logicVars.get(-index) : vars[index]; } + /** @return a Var from this processor, never a global constant. May be null if out of bounds. */ + public @Nullable Var optionalVar(int index){ + return index < 0 || index >= vars.length ? null : vars[index]; + } + public @Nullable Building building(int index){ Object o = var(index).objval; return var(index).isobj && o instanceof Building building ? building : null; @@ -203,6 +209,9 @@ public class LExecutor{ public Object objval; public double numval; + //ms timestamp for when this was last synced; used in the sync instruction + public long syncTime; + public Var(String name){ this.name = name; } @@ -1557,6 +1566,35 @@ public class LExecutor{ } } + public static class EffectI implements LInstruction{ + public EffectEntry type; + public int x, y, rotation, color, data; + + public EffectI(EffectEntry type, int x, int y, int rotation, int color, int data){ + this.type = type; + this.x = x; + this.y = y; + this.rotation = rotation; + this.color = color; + this.data = data; + } + + public EffectI(){ + } + + @Override + public void run(LExecutor exec){ + if(type != null){ + double col = exec.num(color); + //limit size so people don't create lag with ridiculous numbers (some explosions scale with size) + float rot = type.rotate ? exec.numf(rotation) : + Math.min(exec.numf(rotation), 1000f); + + type.effect.at(World.unconv(exec.numf(x)), World.unconv(exec.numf(y)), rot, Tmp.c1.fromDouble(col), exec.obj(data)); + } + } + } + public static class ExplosionI implements LInstruction{ public int team, x, y, radius, damage, air, ground, pierce; @@ -1617,6 +1655,47 @@ public class LExecutor{ } } + @Remote(unreliable = true) + public static void syncVariable(Building building, int variable, Object value){ + if(building instanceof LogicBuild build){ + Var v = build.executor.optionalVar(variable); + if(v != null && !v.constant){ + if(value instanceof Double d){ + v.isobj = false; + v.numval = d; + }else{ + v.isobj = true; + v.objval =value; + } + } + } + } + + public static class SyncI implements LInstruction{ + //10 syncs per second + static final long syncInterval = 1000 / 10; + + public int variable; + + public SyncI(int variable){ + this.variable = variable; + } + + public SyncI(){ + } + + @Override + public void run(LExecutor exec){ + if(exec.build != null && exec.build.block.privileged){ + Var v = exec.var(variable); + if(!v.constant && Time.timeSinceMillis(v.syncTime) > syncInterval){ + v.syncTime = Time.millis(); + Call.syncVariable(exec.build, variable, v.isobj ? v.objval : v.numval); + } + } + } + } + public static class GetFlagI implements LInstruction{ public int result, flag; @@ -1638,6 +1717,15 @@ public class LExecutor{ } } + @Remote(called = Loc.server) + public static void setFlag(String flag, boolean add){ + if(add){ + state.rules.objectiveFlags.add(flag); + }else{ + state.rules.objectiveFlags.remove(flag); + } + } + public static class SetFlagI implements LInstruction{ public int flag, value; @@ -1651,12 +1739,9 @@ public class LExecutor{ @Override public void run(LExecutor exec){ - if(exec.obj(flag) instanceof String str){ - if(!exec.bool(value)){ - state.rules.objectiveFlags.remove(str); - }else{ - state.rules.objectiveFlags.add(str); - } + //don't invoke unless the flag state actually changes + if(exec.obj(flag) instanceof String str && state.rules.objectiveFlags.contains(str) != exec.bool(value)){ + Call.setFlag(str, exec.bool(value)); } } } diff --git a/core/src/mindustry/logic/LStatements.java b/core/src/mindustry/logic/LStatements.java index b829351c3c..1e24a6cb7a 100644 --- a/core/src/mindustry/logic/LStatements.java +++ b/core/src/mindustry/logic/LStatements.java @@ -11,8 +11,10 @@ import mindustry.*; import mindustry.annotations.Annotations.*; import mindustry.ctype.*; import mindustry.gen.*; +import mindustry.graphics.*; import mindustry.logic.LCanvas.*; import mindustry.logic.LExecutor.*; +import mindustry.logic.LogicFx.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.meta.*; @@ -1506,6 +1508,80 @@ public class LStatements{ } } + @RegisterStatement("effect") + public static class EffectStatement extends LStatement{ + public String type = "warn", x = "0", y = "0", sizerot = "2", color = "%ffaaff", data = ""; + + @Override + public void build(Table table){ + table.clearChildren(); + + table.button(b -> { + b.label(() -> type).growX().wrap().labelAlign(Align.center); + b.clicked(() -> ui.effects.show(entry -> { + type = entry.name; + build(table); + })); + }, Styles.logict, () -> {}).size(150f, 40f).margin(5f).pad(4f).color(table.color).colspan(2); + + EffectEntry entry = LogicFx.get(type); + + row(table); + + fields(table, "x", x, str -> x = str); + fields(table, "y", y, str -> y = str); + row(table); + + if(entry != null){ + if(entry.color){ + fields(table, "color", color, str -> color = str).width(120f); + + table.button(b -> { + b.image(Icon.pencilSmall); + b.clicked(() -> { + Color current = Pal.accent.cpy(); + if(color.startsWith("%")){ + try{ + current = Color.valueOf(color.substring(1)); + }catch(Exception ignored){} + } + + ui.picker.show(current, result -> { + color = "%" + result.toString().substring(0, result.a >= 1f ? 6 : 8); + build(table); + }); + }); + }, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color); + } + + row(table); + + if(entry.size || entry.rotate){ + fields(table, entry.size ? "size" : "rotation", sizerot, str -> sizerot = str); + } + + if(entry.data != null){ + fields(table, "data", data, str -> data = str); + } + } + } + + @Override + public boolean privileged(){ + return true; + } + + @Override + public LInstruction build(LAssembler b){ + return new EffectI(LogicFx.get(type), b.var(x), b.var(y), b.var(sizerot), b.var(color), b.var(data)); + } + + @Override + public LCategory category(){ + return LCategory.world; + } + } + @RegisterStatement("explosion") public static class ExplosionStatement extends LStatement{ public String team = "@crux", x = "0", y = "0", radius = "5", damage = "50", air = "true", ground = "true", pierce = "false"; @@ -1626,6 +1702,32 @@ public class LStatements{ } } + //TODO: test this first + //@RegisterStatement("sync") + public static class SyncStatement extends LStatement{ + public String variable = "var"; + + @Override + public void build(Table table){ + fields(table, variable, str -> variable = str).width(190f); + } + + @Override + public boolean privileged(){ + return true; + } + + @Override + public LInstruction build(LAssembler builder){ + return new SyncI(builder.var(variable)); + } + + @Override + public LCategory category(){ + return LCategory.world; + } + } + @RegisterStatement("getflag") public static class GetFlagStatement extends LStatement{ public String result = "result", flag = "\"flag\""; diff --git a/core/src/mindustry/logic/LogicFx.java b/core/src/mindustry/logic/LogicFx.java new file mode 100644 index 0000000000..2296703704 --- /dev/null +++ b/core/src/mindustry/logic/LogicFx.java @@ -0,0 +1,106 @@ +package mindustry.logic; + +import arc.struct.*; +import arc.util.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.world.*; + +public class LogicFx{ + private static OrderedMap map = new OrderedMap<>(); + + static{ + map.putAll( + "warn", new EffectEntry(Fx.unitCapKill), + "cross", new EffectEntry(Fx.unitEnvKill), + "blockFall", new EffectEntry(Fx.blockCrash).data(Block.class).bounds(100f), + "placeBlock", new EffectEntry(Fx.placeBlock).size(), + "placeBlockSpark", new EffectEntry(Fx.coreLaunchConstruct).size(), + "breakBlock", new EffectEntry(Fx.breakBlock).size(), + "spawn", new EffectEntry(Fx.spawn), + "trail", new EffectEntry(Fx.colorTrail).size().color(), + "breakProp", new EffectEntry(Fx.breakProp).size().color(), + "smokeCloud", new EffectEntry(Fx.missileTrailSmoke).color(), + "vapor", new EffectEntry(Fx.vapor).color(), + "hit", new EffectEntry(Fx.hitBulletColor).color(), + "hitSquare", new EffectEntry(Fx.hitSquaresColor).color(), + "shootSmall", new EffectEntry(Fx.shootSmall).color().rotate(), + "shootBig", new EffectEntry(Fx.shootTitan).color().rotate(), + "smokeSmall", new EffectEntry(Fx.shootSmallSmoke).rotate(), + "smokeBig", new EffectEntry(Fx.shootBigSmoke).rotate(), + "smokeColor", new EffectEntry(Fx.shootSmokeTitan).rotate().color(), + "smokeSquare", new EffectEntry(Fx.shootSmokeSquare).rotate().color(), + "smokeSquareBig", new EffectEntry(Fx.shootSmokeSquareBig).rotate().color(), + "spark", new EffectEntry(Fx.hitLaserBlast).color(), + "sparkBig", new EffectEntry(Fx.circleColorSpark).color(), + "sparkShoot", new EffectEntry(Fx.colorSpark).rotate().color(), + "sparkShootBig", new EffectEntry(Fx.randLifeSpark).rotate().color(), + "drill", new EffectEntry(Fx.mine).color(), + "drillBig", new EffectEntry(Fx.mineHuge).color(), + "lightBlock", new EffectEntry(Fx.lightBlock).size().color(), + "explosion", new EffectEntry(Fx.dynamicExplosion).size(), + "smokePuff", new EffectEntry(Fx.smokePuff).color(), + "sparkExplosion", new EffectEntry(Fx.titanExplosion).color(), + "crossExplosion", new EffectEntry(Fx.dynamicSpikes).size().color(), + "wave", new EffectEntry(Fx.dynamicWave).size(), + "bubble", new EffectEntry(Fx.airBubble) + ); + + map.each((n, e) -> e.name = n); + } + + public static Iterable entries(){ + return map.orderedKeys().map(s -> map.get(s)); + } + + public static @Nullable EffectEntry get(String name){ + return map.get(name); + } + + public static String[] all(){ + return map.orderedKeys().toArray(String.class); + } + + public static class EffectEntry{ + public String name = ""; + public Effect effect; + public boolean size, rotate, color; + public @Nullable Class data; + /** cached bounds for this effect, negative if unset */ + public float bounds = -1f; + + public EffectEntry(Effect effect){ + this.effect = effect; + } + + public EffectEntry bounds(float bounds){ + this.bounds = bounds; + return this; + } + + public EffectEntry name(String name){ + this.name = name; + return this; + } + + public EffectEntry size(){ + size = true; + return this; + } + + public EffectEntry rotate(){ + rotate = true; + return this; + } + + public EffectEntry color(){ + color = true; + return this; + } + + public EffectEntry data(Class data){ + this.data = data; + return this; + } + } +} diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index ee2e01a4de..e03fee7f4f 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -936,6 +936,8 @@ public class UnitType extends UnlockableContent{ public void createIcons(MultiPacker packer){ super.createIcons(packer); + if(constructor == null) throw new IllegalArgumentException("No constructor set up for unit '" + name + "'. Make sure you assign a valid value to `constructor`, e.g. `constructor = UnitEntity::new`"); + sample = constructor.get(); var toOutline = new Seq(); diff --git a/core/src/mindustry/type/Weapon.java b/core/src/mindustry/type/Weapon.java index 3841ad2cc9..0b25fd66f1 100644 --- a/core/src/mindustry/type/Weapon.java +++ b/core/src/mindustry/type/Weapon.java @@ -38,7 +38,7 @@ public class Weapon implements Cloneable{ public boolean useAmmo = true; /** whether to create a flipped copy of this weapon upon initialization. default: true */ public boolean mirror = true; - /** whether to flip the weapon's sprite when rendering */ + /** whether to flip the weapon's sprite when rendering. internal use only - do not set! */ public boolean flipSprite = false; /** whether to shoot the weapons in different arms one after another, rather than all at once; only valid when mirror = true */ public boolean alternate = true; diff --git a/core/src/mindustry/ui/BorderImage.java b/core/src/mindustry/ui/BorderImage.java index 4cbdda9ba3..ca0a1a270b 100644 --- a/core/src/mindustry/ui/BorderImage.java +++ b/core/src/mindustry/ui/BorderImage.java @@ -2,6 +2,7 @@ package mindustry.ui; import arc.graphics.*; import arc.graphics.g2d.*; +import arc.scene.style.*; import arc.scene.ui.*; import arc.scene.ui.layout.*; import mindustry.graphics.*; @@ -28,6 +29,10 @@ public class BorderImage extends Image{ thickness = thick; } + public BorderImage(Drawable region){ + super(region); + } + public BorderImage border(Color color){ this.borderColor = color; return this; diff --git a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java index 273855a0d7..8f2a88dd1f 100644 --- a/core/src/mindustry/ui/dialogs/CustomRulesDialog.java +++ b/core/src/mindustry/ui/dialogs/CustomRulesDialog.java @@ -185,13 +185,6 @@ public class CustomRulesDialog extends BaseDialog{ check("@rules.hidebannedblocks", b -> rules.hideBannedBlocks = b, () -> rules.hideBannedBlocks); check("@bannedblocks.whitelist", b -> rules.blockWhitelist = b, () -> rules.blockWhitelist); - //TODO objectives would be nice - if(experimental && false){ - main.button("@objectives", () -> { - - }).left().width(300f).row(); - } - title("@rules.title.unit"); check("@rules.unitcapvariable", b -> rules.unitCapVariable = b, () -> rules.unitCapVariable); numberi("@rules.unitcap", f -> rules.unitCap = f, () -> rules.unitCap, -999, 999); diff --git a/core/src/mindustry/ui/dialogs/EffectsDialog.java b/core/src/mindustry/ui/dialogs/EffectsDialog.java new file mode 100644 index 0000000000..98db4ea91a --- /dev/null +++ b/core/src/mindustry/ui/dialogs/EffectsDialog.java @@ -0,0 +1,235 @@ +package mindustry.ui.dialogs; + +import arc.*; +import arc.func.*; +import arc.graphics.*; +import arc.graphics.g2d.*; +import arc.math.*; +import arc.scene.*; +import arc.scene.event.*; +import arc.scene.ui.*; +import arc.scene.ui.layout.*; +import arc.struct.*; +import arc.util.*; +import mindustry.content.*; +import mindustry.entities.*; +import mindustry.gen.*; +import mindustry.logic.*; +import mindustry.logic.LogicFx.*; +import mindustry.ui.*; +import mindustry.world.*; + +public class EffectsDialog extends BaseDialog{ + static BoundsBatch bounds = new BoundsBatch(); + + Iterable entries; + @Nullable Cons listener; + + public EffectsDialog(Iterable entries){ + super("Effects"); + + this.entries = entries; + + addCloseButton(); + makeButtonOverlay(); + onResize(this::setup); + shown(this::setup); + + setup(); + } + + public EffectsDialog(){ + this(LogicFx.entries()); + } + + public static EffectsDialog withAllEffects(){ + return new EffectsDialog(Seq.select(Fx.class.getFields(), f -> f.getType() == Effect.class).map(f -> new EffectEntry(Reflect.get(f)).name(f.getName()))); + } + + public Dialog show(Cons listener){ + this.listener = listener; + return super.show(); + } + + @Override + public Dialog show(){ + this.listener = null; + return super.show(); + } + + void setup(){ + float size = 280f; + int cols = (int)Math.max(1, Core.graphics.getWidth() / Scl.scl(size + 12f)); + + cont.clearChildren(); + cont.pane(t -> { + int i = 0; + for(var entry : entries){ + float bounds = calculateSize(entry); + + if(bounds <= 0) continue; + + ClickListener cl = new ClickListener(); + + t.stack( + new EffectCell(entry, cl), + new Table(af -> af.add(entry.name).grow().labelAlign(Align.bottomLeft).style(Styles.outlineLabel).bottom().left()) + ).size(size).with(a -> { + a.clicked(() -> { + if(listener != null){ + listener.get(entry); + hide(); + } + }); + a.addListener(cl); + a.addListener(new HandCursorListener(() -> listener != null, true)); + }); + + if(++i % cols == 0) t.row(); + } + }).grow().scrollX(false); + } + + static Object getData(Class type){ + if(type == Block.class) return Blocks.router; + return null; + } + + static float calculateSize(EffectEntry entry){ + if(entry.bounds >= 0) return entry.bounds; + + + var effect = entry.effect; + try{ + effect.init(); + Batch prev = Core.batch; + bounds.reset(); + Core.batch = bounds; + Object data = getData(entry.data); + + float lifetime = effect.lifetime; + float rot = 1f; + int steps = 60; + int seeds = 4; + for(int s = 0; s < seeds; s++){ + for(int i = 0; i <= steps; i++){ + effect.render(1, Color.white, i / (float)steps * lifetime, lifetime, rot, 0f, 0f, data); + } + } + + Core.batch = prev; + + return entry.bounds = bounds.max * 2f; + }catch(Exception e){ + //might crash with invalid data + return -1f; + } + } + + static class BoundsBatch extends Batch{ + float max; + + void reset(){ + max = 0f; + } + + void max(float... xs){ + for(float f : xs){ + if(Float.isNaN(f)) continue; + max = Math.max(max, Math.abs(f)); + } + } + + @Override + protected void draw(Texture texture, float[] spriteVertices, int offset, int count){ + for(int i = offset; i < count; i += SpriteBatch.VERTEX_SIZE){ + max(spriteVertices[i], spriteVertices[i + 1]); + } + } + + @Override + protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){ + float worldOriginX = x + originX; + float worldOriginY = y + originY; + float fx = -originX; + float fy = -originY; + float fx2 = width - originX; + float fy2 = height - originY; + float cos = Mathf.cosDeg(rotation); + float sin = Mathf.sinDeg(rotation); + float x1 = cos * fx - sin * fy + worldOriginX; + float y1 = sin * fx + cos * fy + worldOriginY; + float x2 = cos * fx - sin * fy2 + worldOriginX; + float y2 = sin * fx + cos * fy2 + worldOriginY; + float x3 = cos * fx2 - sin * fy2 + worldOriginX; + float y3 = sin * fx2 + cos * fy2 + worldOriginY; + + max(x1, y1, x2, y2, x3, y3, x1 + (x3 - x2), y3 - (y2 - y1)); + } + + @Override + protected void flush(){} + } + + class EffectCell extends Element{ + EffectEntry effect; + float size = -1f; + + int id = 1; + float time = 0f; + float lifetime; + float rotation = 1f; + Object data; + ClickListener cl; + + public EffectCell(EffectEntry effect, ClickListener cl){ + this.effect = effect; + this.lifetime = effect.effect.lifetime; + this.cl = cl; + + data = getData(effect.data); + } + + @Override + public void draw(){ + if(size < 0){ + size = calculateSize(effect) + 1f; + } + + color.fromHsv((Time.globalTime * 2f) % 360f, 1f, 1f); + + if(clipBegin(x, y, width, height)){ + Draw.colorl(cl.isOver() && listener != null ? 0.4f : 0.5f); + Draw.alpha(parentAlpha); + Tex.alphaBg.draw(x, y, width, height); + Draw.reset(); + Draw.flush(); + + float scale = width / size; + Tmp.m1.set(Draw.trans()); + Draw.trans().translate(x + width/2f, y + height/2f).scale(scale, scale); + Draw.flush(); + this.lifetime = effect.effect.render(id, color, time, lifetime, rotation, 0f, 0f, data); + + Draw.flush(); + Draw.trans().set(Tmp.m1); + clipEnd(); + } + + Lines.stroke(Scl.scl(3f), Color.black); + Lines.rect(x, y, width, height); + Draw.reset(); + } + + @Override + public void act(float delta){ + super.act(delta); + + time += Time.delta; + if(time >= lifetime){ + id ++; + } + time %= lifetime; + } + } +} diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index b694f51db8..fb151dfde5 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -1370,7 +1370,7 @@ public class Block extends UnlockableContent implements Senseable{ return switch(sensor){ case color -> mapColor.toDoubleBits(); case health, maxHealth -> health; - case size -> size * tilesize; + case size -> size; case itemCapacity -> itemCapacity; case liquidCapacity -> liquidCapacity; case powerCapacity -> consPower != null && consPower.buffered ? consPower.capacity : 0f; diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index cc60ec4f6c..a719992935 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -37,6 +37,8 @@ public class CoreBlock extends StorageBlock{ public @Load(value = "@-thruster2", fallback = "clear-effect") TextureRegion thruster2; //bot left public float thrusterLength = 14f/4f; public boolean isFirstTier; + /** If true, this core type requires a core zone to upgrade. */ + public boolean requiresCoreZone; public boolean incinerateNonBuildable = false; public UnitType unitType = UnitTypes.alpha; @@ -60,8 +62,6 @@ public class CoreBlock extends StorageBlock{ //support everything replaceable = false; - //TODO should AI ever rebuild this? - //rebuildable = false; } @Remote(called = Loc.server) @@ -160,7 +160,7 @@ public class CoreBlock extends StorageBlock{ //must have all requirements if(core == null || (!state.rules.infiniteResources && !core.items.has(requirements, state.rules.buildCostMultiplier))) return false; - return tile.block() instanceof CoreBlock && size > tile.block().size; + return tile.block() instanceof CoreBlock && size > tile.block().size && (!requiresCoreZone || tempTiles.allMatch(o -> o.floor().allowCorePlacement)); } @Override diff --git a/core/src/mindustry/world/blocks/units/Reconstructor.java b/core/src/mindustry/world/blocks/units/Reconstructor.java index 77ddf0ae0d..68a3f28932 100644 --- a/core/src/mindustry/world/blocks/units/Reconstructor.java +++ b/core/src/mindustry/world/blocks/units/Reconstructor.java @@ -239,7 +239,7 @@ public class Reconstructor extends UnitBlock{ @Override public int getMaximumAccepted(Item item){ - return capacities[item.id]; + return Mathf.round(capacities[item.id] * state.rules.unitCost(team)); } @Override