Merge branch 'master' into do-you-hear-the-voices-too

This commit is contained in:
Mythril382
2024-05-28 00:40:31 +08:00
committed by GitHub
129 changed files with 3164 additions and 1263 deletions

View File

@@ -66,7 +66,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
Time.setDeltaProvider(() -> {
float result = Core.graphics.getDeltaTime() * 60f;
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, 60f / 10f);
return (Float.isNaN(result) || Float.isInfinite(result)) ? 1f : Mathf.clamp(result, 0.0001f, maxDeltaClient);
});
UI.loadColors();

View File

@@ -137,6 +137,13 @@ public class Vars implements Loadable{
Color.valueOf("4b5ef1"),
Color.valueOf("2cabfe"),
};
/** Icons available to the user for customization in certain dialogs. */
public static final String[] accessibleIcons = {
"effect", "power", "logic", "units", "liquid", "production", "defense", "turret", "distribution", "crafting",
"settings", "cancel", "zoom", "ok", "star", "home", "pencil", "up", "down", "left", "right",
"hammer", "warning", "tree", "admin", "map", "modePvp", "terrain",
"modeSurvival", "commandRally", "commandAttack",
};
/** maximum TCP packet size */
public static final int maxTcpSize = 900;
/** default server port */
@@ -147,6 +154,8 @@ public class Vars implements Loadable{
public static final int maxModSubtitleLength = 40;
/** multicast group for discovery.*/
public static final String multicastGroup = "227.2.7.7";
/** Maximum delta time. If the actual delta time (*60) between frames is higher than this number, the game will start to slow down. */
public static float maxDeltaClient = 6f, maxDeltaServer = 10f;
/** whether the graphical game client has loaded */
public static boolean clientLoaded = false;
/** max GL texture size */

View File

@@ -130,13 +130,13 @@ public class BlockIndexer{
data.turretTree.remove(build);
}
//is no longer registered
build.wasDamaged = false;
//unregister damaged buildings
if(build.damaged() && damagedTiles[team.id] != null){
if(build.wasDamaged && damagedTiles[team.id] != null){
damagedTiles[team.id].remove(build);
}
//is no longer registered
build.wasDamaged = false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -77,6 +77,7 @@ public class RtsAI{
}
public void update(){
if(timer.get(timeUpdate, 60f * 2f)){
assignSquads();
checkBuilding();

View File

@@ -4,7 +4,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.core.*;
import mindustry.entities.*;
@@ -35,7 +34,6 @@ public class CommandAI extends AIController{
protected boolean stopAtTarget, stopWhenInRange;
protected Vec2 lastTargetPos;
protected int pathId = -1;
protected boolean blockingUnit;
protected float timeSpentBlocked;
@@ -205,6 +203,11 @@ public class CommandAI extends AIController{
}
}
boolean alwaysArrive = false;
float engageRange = unit.type.range - 10f;
boolean withinAttackRange = attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram;
if(targetPos != null){
boolean move = true, isFinalPoint = commandQueue.size == 0;
vecOut.set(targetPos);
@@ -221,6 +224,7 @@ public class CommandAI extends AIController{
}
if(unit.isGrounded() && stance != UnitStance.ram){
//TODO: blocking enable or disable?
if(timer.get(timerTarget3, avoidInterval)){
Vec2 dstPos = Tmp.v1.trns(unit.rotation, unit.hitSize/2f);
float max = unit.hitSize/2f;
@@ -248,8 +252,18 @@ public class CommandAI extends AIController{
timeSpentBlocked = 0f;
}
//if you've spent 3 seconds stuck, something is wrong, move regardless
move = Vars.controlPath.getPathPosition(unit, pathId, vecMovePos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
//if the unit is next to the target, stop asking the pathfinder how to get there, it's a waste of CPU
//TODO maybe stop moving too?
if(withinAttackRange){
move = true;
noFound[0] = false;
vecOut.set(vecMovePos);
}else{
move = controlPath.getPathPosition(unit, vecMovePos, targetPos, vecOut, noFound) && (!blockingUnit || timeSpentBlocked > maxBlockTime);
}
//rare case where unit must be perfectly aligned (happens with 1-tile gaps)
alwaysArrive = vecOut.epsilonEquals(unit.tileX() * tilesize, unit.tileY() * tilesize);
//we've reached the final point if the returned coordinate is equal to the supplied input
isFinalPoint &= vecMovePos.epsilonEquals(vecOut, 4.1f);
@@ -266,18 +280,16 @@ public class CommandAI extends AIController{
vecOut.set(vecMovePos);
}
float engageRange = unit.type.range - 10f;
if(move){
if(unit.type.circleTarget && attackTarget != null){
target = attackTarget;
circleAttack(80f);
}else{
moveTo(vecOut,
attackTarget != null && unit.within(attackTarget, engageRange) && stance != UnitStance.ram ? engageRange :
withinAttackRange ? engageRange :
unit.isGrounded() ? 0f :
attackTarget != null && stance != UnitStance.ram ? engageRange :
0f, unit.isFlying() ? 40f : 100f, false, null, isFinalPoint);
attackTarget != null && stance != UnitStance.ram ? engageRange : 0f,
unit.isFlying() ? 40f : 100f, false, null, isFinalPoint || alwaysArrive);
}
}
@@ -417,7 +429,6 @@ public class CommandAI extends AIController{
//this is an allocation, but it's relatively rarely called anyway, and outside mutations must be prevented
targetPos = lastTargetPos = pos.cpy();
attackTarget = null;
pathId = Vars.controlPath.nextTargetId();
this.stopWhenInRange = stopWhenInRange;
}
@@ -432,7 +443,6 @@ public class CommandAI extends AIController{
public void commandTarget(Teamc moveTo, boolean stopAtTarget){
attackTarget = moveTo;
this.stopAtTarget = stopAtTarget;
pathId = Vars.controlPath.nextTargetId();
}
/*

View File

@@ -3,7 +3,6 @@ package mindustry.ai.types;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
@@ -41,8 +40,6 @@ public class LogicAI extends AIController{
public PosTeam posTarget = PosTeam.create();
private ObjectSet<Object> radars = new ObjectSet<>();
private float lastMoveX, lastMoveY;
private int lastPathId = 0;
// LogicAI state should not be reset after reading.
@Override
@@ -52,14 +49,6 @@ public class LogicAI extends AIController{
@Override
public void updateMovement(){
if(control == LUnitControl.pathfind){
if(!Mathf.equal(moveX, lastMoveX, 0.1f) || !Mathf.equal(moveY, lastMoveY, 0.1f)){
lastPathId ++;
lastMoveX = moveX;
lastMoveY = moveY;
}
}
if(targetTimer > 0f){
targetTimer -= Time.delta;
}else{
@@ -86,7 +75,7 @@ public class LogicAI extends AIController{
if(unit.isFlying()){
moveTo(Tmp.v1.set(moveX, moveY), 1f, 30f);
}else{
if(Vars.controlPath.getPathPosition(unit, lastPathId, Tmp.v2.set(moveX, moveY), Tmp.v1, null)){
if(controlPath.getPathPosition(unit, Tmp.v2.set(moveX, moveY), Tmp.v2, Tmp.v1, null)){
moveTo(Tmp.v1, 1f, Tmp.v2.epsilonEquals(Tmp.v1, 4.1f) ? 30f : 0f);
}
}

View File

@@ -4049,7 +4049,7 @@ public class Blocks{
researchCostMultiplier = 0.05f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(12f);
}};
diffuse = new ItemTurret("diffuse"){{
@@ -4108,7 +4108,7 @@ public class Blocks{
rotateSpeed = 3f;
coolant = consume(new ConsumeLiquid(Liquids.water, 15f / 60f));
limitRange();
limitRange(25f);
}};
sublimate = new ContinuousLiquidTurret("sublimate"){{
@@ -4358,7 +4358,7 @@ public class Blocks{
coolant = consume(new ConsumeLiquid(Liquids.water, 20f / 60f));
coolantMultiplier = 2.5f;
limitRange(-5f);
limitRange(5f);
}};
afflict = new PowerTurret("afflict"){{

View File

@@ -37,7 +37,9 @@ public class Bullets{
damageLightningGround = damageLightning.copy();
damageLightningGround.collidesAir = false;
fireball = new FireBulletType(1f, 4);
fireball = new FireBulletType(1f, 4){{
hittable = false;
}};
spaceLiquid = new SpaceLiquidBulletType(){{
knockback = 0.7f;

View File

@@ -2434,12 +2434,9 @@ public class Fx{
shieldBreak = new Effect(40, e -> {
color(e.color);
stroke(3f * e.fout());
if(e.data instanceof Unit u){
var ab = (ForceFieldAbility)Structs.find(u.abilities, a -> a instanceof ForceFieldAbility);
if(ab != null){
Lines.poly(e.x, e.y, ab.sides, e.rotation + e.fin(), ab.rotation);
return;
}
if(e.data instanceof ForceFieldAbility ab){
Lines.poly(e.x, e.y, ab.sides, e.rotation + e.fin(), ab.rotation);
return;
}
Lines.poly(e.x, e.y, 6, e.rotation + e.fin());
@@ -2584,7 +2581,7 @@ public class Fx{
if(!(e.data instanceof Vec2[] vec)) return;
Draw.color(e.color);
Lines.stroke(1f);
Lines.stroke(2f);
if(vec.length == 2){
Lines.line(vec[0].x, vec[0].y, vec[1].x, vec[1].y);
@@ -2596,5 +2593,15 @@ public class Fx{
}
Draw.reset();
}),
debugRect = new Effect(90f, 1000000000000f, e -> {
if(!(e.data instanceof Rect rect)) return;
Draw.color(e.color);
Lines.stroke(2f);
Lines.rect(rect);
Draw.reset();
});
}

View File

@@ -2,6 +2,7 @@ package mindustry.core;
import arc.*;
import arc.assets.loaders.TextureLoader.*;
import arc.audio.*;
import arc.files.*;
import arc.graphics.*;
import arc.graphics.Texture.*;
@@ -554,6 +555,11 @@ public class Renderer implements ApplicationListener{
landTime = landCore.landDuration();
launchCoreType = coreType;
Music music = landCore.launchMusic();
music.stop();
music.play();
music.setVolume(settings.getInt("musicvol") / 100f);
landCore.beginLaunch(coreType);
}

View File

@@ -147,6 +147,11 @@ public abstract class UnlockableContent extends MappableContent{
return Fonts.getUnicodeStr(name);
}
public int emojiChar(){
return Fonts.getUnicode(name);
}
public boolean hasEmoji(){
return Fonts.hasUnicodeStr(name);
}

View File

@@ -24,7 +24,6 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.io.*;
import mindustry.maps.*;
import mindustry.type.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
@@ -212,11 +211,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
margin(0);
update(() -> {
if(Core.scene.getKeyboardFocus() instanceof Dialog && Core.scene.getKeyboardFocus() != this){
return;
}
if(Core.scene != null && Core.scene.getKeyboardFocus() == this){
if(hasKeyboard()){
doInput();
}
});

View File

@@ -14,16 +14,15 @@ import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
public class MapInfoDialog extends BaseDialog{
private final WaveInfoDialog waveInfo;
private final MapGenerateDialog generate;
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
private final MapObjectivesDialog objectives = new MapObjectivesDialog();
private final MapLocalesDialog locales = new MapLocalesDialog();
private WaveInfoDialog waveInfo = new WaveInfoDialog();
private MapGenerateDialog generate = new MapGenerateDialog(false);
private CustomRulesDialog ruleInfo = new CustomRulesDialog();
private MapObjectivesDialog objectives = new MapObjectivesDialog();
private MapLocalesDialog locales = new MapLocalesDialog();
private MapProcessorsDialog processors = new MapProcessorsDialog();
public MapInfoDialog(){
super("@editor.mapinfo");
this.waveInfo = new WaveInfoDialog();
this.generate = new MapGenerateDialog(false);
addCloseButton();
@@ -108,7 +107,12 @@ public class MapInfoDialog extends BaseDialog{
ui.showException(e);
}
hide();
}).marginLeft(10f).width(0f).colspan(2).center().growX();
}).marginLeft(10f);
r.button("@editor.worldprocessors", Icon.logic, style, () -> {
hide();
processors.show();
}).marginLeft(10f);
}).colspan(2).center();
name.change();

View File

@@ -0,0 +1,155 @@
package mindustry.editor;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.logic.*;
import mindustry.world.blocks.logic.LogicBlock.*;
import static mindustry.Vars.*;
public class MapProcessorsDialog extends BaseDialog{
private IconSelectDialog iconSelect = new IconSelectDialog();
private TextField search;
private Seq<Building> processors = new Seq<>();
private Table list;
public MapProcessorsDialog(){
super("@editor.worldprocessors");
shown(this::setup);
addCloseButton();
buttons.button("@add", Icon.add, () -> {
boolean foundAny = false;
outer:
for(int y = 0; y < Vars.world.height(); y++){
for(int x = 0; x < Vars.world.width(); x++){
Tile tile = Vars.world.rawTile(x, y);
if(!tile.synthetic()){
foundAny = true;
tile.setNet(Blocks.worldProcessor, Team.sharded, 0);
if(ui.editor.isShown()){
Vars.editor.renderer.updatePoint(x, y);
}
break outer;
}
}
}
if(!foundAny){
ui.showErrorMessage("@editor.worldprocessors.nospace");
}else{
setup();
}
}).size(210f, 64f);
cont.top();
getCell(cont).grow();
cont.table(s -> {
s.image(Icon.zoom).padRight(8);
search = s.field(null, text -> rebuild()).growX().get();
search.setMessageText("@players.search");
}).width(440f).fillX().padBottom(4).row();
cont.pane(t -> {
list = t;
});
}
private void rebuild(){
list.clearChildren();
if(processors.isEmpty()){
list.add("@editor.worldprocessors.none");
}else{
Table t = list;
var text = search.getText().toLowerCase();
t.defaults().pad(4f);
float h = 50f;
for(var build : processors){
if(build instanceof LogicBuild log && (text.isEmpty() || (log.tag != null && log.tag.toLowerCase().contains(text)))){
t.button(log.iconTag == 0 ? Styles.none : new TextureRegionDrawable(Fonts.getLargeIcon(Fonts.unicodeToName(log.iconTag))), Styles.graySquarei, iconMed, () -> {
iconSelect.show(ic -> {
log.iconTag = (char)ic;
rebuild();
});
}).size(h);
t.button((log.tag == null ? "<no name>\n" : "[accent]" + log.tag + "\n") + "[lightgray][[" + log.tile.x + ", " + log.tile.y + "]", Styles.grayt, () -> {
//TODO: bug: if you edit name inside of the edit dialog, it won't show up in the list properly
log.showEditDialog(true);
}).size(Vars.mobile ? 390f : 450f, h).margin(10f).with(b -> {
b.getLabel().setAlignment(Align.left, Align.left);
});
t.button(Icon.pencil, Styles.graySquarei, Vars.iconMed, () -> {
ui.showTextInput("", "@editor.name", LogicBlock.maxNameLength, log.tag == null ? "" : log.tag, tag -> {
//bypass configuration and set it directly in case privileged checks mess things up
log.tag = tag;
setup();
});
}).size(h);
if(Vars.state.isGame() && state.isEditor()){
t.button(Icon.eyeSmall, Styles.graySquarei, Vars.iconMed, () -> {
hide();
control.input.config.showConfig(build);
control.input.panCamera(Tmp.v1.set(build));
}).size(h);
}
t.button(Icon.trash, Styles.graySquarei, iconMed, () -> {
ui.showConfirm("@editor.worldprocessors.delete.confirm", () -> {
boolean surrounded = true;
for(int i = 0; i < 4; i++){
Tile other = build.tile.nearby(i);
if(other != null && !(other.block().privileged || other.block().isStatic())){
surrounded = false;
break;
}
}
if(surrounded){
build.tile.setNet(build.tile.floor().wall instanceof StaticWall ? build.tile.floor().wall : Blocks.stoneWall);
}else{
build.tile.setNet(Blocks.air);
}
processors.remove(build);
rebuild();
});
}).size(h);
t.row();
}
}
}
}
private void setup(){
processors.clear();
//scan the entire world for processor (Groups.build can be empty, indexer is probably inaccurate)
Vars.world.tiles.eachTile(t -> {
if(t.isCenter() && t.block() == Blocks.worldProcessor){
processors.add(t.build);
}
});
rebuild();
}
}

View File

@@ -25,7 +25,6 @@ public class Damage{
private static final Rect rect = new Rect();
private static final Rect hitrect = new Rect();
private static final Vec2 vec = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
private static final Seq<Unit> units = new Seq<>();
private static final IntSet collidedBlocks = new IntSet();
private static final IntFloatMap damages = new IntFloatMap();
private static final Seq<Collided> collided = new Seq<>();
@@ -41,6 +40,7 @@ public class Damage{
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source){
applySuppression(team, x, y, range, reload, maxDelay, applyParticleChance, source, Pal.sapBullet);
}
public static void applySuppression(Team team, float x, float y, float range, float reload, float maxDelay, float applyParticleChance, @Nullable Position source, Color effectColor){
builds.clear();
indexer.eachBlock(null, x, y, range, build -> build.team != team, build -> {
@@ -175,21 +175,23 @@ public class Damage{
distances.clear();
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
//add distance to list so it can be processed
var build = world.build(x, y);
if(b.type.collidesGround && b.type.collidesTiles){
World.raycast(b.tileX(), b.tileY(), World.toTile(b.x + vec.x), World.toTile(b.y + vec.y), (x, y) -> {
//add distance to list so it can be processed
var build = world.build(x, y);
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
distances.add(b.dst(build));
if(build != null && build.team != b.team && build.collide(b) && b.checkUnderBuild(build, x * tilesize, y * tilesize)){
distances.add(b.dst(build));
if(laser && build.absorbLasers()){
maxDst = Math.min(maxDst, b.dst(build));
return true;
if(laser && build.absorbLasers()){
maxDst = Math.min(maxDst, b.dst(build));
return true;
}
}
}
return false;
});
return false;
});
}
Units.nearbyEnemies(b.team, rect, u -> {
u.hitbox(hitrect);
@@ -247,7 +249,7 @@ public class Damage{
collidedBlocks.clear();
vec.trnsExact(angle, length);
if(hitter.type.collidesGround){
if(hitter.type.collidesGround && hitter.type.collidesTiles){
seg1.set(x, y);
seg2.set(seg1).add(vec);
World.raycastEachWorld(x, y, seg2.x, seg2.y, (cx, cy) -> {

View File

@@ -247,4 +247,4 @@ public class EntityCollisions{
public interface SolidPred{
boolean solid(int x, int y);
}
}
}

View File

@@ -32,6 +32,7 @@ public class ForceFieldAbility extends Ability{
/** State. */
protected float radiusScale, alpha;
protected boolean wasBroken = true;
private static float realRad;
private static Unit paramUnit;
@@ -41,13 +42,6 @@ public class ForceFieldAbility extends Ability{
trait.absorb();
Fx.absorb.at(trait);
//break shield
if(paramUnit.shield <= trait.damage()){
paramUnit.shield -= paramField.cooldown * paramField.regen;
Fx.shieldBreak.at(paramUnit.x, paramUnit.y, paramField.radius, paramUnit.team.color, paramUnit);
}
paramUnit.shield -= trait.damage();
paramField.alpha = 1f;
}
@@ -85,6 +79,14 @@ public class ForceFieldAbility extends Ability{
@Override
public void update(Unit unit){
if(unit.shield <= 0f && !wasBroken){
unit.shield -= cooldown * regen;
Fx.shieldBreak.at(unit.x, unit.y, radius, unit.team.color, this);
}
wasBroken = unit.shield <= 0f;
if(unit.shield < max){
unit.shield += Time.delta * regen;
}

View File

@@ -40,7 +40,7 @@ abstract class BlockUnitComp implements Unitc{
@Replace
@Override
public TextureRegion icon(){
return tile.block.fullIcon;
return tile.block.uiIcon;
}
@Override

View File

@@ -138,7 +138,11 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
if(!(tile.build instanceof ConstructBuild cb)){
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
boolean hasAll = infinite || current.isRotation(team) ||
//derelict repair
(tile.team() == Team.derelict && tile.block() == current.block && tile.build != null && tile.block().allowDerelictRepair && state.rules.derelictRepair) ||
//make sure there's at least 1 item of each type first
!Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item, Math.min(Mathf.round(i.amount * state.rules.buildCostMultiplier), 1)));
if(hasAll){
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
@@ -290,10 +294,6 @@ abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
return plans.size == 0 ? null : plans.first();
}
public void draw(){
drawBuilding();
}
public void drawBuilding(){
//TODO make this more generic so it works with builder "weapons"
boolean active = activelyBuilding();

View File

@@ -1310,7 +1310,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(value instanceof UnitType) type = UnitType.class;
if(builder != null && builder.isPlayer()){
lastAccessed = builder.getPlayer().coloredName();
updateLastAccess(builder.getPlayer());
}
if(block.configurations.containsKey(type)){
@@ -1324,6 +1324,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
}
public void updateLastAccess(Player player){
lastAccessed = player.coloredName();
}
/** Called when the block is tapped by the local player. */
public void tapped(){
@@ -1977,9 +1981,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
switch(prop){
case health -> {
health = (float)Mathf.clamp(value, 0, maxHealth);
healthChanged();
if(health <= 0f && !dead()){
Call.buildDestroyed(self());
}else{
healthChanged();
}
}
case team -> {

View File

@@ -68,7 +68,7 @@ abstract class HitboxComp implements Posc, Sized, QuadTreeObject{
public void hitboxTile(Rect rect){
//tile hitboxes are never bigger than a tile, otherwise units get stuck
float size = Math.min(hitSize * 0.66f, 7.9f);
float size = Math.min(hitSize * 0.66f, 7.8f);
//TODO: better / more accurate version is
//float size = hitSize * 0.85f;
//- for tanks?

View File

@@ -24,6 +24,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
@Import float x, y, rotation, speedMultiplier;
@Import UnitType type;
@Import Team team;
@Import boolean disarmed;
transient Leg[] legs = {};
transient float totalLength;
@@ -191,7 +192,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
}
}
if(type.legSplashDamage > 0){
if(type.legSplashDamage > 0 && !disarmed){
Damage.damage(team, l.base.x, l.base.y, type.legSplashRange, type.legSplashDamage * state.rules.unitDamage(team), false, true);
}
}

View File

@@ -123,31 +123,4 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
}
}
}
@Override
public void draw(){
if(!mining()) return;
float focusLen = hitSize / 2f + Mathf.absin(Time.time, 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = x + Angles.trnsx(rotation, focusLen);
float py = y + Angles.trnsy(rotation, focusLen);
float ex = mineTile.worldx() + Mathf.sin(Time.time + 48, swingScl, swingMag);
float ey = mineTile.worldy() + Mathf.sin(Time.time + 48, swingScl + 2f, swingMag);
Draw.z(Layer.flyingUnit + 0.1f);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time, 0.5f, flashScl));
Drawf.laser(Core.atlas.find("minelaser"), Core.atlas.find("minelaser-end"), px, py, ex, ey, 0.75f);
if(isLocal()){
Lines.stroke(1f, Pal.accent);
Lines.poly(mineTile.worldx(), mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time);
}
Draw.color();
}
}

View File

@@ -72,7 +72,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.fullIcon : ((CoreBlock)bestCore().block).unitType.fullIcon;
if(dead()) return core() == null ? UnitTypes.alpha.uiIcon : ((CoreBlock)bestCore().block).unitType.uiIcon;
return unit.icon();
}

View File

@@ -73,6 +73,7 @@ abstract class StatusComp implements Posc, Flyingc{
}
void clearStatuses(){
statuses.each(e -> e.effect.onRemoved(self()));
statuses.clear();
}
@@ -80,6 +81,7 @@ abstract class StatusComp implements Posc, Flyingc{
void unapply(StatusEffect effect){
statuses.remove(e -> {
if(e.effect == effect){
e.effect.onRemoved(self());
Pools.free(e);
return true;
}
@@ -189,6 +191,10 @@ abstract class StatusComp implements Posc, Flyingc{
entry.time = Math.max(entry.time - Time.delta, 0);
if(entry.effect == null || (entry.time <= 0 && !entry.effect.permanent)){
if(entry.effect != null){
entry.effect.onRemoved(self());
}
Pools.free(entry);
index --;
statuses.remove(index);

View File

@@ -18,7 +18,7 @@ import static mindustry.Vars.*;
@Component
abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec{
@Import float x, y, hitSize, rotation, speedMultiplier;
@Import boolean hovering;
@Import boolean hovering, disarmed;
@Import UnitType type;
@Import Team team;
@@ -51,7 +51,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
//calculate overlapping tiles so it slows down when going "over" walls
int r = Math.max(Math.round(hitSize * 0.6f / tilesize), 1);
int r = Math.max((int)(hitSize * 0.6f / tilesize), 0);
int solids = 0, total = (r*2+1)*(r*2+1);
for(int dx = -r; dx <= r; dx++){
@@ -62,7 +62,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
}
//TODO should this apply to the player team(s)? currently PvE due to balancing
if(type.crushDamage > 0 && (walked || deltaLen() >= 0.01f) && t != null && t.build != null && t.build.team != team
if(type.crushDamage > 0 && !disarmed && (walked || deltaLen() >= 0.01f) && t != null && t.build != null && t.build.team != team
//damage radius is 1 tile smaller to prevent it from just touching walls as it passes
&& Math.max(Math.abs(dx), Math.abs(dy)) <= r - 1){

View File

@@ -402,7 +402,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
return type.allowLegStep && type.legPhysicsLayer ? PhysicsProcess.layerLegs : isGrounded() ? PhysicsProcess.layerGround : PhysicsProcess.layerFlying;
}
/** @return pathfinder path type for calculating costs */
/** @return pathfinder path type for calculating costs. This is used for wave AI only. (TODO: remove) */
public int pathType(){
return Pathfinder.costGround;
}
@@ -666,9 +666,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
}
}
/** @return a preview icon for this unit. */
/** @return a preview UI icon for this unit. */
public TextureRegion icon(){
return type.fullIcon;
return type.uiIcon;
}
/** Actually destroys the unit, removing it and creating explosions. **/
@@ -734,7 +734,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return name of direct or indirect player controller. */
@Override
public @Nullable String getControllerName(){
if(isPlayer()) return getPlayer().name;
if(isPlayer()) return getPlayer().coloredName();
if(controller instanceof LogicAI ai && ai.controller != null) return ai.controller.lastAccessed;
return null;
}

View File

@@ -88,7 +88,7 @@ public class ParticleEffect extends Effect{
rv.trns(realRotation + rand.range(cone), !randLength ? l : rand.random(l));
float x = rv.x, y = rv.y;
Draw.rect(tex, ox + x, oy + y, rad, rad, realRotation + offset + e.time * spin);
Draw.rect(tex, ox + x, oy + y, rad, rad / tex.ratio(), realRotation + offset + e.time * spin);
Drawf.light(ox + x, oy + y, rad * lightScl, lightColor, lightOpacity * Draw.getColor().a);
}
}

View File

@@ -341,7 +341,7 @@ public class AIController implements UnitController{
vec.setLength(speed * length);
}
//do not move when infinite vectors are used or if its zero.
//ignore invalid movement values
if(vec.isNaN() || vec.isInfinite() || vec.isZero()) return;
if(!unit.type.omniMovement && unit.type.rotateMoveFirst){

View File

@@ -129,9 +129,11 @@ public class OverlayRenderer{
Draw.mixcol(Pal.accent, 1f);
Draw.alpha(unitFade);
Building build = (select instanceof BlockUnitc b ? b.tile() : select instanceof Building b ? b : null);
TextureRegion region = build != null ? build.block.fullIcon : select instanceof Unit u ? u.icon() : Core.atlas.white();
TextureRegion region = build != null ? build.block.fullIcon : Core.atlas.white();
Draw.rect(region, select.getX(), select.getY(), select instanceof Unit u && !(select instanceof BlockUnitc) ? u.rotation - 90f : 0f);
if(!(select instanceof Unitc)){
Draw.rect(region, select.getX(), select.getY());
}
for(int i = 0; i < 4; i++){
float rot = i * 90f + 45f + (-Time.time) % 360f;
@@ -255,6 +257,13 @@ public class OverlayRenderer{
}
}
public void checkApplySelection(Unit u){
if(unitFade > 0.001f && lastSelect == u){
Color prev = Draw.getMixColor();
Draw.mixcol(prev.a > 0.001f ? prev.lerp(Pal.accent, unitFade) : Pal.accent, Math.max(unitFade, prev.a));
}
}
private static class CoreEdge{
float x1, y1, x2, y2;
Team t1, t2;

View File

@@ -106,9 +106,9 @@ public class Trail{
int count = (int)(counter += Time.delta);
counter -= count;
if(points.size + ((count - 1) * 3) > length * 3 && points.size > 0){
points.removeRange(0, Math.min(3 * count - 1, points.size - 1));
}
if(count > 0 && points.size > 0){
points.removeRange(0, Math.min(count * 3 - 1, points.size - 1));
}
}
/** Adds a new point to the trail at intervals. */

View File

@@ -391,7 +391,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(build == null || build.team() != player.team() || !build.block.commandable) continue;
build.onCommand(target);
build.lastAccessed = player.name;
build.updateLastAccess(player);
if(!state.isPaused() && player == Vars.player){
Fx.moveCommand.at(target);
@@ -596,7 +596,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
throw new ValidateException(player, "Player cannot rotate a block.");
}
if(player != null) build.lastAccessed = player.name;
if(player != null) build.updateLastAccess(player);
int previous = build.rotation;
build.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4);
build.updateProximity();

View File

@@ -628,7 +628,7 @@ public class TypeIO{
}
public static KickReason readKick(Reads read){
return KickReason.values()[read.b()];
return KickReason.all[read.b()];
}
public static void writeMarkerControl(Writes write, LMarkerControl reason){
@@ -786,7 +786,7 @@ public class TypeIO{
}
public static AdminAction readAction(Reads read){
return AdminAction.values()[read.b()];
return AdminAction.all[read.b()];
}
public static void writeUnitType(Writes write, UnitType effect){

View File

@@ -164,6 +164,12 @@ public class LCanvas extends Table{
this.statements.layout();
}
public void clearStatements(){
jumps.clear();
statements.clearChildren();
statements.layout();
}
StatementElem checkHovered(){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(e != null){

View File

@@ -1480,7 +1480,7 @@ public class LExecutor{
if(t == null) t = Team.derelict;
if(tile.block() != b || tile.team() != t){
tile.setNet(b, t, Mathf.clamp(exec.numi(rotation), 0, 3));
tile.setBlock(b, t, Mathf.clamp(exec.numi(rotation), 0, 3));
}
}
}

View File

@@ -17,6 +17,7 @@ import mindustry.logic.LExecutor.*;
import mindustry.logic.LStatements.*;
import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import mindustry.world.blocks.logic.*;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
@@ -92,11 +93,29 @@ public class LogicDialog extends BaseDialog{
TextButtonStyle style = Styles.flatt;
t.defaults().size(280f, 60f).left();
if(privileged && executor != null && executor.build != null && !ui.editor.isShown()){
t.button("@editor.worldprocessors.editname", Icon.edit, style, () -> {
ui.showTextInput("", "@editor.name", LogicBlock.maxNameLength, executor.build.tag == null ? "" : executor.build.tag, tag -> {
if(privileged && executor != null && executor.build != null){
executor.build.configure(tag);
//just in case of privilege shenanigans...
executor.build.tag = tag;
}
});
dialog.hide();
}).marginLeft(12f).row();
}
t.button("@clear", Icon.cancel, style, () -> {
ui.showConfirm("@logic.clear.confirm", () -> canvas.clearStatements());
dialog.hide();
}).marginLeft(12f).row();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
t.row();
}).marginLeft(12f).row();
t.button("@schematic.copy.import", Icon.download, style, () -> {
dialog.hide();
try{

View File

@@ -33,7 +33,7 @@ public class Maps{
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
EnemySpawnFilter::new, SpawnPathFilter::new
EnemySpawnFilter::new, SpawnPathFilter::new, LogicFilter::new
};
/** List of all built-in maps. Filenames only. */

View File

@@ -27,7 +27,7 @@ public class BlendFilter extends GenerateFilter{
@Override
public char icon(){
return Iconc.blockSand;
return Iconc.blockSandFloor;
}
@Override

View File

@@ -0,0 +1,72 @@
package mindustry.maps.filters;
import arc.scene.ui.layout.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.logic.*;
import mindustry.maps.filters.FilterOption.*;
import mindustry.world.*;
import static mindustry.Vars.*;
public class LogicFilter extends GenerateFilter{
/** max available execution for logic filter */
public static int maxInstructionsExecution = 500 * 500 * 25;
public String code;
public boolean loop;
@Override
public FilterOption[] options(){
return new FilterOption[]{
new FilterOption(){
final String name;
{
name = "code";
}
@Override
public void build(Table table){
table.button(b -> b.image(Icon.pencil).size(iconSmall), () -> {
ui.logic.show(code, null, true, code -> LogicFilter.this.code = code);
}).pad(4).margin(12f);
table.add("@filter.option." + name);
}
},
new ToggleOption("loop", () -> loop, f -> loop = f)
};
}
@Override
public void apply(Tiles tiles, GenerateInput in){
LExecutor executor = new LExecutor();
executor.privileged = true;
try{
//assembler has no variables, all the standard ones are null
executor.load(LAssembler.assemble(code, true));
}catch(Throwable ignored){
//if loading code
return;
}
//this updates map width/height global variables
logicVars.update();
//NOTE: all tile operations will call setNet for tiles, but that should have no overhead during world loading
//executions are limited to prevent infinite generation
for(int i = 1; i < maxInstructionsExecution; i++){
if(!loop && (executor.counter.numval >= executor.instructions.length || executor.counter.numval < 0)) break;
executor.runOnce();
}
}
@Override
public char icon(){
return Iconc.blockMicroProcessor;
}
@Override
public boolean isPost(){
return true;
}
}

View File

@@ -28,7 +28,7 @@ public class SpawnPathFilter extends GenerateFilter{
@Override
public char icon(){
return Iconc.blockCommandCenter;
return Iconc.blockCoreZone;
}
@Override

View File

@@ -6,6 +6,11 @@ import mindustry.*;
public abstract class Mod{
/** @return the folder where configuration files for this mod should go.*/
public Fi getConfigFolder(){
return Vars.mods.getConfigFolder(this);
}
/** @return the config file for this plugin, as the file 'mods/[plugin-name]/config.json'.*/
public Fi getConfig(){
return Vars.mods.getConfig(this);
@@ -26,7 +31,7 @@ public abstract class Mod{
}
/** Register any commands to be used on the client side, e.g. sent from an in-game player.. */
/** Register any commands to be used on the client side, e.g. sent from an in-game player. */
public void registerClientCommands(CommandHandler handler){
}

View File

@@ -62,12 +62,18 @@ public class Mods implements Loadable{
return mainLoader;
}
/** Returns a file named 'config.json' in a special folder for the specified plugin.
/** @return the folder where configuration files for this mod should go. The folder may not exist yet; call mkdirs() before writing to it.
* Call this in init(). */
public Fi getConfig(Mod mod){
public Fi getConfigFolder(Mod mod){
ModMeta load = metas.get(mod.getClass());
if(load == null) throw new IllegalArgumentException("Mod is not loaded yet (or missing)!");
return modDirectory.child(load.name).child("config.json");
return modDirectory.child(load.name);
}
/** @return a file named 'config.json' in the config folder for the specified mod.
* Call this in init(). */
public Fi getConfig(Mod mod){
return getConfigFolder(mod).child("config.json");
}
/** Returns a list of files per mod subdirectory. */
@@ -722,6 +728,11 @@ public class Mods implements Loadable{
Seq<LoadRun> runs = new Seq<>();
for(LoadedMod mod : orderedMods()){
Seq<LoadRun> unorderedContent = new Seq<>();
ObjectMap<String, LoadRun> orderedContent = new ObjectMap<>();
String[] contentOrder = mod.meta.contentOrder;
ObjectSet<String> orderSet = contentOrder == null ? null : ObjectSet.with(contentOrder);
if(mod.root.child("content").exists()){
Fi contentRoot = mod.root.child("content");
for(ContentType type : ContentType.all){
@@ -729,15 +740,34 @@ public class Mods implements Loadable{
Fi folder = contentRoot.child(lower + (lower.endsWith("s") ? "" : "s"));
if(folder.exists()){
for(Fi file : folder.findAll(f -> f.extension().equals("json") || f.extension().equals("hjson"))){
runs.add(new LoadRun(type, file, mod));
//if this is part of the ordered content, put it aside to be dealt with later
if(orderSet != null && orderSet.contains(file.nameWithoutExtension())){
orderedContent.put(file.nameWithoutExtension(), new LoadRun(type, file, mod));
}else{
unorderedContent.add(new LoadRun(type, file, mod));
}
}
}
}
}
//ordered content will be loaded first, if it exists
if(contentOrder != null){
for(String contentName : contentOrder){
LoadRun run = orderedContent.get(contentName);
if(run != null){
runs.add(run);
}else{
Log.warn("Cannot find content defined in contentOrder: @", contentName);
}
}
}
//unordered content is sorted alphabetically per mod
runs.addAll(unorderedContent.sort());
}
//make sure mod content is in proper order
runs.sort();
for(LoadRun l : runs){
Content current = content.getLastAdded();
try{
@@ -1204,6 +1234,8 @@ public class Mods implements Loadable{
public float texturescale = 1.0f;
/** If true, bleeding is skipped and no content icons are generated. */
public boolean pregenerated;
/** If set, load the mod content in this order by content names */
public String[] contentOrder;
public String displayName(){
//useless, kept for legacy reasons

View File

@@ -17,6 +17,8 @@ public class Packets{
nameInUse, idInUse, nameEmpty, customClient, serverClose, vote, typeMismatch,
whitelist, playerLimit, serverRestarting;
public static final KickReason[] all = values();
public final boolean quiet;
KickReason(){
@@ -38,7 +40,9 @@ public class Packets{
}
public enum AdminAction{
kick, ban, trace, wave, switchTeam
kick, ban, trace, wave, switchTeam;
public static final AdminAction[] all = values();
}
/** Generic client connection event. */

View File

@@ -291,7 +291,7 @@ public class GameService{
});
Events.on(SectorLaunchLoadoutEvent.class, e -> {
if(!schematics.isDefaultLoadout(e.loadout)){
if(e.sector.planet == Planets.serpulo && !schematics.isDefaultLoadout(e.loadout)){
launchCoreSchematic.complete();
}
});

View File

@@ -142,6 +142,11 @@ public class StatusEffect extends UnlockableContent{
}
}
/** Called when status effect is removed. */
public void onRemoved(Unit unit){
}
protected void trans(StatusEffect effect, TransitionHandler handler){
transitions.put(effect, handler);
}

View File

@@ -288,6 +288,8 @@ public class UnitType extends UnlockableContent implements Senseable{
/** Function used for calculating cost of moving with ControlPathfinder. Does not affect "normal" flow field pathfinding. */
public @Nullable PathCost pathCost;
/** ID for path cost, to be used in the control path finder. This is the value that actually matters; do not assign manually. Set in init(). */
public int pathCostId;
/** A sample of the unit that this type creates. Do not modify! */
public @Nullable Unit sample;
@@ -427,10 +429,13 @@ public class UnitType extends UnlockableContent implements Senseable{
//(undocumented, you shouldn't need to use these, and if you do just check how they're drawn and copy that)
public TextureRegion baseRegion, legRegion, region, previewRegion, shadowRegion, cellRegion, itemCircleRegion,
softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion, treadRegion;
softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion, treadRegion,
mineLaserRegion, mineLaserEndRegion;
public TextureRegion[] wreckRegions, segmentRegions, segmentOutlineRegions;
public TextureRegion[][] treadRegions;
//INTERNAL REQUIREMENTS
protected float buildTime = -1f;
protected @Nullable ItemStack[] totalRequirements, cachedRequirements, firstRequirements;
@@ -691,6 +696,9 @@ public class UnitType extends UnlockableContent implements Senseable{
ControlPathfinder.costGround;
}
pathCostId = ControlPathfinder.costTypes.indexOf(pathCost);
if(pathCostId == -1) pathCostId = 0;
if(flying){
envEnabled |= Env.space;
}
@@ -842,7 +850,7 @@ public class UnitType extends UnlockableContent implements Senseable{
if(stances.length == 0){
if(canAttack){
Seq<UnitStance> seq = Seq.with(UnitStance.stop, UnitStance.shoot, UnitStance.holdFire, UnitStance.pursueTarget, UnitStance.patrol);
if(crushDamage > 0){
if(!flying){
seq.add(UnitStance.ram);
}
stances = seq.toArray(UnitStance.class);
@@ -910,6 +918,9 @@ public class UnitType extends UnlockableContent implements Senseable{
legBaseRegion = Core.atlas.find(name + "-leg-base", name + "-leg");
baseRegion = Core.atlas.find(name + "-base");
cellRegion = Core.atlas.find(name + "-cell", Core.atlas.find("power-cell"));
mineLaserRegion = Core.atlas.find("minelaser");
mineLaserEndRegion = Core.atlas.find("minelaser-end");
//when linear filtering is on, it's acceptable to use the relatively low-res 'particle' region
softShadowRegion =
squareShape ? Core.atlas.find("square-shadow") :
@@ -1186,6 +1197,10 @@ public class UnitType extends UnlockableContent implements Senseable{
public void draw(Unit unit){
if(unit.inFogTo(Vars.player.team())) return;
unit.drawBuilding();
drawMining(unit);
boolean isPayload = !unit.isAdded();
Mechc mech = unit instanceof Mechc ? (Mechc)unit : null;
@@ -1289,6 +1304,32 @@ public class UnitType extends UnlockableContent implements Senseable{
Draw.reset();
}
public void drawMining(Unit unit){
if(!unit.mining()) return;
float focusLen = unit.hitSize / 2f + Mathf.absin(Time.time, 1.1f, 0.5f);
float swingScl = 12f, swingMag = tilesize / 8f;
float flashScl = 0.3f;
float px = unit.x + Angles.trnsx(unit.rotation, focusLen);
float py = unit.y + Angles.trnsy(unit.rotation, focusLen);
float ex = unit.mineTile.worldx() + Mathf.sin(Time.time + 48, swingScl, swingMag);
float ey = unit.mineTile.worldy() + Mathf.sin(Time.time + 48, swingScl + 2f, swingMag);
Draw.z(Layer.flyingUnit + 0.1f);
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time, 0.5f, flashScl));
Drawf.laser(mineLaserRegion, mineLaserEndRegion, px, py, ex, ey, 0.75f);
if(unit.isLocal()){
Lines.stroke(1f, Pal.accent);
Lines.poly(unit.mineTile.worldx(), unit.mineTile.worldy(), 4, tilesize / 2f * Mathf.sqrt2, Time.time);
}
Draw.color();
}
public <T extends Unit & Payloadc> void drawPayload(T unit){
if(unit.hasPayload()){
Payload pay = unit.payloads().first();
@@ -1465,6 +1506,7 @@ public class UnitType extends UnlockableContent implements Senseable{
}
public <T extends Unit & Tankc> void drawTank(T unit){
applyColor(unit);
Draw.rect(treadRegion, unit.x, unit.y, unit.rotation - 90);
if(treadRegion.found()){
@@ -1636,6 +1678,10 @@ public class UnitType extends UnlockableContent implements Senseable{
if(unit.drownTime > 0 && unit.lastDrownFloor != null){
Draw.mixcol(Tmp.c1.set(unit.lastDrownFloor.mapColor).mul(0.83f), unit.drownTime * 0.9f);
}
//this is horribly scuffed.
if(renderer != null && renderer.overlays != null){
renderer.overlays.checkApplySelection(unit);
}
}
//endregion

View File

@@ -30,6 +30,7 @@ public class Fonts{
private static final String mainFont = "fonts/font.woff";
private static final ObjectSet<String> unscaled = ObjectSet.with("iconLarge");
private static ObjectIntMap<String> unicodeIcons = new ObjectIntMap<>();
private static IntMap<String> unicodeToName = new IntMap<>();
private static ObjectMap<String, String> stringIcons = new ObjectMap<>();
private static ObjectMap<String, TextureRegion> largeIcons = new ObjectMap<>();
private static TextureRegion[] iconTable;
@@ -95,12 +96,16 @@ public class Fonts{
}})).loaded = f -> Fonts.logic = f;
}
public static @Nullable String unicodeToName(int unicode){
return unicodeToName.get(unicode, () -> Iconc.codeToName.get(unicode));
}
public static TextureRegion getLargeIcon(String name){
return largeIcons.get(name, () -> {
var region = new TextureRegion();
int code = Iconc.codes.get(name, '\uF8D4');
var glyph = iconLarge.getData().getGlyph((char)code);
if(glyph == null) return Core.atlas.find("error");
if(glyph == null) return Core.atlas.find(name);
region.set(iconLarge.getRegion().texture);
region.set(glyph.u, glyph.v2, glyph.u2, glyph.v);
return region;
@@ -127,6 +132,7 @@ public class Fonts{
unicodeIcons.put(nametex[0], ch);
stringIcons.put(nametex[0], ((char)ch) + "");
unicodeToName.put(ch, texture);
Vec2 out = Scaling.fit.apply(region.width, region.height, size, size);

View File

@@ -3,9 +3,11 @@ package mindustry.ui;
import arc.*;
import arc.graphics.g2d.*;
import arc.input.*;
import arc.math.*;
import arc.scene.*;
import arc.scene.event.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
@@ -20,6 +22,22 @@ public class Minimap extends Table{
add(new Element(){
{
setSize(Scl.scl(140f));
addListener(new ClickListener(KeyCode.mouseRight){
@Override
public void clicked(InputEvent event, float cx, float cy){
var region = renderer.minimap.getRegion();
if(region == null) return;
float
sx = (cx - x) / width,
sy = (cy - y) / height,
scaledX = Mathf.lerp(region.u, region.u2, sx) * world.width() * tilesize,
scaledY = Mathf.lerp(1f - region.v2, 1f - region.v, sy) * world.height() * tilesize;
control.input.panCamera(Tmp.v1.set(scaledX, scaledY));
}
});
}
@Override

View File

@@ -73,6 +73,8 @@ public class Styles{
geni,
/** Gray, toggleable, no background. */
grayi,
/** Gray square background, standard behavior. Equivalent to grayt. */
graySquarei,
/** Flat, square, black background. */
flati,
/** Square border. */
@@ -288,6 +290,14 @@ public class Styles{
imageUpColor = Color.lightGray;
imageDownColor = Color.white;
}};
graySquarei = new ImageButtonStyle(){{
imageUpColor = Color.white;
imageDownColor = Color.lightGray;
over = flatOver;
down = flatOver;
up = grayPanel;
}};
flati = new ImageButtonStyle(){{
down = flatOver;
up = black;

View File

@@ -1,5 +1,6 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import mindustry.gen.*;
@@ -39,7 +40,7 @@ public class AdminsDialog extends BaseDialog{
res.labelWrap("[lightgray]" + info.lastName).width(w - h - 24f);
res.add().growX();
res.button(Icon.cancel, () -> {
ui.showConfirm("@confirm", "@confirmunadmin", () -> {
ui.showConfirm("@confirm", Core.bundle.format("@confirmunadmin", info.lastName), () -> {
netServer.admins.unAdminPlayer(info.id);
Groups.player.each(player -> {
if(player != null && !player.isLocal() && player.uuid().equals(info.id)){

View File

@@ -3,7 +3,6 @@ package mindustry.ui.dialogs;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.input.KeyCode;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.ImageButton.*;
@@ -193,9 +192,12 @@ public class CustomRulesDialog extends BaseDialog{
cont.clear();
cont.table(t -> {
t.add("@search").padRight(10);
t.field(ruleSearch, text ->
ruleSearch = text.trim().replaceAll(" +", " ").toLowerCase()
).grow().pad(8).get().keyDown(KeyCode.enter, this::setup);
var field = t.field(ruleSearch, text -> {
ruleSearch = text.trim().replaceAll(" +", " ").toLowerCase();
setup();
}).grow().pad(8).get();
field.setCursorPosition(ruleSearch.length());
Core.scene.setKeyboardFocus(field);
t.button(Icon.cancel, Styles.emptyi, () -> {
ruleSearch = "";
setup();
@@ -234,7 +236,7 @@ public class CustomRulesDialog extends BaseDialog{
setup();
}
}, () -> rules.infiniteResources);
withInfo("@rules.onlydepositcore.info", () -> check("@rules.onlydepositcore", b -> rules.onlyDepositCore = b, () -> rules.onlyDepositCore));
check("@rules.onlydepositcore", b -> rules.onlyDepositCore = b, () -> rules.onlyDepositCore);
check("@rules.derelictrepair", b -> rules.derelictRepair = b, () -> rules.derelictRepair);
check("@rules.reactorexplosions", b -> rules.reactorExplosions = b, () -> rules.reactorExplosions);
check("@rules.schematic", b -> rules.schematicsAllowed = b, () -> rules.schematicsAllowed);
@@ -282,7 +284,7 @@ public class CustomRulesDialog extends BaseDialog{
category("enemy");
check("@rules.attack", b -> rules.attackMode = b, () -> rules.attackMode);
check("@rules.corecapture", b -> rules.coreCapture = b, () -> rules.coreCapture);
withInfo("@rules.placerangecheck.info",() -> check("@rules.placerangecheck", b -> rules.placeRangeCheck = b, () -> rules.placeRangeCheck));
check("@rules.placerangecheck", b -> rules.placeRangeCheck = b, () -> rules.placeRangeCheck);
check("@rules.polygoncoreprotection", b -> rules.polygonCoreProtection = b, () -> rules.polygonCoreProtection);
number("@rules.enemycorebuildradius", f -> rules.enemyCoreBuildRadius = f * tilesize, () -> Math.min(rules.enemyCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection);
@@ -490,25 +492,13 @@ public class CustomRulesDialog extends BaseDialog{
public void check(String text, Boolc cons, Boolp prov, Boolp condition){
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get())).padRight(100f).get().left();
current.row();
}
public void withInfo(String info, Runnable add){
Table wasCurrent = current;
current = new Table();
current.left().defaults().fillX().left();
current.button(Icon.infoSmall, () -> ui.showInfo(info)).size(32f).padRight(10f);
add.run();
// rule does not match search pattern (runnable returned without adding anything)
if(current.getCells().size < 2){
current.clear();
}else{
wasCurrent.add(current).row();
String infoText = text.substring(1) + ".info";
var cell = current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get())).padRight(100f);
if(Core.bundle.has(infoText)){
cell.tooltip(text + ".info");
}
current = wasCurrent;
cell.get().left();
current.row();
}
Cell<TextField> field(Table table, float value, Floatc setter){

View File

@@ -45,7 +45,7 @@ public class DatabaseDialog extends BaseDialog{
void rebuild(){
all.clear();
var text = search.getText();
var text = search.getText().toLowerCase();
Seq<Content>[] allContent = Vars.content.getContentMap();
@@ -54,7 +54,7 @@ public class DatabaseDialog extends BaseDialog{
Seq<UnlockableContent> array = allContent[j]
.select(c -> c instanceof UnlockableContent u && !u.isHidden() &&
(text.isEmpty() || u.localizedName.toLowerCase().contains(text.toLowerCase()))).as();
(text.isEmpty() || u.localizedName.toLowerCase().contains(text))).as();
if(array.size == 0) continue;
all.add("@content." + type.name() + ".name").growX().left().color(Pal.accent);

View File

@@ -0,0 +1,74 @@
package mindustry.ui.dialogs;
import arc.*;
import arc.func.*;
import arc.scene.style.*;
import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.util.*;
import mindustry.ctype.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
import static mindustry.Vars.*;
public class IconSelectDialog extends Dialog{
private Intc consumer = i -> Log.info("you have mere seconds");
public IconSelectDialog(){
closeOnBack();
setFillParent(true);
cont.pane(t -> {
resized(true, () -> {
t.clearChildren();
t.marginRight(19f);
t.defaults().size(48f);
t.button(Icon.none, Styles.flati, () -> {
hide();
consumer.get(0);
});
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
int i = 1;
for(var key : accessibleIcons){
var value = Icon.icons.get(key);
t.button(value, Styles.flati, () -> {
hide();
consumer.get(Iconc.codes.get(key));
});
if(++i % cols == 0) t.row();
}
for(ContentType ctype : defaultContentIcons){
t.row();
t.image().colspan(cols).growX().width(Float.NEGATIVE_INFINITY).height(3f).color(Pal.accent);
t.row();
i = 0;
for(UnlockableContent u : content.getBy(ctype).<UnlockableContent>as()){
if(!u.isHidden() && u.unlocked()){
t.button(new TextureRegionDrawable(u.uiIcon), Styles.flati, iconMed, () -> {
hide();
consumer.get(u.emojiChar());
});
if(++i % cols == 0) t.row();
}
}
}
});
});
buttons.button("@back", Icon.left, this::hide).size(210f, 64f);
}
public void show(Intc listener){
consumer = listener;
super.show();
}
}

View File

@@ -213,7 +213,6 @@ public class KeybindDialog extends Dialog{
@Override
public boolean keyDown(InputEvent event, KeyCode keycode){
rebindDialog.hide();
if(keycode == KeyCode.escape) return false;
rebind(section, name, keycode);
return false;
}

View File

@@ -102,7 +102,7 @@ public class MapPlayDialog extends BaseDialog{
ScrollPane pane = new ScrollPane(table);
pane.setFadeScrollBars(false);
table.row();
for(Gamemode mode : Gamemode.values()){
for(Gamemode mode : Gamemode.all){
if(mode.hidden) continue;
table.labelWrap("[accent]" + mode + ":[] [lightgray]" + mode.description()).width(400f);
table.row();

View File

@@ -1,11 +1,13 @@
package mindustry.ui.dialogs;
import arc.*;
import mindustry.editor.*;
import mindustry.gen.*;
import static mindustry.Vars.*;
public class PausedDialog extends BaseDialog{
private MapProcessorsDialog processors = new MapProcessorsDialog();
private SaveDialog save = new SaveDialog();
private LoadDialog load = new LoadDialog();
private boolean wasClient = false;
@@ -49,13 +51,22 @@ public class PausedDialog extends BaseDialog{
cont.row();
cont.button("@hostserver", Icon.host, () -> {
//the button runs out of space when the editor button is added, so use the mobile text
cont.button(state.isEditor() ? "@hostserver.mobile" : "@hostserver", Icon.host, () -> {
if(net.server() && steam){
platform.inviteFriends();
}else{
ui.host.show();
}
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(2).width(dw * 2 + 10f).update(e -> e.setText(net.server() && steam ? "@invitefriends" : "@hostserver"));
}).disabled(b -> !((steam && net.server()) || !net.active())).colspan(state.isEditor() ? 1 : 2).width(state.isEditor() ? dw : dw * 2 + 10f)
.update(e -> e.setText(net.server() && steam ? "@invitefriends" : state.isEditor() ? "@hostserver.mobile" : "@hostserver"));
if(state.isEditor()){
cont.button("@editor.worldprocessors", Icon.logic, () -> {
hide();
processors.show();
});
}
cont.row();

View File

@@ -43,13 +43,6 @@ import static mindustry.graphics.g3d.PlanetRenderer.*;
import static mindustry.ui.dialogs.PlanetDialog.Mode.*;
public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
static final String[] defaultIcons = {
"effect", "power", "logic", "units", "liquid", "production", "defense", "turret", "distribution", "crafting",
"settings", "cancel", "zoom", "ok", "star", "home", "pencil", "up", "down", "left", "right",
"hammer", "warning", "tree", "admin", "map", "modePvp", "terrain",
"modeSurvival", "commandRally", "commandAttack",
};
//if true, enables launching anywhere for testing
public static boolean debugSelect = false;
public static float sectorShowDuration = 60f * 2.4f;
@@ -998,6 +991,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
sector.save = null;
}
updateSelected();
rebuildList();
});
}
@@ -1053,6 +1047,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
Icon.icons.get(sector.info.icon + "Small");
title.button(icon == null ? Icon.noneSmall : icon, Styles.clearNonei, iconSmall, () -> {
//TODO use IconSelectDialog
new Dialog(""){{
closeOnBack();
setFillParent(true);
@@ -1079,7 +1074,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
int i = 1;
for(var key : defaultIcons){
for(var key : accessibleIcons){
var value = Icon.icons.get(key);
t.button(value, Styles.squareTogglei, () -> {

View File

@@ -398,6 +398,7 @@ public class SchematicsDialog extends BaseDialog{
closeOnBack();
setFillParent(true);
//TODO: use IconSelectDialog
cont.pane(t -> {
resized(true, () -> {
t.clearChildren();
@@ -407,7 +408,7 @@ public class SchematicsDialog extends BaseDialog{
int cols = (int)Math.min(20, Core.graphics.getWidth() / Scl.scl(52f));
int i = 0;
for(String icon : PlanetDialog.defaultIcons){
for(String icon : accessibleIcons){
String out = (char)Iconc.codes.get(icon) + "";
if(tags.contains(out)) continue;

View File

@@ -409,7 +409,7 @@ public class Block extends UnlockableContent implements Senseable{
Draw.rect(
variants == 0 ? customShadowRegion :
variantShadowRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantShadowRegions.length - 1))],
tile.drawx(), tile.drawy(), tile.build == null ? 0f : tile.build.drawrot());
tile.drawx(), tile.drawy());
Draw.color();
}

View File

@@ -353,7 +353,7 @@ public class ConstructBlock extends Block{
if(progress <= current.deconstructThreshold || state.rules.infiniteResources){
//add any leftover items that weren't obtained due to rounding errors
if(core != null){
if(core != null && !state.rules.infiniteResources){
for(int i = 0; i < itemsLeft.length; i++){
int target = Mathf.round(requirements[i].amount * state.rules.buildCostMultiplier * state.rules.deconstructRefundMultiplier);
int remaining = target - itemsLeft[i];

View File

@@ -49,6 +49,7 @@ public class ItemTurret extends Turret{
stats.remove(Stat.itemCapacity);
stats.add(Stat.ammo, StatValues.ammo(ammoTypes));
stats.add(Stat.ammoCapacity, maxAmmo / ammoPerShot, StatUnit.shots);
}
@Override
@@ -79,8 +80,8 @@ public class ItemTurret extends Turret{
@Override
public float efficiency(Building build){
//valid when there's any ammo in the turret
return build instanceof ItemTurretBuild it && !it.ammo.isEmpty() ? 1f : 0f;
//valid when it can shoot
return build instanceof ItemTurretBuild it && it.ammo.size > 0 && (it.ammo.peek().amount >= ammoPerShot || it.cheating()) ? 1f : 0f;
}
@Override

View File

@@ -1,6 +1,9 @@
package mindustry.world.blocks.defense.turrets;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
import mindustry.type.*;
import mindustry.world.consumers.*;
import mindustry.world.meta.*;
@@ -19,7 +22,22 @@ public class ReloadTurret extends BaseTurret{
if(coolant != null){
stats.remove(Stat.booster);
stats.add(Stat.booster, StatValues.boosters(reload, coolant.amount, coolantMultiplier, true, l -> l.coolant && consumesLiquid(l)));
//TODO this is very hacky, there is no current way to check if a ConsumeLiquidBase accepts something individually. fix later
ObjectSet<Liquid> notBooster = content.liquids().select(l -> {
for(Consume c : consumers){
if(!c.booster && c != coolant &&
((c instanceof ConsumeLiquid cl && cl.liquid == l) ||
(c instanceof ConsumeLiquids cl2 && Structs.contains(cl2.liquids, s -> s.liquid == l)) ||
(c instanceof ConsumeLiquidFilter clf && clf.filter.get(l)))){
return true;
}
}
return false;
}).asSet();
stats.add(Stat.booster, StatValues.boosters(reload, coolant.amount, coolantMultiplier, true, l -> l.coolant && consumesLiquid(l) && !notBooster.contains(l)));
}
}

View File

@@ -137,6 +137,7 @@ public class Turret extends ReloadTurret{
liquidCapacity = 20f;
quickRotate = false;
outlinedIcon = 1;
drawLiquidLight = false;
}
@Override

View File

@@ -253,7 +253,7 @@ public class Conveyor extends Block implements Autotiler{
mid = 0;
//skip updates if possible
if(len == 0){
if(len == 0 && Mathf.equal(timeScale, 1f)){
clogHeat = 0f;
sleep();
return;

View File

@@ -10,6 +10,7 @@ import mindustry.world.*;
public class TallBlock extends Block{
public float shadowOffset = -3f;
public float layer = Layer.power + 1;
public float shadowLayer = Layer.power - 1;
public float rotationRand = 20f;
public float shadowAlpha = 0.6f;
@@ -30,14 +31,14 @@ public class TallBlock extends Block{
public void drawBase(Tile tile){
float rot = Mathf.randomSeedRange(tile.pos() + 1, rotationRand);
Draw.z(Layer.power - 1);
Draw.z(shadowLayer);
Draw.color(0f, 0f, 0f, shadowAlpha);
Draw.rect(variants > 0 ? variantShadowRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantShadowRegions.length - 1))] : customShadowRegion,
tile.worldx() + shadowOffset, tile.worldy() + shadowOffset, rot);
Draw.color();
Draw.z(Layer.power + 1);
Draw.z(layer);
Draw.rect(variants > 0 ? variantRegions[Mathf.randomSeed(tile.pos(), 0, Math.max(0, variantRegions.length - 1))] : region,
tile.worldx(), tile.worldy(), rot);
}

View File

@@ -3,6 +3,8 @@ package mindustry.world.blocks.logic;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.math.geom.*;
import arc.scene.ui.layout.*;
@@ -10,6 +12,7 @@ import arc.struct.Bits;
import arc.struct.*;
import arc.util.*;
import arc.util.io.*;
import arc.util.pooling.*;
import mindustry.ai.types.*;
import mindustry.core.*;
import mindustry.gen.*;
@@ -31,6 +34,7 @@ import static mindustry.Vars.*;
public class LogicBlock extends Block{
private static final int maxByteLen = 1024 * 100;
public static final int maxNameLength = 32;
public int maxInstructionScale = 5;
public int instructionsPerTick = 1;
@@ -56,6 +60,20 @@ public class LogicBlock extends Block{
build.readCompressed(data, true);
});
config(String.class, (LogicBuild build, String data) -> {
if(!accessible() || !privileged) return;
if(data != null && data.length() < maxNameLength){
build.tag = data;
}
});
config(Character.class, (LogicBuild build, Character data) -> {
if(!accessible() || !privileged) return;
build.iconTag = data;
});
config(Integer.class, (LogicBuild entity, Integer pos) -> {
if(!accessible()) return;
@@ -235,6 +253,9 @@ public class LogicBlock extends Block{
public boolean checkedDuplicates = false;
//dynamic only for privileged processors
public int ipt = instructionsPerTick;
/** Display name, for convenience. This is currently only available for world processors. */
public @Nullable String tag;
public char iconTag;
/** Block of code to run after load. */
public @Nullable Runnable loadBlock;
@@ -563,9 +584,45 @@ public class LogicBlock extends Block{
@Override
public void drawSelect(){
if(!accessible()) return;
Groups.unit.each(u -> u.controller() instanceof LogicAI ai && ai.controller == this, unit -> {
Drawf.square(unit.x, unit.y, unit.hitSize, unit.rotation + 45);
});
//draw tag over processor (world processor only)
if(!(renderer.pixelate || !privileged || tag == null || tag.isEmpty())){
Font font = Fonts.outline;
GlyphLayout l = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
boolean ints = font.usesIntegerPositions();
font.getData().setScale(1 / 4f / Scl.scl(1f));
font.setUseIntegerPositions(false);
l.setText(font, tag, Color.white, 90f, Align.left, true);
float offset = 1f;
//Draw.color(0f, 0f, 0f, 0.1f);
//Fill.rect(x, y + tilesize/2f - l.height/2f - offset, l.width + offset*2f, l.height + offset*2f);
Draw.color();
font.setColor(1f, 1f, 1f, 0.5f);
font.draw(tag, x - l.width/2f, y + tilesize + 2f - offset, 90f, Align.left, true);
font.setUseIntegerPositions(ints);
font.getData().setScale(1f);
Pools.free(l);
}
if(iconTag != 0){
TextureRegion icon = Fonts.getLargeIcon(Fonts.unicodeToName(iconTag));
if(icon.found()){
Draw.alpha(0.5f);
Draw.rect(icon, x, y, tilesize, tilesize / icon.ratio());
Draw.color();
}
}
}
public boolean validLink(Building other){
@@ -579,9 +636,21 @@ public class LogicBlock extends Block{
@Override
public void buildConfiguration(Table table){
table.button(Icon.pencil, Styles.cleari, () -> {
ui.logic.show(code, executor, privileged, code -> configure(compress(code, relativeConnections())));
}).size(40);
table.button(Icon.pencil, Styles.cleari, this::showEditDialog).size(40);
}
public void showEditDialog(){
showEditDialog(false);
}
public void showEditDialog(boolean forceEditor){
ui.logic.show(code, executor, privileged, code -> {
boolean prev = state.rules.editor;
//this is a hack to allow configuration to work correctly in the editor for privileged processors
if(forceEditor) state.rules.editor = true;
configure(compress(code, relativeConnections()));
state.rules.editor = prev;
});
}
@Override
@@ -601,7 +670,7 @@ public class LogicBlock extends Block{
@Override
public byte version(){
return 2;
return 3;
}
@Override
@@ -638,6 +707,9 @@ public class LogicBlock extends Block{
if(privileged){
write.s(Mathf.clamp(ipt, 1, maxInstructionsPerTick));
}
TypeIO.writeString(write, tag);
write.s(iconTag);
}
@Override
@@ -694,6 +766,11 @@ public class LogicBlock extends Block{
ipt = Mathf.clamp(read.s(), 1, maxInstructionsPerTick);
}
if(revision >= 3){
tag = TypeIO.readString(read);
iconTag = (char)read.us();
}
}
}
}

View File

@@ -153,8 +153,9 @@ public class PayloadLoader extends PayloadBlock{
//load up items
if(payload.block().hasItems && items.any()){
boolean acceptedAny = false;
boolean acceptedAny = true;
if(efficiency > 0.01f && timer(timerLoad, loadTime / efficiency)){
acceptedAny = false;
//load up items a set amount of times
for(int j = 0; j < itemsLoaded && items.any(); j++){

View File

@@ -14,6 +14,7 @@ import mindustry.entities.EntityCollisions.*;
import mindustry.entities.*;
import mindustry.game.EventType.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.type.*;
import static mindustry.Vars.*;
@@ -146,22 +147,15 @@ public class UnitPayload implements Payload{
//TODO should not happen
if(unit.type == null) return;
//TODO this would be more accurate but has all sorts of associated problems (?)
if(false){
float e = unit.elevation;
unit.elevation = 0f;
//avoids drawing mining or building
unit.type.draw(unit);
unit.elevation = e;
return;
}
unit.type.drawSoftShadow(unit);
Draw.rect(unit.type.fullIcon, unit.x, unit.y, unit.rotation - 90);
unit.type.drawCell(unit);
float e = unit.elevation;
unit.elevation = 0f;
unit.type.draw(unit);
unit.elevation = e;
//draw warning
if(overlayTime > 0){
float z = Draw.z();
Draw.z(Layer.groundUnit + 1f);
var region = overlayRegion == null ? Icon.warning.getRegion() : overlayRegion;
Draw.color(Color.scarlet);
Draw.alpha(0.8f * Interp.exp5Out.apply(overlayTime));
@@ -172,6 +166,7 @@ public class UnitPayload implements Payload{
Draw.reset();
overlayTime = Math.max(overlayTime - Time.delta/overlayDuration, 0f);
Draw.z(z);
}
}

View File

@@ -102,7 +102,7 @@ public class PowerGenerator extends PowerDistributor{
@Override
public float warmup(){
return productionEfficiency;
return enabled ? productionEfficiency : 0f;
}
@Override
@@ -154,7 +154,7 @@ public class PowerGenerator extends PowerDistributor{
@Override
public float getPowerProduction(){
return powerProduction * productionEfficiency;
return enabled ? powerProduction * productionEfficiency : 0f;
}
@Override

View File

@@ -88,6 +88,11 @@ public class ThermalGenerator extends PowerGenerator{
}
}
@Override
public float totalProgress(){
return enabled ? super.totalProgress() : 0f;
}
@Override
public void drawLight(){
Drawf.light(x, y, (40f + Mathf.absin(10f, 5f)) * Math.min(productionEfficiency, 2f) * size, Color.scarlet, 0.4f);

View File

@@ -265,7 +265,7 @@ public class BeamDrill extends Block{
@Override
public boolean shouldConsume(){
return items.total() < itemCapacity && lastItem != null && enabled;
return items.total() < itemCapacity && facingAmount > 0 && enabled;
}
@Override

View File

@@ -11,11 +11,20 @@ public class PowerSource extends PowerNode{
maxNodes = 100;
outputsPower = true;
consumesPower = false;
drawDisabled = true;
//TODO maybe don't?
envEnabled = Env.any;
}
public class PowerSourceBuild extends PowerNodeBuild{
@Override
public void onProximityUpdate(){
super.onProximityUpdate();
if(!allowUpdate()){
enabled = false;
}
}
@Override
public float getPowerProduction(){
return enabled ? powerProduction : 0f;

View File

@@ -51,6 +51,7 @@ public class CoreBlock extends StorageBlock{
public UnitType unitType = UnitTypes.alpha;
public float landDuration = 160f;
public Music landMusic = Musics.land;
public Music launchMusic = Musics.coreLaunch;
public Effect launchEffect = Fx.launch;
public Interp landZoomInterp = Interp.pow3;
@@ -333,6 +334,10 @@ public class CoreBlock extends StorageBlock{
return landMusic;
}
public Music launchMusic(){
return launchMusic;
}
@Override
public void draw(){
//draw thrusters when just landed

View File

@@ -24,6 +24,9 @@ public class Unloader extends Block{
public float speed = 1f;
/** Cached result of content.items() */
static Item[] allItems;
public Unloader(String name){
super(name);
update = true;
@@ -41,6 +44,13 @@ public class Unloader extends Block{
configClear((UnloaderBuild tile) -> tile.sortItem = null);
}
@Override
public void init(){
super.init();
allItems = content.items().toArray(Item.class);
}
@Override
public void setStats(){
super.setStats();
@@ -63,27 +73,17 @@ public class Unloader extends Block{
float loadFactor;
boolean canLoad;
boolean canUnload;
/** Cached !(building instanceof StorageBuild) */
boolean notStorage;
int lastUsed;
@Override
public String toString(){
return "ContainerStat{" +
"building=" + building.block + "#" + building.id +
", loadFactor=" + loadFactor +
", canLoad=" + canLoad +
", canUnload=" + canUnload +
", lastUsed=" + lastUsed +
'}';
}
}
public class UnloaderBuild extends Building{
public float unloadTimer = 0f;
public int rotations = 0;
private final int itemsLength = content.items().size;
public Item sortItem = null;
public ContainerStat dumpingFrom, dumpingTo;
public final Seq<ContainerStat> possibleBlocks = new Seq<>();
public final Seq<ContainerStat> possibleBlocks = new Seq<>(ContainerStat.class);
protected final Comparator<ContainerStat> comparator = (x, y) -> {
//sort so it gives priority for blocks that can only either receive or give (not both), and then by load, and then by last use
@@ -99,19 +99,20 @@ public class Unloader extends Block{
private boolean isPossibleItem(Item item){
boolean hasProvider = false,
hasReceiver = false,
isDistinct = false;
hasReceiver = false,
isDistinct = false;
for(int i = 0; i < possibleBlocks.size; i++){
var pb = possibleBlocks.get(i);
var pbi = possibleBlocks.items;
for(int i = 0, l = possibleBlocks.size; i < l; i++){
var pb = pbi[i];
var other = pb.building;
//set the stats of buildings in possibleBlocks while we are at it
pb.canLoad = !(other.block instanceof StorageBlock) && other.acceptItem(this, item);
pb.canLoad = pb.notStorage && other.acceptItem(this, item);
pb.canUnload = other.canUnload() && other.items != null && other.items.has(item);
//thats also handling framerate issues and slow conveyor belts, to avoid skipping items if nulloader
if((hasProvider && pb.canLoad) || (hasReceiver && pb.canUnload)) isDistinct = true;
isDistinct |= (hasProvider && pb.canLoad) || (hasReceiver && pb.canUnload);
hasProvider |= pb.canUnload;
hasReceiver |= pb.canLoad;
}
@@ -129,14 +130,15 @@ public class Unloader extends Block{
for(int i = 0; i < proximity.size; i++){
var other = proximity.get(i);
if(!other.interactable(team)) continue; //avoid blocks of the wrong team
ContainerStat pb = Pools.obtain(ContainerStat.class, ContainerStat::new);
//partial check
boolean canLoad = !(other.block instanceof StorageBlock);
boolean canUnload = other.canUnload() && other.items != null;
if(canLoad || canUnload){ //avoid blocks that can neither give nor receive items
var pb = Pools.obtain(ContainerStat.class, ContainerStat::new);
pb.building = other;
pb.notStorage = canLoad;
//TODO store the partial canLoad/canUnload?
possibleBlocks.add(pb);
}
@@ -154,9 +156,9 @@ public class Unloader extends Block{
}else{
//selects the next item for nulloaders
//inspired of nextIndex() but for all "proximity" (possibleBlocks) at once, and also way more powerful
for(int i = 0; i < itemsLength; i++){
int total = (rotations + i + 1) % itemsLength;
Item possibleItem = content.item(total);
for(int i = 0, l = allItems.length; i < l; i++){
int id = (rotations + i + 1) % l;
var possibleItem = allItems[id];
if(isPossibleItem(possibleItem)){
item = possibleItem;
@@ -167,11 +169,14 @@ public class Unloader extends Block{
if(item != null){
rotations = item.id; //next rotation for nulloaders //TODO maybe if(sortItem == null)
var pbi = possibleBlocks.items;
int pbs = possibleBlocks.size;
for(int i = 0; i < possibleBlocks.size; i++){
var pb = possibleBlocks.get(i);
for(int i = 0; i < pbs; i++){
var pb = pbi[i];
var other = pb.building;
pb.loadFactor = (other.getMaximumAccepted(item) == 0) || (other.items == null) ? 0 : other.items.get(item) / (float)other.getMaximumAccepted(item);
int maxAccepted = other.getMaximumAccepted(item);
pb.loadFactor = maxAccepted == 0 || other.items == null ? 0 : other.items.get(item) / (float)maxAccepted;
pb.lastUsed = (pb.lastUsed + 1) % Integer.MAX_VALUE; //increment the priority if not used
}
@@ -181,17 +186,17 @@ public class Unloader extends Block{
dumpingFrom = null;
//choose the building to accept the item
for(int i = 0; i < possibleBlocks.size; i++){
if(possibleBlocks.get(i).canLoad){
dumpingTo = possibleBlocks.get(i);
for(int i = 0; i < pbs; i++){
if(pbi[i].canLoad){
dumpingTo = pbi[i];
break;
}
}
//choose the building to take the item from
for(int i = possibleBlocks.size - 1; i >= 0; i--){
if(possibleBlocks.get(i).canUnload){
dumpingFrom = possibleBlocks.get(i);
for(int i = pbs - 1; i >= 0; i--){
if(pbi[i].canUnload){
dumpingFrom = pbi[i];
break;
}
}

View File

@@ -373,12 +373,12 @@ public class UnitAssembler extends PayloadBlock{
units.clear();
}
float powerStatus = power == null ? 1f : power.status;
float powerStatus = !enabled ? 0f : power == null ? 1f : power.status;
powerWarmup = Mathf.lerpDelta(powerStatus, powerStatus > 0.0001f ? 1f : 0f, 0.1f);
droneWarmup = Mathf.lerpDelta(droneWarmup, units.size < dronesCreated ? powerStatus : 0f, 0.1f);
totalDroneProgress += droneWarmup * delta();
if(units.size < dronesCreated && (droneProgress += delta() * state.rules.unitBuildSpeed(team) * powerStatus / droneConstructTime) >= 1f){
if(units.size < dronesCreated && enabled && (droneProgress += delta() * state.rules.unitBuildSpeed(team) * powerStatus / droneConstructTime) >= 1f){
if(!net.client()){
var unit = droneType.create(team);
if(unit instanceof BuildingTetherc bt){

View File

@@ -14,6 +14,7 @@ public class DrawFlame extends DrawBlock{
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 float flameX = 0, flameY = 0;
public DrawFlame(){
}
@@ -43,9 +44,9 @@ public class DrawFlame extends DrawBlock{
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);
Fill.circle(build.x + flameX, build.y + flameY, flameRadius + Mathf.absin(Time.time, flameRadiusScl, flameRadiusMag) + cr);
Draw.color(1f, 1f, 1f, build.warmup());
Fill.circle(build.x, build.y, flameRadiusIn + Mathf.absin(Time.time, flameRadiusScl, flameRadiusInMag) + cr);
Fill.circle(build.x + flameX, build.y + flameY, flameRadiusIn + Mathf.absin(Time.time, flameRadiusScl, flameRadiusInMag) + cr);
Draw.color();
}
@@ -53,6 +54,6 @@ public class DrawFlame extends DrawBlock{
@Override
public void drawLight(Building build){
Drawf.light(build.x, build.y, (lightRadius + Mathf.absin(lightSinScl, lightSinMag)) * build.warmup() * build.block.size, flameColor, lightAlpha);
Drawf.light(build.x + flameX, build.y + flameY, (lightRadius + Mathf.absin(lightSinScl, lightSinMag)) * build.warmup() * build.block.size, flameColor, lightAlpha);
}
}

View File

@@ -86,6 +86,7 @@ public class Stat implements Comparable<Stat>{
targetsGround = new Stat("targetsGround", StatCat.function),
damage = new Stat("damage", StatCat.function),
ammo = new Stat("ammo", StatCat.function),
ammoCapacity = new Stat("ammoCapacity", StatCat.function),
ammoUse = new Stat("ammoUse", StatCat.function),
shieldHealth = new Stat("shieldHealth", StatCat.function),
cooldownTime = new Stat("cooldownTime", StatCat.function),

View File

@@ -24,6 +24,7 @@ public class StatUnit{
degrees = new StatUnit("degrees"),
seconds = new StatUnit("seconds"),
minutes = new StatUnit("minutes"),
shots = new StatUnit("shots"),
perSecond = new StatUnit("perSecond", false),
perMinute = new StatUnit("perMinute", false),
perShot = new StatUnit("perShot", false),