Merge branch 'master' into do-you-hear-the-voices-too
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
@@ -77,6 +77,7 @@ public class RtsAI{
|
||||
}
|
||||
|
||||
public void update(){
|
||||
|
||||
if(timer.get(timeUpdate, 60f * 2f)){
|
||||
assignSquads();
|
||||
checkBuilding();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"){{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
155
core/src/mindustry/editor/MapProcessorsDialog.java
Normal file
155
core/src/mindustry/editor/MapProcessorsDialog.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -247,4 +247,4 @@ public class EntityCollisions{
|
||||
public interface SolidPred{
|
||||
boolean solid(int x, int y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ abstract class BlockUnitComp implements Unitc{
|
||||
@Replace
|
||||
@Override
|
||||
public TextureRegion icon(){
|
||||
return tile.block.fullIcon;
|
||||
return tile.block.uiIcon;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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){
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -27,7 +27,7 @@ public class BlendFilter extends GenerateFilter{
|
||||
|
||||
@Override
|
||||
public char icon(){
|
||||
return Iconc.blockSand;
|
||||
return Iconc.blockSandFloor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
72
core/src/mindustry/maps/filters/LogicFilter.java
Normal file
72
core/src/mindustry/maps/filters/LogicFilter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class SpawnPathFilter extends GenerateFilter{
|
||||
|
||||
@Override
|
||||
public char icon(){
|
||||
return Iconc.blockCommandCenter;
|
||||
return Iconc.blockCoreZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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){
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
74
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal file
74
core/src/mindustry/ui/dialogs/IconSelectDialog.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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, () -> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ public class Turret extends ReloadTurret{
|
||||
liquidCapacity = 20f;
|
||||
quickRotate = false;
|
||||
outlinedIcon = 1;
|
||||
drawLiquidLight = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++){
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user