package mindustry.world; import arc.*; import mindustry.annotations.Annotations.*; import arc.Graphics.*; import arc.Graphics.Cursor.*; import arc.audio.*; import arc.struct.EnumSet; import arc.struct.*; import arc.func.*; import arc.graphics.*; import arc.graphics.g2d.*; import arc.graphics.g2d.TextureAtlas.*; import arc.math.*; import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.util.*; import arc.util.ArcAnnotate.*; import arc.util.pooling.*; import mindustry.ctype.*; import mindustry.ctype.ContentType; import mindustry.entities.*; import mindustry.entities.effect.*; import mindustry.entities.traits.BuilderTrait.*; import mindustry.entities.type.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.graphics.MultiPacker.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.blocks.*; import mindustry.world.blocks.power.*; import mindustry.world.consumers.*; import mindustry.world.meta.*; import mindustry.world.meta.values.*; import java.util.*; import static mindustry.Vars.*; public class Block extends BlockStorage{ public static final int crackRegions = 8, maxCrackSize = 5; /** whether this block has a tile entity that updates */ public boolean update; /** whether this block has health and can be destroyed */ public boolean destructible; /** whether unloaders work on this block*/ public boolean unloadable = true; /** whether this is solid */ public boolean solid; /** whether this block CAN be solid. */ public boolean solidifes; /** whether this is rotateable */ public boolean rotate; /** whether you can break this with rightclick */ public boolean breakable; /** whether to add this block to brokenblocks */ public boolean rebuildable = true; /** whether this floor can be placed on. */ public boolean placeableOn = true; /** whether this block has insulating properties. */ public boolean insulated = false; /** tile entity health */ public int health = -1; /** base block explosiveness */ public float baseExplosiveness = 0f; /** whether this block can be placed on edges of liquids. */ public boolean floating = false; /** multiblock size */ public int size = 1; /** Whether to draw this block in the expanded draw range. */ public boolean expanded = false; /** Max of timers used. */ public int timers = 0; /** Cache layer. Only used for 'cached' rendering. */ public CacheLayer cacheLayer = CacheLayer.normal; /** Special flag; if false, floor will be drawn under this block even if it is cached. */ public boolean fillsTile = true; /** Layer to draw extra stuff on. */ public Layer layer = null; /** Extra layer to draw extra extra stuff on. */ public Layer layer2 = null; /** whether this block can be replaced in all cases */ public boolean alwaysReplace = false; /** The block group. Unless {@link #canReplace} is overriden, blocks in the same group can replace each other. */ public BlockGroup group = BlockGroup.none; /** List of block flags. Used for AI indexing. */ public EnumSet flags = EnumSet.of(); /** Targeting priority of this block, as seen by enemies.*/ public TargetPriority priority = TargetPriority.base; /** Whether the block can be tapped and selected to configure. */ public boolean configurable; /** Whether this block consumes touchDown events when tapped. */ public boolean consumesTap; /** Whether to draw the glow of the liquid for this block, if it has one. */ public boolean drawLiquidLight = true; /** Whether the config is positional and needs to be shifted. */ public boolean posConfig; /** Whether to periodically sync this block across the network.*/ public boolean sync; /** Whether this block uses conveyor-type placement mode.*/ public boolean conveyorPlacement; /** * The color of this block when displayed on the minimap or map preview. * Do not set manually! This is overriden when loading for most blocks. */ public Color color = new Color(0, 0, 0, 1); /** Whether units target this block. */ public boolean targetable = true; /** Whether the overdrive core has any effect on this block. */ public boolean canOverdrive = true; /** Outlined icon color.*/ public Color outlineColor = Color.valueOf("404049"); /** Whether the icon region has an outline added. */ public boolean outlineIcon = false; /** Whether this block has a shadow under it. */ public boolean hasShadow = true; /** Sounds made when this block breaks.*/ public Sound breakSound = Sounds.boom; /** The sound that this block makes while active. One sound loop. Do not overuse.*/ public Sound activeSound = Sounds.none; /** Active sound base volume. */ public float activeSoundVolume = 0.5f; /** The sound that this block makes while idle. Uses one sound loop for all blocks.*/ public Sound idleSound = Sounds.none; /** Idle sound base volume. */ public float idleSoundVolume = 0.5f; /** Cost of constructing this block. */ public ItemStack[] requirements = {}; /** Category in place menu. */ public Category category = Category.distribution; /** Cost of building this block; do not modify directly! */ public float buildCost; /** Whether this block is visible and can currently be built. */ public BuildVisibility buildVisibility = BuildVisibility.hidden; /** Multiplier for speed of building this block. */ public float buildCostMultiplier = 1f; /** Whether this block has instant transfer.*/ public boolean instantTransfer = false; public boolean alwaysUnlocked = false; protected TextureRegion[] cacheRegions = {}; protected Array cacheRegionStrings = new Array<>(); protected Prov entityType = TileEntity::new; protected Array tempTiles = new Array<>(); protected TextureRegion[] generatedIcons; protected TextureRegion[] variantRegions, editorVariantRegions; protected TextureRegion region, editorIcon; protected static TextureRegion[][] cracks; /** Dump timer ID.*/ protected final int timerDump = timers++; /** How often to try dumping items in ticks, e.g. 5 = 12 times/sec*/ protected final int dumpTime = 5; public Block(String name){ super(name); this.solid = false; } public boolean canBreak(Tile tile){ return true; } public boolean isBuildable(){ return buildVisibility != BuildVisibility.hidden && buildVisibility != BuildVisibility.debugOnly; } public boolean isStatic(){ return cacheLayer == CacheLayer.walls; } public void onProximityRemoved(Tile tile){ if(tile.entity.power != null){ tile.block().powerGraphRemoved(tile); } } public void onProximityAdded(Tile tile){ if(tile.block().hasPower) tile.block().updatePowerGraph(tile); } protected void updatePowerGraph(Tile tile){ TileEntity entity = tile.ent(); for(Tile other : getPowerConnections(tile, tempTiles)){ if(other.entity.power != null){ other.entity.power.graph.add(entity.power.graph); } } } protected void powerGraphRemoved(Tile tile){ if(tile.entity == null || tile.entity.power == null){ return; } tile.entity.power.graph.remove(tile); for(int i = 0; i < tile.entity.power.links.size; i++){ Tile other = world.tile(tile.entity.power.links.get(i)); if(other != null && other.entity != null && other.entity.power != null){ other.entity.power.links.removeValue(tile.pos()); } } } public Array getPowerConnections(Tile tile, Array out){ out.clear(); if(tile == null || tile.entity == null || tile.entity.power == null) return out; for(Tile other : tile.entity.proximity()){ if(other != null && other.entity != null && other.entity.power != null && !(consumesPower && other.block().consumesPower && !outputsPower && !other.block().outputsPower) && !tile.entity.power.links.contains(other.pos())){ out.add(other); } } for(int i = 0; i < tile.entity.power.links.size; i++){ Tile link = world.tile(tile.entity.power.links.get(i)); if(link != null && link.entity != null && link.entity.power != null) out.add(link); } return out; } protected float getProgressIncrease(TileEntity entity, float baseTime){ return 1f / baseTime * entity.delta() * entity.efficiency(); } /** @return whether this block should play its active sound.*/ public boolean shouldActiveSound(Tile tile){ return false; } /** @return whether this block should play its idle sound.*/ public boolean shouldIdleSound(Tile tile){ return shouldConsume(tile); } public void drawLayer(Tile tile){ } public void drawLayer2(Tile tile){ } public void drawCracks(Tile tile){ if(!tile.entity.damaged() || size > maxCrackSize) return; int id = tile.pos(); TextureRegion region = cracks[size - 1][Mathf.clamp((int)((1f - tile.entity.healthf()) * crackRegions), 0, crackRegions-1)]; Draw.colorl(0.2f, 0.1f + (1f - tile.entity.healthf())* 0.6f); Draw.rect(region, tile.drawx(), tile.drawy(), (id%4)*90); Draw.color(); } /** Draw the block overlay that is shown when a cursor is over the block. */ public void drawSelect(Tile tile){ } /** Drawn when you are placing a block. */ public void drawPlace(int x, int y, int rotation, boolean valid){ } public float drawPlaceText(String text, int x, int y, boolean valid){ if(renderer.pixelator.enabled()) return 0; Color color = valid ? Pal.accent : Pal.remove; BitmapFont font = Fonts.outline; GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new); boolean ints = font.usesIntegerPositions(); font.setUseIntegerPositions(false); font.getData().setScale(1f / 4f / Scl.scl(1f)); layout.setText(font, text); float width = layout.width; font.setColor(color); float dx = x * tilesize + offset(), dy = y * tilesize + offset() + size * tilesize / 2f + 3; font.draw(text, dx, dy + layout.height + 1, Align.center); dy -= 1f; Lines.stroke(2f, Color.darkGray); Lines.line(dx - layout.width / 2f - 2f, dy, dx + layout.width / 2f + 1.5f, dy); Lines.stroke(1f, color); Lines.line(dx - layout.width / 2f - 2f, dy, dx + layout.width / 2f + 1.5f, dy); font.setUseIntegerPositions(ints); font.setColor(Color.white); font.getData().setScale(1f); Draw.reset(); Pools.free(layout); return width; } public void draw(Tile tile){ Draw.rect(region, tile.drawx(), tile.drawy(), rotate ? tile.rotation() * 90 : 0); } public void drawLight(Tile tile){ if(tile.entity != null && hasLiquids && drawLiquidLight && tile.entity.liquids.current().lightColor.a > 0.001f){ drawLiquidLight(tile, tile.entity.liquids.current(), tile.entity.liquids.smoothAmount()); } } public void drawLiquidLight(Tile tile, Liquid liquid, float amount){ if(amount > 0.01f){ Color color = liquid.lightColor; float fract = 1f; float opacity = color.a * fract; if(opacity > 0.001f){ renderer.lights.add(tile.drawx(), tile.drawy(), size * 30f * fract, color, opacity); } } } public void drawTeam(Tile tile){ Draw.color(tile.getTeam().color); Draw.rect("block-border", tile.drawx() - size * tilesize / 2f + 4, tile.drawy() - size * tilesize / 2f + 4); Draw.color(); } /** Called after the block is placed by this client. */ @CallSuper public void playerPlaced(Tile tile){ } /** Called after the block is placed by anyone. */ @CallSuper public void placed(Tile tile){ if(net.client()) return; if((consumesPower && !outputsPower) || (!consumesPower && outputsPower)){ int range = 10; tempTiles.clear(); Geometry.circle(tile.x, tile.y, range, (x, y) -> { Tile other = world.ltile(x, y); if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, tile) && !PowerNode.insulated(other, tile) && !other.entity.proximity().contains(tile) && !(outputsPower && tile.entity.proximity().contains(p -> p.entity != null && p.entity.power != null && p.entity.power.graph == other.entity.power.graph))){ tempTiles.add(other); } }); tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile))); if(!tempTiles.isEmpty()){ Tile toLink = tempTiles.first(); if(!toLink.entity.power.links.contains(tile.pos())){ toLink.configureAny(tile.pos()); } } } } public void removed(Tile tile){ } /** Called every frame a unit is on this tile. */ public void unitOn(Tile tile, Unit unit){ } /** Called when a unit that spawned at this tile is removed. */ public void unitRemoved(Tile tile, Unit unit){ } /** Returns whether ot not this block can be place on the specified tile. */ public boolean canPlaceOn(Tile tile){ return true; } /** Call when some content is produced. This unlocks the content if it is applicable. */ public void useContent(Tile tile, UnlockableContent content){ //only unlocks content in zones if(!headless && tile.getTeam() == player.getTeam() && world.isZone()){ logic.handleContent(content); } } public float sumAttribute(Attribute attr, int x, int y){ Tile tile = world.tile(x, y); if(tile == null) return 0; float sum = 0; for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){ sum += other.floor().attributes.get(attr); } return sum; } public float percentSolid(int x, int y){ Tile tile = world.tile(x, y); if(tile == null) return 0; float sum = 0; for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){ sum += !other.floor.isLiquid ? 1f : 0f; } return sum / size / size; } @Override public void displayInfo(Table table){ ContentDisplay.displayBlock(table, this); } @Override public ContentType getContentType(){ return ContentType.block; } /** Called after all blocks are created. */ @Override @CallSuper public void init(){ //initialize default health based on size if(health == -1){ health = size * size * 40; } buildCost = 0f; for(ItemStack stack : requirements){ buildCost += stack.amount * stack.item.cost; } buildCost *= buildCostMultiplier; if(consumes.has(ConsumeType.power)) hasPower = true; if(consumes.has(ConsumeType.item)) hasItems = true; if(consumes.has(ConsumeType.liquid)) hasLiquids = true; setStats(); setBars(); consumes.init(); if(!outputsPower && consumes.hasPower() && consumes.getPower().buffered){ throw new IllegalArgumentException("Consumer using buffered power: " + name); } } @Override public void load(){ region = Core.atlas.find(name); cacheRegions = new TextureRegion[cacheRegionStrings.size]; for(int i = 0; i < cacheRegions.length; i++){ cacheRegions[i] = Core.atlas.find(cacheRegionStrings.get(i)); } if(cracks == null || (cracks[0][0].getTexture() != null && cracks[0][0].getTexture().isDisposed())){ cracks = new TextureRegion[maxCrackSize][crackRegions]; for(int size = 1; size <= maxCrackSize; size++){ for(int i = 0; i < crackRegions; i++){ cracks[size - 1][i] = Core.atlas.find("cracks-" + size + "-" + i); } } } } /** Adds a region by name to be loaded, with the final name "{name}-suffix". Returns an ID to looks this region up by in {@link #reg(int)}. */ protected int reg(String suffix){ cacheRegionStrings.add(name + suffix); return cacheRegionStrings.size - 1; } /** Returns an internally cached region by ID. */ protected TextureRegion reg(int id){ return cacheRegions[id]; } /** Called when the block is tapped. */ public void tapped(Tile tile, Player player){ } /** Called when arbitrary configuration is applied to a tile. */ public void configured(Tile tile, @Nullable Player player, int value){ } /** Returns whether or not a hand cursor should be shown over this block. */ public Cursor getCursor(Tile tile){ return configurable ? SystemCursor.hand : SystemCursor.arrow; } /** * Called when this block is tapped to build a UI on the table. * {@link #configurable} must return true for this to be called. */ public void buildConfiguration(Tile tile, Table table){ } /** Update table alignment after configuring.*/ public void updateTableAlign(Tile tile, Table table){ Vec2 pos = Core.input.mouseScreen(tile.drawx(), tile.drawy() - tile.block().size * tilesize / 2f - 1); table.setPosition(pos.x, pos.y, Align.top); } /** * Called when another tile is tapped while this block is selected. * Returns whether or not this block should be deselected. */ public boolean onConfigureTileTapped(Tile tile, Tile other){ return tile != other; } /** Returns whether this config menu should show when the specified player taps it. */ public boolean shouldShowConfigure(Tile tile, Player player){ return true; } /** Whether this configuration should be hidden now. Called every frame the config is open. */ public boolean shouldHideConfigure(Tile tile, Player player){ return false; } public boolean synthetic(){ return update || destructible; } public void drawConfigure(Tile tile){ Draw.color(Pal.accent); Lines.stroke(1f); Lines.square(tile.drawx(), tile.drawy(), tile.block().size * tilesize / 2f + 1f); Draw.reset(); } public void setStats(){ stats.add(BlockStat.size, "{0}x{0}", size); stats.add(BlockStat.health, health, StatUnit.none); if(isBuildable()){ stats.add(BlockStat.buildTime, buildCost / 60, StatUnit.seconds); stats.add(BlockStat.buildCost, new ItemListValue(false, requirements)); } consumes.display(stats); // Note: Power stats are added by the consumers. if(hasLiquids) stats.add(BlockStat.liquidCapacity, liquidCapacity, StatUnit.liquidUnits); if(hasItems) stats.add(BlockStat.itemCapacity, itemCapacity, StatUnit.items); } public void setBars(){ bars.add("health", entity -> new Bar("blocks.health", Pal.health, entity::healthf).blink(Color.white)); if(hasLiquids){ Func current; if(consumes.has(ConsumeType.liquid) && consumes.get(ConsumeType.liquid) instanceof ConsumeLiquid){ Liquid liquid = consumes.get(ConsumeType.liquid).liquid; current = entity -> liquid; }else{ current = entity -> entity.liquids.current(); } bars.add("liquid", entity -> new Bar(() -> entity.liquids.get(current.get(entity)) <= 0.001f ? Core.bundle.get("bar.liquid") : current.get(entity).localizedName, () -> current.get(entity).barColor(), () -> entity.liquids.get(current.get(entity)) / liquidCapacity)); } if(hasPower && consumes.hasPower()){ ConsumePower cons = consumes.getPower(); boolean buffered = cons.buffered; float capacity = cons.capacity; bars.add("power", entity -> new Bar(() -> buffered ? Core.bundle.format("bar.poweramount", Float.isNaN(entity.power.status * capacity) ? "" : (int)(entity.power.status * capacity)) : Core.bundle.get("bar.power"), () -> Pal.powerBar, () -> Mathf.zero(cons.requestedPower(entity)) && entity.power.graph.getPowerProduced() + entity.power.graph.getBatteryStored() > 0f ? 1f : entity.power.status)); } if(hasItems && configurable){ bars.add("items", entity -> new Bar(() -> Core.bundle.format("bar.items", entity.items.total()), () -> Pal.items, () -> (float)entity.items.total() / itemCapacity)); } } public Tile linked(Tile tile){ return tile; } public boolean isSolidFor(Tile tile){ return false; } public boolean canReplace(Block other){ return (other != this || rotate) && this.group != BlockGroup.none && other.group == this.group; } /** @return a possible replacement for this block when placed in a line by the player. */ public Block getReplacement(BuildRequest req, Array requests){ return this; } public float handleDamage(Tile tile, float amount){ return amount; } public void handleBulletHit(TileEntity entity, Bullet bullet){ entity.damage(bullet.damage()); } public void update(Tile tile){ } public boolean isAccessible(){ return (hasItems && itemCapacity > 0); } /** Called when the block is destroyed. */ public void onDestroyed(Tile tile){ float x = tile.worldx(), y = tile.worldy(); float explosiveness = baseExplosiveness; float flammability = 0f; float power = 0f; if(hasItems){ for(Item item : content.items()){ int amount = tile.entity.items.get(item); explosiveness += item.explosiveness * amount; flammability += item.flammability * amount; } } if(hasLiquids){ flammability += tile.entity.liquids.sum((liquid, amount) -> liquid.explosiveness * amount / 2f); explosiveness += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount / 2f); } if(consumes.hasPower() && consumes.getPower().buffered){ power += tile.entity.power.status * consumes.getPower().capacity; } if(hasLiquids){ tile.entity.liquids.each((liquid, amount) -> { float splash = Mathf.clamp(amount / 4f, 0f, 10f); for(int i = 0; i < Mathf.clamp(amount / 5, 0, 30); i++){ Time.run(i / 2f, () -> { Tile other = world.tile(tile.x + Mathf.range(size / 2), tile.y + Mathf.range(size / 2)); if(other != null){ Puddle.deposit(other, liquid, splash); } }); } }); } Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * size / 2f, Pal.darkFlame); if(!tile.floor().solid && !tile.floor().isLiquid){ RubbleDecal.create(tile.drawx(), tile.drawy(), size); } } /** * Returns the flammability of the tile. Used for fire calculations. * Takes flammability of floor liquid into account. */ public float getFlammability(Tile tile){ if(!hasItems || tile.entity == null){ if(tile.floor().isLiquid && !solid){ return tile.floor().liquidDrop.flammability; } return 0; }else{ float result = tile.entity.items.sum((item, amount) -> item.flammability * amount); if(hasLiquids){ result += tile.entity.liquids.sum((liquid, amount) -> liquid.flammability * amount / 3f); } return result; } } public String getDisplayName(Tile tile){ return localizedName; } public TextureRegion getDisplayIcon(Tile tile){ return icon(Cicon.medium); } public void display(Tile tile, Table table){ TileEntity entity = tile.entity; if(entity != null){ table.table(bars -> { bars.defaults().growX().height(18f).pad(4); displayBars(tile, bars); }).growX(); table.row(); table.table(ctable -> { displayConsumption(tile, ctable); }).growX(); table.marginBottom(-5); } } public void displayConsumption(Tile tile, Table table){ table.left(); for(Consume cons : consumes.all()){ if(cons.isOptional() && cons.isBoost()) continue; cons.build(tile, table); } } public void displayBars(Tile tile, Table table){ for(Func bar : bars.list()){ table.add(bar.get(tile.entity)).growX(); table.row(); } } public void drawRequest(BuildRequest req, Eachable list, boolean valid){ Draw.reset(); Draw.mixcol(!valid ? Pal.breakInvalid : Color.white, (!valid ? 0.4f : 0.24f) + Mathf.absin(Time.globalTime(), 6f, 0.28f)); Draw.alpha(1f); drawRequestRegion(req, list); Draw.reset(); } public void drawRequestRegion(BuildRequest req, Eachable list){ TextureRegion reg = icon(Cicon.full); Draw.rect(icon(Cicon.full), req.drawx(), req.drawy(), reg.getWidth() * req.animScale * Draw.scl, reg.getHeight() * req.animScale * Draw.scl, !rotate ? 0 : req.rotation * 90); if(req.hasConfig){ drawRequestConfig(req, list); } } public void drawRequestConfig(BuildRequest req, Eachable list){ } public void drawRequestConfigCenter(BuildRequest req, Content content, String region){ Color color = content instanceof Item ? ((Item)content).color : content instanceof Liquid ? ((Liquid)content).color : null; if(color == null) return; float prev = Draw.scl; Draw.color(color); Draw.scl *= req.animScale; Draw.rect(region, req.drawx(), req.drawy()); Draw.scl = prev; Draw.color(); } /** @return a custom minimap color for this tile, or 0 to use default colors. */ public int minimapColor(Tile tile){ return 0; } public void drawRequestConfigTop(BuildRequest req, Eachable list){ } @Override public void createIcons(MultiPacker packer){ super.createIcons(packer); packer.add(PageType.editor, name + "-icon-editor", Core.atlas.getPixmap((AtlasRegion)icon(Cicon.full))); if(!synthetic()){ PixmapRegion image = Core.atlas.getPixmap((AtlasRegion)icon(Cicon.full)); color.set(image.getPixel(image.width/2, image.height/2)); } getGeneratedIcons(); Pixmap last = null; if(outlineIcon){ final int radius = 4; PixmapRegion region = Core.atlas.getPixmap(getGeneratedIcons()[getGeneratedIcons().length-1]); Pixmap out = new Pixmap(region.width, region.height); Color color = new Color(); for(int x = 0; x < region.width; x++){ for(int y = 0; y < region.height; y++){ region.getPixel(x, y, color); out.draw(x, y, color); if(color.a < 1f){ boolean found = false; outer: for(int rx = -radius; rx <= radius; rx++){ for(int ry = -radius; ry <= radius; ry++){ if(Structs.inBounds(rx + x, ry + y, region.width, region.height) && Mathf.dst2(rx, ry) <= radius*radius && color.set(region.getPixel(rx + x, ry + y)).a > 0.01f){ found = true; break outer; } } } if(found){ out.draw(x, y, outlineColor); } } } } last = out; packer.add(PageType.main, name, out); } if(generatedIcons.length > 1){ Pixmap base = Core.atlas.getPixmap(generatedIcons[0]).crop(); for(int i = 1; i < generatedIcons.length; i++){ if(i == generatedIcons.length - 1 && last != null){ base.drawPixmap(last); }else{ base.draw(Core.atlas.getPixmap(generatedIcons[i])); } } packer.add(PageType.main, "block-" + name + "-full", base); generatedIcons = null; Arrays.fill(cicons, null); } } /** Never use outside of the editor! */ public TextureRegion editorIcon(){ if(editorIcon == null) editorIcon = Core.atlas.find(name + "-icon-editor"); return editorIcon; } /** Never use outside of the editor! */ public TextureRegion[] editorVariantRegions(){ if(editorVariantRegions == null){ variantRegions(); editorVariantRegions = new TextureRegion[variantRegions.length]; for(int i = 0; i < variantRegions.length; i++){ AtlasRegion region = (AtlasRegion)variantRegions[i]; editorVariantRegions[i] = Core.atlas.find("editor-" + region.name); } } return editorVariantRegions; } protected TextureRegion[] generateIcons(){ return new TextureRegion[]{Core.atlas.find(name)}; } public TextureRegion[] getGeneratedIcons(){ if(generatedIcons == null){ generatedIcons = generateIcons(); } return generatedIcons; } public TextureRegion[] variantRegions(){ if(variantRegions == null){ variantRegions = new TextureRegion[]{icon(Cicon.full)}; } return variantRegions; } public boolean hasEntity(){ return destructible || update; } public final TileEntity newEntity(){ return entityType.get(); } /** Offset for placing and drawing multiblocks. */ public float offset(){ return ((size + 1) % 2) * tilesize / 2f; } public Rect bounds(int x, int y, Rect rect){ return rect.setSize(size * tilesize).setCenter(x * tilesize + offset(), y * tilesize + offset()); } public boolean isMultiblock(){ return size > 1; } public boolean isVisible(){ return buildVisibility.visible() && !isHidden(); } public boolean isFloor(){ return this instanceof Floor; } public boolean isOverlay(){ return this instanceof OverlayFloor; } public Floor asFloor(){ return (Floor)this; } @Override public boolean isHidden(){ return !buildVisibility.visible(); } @Override public boolean alwaysUnlocked(){ return alwaysUnlocked; } protected void requirements(Category cat, ItemStack[] stacks, boolean unlocked){ requirements(cat, BuildVisibility.shown, stacks); this.alwaysUnlocked = unlocked; } protected void requirements(Category cat, ItemStack[] stacks){ requirements(cat, BuildVisibility.shown, stacks); } /** Sets up requirements. Use only this method to set up requirements. */ protected void requirements(Category cat, BuildVisibility visible, ItemStack[] stacks){ this.category = cat; this.requirements = stacks; this.buildVisibility = visible; Arrays.sort(requirements, Structs.comparingInt(i -> i.item.id)); } }