WIP logic particle effect instruction

This commit is contained in:
Anuken 2023-08-02 22:15:44 -04:00
parent 660c38db43
commit 6bf5e8ae1e
19 changed files with 596 additions and 23 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

View File

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

View File

@ -204,6 +204,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
if(limitFps){
nextFrame += (1000 * 1000000) / targetfps;
}else{
nextFrame = Time.nanos();
}
if(!finished){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String, EffectEntry> 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<EffectEntry> 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<EffectEntry> entries;
@Nullable Cons<EffectEntry> listener;
public EffectsDialog(Iterable<EffectEntry> 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<EffectEntry> 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;
}
}
}

View File

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

View File

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

View File

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