Partial 7.0 merge - API preview

This commit is contained in:
Anuken
2021-06-02 11:08:08 -04:00
parent ea75a357ca
commit 28b235ef07
531 changed files with 12356 additions and 6286 deletions

View File

@@ -29,7 +29,6 @@ import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.power.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import java.lang.reflect.*;
import java.util.*;
@@ -45,6 +44,7 @@ public class Block extends UnlockableContent{
public boolean consumesPower = true;
public boolean outputsPower = false;
public boolean outputsPayload = false;
public boolean acceptsPayload = false;
public boolean outputFacing = true;
public boolean acceptsItems = false;
@@ -77,6 +77,8 @@ public class Block extends UnlockableContent{
public boolean solidifes;
/** whether this is rotateable */
public boolean rotate;
/** whether to draw a rotation arrow - this does not apply to lines of blocks */
public boolean drawArrow = true;
/** for static blocks only: if true, tile data() is saved in world data. */
public boolean saveData;
/** whether you can break this with rightclick */
@@ -105,6 +107,8 @@ public class Block extends UnlockableContent{
public boolean noUpdateDisabled = false;
/** Whether to use this block's color in the minimap. Only used for overlays. */
public boolean useColor = true;
/** item that drops from this block, used for drills */
public @Nullable Item itemDrop = null;
/** tile entity health */
public int health = -1;
/** base block explosiveness */
@@ -115,8 +119,12 @@ public class Block extends UnlockableContent{
public int size = 1;
/** multiblock offset */
public float offset = 0f;
/** Whether to draw this block in the expanded draw range. */
/** Deprecated for removal: Use clipSize instead.
* This does nothing, and is kept to preserve compatibility with v6 mods. */
@Deprecated
public boolean expanded = false;
/** Clipping size of this block. Should be as large as the block will draw. */
public float clipSize = -1f;
/** Max of timers used. */
public int timers = 0;
/** Cache layer. Only used for 'cached' rendering. */
@@ -144,6 +152,12 @@ public class Block extends UnlockableContent{
public boolean consumesTap;
/** Whether to draw the glow of the liquid for this block, if it has one. */
public boolean drawLiquidLight = true;
/** Environmental flags that are *all* required for this block to function. 0 = any environment */
public int envRequired = 0;
/** The environment flags that this block can function in. If the env matches any of these, it will be enabled. */
public int envEnabled = Env.terrestrial;
/** The environment flags that this block *cannot* function in. If the env matches any of these, it will be *disabled*. */
public int envDisabled = 0;
/** Whether to periodically sync this block across the network. */
public boolean sync;
/** Whether this block uses conveyor-type placement mode. */
@@ -167,7 +181,9 @@ public class Block extends UnlockableContent{
public Color outlineColor = Color.valueOf("404049");
/** Whether any icon region has an outline added. */
public boolean outlineIcon = false;
/** Which of the icon regions gets the outline added. */
/** Outline icon radius. */
public int outlineRadius = 4;
/** Which of the icon regions gets the outline added. Uses last icon if <= 0. */
public int outlinedIcon = -1;
/** Whether this block has a shadow under it. */
public boolean hasShadow = true;
@@ -179,7 +195,7 @@ public class Block extends UnlockableContent{
public Color lightColor = Color.white.cpy();
/**
* Whether this environmental block passively emits light.
* Not valid for non-environmental blocks. */
* Does not change behavior for non-environmental blocks, but still updates clipSize. */
public boolean emitLight = false;
/** Radius of the light emitted by this block. */
public float lightRadius = 60f;
@@ -198,19 +214,23 @@ public class Block extends UnlockableContent{
public ItemStack[] requirements = {};
/** Category in place menu. */
public Category category = Category.distribution;
/** Cost of building this block; do not modify directly! */
public float buildCost;
/** Time to build this block in ticks; do not modify directly! */
public float buildCost = 20f;
/** 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;
/** Build completion at which deconstruction finishes. */
public float deconstructThreshold = 0f;
/** If true, this block deconstructs immediately. Instant deconstruction implies no resource refund. */
public boolean instantDeconstruct = false;
/** Effect for breaking the block. Passes size as rotation. */
public Effect breakEffect = Fx.breakBlock;
/** Multiplier for cost of research in tech tree. */
public float researchCostMultiplier = 1;
/** Whether this block has instant transfer.*/
public boolean instantTransfer = false;
/** Whether you can rotate this block with Keybind rotateplaced + Scroll Wheel. */
/** Whether you can rotate this block after it is placed. */
public boolean quickRotate = true;
/** Main subclass. Non-anonymous. */
public @Nullable Class<?> subclass;
@@ -318,7 +338,7 @@ public class Block extends UnlockableContent{
}
public TextureRegion getDisplayIcon(Tile tile){
return tile.build == null ? icon(Cicon.medium) : tile.build.getDisplayIcon();
return tile.build == null ? uiIcon : tile.build.getDisplayIcon();
}
public String getDisplayName(Tile tile){
@@ -363,7 +383,7 @@ public class Block extends UnlockableContent{
if(canBeBuilt()){
stats.add(Stat.buildTime, buildCost / 60, StatUnit.seconds);
stats.add(Stat.buildCost, new ItemListValue(false, requirements));
stats.add(Stat.buildCost, StatValues.items(false, requirements));
}
if(instantTransfer){
@@ -405,7 +425,9 @@ public class Block extends UnlockableContent{
bars.add("items", entity -> new Bar(() -> Core.bundle.format("bar.items", entity.items.total()), () -> Pal.items, () -> (float)entity.items.total() / itemCapacity));
}
if(flags.contains(BlockFlag.unitModifier)) stats.add(Stat.maxUnits, (unitCapModifier < 0 ? "-" : "+") + Math.abs(unitCapModifier));
if(unitCapModifier != 0){
stats.add(Stat.maxUnits, (unitCapModifier < 0 ? "-" : "+") + Math.abs(unitCapModifier));
}
}
public boolean canReplace(Block other){
@@ -470,7 +492,7 @@ public class Block extends UnlockableContent{
}
public TextureRegion getRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
return icon(Cicon.full);
return fullIcon;
}
public void drawRequestConfig(BuildPlan req, Eachable<BuildPlan> list){
@@ -554,6 +576,11 @@ public class Block extends UnlockableContent{
return editorVariantRegions;
}
/** @return special icons to outline and save with an -outline variant. Vanilla only. */
public TextureRegion[] makeIconRegions(){
return new TextureRegion[0];
}
protected TextureRegion[] icons(){
//use team region in vanilla team blocks
return teamRegion.found() && minfo.mod == null ? new TextureRegion[]{region, teamRegions[Team.sharded.id]} : new TextureRegion[]{region};
@@ -564,7 +591,7 @@ public class Block extends UnlockableContent{
}
public TextureRegion[] variantRegions(){
return variantRegions == null ? (variantRegions = new TextureRegion[]{icon(Cicon.full)}) : variantRegions;
return variantRegions == null ? (variantRegions = new TextureRegion[]{fullIcon}) : variantRegions;
}
public boolean hasBuilding(){
@@ -588,7 +615,12 @@ public class Block extends UnlockableContent{
}
public boolean isPlaceable(){
return isVisible() && (!state.rules.bannedBlocks.contains(this) || state.rules.editor);
return isVisible() && (!state.rules.bannedBlocks.contains(this) || state.rules.editor) && supportsEnv(state.rules.environment);
}
/** @return whether this block supports a specific environment. */
public boolean supportsEnv(int env){
return (envEnabled & env) != 0 && (envDisabled & env) == 0 && (envRequired == 0 || (envRequired & env) == envRequired);
}
/** Called when building of this block begins. */
@@ -737,16 +769,25 @@ public class Block extends UnlockableContent{
health = size * size * 40;
}
clipSize = Math.max(clipSize, size * tilesize);
if(emitLight){
clipSize = Math.max(clipSize, lightRadius * 2f);
}
if(group == BlockGroup.transportation || consumes.has(ConsumeType.item) || category == Category.distribution){
acceptsItems = true;
}
offset = ((size + 1) % 2) * tilesize / 2f;
buildCost = 0f;
for(ItemStack stack : requirements){
buildCost += stack.amount * stack.item.cost;
if(requirements.length > 0){
buildCost = 0f;
for(ItemStack stack : requirements){
buildCost += stack.amount * stack.item.cost;
}
}
buildCost *= buildCostMultiplier;
if(consumes.has(ConsumeType.power)) hasPower = true;
@@ -772,9 +813,10 @@ public class Block extends UnlockableContent{
}
}
@CallSuper
@Override
public void load(){
super.load();
region = Core.atlas.find(name);
ContentRegions.loadRegions(this);
@@ -795,62 +837,41 @@ public class Block extends UnlockableContent{
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));
mapColor.set(image.getPixel(image.width/2, image.height/2));
PixmapRegion image = Core.atlas.getPixmap(fullIcon);
mapColor.set(image.get(image.width/2, image.height/2));
}
getGeneratedIcons();
Pixmap last = null;
var gen = icons();
if(outlineIcon){
final int radius = 4;
PixmapRegion region = Core.atlas.getPixmap(getGeneratedIcons()[outlinedIcon >= 0 ? outlinedIcon : 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.within(rx, ry, radius) && color.set(region.getPixel(rx + x, ry + y)).a > 0.01f){
found = true;
break outer;
}
}
}
if(found){
out.draw(x, y, outlineColor);
}
}
}
PixmapRegion region = Core.atlas.getPixmap(gen[outlinedIcon >= 0 ? outlinedIcon : gen.length -1]);
Pixmap out = last = Pixmaps.outline(region, outlineColor, outlineRadius);
if(Core.settings.getBool("linear")){
Pixmaps.bleed(out);
}
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);
var editorBase = Core.atlas.getPixmap(fullIcon);
if(gen.length > 1){
Pixmap base = Core.atlas.getPixmap(gen[0]).crop();
for(int i = 1; i < gen.length; i++){
if(i == gen.length - 1 && last != null){
base.draw(last, 0, 0, true);
}else{
base.draw(Core.atlas.getPixmap(generatedIcons[i]));
base.draw(Core.atlas.getPixmap(gen[i]), true);
}
}
packer.add(PageType.main, "block-" + name + "-full", base);
generatedIcons = null;
Arrays.fill(cicons, null);
editorBase = new PixmapRegion(base);
}
packer.add(PageType.editor, name + "-icon-editor", editorBase);
}
}

View File

@@ -35,6 +35,13 @@ public class Build{
int rotation = tile.build != null ? tile.build.rotation : 0;
Block previous = tile.block();
//instantly deconstruct if necessary
if(previous.instantDeconstruct){
ConstructBlock.deconstructFinish(tile, previous, unit);
return;
}
Block sub = ConstructBlock.get(previous.size);
Seq<Building> prevBuild = new Seq<>(1);
@@ -75,6 +82,14 @@ public class Build{
return;
}
//break all props in the way
tile.getLinkedTilesAs(result, out -> {
if(out.block != Blocks.air && out.block.alwaysReplace){
out.block.breakEffect.at(out.drawx(), out.drawy(), out.block.size, out.block.mapColor);
out.remove();
}
});
Block previous = tile.block();
Block sub = ConstructBlock.get(result.size);
Seq<Building> prevBuild = new Seq<>(9);
@@ -159,7 +174,7 @@ public class Build{
//this could be buggy and abuse-able, so I'm not enabling it yet
//note that this requires a change in BuilderComp as well
//(type == check.block() && check.centerX() == x && check.centerY() == y && check.build != null && check.build.health < check.build.maxHealth - 0.0001f) ||
(check.build instanceof ConstructBuild build && build.cblock == type && check.centerX() == tile.x && check.centerY() == tile.y)) && //same type in construction
(check.build instanceof ConstructBuild build && build.current == type && check.centerX() == tile.x && check.centerY() == tile.y)) && //same type in construction
type.bounds(tile.x, tile.y, Tmp.r1).grow(0.01f).contains(check.block.bounds(check.centerX(), check.centerY(), Tmp.r2))) || //no replacement
(type.requiresWater && check.floor().liquidDrop != Liquids.water) //requires water but none found
) return false;

View File

@@ -1,5 +1,6 @@
package mindustry.world;
import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
@@ -11,6 +12,7 @@ import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
@@ -19,7 +21,9 @@ import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
public class Tile implements Position, QuadTreeObject, Displayable{
static final ObjectSet<Building> tileSet = new ObjectSet<>();
private static final TileChangeEvent tileChange = new TileChangeEvent();
private static final TilePreChangeEvent preChange = new TilePreChangeEvent();
private static final ObjectSet<Building> tileSet = new ObjectSet<>();
/** Extra data for very specific blocks. */
public byte data;
@@ -108,16 +112,18 @@ public class Tile implements Position, QuadTreeObject, Displayable{
* Takes flammability of floor liquid into account.
*/
public float getFlammability(){
if(!block.hasItems){
if(floor.liquidDrop != null && !block.solid){
return floor.liquidDrop.flammability;
}
if(block == Blocks.air){
if(floor.liquidDrop != null) return floor.liquidDrop.flammability;
return 0;
}else if(build != null){
float result = build.items.sum((item, amount) -> item.flammability * amount);
float result = 0f;
if(block.hasItems){
result += build.items.sum((item, amount) -> item.flammability * amount) / block.itemCapacity * Mathf.clamp(block.itemCapacity / 2.4f, 1f, 3f);
}
if(block.hasLiquids){
result += build.liquids.sum((liquid, amount) -> liquid.flammability * amount / 3f);
result += build.liquids.sum((liquid, amount) -> liquid.flammability * amount / 1.6f) / block.liquidCapacity * Mathf.clamp(block.liquidCapacity / 30f, 1f, 2f);
}
return result;
@@ -166,6 +172,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
return build == null ? Team.derelict : build.team;
}
/** Do not call unless you know what you are doing! This does not update the indexer! */
public void setTeam(Team team){
if(build != null){
build.team(team);
@@ -197,10 +204,12 @@ public class Tile implements Position, QuadTreeObject, Displayable{
if(type.isStatic() || this.block.isStatic()){
recache();
recacheWall();
}
this.block = type;
preChanged();
this.block = type;
changeBuild(team, entityprov, (byte)Mathf.mod(rotation, 4));
if(build != null){
@@ -287,10 +296,17 @@ public class Tile implements Position, QuadTreeObject, Displayable{
circle(radius, (x, y) -> cons.get(world.rawTile(x, y)));
}
public void recacheWall(){
if(!headless && !world.isGenerating()){
renderer.blocks.recacheWall(this);
}
}
public void recache(){
if(!headless && !world.isGenerating()){
renderer.blocks.floor.recacheTile(this);
renderer.minimap.update(this);
//update neighbor tiles as well
for(int i = 0; i < 8; i++){
Tile other = world.tile(x + Geometry.d8[i].x, y + Geometry.d8[i].y);
if(other != null){
@@ -487,6 +503,13 @@ public class Tile implements Position, QuadTreeObject, Displayable{
return overlay == Blocks.air || overlay.itemDrop == null ? floor.itemDrop : overlay.itemDrop;
}
public @Nullable Item wallDrop(){
return block.solid ?
block.itemDrop != null ? block.itemDrop :
overlay instanceof WallOreBlock ? overlay.itemDrop :
null : null;
}
public int staticDarkness(){
return block.solid && block.fillsTile && !block.synthetic() ? data : 0;
}
@@ -497,6 +520,10 @@ public class Tile implements Position, QuadTreeObject, Displayable{
}
protected void preChanged(){
if(!world.isGenerating()){
Events.fire(preChange.set(this));
}
if(build != null){
//only call removed() for the center block - this only gets called once.
build.onRemoved();
@@ -514,6 +541,9 @@ public class Tile implements Position, QuadTreeObject, Displayable{
if(other != null){
//reset entity and block *manually* - thus, preChanged() will not be called anywhere else, for multiblocks
if(other != this){ //do not remove own entity so it can be processed in changed()
//manually call pre-change event for other tile
Events.fire(preChange.set(other));
other.build = null;
other.block = Blocks.air;
@@ -525,12 +555,6 @@ public class Tile implements Position, QuadTreeObject, Displayable{
}
}
}
//recache when static blocks get changed
if(block.isStatic()){
recache();
}
}
protected void changeBuild(Team team, Prov<Building> entityprov, int rotation){
@@ -584,12 +608,18 @@ public class Tile implements Position, QuadTreeObject, Displayable{
}
protected void fireChanged(){
world.notifyChanged(this);
if(!world.isGenerating()){
Events.fire(tileChange.set(this));
}
}
@Override
public void display(Table table){
Block toDisplay = overlay.itemDrop != null ? overlay : floor;
Block toDisplay =
block.itemDrop != null ? block :
overlay.itemDrop != null || wallDrop() != null ? overlay :
floor;
table.table(t -> {
t.left();
@@ -635,8 +665,7 @@ public class Tile implements Position, QuadTreeObject, Displayable{
@Remote(called = Loc.server)
public static void setTeam(Building build, Team team){
if(build != null){
build.team = team;
indexer.updateIndices(build.tile);
build.changeTeam(team);
}
}

View File

@@ -7,27 +7,31 @@ import mindustry.world.meta.*;
import java.util.*;
public class Attributes implements JsonSerializable{
private final float[] arr = new float[Attribute.all.length];
private float[] arr = new float[Attribute.all.length];
public void clear(){
Arrays.fill(arr, 0);
}
public float get(Attribute attr){
return arr[attr.ordinal()];
check();
return arr[attr.id];
}
public void set(Attribute attr, float value){
arr[attr.ordinal()] = value;
check();
arr[attr.id] = value;
}
public void add(Attributes other){
check();
for(int i = 0; i < arr.length; i++){
arr[i] += other.arr[i];
}
}
public void add(Attributes other, float scl){
check();
for(int i = 0; i < arr.length; i++){
arr[i] += other.arr[i] * scl;
}
@@ -35,17 +39,23 @@ public class Attributes implements JsonSerializable{
@Override
public void write(Json json){
check();
for(Attribute at : Attribute.all){
if(arr[at.ordinal()] != 0){
json.writeValue(at.name(), arr[at.ordinal()]);
if(arr[at.id] != 0){
json.writeValue(at.name, arr[at.id]);
}
}
}
@Override
public void read(Json json, JsonValue data){
check();
for(Attribute at : Attribute.all){
arr[at.ordinal()] = data.getFloat(at.name(), 0);
arr[at.id] = data.getFloat(at.name, 0);
}
}
private void check(){
if(arr.length != Attribute.all.length) arr = new float[Attribute.all.length];
}
}

View File

@@ -16,8 +16,8 @@ import mindustry.game.EventType.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.modules.*;
@@ -54,7 +54,7 @@ public class ConstructBlock extends Block{
@Remote(called = Loc.server)
public static void deconstructFinish(Tile tile, Block block, Unit builder){
Team team = tile.team();
Fx.breakBlock.at(tile.drawx(), tile.drawy(), block.size);
block.breakEffect.at(tile.drawx(), tile.drawy(), block.size, block.mapColor);
Events.fire(new BlockBuildEndEvent(tile, builder, team, true, null));
tile.remove();
if(shouldPlay()) Sounds.breaks.at(tile, calcPitch(false));
@@ -133,43 +133,36 @@ public class ConstructBlock extends Block{
}
public class ConstructBuild extends Building{
/**
* The recipe of the block that is being constructed.
* If there is no recipe for this block, as is the case with rocks, 'previous' is used.
*/
public @Nullable Block cblock;
/** The recipe of the block that is being (de)constructed. Never null. */
public Block current = Blocks.air;
/** The block that used to be here. Never null. */
public Block previous = Blocks.air;
/** Buildings that previously occupied this location. */
public @Nullable Seq<Building> prevBuild;
public float progress = 0;
public float buildCost;
/**
* The block that used to be here.
* If a non-recipe block is being deconstructed, this is the block that is being deconstructed.
*/
public Block previous;
public @Nullable Object lastConfig;
public @Nullable Unit lastBuilder;
public boolean wasConstructing, activeDeconstruct;
public float constructColor;
@Nullable
public Unit lastBuilder;
private float[] accumulator;
private float[] totalAccumulator;
@Override
public String getDisplayName(){
return Core.bundle.format("block.constructing", cblock == null ? previous.localizedName : cblock.localizedName);
return Core.bundle.format("block.constructing", current.localizedName);
}
@Override
public TextureRegion getDisplayIcon(){
return (cblock == null ? previous : cblock).icon(Cicon.full);
return current.fullIcon;
}
@Override
public boolean checkSolid(){
return (cblock != null && cblock.solid) || previous == null || previous.solid;
return current.solid || previous.solid;
}
@Override
@@ -180,14 +173,20 @@ public class ConstructBlock extends Block{
@Override
public void tapped(){
//if the target is constructable, begin constructing
if(cblock != null){
if(current.isPlaceable()){
if(control.input.buildWasAutoPaused && !control.input.isBuilding && player.isBuilder()){
control.input.isBuilding = true;
}
player.unit().addBuild(new BuildPlan(tile.x, tile.y, rotation, cblock, lastConfig), false);
player.unit().addBuild(new BuildPlan(tile.x, tile.y, rotation, current, lastConfig), false);
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(progress);
return super.sense(sensor);
}
@Override
public void onDestroyed(){
Fx.blockExplosionSmoke.at(tile);
@@ -199,29 +198,33 @@ public class ConstructBlock extends Block{
@Override
public void updateTile(){
//auto-remove air blocks
if(current == Blocks.air){
remove();
}
constructColor = Mathf.lerpDelta(constructColor, activeDeconstruct ? 1f : 0f, 0.2f);
activeDeconstruct = false;
}
@Override
public void draw(){
if(!(previous == null || cblock == null || previous == cblock) && Core.atlas.isFound(previous.icon(Cicon.full))){
Draw.rect(previous.icon(Cicon.full), x, y, previous.rotate ? rotdeg() : 0);
//do not draw air
if(current == Blocks.air) return;
if(previous != current && previous != Blocks.air && previous.fullIcon.found()){
Draw.rect(previous.fullIcon, x, y, previous.rotate ? rotdeg() : 0);
}
Draw.draw(Layer.blockBuilding, () -> {
Draw.color(Pal.accent, Pal.remove, constructColor);
Block target = cblock == null ? previous : cblock;
for(TextureRegion region : current.getGeneratedIcons()){
Shaders.blockbuild.region = region;
Shaders.blockbuild.progress = progress;
if(target != null){
for(TextureRegion region : target.getGeneratedIcons()){
Shaders.blockbuild.region = region;
Shaders.blockbuild.progress = progress;
Draw.rect(region, x, y, target.rotate ? rotdeg() : 0);
Draw.flush();
}
Draw.rect(region, x, y, current.rotate ? rotdeg() : 0);
Draw.flush();
}
Draw.color();
@@ -231,10 +234,6 @@ public class ConstructBlock extends Block{
public void construct(Unit builder, @Nullable Building core, float amount, Object config){
wasConstructing = true;
activeDeconstruct = false;
if(cblock == null){
kill();
return;
}
if(builder.isPlayer()){
lastBuilder = builder;
@@ -242,14 +241,14 @@ public class ConstructBlock extends Block{
lastConfig = config;
if(cblock.requirements.length != accumulator.length || totalAccumulator.length != cblock.requirements.length){
setConstruct(previous, cblock);
if(current.requirements.length != accumulator.length || totalAccumulator.length != current.requirements.length){
setConstruct(previous, current);
}
float maxProgress = core == null || team.rules().infiniteResources ? amount : checkRequired(core.items, amount, false);
for(int i = 0; i < cblock.requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount);
for(int i = 0; i < current.requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * current.requirements[i].amount);
accumulator[i] += Math.min(reqamount * maxProgress, reqamount - totalAccumulator[i] + 0.00001f); //add min amount progressed to the accumulator
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * maxProgress, reqamount);
}
@@ -260,16 +259,17 @@ public class ConstructBlock extends Block{
if(progress >= 1f || state.rules.infiniteResources){
if(lastBuilder == null) lastBuilder = builder;
constructed(tile, cblock, lastBuilder, (byte)rotation, builder.team, config);
constructed(tile, current, lastBuilder, (byte)rotation, builder.team, config);
}
}
public void deconstruct(Unit builder, @Nullable Building core, float amount){
public void deconstruct(Unit builder, @Nullable CoreBuild core, float amount){
//reset accumulated resources when switching modes
if(wasConstructing){
Arrays.fill(accumulator, 0);
Arrays.fill(totalAccumulator, 0);
}
wasConstructing = false;
activeDeconstruct = true;
float deconstructMultiplier = state.rules.deconstructRefundMultiplier;
@@ -278,55 +278,53 @@ public class ConstructBlock extends Block{
lastBuilder = builder;
}
if(cblock != null){
ItemStack[] requirements = cblock.requirements;
if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){
setDeconstruct(cblock);
}
ItemStack[] requirements = current.requirements;
if(requirements.length != accumulator.length || totalAccumulator.length != requirements.length){
setDeconstruct(current);
}
//make sure you take into account that you can't deconstruct more than there is deconstructed
float clampedAmount = Math.min(amount, progress);
//make sure you take into account that you can't deconstruct more than there is deconstructed
float clampedAmount = Math.min(amount, progress);
for(int i = 0; i < requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * requirements[i].amount);
accumulator[i] += Math.min(clampedAmount * deconstructMultiplier * reqamount, deconstructMultiplier * reqamount - totalAccumulator[i]); //add scaled amount progressed to the accumulator
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * clampedAmount * deconstructMultiplier, reqamount);
for(int i = 0; i < requirements.length; i++){
int reqamount = Math.round(state.rules.buildCostMultiplier * requirements[i].amount);
accumulator[i] += Math.min(clampedAmount * deconstructMultiplier * reqamount, deconstructMultiplier * reqamount - totalAccumulator[i]); //add scaled amount progressed to the accumulator
totalAccumulator[i] = Math.min(totalAccumulator[i] + reqamount * clampedAmount * deconstructMultiplier, reqamount);
int accumulated = (int)(accumulator[i]); //get amount
int accumulated = (int)(accumulator[i]); //get amount
if(clampedAmount > 0 && accumulated > 0){ //if it's positive, add it to the core
if(core != null && requirements[i].item.unlockedNow()){ //only accept items that are unlocked
int accepting = Math.min(accumulated, ((CoreBuild)core).storageCapacity - core.items.get(requirements[i].item));
//transfer items directly, as this is not production.
core.items.add(requirements[i].item, accepting);
accumulator[i] -= accepting;
}else{
accumulator[i] -= accumulated;
}
if(clampedAmount > 0 && accumulated > 0){ //if it's positive, add it to the core
if(core != null && requirements[i].item.unlockedNow()){ //only accept items that are unlocked
int accepting = Math.min(accumulated, core.storageCapacity - core.items.get(requirements[i].item));
//transfer items directly, as this is not production.
core.items.add(requirements[i].item, accepting);
accumulator[i] -= accepting;
}else{
accumulator[i] -= accumulated;
}
}
}
progress = Mathf.clamp(progress - amount);
if(progress <= (previous == null ? 0 : previous.deconstructThreshold) || state.rules.infiniteResources){
if(progress <= current.deconstructThreshold || state.rules.infiniteResources){
if(lastBuilder == null) lastBuilder = builder;
Call.deconstructFinish(tile, this.cblock == null ? previous : this.cblock, lastBuilder);
Call.deconstructFinish(tile, this.current, lastBuilder);
}
}
private float checkRequired(ItemModule inventory, float amount, boolean remove){
float maxProgress = amount;
for(int i = 0; i < cblock.requirements.length; i++){
int sclamount = Math.round(state.rules.buildCostMultiplier * cblock.requirements[i].amount);
for(int i = 0; i < current.requirements.length; i++){
int sclamount = Math.round(state.rules.buildCostMultiplier * current.requirements[i].amount);
int required = (int)(accumulator[i]); //calculate items that are required now
if(inventory.get(cblock.requirements[i].item) == 0 && sclamount != 0){
if(inventory.get(current.requirements[i].item) == 0 && sclamount != 0){
maxProgress = 0f;
}else if(required > 0){ //if this amount is positive...
//calculate how many items it can actually use
int maxUse = Math.min(required, inventory.get(cblock.requirements[i].item));
int maxUse = Math.min(required, inventory.get(current.requirements[i].item));
//get this as a fraction
float fraction = maxUse / (float)required;
@@ -337,7 +335,7 @@ public class ConstructBlock extends Block{
//remove stuff that is actually used
if(remove){
inventory.remove(cblock.requirements[i].item, maxUse);
inventory.remove(current.requirements[i].item, maxUse);
}
}
//else, no items are required yet, so just keep going
@@ -351,13 +349,15 @@ public class ConstructBlock extends Block{
}
public void setConstruct(Block previous, Block block){
if(block == null) return;
this.constructColor = 0f;
this.wasConstructing = true;
this.cblock = block;
this.current = block;
this.previous = previous;
this.buildCost = block.buildCost * state.rules.buildCostMultiplier;
this.accumulator = new float[block.requirements.length];
this.totalAccumulator = new float[block.requirements.length];
this.buildCost = block.buildCost * state.rules.buildCostMultiplier;
}
public void setDeconstruct(Block previous){
@@ -367,12 +367,8 @@ public class ConstructBlock extends Block{
this.wasConstructing = false;
this.previous = previous;
this.progress = 1f;
if(previous.buildCost >= 0.01f){
this.cblock = previous;
this.buildCost = previous.buildCost * state.rules.buildCostMultiplier;
}else{
this.buildCost = 20f; //default no-requirement build cost is 20
}
this.current = previous;
this.buildCost = previous.buildCost * state.rules.buildCostMultiplier;
this.accumulator = new float[previous.requirements.length];
this.totalAccumulator = new float[previous.requirements.length];
}
@@ -381,8 +377,8 @@ public class ConstructBlock extends Block{
public void write(Writes write){
super.write(write);
write.f(progress);
write.s(previous == null ? -1 : previous.id);
write.s(cblock == null ? -1 : cblock.id);
write.s(previous.id);
write.s(current.id);
if(accumulator == null){
write.b(-1);
@@ -413,13 +409,12 @@ public class ConstructBlock extends Block{
}
if(pid != -1) previous = content.block(pid);
if(rid != -1) cblock = content.block(rid);
if(rid != -1) current = content.block(rid);
if(cblock != null){
buildCost = cblock.buildCost * state.rules.buildCostMultiplier;
}else{
buildCost = 20f;
}
if(previous == null) previous = Blocks.air;
if(current == null) current = Blocks.air;
buildCost = current.buildCost * state.rules.buildCostMultiplier;
}
}
}

View File

@@ -34,7 +34,7 @@ public class ItemSelection{
if(closeSelect) control.input.frag.config.hideConfig();
}).group(group).get();
button.changed(() -> consumer.get(button.isChecked() ? item : null));
button.getStyle().imageUp = new TextureRegionDrawable(item.icon(Cicon.small));
button.getStyle().imageUp = new TextureRegionDrawable(item.uiIcon);
button.update(() -> button.setChecked(holder.get() == item));
if(i++ % 4 == 3){

View File

@@ -109,7 +109,10 @@ public class Accelerator extends Block{
if(!state.isCampaign() || !consValid()) return;
ui.showInfo("@indev.campaign");
ui.planet.showPlanetLaunch(state.rules.sector, sector -> {
//TODO cutscene, etc...
consume();
});
Events.fire(Trigger.acceleratorUse);
}

View File

@@ -1,9 +1,9 @@
package mindustry.world.blocks.campaign;
import arc.*;
import arc.audio.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -11,12 +11,14 @@ import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
@@ -26,9 +28,8 @@ import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class LaunchPad extends Block{
public final int timerLaunch = timers++;
/** Time inbetween launches. */
public float launchTime;
public float launchTime = 1f;
public Sound launchSound = Sounds.none;
public @Load("@-light") TextureRegion lightRegion;
@@ -42,6 +43,7 @@ public class LaunchPad extends Block{
update = true;
configurable = true;
drawDisabled = false;
flags = EnumSet.of(BlockFlag.launchPad);
}
@Override
@@ -64,6 +66,7 @@ public class LaunchPad extends Block{
}
public class LaunchPadBuild extends Building{
public float launchCounter;
@Override
public Cursor getCursor(){
@@ -81,6 +84,12 @@ public class LaunchPad extends Block{
return true;
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(launchCounter / launchTime);
return super.sense(sensor);
}
@Override
public void draw(){
super.draw();
@@ -89,7 +98,7 @@ public class LaunchPad extends Block{
if(lightRegion.found()){
Draw.color(lightColor);
float progress = Math.min((float)items.total() / itemCapacity, timer.getTime(timerLaunch) / (launchTime / timeScale));
float progress = Math.min((float)items.total() / itemCapacity, launchCounter / launchTime);
int steps = 3;
float step = 1f;
@@ -106,7 +115,7 @@ public class LaunchPad extends Block{
Draw.reset();
}
float cooldown = Mathf.clamp(timer.getTime(timerLaunch) / (90f / timeScale));
float cooldown = Mathf.clamp(launchCounter / (90f));
Draw.mixcol(lightColor, 1f - cooldown);
@@ -125,7 +134,7 @@ public class LaunchPad extends Block{
if(!state.isCampaign()) return;
//launch when full and base conditions are met
if(items.total() >= itemCapacity && efficiency() >= 1f && timer(timerLaunch, launchTime / timeScale)){
if(items.total() >= itemCapacity && efficiency() >= 1f && (launchCounter += edelta()) >= launchTime){
launchSound.at(x, y);
LaunchPayload entity = LaunchPayload.create();
items.each((item, amount) -> entity.stacks.add(new ItemStack(item, amount)));
@@ -136,6 +145,7 @@ public class LaunchPad extends Block{
Fx.launchPod.at(this);
items.clear();
Effect.shake(3f, 3f, this);
launchCounter = 0f;
}
}
@@ -171,6 +181,25 @@ public class LaunchPad extends Block{
deselect();
}).size(40f);
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
write.f(launchCounter);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
if(revision >= 1){
launchCounter = read.f();
}
}
}
@EntityDef(LaunchPayloadc.class)

View File

@@ -0,0 +1,280 @@
package mindustry.world.blocks.campaign;
import arc.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
//TODO very much unfinished
public class PayloadLaunchPad extends PayloadBlock{
/** Time between launches. */
public float launchTime;
public Sound launchSound = Sounds.none;
public @Load("@-light") TextureRegion lightRegion;
public @Load(value = "@-pod", fallback = "launchpod") TextureRegion podRegion;
public Color lightColor = Color.valueOf("eab678");
public PayloadLaunchPad(String name){
super(name);
solid = true;
update = true;
configurable = true;
drawDisabled = false;
flags = EnumSet.of(BlockFlag.launchPad);
acceptsPayload = true;
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.launchTime, launchTime / 60f, StatUnit.seconds);
}
public class PayloadLaunchPadBuild extends PayloadBlockBuild<Payload>{
public float launchCounter;
@Override
public Cursor getCursor(){
return !state.isCampaign() || net.client() ? SystemCursor.arrow : super.getCursor();
}
//cannot be disabled
@Override
public float efficiency(){
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
}
@Override
public boolean shouldConsume(){
return true;
}
@Override
public void draw(){
super.draw();
if(!state.isCampaign()) return;
if(lightRegion.found()){
Draw.color(lightColor);
float progress = launchCounter / launchTime;
int steps = 3;
float step = 1f;
for(int i = 0; i < 4; i++){
for(int j = 0; j < steps; j++){
float alpha = Mathf.curve(progress, (float)j / steps, (j+1f) / steps);
float offset = -(j - 1f) * step;
Draw.color(Pal.metalGrayDark, lightColor, alpha);
Draw.rect(lightRegion, x + Geometry.d8edge(i).x * offset, y + Geometry.d8edge(i).y * offset, i * 90);
}
}
Draw.reset();
}
drawPayload();
float cooldown = Mathf.clamp(launchCounter / (90f));
Draw.mixcol(lightColor, 1f - cooldown);
Draw.rect(podRegion, x, y);
Draw.reset();
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(launchCounter / launchTime);
return super.sense(sensor);
}
@Override
public void updateTile(){
if(!state.isCampaign()) return;
//launch when full and base conditions are met
if(payload != null && moveInPayload() && efficiency() >= 1f && (launchCounter += edelta()) >= launchTime){
launchSound.at(x, y);
LargeLaunchPayload entity = LargeLaunchPayload.create();
entity.payload = payload;
entity.set(this);
entity.lifetime(120f);
entity.team(team);
entity.add();
Fx.launchPod.at(this);
payload = null;
Effect.shake(3f, 3f, this);
launchCounter = 0f;
}
}
@Override
public void display(Table table){
super.display(table);
if(!state.isCampaign()) return;
table.row();
table.label(() -> {
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.getRealDestination();
return Core.bundle.format("launch.destination",
dest == null ? Core.bundle.get("sectors.nonelaunch") :
"[accent]" + dest.name());
}).pad(4).wrap().width(200f).left();
}
@Override
public void buildConfiguration(Table table){
if(!state.isCampaign() || net.client()){
deselect();
return;
}
table.button(Icon.upOpen, Styles.clearTransi, () -> {
ui.planet.showSelect(state.rules.sector, other -> {
if(state.isCampaign()){
state.rules.sector.info.destination = other;
}
});
deselect();
}).size(40f);
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
write.f(launchCounter);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
if(revision >= 1){
launchCounter = read.f();
}
}
}
@EntityDef(LargeLaunchPayloadc.class)
@Component(base = true)
static abstract class LargeLaunchPayloadComp implements Drawc, Timedc, Teamc{
@Import float x,y;
Payload payload;
transient Interval in = new Interval();
@Override
public void draw(){
float alpha = fout(Interp.pow5Out);
float scale = (1f - alpha) * 1.3f + 1f;
float cx = cx(), cy = cy();
float rotation = fin() * (130f + Mathf.randomSeedRange(id(), 50f));
Draw.z(Layer.effect + 0.001f);
Draw.color(Pal.engine);
float rad = 0.2f + fslope();
Fill.light(cx, cy, 10, 25f * (rad + scale-1f), Tmp.c2.set(Pal.engine).a(alpha), Tmp.c1.set(Pal.engine).a(0f));
Draw.alpha(alpha);
for(int i = 0; i < 4; i++){
Drawf.tri(cx, cy, 6f, 40f * (rad + scale-1f), i * 90f + rotation);
}
Draw.color();
Draw.z(Layer.weather - 1);
TextureRegion region = blockOn() instanceof mindustry.world.blocks.campaign.PayloadLaunchPad p ? p.podRegion : Core.atlas.find("launchpod");
float rw = region.width * Draw.scl * scale, rh = region.height * Draw.scl * scale;
Draw.alpha(alpha);
Draw.rect(region, cx, cy, rw, rh, rotation);
Tmp.v1.trns(225f, fin(Interp.pow3In) * 250f);
Draw.z(Layer.flyingUnit + 1);
Draw.color(0, 0, 0, 0.22f * alpha);
Draw.rect(region, cx + Tmp.v1.x, cy + Tmp.v1.y, rw, rh, rotation);
Draw.reset();
}
float cx(){
return x + fin(Interp.pow2In) * (12f + Mathf.randomSeedRange(id() + 3, 4f));
}
float cy(){
return y + fin(Interp.pow5In) * (100f + Mathf.randomSeedRange(id() + 2, 30f));
}
@Override
public void update(){
float r = 3f;
if(in.get(4f - fin()*2f)){
Fx.rocketSmoke.at(cx() + Mathf.range(r), cy() + Mathf.range(r), fin());
}
}
@Override
public void remove(){
if(!state.isCampaign()) return;
Sector destsec = state.rules.sector.info.getRealDestination();
//actually launch the items upon removal
if(team() == state.rules.defaultTeam){
if(destsec != null && (destsec != state.rules.sector || net.client())){
/*
ItemSeq dest = new ItemSeq();
for(ItemStack stack : stacks){
dest.add(stack);
//update export
state.rules.sector.info.handleItemExport(stack);
Events.fire(new LaunchItemEvent(stack));
}
if(!net.client()){
destsec.addItems(dest);
}*/
}
}
}
}
}

View File

@@ -31,7 +31,6 @@ public class ForceProjector extends Block{
public float cooldownNormal = 1.75f;
public float cooldownLiquid = 1.5f;
public float cooldownBrokenBase = 0.35f;
public float basePowerDraw = 0.2f;
public @Load("@-top") TextureRegion topRegion;
static ForceBuild paramEntity;
@@ -40,7 +39,7 @@ public class ForceProjector extends Block{
trait.absorb();
Fx.absorb.at(trait);
paramEntity.hit = 1f;
paramEntity.buildup += trait.damage() * paramEntity.warmup;
paramEntity.buildup += trait.damage();
}
};
@@ -57,6 +56,12 @@ public class ForceProjector extends Block{
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, 0.1f)).boost().update(false);
}
@Override
public void init(){
clipSize = Math.max(clipSize, (radius + phaseRadiusBoost + 3f) * 2f);
super.init();
}
@Override
public void setBars(){
super.setBars();
@@ -70,10 +75,10 @@ public class ForceProjector extends Block{
@Override
public void setStats(){
stats.timePeriod = phaseUseTime;
super.setStats();
stats.add(Stat.shieldHealth, shieldHealth, StatUnit.none);
stats.add(Stat.cooldownTime, (int) (shieldHealth / cooldownBrokenBase / 60f), StatUnit.seconds);
stats.add(Stat.powerUse, basePowerDraw * 60f, StatUnit.powerSecond);
stats.add(Stat.boostEffect, phaseRadiusBoost / tilesize, StatUnit.blocks);
stats.add(Stat.boostEffect, phaseShieldBoost, StatUnit.shieldHealth);
}
@@ -94,22 +99,12 @@ public class ForceProjector extends Block{
public class ForceBuild extends Building implements Ranged{
public boolean broken = true;
public float buildup, radscl, hit, warmup, phaseHeat;
public ForceDraw drawer;
@Override
public float range(){
return realRadius();
}
@Override
public void created(){
super.created();
drawer = ForceDraw.create();
drawer.build = this;
drawer.set(x, y);
drawer.add();
}
@Override
public boolean shouldAmbientSound(){
return !broken && realRadius() > 1f;
@@ -120,7 +115,6 @@ public class ForceProjector extends Block{
float radius = realRadius();
if(!broken && radius > 1f) Fx.forceShrink.at(x, y, radius, team.color);
super.onRemoved();
drawer.remove();
}
@Override
@@ -188,10 +182,6 @@ public class ForceProjector extends Block{
public void draw(){
super.draw();
if(drawer != null){
drawer.set(x, y);
}
if(buildup > 0f){
Draw.alpha(buildup / shieldHealth * 0.75f);
Draw.blend(Blending.additive);
@@ -199,6 +189,8 @@ public class ForceProjector extends Block{
Draw.blend();
Draw.reset();
}
drawShield();
}
public void drawShield(){
@@ -244,21 +236,4 @@ public class ForceProjector extends Block{
phaseHeat = read.f();
}
}
@EntityDef(value = {ForceDrawc.class}, serialize = false)
@Component(base = true)
abstract class ForceDrawComp implements Drawc{
transient ForceBuild build;
@Override
public void draw(){
build.drawShield();
}
@Replace
@Override
public float clipSize(){
return build.realRadius() * 3f;
}
}
}

View File

@@ -43,6 +43,7 @@ public class MendProjector extends Block{
@Override
public void setStats(){
stats.timePeriod = useTime;
super.setStats();
stats.add(Stat.repairTime, (int)(100f / healPercent * reload / 60f), StatUnit.seconds);
@@ -90,11 +91,17 @@ public class MendProjector extends Block{
indexer.eachBlock(this, realRange, other -> other.damaged(), other -> {
other.heal(other.maxHealth() * (healPercent + phaseHeat * phaseBoost) / 100f * efficiency());
Fx.healBlockFull.at(other.x, other.y, other.block.size, Tmp.c1.set(baseColor).lerp(phaseColor, phaseHeat));
Fx.healBlockFull.at(other.x, other.y, other.block.size, baseColor);
});
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(charge / reload);
return super.sense(sensor);
}
@Override
public void drawSelect(){
float realRange = range + phaseHeat * phaseRangeBoost;

View File

@@ -57,6 +57,7 @@ public class OverdriveProjector extends Block{
@Override
public void setStats(){
stats.timePeriod = useTime;
super.setStats();
stats.add(Stat.speedIncrease, (int)(100f * speedBoost), StatUnit.percent);

View File

@@ -14,7 +14,6 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -28,7 +27,21 @@ public class ItemTurret extends Turret{
/** Initializes accepted ammo map. Format: [item1, bullet1, item2, bullet2...] */
public void ammo(Object... objects){
ammoTypes = OrderedMap.of(objects);
ammoTypes = ObjectMap.of(objects);
}
/** Makes copies of all bullets and limits their range. */
public void limitRange(){
limitRange(1f);
}
/** Makes copies of all bullets and limits their range. */
public void limitRange(float margin){
for(var entry : ammoTypes.copy().entries()){
var copy = entry.value.copy();
copy.lifetime = (range + margin) / copy.speed;
ammoTypes.put(entry.key, copy);
}
}
@Override
@@ -36,7 +49,7 @@ public class ItemTurret extends Turret{
super.setStats();
stats.remove(Stat.itemCapacity);
stats.add(Stat.ammo, new AmmoListValue<>(ammoTypes));
stats.add(Stat.ammo, StatValues.ammo(ammoTypes));
}
@Override
@@ -45,7 +58,7 @@ public class ItemTurret extends Turret{
@Override
public void build(Building tile, Table table){
MultiReqImage image = new MultiReqImage();
content.items().each(i -> filter.get(i) && i.unlockedNow(), item -> image.add(new ReqImage(new ItemImage(item.icon(Cicon.medium)),
content.items().each(i -> filter.get(i) && i.unlockedNow(), item -> image.add(new ReqImage(new ItemImage(item.uiIcon),
() -> tile instanceof ItemTurretBuild it && !it.ammo.isEmpty() && ((ItemEntry)it.ammo.peek()).item == item)));
table.add(image).size(8 * 4);

View File

@@ -4,10 +4,10 @@ import arc.math.*;
import arc.util.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -34,7 +34,7 @@ public class LaserTurret extends PowerTurret{
super.setStats();
stats.remove(Stat.booster);
stats.add(Stat.input, new BoosterListValue(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, false, l -> consumes.liquidfilters.get(l.id)));
stats.add(Stat.input, StatValues.boosters(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, false, l -> consumes.liquidfilters.get(l.id)));
}
public class LaserTurretBuild extends PowerTurretBuild{
@@ -67,8 +67,8 @@ public class LaserTurret extends PowerTurret{
Liquid liquid = liquids.current();
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
float used = (cheating() ? maxUsed * Time.delta : Math.min(liquids.get(liquid), maxUsed * Time.delta)) * liquid.heatCapacity * coolantMultiplier;
reload -= used;
float used = (cheating() ? maxUsed * Time.delta : Math.min(liquids.get(liquid), maxUsed * Time.delta));
reload -= used * liquid.heatCapacity * coolantMultiplier;
liquids.remove(liquid, used);
if(Mathf.chance(0.06 * used)){
@@ -77,6 +77,13 @@ public class LaserTurret extends PowerTurret{
}
}
@Override
public double sense(LAccess sensor){
//reload reversed for laser turrets
if(sensor == LAccess.progress) return Mathf.clamp(1f - reload / reloadTime);
return super.sense(sensor);
}
@Override
protected void updateShooting(){
if(bulletLife > 0 && bullet != null){

View File

@@ -3,6 +3,7 @@ package mindustry.world.blocks.defense.turrets;
import arc.graphics.g2d.*;
import arc.struct.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
@@ -11,7 +12,6 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -32,14 +32,14 @@ public class LiquidTurret extends Turret{
/** Initializes accepted ammo map. Format: [liquid1, bullet1, liquid2, bullet2...] */
public void ammo(Object... objects){
ammoTypes = OrderedMap.of(objects);
ammoTypes = ObjectMap.of(objects);
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.ammo, new AmmoListValue<>(ammoTypes));
stats.add(Stat.ammo, StatValues.ammo(ammoTypes));
}
@Override
@@ -88,7 +88,9 @@ public class LiquidTurret extends Turret{
@Override
public void updateTile(){
unit.ammo(unit.type().ammoCapacity * liquids.currentAmount() / liquidCapacity);
if(unit != null){
unit.ammo(unit.type().ammoCapacity * liquids.currentAmount() / liquidCapacity);
}
super.updateTile();
}
@@ -116,8 +118,11 @@ public class LiquidTurret extends Turret{
protected void effects(){
BulletType type = peekAmmo();
type.shootEffect.at(x + tr.x, y + tr.y, rotation, liquids.current().color);
type.smokeEffect.at(x + tr.x, y + tr.y, rotation, liquids.current().color);
Effect fshootEffect = shootEffect == Fx.none ? type.shootEffect : shootEffect;
Effect fsmokeEffect = smokeEffect == Fx.none ? type.smokeEffect : smokeEffect;
fshootEffect.at(x + tr.x, y + tr.y, rotation, liquids.current().color);
fsmokeEffect.at(x + tr.x, y + tr.y, rotation, liquids.current().color);
shootSound.at(tile);
if(shootShake > 0){

View File

@@ -4,7 +4,6 @@ import arc.struct.*;
import mindustry.entities.bullet.*;
import mindustry.logic.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
public class PowerTurret extends Turret{
public BulletType shootType;
@@ -18,7 +17,7 @@ public class PowerTurret extends Turret{
@Override
public void setStats(){
super.setStats();
stats.add(Stat.ammo, new AmmoListValue<>(OrderedMap.of(this, shootType)));
stats.add(Stat.ammo, StatValues.ammo(ObjectMap.of(this, shootType)));
}
@Override
@@ -31,7 +30,9 @@ public class PowerTurret extends Turret{
@Override
public void updateTile(){
unit.ammo(power.status * unit.type().ammoCapacity);
if(unit != null){
unit.ammo(power.status * unit.type().ammoCapacity);
}
super.updateTile();
}

View File

@@ -5,7 +5,6 @@ import arc.util.*;
import mindustry.type.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -21,7 +20,7 @@ public class ReloadTurret extends BaseTurret{
super.setStats();
if(acceptCoolant){
stats.add(Stat.booster, new BoosterListValue(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, true, l -> consumes.liquidfilters.get(l.id)));
stats.add(Stat.booster, StatValues.boosters(reloadTime, consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount, coolantMultiplier, true, l -> consumes.liquidfilters.get(l.id)));
}
}
@@ -29,16 +28,17 @@ public class ReloadTurret extends BaseTurret{
public float reload;
protected void updateCooling(){
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
if(reload < reloadTime){
float maxUsed = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid).amount;
Liquid liquid = liquids.current();
Liquid liquid = liquids.current();
float used = Math.min(liquids.get(liquid), maxUsed * Time.delta) * baseReloadSpeed();
reload += used * liquid.heatCapacity * coolantMultiplier;
liquids.remove(liquid, used);
float used = Math.min(Math.min(liquids.get(liquid), maxUsed * Time.delta), Math.max(0, ((reloadTime - reload) / coolantMultiplier) / liquid.heatCapacity)) * baseReloadSpeed();
reload += used * liquid.heatCapacity * coolantMultiplier;
liquids.remove(liquid, used);
if(Mathf.chance(0.06 * used)){
coolEffect.at(x + Mathf.range(size * tilesize / 2f), y + Mathf.range(size * tilesize / 2f));
if(Mathf.chance(0.06 * used)){
coolEffect.at(x + Mathf.range(size * tilesize / 2f), y + Mathf.range(size * tilesize / 2f));
}
}
}

View File

@@ -47,7 +47,6 @@ public class TractorBeamTurret extends BaseTurret{
//disabled due to version mismatch problems
acceptCoolant = false;
expanded = true;
}
@Override
@@ -64,6 +63,13 @@ public class TractorBeamTurret extends BaseTurret{
stats.add(Stat.damage, damage * 60f, StatUnit.perSecond);
}
@Override
public void init(){
super.init();
clipSize = Math.max(clipSize, (range + tilesize) * 2);
}
public class TractorBeamBuild extends BaseTurretBuild{
public @Nullable Unit target;
public float lastX, lastY, strength;

View File

@@ -106,7 +106,7 @@ public class Turret extends ReloadTurret{
super.setStats();
stats.add(Stat.inaccuracy, (int)inaccuracy, StatUnit.degrees);
stats.add(Stat.reload, 60f / (reloadTime + 1) * (alternate ? 1 : shots), StatUnit.none);
stats.add(Stat.reload, 60f / (reloadTime) * (alternate ? 1 : shots), StatUnit.none);
stats.add(Stat.targetsAir, targetAir);
stats.add(Stat.targetsGround, targetGround);
if(ammoPerShot != 1) stats.add(Stat.ammoUse, ammoPerShot, StatUnit.perShot);
@@ -144,7 +144,7 @@ public class Turret extends ReloadTurret{
public boolean logicShooting = false;
public @Nullable Posc target;
public Vec2 targetPos = new Vec2();
public BlockUnitc unit = Nulls.blockUnit;
public @Nullable BlockUnitc unit;
public boolean wasShooting, charging;
@Override
@@ -155,7 +155,7 @@ public class Turret extends ReloadTurret{
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
if(type == LAccess.shoot && !unit.isPlayer()){
if(type == LAccess.shoot && (unit == null || !unit.isPlayer())){
targetPos.set(World.unconv((float)p1), World.unconv((float)p2));
logicControlTime = logicControlCooldown;
logicShooting = !Mathf.zero(p3);
@@ -166,7 +166,7 @@ public class Turret extends ReloadTurret{
@Override
public void control(LAccess type, Object p1, double p2, double p3, double p4){
if(type == LAccess.shootp && !unit.isPlayer()){
if(type == LAccess.shootp && (unit == null || !unit.isPlayer())){
logicControlTime = logicControlCooldown;
logicShooting = !Mathf.zero(p2);
@@ -187,16 +187,21 @@ public class Turret extends ReloadTurret{
case shootX -> World.conv(targetPos.x);
case shootY -> World.conv(targetPos.y);
case shooting -> isShooting() ? 1 : 0;
case progress -> Mathf.clamp(reload / reloadTime);
default -> super.sense(sensor);
};
}
public boolean isShooting(){
return (isControlled() ? unit.isShooting() : logicControlled() ? logicShooting : target != null);
return (isControlled() ? (unit != null && unit.isShooting()) : logicControlled() ? logicShooting : target != null);
}
@Override
public Unit unit(){
if(unit == null){
unit = (BlockUnitc)UnitTypes.block.create(team);
unit.tile(this);
}
return (Unit)unit;
}
@@ -205,7 +210,7 @@ public class Turret extends ReloadTurret{
}
public boolean isActive(){
return target != null || wasShooting;
return (target != null || wasShooting) && enabled;
}
public void targetPosition(Posc pos){
@@ -247,10 +252,12 @@ public class Turret extends ReloadTurret{
recoil = Mathf.lerpDelta(recoil, 0f, restitution);
heat = Mathf.lerpDelta(heat, 0f, cooldown);
unit.health(health);
unit.rotation(rotation);
unit.team(team);
unit.set(x, y);
if(unit != null){
unit.health(health);
unit.rotation(rotation);
unit.team(team);
unit.set(x, y);
}
if(logicControlTime > 0){
logicControlTime -= Time.delta;
@@ -353,14 +360,14 @@ public class Turret extends ReloadTurret{
}
protected void updateShooting(){
reload += delta() * peekAmmo().reloadMultiplier * baseReloadSpeed();
if(reload >= reloadTime && !charging){
BulletType type = peekAmmo();
shoot(type);
reload = 0f;
}else{
reload += delta() * peekAmmo().reloadMultiplier * baseReloadSpeed();
reload %= reloadTime;
}
}

View File

@@ -38,6 +38,11 @@ public class BufferedItemBridge extends ExtendingItemBridge{
}
}
@Override
public void doDump(){
dump();
}
@Override
public void write(Writes write){
super.write(write);

View File

@@ -106,8 +106,7 @@ public class Conveyor extends Block implements Autotiler{
public class ConveyorBuild extends Building implements ChainedBuilding{
//parallel array data
public Item[] ids = new Item[capacity];
public float[] xs = new float[capacity];
public float[] ys = new float[capacity];
public float[] xs = new float[capacity], ys = new float[capacity];
//amount of items, always < capacity
public int len = 0;
//next entity
@@ -150,7 +149,7 @@ public class Conveyor extends Block implements Autotiler{
tr1.trns(rotation * 90, tilesize, 0);
tr2.trns(rotation * 90, -tilesize / 2f, xs[i] * tilesize / 2f);
Draw.rect(item.icon(Cicon.medium),
Draw.rect(item.fullIcon,
(tile.x * tilesize + tr1.x * ys[i] + tr2.x),
(tile.y * tilesize + tr1.y * ys[i] + tr2.y),
itemSize, itemSize);

View File

@@ -0,0 +1,187 @@
package mindustry.world.blocks.distribution;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Duct extends Block implements Autotiler{
public float speed = 5f;
public @Load(value = "@-top-#", length = 5) TextureRegion[] topRegions;
public @Load(value = "@-bottom-#", length = 5, fallback = "duct-bottom-#") TextureRegion[] botRegions;
public Duct(String name){
super(name);
group = BlockGroup.transportation;
update = true;
solid = false;
hasItems = true;
conveyorPlacement = true;
unloadable = false;
itemCapacity = 1;
noUpdateDisabled = true;
rotate = true;
envEnabled = Env.space | Env.terrestrial | Env.underwater;
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.itemsMoved, 60f / speed, StatUnit.itemsSecond);
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
int[] bits = getTiling(req, list);
if(bits == null) return;
Draw.scl(bits[1], bits[2]);
Draw.alpha(0.5f);
Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90);
Draw.color();
Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90);
Draw.scl();
}
@Override
public boolean blends(Tile tile, int rotation, int otherx, int othery, int otherrot, Block otherblock){
return otherblock.outputsItems() && blendsArmored(tile, rotation, otherx, othery, otherrot, otherblock);
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{Core.atlas.find("duct-bottom"), topRegions[0]};
}
@Override
public void handlePlacementLine(Seq<BuildPlan> plans){
Placement.calculateDuctBridges(plans, (DuctBridge)Blocks.ductBridge);
}
public class DuctBuild extends Building{
public float progress;
public @Nullable Item current;
public int recDir = 0;
public int blendbits, xscl, yscl, blending;
public @Nullable Building next;
public @Nullable DuctBuild nextc;
@Override
public void draw(){
float rotation = rotdeg();
int r = this.rotation;
//draw extra ducts facing this one for tiling purposes
for(int i = 0; i < 4; i++){
if((blending & (1 << i)) != 0 && !(i == 0 && nextc != null)){
int dir = r - i;
float rot = i == 0 ? rotation : (dir)*90;
drawAt(x + Geometry.d4x(dir) * tilesize*0.75f, y + Geometry.d4y(dir) * tilesize*0.75f, 0, rot, i != 0 ? SliceMode.bottom : SliceMode.top);
}
}
//draw item
if(current != null){
Draw.z(Layer.blockUnder + 0.1f);
Tmp.v1.set(Geometry.d4x(recDir) * tilesize / 2f, Geometry.d4y(recDir) * tilesize / 2f)
.lerp(Geometry.d4x(r) * tilesize / 2f, Geometry.d4y(r) * tilesize / 2f,
Mathf.clamp((progress + 1f) / 2f));
Draw.rect(current.fullIcon, x + Tmp.v1.x, y + Tmp.v1.y, itemSize, itemSize);
}
Draw.scl(xscl, yscl);
drawAt(x, y, blendbits, rotation, SliceMode.none);
Draw.reset();
}
protected void drawAt(float x, float y, int bits, float rotation, SliceMode slice){
Draw.z(Layer.blockUnder);
Draw.rect(sliced(botRegions[bits], slice), x, y, rotation);
Draw.z(Layer.blockUnder + 0.2f);
Draw.color(0.4f, 0.4f, 0.4f, 0.4f);
Draw.rect(sliced(botRegions[bits], slice), x, y, rotation);
Draw.color();
Draw.rect(sliced(topRegions[bits], slice), x, y, rotation);
}
@Override
public void updateTile(){
progress += edelta() / speed * 2f;
if(current != null && next != null){
if(progress >= (1f - 1f/speed) && moveForward(current)){
items.remove(current, 1);
current = null;
progress %= (1f - 1f/speed);
}
}else{
progress = 0;
}
if(current == null && items.total() > 0){
current = items.first();
}
}
@Override
public boolean acceptItem(Building source, Item item){
return current == null && items.total() == 0 &&
(source.block instanceof Duct || Edges.getFacingEdge(source.tile(), tile).relativeTo(tile) == rotation);
}
@Override
public int removeStack(Item item, int amount){
int removed = super.removeStack(item, amount);
if(item == current) current = null;
return removed;
}
@Override
public void handleStack(Item item, int amount, Teamc source){
super.handleStack(item, amount, source);
current = item;
}
@Override
public void handleItem(Building source, Item item){
current = item;
progress = -1f;
recDir = relativeToEdge(source.tile);
items.add(item, 1);
noSleep();
}
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
int[] bits = buildBlending(tile, rotation, null, true);
blendbits = bits[0];
xscl = bits[1];
yscl = bits[2];
blending = bits[4];
next = front();
nextc = next instanceof DuctBuild d ? d : null;
}
}
}

View File

@@ -0,0 +1,149 @@
package mindustry.world.blocks.distribution;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.core.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.input.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
//TODO display range
public class DuctBridge extends Block{
public @Load("@-bridge") TextureRegion bridgeRegion;
public @Load("@-bridge-bottom") TextureRegion bridgeBotRegion;
//public @Load("@-bridge-top") TextureRegion bridgeTopRegion;
public @Load("@-arrow") TextureRegion arrowRegion;
public @Load("@-dir") TextureRegion dirRegion;
public int range = 4;
public float moveDelay = 5f;
public DuctBridge(String name){
super(name);
update = true;
solid = true;
rotate = true;
itemCapacity = 4;
hasItems = true;
group = BlockGroup.transportation;
noUpdateDisabled = true;
envEnabled = Env.space | Env.terrestrial | Env.underwater;
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(dirRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, dirRegion};
}
@Override
public void changePlacementPath(Seq<Point2> points, int rotation){
Placement.calculateNodes(points, this, rotation, (point, other) -> Math.max(Math.abs(point.x - other.x), Math.abs(point.y - other.y)) <= range);
}
public boolean positionsValid(int x1, int y1, int x2, int y2){
if(x1 == x2){
return Math.abs(y1 - y2) <= range;
}else if(y1 == y2){
return Math.abs(x1 - x2) <= range;
}else{
return false;
}
}
public class DuctBridgeBuild extends Building{
public Building[] occupied = new Building[4];
public float progress = 0f;
@Override
public void draw(){
Draw.rect(block.region, x, y);
Draw.rect(dirRegion, x, y, rotdeg());
var link = findLink();
if(link != null){
Draw.z(Layer.power);
Draw.alpha(Renderer.bridgeOpacity);
float
angle = angleTo(link),
cx = (x + link.x)/2f,
cy = (y + link.y)/2f,
len = Math.max(Math.abs(x - link.x), Math.abs(y - link.y)) - size * tilesize;
Draw.rect(bridgeRegion, cx, cy, len, tilesize, angle);
Draw.color(0.4f, 0.4f, 0.4f, 0.4f * Renderer.bridgeOpacity);
Draw.rect(bridgeBotRegion, cx, cy, len, tilesize, angle);
Draw.reset();
Draw.alpha(Renderer.bridgeOpacity);
//Draw.rect(bridgeTopRegion, cx, cy, len, tilesize, angle);
for(float i = 6f; i <= len + size * tilesize - 5f; i += 5f){
Draw.rect(arrowRegion, x + Geometry.d4x(rotation) * i, y + Geometry.d4y(rotation) * i, angle);
}
Draw.reset();
}
}
@Nullable
public DuctBridgeBuild findLink(){
for(int i = 1; i <= range; i++){
Tile other = tile.nearby(Geometry.d4x(rotation) * i, Geometry.d4y(rotation) * i);
if(other.build instanceof DuctBridgeBuild build && build.team == team){
return build;
}
}
return null;
}
@Override
public void updateTile(){
var link = findLink();
if(link != null){
link.occupied[rotation % 4] = this;
if(items.any() && link.items.total() < link.block.itemCapacity){
progress += edelta();
while(progress > moveDelay){
Item next = items.take();
if(next != null && link.items.total() < link.block.itemCapacity){
link.handleItem(this, next);
}
progress -= moveDelay;
}
}
}
if(link == null && items.any()){
Item next = items.first();
if(moveForward(next)){
items.remove(next, 1);
}
}
for(int i = 0; i < 4; i++){
if(occupied[i] == null || occupied[i].rotation != i || !occupied[i].isValid()){
occupied[i] = null;
}
}
}
@Override
public boolean acceptItem(Building source, Item item){
int rel = this.relativeTo(source);
return items.total() < itemCapacity && rel != rotation && occupied[(rel + 2) % 4] == null;
}
}
}

View File

@@ -0,0 +1,125 @@
package mindustry.world.blocks.distribution;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
public class DuctRouter extends Block{
public float speed = 5f;
public @Load(value = "@-top") TextureRegion topRegion;
public DuctRouter(String name){
super(name);
group = BlockGroup.transportation;
update = true;
solid = false;
hasItems = true;
conveyorPlacement = true;
unloadable = false;
itemCapacity = 1;
noUpdateDisabled = true;
rotate = true;
envEnabled = Env.space | Env.terrestrial | Env.underwater;
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.itemsMoved, 60f / speed, StatUnit.itemsSecond);
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, topRegion};
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(topRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
public class DuctBuild extends Building{
public float progress;
public @Nullable Item current;
@Override
public void draw(){
Draw.rect(region, x, y);
Draw.rect(topRegion, x, y, rotdeg());
}
@Override
public void updateTile(){
progress += edelta() / speed * 2f;
if(current != null){
if(progress >= (1f - 1f/speed)){
var target = target();
if(target != null){
target.handleItem(this, current);
cdump = (byte)((cdump + 1) % 3);
items.remove(current, 1);
current = null;
progress %= (1f - 1f/speed);
}
}
}else{
progress = 0;
}
if(current == null && items.total() > 0){
current = items.first();
}
}
@Nullable
public Building target(){
if(current == null) return null;
for(int i = -1; i <= 1; i++){
Building other = nearby(Mathf.mod(rotation + i + cdump, 4));
if(other != null && other.team == team && other.acceptItem(this, current)){
return other;
}
}
return null;
}
@Override
public boolean acceptItem(Building source, Item item){
return current == null && items.total() == 0 &&
(Edges.getFacingEdge(source.tile(), tile).relativeTo(tile) == rotation);
}
@Override
public int removeStack(Item item, int amount){
int removed = super.removeStack(item, amount);
if(item == current) current = null;
return removed;
}
@Override
public void handleStack(Item item, int amount, Teamc source){
super.handleStack(item, amount, source);
current = item;
}
@Override
public void handleItem(Building source, Item item){
current = item;
progress = -1f;
items.add(item, 1);
noSleep();
}
}
}

View File

@@ -23,7 +23,6 @@ import static mindustry.Vars.*;
public class ItemBridge extends Block{
private static BuildPlan otherReq;
public final int timerTransport = timers++;
public int range;
public float transportTime = 2f;
public @Load("@-end") TextureRegion endRegion;
@@ -38,7 +37,6 @@ public class ItemBridge extends Block{
update = true;
solid = true;
hasPower = true;
expanded = true;
itemCapacity = 10;
configurable = true;
hasItems = true;
@@ -95,14 +93,12 @@ public class ItemBridge extends Block{
Tile link = findLink(x, y);
Lines.stroke(2f, Pal.placing);
for(int i = 0; i < 4; i++){
Lines.dashLine(
Drawf.dashLine(Pal.placing,
x * tilesize + Geometry.d4[i].x * (tilesize / 2f + 2),
y * tilesize + Geometry.d4[i].y * (tilesize / 2f + 2),
x * tilesize + Geometry.d4[i].x * (range + 0.5f) * tilesize,
y * tilesize + Geometry.d4[i].y * (range + 0.5f) * tilesize,
range);
x * tilesize + Geometry.d4[i].x * (range) * tilesize,
y * tilesize + Geometry.d4[i].y * (range) * tilesize);
}
Draw.reset();
@@ -149,6 +145,12 @@ public class ItemBridge extends Block{
return null;
}
@Override
public void init(){
super.init();
clipSize = Math.max(clipSize, (range + 0.5f) * tilesize * 2);
}
@Override
public void handlePlacementLine(Seq<BuildPlan> plans){
for(int i = 0; i < plans.size - 1; i++){
@@ -167,18 +169,20 @@ public class ItemBridge extends Block{
public class ItemBridgeBuild extends Building{
public int link = -1;
//TODO awful
public IntSet incoming = new IntSet();
public float uptime;
public float time;
public float time2;
public float cycleSpeed = 1f;
public float transportCounter;
@Override
public void playerPlaced(Object config){
super.playerPlaced(config);
Tile link = findLink(tile.x, tile.y);
if(linkValid(tile, link) && !proximity.contains(link.build)){
if(linkValid(tile, link) && this.link != link.pos() && !proximity.contains(link.build)){
link.build.configure(tile.pos());
}
@@ -286,7 +290,7 @@ public class ItemBridge extends Block{
Tile other = world.tile(link);
if(!linkValid(tile, other)){
dump();
doDump();
uptime = 0f;
}else{
((ItemBridgeBuild)other.build).incoming.add(tile.pos());
@@ -301,20 +305,27 @@ public class ItemBridge extends Block{
}
}
public void doDump(){
//allow dumping multiple times per frame
dumpAccumulate();
}
public void updateTransport(Building other){
if(uptime >= 0.5f && timer(timerTransport, transportTime)){
boolean any = false;
transportCounter += edelta();
while(transportCounter >= transportTime){
Item item = items.take();
if(item != null && other.acceptItem(this, item)){
other.handleItem(this, item);
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 4f, 0.05f); //TODO this is kinda broken, because lerping only happens on a timer
}else{
cycleSpeed = Mathf.lerpDelta(cycleSpeed, 1f, 0.01f);
if(item != null){
items.add(item, 1);
items.undoFlow(item);
}
any = true;
}else if(item != null){
items.add(item, 1);
items.undoFlow(item);
}
transportCounter -= transportTime;
}
cycleSpeed = Mathf.lerpDelta(cycleSpeed, any ? 4f : 1f, any ? 0.05f : 0.01f);
}
@Override

View File

@@ -14,6 +14,7 @@ import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.meta.*;
@@ -22,7 +23,7 @@ import static mindustry.Vars.*;
public class MassDriver extends Block{
public float range;
public float rotateSpeed = 0.04f;
public float rotateSpeed = 5f;
public float translation = 7f;
public int minDistribute = 10;
public float knockback = 4f;
@@ -73,7 +74,7 @@ public class MassDriver extends Block{
//check if a mass driver is selected while placing this driver
if(!control.input.frag.config.isShown()) return;
Building selected = control.input.frag.config.getSelectedTile();
if(selected == null || !(selected.block instanceof MassDriver) || !(selected.within(x * tilesize, y * tilesize, range))) return;
if(selected == null || selected.block != this || !selected.within(x * tilesize, y * tilesize, range)) return;
//if so, draw a dotted line towards it while it is in range
float sin = Mathf.absin(Time.time, 6f, 1f);
@@ -89,7 +90,7 @@ public class MassDriver extends Block{
Draw.reset();
}
public class DriverBulletData implements Poolable{
public static class DriverBulletData implements Poolable{
public MassDriverBuild from, to;
public int[] items = new int[content.items().size];
@@ -105,9 +106,10 @@ public class MassDriver extends Block{
public float rotation = 90;
public float reload = 0f;
public DriverState state = DriverState.idle;
public OrderedSet<Tile> waitingShooters = new OrderedSet<>();
//TODO use queue? this array usually holds about 3 shooters max anyway
public OrderedSet<Building> waitingShooters = new OrderedSet<>();
public Tile currentShooter(){
public Building currentShooter(){
return waitingShooters.isEmpty() ? null : waitingShooters.first();
}
@@ -125,9 +127,11 @@ public class MassDriver extends Block{
reload = Mathf.clamp(reload - edelta() / reloadTime);
}
var current = currentShooter();
//cleanup waiting shooters that are not valid
if(!shooterValid(currentShooter())){
waitingShooters.remove(currentShooter());
if(current != null && !shooterValid(current)){
waitingShooters.remove(current);
}
//switch states
@@ -142,7 +146,7 @@ public class MassDriver extends Block{
//dump when idle or accepting
if(state == DriverState.idle || state == DriverState.accepting){
dump();
dumpAccumulate();
}
//skip when there's no power
@@ -158,7 +162,7 @@ public class MassDriver extends Block{
}
//align to shooter rotation
rotation = Mathf.slerpDelta(rotation, tile.angleTo(currentShooter()), rotateSpeed * efficiency());
rotation = Angles.moveToward(rotation, tile.angleTo(currentShooter()), rotateSpeed * efficiency());
}else if(state == DriverState.shooting){
//if there's nothing to shoot at OR someone wants to shoot at this thing, bail
if(!hasLink || (!waitingShooters.isEmpty() && (itemCapacity - items.total() >= minDistribute))){
@@ -173,15 +177,15 @@ public class MassDriver extends Block{
link.block.itemCapacity - link.items.total() >= minDistribute //must have minimum amount of space
){
MassDriverBuild other = (MassDriverBuild)link;
other.waitingShooters.add(tile);
other.waitingShooters.add(this);
if(reload <= 0.0001f){
//align to target location
rotation = Mathf.slerpDelta(rotation, targetRotation, rotateSpeed * efficiency());
rotation = Angles.moveToward(rotation, targetRotation, rotateSpeed * efficiency());
//fire when it's the first in the queue and angles are ready.
if(other.currentShooter() == tile &&
if(other.currentShooter() == this &&
other.state == DriverState.accepting &&
Angles.near(rotation, targetRotation, 2f) && Angles.near(other.rotation, targetRotation + 180f, 2f)){
//actually fire
@@ -189,7 +193,7 @@ public class MassDriver extends Block{
float timeToArrive = Math.min(bulletLifetime, dst(other) / bulletSpeed);
Time.run(timeToArrive, () -> {
//remove waiting shooters, it's done firing
other.waitingShooters.remove(tile);
other.waitingShooters.remove(this);
other.state = DriverState.idle;
});
//driver is immediately idle
@@ -200,6 +204,12 @@ public class MassDriver extends Block{
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(1f - reload / reloadTime);
return super.sense(sensor);
}
@Override
public void draw(){
Draw.rect(baseRegion, x, y);
@@ -222,9 +232,9 @@ public class MassDriver extends Block{
Lines.stroke(1f);
Drawf.circles(x, y, (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.accent);
for(Tile shooter : waitingShooters){
Drawf.circles(shooter.drawx(), shooter.drawy(), (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.place);
Drawf.arrow(shooter.drawx(), shooter.drawy(), x, y, size * tilesize + sin, 4f + sin, Pal.place);
for(var shooter : waitingShooters){
Drawf.circles(shooter.x, shooter.y, (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.place);
Drawf.arrow(shooter.x, shooter.y, x, y, size * tilesize + sin, 4f + sin, Pal.place);
}
if(linkValid()){
@@ -246,7 +256,7 @@ public class MassDriver extends Block{
if(link == other.pos()){
configure(-1);
return false;
}else if(other.block instanceof MassDriver && other.dst(tile) <= range && other.team == team){
}else if(other.block == block && other.dst(tile) <= range && other.team == team){
configure(other.pos());
return false;
}
@@ -281,11 +291,8 @@ public class MassDriver extends Block{
x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation),
angle, -1f, bulletSpeed, bulletLifetime, data);
shootEffect.at(x + Angles.trnsx(angle, translation),
y + Angles.trnsy(angle, translation), angle);
smokeEffect.at(x + Angles.trnsx(angle, translation),
y + Angles.trnsy(angle, translation), angle);
shootEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle);
smokeEffect.at(x + Angles.trnsx(angle, translation), y + Angles.trnsy(angle, translation), angle);
Effect.shake(shake, shake, this);
@@ -314,16 +321,13 @@ public class MassDriver extends Block{
bullet.remove();
}
protected boolean shooterValid(Tile other){
if(other == null) return true;
if(!(other.build instanceof MassDriverBuild entity)) return false;
return entity.link == tile.pos() && tile.dst(other) <= range;
protected boolean shooterValid(Building other){
return other instanceof MassDriverBuild entity && other.consValid() && entity.block == block && entity.link == pos() && within(other, range);
}
protected boolean linkValid(){
if(link == -1) return false;
Building link = world.build(this.link);
return link instanceof MassDriverBuild && link.team == team && within(link, range);
return world.build(this.link) instanceof MassDriverBuild other && other.block == block && other.team == team && within(other, range);
}
@Override
@@ -352,8 +356,7 @@ public class MassDriver extends Block{
public enum DriverState{
idle, //nothing is shooting at this mass driver and it does not have any target
accepting, //currently getting shot at, unload items
shooting,
unloading;
shooting;
public static final DriverState[] all = values();
}

View File

@@ -11,7 +11,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.production.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -21,7 +20,7 @@ public class PayloadConveyor extends Block{
public @Load("@-top") TextureRegion topRegion;
public @Load("@-edge") TextureRegion edgeRegion;
public Interp interp = Interp.pow5;
public float payloadLimit = 2.5f;
public float payloadLimit = 2.9f;
public PayloadConveyor(String name){
super(name);
@@ -51,6 +50,13 @@ public class PayloadConveyor extends Block{
}
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.payloadCapacity, (payloadLimit), StatUnit.blocksSquared);
}
public class PayloadConveyorBuild extends Building{
public @Nullable Payload item;
public float progress, itemRotation, animation;
@@ -59,6 +65,16 @@ public class PayloadConveyor extends Block{
public boolean blocked;
public int step = -1, stepAccepted = -1;
@Override
public boolean canControlSelect(Player player){
return this.item == null && !player.unit().spawnedByCore && player.unit().hitSize / tilesize <= payloadLimit && player.tileOn().build == this;
}
@Override
public void onControlSelect(Player player){
acceptPlayerPayload(player, p -> item = p);
}
@Override
public Payload takePayload(){
Payload t = item;
@@ -108,6 +124,9 @@ public class PayloadConveyor extends Block{
progress = time() % moveTime;
updatePayload();
if(item != null && next == null){
PayloadBlock.pushOutput(item, progress / moveTime);
}
//TODO nondeterministic input priority
int curStep = curStep();
@@ -283,7 +302,7 @@ public class PayloadConveyor extends Block{
if(direction == rotation){
return !blocked || next != null;
}
return PayloadAcceptor.blends(this, direction);
return PayloadBlock.blends(this, direction);
}
protected TextureRegion clipRegion(Rect bounds, Rect sprite, TextureRegion region){

View File

@@ -7,6 +7,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.world.blocks.payloads.*;
public class PayloadRouter extends PayloadConveyor{
@@ -28,6 +29,7 @@ public class PayloadRouter extends PayloadConveyor{
public class PayloadRouterBuild extends PayloadConveyorBuild{
public float smoothRot;
public float controlTime = -1f;
@Override
public void add(){
@@ -36,7 +38,7 @@ public class PayloadRouter extends PayloadConveyor{
}
public void pickNext(){
if(item != null){
if(item != null && controlTime <= 0f){
int rotations = 0;
do{
rotation = (rotation + 1) % 4;
@@ -47,6 +49,16 @@ public class PayloadRouter extends PayloadConveyor{
}
}
@Override
public void control(LAccess type, double p1, double p2, double p3, double p4){
super.control(type, p1, p2, p3, p4);
if(type == LAccess.config){
rotation = (int)p1;
//when manually controlled, routers do not turn automatically for a while, same as turrets
controlTime = Building.timeToUncontrol;
}
}
@Override
public void handlePayload(Building source, Payload payload){
super.handlePayload(source, payload);
@@ -62,6 +74,7 @@ public class PayloadRouter extends PayloadConveyor{
public void updateTile(){
super.updateTile();
controlTime -= Time.delta;
smoothRot = Mathf.slerpDelta(smoothRot, rotdeg(), 0.2f);
}

View File

@@ -145,7 +145,7 @@ public class StackConveyor extends Block implements Autotiler{
//item
float size = itemSize * Mathf.lerp(Math.min((float)items.total() / itemCapacity, 1), 1f, 0.4f);
Drawf.shadow(Tmp.v1.x, Tmp.v1.y, size * 1.2f);
Draw.rect(lastItem.icon(Cicon.medium), Tmp.v1.x, Tmp.v1.y, size, size, 0);
Draw.rect(lastItem.fullIcon, Tmp.v1.x, Tmp.v1.y, size, size, 0);
}
@Override

View File

@@ -0,0 +1,46 @@
package mindustry.world.blocks.environment;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.world.*;
public class Bush extends Prop{
public @Load(value = "@-bot", fallback = "@") TextureRegion botRegion;
public @Load(value = "@-center") TextureRegion centerRegion;
public int lobesMin = 7, lobesMax = 7;
public float botAngle = 60f, origin = 0.1f;
public float sclMin = 30f, sclMax = 50f, magMin = 5f, magMax = 15f, timeRange = 40f, spread = 0f;
static Rand rand = new Rand();
public Bush(String name){
super(name);
variants = 0;
}
@Override
public void drawBase(Tile tile){
rand.setSeed(tile.pos());
float offset = rand.random(180f);
int lobes = rand.random(lobesMin, lobesMax);
for(int i = 0; i < lobes; i++){
float ba = i / (float)lobes * 360f + offset + rand.range(spread), angle = ba + Mathf.sin(Time.time + rand.random(0, timeRange), rand.random(sclMin, sclMax), rand.random(magMin, magMax));
float w = region.width * Draw.scl, h = region.height * Draw.scl;
var region = Angles.angleDist(ba, 225f) <= botAngle ? botRegion : this.region;
Draw.rect(region,
tile.worldx() - Angles.trnsx(angle, origin) + w*0.5f, tile.worldy() - Angles.trnsy(angle, origin),
w, h,
origin*4f, h/2f,
angle
);
}
if(centerRegion.found()){
Draw.rect(centerRegion, tile.worldx(), tile.worldy());
}
}
}

View File

@@ -4,7 +4,6 @@ import arc.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.g2d.TextureAtlas.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
@@ -15,7 +14,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.MultiPacker.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
@@ -50,8 +48,6 @@ public class Floor extends Block{
public @Nullable Liquid liquidDrop = null;
/** Multiplier for pumped liquids, used for deep water. */
public float liquidMultiplier = 1f;
/** item that drops from this block, used for drills */
public @Nullable Item itemDrop = null;
/** whether this block can be drowned in */
public boolean isLiquid;
/** if true, this block cannot be mined by players. useful for annoying things like sand. */
@@ -119,7 +115,7 @@ public class Floor extends Block{
if(wall == null) wall = Blocks.air;
if(decoration == Blocks.air){
decoration = content.blocks().min(b -> b instanceof Boulder && b.minfo.mod == null && b.breakable ? mapColor.diff(b.mapColor) : Float.POSITIVE_INFINITY);
decoration = content.blocks().min(b -> b instanceof Prop && b.minfo.mod == null && b.breakable ? mapColor.diff(b.mapColor) : Float.POSITIVE_INFINITY);
}
if(isLiquid && walkEffect == Fx.none){
@@ -134,7 +130,7 @@ public class Floor extends Block{
@Override
public void createIcons(MultiPacker packer){
super.createIcons(packer);
packer.add(PageType.editor, "editor-" + name, Core.atlas.getPixmap((AtlasRegion)icon(Cicon.full)).crop());
packer.add(PageType.editor, "editor-" + name, Core.atlas.getPixmap(fullIcon).crop());
if(blendGroup != this){
return;
@@ -147,16 +143,13 @@ public class Floor extends Block{
}
}
Color color = new Color();
Color color2 = new Color();
PixmapRegion image = Core.atlas.getPixmap((AtlasRegion)icons()[0]);
PixmapRegion image = Core.atlas.getPixmap(icons()[0]);
PixmapRegion edge = Core.atlas.getPixmap("edge-stencil");
Pixmap result = new Pixmap(edge.width, edge.height);
for(int x = 0; x < edge.width; x++){
for(int y = 0; y < edge.height; y++){
edge.getPixel(x, y, color);
result.draw(x, y, color.mul(color2.set(image.getPixel(x % image.width, y % image.height))));
result.set(x, y, Color.muli(edge.get(x, y), image.get(x % image.width, y % image.height)));
}
}

View File

@@ -14,8 +14,8 @@ import static mindustry.Vars.*;
/**An overlay ore for a specific item type.*/
public class OreBlock extends OverlayFloor{
public OreBlock(Item ore){
super("ore-" + ore.name);
public OreBlock(String name, Item ore){
super(name);
this.localizedName = ore.localizedName;
this.itemDrop = ore;
this.variants = 3;
@@ -23,6 +23,10 @@ public class OreBlock extends OverlayFloor{
this.useColor = true;
}
public OreBlock(Item ore){
this("ore-" + ore.name, ore);
}
/** For mod use only!*/
public OreBlock(String name){
super(name);
@@ -39,25 +43,20 @@ public class OreBlock extends OverlayFloor{
@OverrideCallSuper
public void createIcons(MultiPacker packer){
for(int i = 0; i < variants; i++){
Pixmap image = new Pixmap(32, 32);
PixmapRegion shadow = Core.atlas.getPixmap(itemDrop.name + (i + 1));
Pixmap image = shadow.crop();
int offset = image.getWidth() / tilesize - 1;
Color color = new Color();
int offset = image.width / tilesize - 1;
int shadowColor = Color.rgba8888(0, 0, 0, 0.3f);
for(int x = 0; x < image.getWidth(); x++){
for(int y = offset; y < image.getHeight(); y++){
shadow.getPixel(x, y - offset, color);
if(color.a > 0.001f){
color.set(0, 0, 0, 0.3f);
image.draw(x, y, color);
for(int x = 0; x < image.width; x++){
for(int y = offset; y < image.height; y++){
if(shadow.getA(x, y) == 0 && shadow.getA(x, y - offset) != 0){
image.setRaw(x, y, shadowColor);
}
}
}
image.draw(shadow);
packer.add(PageType.environment, name + (i + 1), image);
packer.add(PageType.editor, "editor-" + name + (i + 1), image);

View File

@@ -3,26 +3,25 @@ package mindustry.world.blocks.environment;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.content.*;
import mindustry.world.*;
public class Boulder extends Block{
public class Prop extends Block{
public int variants;
public Boulder(String name){
public Prop(String name){
super(name);
breakable = true;
alwaysReplace = true;
instantDeconstruct = true;
deconstructThreshold = 0.35f;
breakEffect = Fx.breakProp;
}
@Override
public void drawBase(Tile tile){
if(variants > 0){
Draw.rect(variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))], tile.worldx(), tile.worldy());
}else{
Draw.rect(region, tile.worldx(), tile.worldy());
}
Draw.rect(variants > 0 ? variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))] : region, tile.worldx(), tile.worldy());
}
@Override

View File

@@ -0,0 +1,25 @@
package mindustry.world.blocks.environment;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.annotations.Annotations.*;
import mindustry.world.*;
public class StaticClusterWall extends StaticWall{
public @Load(value = "@-cluster#", length = 1) TextureRegion[] clusters;
public StaticClusterWall(String name){
super(name);
variants = 1;
}
@Override
public void drawBase(Tile tile){
super.drawBase(tile);
if(Mathf.randomSeed(tile.pos(), 10) < 2){
Draw.rect(clusters[0], tile.worldx(), tile.worldy(), Mathf.randomSeedRange(tile.pos() + 1, 180f));
}
}
}

View File

@@ -10,7 +10,7 @@ import mindustry.world.*;
import static mindustry.Vars.*;
public class StaticWall extends Boulder{
public class StaticWall extends Prop{
public @Load("@-large") TextureRegion large;
public TextureRegion[][] split;
@@ -34,6 +34,11 @@ public class StaticWall extends Boulder{
}else{
Draw.rect(region, tile.worldx(), tile.worldy());
}
//draw ore on top
if(tile.overlay() instanceof WallOreBlock ore){
ore.drawBase(tile);
}
}
@Override

View File

@@ -2,7 +2,6 @@ package mindustry.world.blocks.environment;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.graphics.*;
@@ -15,16 +14,17 @@ public class TreeBlock extends Block{
public TreeBlock(String name){
super(name);
solid = true;
expanded = true;
clipSize = 90;
}
@Override
public void drawBase(Tile tile){
float x = tile.worldx(), y = tile.worldy();
float rot = Mathf.randomSeed(tile.pos(), 0, 4) * 90 + Mathf.sin(Time.time + x, 50f, 0.5f) + Mathf.sin(Time.time - y, 65f, 0.9f) + Mathf.sin(Time.time + y - x, 85f, 0.9f);
float w = region.width * Draw.scl, h = region.height * Draw.scl;
float scl = 30f, mag = 0.2f;
float
x = tile.worldx(), y = tile.worldy(),
rot = Mathf.randomSeed(tile.pos(), 0, 4) * 90 + Mathf.sin(Time.time + x, 50f, 0.5f) + Mathf.sin(Time.time - y, 65f, 0.9f) + Mathf.sin(Time.time + y - x, 85f, 0.9f),
w = region.width * Draw.scl, h = region.height * Draw.scl,
scl = 30f, mag = 0.2f;
if(shadow.found()){
Draw.z(Layer.power - 1);
@@ -32,15 +32,9 @@ public class TreeBlock extends Block{
}
Draw.z(Layer.power + 1);
Draw.rectv(region, x, y, w, h, rot, vec -> {
vec.add(
Mathf.sin(vec.y*3 + Time.time, scl, mag) + Mathf.sin(vec.x*3 - Time.time, 70, 0.8f),
Mathf.cos(vec.x*3 + Time.time + 8, scl + 6f, mag * 1.1f) + Mathf.sin(vec.y*3 - Time.time, 50, 0.2f)
);
});
}
void tweak(Vec2 vec){
Draw.rectv(region, x, y, w, h, rot, vec -> vec.add(
Mathf.sin(vec.y*3 + Time.time, scl, mag) + Mathf.sin(vec.x*3 - Time.time, 70, 0.8f),
Mathf.cos(vec.x*3 + Time.time + 8, scl + 6f, mag * 1.1f) + Mathf.sin(vec.y*3 - Time.time, 50, 0.2f)
));
}
}

View File

@@ -0,0 +1,16 @@
package mindustry.world.blocks.environment;
import mindustry.type.*;
/**An overlay ore that draws on top of walls. */
public class WallOreBlock extends OreBlock{
public WallOreBlock(Item ore){
super("wall-ore-" + ore.name, ore);
}
//mods only
public WallOreBlock(String name){
super(name);
}
}

View File

@@ -0,0 +1,30 @@
package mindustry.world.blocks.environment;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.world.*;
public class WavingProp extends Prop{
public WavingProp(String name){
super(name);
}
@Override
public void drawBase(Tile tile){
var region = variants > 0 ? variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))] : this.region;
float
x = tile.worldx(), y = tile.worldy(),
rotmag = 3f, rotscl = 0.5f,
rot = Mathf.randomSeedRange(tile.pos(), 20f) - 45 + Mathf.sin(Time.time + x, 50f * rotscl, 0.5f * rotmag) + Mathf.sin(Time.time - y, 65f * rotscl, 0.9f* rotmag) + Mathf.sin(Time.time + y - x, 85f * rotscl, 0.9f* rotmag),
w = region.width * Draw.scl, h = region.height * Draw.scl,
scl = 30f, mag = 0.3f;
Draw.rectv(region, x, y, w, h, rot, vec -> vec.add(
Mathf.sin(vec.y*3 + Time.time, scl, mag) + Mathf.sin(vec.x*3 - Time.time, 70, 0.8f),
Mathf.cos(vec.x*3 + Time.time + 8, scl + 6f, mag * 1.1f) + Mathf.sin(vec.y*3 - Time.time, 50, 0.2f)
));
}
}

View File

@@ -0,0 +1,24 @@
package mindustry.world.blocks.environment;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.world.*;
public class WobbleProp extends Prop{
public float wscl = 25f, wmag = 0.4f, wtscl = 1f, wmag2 = 1f;
public WobbleProp(String name){
super(name);
}
@Override
public void drawBase(Tile tile){
var region = variants > 0 ? variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))] : this.region;
Draw.rectv(region, tile.worldx(), tile.worldy(), region.width * Draw.scl, region.height * Draw.scl, 0, vec -> vec.add(
Mathf.sin(vec.y*3 + Time.time, wscl, wmag) + Mathf.sin(vec.x*3 - Time.time, 70 * wtscl, 0.8f * wmag2),
Mathf.cos(vec.x*3 + Time.time + 8, wscl + 6f, wmag * 1.1f) + Mathf.sin(vec.y*3 - Time.time, 50 * wtscl, 0.2f * wmag2)
));
}
}

View File

@@ -1,169 +0,0 @@
package mindustry.world.blocks.experimental;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.production.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class BlockForge extends PayloadAcceptor{
public float buildSpeed = 0.4f;
public int minBlockSize = 1, maxBlockSize = 2;
public BlockForge(String name){
super(name);
size = 3;
update = true;
outputsPayload = true;
hasItems = true;
configurable = true;
hasPower = true;
rotate = true;
config(Block.class, (BlockForgeBuild tile, Block block) -> {
if(tile.recipe != block) tile.progress = 0f;
tile.recipe = block;
});
consumes.add(new ConsumeItemDynamic((BlockForgeBuild e) -> e.recipe != null ? e.recipe.requirements : ItemStack.empty));
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, outRegion};
}
@Override
public void setBars(){
super.setBars();
bars.add("progress", (BlockForgeBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe == null ? 0f : (entity.progress / entity.recipe.buildCost)));
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.output, "@x@ ~ @x@", minBlockSize, minBlockSize, maxBlockSize, maxBlockSize);
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
public class BlockForgeBuild extends PayloadAcceptorBuild<BuildPayload>{
public @Nullable Block recipe;
public float progress, time, heat;
@Override
public boolean acceptItem(Building source, Item item){
return items.get(item) < getMaximumAccepted(item);
}
@Override
public int getMaximumAccepted(Item item){
if(recipe == null) return 0;
for(ItemStack stack : recipe.requirements){
if(stack.item == item) return stack.amount * 2;
}
return 0;
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return false;
}
@Override
public void updateTile(){
boolean produce = recipe != null && consValid() && payload == null;
if(produce){
progress += buildSpeed * edelta();
if(progress >= recipe.buildCost){
consume();
payload = new BuildPayload(recipe, team);
payVector.setZero();
progress %= 1f;
}
}
heat = Mathf.lerpDelta(heat, Mathf.num(produce), 0.3f);
time += heat * delta();
moveOutPayload();
}
@Override
public void buildConfiguration(Table table){
Seq<Block> blocks = Vars.content.blocks().select(b -> b.isVisible() && b.size >= minBlockSize && b.size <= maxBlockSize);
ItemSelection.buildTable(table, blocks, () -> recipe, this::configure);
}
@Override
public Object config(){
return recipe;
}
@Override
public void draw(){
Draw.rect(region, x, y);
Draw.rect(outRegion, x, y, rotdeg());
if(recipe != null){
Draw.draw(Layer.blockOver, () -> Drawf.construct(this, recipe, 0, progress / recipe.buildCost, heat, time));
}
drawPayload();
}
@Override
public void drawSelect(){
if(recipe != null){
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f;
TextureRegion icon = recipe.icon(Cicon.medium);
Draw.mixcol(Color.darkGray, 1f);
//Fixes size because modded content icons are not scaled
Draw.rect(icon, dx - 0.7f, dy - 1f, Draw.scl * Draw.xscl * 24f, Draw.scl * Draw.yscl * 24f);
Draw.reset();
Draw.rect(icon, dx, dy, Draw.scl * Draw.xscl * 24f, Draw.scl * Draw.yscl * 24f);
}
}
@Override
public void write(Writes write){
super.write(write);
write.s(recipe == null ? -1 : recipe.id);
write.f(progress);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
recipe = Vars.content.block(read.s());
progress = read.f();
}
}
}

View File

@@ -9,11 +9,10 @@ import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.production.*;
import static mindustry.Vars.*;
public class BlockLoader extends PayloadAcceptor{
public class BlockLoader extends PayloadBlock{
public final int timerLoad = timers++;
public float loadTime = 2f;
@@ -58,7 +57,7 @@ public class BlockLoader extends PayloadAcceptor{
Draw.rect(topRegion, req.drawx(), req.drawy());
}
public class BlockLoaderBuild extends PayloadAcceptorBuild<BuildPayload>{
public class BlockLoaderBuild extends PayloadBlockBuild<BuildPayload>{
@Override
public boolean acceptPayload(Building source, Payload payload){
@@ -90,7 +89,6 @@ public class BlockLoader extends PayloadAcceptor{
Draw.rect(outRegion, x, y, rotdeg());
Draw.z(Layer.blockOver);
payRotation = rotdeg();
drawPayload();
Draw.z(Layer.blockOver + 0.1f);

View File

@@ -197,14 +197,15 @@ public class LogicBlock extends Block{
public Seq<LogicLink> links = new Seq<>();
public boolean checkedDuplicates = false;
public void readCompressed(byte[] data, boolean relative){
DataInputStream stream = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
/** Block of code to run after load. */
public @Nullable Runnable loadBlock;
try{
public void readCompressed(byte[] data, boolean relative){
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)))){
int version = stream.read();
int bytelen = stream.readInt();
if(bytelen > maxByteLen) throw new RuntimeException("Malformed logic data! Length: " + bytelen);
if(bytelen > maxByteLen) throw new IOException("Malformed logic data! Length: " + bytelen);
byte[] bytes = new byte[bytelen];
stream.readFully(bytes);
@@ -243,6 +244,7 @@ public class LogicBlock extends Block{
updateCode(new String(bytes, charset));
}catch(Exception ignored){
//invalid logic doesn't matter here
}
}
@@ -350,11 +352,8 @@ public class LogicBlock extends Block{
executor.load(asm);
}catch(Exception e){
Log.err("Failed to compile logic program @", code);
Log.err(e);
//handle malformed code and replace it with nothing
executor.load("");
executor.load(code = "");
}
}
}
@@ -372,6 +371,12 @@ public class LogicBlock extends Block{
@Override
public void updateTile(){
//load up code from read()
if(loadBlock != null){
loadBlock.run();
loadBlock = null;
}
executor.team = team;
if(!checkedDuplicates){
@@ -447,9 +452,9 @@ public class LogicBlock extends Block{
}
public Seq<LogicLink> relativeConnections(){
Seq<LogicLink> copy = new Seq<>(links.size);
for(LogicLink l : links){
LogicLink c = l.copy();
var copy = new Seq<LogicLink>(links.size);
for(var l : links){
var c = l.copy();
c.x -= tileX();
c.y -= tileY();
copy.add(c);
@@ -582,8 +587,7 @@ public class LogicBlock extends Block{
//skip memory, it isn't used anymore
read.skip(memory * 8);
updateCode(code, false, asm -> {
loadBlock = () -> updateCode(code, false, asm -> {
//load up the variables that were stored
for(int i = 0; i < varcount; i++){
BVar dest = asm.getVar(names[i]);
@@ -592,6 +596,7 @@ public class LogicBlock extends Block{
}
}
});
}
}
}

View File

@@ -0,0 +1,29 @@
package mindustry.world.blocks.payloads;
import mindustry.gen.*;
import mindustry.world.blocks.payloads.NuclearWarhead.*;
public class BallisticSilo extends PayloadBlock{
public BallisticSilo(String name){
super(name);
}
public class BallisticSiloBuild extends PayloadBlockBuild<BuildPayload>{
@Override
public boolean acceptPayload(Building source, Payload payload){
return this.payload == null && payload instanceof BuildPayload b && b.build instanceof NuclearWarheadBuild;
}
@Override
public void updateTile(){
moveInPayload();
}
@Override
public void draw(){
super.draw();
drawPayload();
}
}
}

View File

@@ -0,0 +1,89 @@
package mindustry.world.blocks.payloads;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
/** Configurable BlockProducer variant. */
public class BlockForge extends BlockProducer{
public float buildSpeed = 0.4f;
public int minBlockSize = 1, maxBlockSize = 2;
public BlockForge(String name){
super(name);
size = 3;
configurable = true;
config(Block.class, (BlockForgeBuild tile, Block block) -> {
if(tile.recipe != block) tile.progress = 0f;
if(canProduce(block)){
tile.recipe = block;
}
});
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.output, "@x@ ~ @x@", minBlockSize, minBlockSize, maxBlockSize, maxBlockSize);
}
public boolean canProduce(Block b){
return b.isVisible() && b.size >= minBlockSize && b.size <= maxBlockSize;
}
public class BlockForgeBuild extends BlockProducerBuild{
public @Nullable Block recipe;
@Override
public @Nullable Block recipe(){
return recipe;
}
@Override
public void buildConfiguration(Table table){
ItemSelection.buildTable(table, content.blocks().select(BlockForge.this::canProduce), () -> recipe, this::configure);
}
@Override
public Object config(){
return recipe;
}
@Override
public void drawSelect(){
if(recipe != null){
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f;
TextureRegion icon = recipe.uiIcon;
Draw.mixcol(Color.darkGray, 1f);
//Fixes size because modded content icons are not scaled
Draw.rect(icon, dx - 0.7f, dy - 1f, Draw.scl * Draw.xscl * 24f, Draw.scl * Draw.yscl * 24f);
Draw.reset();
Draw.rect(icon, dx, dy, Draw.scl * Draw.xscl * 24f, Draw.scl * Draw.yscl * 24f);
}
}
@Override
public void write(Writes write){
super.write(write);
write.s(recipe == null ? -1 : recipe.id);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
recipe = Vars.content.block(read.s());
}
}
}

View File

@@ -0,0 +1,143 @@
package mindustry.world.blocks.payloads;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import static mindustry.Vars.*;
/** Generic building that produces other buildings. */
public abstract class BlockProducer extends PayloadBlock{
public float buildSpeed = 0.4f;
public BlockProducer(String name){
super(name);
size = 3;
update = true;
outputsPayload = true;
hasItems = true;
hasPower = true;
rotate = true;
consumes.add(new ConsumeItemDynamic((BlockProducerBuild e) -> e.recipe() != null ? e.recipe().requirements : ItemStack.empty));
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, outRegion};
}
@Override
public void setBars(){
super.setBars();
bars.add("progress", (BlockProducerBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe() == null ? 0f : (entity.progress / entity.recipe().buildCost)));
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
public abstract class BlockProducerBuild extends PayloadBlockBuild<BuildPayload>{
public float progress, time, heat;
public abstract @Nullable Block recipe();
@Override
public boolean acceptItem(Building source, Item item){
return items.get(item) < getMaximumAccepted(item);
}
@Override
public int getMaximumAccepted(Item item){
if(recipe() == null) return 0;
for(ItemStack stack : recipe().requirements){
if(stack.item == item) return stack.amount * 2;
}
return 0;
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return false;
}
@Override
public void updateTile(){
var recipe = recipe();
boolean produce = recipe != null && consValid() && payload == null;
if(produce){
progress += buildSpeed * edelta();
if(progress >= recipe.buildCost){
consume();
payload = new BuildPayload(recipe, team);
payVector.setZero();
progress %= 1f;
}
}
heat = Mathf.lerpDelta(heat, Mathf.num(produce), 0.15f);
time += heat * delta();
moveOutPayload();
}
@Override
public void draw(){
Draw.rect(region, x, y);
Draw.rect(outRegion, x, y, rotdeg());
var recipe = recipe();
if(recipe != null){
Drawf.shadow(x, y, recipe.size * tilesize * 2f, progress / recipe.buildCost);
Draw.draw(Layer.blockBuilding, () -> {
Draw.color(Pal.accent);
for(TextureRegion region : recipe.getGeneratedIcons()){
Shaders.blockbuild.region = region;
Shaders.blockbuild.progress = progress / recipe.buildCost;
Draw.rect(region, x, y, recipe.rotate ? rotdeg() : 0);
Draw.flush();
}
Draw.color();
});
Draw.z(Layer.blockBuilding + 1);
Draw.color(Pal.accent, heat);
Lines.lineAngleCenter(x + Mathf.sin(time, 10f, Vars.tilesize / 2f * recipe.size + 1f), y, 90, recipe.size * Vars.tilesize + 1f);
Draw.reset();
}
drawPayload();
}
@Override
public void write(Writes write){
super.write(write);
write.f(progress);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
progress = read.f();
}
}
}

View File

@@ -5,7 +5,6 @@ import arc.util.io.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.*;
@@ -34,6 +33,16 @@ public class BuildPayload implements Payload{
build.dropped();
}
@Override
public float x(){
return build.x;
}
@Override
public float y(){
return build.y;
}
@Override
public float size(){
return build.block.size * tilesize;
@@ -55,11 +64,11 @@ public class BuildPayload implements Payload{
@Override
public void draw(){
Drawf.shadow(build.x, build.y, build.block.size * tilesize * 2f);
Draw.rect(build.block.icon(Cicon.full), build.x, build.y);
Draw.rect(build.block.fullIcon, build.x, build.y);
}
@Override
public TextureRegion icon(Cicon icon){
return block().icon(icon);
public TextureRegion icon(){
return block().fullIcon;
}
}

View File

@@ -0,0 +1,18 @@
package mindustry.world.blocks.payloads;
import mindustry.gen.*;
import mindustry.world.*;
public class NuclearWarhead extends Block{
public float radius = 100f;
public NuclearWarhead(String name){
super(name);
solid = true;
update = true;
}
public class NuclearWarheadBuild extends Building{
}
}

View File

@@ -1,16 +1,16 @@
package mindustry.world.blocks.payloads;
import arc.graphics.g2d.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public interface Payload{
public interface Payload extends Position{
int payloadUnit = 0, payloadBlock = 1;
/** sets this payload's position on the map. */
@@ -22,6 +22,9 @@ public interface Payload{
/** @return hitbox size of the payload. */
float size();
float x();
float y();
/** @return whether this payload was dumped. */
default boolean dump(){
return false;
@@ -41,7 +44,17 @@ public interface Payload{
void write(Writes write);
/** @return icon describing the contents. */
TextureRegion icon(Cicon icon);
TextureRegion icon();
@Override
default float getX(){
return x();
}
@Override
default float getY(){
return y();
}
static void write(@Nullable Payload payload, Writes write){
if(payload == null){

View File

@@ -0,0 +1,233 @@
package mindustry.world.blocks.payloads;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class PayloadBlock extends Block{
public float payloadSpeed = 0.5f, payloadRotateSpeed = 5f;
public @Load(value = "@-top", fallback = "factory-top-@size") TextureRegion topRegion;
public @Load(value = "@-out", fallback = "factory-out-@size") TextureRegion outRegion;
public @Load(value = "@-in", fallback = "factory-in-@size") TextureRegion inRegion;
public PayloadBlock(String name){
super(name);
update = true;
sync = true;
}
public static boolean blends(Building build, int direction){
int size = build.block.size;
int trns = build.block.size/2 + 1;
Building accept = build.nearby(Geometry.d4(direction).x * trns, Geometry.d4(direction).y * trns);
return accept != null &&
accept.block.outputsPayload &&
//if size is the same, block must either be facing this one, or not be rotating
((accept.block.size == size
&& Math.abs(accept.tileX() - build.tileX()) % size == 0 //check alignment
&& Math.abs(accept.tileY() - build.tileY()) % size == 0
&& ((accept.block.rotate && accept.tileX() + Geometry.d4(accept.rotation).x * size == build.tileX() && accept.tileY() + Geometry.d4(accept.rotation).y * size == build.tileY())
|| !accept.block.rotate
|| !accept.block.outputFacing)) ||
//if the other block is smaller, check alignment
(accept.block.size != size &&
(accept.rotation % 2 == 0 ? //check orientation; make sure it's aligned properly with this block.
Math.abs(accept.y - build.y) <= Math.abs(size * tilesize - accept.block.size * tilesize)/2f : //check Y alignment
Math.abs(accept.x - build.x) <= Math.abs(size * tilesize - accept.block.size * tilesize)/2f //check X alignment
)) && (!accept.block.rotate || accept.front() == build || !accept.block.outputFacing) //make sure it's facing this block
);
}
public static void pushOutput(Payload payload, float progress){
float thresh = 0.55f;
if(progress >= thresh){
boolean legStep = payload instanceof UnitPayload u && u.unit.type.allowLegStep;
float size = payload.size(), radius = size/2f, x = payload.x(), y = payload.y(), scl = Mathf.clamp(((progress - thresh) / (1f - thresh)) * 1.1f);
Groups.unit.intersect(x - size/2f, y - size/2f, size, size, u -> {
float dst = u.dst(payload);
float rs = radius + u.hitSize/2f;
if(u.isGrounded() && u.type.allowLegStep == legStep && dst < rs){
u.vel.add(Tmp.v1.set(u.x - x, u.y - y).setLength(Math.min(rs - dst, 1f)).scl(scl));
}
});
}
}
public class PayloadBlockBuild<T extends Payload> extends Building{
public @Nullable T payload;
//TODO redundant; already stored in payload?
public Vec2 payVector = new Vec2();
public float payRotation;
public boolean carried;
public boolean acceptUnitPayload(Unit unit){
return false;
}
@Override
public boolean canControlSelect(Player player){
return !player.unit().spawnedByCore && this.payload == null && acceptUnitPayload(player.unit()) && player.tileOn().build == this;
}
@Override
public void onControlSelect(Player player){
float x = player.x, y = player.y;
acceptPlayerPayload(player, p -> payload = (T)p);
this.payVector.set(x, y).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f);
this.payRotation = player.unit().rotation;
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return this.payload == null;
}
@Override
public void handlePayload(Building source, Payload payload){
this.payload = (T)payload;
this.payVector.set(source).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f);
this.payRotation = payload.rotation();
updatePayload();
}
@Override
public Payload getPayload(){
return payload;
}
@Override
public void pickedUp(){
carried = true;
}
@Override
public void drawTeamTop(){
carried = false;
}
@Override
public Payload takePayload(){
T t = payload;
payload = null;
return t;
}
@Override
public void onRemoved(){
super.onRemoved();
if(payload != null && !carried) payload.dump();
}
public boolean blends(int direction){
return PayloadBlock.blends(this, direction);
}
public void updatePayload(){
if(payload != null){
payload.set(x + payVector.x, y + payVector.y, payRotation);
}
}
/** @return true if the payload is in position. */
public boolean moveInPayload(){
return moveInPayload(true);
}
/** @return true if the payload is in position. */
public boolean moveInPayload(boolean rotate){
if(payload == null) return false;
updatePayload();
if(rotate){
payRotation = Angles.moveToward(payRotation, rotate ? rotdeg() : 90f, payloadRotateSpeed * edelta());
}
payVector.approach(Vec2.ZERO, payloadSpeed * delta());
return hasArrived();
}
public void moveOutPayload(){
if(payload == null) return;
updatePayload();
Vec2 dest = Tmp.v1.trns(rotdeg(), size * tilesize/2f);
payRotation = Angles.moveToward(payRotation, rotdeg(), payloadRotateSpeed * edelta());
payVector.approach(dest, payloadSpeed * delta());
Building front = front();
boolean canDump = front == null || !front.tile().solid();
boolean canMove = front != null && (front.block.outputsPayload || front.block.acceptsPayload);
if(canDump && !canMove){
pushOutput(payload, 1f - (payVector.dst(dest) / (size * tilesize / 2f)));
}
if(payVector.within(dest, 0.001f)){
payVector.clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f);
if(canMove){
if(movePayload(payload)){
payload = null;
}
}else if(canDump){
dumpPayload();
}
}
}
public void dumpPayload(){
if(payload.dump()){
payload = null;
}
}
public boolean hasArrived(){
return payVector.isZero(0.01f);
}
public void drawPayload(){
if(payload != null){
updatePayload();
Draw.z(Layer.blockOver);
payload.draw();
}
}
@Override
public void write(Writes write){
super.write(write);
write.f(payVector.x);
write.f(payVector.y);
write.f(payRotation);
Payload.write(payload, write);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
payVector.set(read.f(), read.f());
payRotation = read.f();
payload = Payload.read(read);
}
}
}

View File

@@ -0,0 +1,481 @@
package mindustry.world.blocks.payloads;
import arc.audio.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
import static mindustry.world.blocks.payloads.PayloadMassDriver.PayloadDriverState.*;
public class PayloadMassDriver extends PayloadBlock{
public float range = 100f;
public float rotateSpeed = 2f;
public float length = 89 / 8f;
public float knockback = 5f;
public float reloadTime = 30f;
public float chargeTime = 100f;
public float maxPayloadSize = 3;
public float grabWidth = 8f, grabHeight = 11/4f;
public Effect shootEffect = Fx.shootBig2;
public Effect smokeEffect = Fx.shootPayloadDriver;
public Effect receiveEffect = Fx.payloadReceive;
public Sound shootSound = Sounds.shootBig;
public float shake = 3f;
public Effect transferEffect = new Effect(11f, 300f, e -> {
if(!(e.data instanceof PayloadMassDriverData data)) return;
Tmp.v1.set(data.x, data.y).lerp(data.ox, data.oy, Interp.sineIn.apply(e.fin()));
data.payload.set(Tmp.v1.x, Tmp.v1.y, e.rotation);
data.payload.draw();
}).layer(Layer.flyingUnitLow - 1);
public @Load("@-base") TextureRegion baseRegion;
public @Load("@-cap") TextureRegion capRegion;
public @Load("@-left") TextureRegion leftRegion;
public @Load("@-right") TextureRegion rightRegion;
public @Load("@-cap-outline") TextureRegion capOutlineRegion;
public @Load("@-left-outline") TextureRegion leftOutlineRegion;
public @Load("@-right-outline") TextureRegion rightOutlineRegion;
public @Load("bridge-arrow") TextureRegion arrow;
public PayloadMassDriver(String name){
super(name);
update = true;
solid = true;
configurable = true;
hasPower = true;
outlineIcon = true;
sync = true;
rotate = true;
outputsPayload = true;
//point2 is relative
config(Point2.class, (PayloadDriverBuild tile, Point2 point) -> tile.link = Point2.pack(point.x + tile.tileX(), point.y + tile.tileY()));
config(Integer.class, (PayloadDriverBuild tile, Integer point) -> tile.link = point);
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.payloadCapacity, maxPayloadSize, StatUnit.blocksSquared);
stats.add(Stat.reload, 60f / (chargeTime + reloadTime), StatUnit.seconds);
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{baseRegion, outRegion, region};
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(baseRegion, req.drawx(), req.drawy());
Draw.rect(topRegion, req.drawx(), req.drawy());
Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90);
Draw.rect(region, req.drawx(), req.drawy());
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
Drawf.dashCircle(x * tilesize, y * tilesize, range, Pal.accent);
//check if a mass driver is selected while placing this driver
if(!control.input.frag.config.isShown()) return;
Building selected = control.input.frag.config.getSelectedTile();
if(selected == null || selected.block != this || !selected.within(x * tilesize, y * tilesize, range)) return;
//if so, draw a dotted line towards it while it is in range
float sin = Mathf.absin(Time.time, 6f, 1f);
Tmp.v1.set(x * tilesize + offset, y * tilesize + offset).sub(selected.x, selected.y).limit((size / 2f + 1) * tilesize + sin + 0.5f);
float x2 = x * tilesize - Tmp.v1.x, y2 = y * tilesize - Tmp.v1.y,
x1 = selected.x + Tmp.v1.x, y1 = selected.y + Tmp.v1.y;
int segs = (int)(selected.dst(x * tilesize, y * tilesize) / tilesize);
Lines.stroke(4f, Pal.gray);
Lines.dashLine(x1, y1, x2, y2, segs);
Lines.stroke(2f, Pal.placing);
Lines.dashLine(x1, y1, x2, y2, segs);
Draw.reset();
}
@Override
public TextureRegion[] makeIconRegions(){
return new TextureRegion[]{leftRegion, rightRegion, capRegion};
}
public class PayloadDriverBuild extends PayloadBlockBuild<Payload>{
public int link = -1;
public float turretRotation = 90;
public float reload = 0f, charge = 0f;
public float targetSize = grabWidth*2f, curSize = targetSize;
public float payLength = 0f;
public boolean loaded;
public boolean charging;
public PayloadDriverState state = idle;
public Queue<Building> waitingShooters = new Queue<>();
public Payload recPayload;
public Building currentShooter(){
return waitingShooters.isEmpty() ? null : waitingShooters.first();
}
@Override
public void updateTile(){
Building link = world.build(this.link);
boolean hasLink = linkValid();
//discharge when charging isn't happening
if(!charging){
charge -= Time.delta * 10f;
if(charge < 0) charge = 0f;
}
curSize = Mathf.lerpDelta(curSize, targetSize, 0.05f);
targetSize = grabWidth*2f;
if(payload != null){
targetSize = payload.size();
}
charging = false;
if(hasLink){
this.link = link.pos();
}
//reload regardless of state
reload -= edelta() / reloadTime;
if(reload < 0) reload = 0f;
var current = currentShooter();
//cleanup waiting shooters that are not valid
if(current != null &&
!(
current instanceof PayloadDriverBuild entity &&
entity.consValid() && entity.block == block &&
entity.link == pos() && within(current, range)
)){
waitingShooters.removeFirst();
}
//switch states
if(state == idle){
//start accepting when idle and there's space
if(!waitingShooters.isEmpty() && payload == null){
state = accepting;
}else if(hasLink){ //switch to shooting if there's a valid link.
state = shooting;
}
}
//dump when idle or accepting
if((state == idle || state == accepting) && payload != null){
if(loaded){
payLength -= payloadSpeed * delta();
if(payLength <= 0f){
loaded = false;
payVector.setZero();
payRotation = Angles.moveToward(payRotation, turretRotation + 180f, payloadRotateSpeed * delta());
}
}else{
moveOutPayload();
}
}
//skip when there's no power
if(!consValid()){
return;
}
if(state == accepting){
//if there's nothing shooting at this or items are full, bail out
if(currentShooter() == null || payload != null){
state = idle;
return;
}
if(currentShooter().getPayload() != null){
targetSize = recPayload == null ? currentShooter().getPayload().size() : recPayload.size();
}
//align to shooter rotation
turretRotation = Angles.moveToward(turretRotation, tile.angleTo(currentShooter()), rotateSpeed * efficiency());
}else if(state == shooting){
//if there's nothing to shoot at OR someone wants to shoot at this thing, bail
if(!hasLink || (!waitingShooters.isEmpty() && payload == null)){
state = idle;
return;
}
float targetRotation = tile.angleTo(link);
boolean movedOut = false;
payRotation = Angles.moveToward(payRotation, turretRotation, payloadRotateSpeed * delta());
if(loaded){
float loadLength = length - reload*knockback;
payLength += payloadSpeed * delta();
if(payLength >= loadLength){
payLength = loadLength;
movedOut = true;
}
}else if(moveInPayload()){
payLength = 0f;
loaded = true;
}
//make sure payload firing can happen
if(movedOut && payload != null && link.getPayload() == null){
var other = (PayloadDriverBuild)link;
if(!other.waitingShooters.contains(this)){
other.waitingShooters.addLast(this);
}
if(reload <= 0){
//align to target location
turretRotation = Angles.moveToward(turretRotation, targetRotation, rotateSpeed * efficiency());
//fire when it's the first in the queue and angles are ready.
if(other.currentShooter() == this &&
other.state == accepting &&
other.reload <= 0f &&
Angles.within(turretRotation, targetRotation, 1f) && Angles.within(other.turretRotation, targetRotation + 180f, 1f)){
charge += edelta();
charging = true;
if(charge >= chargeTime){
float cx = Angles.trnsx(turretRotation, length), cy = Angles.trnsy(turretRotation, length);
//effects
shootEffect.at(x + cx, y + cy, turretRotation);
smokeEffect.at(x, y, turretRotation);
Effect.shake(shake, shake, this);
shootSound.at(this, Mathf.random(0.9f, 1.1f));
transferEffect.at(x + cx, y + cy, turretRotation, new PayloadMassDriverData(x + cx, y + cy, other.x - cx, other.y - cy, payload));
Payload pay = payload;
other.recPayload = payload;
Time.run(transferEffect.lifetime, () -> {
receiveEffect.at(other.x - cx/2f, other.y - cy/2f, other.turretRotation);
Effect.shake(shake, shake, this);
//transfer payload
other.reload = 1f;
other.handlePayload(this, pay);
other.payVector.set(-cx, -cy);
other.payRotation = turretRotation;
other.payLength = length;
other.loaded = true;
other.updatePayload();
other.recPayload = null;
if(other.waitingShooters.size != 0 && other.waitingShooters.first() == this){
other.waitingShooters.removeFirst();
}
other.state = idle;
});
//reset state after shooting immediately
payload = null;
payLength = 0f;
loaded = false;
state = idle;
reload = 1f;
}
}
}
}
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(1f - reload / reloadTime);
return super.sense(sensor);
}
@Override
public void updatePayload(){
if(payload != null){
if(loaded){
payload.set(x + Angles.trnsx(turretRotation, payLength), y + Angles.trnsy(turretRotation, payLength), payRotation);
}else{
payload.set(x + payVector.x, y + payVector.y, payRotation);
}
}
}
@Override
public void draw(){
float
tx = x + Angles.trnsx(turretRotation + 180f, reload * knockback),
ty = y + Angles.trnsy(turretRotation + 180f, reload * knockback), r = turretRotation - 90;
Draw.rect(baseRegion, x, y);
//draw input
for(int i = 0; i < 4; i++){
if(blends(i) && i != rotation){
Draw.rect(inRegion, x, y, (i * 90) - 180);
}
}
Draw.rect(outRegion, x, y, rotdeg());
if(payload != null){
updatePayload();
Draw.z(loaded ? Layer.blockOver + 0.2f : Layer.blockOver);
payload.draw();
}
Draw.z(Layer.blockOver + 0.1f);
Draw.rect(topRegion, x, y);
Draw.z(Layer.turret);
//TODO
Drawf.shadow(region, tx - (size / 2f), ty - (size / 2f), r);
Tmp.v1.trns(turretRotation, 0, -(curSize/2f - grabWidth));
Tmp.v2.trns(rotation, -Math.max(curSize/2f - grabHeight - length, 0f), 0f);
float rx = tx + Tmp.v1.x + Tmp.v2.x, ry = ty + Tmp.v1.y + Tmp.v2.y;
float lx = tx - Tmp.v1.x + Tmp.v2.x, ly = ty - Tmp.v1.y + Tmp.v2.y;
Draw.rect(capOutlineRegion, tx, ty, r);
Draw.rect(leftOutlineRegion, lx, ly, r);
Draw.rect(rightOutlineRegion, rx, ry, r);
Draw.rect(leftRegion, lx, ly, r);
Draw.rect(rightRegion, rx, ry, r);
Draw.rect(capRegion, tx, ty, r);
Draw.z(Layer.effect);
if(charge > 0 && linkValid()){
Building link = world.build(this.link);
float fin = Interp.pow2Out.apply(charge / chargeTime), fout = 1f-fin, len = length*1.8f, w = curSize/2f + 7f*fout;
Vec2 right = Tmp.v1.trns(turretRotation, len, w);
Vec2 left = Tmp.v2.trns(turretRotation, len, -w);
Lines.stroke(fin * 1.2f, Pal.accent);
Lines.line(x + left.x, y + left.y, link.x - right.x, link.y - right.y);
Lines.line(x + right.x, y + right.y, link.x - left.x, link.y - left.y);
for(int i = 0; i < 4; i++){
Tmp.v3.set(x, y).lerp(link.x, link.y, 0.5f + (i - 2) * 0.1f);
Draw.scl(fin * 1.1f);
Draw.rect(arrow, Tmp.v3.x, Tmp.v3.y, turretRotation);
Draw.scl();
}
Draw.reset();
}
}
@Override
public void drawConfigure(){
float sin = Mathf.absin(Time.time, 6f, 1f);
Draw.color(Pal.accent);
Lines.stroke(1f);
Drawf.circles(x, y, (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.accent);
for(var shooter : waitingShooters){
Drawf.circles(shooter.x, shooter.y, (tile.block().size / 2f + 1) * tilesize + sin - 2f, Pal.place);
Drawf.arrow(shooter.x, shooter.y, x, y, size * tilesize + sin, 4f + sin, Pal.place);
}
if(linkValid()){
Building target = world.build(link);
Drawf.circles(target.x, target.y, (target.block().size / 2f + 1) * tilesize + sin - 2f, Pal.place);
Drawf.arrow(x, y, target.x, target.y, size * tilesize + sin, 4f + sin);
}
Drawf.dashCircle(x, y, range, Pal.accent);
}
@Override
public boolean onConfigureTileTapped(Building other){
if(this == other){
configure(-1);
return false;
}
if(link == other.pos()){
configure(-1);
return false;
}else if(other.block instanceof PayloadMassDriver && other.dst(tile) <= range && other.team == team){
configure(other.pos());
return false;
}
return true;
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return super.acceptPayload(source, payload) && payload.size() <= maxPayloadSize * tilesize;
}
protected boolean linkValid(){
return link != -1 && world.build(this.link) instanceof PayloadDriverBuild other && other.block == block && other.team == team && within(other, range);
}
@Override
public Point2 config(){
return Point2.unpack(link).sub(tile.x, tile.y);
}
@Override
public void write(Writes write){
super.write(write);
write.i(link);
write.f(turretRotation);
write.b((byte)state.ordinal());
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
link = read.i();
turretRotation = read.f();
state = PayloadDriverState.all[read.b()];
}
}
public static class PayloadMassDriverData{
public float x, y, ox, oy;
public Payload payload;
public PayloadMassDriverData(float x, float y, float ox, float oy, Payload payload){
this.x = x;
this.y = y;
this.ox = ox;
this.oy = oy;
this.payload = payload;
}
}
public enum PayloadDriverState{
idle, accepting, shooting;
public static final PayloadDriverState[] all = values();
}
}

View File

@@ -0,0 +1,135 @@
package mindustry.world.blocks.payloads;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.ctype.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import static mindustry.Vars.*;
/** Generic building that produces other buildings. */
public class PayloadSource extends PayloadBlock{
public PayloadSource(String name){
super(name);
size = 3;
update = true;
outputsPayload = true;
hasPower = true;
rotate = true;
configurable = true;
config(Block.class, (PayloadSourceBuild build, Block block) -> {
if(canProduce(block) && build.block != block){
build.block = block;
build.unit = null;
build.payload = null;
build.scl = 0f;
}
});
config(UnitType.class, (PayloadSourceBuild build, UnitType unit) -> {
if(canProduce(unit) && build.unit != unit){
build.unit = unit;
build.block = null;
build.payload = null;
build.scl = 0f;
}
});
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, outRegion, topRegion};
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90);
Draw.rect(topRegion, req.drawx(), req.drawy());
}
public boolean canProduce(Block b){
return b.isVisible() && b.size < size;
}
public boolean canProduce(UnitType t){
return !t.isHidden();
}
public class PayloadSourceBuild extends PayloadBlockBuild<Payload>{
public UnitType unit;
public Block block;
public float scl;
@Override
public void buildConfiguration(Table table){
ItemSelection.buildTable(table,
content.blocks().select(PayloadSource.this::canProduce).<UnlockableContent>as()
.and(content.units().select(PayloadSource.this::canProduce).as()),
() -> (UnlockableContent)config(), this::configure);
}
@Override
public Object config(){
return unit == null ? block : unit;
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return false;
}
@Override
public void updateTile(){
if(payload == null){
scl = 0f;
if(unit != null){
payload = new UnitPayload(unit.create(team));
}else if(block != null){
payload = new BuildPayload(block, team);
}
payVector.setZero();
payRotation = rotdeg();
}
scl = Mathf.lerpDelta(scl, 1f, 0.1f);
moveOutPayload();
}
@Override
public void draw(){
Draw.rect(region, x, y);
Draw.rect(outRegion, x, y, rotdeg());
Draw.rect(topRegion, x, y);
Draw.scl(scl);
drawPayload();
Draw.reset();
}
@Override
public void write(Writes write){
super.write(write);
write.s(unit == null ? -1 : unit.id);
write.s(block == null ? -1 : block.id);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
unit = Vars.content.unit(read.s());
block = Vars.content.block(read.s());
}
}
}

View File

@@ -0,0 +1,57 @@
package mindustry.world.blocks.payloads;
import arc.audio.*;
import arc.graphics.g2d.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.graphics.*;
public class PayloadVoid extends PayloadBlock{
public Effect incinerateEffect = Fx.blastExplosion;
public Sound incinerateSound = Sounds.bang;
public PayloadVoid(String name){
super(name);
outputsPayload = false;
acceptsPayload = true;
update = true;
rotate = false;
size = 3;
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, topRegion};
}
public class BlockLoaderBuild extends PayloadBlockBuild<Payload>{
@Override
public void draw(){
Draw.rect(region, x, y);
//draw input
for(int i = 0; i < 4; i++){
if(blends(i)){
Draw.rect(inRegion, x, y, (i * 90) - 180);
}
}
Draw.rect(topRegion, x, y);
Draw.z(Layer.blockOver);
drawPayload();
}
@Override
public void updateTile(){
if(moveInPayload(false) && cons.valid()){
payload = null;
incinerateEffect.at(this);
incinerateSound.at(this);
}
}
}
}

View File

@@ -12,8 +12,6 @@ import mindustry.entities.EntityCollisions.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
public class UnitPayload implements Payload{
public static final float deactiveDuration = 40f;
@@ -38,6 +36,16 @@ public class UnitPayload implements Payload{
unit.rotation = rotation;
}
@Override
public float x(){
return unit.x;
}
@Override
public float y(){
return unit.y;
}
@Override
public float rotation(){
return unit.rotation;
@@ -71,6 +79,11 @@ public class UnitPayload implements Payload{
if(!nearEmpty) return false;
}
//cannnot dump when there's a lot of overlap going on
if(!unit.type.flying && Units.count(unit.x, unit.y, unit.physicSize(), o -> o.isGrounded() && (o.type.allowLegStep == unit.type.allowLegStep)) > 0){
return false;
}
//no client dumping
if(Vars.net.client()) return true;
@@ -87,8 +100,8 @@ public class UnitPayload implements Payload{
//TODO should not happen
if(unit.type == null) return;
Drawf.shadow(unit.x, unit.y, 20);
Draw.rect(unit.type.icon(Cicon.full), unit.x, unit.y, unit.rotation - 90);
unit.type.drawSoftShadow(unit);
Draw.rect(unit.type.fullIcon, unit.x, unit.y, unit.rotation - 90);
unit.type.drawCell(unit);
//draw warning
@@ -106,7 +119,7 @@ public class UnitPayload implements Payload{
}
@Override
public TextureRegion icon(Cicon icon){
return unit.type.icon(icon);
public TextureRegion icon(){
return unit.type.fullIcon;
}
}

View File

@@ -29,6 +29,13 @@ public class LightBlock extends Block{
config(Integer.class, (LightBuild tile, Integer value) -> tile.color = value);
}
@Override
public void init(){
lightRadius = radius;
emitLight = true;
super.init();
}
public class LightBuild extends Building{
public int color = Pal.accent.rgba();
public float smoothTime = 1f;
@@ -67,7 +74,7 @@ public class LightBlock extends Block{
@Override
public void drawLight(){
Drawf.light(team, x, y, radius * Math.min(smoothTime, 2f), Tmp.c1.set(color), brightness * efficiency());
Drawf.light(team, x, y, lightRadius * Math.min(smoothTime, 2f), Tmp.c1.set(color), brightness * efficiency());
}
@Override

View File

@@ -30,6 +30,7 @@ public class NuclearReactor extends PowerGenerator{
public Color lightColor = Color.valueOf("7f19ea");
public Color coolColor = new Color(1, 1, 1, 0f);
public Color hotColor = Color.valueOf("ff9575a3");
public Effect explodeEffect = Fx.reactorExplosion;
/** ticks to consume 1 fuel */
public float itemDuration = 120;
/** heating per frame * fullness */
@@ -135,26 +136,9 @@ public class NuclearReactor extends PowerGenerator{
if((fuel < 5 && heat < 0.5f) || !state.rules.reactorExplosions) return;
Effect.shake(6f, 16f, x, y);
Fx.nuclearShockwave.at(x, y);
for(int i = 0; i < 6; i++){
Time.run(Mathf.random(40), () -> Fx.nuclearcloud.at(x, y));
}
Damage.damage(x, y, explosionRadius * tilesize, explosionDamage * 4);
for(int i = 0; i < 20; i++){
Time.run(Mathf.random(50), () -> {
tr.rnd(Mathf.random(40f));
Fx.explosion.at(tr.x + x, tr.y + y);
});
}
for(int i = 0; i < 70; i++){
Time.run(Mathf.random(80), () -> {
tr.rnd(Mathf.random(120f));
Fx.nuclearsmoke.at(tr.x + x, tr.y + y);
});
}
explodeEffect.at(x, y);
}
@Override

View File

@@ -35,7 +35,7 @@ public class PowerDiode extends Block{
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(icon(Cicon.full), req.drawx(), req.drawy());
Draw.rect(fullIcon, req.drawx(), req.drawy());
Draw.rect(arrow, req.drawx(), req.drawy(), !rotate ? 0 : req.rotation * 90);
}

View File

@@ -0,0 +1,58 @@
package mindustry.world.blocks.production;
import arc.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
/** A crafter that gains efficiency from attribute tiles. */
public class AttributeCrafter extends GenericCrafter{
public Attribute attribute = Attribute.heat;
public float baseEfficiency = 1f;
public float boostScale = 1f;
public float maxBoost = 1f;
public AttributeCrafter(String name){
super(name);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
drawPlaceText(Core.bundle.format("bar.efficiency",
(int)((baseEfficiency + Math.min(maxBoost, boostScale * sumAttribute(attribute, x, y))) * 100f)), x, y, valid);
}
@Override
public void setBars(){
super.setBars();
bars.add("efficiency", entity ->
new Bar(() ->
Core.bundle.format("bar.efficiency", (int)(entity.efficiency() * 100)),
() -> Pal.lightOrange,
entity::efficiency));
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.affinities, attribute, boostScale);
}
public class AttributeCrafterBuild extends GenericCrafterBuild{
public float attrsum;
@Override
public float efficiency(){
return (baseEfficiency + Math.min(maxBoost, boostScale * attrsum)) * super.efficiency();
}
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
attrsum = sumAttribute(attribute, tile.x, tile.y);
}
}
}

View File

@@ -1,58 +1,25 @@
package mindustry.world.blocks.production;
import arc.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import mindustry.annotations.Annotations.*;
/** A smelter that gains efficiency from attribute tiles. */
public class AttributeSmelter extends GenericSmelter{
public Attribute attribute = Attribute.heat;
public float baseEfficiency = 1f;
public float boostScale = 1f;
/** @deprecated use AttributeCrafter instead, this is only a transition class. No flame effects are drawn, to encourage transition! */
@Deprecated
public class AttributeSmelter extends AttributeCrafter{
//parameters are kept for compatibility but deliberately unused
public Color flameColor = Color.valueOf("ffc999");
public @Load("@-top") TextureRegion topRegion;
//compat
public float maxHeatBoost = 1f;
public AttributeSmelter(String name){
super(name);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
drawPlaceText(Core.bundle.format("bar.efficiency",
(int)((baseEfficiency + Math.min(maxHeatBoost, boostScale * sumAttribute(attribute, x, y))) * 100f)), x, y, valid);
}
//unused, kept for compatibility
@Deprecated
public class AttributeSmelterBuild extends AttributeCrafterBuild{
@Override
public void setBars(){
super.setBars();
bars.add("efficiency", entity ->
new Bar(() ->
Core.bundle.format("bar.efficiency", (int)(entity.efficiency() * 100)),
() -> Pal.lightOrange,
entity::efficiency));
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.affinities, attribute, boostScale);
}
public class AttributeSmelterBuild extends SmelterBuild{
public float attrsum;
@Override
public float efficiency(){
return (baseEfficiency + Math.min(maxHeatBoost, boostScale * attrsum)) * super.efficiency();
}
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
attrsum = sumAttribute(attribute, tile.x, tile.y);
}
}
}

View File

@@ -0,0 +1,254 @@
package mindustry.world.blocks.production;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class BeamDrill extends Block{
public @Load("minelaser") TextureRegion laser;
public @Load("minelaser-end") TextureRegion laserEnd;
public @Load("@-top") TextureRegion topRegion;
public float drillTime = 200f;
public int range = 5;
public int tier = 1;
public float laserWidth = 0.7f;
/** Effect randomly played while drilling. */
public Effect updateEffect = Fx.mineSmall;
public BeamDrill(String name){
super(name);
hasItems = true;
rotate = true;
update = true;
solid = true;
drawArrow = false;
}
@Override
public void init(){
clipSize = Math.max(clipSize, size * tilesize + (range + 1) * tilesize);
super.init();
}
@Override
public boolean outputsItems(){
return true;
}
@Override
public boolean rotatedOutput(int x, int y){
return false;
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, topRegion};
}
@Override
public void drawRequestRegion(BuildPlan req, Eachable<BuildPlan> list){
Draw.rect(region, req.drawx(), req.drawy());
Draw.rect(topRegion, req.drawx(), req.drawy(), req.rotation * 90);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
Item item = null;
boolean multiple = false;
int count = 0;
for(int i = 0; i < size; i++){
getLaserPos(x, y, rotation, i, Tmp.p1);
int j = 0;
Item found = null;
for(; j < range; j++){
int rx = Tmp.p1.x + Geometry.d4x(rotation)*j, ry = Tmp.p1.y + Geometry.d4y(rotation)*j;
Tile other = world.tile(rx, ry);
if(other != null){
if(other.solid()){
Item drop = other.wallDrop();
if(drop != null && drop.hardness <= tier){
found = drop;
count ++;
}
break;
}
}
}
if(found != null){
//check if multiple items will be drilled
if(item != found && item != null){
multiple = true;
}
item = found;
}
int len = Math.min(j, range - 1);
Drawf.dashLine(found == null ? Pal.remove : Pal.placing,
Tmp.p1.x * tilesize,
Tmp.p1.y *tilesize,
(Tmp.p1.x + Geometry.d4x(rotation)*len) * tilesize,
(Tmp.p1.y + Geometry.d4y(rotation)*len) * tilesize
);
}
if(item != null){
float width = drawPlaceText(Core.bundle.formatFloat("bar.drillspeed", 60f / drillTime * count, 2), x, y, valid);
if(!multiple){
float dx = x * tilesize + offset - width/2f - 4f, dy = y * tilesize + offset + size * tilesize / 2f + 5, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(item.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(item.fullIcon, dx, dy, s, s);
}
}
}
void getLaserPos(int tx, int ty, int rotation, int i, Point2 out){
int cornerX = tx - (size-1)/2, cornerY = ty - (size-1)/2, s = size;
switch(rotation){
case 0 -> out.set(cornerX + s, cornerY + i);
case 1 -> out.set(cornerX + i, cornerY + s);
case 2 -> out.set(cornerX - 1, cornerY + i);
case 3 -> out.set(cornerX + i, cornerY - 1);
}
}
public class BeamDrillBuild extends Building{
public Tile[] facing = new Tile[size];
public Point2[] lasers = new Point2[size];
public @Nullable Item lastItem;
public float time;
public float warmup;
@Override
public void drawSelect(){
if(lastItem != null){
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(lastItem.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(lastItem.fullIcon, dx, dy, s, s);
}
}
@Override
public void updateTile(){
super.updateTile();
if(lasers[0] == null) updateLasers();
boolean cons = shouldConsume();
warmup = Mathf.lerpDelta(warmup, Mathf.num(consValid()), 0.1f);
lastItem = null;
boolean multiple = false;
//update facing tiles
for(int p = 0; p < size; p++){
Point2 l = lasers[p];
Tile dest = null;
for(int i = 0; i < range; i++){
int rx = l.x + Geometry.d4x(rotation)*i, ry = l.y + Geometry.d4y(rotation)*i;
Tile other = world.tile(rx, ry);
if(other != null){
if(other.solid()){
Item drop = other.wallDrop();
if(drop != null && drop.hardness <= tier){
if(lastItem != drop && lastItem != null){
multiple = true;
}
lastItem = drop;
dest = other;
}
break;
}
}
}
facing[p] = dest;
if(cons && dest != null && Mathf.chanceDelta(0.05 * warmup)){
updateEffect.at(dest.worldx() + Mathf.range(4f), dest.worldy() + Mathf.range(4f), dest.wallDrop().color);
}
}
//when multiple items are present, count that as no item
if(multiple){
lastItem = null;
}
time += edelta();
if(time >= drillTime){
for(Tile tile : facing){
Item drop = tile == null ? null : tile.wallDrop();
if(items.total() < itemCapacity && drop != null){
items.add(drop, 1);
}
}
time %= drillTime;
}
if(timer(timerDump, dumpTime)){
dump();
}
}
@Override
public boolean shouldConsume(){
return items.total() < itemCapacity;
}
@Override
public void draw(){
Draw.rect(block.region, x, y);
Draw.rect(topRegion, x, y, rotdeg());
Draw.z(Layer.power - 1);
var dir = Geometry.d4(rotation);
for(int i = 0; i < size; i++){
Tile face = facing[i];
if(face != null){
Point2 p = lasers[i];
Drawf.laser(team, laser, laserEnd, (p.x - dir.x/2f) * tilesize, (p.y - dir.y/2f) * tilesize,
face.worldx() - (dir.x/2f)*(tilesize), face.worldy() - (dir.y/2f)*(tilesize),
(laserWidth + Mathf.absin(Time.time + i*4 + (id%20)*6, 3f, 0.07f)) * warmup);
}
}
Draw.reset();
}
@Override
public void onProximityUpdate(){
//when rotated.
updateLasers();
}
void updateLasers(){
for(int i = 0; i < size; i++){
if(lasers[i] == null) lasers[i] = new Point2();
getLaserPos(tileX(), tileY(), rotation, i, lasers[i]);
}
}
}
}

View File

@@ -1,18 +1,20 @@
package mindustry.world.blocks.production;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
/**
* @deprecated use GenericCrafter or AttributeCrafter with a DrawCultivator instead.
* WARNING: If you switch from a class that used Cultivator to a GenericCrafter, make sure you set legacyReadWarmup to true! Failing to do so will break saves.
* This class has been gutted of its behavior.
* */
@Deprecated
public class Cultivator extends GenericCrafter{
//fields are kept for compatibility
public Color plantColor = Color.valueOf("5541b1");
public Color plantColorLight = Color.valueOf("7457ce");
public Color bottomColor = Color.valueOf("474747");
@@ -25,84 +27,11 @@ public class Cultivator extends GenericCrafter{
public Cultivator(String name){
super(name);
craftEffect = Fx.none;
}
@Override
public void setBars(){
super.setBars();
bars.add("multiplier", (CultivatorBuild entity) -> new Bar(() ->
Core.bundle.formatFloat("bar.efficiency",
((entity.boost + 1f + attribute.env()) * entity.warmup) * 100f, 1),
() -> Pal.ammo,
() -> entity.warmup));
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.affinities, attribute);
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
drawPlaceText(Core.bundle.formatFloat("bar.efficiency", (1 + sumAttribute(attribute, x, y)) * 100, 1), x, y, valid);
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{region, topRegion};
}
public class CultivatorBuild extends GenericCrafterBuild{
public float warmup;
public float boost;
@Override
public void updateTile(){
super.updateTile();
warmup = Mathf.lerpDelta(warmup, consValid() ? 1f : 0f, 0.015f);
}
@Override
public void draw(){
Draw.rect(region, x, y);
Drawf.liquid(middleRegion, x, y, warmup, plantColor);
Draw.color(bottomColor, plantColorLight, warmup);
random.setSeed(tile.pos());
for(int i = 0; i < 12; i++){
float offset = random.nextFloat() * 999999f;
float x = random.range(4f), y = random.range(4f);
float life = 1f - (((Time.time + offset) / 50f) % recurrence);
if(life > 0){
Lines.stroke(warmup * (life + 0.2f));
Lines.poly(x + x, y + y, 8, (1f - life) * 3f);
}
}
Draw.color();
Draw.rect(topRegion, x, y);
}
@Override
public void onProximityUpdate(){
super.onProximityAdded();
boost = sumAttribute(attribute, tile.x, tile.y);
}
@Override
public float getProgressIncrease(float baseTime){
return super.getProgressIncrease(baseTime) * (1f + boost + attribute.env());
}
//compat
public float warmup, boost;
@Override
public void write(Writes write){

View File

@@ -6,6 +6,7 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
@@ -13,12 +14,12 @@ import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -122,11 +123,11 @@ public class Drill extends Block{
if(returnItem != null){
float width = drawPlaceText(Core.bundle.formatFloat("bar.drillspeed", 60f / (drillTime + hardnessDrillMultiplier * returnItem.hardness) * returnCount, 2), x, y, valid);
float dx = x * tilesize + offset - width/2f - 4f, dy = y * tilesize + offset + size * tilesize / 2f + 5;
float dx = x * tilesize + offset - width/2f - 4f, dy = y * tilesize + offset + size * tilesize / 2f + 5, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(returnItem.icon(Cicon.small), dx, dy - 1);
Draw.rect(returnItem.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(returnItem.icon(Cicon.small), dx, dy);
Draw.rect(returnItem.fullIcon, dx, dy, s, s);
if(drawMineItem){
Draw.color(returnItem.color);
@@ -146,7 +147,7 @@ public class Drill extends Block{
public void setStats(){
super.setStats();
stats.add(Stat.drillTier, new BlockFilterValue(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= tier));
stats.add(Stat.drillTier, StatValues.blocks(b -> b instanceof Floor f && f.itemDrop != null && f.itemDrop.hardness <= tier));
stats.add(Stat.drillSpeed, 60f / drillTime * size * size, StatUnit.itemsSecond);
if(liquidBoostIntensity != 1){
@@ -225,16 +226,18 @@ public class Drill extends Block{
@Override
public void drawSelect(){
if(dominantItem != null){
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f;
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(dominantItem.icon(Cicon.small), dx, dy - 1);
Draw.rect(dominantItem.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(dominantItem.icon(Cicon.small), dx, dy);
Draw.rect(dominantItem.fullIcon, dx, dy, s, s);
}
}
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
countOre(tile);
dominantItem = returnItem;
dominantItems = returnCount;
@@ -247,7 +250,7 @@ public class Drill extends Block{
}
if(timer(timerDump, dumpTime)){
dump(dominantItem);
dump(items.has(dominantItem) ? dominantItem : null);
}
timeDrilled += warmup * delta();
@@ -285,6 +288,12 @@ public class Drill extends Block{
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress && dominantItem != null) return Mathf.clamp(progress / (drillTime + hardnessDrillMultiplier * dominantItem.hardness));
return super.sense(sensor);
}
@Override
public void drawCracks(){}
@@ -315,6 +324,27 @@ public class Drill extends Block{
Draw.color();
}
}
@Override
public byte version(){
return 1;
}
@Override
public void write(Writes write){
super.write(write);
write.f(progress);
write.f(warmup);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
if(revision >= 1){
progress = read.f();
warmup = read.f();
}
}
}
}

View File

@@ -18,10 +18,12 @@ public class Fracker extends SolidPump{
hasItems = true;
ambientSound = Sounds.drill;
ambientSoundVolume = 0.03f;
envRequired |= Env.groundOil;
}
@Override
public void setStats(){
stats.timePeriod = itemUseTime;
super.setStats();
stats.add(Stat.productionTime, itemUseTime / 60f, StatUnit.seconds);

View File

@@ -8,6 +8,7 @@ import arc.util.io.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.draw.*;
@@ -21,6 +22,9 @@ public class GenericCrafter extends Block{
public Effect craftEffect = Fx.none;
public Effect updateEffect = Fx.none;
public float updateEffectChance = 0.04f;
public float warmupSpeed = 0.019f;
/** Only used for legacy cultivator blocks. */
public boolean legacyReadWarmup = false;
public DrawBlock drawer = new DrawBlock();
@@ -37,11 +41,12 @@ public class GenericCrafter extends Block{
@Override
public void setStats(){
stats.timePeriod = craftTime;
super.setStats();
stats.add(Stat.productionTime, craftTime / 60f, StatUnit.seconds);
if(outputItem != null){
stats.add(Stat.output, outputItem);
stats.add(Stat.output, StatValues.items(craftTime, outputItem));
}
if(outputLiquid != null){
@@ -82,9 +87,15 @@ public class GenericCrafter extends Block{
drawer.draw(this);
}
@Override
public void drawLight(){
super.drawLight();
drawer.drawLight(this);
}
@Override
public boolean shouldConsume(){
if(outputItem != null && items.get(outputItem.item) >= itemCapacity){
if(outputItem != null && items.get(outputItem.item) + outputItem.amount > itemCapacity){
return false;
}
return (outputLiquid == null || !(liquids.get(outputLiquid.liquid) >= liquidCapacity - 0.001f)) && enabled;
@@ -96,13 +107,13 @@ public class GenericCrafter extends Block{
progress += getProgressIncrease(craftTime);
totalProgress += delta();
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);
warmup = Mathf.approachDelta(warmup, 1f, warmupSpeed);
if(Mathf.chanceDelta(updateEffectChance)){
updateEffect.at(getX() + Mathf.range(size * 4f), getY() + Mathf.range(size * 4));
}
}else{
warmup = Mathf.lerp(warmup, 0f, 0.02f);
warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed);
}
if(progress >= 1f){
@@ -122,7 +133,7 @@ public class GenericCrafter extends Block{
progress %= 1f;
}
if(outputItem != null && timer(timerDump, dumpTime)){
if(outputItem != null && timer(timerDump, dumpTime / timeScale)){
dump(outputItem.item);
}
@@ -131,6 +142,12 @@ public class GenericCrafter extends Block{
}
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(progress);
return super.sense(sensor);
}
@Override
public int getMaximumAccepted(Item item){
return itemCapacity;
@@ -146,6 +163,7 @@ public class GenericCrafter extends Block{
super.write(write);
write.f(progress);
write.f(warmup);
if(legacyReadWarmup) write.f(0f);
}
@Override
@@ -153,6 +171,7 @@ public class GenericCrafter extends Block{
super.read(read, revision);
progress = read.f();
warmup = read.f();
if(legacyReadWarmup) read.f();
}
}
}

View File

@@ -8,10 +8,12 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
/** A GenericCrafter with a new glowing region drawn on top. */
/** @deprecated this class has no new functionality over GenericCrafter, use GenericCrafter with a DrawSmelter drawer instead. See vanilla smelter blocks. */
@Deprecated
public class GenericSmelter extends GenericCrafter{
public Color flameColor = Color.valueOf("ffc999");
public @Load("@-top") TextureRegion topRegion;
public float flameRadius = 3f, flameRadiusIn = 1.9f, flameRadiusScl = 5f, flameRadiusMag = 2f, flameRadiusInMag = 1f;
public GenericSmelter(String name){
super(name);
@@ -35,10 +37,10 @@ public class GenericSmelter extends GenericCrafter{
Draw.alpha(((1f - g) + Mathf.absin(Time.time, 8f, g) + Mathf.random(r) - r) * warmup);
Draw.tint(flameColor);
Fill.circle(x, y, 3f + Mathf.absin(Time.time, 5f, 2f) + cr);
Fill.circle(x, y, flameRadius + Mathf.absin(Time.time, flameRadiusScl, flameRadiusMag) + cr);
Draw.color(1f, 1f, 1f, warmup);
Draw.rect(topRegion, x, y);
Fill.circle(x, y, 1.9f + Mathf.absin(Time.time, 5f, 1f) + cr);
Fill.circle(x, y, flameRadiusIn + Mathf.absin(Time.time, flameRadiusScl, flameRadiusInMag) + cr);
Draw.color();
}

View File

@@ -1,5 +1,6 @@
package mindustry.world.blocks.production;
import arc.math.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
@@ -47,16 +48,25 @@ public class LiquidConverter extends GenericCrafter{
ConsumeLiquid cl = consumes.get(ConsumeType.liquid);
if(cons.valid()){
if(Mathf.chanceDelta(updateEffectChance)){
updateEffect.at(getX() + Mathf.range(size * 4f), getY() + Mathf.range(size * 4));
}
warmup = Mathf.lerpDelta(warmup, 1f, 0.02f);
float use = Math.min(cl.amount * edelta(), liquidCapacity - liquids.get(outputLiquid.liquid));
float ratio = outputLiquid.amount / cl.amount;
liquids.remove(cl.liquid, Math.min(use, liquids.get(cl.liquid)));
progress += use / cl.amount;
liquids.add(outputLiquid.liquid, use);
liquids.add(outputLiquid.liquid, use * ratio);
if(progress >= craftTime){
consume();
progress %= craftTime;
}
}else{
//warmup is still 1 even if not consuming
warmup = Mathf.lerp(warmup, cons.canConsume() ? 1f : 0f, 0.02f);
}
dumpLiquid(outputLiquid.liquid);

View File

@@ -1,186 +1,18 @@
package mindustry.world.blocks.production;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
import static mindustry.Vars.*;
public class PayloadAcceptor extends Block{
public float payloadSpeed = 0.5f, payloadRotateSpeed = 5f;
public @Load(value = "@-top", fallback = "factory-top-@size") TextureRegion topRegion;
public @Load(value = "@-out", fallback = "factory-out-@size") TextureRegion outRegion;
public @Load(value = "@-in", fallback = "factory-in-@size") TextureRegion inRegion;
/** @deprecated used PayloadBlock instead. */
@Deprecated
public abstract class PayloadAcceptor extends PayloadBlock{
public PayloadAcceptor(String name){
super(name);
update = true;
sync = true;
}
public static boolean blends(Building build, int direction){
int size = build.block.size;
int trns = build.block.size/2 + 1;
Building accept = build.nearby(Geometry.d4(direction).x * trns, Geometry.d4(direction).y * trns);
return accept != null &&
accept.block.outputsPayload &&
/** @deprecated used PayloadBlockBuild instead. */
@Deprecated
public class PayloadAcceptorBuild<T extends Payload> extends PayloadBlockBuild<T>{
//if size is the same, block must either be facing this one, or not be rotating
((accept.block.size == size
&& Math.abs(accept.tileX() - build.tileX()) % size == 0 //check alignment
&& Math.abs(accept.tileY() - build.tileY()) % size == 0
&& ((accept.block.rotate && accept.tileX() + Geometry.d4(accept.rotation).x * size == build.tileX() && accept.tileY() + Geometry.d4(accept.rotation).y * size == build.tileY())
|| !accept.block.rotate
|| !accept.block.outputFacing)) ||
//if the other block is smaller, check alignment
(accept.block.size < size &&
(accept.rotation % 2 == 0 ? //check orientation; make sure it's aligned properly with this block.
Math.abs(accept.y - build.y) <= (size * tilesize - accept.block.size * tilesize)/2f : //check Y alignment
Math.abs(accept.x - build.x) <= (size * tilesize - accept.block.size * tilesize)/2f //check X alignment
)) && (!accept.block.rotate || accept.front() == build || !accept.block.outputFacing) //make sure it's facing this block
);
}
public class PayloadAcceptorBuild<T extends Payload> extends Building{
public @Nullable T payload;
public Vec2 payVector = new Vec2();
public float payRotation;
public boolean carried;
@Override
public boolean acceptPayload(Building source, Payload payload){
return this.payload == null;
}
@Override
public void handlePayload(Building source, Payload payload){
this.payload = (T)payload;
this.payVector.set(source).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f);
this.payRotation = payload.rotation();
updatePayload();
}
@Override
public Payload getPayload(){
return payload;
}
@Override
public void pickedUp(){
carried = true;
}
@Override
public void drawTeamTop(){
carried = false;
}
@Override
public Payload takePayload(){
T t = payload;
payload = null;
return t;
}
@Override
public void onRemoved(){
super.onRemoved();
if(payload != null && !carried) payload.dump();
}
public boolean blends(int direction){
return PayloadAcceptor.blends(this, direction);
}
public void updatePayload(){
if(payload != null){
payload.set(x + payVector.x, y + payVector.y, payRotation);
}
}
/** @return true if the payload is in position. */
public boolean moveInPayload(){
if(payload == null) return false;
updatePayload();
payRotation = Angles.moveToward(payRotation, rotate ? rotdeg() : 90f, payloadRotateSpeed * edelta());
payVector.approach(Vec2.ZERO, payloadSpeed * delta());
return hasArrived();
}
public void moveOutPayload(){
if(payload == null) return;
updatePayload();
Vec2 dest = Tmp.v1.trns(rotdeg(), size* tilesize/2f);
payRotation = Angles.moveToward(payRotation, rotdeg(), payloadRotateSpeed * edelta());
payVector.approach(dest, payloadSpeed * delta());
if(payVector.within(dest, 0.001f)){
payVector.clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f);
Building front = front();
if(front != null && front.block.outputsPayload){
if(movePayload(payload)){
payload = null;
}
}else if(front == null || !front.tile().solid()){
dumpPayload();
}
}
}
public void dumpPayload(){
if(payload.dump()){
payload = null;
}
}
public boolean hasArrived(){
return payVector.isZero(0.01f);
}
public void drawPayload(){
if(payload != null){
updatePayload();
Draw.z(Layer.blockOver);
payload.draw();
}
}
@Override
public void write(Writes write){
super.write(write);
write.f(payVector.x);
write.f(payVector.y);
write.f(payRotation);
Payload.write(payload, write);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
payVector.set(read.f(), read.f());
payRotation = read.f();
payload = Payload.read(read);
}
}
}

View File

@@ -6,7 +6,6 @@ import arc.graphics.g2d.*;
import mindustry.game.*;
import mindustry.graphics.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.liquid.*;
import mindustry.world.meta.*;
@@ -48,11 +47,11 @@ public class Pump extends LiquidBlock{
if(liquidDrop != null){
float width = drawPlaceText(Core.bundle.formatFloat("bar.pumpspeed", amount * pumpAmount * 60f, 0), x, y, valid);
float dx = x * tilesize + offset - width/2f - 4f, dy = y * tilesize + offset + size * tilesize / 2f + 5;
float dx = x * tilesize + offset - width/2f - 4f, dy = y * tilesize + offset + size * tilesize / 2f + 5, s = iconSmall / 4f;
Draw.mixcol(Color.darkGray, 1f);
Draw.rect(liquidDrop.icon(Cicon.small), dx, dy - 1);
Draw.rect(liquidDrop.fullIcon, dx, dy - 1, s, s);
Draw.reset();
Draw.rect(liquidDrop.icon(Cicon.small), dx, dy);
Draw.rect(liquidDrop.fullIcon, dx, dy, s, s);
}
}

View File

@@ -3,6 +3,7 @@ package mindustry.world.blocks.production;
import arc.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@@ -11,7 +12,6 @@ import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
/**
* Extracts a random list of items from an input item and an input liquid.
@@ -34,15 +34,10 @@ public class Separator extends Block{
@Override
public void setStats(){
stats.timePeriod = craftTime;
super.setStats();
stats.add(Stat.output, new ItemFilterValue(item -> {
for(ItemStack i : results){
if(item == i.item) return true;
}
return false;
}));
stats.add(Stat.output, StatValues.items(item -> Structs.contains(results, i -> i.item == item)));
stats.add(Stat.productionTime, craftTime / 60f, StatUnit.seconds);
}

View File

@@ -0,0 +1,23 @@
package mindustry.world.blocks.production;
import arc.util.*;
import mindustry.content.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
public class SingleBlockProducer extends BlockProducer{
public Block result = Blocks.router;
public SingleBlockProducer(String name){
super(name);
}
public class SingleBlockProducerBuild extends BlockProducerBuild{
@Nullable
@Override
public Block recipe(){
return result;
}
}
}

View File

@@ -24,6 +24,7 @@ public class ItemSource extends Block{
configurable = true;
saveConfig = true;
noUpdateDisabled = true;
envEnabled = Env.any;
config(Item.class, (ItemSourceBuild tile, Item item) -> tile.outputItem = item);
configClear((ItemSourceBuild tile) -> tile.outputItem = null);

View File

@@ -11,6 +11,7 @@ public class ItemVoid extends Block{
super(name);
group = BlockGroup.transportation;
update = solid = acceptsItems = true;
envEnabled = Env.any;
}
public class ItemVoidBuild extends Building{

View File

@@ -1,6 +1,7 @@
package mindustry.world.blocks.sandbox;
import mindustry.world.blocks.power.*;
import mindustry.world.meta.*;
public class PowerSource extends PowerNode{
@@ -11,6 +12,8 @@ public class PowerSource extends PowerNode{
maxNodes = 100;
outputsPower = true;
consumesPower = false;
//TODO maybe don't?
envEnabled = Env.any;
}
public class PowerSourceBuild extends PowerNodeBuild{

View File

@@ -8,6 +8,7 @@ public class PowerVoid extends PowerBlock{
public PowerVoid(String name){
super(name);
consumes.power(Float.MAX_VALUE);
envEnabled = Env.any;
}
@Override

View File

@@ -6,6 +6,7 @@ import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
@@ -19,7 +20,6 @@ import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.units.*;
import mindustry.world.meta.*;
import mindustry.world.modules.*;
@@ -37,6 +37,7 @@ public class CoreBlock extends StorageBlock{
public int ammoAmount = 5;
public float resupplyRate = 10f;
public float resupplyRange = 60f;
public float captureInvicibility = 60f * 15f;
public Item resupplyItem = Items.copper;
public CoreBlock(String name){
@@ -46,12 +47,16 @@ public class CoreBlock extends StorageBlock{
update = true;
hasItems = true;
priority = TargetPriority.core;
flags = EnumSet.of(BlockFlag.core, BlockFlag.unitModifier);
flags = EnumSet.of(BlockFlag.core);
unitCapModifier = 10;
loopSound = Sounds.respawning;
loopSoundVolume = 1f;
drawDisabled = false;
canOverdrive = false;
//support everything
envEnabled = Env.any;
drawDisabled = false;
replaceable = false;
}
@@ -97,6 +102,15 @@ public class CoreBlock extends StorageBlock{
));
}
@Override
public void init(){
//assign to update clipSize internally
lightRadius = 30f + 20f * size;
emitLight = true;
super.init();
}
@Override
public boolean canBreak(Tile tile){
return false;
@@ -156,19 +170,29 @@ public class CoreBlock extends StorageBlock{
if(!canPlaceOn(world.tile(x, y), player.team())){
drawPlaceText(Core.bundle.get((player.team().core() != null && player.team().core().items.has(requirements, state.rules.buildCostMultiplier)) || state.rules.infiniteResources ?
drawPlaceText(Core.bundle.get(
(player.team().core() != null && player.team().core().items.has(requirements, state.rules.buildCostMultiplier)) || state.rules.infiniteResources ?
"bar.corereq" :
"bar.noresources"
), x, y, valid);
}
}
public class CoreBuild extends Building implements ControlBlock{
public class CoreBuild extends Building{
public int storageCapacity;
//note that this unit is never actually used for control; the possession handler makes the player respawn when this unit is controlled
public BlockUnitc unit = Nulls.blockUnit;
public boolean noEffect = false;
public Team lastDamage = Team.derelict;
public float iframes = -1f;
@Override
public void damage(@Nullable Team source, float damage){
if(iframes > 0) return;
if(source != null && source != team){
lastDamage = source;
}
super.damage(source, damage);
}
@Override
public double sense(LAccess sensor){
@@ -177,23 +201,34 @@ public class CoreBlock extends StorageBlock{
}
@Override
public void created(){
unit = (BlockUnitc)UnitTypes.block.create(team);
unit.tile(this);
public boolean canControlSelect(Player player){
return true;
}
@Override
public Unit unit(){
return (Unit)unit;
public void onControlSelect(Player player){
Fx.spawn.at(player);
if(net.client()){
control.input.controlledType = null;
}
player.clearUnit();
player.deathTimer = Player.deathDelay + 1f;
requestSpawn(player);
}
public void requestSpawn(Player player){
//do not try to respawn in unsupported environments at all
if(!unitType.supportsEnv(state.rules.environment)) return;
Call.playerSpawn(tile, player);
}
@Override
public void updateTile(){
iframes -= Time.delta;
//resupply nearby units
if(items.has(resupplyItem) && timer(timerResupply, resupplyRate) && ResupplyPoint.resupply(this, resupplyRange, ammoAmount, resupplyItem.color)){
items.remove(resupplyItem, 1);
@@ -208,7 +243,13 @@ public class CoreBlock extends StorageBlock{
@Override
public void onDestroyed(){
super.onDestroyed();
if(state.rules.coreCapture){
//just create an explosion, no fire. this prevents immediate recapture
Damage.dynamicExplosion(x, y, 0, 0, 0, tilesize * block.size / 2f, state.rules.damageExplosions);
Fx.commandSend.at(x, y, 140f);
}else{
super.onDestroyed();
}
//add a spawn to the map for future reference - waves should be disabled, so it shouldn't matter
if(state.isCampaign() && team == state.rules.waveTeam && team.cores().size <= 1){
@@ -221,9 +262,18 @@ public class CoreBlock extends StorageBlock{
}
}
@Override
public void afterDestroyed(){
if(state.rules.coreCapture){
tile.setBlock(block, lastDamage);
//core is invincible for several seconds to prevent recapture
((CoreBuild)tile.build).iframes = captureInvicibility;
}
}
@Override
public void drawLight(){
Drawf.light(team, x, y, 30f + 20f * size, Pal.accent, 0.65f + Mathf.absin(20f, 0.1f));
Drawf.light(team, x, y, lightRadius, Pal.accent, 0.65f + Mathf.absin(20f, 0.1f));
}
@Override
@@ -238,6 +288,8 @@ public class CoreBlock extends StorageBlock{
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
for(Building other : state.teams.cores(team)){
if(other.tile() != tile){
this.items = other.items;

View File

@@ -10,12 +10,12 @@ import mindustry.gen.*;
import mindustry.type.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Unloader extends Block{
public float speed = 1f;
public final int timerUnload = timers++;
public Unloader(String name){
super(name);
@@ -33,6 +33,12 @@ public class Unloader extends Block{
configClear((UnloaderBuild tile) -> tile.sortItem = null);
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.speed, 60f / speed, StatUnit.itemsSecond);
}
@Override
public void drawRequestConfig(BuildPlan req, Eachable<BuildPlan> list){
drawRequestConfigCenter(req, req.config, "unloader-center");
@@ -45,6 +51,7 @@ public class Unloader extends Block{
}
public class UnloaderBuild extends Building{
public float unloadTimer = 0f;
public Item sortItem = null;
public Building dumpingTo;
public int offset = 0;
@@ -52,7 +59,8 @@ public class Unloader extends Block{
@Override
public void updateTile(){
if(timer(timerUnload, speed / timeScale)){
if((unloadTimer += delta()) >= speed){
boolean any = false;
if(rotations == null || rotations.length != proximity.size){
rotations = new int[proximity.size];
}
@@ -72,6 +80,7 @@ public class Unloader extends Block{
//remove item if it's dumped correctly
if(put(item)){
other.items.remove(item, 1);
any = true;
if(sortItem == null){
rotations[pos] = item.id + 1;
@@ -84,6 +93,12 @@ public class Unloader extends Block{
}
}
if(any){
unloadTimer %= speed;
}else{
unloadTimer = Math.min(unloadTimer, speed);
}
if(proximity.size > 0){
offset ++;
offset %= proximity.size;

View File

@@ -13,6 +13,7 @@ import mindustry.entities.units.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.blocks.payloads.*;
@@ -71,12 +72,12 @@ public class Reconstructor extends UnitBlock{
for(var upgrade : upgrades){
float size = 8 * 3;
if(upgrade[0].unlockedNow() && upgrade[1].unlockedNow()){
table.image(upgrade[0].icon(Cicon.small)).size(size).padRight(4).padLeft(10).scaling(Scaling.fit).right();
table.image(upgrade[0].uiIcon).size(size).padRight(4).padLeft(10).scaling(Scaling.fit).right();
table.add(upgrade[0].localizedName).left();
table.add("[lightgray] -> ");
table.image(upgrade[1].icon(Cicon.small)).size(size).padRight(4).scaling(Scaling.fit);
table.image(upgrade[1].uiIcon).size(size).padRight(4).scaling(Scaling.fit);
table.add(upgrade[1].localizedName).left();
table.row();
}
@@ -107,6 +108,11 @@ public class Reconstructor extends UnitBlock{
return progress / constructTime;
}
@Override
public boolean acceptUnitPayload(Unit unit){
return hasUpgrade(unit.type);
}
@Override
public boolean acceptPayload(Building source, Payload payload){
return this.payload == null
@@ -147,7 +153,7 @@ public class Reconstructor extends UnitBlock{
if(constructing() && hasArrived()){
Draw.draw(Layer.blockOver, () -> {
Draw.alpha(1f - progress/ constructTime);
Draw.rect(payload.unit.type.icon(Cicon.full), x, y, payload.rotation() - 90);
Draw.rect(payload.unit.type.fullIcon, x, y, payload.rotation() - 90);
Draw.reset();
Drawf.construct(this, upgrade(payload.unit.type), payload.rotation() - 90f, progress / constructTime, speedScl, time);
});
@@ -193,6 +199,12 @@ public class Reconstructor extends UnitBlock{
time += edelta() * speedScl * state.rules.unitBuildSpeedMultiplier;
}
@Override
public double sense(LAccess sensor){
if(sensor == LAccess.progress) return Mathf.clamp(fraction());
return super.sense(sensor);
}
@Override
public boolean shouldConsume(){
return constructing();

View File

@@ -8,29 +8,47 @@ import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.logic.*;
import mindustry.world.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class RepairPoint extends Block{
static final Rect rect = new Rect();
static final Rand rand = new Rand();
public int timerTarget = timers++;
public int timerEffect = timers++;
public float repairRadius = 50f;
public float repairSpeed = 0.3f;
public float powerUse;
public float length = 5f;
public float beamWidth = 1f;
public float pulseRadius = 6f;
public float pulseStroke = 2f;
public boolean acceptCoolant = false;
public @Load("@-base") TextureRegion baseRegion;
public @Load("laser") TextureRegion laser;
public @Load("laser-end") TextureRegion laserEnd;
public float coolantUse = 0.5f;
/** Effect displayed when coolant is used. */
public Effect coolEffect = Fx.fuelburn;
/** How much healing is increased by with heat capacity. */
public float coolantMultiplier = 1f;
public Color laserColor = Color.valueOf("e8ffd7");
public @Load(value = "@-base", fallback = "block-@size") TextureRegion baseRegion;
public @Load("laser-white") TextureRegion laser;
public @Load("laser-white-end") TextureRegion laserEnd;
public @Load("laser-top") TextureRegion laserTop;
public @Load("laser-top-end") TextureRegion laserTopEnd;
public Color laserColor = Color.valueOf("98ffa9"), laserTopColor = Color.white.cpy();
public RepairPoint(String name){
super(name);
@@ -39,18 +57,30 @@ public class RepairPoint extends Block{
flags = EnumSet.of(BlockFlag.repair);
hasPower = true;
outlineIcon = true;
expanded = true;
//yeah, this isn't the same thing, but it's close enough
group = BlockGroup.projectors;
}
@Override
public void setStats(){
super.setStats();
stats.add(Stat.range, repairRadius / tilesize, StatUnit.blocks);
stats.add(Stat.repairSpeed, repairSpeed * 60f, StatUnit.perSecond);
if(acceptCoolant){
stats.add(Stat.booster, StatValues.strengthBoosters(coolantMultiplier, l -> consumes.liquidfilters.get(l.id)));
}
}
@Override
public void init(){
consumes.powerCond(powerUse, entity -> ((RepairPointBuild)entity).target != null);
if(acceptCoolant){
hasLiquids = true;
consumes.add(new ConsumeLiquidFilter(liquid -> liquid.temperature <= 0.5f && liquid.flammability < 0.1f, coolantUse)).optional(true, true);
}
consumes.powerCond(powerUse, (RepairPointBuild entity) -> entity.target != null);
clipSize = Math.max(clipSize, (repairRadius + tilesize) * 2);
super.init();
}
@@ -66,8 +96,56 @@ public class RepairPoint extends Block{
return new TextureRegion[]{baseRegion, region};
}
public static void drawBeam(float x, float y, float rotation, float length, int id, Sized target, Team team,
float strength, float pulseStroke, float pulseRadius, float beamWidth,
Vec2 lastEnd, Vec2 offset,
Color laserColor, Color laserTopColor,
TextureRegion laser, TextureRegion laserEnd, TextureRegion laserTop, TextureRegion laserTopEnd){
if(target != null){
float
originX = x + Angles.trnsx(rotation, length),
originY = y + Angles.trnsy(rotation, length);
rand.setSeed(id + (target instanceof Entityc e ? e.id() : 0));
lastEnd.set(target).sub(originX, originY);
lastEnd.setLength(Math.max(2f, lastEnd.len()));
lastEnd.add(offset.trns(
rand.random(360f) + Time.time/2f,
Mathf.sin(Time.time + rand.random(200f), 55f, rand.random(target.hitSize() * 0.2f, target.hitSize() * 0.45f))
).rotate(target instanceof Rotc rot ? rot.rotation() : 0f));
lastEnd.add(originX, originY);
}
if(strength > 0.01f){
float
originX = x + Angles.trnsx(rotation, length),
originY = y + Angles.trnsy(rotation, length);
Draw.z(Layer.flyingUnit + 1); //above all units
Draw.color(laserColor);
float f = (Time.time / 85f + rand.random(1f)) % 1f;
Draw.alpha(1f - Interp.pow5In.apply(f));
Lines.stroke(strength * pulseStroke);
Lines.circle(lastEnd.x, lastEnd.y, 1f + f * pulseRadius);
Draw.color(laserColor);
Drawf.laser(team, laser, laserEnd, originX, originY, lastEnd.x, lastEnd.y, strength * beamWidth);
Draw.z(Layer.flyingUnit + 1.1f);
Draw.color(laserTopColor);
Drawf.laser(team, laserTop, laserTopEnd, originX, originY, lastEnd.x, lastEnd.y, strength * beamWidth);
Draw.color();
}
}
public class RepairPointBuild extends Building implements Ranged{
public Unit target;
public Vec2 offset = new Vec2(), lastEnd = new Vec2();
public float strength, rotation = 90;
@Override
@@ -78,17 +156,9 @@ public class RepairPoint extends Block{
Drawf.shadow(region, x - (size / 2f), y - (size / 2f), rotation - 90);
Draw.rect(region, x, y, rotation - 90);
if(target != null && Angles.angleDist(angleTo(target), rotation) < 30f){
Draw.z(Layer.flyingUnit + 1); //above all units
float ang = angleTo(target);
float len = 5f;
Draw.color(laserColor);
Drawf.laser(team, laser, laserEnd,
x + Angles.trnsx(ang, len), y + Angles.trnsy(ang, len),
target.x(), target.y(), strength);
Draw.color();
}
drawBeam(x, y, rotation, length, id, target, team, strength,
pulseStroke, pulseRadius, beamWidth, lastEnd, offset, laserColor, laserTopColor,
laser, laserEnd, laserTop, laserTopEnd);
}
@Override
@@ -98,21 +168,33 @@ public class RepairPoint extends Block{
@Override
public void updateTile(){
boolean targetIsBeingRepaired = false;
if(target != null && (target.dead() || target.dst(tile) - target.hitSize/2f > repairRadius || target.health() >= target.maxHealth())){
target = null;
}else if(target != null && consValid()){
target.heal(repairSpeed * strength * edelta());
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.5f * efficiency() * timeScale);
targetIsBeingRepaired = true;
float multiplier = 1f;
if(acceptCoolant){
var liq = consumes.<ConsumeLiquidBase>get(ConsumeType.liquid);
multiplier = liq.valid(this) ? 1f + liquids.current().heatCapacity * coolantMultiplier : 1f;
}
if(target != null && targetIsBeingRepaired){
strength = Mathf.lerpDelta(strength, 1f, 0.08f * Time.delta);
}else{
strength = Mathf.lerpDelta(strength, 0f, 0.07f * Time.delta);
if(target != null && (target.dead() || target.dst(tile) - target.hitSize/2f > repairRadius || target.health() >= target.maxHealth())){
target = null;
}
if(target == null){
offset.setZero();
}
boolean healed = false;
if(target != null && consValid()){
float angle = Angles.angle(x, y, target.x + offset.x, target.y + offset.y);
if(Angles.angleDist(angle, rotation) < 30f){
healed = true;
target.heal(repairSpeed * strength * edelta() * multiplier);
}
rotation = Mathf.slerpDelta(rotation, angle, 0.5f * efficiency() * timeScale);
}
strength = Mathf.lerpDelta(strength, healed ? 1f : 0f, 0.08f * Time.delta);
if(timer(timerTarget, 20)){
rect.setSize(repairRadius * 2).setCenter(x, y);
target = Units.closest(team, x, y, repairRadius, Unit::damaged);

View File

@@ -1,20 +1,12 @@
package mindustry.world.blocks.units;
import arc.*;
import arc.math.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.production.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class UnitBlock extends PayloadAcceptor{
public class UnitBlock extends PayloadBlock{
public UnitBlock(String name){
super(name);
@@ -31,7 +23,7 @@ public class UnitBlock extends PayloadAcceptor{
build.spawned();
}
public class UnitBuild extends PayloadAcceptorBuild<UnitPayload>{
public class UnitBuild extends PayloadBlockBuild<UnitPayload>{
public float progress, time, speedScl;
public void spawned(){

View File

@@ -98,7 +98,7 @@ public class UnitFactory extends UnitBlock{
table.row();
for(var plan : p){
if(plan.unit.unlockedNow()){
table.image(plan.unit.icon(Cicon.small)).size(8 * 3).padRight(2).right();
table.image(plan.unit.uiIcon).size(8 * 3).padRight(2).right();
table.add(plan.unit.localizedName).left();
table.add(Strings.autoFixed(plan.time / 60f, 1) + " " + Core.bundle.get("unit.seconds")).color(Color.lightGray).padLeft(12).left();
table.row();
@@ -143,6 +143,7 @@ public class UnitFactory extends UnitBlock{
@Override
public Object senseObject(LAccess sensor){
if(sensor == LAccess.config) return currentPlan == -1 ? null : plans.get(currentPlan).unit;
if(sensor == LAccess.progress) return Mathf.clamp(fraction());
return super.senseObject(sensor);
}
@@ -172,7 +173,7 @@ public class UnitFactory extends UnitBlock{
table.table(t -> {
t.left();
t.image().update(i -> {
i.setDrawable(currentPlan == -1 ? Icon.cancel : reg.set(plans.get(currentPlan).unit.icon(Cicon.medium)));
i.setDrawable(currentPlan == -1 ? Icon.cancel : reg.set(plans.get(currentPlan).unit.uiIcon));
i.setScaling(Scaling.fit);
i.setColor(currentPlan == -1 ? Color.lightGray : Color.white);
}).size(32).padBottom(-4).padRight(2);

View File

@@ -8,10 +8,10 @@ import mindustry.world.meta.*;
/** An abstract class that defines a type of resource that a block can consume. */
public abstract class Consume{
/** If true, this consumer will not influence consumer validity. */
protected boolean optional;
public boolean optional;
/** If true, this consumer will be displayed as a boost input. */
protected boolean booster;
protected boolean update = true;
public boolean booster;
public boolean update = true;
/**
* Apply a filter to items accepted.

View File

@@ -46,7 +46,7 @@ public class ConsumeItemDynamic extends Consume{
int i = 0;
for(ItemStack stack : items.get(tile)){
table.add(new ReqImage(new ItemImage(stack.item.icon(Cicon.medium), stack.amount),
table.add(new ReqImage(new ItemImage(stack.item.uiIcon, stack.amount),
() -> tile.items != null && tile.items.has(stack.item, stack.amount))).padRight(8).left();
if(++i % 4 == 0) table.row();
}

View File

@@ -7,7 +7,6 @@ import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -31,7 +30,7 @@ public class ConsumeItemFilter extends Consume{
@Override
public void build(Building tile, Table table){
MultiReqImage image = new MultiReqImage();
content.items().each(i -> filter.get(i) && i.unlockedNow(), item -> image.add(new ReqImage(new ItemImage(item.icon(Cicon.medium), 1),
content.items().each(i -> filter.get(i) && i.unlockedNow(), item -> image.add(new ReqImage(new ItemImage(item.uiIcon, 1),
() -> tile.items != null && tile.items.has(item))));
table.add(image).size(8 * 4);
@@ -71,6 +70,6 @@ public class ConsumeItemFilter extends Consume{
@Override
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, new ItemFilterValue(filter));
stats.add(booster ? Stat.booster : Stat.input, stats.timePeriod < 0 ? StatValues.items(filter) : StatValues.items(stats.timePeriod, filter));
}
}

View File

@@ -6,7 +6,6 @@ import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
public class ConsumeItems extends Consume{
public final ItemStack[] items;
@@ -22,7 +21,7 @@ public class ConsumeItems extends Consume{
@Override
public void applyItemFilter(Bits filter){
for(ItemStack stack : items){
for(var stack : items){
filter.set(stack.item.id);
}
}
@@ -36,10 +35,10 @@ public class ConsumeItems extends Consume{
public void build(Building tile, Table table){
table.table(c -> {
int i = 0;
for(ItemStack stack : items){
c.add(new ReqImage(new ItemImage(stack.item.icon(Cicon.medium), stack.amount),
for(var stack : items){
c.add(new ReqImage(new ItemImage(stack.item.uiIcon, stack.amount),
() -> tile.items != null && tile.items.has(stack.item, stack.amount))).padRight(8);
if(++i % 4 == 0) table.row();
if(++i % 4 == 0) c.row();
}
}).left();
}
@@ -56,7 +55,7 @@ public class ConsumeItems extends Consume{
@Override
public void trigger(Building entity){
for(ItemStack stack : items){
for(var stack : items){
entity.items.remove(stack);
}
}
@@ -68,6 +67,6 @@ public class ConsumeItems extends Consume{
@Override
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, new ItemListValue(items));
stats.add(booster ? Stat.booster : Stat.input, stats.timePeriod < 0 ? StatValues.items(items) : StatValues.items(stats.timePeriod, items));
}
}

View File

@@ -7,6 +7,8 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class ConsumeLiquid extends ConsumeLiquidBase{
public final Liquid liquid;
@@ -26,7 +28,7 @@ public class ConsumeLiquid extends ConsumeLiquidBase{
@Override
public void build(Building tile, Table table){
table.add(new ReqImage(liquid.icon(Cicon.medium), () -> valid(tile))).size(8 * 4);
table.add(new ReqImage(liquid.uiIcon, () -> valid(tile))).size(iconMed).top().left();
}
@Override

View File

@@ -7,7 +7,6 @@ import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import mindustry.world.meta.values.*;
import static mindustry.Vars.*;
@@ -28,7 +27,7 @@ public class ConsumeLiquidFilter extends ConsumeLiquidBase{
public void build(Building build, Table table){
Seq<Liquid> list = content.liquids().select(l -> !l.isHidden() && filter.get(l));
MultiReqImage image = new MultiReqImage();
list.each(liquid -> image.add(new ReqImage(liquid.icon(Cicon.medium), () ->
list.each(liquid -> image.add(new ReqImage(liquid.uiIcon, () ->
build.liquids != null && build.liquids.current() == liquid && build.liquids.get(liquid) >= Math.max(use(build), amount * build.delta()))));
table.add(image).size(8 * 4);
@@ -51,6 +50,6 @@ public class ConsumeLiquidFilter extends ConsumeLiquidBase{
@Override
public void display(Stats stats){
stats.add(booster ? Stat.booster : Stat.input, new LiquidFilterValue(filter, amount * 60f, true));
stats.add(booster ? Stat.booster : Stat.input, StatValues.liquids(filter, amount * 60f, true));
}
}

View File

@@ -15,17 +15,21 @@ public class DrawAnimation extends DrawBlock{
public TextureRegion liquid, top;
@Override
public void draw(GenericCrafterBuild entity){
Draw.rect(entity.block.region, entity.x, entity.y);
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y);
Draw.rect(
sine ?
frames[(int)Mathf.absin(entity.totalProgress, frameSpeed, frameCount - 0.001f)] :
frames[(int)((entity.totalProgress / frameSpeed) % frameCount)],
entity.x, entity.y);
Draw.color(Color.clear, entity.liquids.current().color, entity.liquids.total() / entity.block.liquidCapacity);
Draw.rect(liquid, entity.x, entity.y);
Draw.color();
Draw.rect(top, entity.x, entity.y);
frames[(int)Mathf.absin(build.totalProgress, frameSpeed, frameCount - 0.001f)] :
frames[(int)((build.totalProgress / frameSpeed) % frameCount)],
build.x, build.y);
if(build.liquids != null){
Draw.color(Color.clear, build.liquids.current().color, build.liquids.total() / build.block.liquidCapacity);
Draw.rect(liquid, build.x, build.y);
Draw.color();
}
if(top.found()){
Draw.rect(top, build.x, build.y);
}
}
@Override

View File

@@ -0,0 +1,69 @@
package mindustry.world.draw;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
//TODO
public class DrawArcSmelter extends DrawBlock{
public TextureRegion top, bottom;
public Color flameColor = Color.valueOf("f58349"), midColor = Color.valueOf("f2d585");
public float flameRad = 1f, circleSpace = 2f, flameRadiusScl = 3f, flameRadiusMag = 0.3f, circleStroke = 1.5f;
public float alpha = 0.68f;
public int particles = 25;
public float particleLife = 40f, particleRad = 7f, particleStroke = 1.1f, particleLen = 3f;
@Override
public void draw(GenericCrafterBuild build){
Draw.rect(bottom, build.x, build.y);
if(build.warmup > 0f && flameColor.a > 0.001f){
Lines.stroke(circleStroke * build.warmup);
float si = Mathf.absin(flameRadiusScl, flameRadiusMag);
float a = alpha * build.warmup;
Draw.blend(Blending.additive);
Draw.color(midColor, a);
Fill.circle(build.x, build.y, flameRad + si);
Draw.color(flameColor, a);
Lines.circle(build.x, build.y, (flameRad + circleSpace + si) * build.warmup);
Lines.stroke(particleStroke * build.warmup);
float base = (Time.time / particleLife);
rand.setSeed(build.id);
for(int i = 0; i < particles; i++){
float fin = (rand.random(1f) + base) % 1f, fout = 1f - fin;
float angle = rand.random(360f);
float len = particleRad * Interp.pow2Out.apply(fin);
Lines.lineAngle(build.x + Angles.trnsx(angle, len), build.y + Angles.trnsy(angle, len), angle, particleLen * fout * build.warmup);
}
Draw.blend();
Draw.reset();
}
Draw.rect(top, build.x, build.y);
Draw.rect(build.block.region, build.x, build.y);
}
@Override
public void load(Block block){
top = Core.atlas.find(block.name + "-top");
bottom = Core.atlas.find(block.name + "-bottom");
}
@Override
public TextureRegion[] icons(Block block){
return new TextureRegion[]{bottom, block.region, top};
}
}

View File

@@ -1,16 +1,23 @@
package mindustry.world.draw;
import arc.graphics.g2d.*;
import arc.math.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
/** An implementation of custom rendering behavior for a block.
* This is used mostly for mods. */
public class DrawBlock{
protected static final Rand rand = new Rand();
/** Draws the block. */
public void draw(GenericCrafterBuild entity){
Draw.rect(entity.block.region, entity.x, entity.y, entity.block.rotate ? entity.rotdeg() : 0);
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y, build.block.rotate ? build.rotdeg() : 0);
}
/** Draws any extra light for the block. */
public void drawLight(GenericCrafterBuild build){
}
/** Load any relevant texture regions. */

View File

@@ -0,0 +1,57 @@
package mindustry.world.draw;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
public class DrawCells extends DrawBlock{
public TextureRegion bottom, middle;
public Color color = Color.white.cpy(), particleColorFrom = Color.black.cpy(), particleColorTo = Color.black.cpy();
public int particles = 12;
public float range = 4f, recurrence = 6f, radius = 3f, lifetime = 60f;
@Override
public void draw(GenericCrafterBuild build){
Draw.rect(bottom, build.x, build.y);
Drawf.liquid(middle, build.x, build.y, build.warmup, color);
if(build.warmup > 0.001f){
rand.setSeed(build.id);
for(int i = 0; i < particles; i++){
float offset = rand.nextFloat() * 999999f;
float x = rand.range(range), y = rand.range(range);
float fin = 1f - (((Time.time + offset) / lifetime) % recurrence);
float ca = rand.random(0.1f, 1f);
float fslope = Mathf.slope(fin);
if(fin > 0){
Draw.color(particleColorFrom, particleColorTo, ca);
Draw.alpha(build.warmup);
Fill.circle(build.x + x, build.y + y, fslope * radius);
}
}
}
Draw.color();
Draw.rect(build.block.region, build.x, build.y);
}
@Override
public void load(Block block){
bottom = Core.atlas.find(block.name + "-bottom");
middle = Core.atlas.find(block.name + "-middle");
}
@Override
public TextureRegion[] icons(Block block){
return new TextureRegion[]{bottom, block.region};
}
}

View File

@@ -0,0 +1,56 @@
package mindustry.world.draw;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.util.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
public class DrawCultivator extends DrawBlock{
public Color plantColor = Color.valueOf("5541b1");
public Color plantColorLight = Color.valueOf("7457ce");
public Color bottomColor = Color.valueOf("474747");
public int bubbles = 12, sides = 8;
public float strokeMin = 0.2f, spread = 3f, timeScl = 70f;
public float recurrence = 6f, radius = 3f;
public TextureRegion middle;
public TextureRegion top;
@Override
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y);
Drawf.liquid(middle, build.x, build.y, build.warmup, plantColor);
Draw.color(bottomColor, plantColorLight, build.warmup);
rand.setSeed(build.pos());
for(int i = 0; i < bubbles; i++){
float x = rand.range(spread), y = rand.range(spread);
float life = 1f - ((Time.time / timeScl + rand.random(recurrence)) % recurrence);
if(life > 0){
Lines.stroke(build.warmup * (life + strokeMin));
Lines.poly(build.x + x, build.y + y, sides, (1f - life) * radius);
}
}
Draw.color();
Draw.rect(top, build.x, build.y);
}
@Override
public void load(Block block){
middle = Core.atlas.find(block.name + "-middle");
top = Core.atlas.find(block.name + "-top");
}
@Override
public TextureRegion[] icons(Block block){
return new TextureRegion[]{block.region, top};
}
}

View File

@@ -11,10 +11,10 @@ public class DrawGlow extends DrawBlock{
public TextureRegion top;
@Override
public void draw(GenericCrafterBuild entity){
Draw.rect(entity.block.region, entity.x, entity.y);
Draw.alpha(Mathf.absin(entity.totalProgress, glowScale, glowAmount) * entity.warmup);
Draw.rect(top, entity.x, entity.y);
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y);
Draw.alpha(Mathf.absin(build.totalProgress, glowScale, glowAmount) * build.warmup);
Draw.rect(top, build.x, build.y);
Draw.reset();
}

View File

@@ -10,19 +10,19 @@ public class DrawMixer extends DrawBlock{
public TextureRegion liquid, top, bottom;
@Override
public void draw(GenericCrafterBuild entity){
float rotation = entity.block.rotate ? entity.rotdeg() : 0;
public void draw(GenericCrafterBuild build){
float rotation = build.block.rotate ? build.rotdeg() : 0;
Draw.rect(bottom, entity.x, entity.y, rotation);
Draw.rect(bottom, build.x, build.y, rotation);
if(entity.liquids.total() > 0.001f){
Draw.color(((GenericCrafter)entity.block).outputLiquid.liquid.color);
Draw.alpha(entity.liquids.get(((GenericCrafter)entity.block).outputLiquid.liquid) / entity.block.liquidCapacity);
Draw.rect(liquid, entity.x, entity.y, rotation);
if(build.liquids.total() > 0.001f){
Draw.color(((GenericCrafter)build.block).outputLiquid.liquid.color);
Draw.alpha(build.liquids.get(((GenericCrafter)build.block).outputLiquid.liquid) / build.block.liquidCapacity);
Draw.rect(liquid, build.x, build.y, rotation);
Draw.color();
}
Draw.rect(top, entity.x, entity.y, rotation);
Draw.rect(top, build.x, build.y, rotation);
}
@Override

View File

@@ -9,9 +9,9 @@ public class DrawRotator extends DrawBlock{
public TextureRegion rotator;
@Override
public void draw(GenericCrafterBuild entity){
Draw.rect(entity.block.region, entity.x, entity.y);
Draw.rect(rotator, entity.x, entity.y, entity.totalProgress * 2f);
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y);
Draw.rect(rotator, build.x, build.y, build.totalProgress * 2f);
}
@Override

View File

@@ -0,0 +1,57 @@
package mindustry.world.draw;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.production.GenericCrafter.*;
public class DrawSmelter extends DrawBlock{
public Color flameColor = Color.valueOf("ffc999");
public TextureRegion top;
public float lightRadius = 60f, lightAlpha = 0.65f, lightSinScl = 10f, lightSinMag = 5;
public float flameRadius = 3f, flameRadiusIn = 1.9f, flameRadiusScl = 5f, flameRadiusMag = 2f, flameRadiusInMag = 1f;
public DrawSmelter(){
}
public DrawSmelter(Color flameColor){
this.flameColor = flameColor;
}
@Override
public void load(Block block){
top = Core.atlas.find(block.name + "-top");
}
@Override
public void draw(GenericCrafterBuild build){
Draw.rect(build.block.region, build.x, build.y, build.block.rotate ? build.rotdeg() : 0);
if(build.warmup > 0f && flameColor.a > 0.001f){
float g = 0.3f;
float r = 0.06f;
float cr = Mathf.random(0.1f);
Draw.z(Layer.block + 0.01f);
Draw.alpha(((1f - g) + Mathf.absin(Time.time, 8f, g) + Mathf.random(r) - r) * build.warmup);
Draw.tint(flameColor);
Fill.circle(build.x, build.y, flameRadius + Mathf.absin(Time.time, flameRadiusScl, flameRadiusMag) + cr);
Draw.color(1f, 1f, 1f, build.warmup);
Draw.rect(top, build.x, build.y);
Fill.circle(build.x, build.y, flameRadiusIn + Mathf.absin(Time.time, flameRadiusScl, flameRadiusInMag) + cr);
Draw.color();
}
}
@Override
public void drawLight(GenericCrafterBuild build){
Drawf.light(build.team, build.x, build.y, (lightRadius + Mathf.absin(lightSinScl, lightSinMag)) * build.warmup * build.block.size, flameColor, lightAlpha);
}
}

View File

@@ -12,22 +12,22 @@ public class DrawWeave extends DrawBlock{
public TextureRegion weave, bottom;
@Override
public void draw(GenericCrafterBuild entity){
Draw.rect(bottom, entity.x, entity.y);
Draw.rect(weave, entity.x, entity.y, entity.totalProgress);
public void draw(GenericCrafterBuild build){
Draw.rect(bottom, build.x, build.y);
Draw.rect(weave, build.x, build.y, build.totalProgress);
Draw.color(Pal.accent);
Draw.alpha(entity.warmup);
Draw.alpha(build.warmup);
Lines.lineAngleCenter(
entity.x + Mathf.sin(entity.totalProgress, 6f, Vars.tilesize / 3f * entity.block.size),
entity.y,
build.x + Mathf.sin(build.totalProgress, 6f, Vars.tilesize / 3f * build.block.size),
build.y,
90,
entity.block.size * Vars.tilesize / 2f);
build.block.size * Vars.tilesize / 2f);
Draw.reset();
Draw.rect(entity.block.region, entity.x, entity.y);
Draw.rect(build.block.region, build.x, build.y);
}
@Override

View File

@@ -2,23 +2,47 @@ package mindustry.world.meta;
import mindustry.*;
public enum Attribute{
/** Heat of this block. Used for calculating output of thermal generators. */
heat,
/** Spore content of this block. Used for increasing cultivator yield. */
spores,
/** Water content of this block. Used for increasing water extractor yield. */
water,
/** Oil content of this block. Used for increasing oil extractor yield. */
oil,
public class Attribute{
public static Attribute[] all = {};
public static final Attribute
/** Heat content. Used for thermal generator yield. */
heat = add("heat"),
/** Spore content. Used for cultivator yield. */
spores = add("spores"),
/** Water content. Used for water extractor yield. */
water = add("water"),
/** Oil content. Used for oil extractor yield. */
oil = add("oil"),
/** Light coverage. Negative values decrease solar panel efficiency. */
light;
light = add("light");
public static final Attribute[] all = values();
public final int id;
public final String name;
/** @return the envrionmental value for this attribute. */
/** @return the environmental value for this attribute. */
public float env(){
if(Vars.state == null) return 0;
return Vars.state.envAttrs.get(this);
}
Attribute(int id, String name){
this.id = id;
this.name = name;
}
@Override
public String toString(){
return name;
}
/** Automatically registers this attribute for use. Do not call after mod init. */
public static Attribute add(String name){
Attribute a = new Attribute(all.length, name);
Attribute[] prev = all;
all = new Attribute[all.length + 1];
System.arraycopy(prev, 0, all, 0, a.id);
all[a.id] = a;
return a;
}
}

View File

@@ -22,10 +22,13 @@ public enum BlockFlag{
resupply,
/** Any reactor block. */
reactor,
/** Any block that boosts unit capacity. */
/** This flag is unused, and will be removed. */
@Deprecated
unitModifier,
/** Blocks that extinguishes fires. */
extinguisher;
extinguisher,
/** Just a launch pad. */
launchPad;
public final static BlockFlag[] all = values();

View File

@@ -0,0 +1,14 @@
package mindustry.world.meta;
/** Environmental flags for different types of locations. */
public class Env{
public static final int
terrestrial = 1,
space = 1 << 1,
underwater = 1 << 2,
spores = 1 << 3,
scorching = 1 << 4,
groundOil = 1 << 5,
groundWater = 1 << 6,
any = 0xffffffff;
}

View File

@@ -70,6 +70,7 @@ public enum Stat{
speedIncrease(StatCat.function),
repairTime(StatCat.function),
repairSpeed(StatCat.function),
range(StatCat.function),
shootRange(StatCat.function),
inaccuracy(StatCat.function),

View File

@@ -0,0 +1,310 @@
package mindustry.world.meta;
import arc.*;
import arc.func.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.ctype.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.*;
import mindustry.world.blocks.defense.turrets.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
/** Utilities for displaying certain stats in a table. */
public class StatValues{
public static StatValue string(String value, Object... args){
String result = Strings.format(value, args);
return table -> table.add(result);
}
public static StatValue bool(boolean value){
return table -> table.add(!value ? "@no" : "@yes");
}
public static StatValue number(float value, StatUnit unit){
return table -> {
int precision = Math.abs((int)value - value) <= 0.001f ? 0 : Math.abs((int)(value * 10) - value * 10) <= 0.001f ? 1 : 2;
table.add(Strings.fixed(value, precision));
table.add((unit.space ? " " : "") + unit.localized());
};
}
public static StatValue liquid(Liquid liquid, float amount, boolean perSecond){
return table -> table.add(new LiquidDisplay(liquid, amount, perSecond));
}
public static StatValue liquids(Boolf<Liquid> filter, float amount, boolean perSecond){
return table -> {
Seq<Liquid> list = new Seq<>();
for(Liquid item : content.liquids()){
if(!item.isHidden() && filter.get(item)) list.add(item);
}
for(int i = 0; i < list.size; i++){
table.add(new LiquidDisplay(list.get(i), amount, perSecond)).padRight(5);
if(i != list.size - 1){
table.add("/");
}
}
};
}
public static StatValue items(ItemStack... stacks){
return items(true, stacks);
}
public static StatValue items(boolean displayName, ItemStack... stacks){
return table -> {
for(ItemStack stack : stacks){
table.add(new ItemDisplay(stack.item, stack.amount, displayName)).padRight(5);
}
};
}
public static StatValue items(float timePeriod, ItemStack... stacks){
return table -> {
for(ItemStack stack : stacks){
table.add(new ItemDisplay(stack.item, stack.amount, timePeriod, true)).padRight(5);
}
};
}
public static StatValue items(Boolf<Item> filter){
return items(-1, filter);
}
public static StatValue items(float timePeriod, Boolf<Item> filter){
return table -> {
Seq<Item> list = content.items().select(filter);
for(int i = 0; i < list.size; i++){
Item item = list.get(i);
table.add(timePeriod <= 0 ? new ItemDisplay(item) : new ItemDisplay(item, 0, timePeriod, true)).padRight(5);
if(i != list.size - 1){
table.add("/");
}
}
};
}
public static StatValue content(UnlockableContent content){
return table -> {
table.add(new Image(content.uiIcon)).size(iconSmall).padRight(3);
table.add(content.localizedName).padRight(3);
};
}
public static StatValue floorEfficiency(Floor floor, float multiplier, boolean startZero){
return table -> table.stack(
new Image(floor.uiIcon).setScaling(Scaling.fit),
new Table(t -> t.top().right().add((multiplier < 0 ? "[scarlet]" : startZero ? "[accent]" : "[accent]+") + (int)((multiplier) * 100) + "%").style(Styles.outlineLabel))
);
}
public static StatValue blocks(Boolf<Block> pred){
return blocks(content.blocks().select(pred));
}
public static StatValue blocks(Seq<Block> list){
return table -> table.table(l -> {
l.left();
for(int i = 0; i < list.size; i++){
Block item = list.get(i);
l.image(item.uiIcon).size(iconSmall).padRight(2).padLeft(2).padTop(3).padBottom(3);
l.add(item.localizedName).left().padLeft(1).padRight(4);
if(i % 5 == 4){
l.row();
}
}
});
}
public static StatValue boosters(float reload, float maxUsed, float multiplier, boolean baseReload, Boolf<Liquid> filter){
return table -> {
table.row();
table.table(c -> {
for(Liquid liquid : content.liquids()){
if(!filter.get(liquid)) continue;
c.image(liquid.uiIcon).size(3 * 8).padRight(4).right().top();
c.add(liquid.localizedName).padRight(10).left().top();
c.table(Tex.underline, bt -> {
bt.left().defaults().padRight(3).left();
float reloadRate = (baseReload ? 1f : 0f) + maxUsed * multiplier * liquid.heatCapacity;
float standardReload = baseReload ? reload : reload / (maxUsed * multiplier * 0.4f);
float result = standardReload / (reload / reloadRate);
bt.add(Core.bundle.format("bullet.reload", Strings.autoFixed(result, 2)));
}).left().padTop(-9);
c.row();
}
}).colspan(table.getColumns());
table.row();
};
}
public static StatValue strengthBoosters(float multiplier, Boolf<Liquid> filter){
return table -> {
table.row();
table.table(c -> {
for(Liquid liquid : content.liquids()){
if(!filter.get(liquid)) continue;
c.image(liquid.uiIcon).size(3 * 8).padRight(4).right().top();
c.add(liquid.localizedName).padRight(10).left().top();
c.table(Tex.underline, bt -> {
bt.left().defaults().padRight(3).left();
float newRate = (1f + multiplier * liquid.heatCapacity);
bt.add(Core.bundle.format("bar.strength", Strings.autoFixed(newRate, 2)));
}).left().padTop(-9);
c.row();
}
}).colspan(table.getColumns());
table.row();
};
}
public static StatValue weapons(UnitType unit, Seq<Weapon> weapons){
return table -> {
table.row();
for(int i = 0; i < weapons.size;i ++){
Weapon weapon = weapons.get(i);
if(weapon.flipSprite){
//flipped weapons are not given stats
continue;
}
TextureRegion region = !weapon.name.equals("") && weapon.outlineRegion.found() ? weapon.outlineRegion : unit.fullIcon;
table.image(region).size(60).scaling(Scaling.bounded).right().top();
table.table(Tex.underline, w -> {
w.left().defaults().padRight(3).left();
if(weapon.inaccuracy > 0){
sep(w, "[lightgray]" + Stat.inaccuracy.localized() + ": [white]" + (int)weapon.inaccuracy + " " + StatUnit.degrees.localized());
}
sep(w, "[lightgray]" + Stat.reload.localized() + ": " + (weapon.mirror ? "2x " : "") + "[white]" + Strings.autoFixed(60f / weapon.reload * weapon.shots, 2));
ammo(ObjectMap.of(unit, weapon.bullet)).display(w);
}).padTop(-9).left();
table.row();
}
};
}
public static <T extends UnlockableContent> StatValue ammo(ObjectMap<T, BulletType> map){
return table -> {
table.row();
var orderedKeys = map.keys().toSeq();
orderedKeys.sort();
for(T t : orderedKeys){
boolean unit = t instanceof UnitType;
BulletType type = map.get(t);
//no point in displaying unit icon twice
if(!unit & !(t instanceof PowerTurret)){
table.image(icon(t)).size(3 * 8).padRight(4).right().top();
table.add(t.localizedName).padRight(10).left().top();
}
table.table(bt -> {
bt.left().defaults().padRight(3).left();
if(type.damage > 0 && (type.collides || type.splashDamage <= 0)){
if(type.continuousDamage() > 0){
bt.add(Core.bundle.format("bullet.damage", type.continuousDamage()) + StatUnit.perSecond.localized());
}else{
bt.add(Core.bundle.format("bullet.damage", type.damage));
}
}
if(type.buildingDamageMultiplier != 1){
sep(bt, Core.bundle.format("bullet.buildingdamage", (int)(type.buildingDamageMultiplier * 100)));
}
if(type.splashDamage > 0){
sep(bt, Core.bundle.format("bullet.splashdamage", (int)type.splashDamage, Strings.fixed(type.splashDamageRadius / tilesize, 1)));
}
if(!unit && !Mathf.equal(type.ammoMultiplier, 1f)){
sep(bt, Core.bundle.format("bullet.multiplier", (int)type.ammoMultiplier));
}
if(!Mathf.equal(type.reloadMultiplier, 1f)){
sep(bt, Core.bundle.format("bullet.reload", Strings.autoFixed(type.reloadMultiplier, 2)));
}
if(type.knockback > 0){
sep(bt, Core.bundle.format("bullet.knockback", Strings.autoFixed(type.knockback, 2)));
}
if(type.healPercent > 0f){
sep(bt, Core.bundle.format("bullet.healpercent", (int)type.healPercent));
}
if(type.pierce || type.pierceCap != -1){
sep(bt, type.pierceCap == -1 ? "@bullet.infinitepierce" : Core.bundle.format("bullet.pierce", type.pierceCap));
}
if(type.incendAmount > 0){
sep(bt, "@bullet.incendiary");
}
if(type.status != StatusEffects.none){
sep(bt, (type.minfo.mod == null ? type.status.emoji() : "") + "[stat]" + type.status.localizedName);
}
if(type.homingPower > 0.01f){
sep(bt, "@bullet.homing");
}
if(type.lightning > 0){
sep(bt, Core.bundle.format("bullet.lightning", type.lightning, type.lightningDamage < 0 ? type.damage : type.lightningDamage));
}
if(type.fragBullet != null){
sep(bt, "@bullet.frag");
}
}).padTop(unit ? 0 : -9).left().get().background(unit ? null : Tex.underline);
table.row();
}
};
}
//for AmmoListValue
private static void sep(Table table, String text){
table.row();
table.add(text);
}
private static TextureRegion icon(UnlockableContent t){
return t.uiIcon;
}
}

Some files were not shown because too many files have changed in this diff Show More