Merge branch 'Anuken:master' into balancing_burst-drill-optional-multiplier
This commit is contained in:
@@ -494,7 +494,11 @@ public class Vars implements Loadable{
|
||||
|
||||
//router
|
||||
if(locale.toString().equals("router")){
|
||||
bundle.debug("router");
|
||||
I18NBundle defBundle = I18NBundle.createBundle(Core.files.internal("bundles/bundle"));
|
||||
String router = Character.toString(Iconc.blockRouter);
|
||||
for(String s : bundle.getKeys()){
|
||||
bundle.getProperties().put(s, Strings.stripColors(defBundle.get(s)).replaceAll("\\S", router));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,15 @@ public class CommandAI extends AIController{
|
||||
commandTarget(target, false);
|
||||
}
|
||||
|
||||
//pursue the target for patrol, keeping the current position
|
||||
if(stance == UnitStance.patrol && target != null && attackTarget == null){
|
||||
//commanding a target overwrites targetPos, so add it to the queue
|
||||
if(targetPos != null){
|
||||
commandQueue.add(targetPos.cpy());
|
||||
}
|
||||
commandTarget(target, false);
|
||||
}
|
||||
|
||||
//remove invalid targets
|
||||
if(commandQueue.any()){
|
||||
commandQueue.removeAll(e -> e instanceof Healthc h && !h.isValid());
|
||||
@@ -123,6 +132,14 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Teamc findMainTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
if(!unit.type.autoFindTarget && !(targetPos == null || nearAttackTarget(unit.x, unit.y, unit.range()))){
|
||||
return null;
|
||||
}
|
||||
return super.findMainTarget(x, y, range, air, ground);
|
||||
}
|
||||
|
||||
public void defaultBehavior(){
|
||||
|
||||
if(!net.client() && unit instanceof Payloadc pay){
|
||||
@@ -164,28 +181,8 @@ public class CommandAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
//acquiring naval targets isn't supported yet, so use the fallback dumb AI
|
||||
if(unit.team.isAI() && unit.team.rules().rtsAi && unit.type.naval){
|
||||
if(fallback == null) fallback = new GroundAI();
|
||||
|
||||
if(fallback.unit() != unit) fallback.unit(unit);
|
||||
fallback.updateUnit();
|
||||
return;
|
||||
}
|
||||
|
||||
updateVisuals();
|
||||
//only autotarget if the unit supports it
|
||||
if((targetPos == null || nearAttackTarget(unit.x, unit.y, unit.range())) || unit.type.autoFindTarget){
|
||||
updateTargeting();
|
||||
}else if(attackTarget == null){
|
||||
//if the unit does not have an attack target, is currently moving, and does not have autotargeting, stop attacking stuff
|
||||
target = null;
|
||||
for(var mount : unit.mounts){
|
||||
if(mount.weapon.controllable){
|
||||
mount.target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTargeting();
|
||||
|
||||
if(attackTarget != null && invalid(attackTarget)){
|
||||
attackTarget = null;
|
||||
|
||||
@@ -163,7 +163,8 @@ public class Blocks{
|
||||
worldProcessor, worldCell, worldMessage, worldSwitch,
|
||||
|
||||
//campaign
|
||||
launchPad, interplanetaryAccelerator
|
||||
launchPad, advancedLaunchPad, landingPad,
|
||||
interplanetaryAccelerator
|
||||
|
||||
;
|
||||
|
||||
@@ -1006,7 +1007,7 @@ public class Blocks{
|
||||
outputsLiquid = true;
|
||||
envEnabled = Env.any;
|
||||
drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(Liquids.water), new DrawLiquidTile(Liquids.cryofluid){{drawLiquidLight = true;}}, new DrawDefault());
|
||||
liquidCapacity = 24f;
|
||||
liquidCapacity = 36f;
|
||||
craftTime = 120;
|
||||
lightLiquid = Liquids.cryofluid;
|
||||
|
||||
@@ -1237,7 +1238,7 @@ public class Blocks{
|
||||
|
||||
researchCostMultiplier = 1.1f;
|
||||
itemCapacity = 0;
|
||||
liquidCapacity = 40f;
|
||||
liquidCapacity = 60f;
|
||||
consumePower(2f);
|
||||
ambientSound = Sounds.extractLoop;
|
||||
ambientSoundVolume = 0.06f;
|
||||
@@ -1295,7 +1296,7 @@ public class Blocks{
|
||||
drawer = new DrawMulti(new DrawRegion("-bottom"), new DrawLiquidTile(Liquids.slag), new DrawDefault(), new DrawHeatOutput());
|
||||
size = 3;
|
||||
itemCapacity = 0;
|
||||
liquidCapacity = 40f;
|
||||
liquidCapacity = 120f;
|
||||
rotateDraw = false;
|
||||
regionRotated1 = 1;
|
||||
ambientSound = Sounds.hum;
|
||||
@@ -1934,12 +1935,13 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
itemBridge = new BufferedItemBridge("bridge-conveyor"){{
|
||||
requirements(Category.distribution, with(Items.lead, 6, Items.copper, 6));
|
||||
requirements(Category.distribution, with(Items.lead, 10, Items.copper, 10));
|
||||
fadeIn = moveArrows = false;
|
||||
range = 4;
|
||||
speed = 74f;
|
||||
arrowSpacing = 6f;
|
||||
bufferCapacity = 14;
|
||||
buildCostMultiplier = 2f;
|
||||
}};
|
||||
|
||||
phaseConveyor = new ItemBridge("phase-conveyor"){{
|
||||
@@ -2119,13 +2121,14 @@ public class Blocks{
|
||||
mechanicalPump = new Pump("mechanical-pump"){{
|
||||
requirements(Category.liquid, with(Items.copper, 15, Items.metaglass, 10));
|
||||
pumpAmount = 7f / 60f;
|
||||
liquidCapacity = 20f;
|
||||
}};
|
||||
|
||||
rotaryPump = new Pump("rotary-pump"){{
|
||||
requirements(Category.liquid, with(Items.copper, 70, Items.metaglass, 50, Items.silicon, 20, Items.titanium, 35));
|
||||
pumpAmount = 0.2f;
|
||||
consumePower(0.3f);
|
||||
liquidCapacity = 30f;
|
||||
liquidCapacity = 80f;
|
||||
hasPower = true;
|
||||
size = 2;
|
||||
}};
|
||||
@@ -2134,33 +2137,34 @@ public class Blocks{
|
||||
requirements(Category.liquid, with(Items.copper, 80, Items.metaglass, 90, Items.silicon, 30, Items.titanium, 40, Items.thorium, 35));
|
||||
pumpAmount = 0.22f;
|
||||
consumePower(1.3f);
|
||||
liquidCapacity = 40f;
|
||||
liquidCapacity = 200f;
|
||||
hasPower = true;
|
||||
size = 3;
|
||||
}};
|
||||
|
||||
conduit = new Conduit("conduit"){{
|
||||
requirements(Category.liquid, with(Items.metaglass, 1));
|
||||
liquidCapacity = 20f;
|
||||
health = 45;
|
||||
}};
|
||||
|
||||
pulseConduit = new Conduit("pulse-conduit"){{
|
||||
requirements(Category.liquid, with(Items.titanium, 2, Items.metaglass, 1));
|
||||
liquidCapacity = 16f;
|
||||
liquidCapacity = 40f;
|
||||
liquidPressure = 1.025f;
|
||||
health = 90;
|
||||
}};
|
||||
|
||||
platedConduit = new ArmoredConduit("plated-conduit"){{
|
||||
requirements(Category.liquid, with(Items.thorium, 2, Items.metaglass, 1, Items.plastanium, 1));
|
||||
liquidCapacity = 16f;
|
||||
liquidCapacity = 50f;
|
||||
liquidPressure = 1.025f;
|
||||
health = 220;
|
||||
}};
|
||||
|
||||
liquidRouter = new LiquidRouter("liquid-router"){{
|
||||
requirements(Category.liquid, with(Items.graphite, 4, Items.metaglass, 2));
|
||||
liquidCapacity = 20f;
|
||||
liquidCapacity = 120f;
|
||||
underBullets = true;
|
||||
solid = false;
|
||||
}};
|
||||
@@ -2191,6 +2195,7 @@ public class Blocks{
|
||||
arrowSpacing = 6f;
|
||||
range = 4;
|
||||
hasPower = false;
|
||||
liquidCapacity = 100f;
|
||||
}};
|
||||
|
||||
phaseConduit = new LiquidBridge("phase-conduit"){{
|
||||
@@ -2201,6 +2206,7 @@ public class Blocks{
|
||||
hasPower = true;
|
||||
canOverdrive = false;
|
||||
pulse = true;
|
||||
liquidCapacity = 100f;
|
||||
consumePower(0.30f);
|
||||
}};
|
||||
|
||||
@@ -2219,19 +2225,17 @@ public class Blocks{
|
||||
requirements(Category.liquid, with(Items.beryllium, 2));
|
||||
botColor = Pal.darkestMetal;
|
||||
leaks = true;
|
||||
liquidCapacity = 20f;
|
||||
liquidCapacity = 50f;
|
||||
liquidPressure = 1.03f;
|
||||
health = 250;
|
||||
researchCostMultiplier = 3;
|
||||
underBullets = true;
|
||||
}};
|
||||
|
||||
//TODO is this necessary? junctions are not good design
|
||||
//TODO make it leak
|
||||
reinforcedLiquidJunction = new LiquidJunction("reinforced-liquid-junction"){{
|
||||
requirements(Category.liquid, with(Items.graphite, 4, Items.beryllium, 8));
|
||||
buildCostMultiplier = 3f;
|
||||
health = 260;
|
||||
health = 250;
|
||||
((Conduit)reinforcedConduit).junctionReplacement = this;
|
||||
researchCostMultiplier = 1;
|
||||
solid = false;
|
||||
@@ -2242,19 +2246,22 @@ public class Blocks{
|
||||
requirements(Category.liquid, with(Items.graphite, 8, Items.beryllium, 20));
|
||||
range = 4;
|
||||
hasPower = false;
|
||||
liquidCapacity = 120f;
|
||||
researchCostMultiplier = 1;
|
||||
underBullets = true;
|
||||
health = 250;
|
||||
|
||||
((Conduit)reinforcedConduit).rotBridgeReplacement = this;
|
||||
}};
|
||||
|
||||
reinforcedLiquidRouter = new LiquidRouter("reinforced-liquid-router"){{
|
||||
requirements(Category.liquid, with(Items.graphite, 8, Items.beryllium, 4));
|
||||
liquidCapacity = 30f;
|
||||
liquidCapacity = 150f;
|
||||
liquidPadding = 3f/4f;
|
||||
researchCostMultiplier = 3;
|
||||
underBullets = true;
|
||||
solid = false;
|
||||
health = 250;
|
||||
}};
|
||||
|
||||
reinforcedLiquidContainer = new LiquidRouter("reinforced-liquid-container"){{
|
||||
@@ -2264,6 +2271,7 @@ public class Blocks{
|
||||
liquidPadding = 6f/4f;
|
||||
researchCostMultiplier = 4;
|
||||
solid = true;
|
||||
health = 400;
|
||||
}};
|
||||
|
||||
reinforcedLiquidTank = new LiquidRouter("reinforced-liquid-tank"){{
|
||||
@@ -2272,15 +2280,17 @@ public class Blocks{
|
||||
solid = true;
|
||||
liquidCapacity = 2700f;
|
||||
liquidPadding = 2f;
|
||||
health = 900;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region power
|
||||
|
||||
powerNode = new PowerNode("power-node"){{
|
||||
requirements(Category.power, with(Items.copper, 1, Items.lead, 3));
|
||||
requirements(Category.power, with(Items.copper, 2, Items.lead, 6));
|
||||
maxNodes = 10;
|
||||
laserRange = 6;
|
||||
buildCostMultiplier = 2.5f;
|
||||
}};
|
||||
|
||||
powerNodeLarge = new PowerNode("power-node-large"){{
|
||||
@@ -2396,17 +2406,18 @@ public class Blocks{
|
||||
envEnabled = Env.any;
|
||||
generateEffect = Fx.generatespark;
|
||||
|
||||
itemDurationMultipliers.put(Items.phaseFabric, 210f / 14f);
|
||||
drawer = new DrawMulti(new DrawDefault(), new DrawWarmupRegion());
|
||||
consume(new ConsumeItemRadioactive());
|
||||
}};
|
||||
|
||||
solarPanel = new SolarGenerator("solar-panel"){{
|
||||
requirements(Category.power, with(Items.lead, 10, Items.silicon, 15));
|
||||
powerProduction = 0.1f;
|
||||
requirements(Category.power, with(Items.lead, 10, Items.silicon, 10));
|
||||
powerProduction = 0.12f;
|
||||
}};
|
||||
|
||||
largeSolarPanel = new SolarGenerator("solar-panel-large"){{
|
||||
requirements(Category.power, with(Items.lead, 80, Items.silicon, 110, Items.phaseFabric, 15));
|
||||
requirements(Category.power, with(Items.lead, 60, Items.silicon, 70, Items.phaseFabric, 15));
|
||||
size = 3;
|
||||
powerProduction = 1.6f;
|
||||
}};
|
||||
@@ -2433,7 +2444,7 @@ public class Blocks{
|
||||
itemDuration = 140f;
|
||||
ambientSound = Sounds.pulse;
|
||||
ambientSoundVolume = 0.07f;
|
||||
liquidCapacity = 60f;
|
||||
liquidCapacity = 80f;
|
||||
|
||||
consumePower(25f);
|
||||
consumeItem(Items.blastCompound);
|
||||
@@ -2443,12 +2454,13 @@ public class Blocks{
|
||||
//erekir
|
||||
|
||||
beamNode = new BeamNode("beam-node"){{
|
||||
requirements(Category.power, with(Items.beryllium, 8));
|
||||
requirements(Category.power, with(Items.beryllium, 10));
|
||||
consumesPower = outputsPower = true;
|
||||
health = 90;
|
||||
range = 10;
|
||||
fogRadius = 1;
|
||||
researchCost = with(Items.beryllium, 5);
|
||||
buildCostMultiplier = 2f;
|
||||
|
||||
consumePowerBuffered(1000f);
|
||||
}};
|
||||
@@ -2715,7 +2727,7 @@ public class Blocks{
|
||||
result = Liquids.water;
|
||||
pumpAmount = 0.11f;
|
||||
size = 2;
|
||||
liquidCapacity = 30f;
|
||||
liquidCapacity = 40f;
|
||||
rotateSpeed = 1.4f;
|
||||
attribute = Attribute.water;
|
||||
envRequired |= Env.groundWater;
|
||||
@@ -2731,6 +2743,7 @@ public class Blocks{
|
||||
hasLiquids = true;
|
||||
hasPower = true;
|
||||
hasItems = true;
|
||||
liquidCapacity = 80f;
|
||||
|
||||
craftEffect = Fx.none;
|
||||
envRequired |= Env.spores;
|
||||
@@ -2757,7 +2770,7 @@ public class Blocks{
|
||||
updateEffectChance = 0.05f;
|
||||
pumpAmount = 0.25f;
|
||||
size = 3;
|
||||
liquidCapacity = 30f;
|
||||
liquidCapacity = 40f;
|
||||
attribute = Attribute.oil;
|
||||
baseEfficiency = 0f;
|
||||
itemUseTime = 60f;
|
||||
@@ -3390,18 +3403,18 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
parallax = new TractorBeamTurret("parallax"){{
|
||||
requirements(Category.turret, with(Items.silicon, 120, Items.titanium, 90, Items.graphite, 30));
|
||||
requirements(Category.turret, with(Items.silicon, 160, Items.titanium, 110, Items.graphite, 50));
|
||||
|
||||
hasPower = true;
|
||||
size = 2;
|
||||
force = 12f;
|
||||
scaledForce = 6f;
|
||||
range = 240f;
|
||||
damage = 0.3f;
|
||||
force = 16f;
|
||||
scaledForce = 9f;
|
||||
range = 300f;
|
||||
damage = 0.5f;
|
||||
scaledHealth = 160;
|
||||
rotateSpeed = 10;
|
||||
rotateSpeed = 12;
|
||||
|
||||
consumePower(3f);
|
||||
consumePower(3.3f);
|
||||
}};
|
||||
|
||||
swarmer = new ItemTurret("swarmer"){{
|
||||
@@ -3757,6 +3770,7 @@ public class Blocks{
|
||||
ammoUseEffect = Fx.casing3Double;
|
||||
ammoPerShot = 2;
|
||||
velocityRnd = 0.2f;
|
||||
scaleLifetimeOffset = 1f / 9f;
|
||||
recoil = 6f;
|
||||
shake = 2f;
|
||||
range = 290f;
|
||||
@@ -5217,7 +5231,7 @@ public class Blocks{
|
||||
|
||||
shake = 6f;
|
||||
ammoPerShot = 15;
|
||||
maxAmmo = 30;
|
||||
maxAmmo = 45;
|
||||
shootY = -1;
|
||||
outlineColor = Pal.darkOutline;
|
||||
size = 4;
|
||||
@@ -5504,7 +5518,7 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
malign = new PowerTurret("malign"){{
|
||||
requirements(Category.turret, with(Items.carbide, 400, Items.beryllium, 2000, Items.silicon, 800, Items.graphite, 800, Items.phaseFabric, 300));
|
||||
requirements(Category.turret, with(Items.carbide, 200, Items.beryllium, 1000, Items.silicon, 500, Items.graphite, 500, Items.phaseFabric, 200));
|
||||
|
||||
var haloProgress = PartProgress.warmup;
|
||||
Color haloColor = Color.valueOf("d370d3"), heatCol = Color.purple;
|
||||
@@ -5812,26 +5826,26 @@ public class Blocks{
|
||||
}};
|
||||
|
||||
velocityRnd = 0.15f;
|
||||
heatRequirement = 90f;
|
||||
heatRequirement = 72f;
|
||||
maxHeatEfficiency = 2f;
|
||||
warmupMaintainTime = 120f;
|
||||
consumePower(10f);
|
||||
|
||||
shoot = new ShootSummon(0f, 0f, circleRad, 48f);
|
||||
consumePower(40f);
|
||||
unitSort = UnitSorts.strongest;
|
||||
shoot = new ShootSummon(0f, 0f, circleRad, 20f);
|
||||
|
||||
minWarmup = 0.96f;
|
||||
shootWarmupSpeed = 0.03f;
|
||||
shootWarmupSpeed = 0.08f;
|
||||
|
||||
shootY = circleY - 5f;
|
||||
|
||||
outlineColor = Pal.darkOutline;
|
||||
envEnabled |= Env.space;
|
||||
reload = 9f;
|
||||
range = 370;
|
||||
reload = 7f;
|
||||
range = 380;
|
||||
trackingRange = range * 1.4f;
|
||||
shootCone = 100f;
|
||||
scaledHealth = 370;
|
||||
rotateSpeed = 2f;
|
||||
rotateSpeed = 2.6f;
|
||||
recoil = 0.5f;
|
||||
recoilTime = 30f;
|
||||
shake = 3f;
|
||||
@@ -6357,17 +6371,42 @@ public class Blocks{
|
||||
//region campaign
|
||||
|
||||
launchPad = new LaunchPad("launch-pad"){{
|
||||
requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 350, Items.silicon, 140, Items.lead, 200, Items.titanium, 150));
|
||||
requirements(Category.effect, BuildVisibility.legacyLaunchPadOnly, with(Items.copper, 350, Items.silicon, 140, Items.lead, 200, Items.titanium, 150));
|
||||
size = 3;
|
||||
itemCapacity = 100;
|
||||
launchTime = 60f * 20;
|
||||
hasPower = true;
|
||||
acceptMultipleItems = true;
|
||||
consumePower(4f);
|
||||
}};
|
||||
|
||||
advancedLaunchPad = new LaunchPad("advanced-launch-pad"){{
|
||||
requirements(Category.effect, BuildVisibility.notLegacyLaunchPadOnly, with(Items.copper, 350, Items.silicon, 250, Items.lead, 300, Items.titanium, 200));
|
||||
size = 4;
|
||||
itemCapacity = 100;
|
||||
launchTime = 60f * 30;
|
||||
liquidCapacity = 40f;
|
||||
hasPower = true;
|
||||
drawLiquid = Liquids.oil;
|
||||
consumeLiquid(Liquids.oil, 9f/60f);
|
||||
consumePower(8f);
|
||||
}};
|
||||
|
||||
landingPad = new LandingPad("landing-pad"){{
|
||||
requirements(Category.effect, BuildVisibility.notLegacyLaunchPadOnly, with(Items.copper, 200, Items.graphite, 100, Items.titanium, 100));
|
||||
size = 4;
|
||||
|
||||
itemCapacity = 100;
|
||||
|
||||
coolingEffect = new RadialEffect(Fx.steamCoolSmoke, 4, 90f, 9.5f, 180f);
|
||||
liquidCapacity = 3000f;
|
||||
consumeLiquidAmount = 1500f;
|
||||
}};
|
||||
|
||||
interplanetaryAccelerator = new Accelerator("interplanetary-accelerator"){{
|
||||
requirements(Category.effect, BuildVisibility.hidden, with(Items.copper, 16000, Items.silicon, 11000, Items.thorium, 13000, Items.titanium, 12000, Items.surgeAlloy, 6000, Items.phaseFabric, 5000));
|
||||
requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 16000, Items.silicon, 11000, Items.thorium, 13000, Items.titanium, 12000, Items.surgeAlloy, 6000, Items.phaseFabric, 5000));
|
||||
researchCostMultiplier = 0.1f;
|
||||
powerBufferRequirement = 1_000_000f;
|
||||
size = 7;
|
||||
hasPower = true;
|
||||
consumePower(10f);
|
||||
|
||||
@@ -1474,6 +1474,12 @@ public class Fx{
|
||||
Lines.circle(e.x, e.y, e.fin() * (e.rotation + 50f));
|
||||
}),
|
||||
|
||||
podLandShockwave = new Effect(12f, 80f, e -> {
|
||||
color(Pal.accent);
|
||||
stroke(e.fout() * 2f + 0.2f);
|
||||
Lines.circle(e.x, e.y, e.fin() * 26f);
|
||||
}),
|
||||
|
||||
explosion = new Effect(30, e -> {
|
||||
e.scaled(7, i -> {
|
||||
stroke(3f * i.fout());
|
||||
@@ -1624,6 +1630,15 @@ public class Fx{
|
||||
});
|
||||
}),
|
||||
|
||||
steamCoolSmoke = new Effect(35f, e -> {
|
||||
color(Pal.water, Color.lightGray, e.fin(Interp.pow2Out));
|
||||
alpha(e.fout(Interp.pow3Out));
|
||||
|
||||
randLenVectors(e.id, 4, e.finpow() * 7f, e.rotation, 30f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, Math.max(e.fout(), Math.min(1f, e.fin() * 8f)) * 2.8f);
|
||||
});
|
||||
}),
|
||||
|
||||
smokePuff = new Effect(30, e -> {
|
||||
color(e.color);
|
||||
|
||||
@@ -2464,6 +2479,12 @@ public class Fx{
|
||||
});
|
||||
}),
|
||||
|
||||
launchAccelerator = new Effect(22, e -> {
|
||||
color(Pal.accent);
|
||||
stroke(e.fout() * 2f);
|
||||
Lines.circle(e.x, e.y, 4f + e.finpow() * 160f);
|
||||
}),
|
||||
|
||||
launch = new Effect(28, e -> {
|
||||
color(Pal.command);
|
||||
stroke(e.fout() * 2f);
|
||||
@@ -2568,6 +2589,13 @@ public class Fx{
|
||||
Fill.circle(e.x + Tmp.v1.x, e.y + Tmp.v1.y, 8f * rand.random(0.6f, 1f) * e.fout(0.2f));
|
||||
}).layer(Layer.groundUnit + 1f),
|
||||
|
||||
podLandDust = new Effect(70f, e -> {
|
||||
color(e.color, e.fout(0.1f));
|
||||
rand.setSeed(e.id);
|
||||
Tmp.v1.trns(e.rotation, e.finpow() * 35f * rand.random(0.2f, 1f));
|
||||
Fill.circle(e.x + Tmp.v1.x, e.y + Tmp.v1.y, 5f * rand.random(0.6f, 1f) * e.fout(0.2f));
|
||||
}).layer(Layer.groundUnit + 1f),
|
||||
|
||||
unitShieldBreak = new Effect(35, e -> {
|
||||
if(!(e.data instanceof Unit unit)) return;
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ public class Planets{
|
||||
}};
|
||||
|
||||
//TODO names
|
||||
gier = makeAsteroid("gier", erekir, Blocks.ferricStoneWall, Blocks.carbonWall, 0.4f, 7, 1f, gen -> {
|
||||
gier = makeAsteroid("gier", erekir, Blocks.ferricStoneWall, Blocks.carbonWall, -5, 0.4f, 7, 1f, gen -> {
|
||||
gen.min = 25;
|
||||
gen.max = 35;
|
||||
gen.carbonChance = 0.6f;
|
||||
@@ -100,7 +100,7 @@ public class Planets{
|
||||
gen.berylChance = 0.1f;
|
||||
});
|
||||
|
||||
notva = makeAsteroid("notva", sun, Blocks.ferricStoneWall, Blocks.beryllicStoneWall, 0.55f, 9, 1.3f, gen -> {
|
||||
notva = makeAsteroid("notva", sun, Blocks.ferricStoneWall, Blocks.beryllicStoneWall, -4, 0.55f, 9, 1.3f, gen -> {
|
||||
gen.berylChance = 0.8f;
|
||||
gen.iceChance = 0f;
|
||||
gen.carbonChance = 0.01f;
|
||||
@@ -134,6 +134,7 @@ public class Planets{
|
||||
launchCapacityMultiplier = 0.5f;
|
||||
sectorSeed = 2;
|
||||
allowWaves = true;
|
||||
allowLegacyLaunchPads = true;
|
||||
allowWaveSimulation = true;
|
||||
allowSectorInvasion = true;
|
||||
allowLaunchSchematics = true;
|
||||
@@ -153,10 +154,11 @@ public class Planets{
|
||||
atmosphereRadOut = 0.3f;
|
||||
startSector = 15;
|
||||
alwaysUnlocked = true;
|
||||
allowSelfSectorLaunch = true;
|
||||
landCloudColor = Pal.spore.cpy().a(0.5f);
|
||||
}};
|
||||
|
||||
verilus = makeAsteroid("verlius", sun, Blocks.stoneWall, Blocks.iceWall, 0.5f, 12, 2f, gen -> {
|
||||
verilus = makeAsteroid("verlius", sun, Blocks.stoneWall, Blocks.iceWall, -1, 0.5f, 12, 2f, gen -> {
|
||||
gen.berylChance = 0f;
|
||||
gen.iceChance = 0.6f;
|
||||
gen.carbonChance = 0.1f;
|
||||
@@ -164,7 +166,7 @@ public class Planets{
|
||||
});
|
||||
}
|
||||
|
||||
private static Planet makeAsteroid(String name, Planet parent, Block base, Block tint, float tintThresh, int pieces, float scale, Cons<AsteroidGenerator> cgen){
|
||||
private static Planet makeAsteroid(String name, Planet parent, Block base, Block tint, int seed, float tintThresh, int pieces, float scale, Cons<AsteroidGenerator> cgen){
|
||||
return new Planet(name, parent, 0.12f){{
|
||||
hasAtmosphere = false;
|
||||
updateLighting = false;
|
||||
@@ -187,13 +189,13 @@ public class Planets{
|
||||
Rand rand = new Rand(id + 2);
|
||||
|
||||
meshes.add(new NoiseMesh(
|
||||
this, 0, 2, radius, 2, 0.55f, 0.45f, 14f,
|
||||
this, seed, 2, radius, 2, 0.55f, 0.45f, 14f,
|
||||
color, tinted, 3, 0.6f, 0.38f, tintThresh
|
||||
));
|
||||
|
||||
for(int j = 0; j < pieces; j++){
|
||||
meshes.add(new MatMesh(
|
||||
new NoiseMesh(this, j + 1, 1, 0.022f + rand.random(0.039f) * scale, 2, 0.6f, 0.38f, 20f,
|
||||
new NoiseMesh(this, seed + j + 1, 1, 0.022f + rand.random(0.039f) * scale, 2, 0.6f, 0.38f, 20f,
|
||||
color, tinted, 3, 0.6f, 0.38f, tintThresh),
|
||||
new Mat3D().setToTranslation(Tmp.v31.setToRandomDirection(rand).setLength(rand.random(0.44f, 1.4f) * scale)))
|
||||
);
|
||||
|
||||
@@ -19,11 +19,12 @@ public class SerpuloTechTree{
|
||||
|
||||
node(junction, () -> {
|
||||
node(router, () -> {
|
||||
node(launchPad, Seq.with(new SectorComplete(extractionOutpost)), () -> {
|
||||
//no longer necessary to beat the campaign
|
||||
//node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> {
|
||||
node(advancedLaunchPad, Seq.with(new SectorComplete(extractionOutpost)), () -> {
|
||||
node(landingPad, () -> {
|
||||
node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> {
|
||||
|
||||
//});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(distributor);
|
||||
@@ -523,7 +524,7 @@ public class SerpuloTechTree{
|
||||
new Research(sei),
|
||||
new Research(omura),
|
||||
new Research(spectre),
|
||||
new Research(launchPad),
|
||||
new Research(advancedLaunchPad),
|
||||
new Research(massDriver),
|
||||
new Research(impactReactor),
|
||||
new Research(additiveReconstructor),
|
||||
|
||||
@@ -99,6 +99,7 @@ public class TechTree{
|
||||
public TechNode(@Nullable TechNode parent, UnlockableContent content, ItemStack[] requirements){
|
||||
if(parent != null){
|
||||
parent.children.add(this);
|
||||
planet = parent.planet;
|
||||
researchCostMultipliers = parent.researchCostMultipliers;
|
||||
}else if(researchCostMultipliers == null){
|
||||
researchCostMultipliers = new ObjectFloatMap<>();
|
||||
|
||||
@@ -610,7 +610,7 @@ public class UnitTypes{
|
||||
|
||||
speed = 1f;
|
||||
hitSize = 8f;
|
||||
health = 200;
|
||||
health = 150;
|
||||
mechSideSway = 0.25f;
|
||||
range = 40f;
|
||||
ammoType = new ItemAmmoType(Items.coal);
|
||||
@@ -629,12 +629,12 @@ public class UnitTypes{
|
||||
collides = false;
|
||||
hitSound = Sounds.explosion;
|
||||
|
||||
rangeOverride = 30f;
|
||||
rangeOverride = 25f;
|
||||
hitEffect = Fx.pulverize;
|
||||
speed = 0f;
|
||||
splashDamageRadius = 55f;
|
||||
splashDamageRadius = 44f;
|
||||
instantDisappear = true;
|
||||
splashDamage = 90f;
|
||||
splashDamage = 80f;
|
||||
killShooter = true;
|
||||
hittable = false;
|
||||
collidesAir = true;
|
||||
|
||||
@@ -199,9 +199,9 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
float coreDelay = 0f;
|
||||
if(!settings.getBool("skipcoreanimation") && !state.rules.pvp){
|
||||
coreDelay = core.landDuration();
|
||||
coreDelay = core.launchDuration();
|
||||
//delay player respawn so animation can play.
|
||||
player.deathTimer = Player.deathDelay - core.landDuration();
|
||||
player.deathTimer = Player.deathDelay - core.launchDuration();
|
||||
//TODO this sounds pretty bad due to conflict
|
||||
if(settings.getInt("musicvol") > 0){
|
||||
//TODO what to do if another core with different music is already playing?
|
||||
@@ -215,6 +215,10 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
if(state.rules.sector.info.importRateCache != null){
|
||||
state.rules.sector.info.refreshImportRates(state.rules.sector.planet);
|
||||
}
|
||||
|
||||
//don't run when hosting, that doesn't really work.
|
||||
if(state.rules.sector.planet.prebuildBase){
|
||||
toBePlaced.clear();
|
||||
|
||||
@@ -405,7 +405,9 @@ public class Logic implements ApplicationListener{
|
||||
@Override
|
||||
public void dispose(){
|
||||
//save the settings before quitting
|
||||
netServer.admins.forceSave();
|
||||
if(netServer != null){
|
||||
netServer.admins.forceSave();
|
||||
}
|
||||
Core.settings.manualSave();
|
||||
}
|
||||
|
||||
@@ -427,6 +429,8 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
|
||||
if(!state.isPaused()){
|
||||
Events.fire(Trigger.beforeGameUpdate);
|
||||
|
||||
float delta = Core.graphics.getDeltaTime();
|
||||
state.tick += Float.isNaN(delta) || Float.isInfinite(delta) ? 0f : delta * 60f;
|
||||
state.updateId ++;
|
||||
@@ -486,6 +490,8 @@ public class Logic implements ApplicationListener{
|
||||
Groups.weather.each(w -> state.envAttrs.add(w.weather.attrs, w.opacity));
|
||||
|
||||
Groups.update();
|
||||
|
||||
Events.fire(Trigger.afterGameUpdate);
|
||||
}
|
||||
|
||||
if(runStateCheck){
|
||||
|
||||
@@ -322,7 +322,7 @@ public class NetClient implements ApplicationListener{
|
||||
ui.join.connect(ip, port);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
@Remote(targets = Loc.client, priority = PacketPriority.high)
|
||||
public static void ping(Player player, long time){
|
||||
Call.pingResponse(player.con, time);
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ public class NetServer implements ApplicationListener{
|
||||
return Float.isInfinite(f) || Float.isNaN(f);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client, unreliable = true)
|
||||
@Remote(targets = Loc.client, unreliable = true, priority = PacketPriority.high)
|
||||
public static void clientSnapshot(
|
||||
Player player,
|
||||
int snapshotID,
|
||||
@@ -815,7 +815,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
case trace -> {
|
||||
PlayerInfo stats = netServer.admins.getInfo(other.uuid());
|
||||
TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.con.modclient, other.con.mobile, stats.timesJoined, stats.timesKicked, stats.ips.toArray(String.class), stats.names.toArray(String.class));
|
||||
TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.locale, other.con.modclient, other.con.mobile, stats.timesJoined, stats.timesKicked, stats.ips.toArray(String.class), stats.names.toArray(String.class));
|
||||
if(player.con != null){
|
||||
Call.traceInfo(player.con, other, info);
|
||||
}else{
|
||||
@@ -830,7 +830,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
@Remote(targets = Loc.client, priority = PacketPriority.high)
|
||||
public static void connectConfirm(Player player){
|
||||
if(player.con.kicked) return;
|
||||
|
||||
|
||||
@@ -20,15 +20,14 @@ import mindustry.graphics.*;
|
||||
import mindustry.graphics.g3d.*;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Renderer implements ApplicationListener{
|
||||
/** These are global variables, for headless access. Cached. */
|
||||
public static float laserOpacity = 0.5f, bridgeOpacity = 0.75f;
|
||||
public static float laserOpacity = 0.5f, unitLaserOpacity = 1f, bridgeOpacity = 0.75f;
|
||||
|
||||
public final BlockRenderer blocks = new BlockRenderer();
|
||||
public final FogRenderer fog = new FogRenderer();
|
||||
@@ -51,8 +50,7 @@ public class Renderer implements ApplicationListener{
|
||||
public TextureRegion[][] fluidFrames;
|
||||
|
||||
//currently landing core, null if there are no cores or it has finished landing.
|
||||
private @Nullable CoreBuild landCore;
|
||||
private @Nullable CoreBlock launchCoreType;
|
||||
private @Nullable LaunchAnimator launchAnimator;
|
||||
private Color clearColor = new Color(0f, 0f, 0f, 1f);
|
||||
private float
|
||||
//target camera scale that is lerp-ed to
|
||||
@@ -61,8 +59,6 @@ public class Renderer implements ApplicationListener{
|
||||
camerascale = targetscale,
|
||||
//starts at coreLandDuration, ends at 0. if positive, core is landing.
|
||||
landTime,
|
||||
//timer for core landing particles
|
||||
landPTimer,
|
||||
//intensity for screen shake
|
||||
shakeIntensity,
|
||||
//reduction rate of screen shake
|
||||
@@ -162,6 +158,7 @@ public class Renderer implements ApplicationListener{
|
||||
float dest = Mathf.clamp(Mathf.round(baseTarget, 0.5f), minScale(), maxScale());
|
||||
camerascale = Mathf.lerpDelta(camerascale, dest, 0.1f);
|
||||
if(Mathf.equal(camerascale, dest, 0.001f)) camerascale = dest;
|
||||
unitLaserOpacity = settings.getInt("unitlaseropacity") / 100f;
|
||||
laserOpacity = settings.getInt("lasersopacity") / 100f;
|
||||
bridgeOpacity = settings.getInt("bridgeopacity") / 100f;
|
||||
animateShields = settings.getBool("animatedshields");
|
||||
@@ -172,21 +169,21 @@ public class Renderer implements ApplicationListener{
|
||||
pixelate = settings.getBool("pixelate");
|
||||
|
||||
//don't bother drawing landing animation if core is null
|
||||
if(landCore == null) landTime = 0f;
|
||||
if(launchAnimator == null) landTime = 0f;
|
||||
if(landTime > 0){
|
||||
if(!state.isPaused()) landCore.updateLaunching();
|
||||
if(!state.isPaused()) launchAnimator.updateLaunch();
|
||||
|
||||
weatherAlpha = 0f;
|
||||
camerascale = landCore.zoomLaunching();
|
||||
camerascale = launchAnimator.zoomLaunch();
|
||||
|
||||
if(!state.isPaused()) landTime -= Time.delta;
|
||||
}else{
|
||||
weatherAlpha = Mathf.lerpDelta(weatherAlpha, 1f, 0.08f);
|
||||
}
|
||||
|
||||
if(landCore != null && landTime <= 0f){
|
||||
landCore.endLaunch();
|
||||
landCore = null;
|
||||
if(launchAnimator != null && landTime <= 0f){
|
||||
launchAnimator.endLaunch();
|
||||
launchAnimator = null;
|
||||
}
|
||||
|
||||
camera.width = graphics.getWidth() / camerascale;
|
||||
@@ -378,9 +375,14 @@ public class Renderer implements ApplicationListener{
|
||||
Draw.draw(Layer.overlayUI, overlays::drawTop);
|
||||
if(state.rules.fog) Draw.draw(Layer.fogOfWar, fog::drawFog);
|
||||
Draw.draw(Layer.space, () -> {
|
||||
if(landCore == null || landTime <= 0f) return;
|
||||
landCore.drawLanding(launching && launchCoreType != null ? launchCoreType : (CoreBlock)landCore.block);
|
||||
if(launchAnimator == null || landTime <= 0f) return;
|
||||
launchAnimator.drawLaunch();
|
||||
});
|
||||
if(launchAnimator != null){
|
||||
Draw.z(Layer.space);
|
||||
launchAnimator.drawLaunchGlobalZ();
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
Events.fire(Trigger.drawOver);
|
||||
blocks.drawBlocks();
|
||||
@@ -504,66 +506,41 @@ public class Renderer implements ApplicationListener{
|
||||
return launching;
|
||||
}
|
||||
|
||||
public CoreBlock getLaunchCoreType(){
|
||||
return launchCoreType;
|
||||
}
|
||||
|
||||
public float getLandTime(){
|
||||
return landTime;
|
||||
}
|
||||
|
||||
public float getLandTimeIn(){
|
||||
if(landCore == null) return 0f;
|
||||
float fin = landTime / landCore.landDuration();
|
||||
if(launchAnimator == null) return 0f;
|
||||
float fin = landTime / launchAnimator.launchDuration();
|
||||
if(!launching) fin = 1f - fin;
|
||||
return fin;
|
||||
}
|
||||
|
||||
public float getLandPTimer(){
|
||||
return landPTimer;
|
||||
}
|
||||
|
||||
public void setLandPTimer(float landPTimer){
|
||||
this.landPTimer = landPTimer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void showLanding(){
|
||||
var core = player.bestCore();
|
||||
if(core != null) showLanding(core);
|
||||
}
|
||||
|
||||
public void showLanding(CoreBuild landCore){
|
||||
this.landCore = landCore;
|
||||
public void showLanding(LaunchAnimator landCore){
|
||||
this.launchAnimator = landCore;
|
||||
launching = false;
|
||||
landTime = landCore.landDuration();
|
||||
landTime = landCore.launchDuration();
|
||||
|
||||
landCore.beginLaunch(null);
|
||||
camerascale = landCore.zoomLaunching();
|
||||
landCore.beginLaunch(false);
|
||||
camerascale = landCore.zoomLaunch();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void showLaunch(CoreBlock coreType){
|
||||
var core = player.team().core();
|
||||
if(core != null) showLaunch(core, coreType);
|
||||
}
|
||||
|
||||
public void showLaunch(CoreBuild landCore, CoreBlock coreType){
|
||||
public void showLaunch(LaunchAnimator landCore){
|
||||
control.input.config.hideConfig();
|
||||
control.input.planConfig.hide();
|
||||
control.input.inv.hide();
|
||||
|
||||
this.landCore = landCore;
|
||||
this.launchAnimator = landCore;
|
||||
launching = true;
|
||||
landTime = landCore.landDuration();
|
||||
launchCoreType = coreType;
|
||||
landTime = landCore.launchDuration();
|
||||
|
||||
Music music = landCore.launchMusic();
|
||||
music.stop();
|
||||
music.play();
|
||||
music.setVolume(settings.getInt("musicvol") / 100f);
|
||||
|
||||
landCore.beginLaunch(coreType);
|
||||
landCore.beginLaunch(true);
|
||||
}
|
||||
|
||||
public void takeMapScreenshot(){
|
||||
|
||||
@@ -32,8 +32,10 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
public boolean alwaysUnlocked = false;
|
||||
/** Whether to show the description in the research dialog preview. */
|
||||
public boolean inlineDescription = true;
|
||||
/** Whether details of blocks are hidden in custom games if they haven't been unlocked in campaign mode. */
|
||||
/** Whether details are hidden in custom games if this hasn't been unlocked in campaign mode. */
|
||||
public boolean hideDetails = true;
|
||||
/** Whether this is hidden from the Core Database. */
|
||||
public boolean hideDatabase = false;
|
||||
/** If false, all icon generation is disabled for this content; createIcons is not called. */
|
||||
public boolean generateIcons = true;
|
||||
/** How big the content appears in certain selection menus */
|
||||
|
||||
210
core/src/mindustry/editor/BannedContentDialog.java
Normal file
210
core/src/mindustry/editor/BannedContentDialog.java
Normal file
@@ -0,0 +1,210 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BannedContentDialog<T extends UnlockableContent> extends BaseDialog{
|
||||
private final ContentType type;
|
||||
private Table selectedTable;
|
||||
private Table deselectedTable;
|
||||
private ObjectSet<T> contentSet;
|
||||
private final Boolf<T> pred;
|
||||
private String contentSearch;
|
||||
private Category selectedCategory;
|
||||
private Seq<T> filteredContent;
|
||||
|
||||
public BannedContentDialog(String title, ContentType type, Boolf<T> pred){
|
||||
super(title);
|
||||
this.type = type;
|
||||
this.pred = pred;
|
||||
contentSearch = "";
|
||||
|
||||
selectedTable = new Table();
|
||||
deselectedTable = new Table();
|
||||
|
||||
addCloseButton();
|
||||
|
||||
shown(this::build);
|
||||
resized(this::build);
|
||||
}
|
||||
|
||||
public void show(ObjectSet<T> contentSet){
|
||||
this.contentSet = contentSet;
|
||||
show();
|
||||
}
|
||||
|
||||
public void build(){
|
||||
cont.clear();
|
||||
|
||||
var cell = cont.table(t -> {
|
||||
t.table(s -> {
|
||||
s.label(() -> "@search").padRight(10);
|
||||
var field = s.field(contentSearch, value -> {
|
||||
contentSearch = value;
|
||||
rebuildTables();
|
||||
}).get();
|
||||
s.button(Icon.cancel, Styles.emptyi, () -> {
|
||||
contentSearch = "";
|
||||
field.setText("");
|
||||
rebuildTables();
|
||||
}).padLeft(10f).size(35f);
|
||||
});
|
||||
if(type == ContentType.block){
|
||||
t.row();
|
||||
t.table(c -> {
|
||||
c.marginTop(8f);
|
||||
c.defaults().marginRight(4f);
|
||||
for(Category category : Category.values()){
|
||||
c.button(ui.getIcon(category.name()), Styles.squareTogglei, () -> {
|
||||
if(selectedCategory == category){
|
||||
selectedCategory = null;
|
||||
}else{
|
||||
selectedCategory = category;
|
||||
}
|
||||
rebuildTables();
|
||||
}).size(45f).update(i -> i.setChecked(selectedCategory == category)).padLeft(4f);
|
||||
}
|
||||
c.add("").padRight(4f);
|
||||
}).center();
|
||||
}
|
||||
});
|
||||
cont.row();
|
||||
if(!Core.graphics.isPortrait()) cell.colspan(2);
|
||||
|
||||
filteredContent = content.<T>getBy(type).select(pred);
|
||||
if(!contentSearch.isEmpty()) filteredContent.removeAll(content -> !content.localizedName.toLowerCase().contains(contentSearch.toLowerCase()));
|
||||
|
||||
cont.table(table -> {
|
||||
if(type == ContentType.block){
|
||||
table.add("@bannedblocks").color(Color.valueOf("f25555")).padBottom(-1).top().row();
|
||||
}else{
|
||||
table.add("@bannedunits").color(Color.valueOf("f25555")).padBottom(-1).top().row();
|
||||
}
|
||||
|
||||
table.image().color(Color.valueOf("f25555")).height(3f).padBottom(5f).fillX().expandX().top().row();
|
||||
table.pane(table2 -> selectedTable = table2).fill().expand().row();
|
||||
table.button("@addall", Icon.add, () -> {
|
||||
contentSet.addAll(filteredContent);
|
||||
rebuildTables();
|
||||
}).disabled(button -> contentSet.toSeq().containsAll(filteredContent)).padTop(10f).bottom().fillX();
|
||||
}).fill().expandY().uniform();
|
||||
|
||||
if(Core.graphics.isPortrait()) cont.row();
|
||||
|
||||
var cell2 = cont.table(table -> {
|
||||
if(type == ContentType.block){
|
||||
table.add("@unbannedblocks").color(Pal.accent).padBottom(-1).top().row();
|
||||
}else{
|
||||
table.add("@unbannedunits").color(Pal.accent).padBottom(-1).top().row();
|
||||
}
|
||||
|
||||
table.image().color(Pal.accent).height(3f).padBottom(5f).fillX().top().row();
|
||||
table.pane(table2 -> deselectedTable = table2).fill().expand().row();
|
||||
table.button("@addall", Icon.add, () -> {
|
||||
contentSet.removeAll(filteredContent);
|
||||
rebuildTables();
|
||||
}).disabled(button -> {
|
||||
Seq<T> array = content.getBy(type);
|
||||
array = array.copy();
|
||||
array.removeAll(contentSet.toSeq());
|
||||
return array.containsAll(filteredContent);
|
||||
}).padTop(10f).bottom().fillX();
|
||||
}).fill().expandY().uniform();
|
||||
if(Core.graphics.isPortrait()){
|
||||
cell2.padTop(10f);
|
||||
}else{
|
||||
cell2.padLeft(10f);
|
||||
}
|
||||
|
||||
rebuildTables();
|
||||
}
|
||||
|
||||
private void rebuildTables(){
|
||||
filteredContent.clear();
|
||||
filteredContent = content.getBy(type);
|
||||
filteredContent = filteredContent.select(pred);
|
||||
|
||||
if(!contentSearch.isEmpty()) filteredContent.removeAll(content -> !content.localizedName.toLowerCase().contains(contentSearch.toLowerCase()));
|
||||
if(type == ContentType.block){
|
||||
filteredContent.removeAll(content -> selectedCategory != null && ((Block)content).category != selectedCategory);
|
||||
}
|
||||
|
||||
rebuildTable(selectedTable, true);
|
||||
rebuildTable(deselectedTable, false);
|
||||
}
|
||||
|
||||
private void rebuildTable(Table table, boolean isSelected){
|
||||
table.clear();
|
||||
|
||||
int cols;
|
||||
if(Core.graphics.isPortrait()){
|
||||
cols = Math.max(4, (int)((Core.graphics.getWidth() / Scl.scl() - 100f) / 50f));
|
||||
}else{
|
||||
cols = Math.max(4, (int)((Core.graphics.getWidth() / Scl.scl() - 300f) / 50f / 2));
|
||||
}
|
||||
|
||||
if((isSelected && contentSet.isEmpty()) || (!isSelected && contentSet.size == content.<T>getBy(type).count(pred))){
|
||||
table.add("@empty").width(50f * cols).padBottom(5f).get().setAlignment(Align.center);
|
||||
}else{
|
||||
Seq<T> array;
|
||||
if(!isSelected){
|
||||
array = content.getBy(type);
|
||||
array = array.copy();
|
||||
array.removeAll(contentSet.toSeq());
|
||||
}else{
|
||||
array = contentSet.toSeq();
|
||||
}
|
||||
array.sort();
|
||||
array.removeAll(content -> !filteredContent.contains(content));
|
||||
|
||||
if(array.isEmpty()){
|
||||
table.add("@empty").width(50f * cols).padBottom(5f).get().setAlignment(Align.center);
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
boolean requiresPad = true;
|
||||
|
||||
for(T content : array){
|
||||
TextureRegion region = content.uiIcon;
|
||||
|
||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearNonei);
|
||||
button.getStyle().imageUp = new TextureRegionDrawable(region);
|
||||
button.resizeImage(8 * 4f);
|
||||
if(isSelected) button.clicked(() -> {
|
||||
contentSet.remove(content);
|
||||
rebuildTables();
|
||||
});
|
||||
else button.clicked(() -> {
|
||||
contentSet.add(content);
|
||||
rebuildTables();
|
||||
});
|
||||
table.add(button).size(50f).tooltip(content.localizedName);
|
||||
|
||||
if(++i % cols == 0){
|
||||
table.row();
|
||||
requiresPad = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(requiresPad){
|
||||
table.add("").padRight(50f * (cols - i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,8 +41,7 @@ public class ForceFieldAbility extends Ability{
|
||||
if(trait.team != paramUnit.team && trait.type.absorbable && Intersector.isInRegularPolygon(paramField.sides, paramUnit.x, paramUnit.y, realRad, paramField.rotation, trait.x(), trait.y()) && paramUnit.shield > 0){
|
||||
trait.absorb();
|
||||
Fx.absorb.at(trait);
|
||||
|
||||
paramUnit.shield -= trait.damage();
|
||||
paramUnit.shield -= trait.type().shieldDamage(trait);
|
||||
paramField.alpha = 1f;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,6 +84,8 @@ public class BulletType extends Content implements Cloneable{
|
||||
public float reloadMultiplier = 1f;
|
||||
/** Multiplier of how much base damage is done to tiles. */
|
||||
public float buildingDamageMultiplier = 1f;
|
||||
/** Multiplier of how much base damage is done to force shields. */
|
||||
public float shieldDamageMultiplier = 1f;
|
||||
/** Recoil from shooter entities. */
|
||||
public float recoil;
|
||||
/** Whether to kill the shooter when this is shot. For suicide bombers. */
|
||||
@@ -569,6 +571,14 @@ public class BulletType extends Content implements Cloneable{
|
||||
}
|
||||
}
|
||||
|
||||
public float buildingDamage(Bullet b){
|
||||
return b.damage() * buildingDamageMultiplier;
|
||||
}
|
||||
|
||||
public float shieldDamage(Bullet b){
|
||||
return b.damage() * shieldDamageMultiplier;
|
||||
}
|
||||
|
||||
public void draw(Bullet b){
|
||||
drawTrail(b);
|
||||
drawParts(b);
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
static final BuildTeamChangeEvent teamChangeEvent = new BuildTeamChangeEvent();
|
||||
static final BuildDamageEvent bulletDamageEvent = new BuildDamageEvent();
|
||||
static int sleepingEntities = 0;
|
||||
|
||||
|
||||
@Import float x, y, health, maxHealth;
|
||||
@Import Team team;
|
||||
@Import boolean dead;
|
||||
@@ -1029,10 +1029,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
int itemSize = allItems.size;
|
||||
Object[] itemArray = allItems.items;
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
|
||||
if(todump == null){
|
||||
if(todump == null){
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
|
||||
for(int ii = 0; ii < itemSize; ii++){
|
||||
if(!items.has(ii)) continue;
|
||||
@@ -1045,16 +1044,22 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
incrementDump(proximity.size);
|
||||
}
|
||||
}else{
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
|
||||
if(other.acceptItem(self(), todump) && canDump(other, todump)){
|
||||
other.handleItem(self(), todump);
|
||||
items.remove(todump, 1);
|
||||
incrementDump(proximity.size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
incrementDump(proximity.size);
|
||||
incrementDump(proximity.size);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1118,7 +1123,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
power.links.clear();
|
||||
}
|
||||
|
||||
|
||||
public boolean conductsTo(Building other){
|
||||
return !block.insulated;
|
||||
}
|
||||
@@ -1320,7 +1325,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(value instanceof Block) type = Block.class;
|
||||
if(value instanceof Liquid) type = Liquid.class;
|
||||
if(value instanceof UnitType) type = UnitType.class;
|
||||
|
||||
|
||||
if(builder != null && builder.isPlayer()){
|
||||
updateLastAccess(builder.getPlayer());
|
||||
}
|
||||
@@ -1640,7 +1645,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public boolean collision(Bullet other){
|
||||
boolean wasDead = health <= 0;
|
||||
|
||||
float damage = other.damage() * other.type().buildingDamageMultiplier;
|
||||
float damage = other.type.buildingDamage(other);
|
||||
if(!other.type.pierceArmor){
|
||||
damage = Damage.applyArmor(damage, block.armor);
|
||||
}
|
||||
@@ -1727,7 +1732,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void updateProximity(){
|
||||
tmpTiles.clear();
|
||||
proximity.clear();
|
||||
|
||||
|
||||
Point2[] nearby = Edges.getEdges(block.size);
|
||||
for(Point2 point : nearby){
|
||||
Building other = world.build(tile.x + point.x, tile.y + point.y);
|
||||
@@ -1983,6 +1988,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public double sense(Content content){
|
||||
if(content instanceof Item i && items != null) return items.get(i);
|
||||
if(content instanceof Liquid l && liquids != null) return liquids.get(l);
|
||||
if(getPayloads() != null){
|
||||
if(content instanceof UnitType u) return getPayloads().get(u);
|
||||
if(content instanceof Block b) return getPayloads().get(b);
|
||||
}
|
||||
return Float.NaN; //invalid sense
|
||||
}
|
||||
|
||||
@@ -2080,6 +2089,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
stopSound();
|
||||
}
|
||||
|
||||
public void stopSound(){
|
||||
if(sound != null){
|
||||
sound.stop();
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
|
||||
}
|
||||
|
||||
public boolean canMine(){
|
||||
return type.mineSpeed > 0 && type.mineTier >= 0;
|
||||
return type.mineSpeed * state.rules.unitMineSpeed(team()) > 0 && type.mineTier >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +89,7 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc{
|
||||
mineTile = null;
|
||||
mineTimer = 0f;
|
||||
}else if(mining() && item != null){
|
||||
mineTimer += Time.delta * type.mineSpeed;
|
||||
mineTimer += Time.delta * type.mineSpeed * state.rules.unitMineSpeed(team());
|
||||
|
||||
if(Mathf.chance(0.06 * Time.delta)){
|
||||
Fx.pulverizeSmall.at(mineTile.worldx() + Mathf.range(tilesize / 2f), mineTile.worldy() + Mathf.range(tilesize / 2f), 0f, item.color);
|
||||
|
||||
@@ -61,6 +61,13 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
for(Payload pay : payloads){
|
||||
pay.remove();
|
||||
}
|
||||
payloads.clear();
|
||||
}
|
||||
|
||||
public void destroy(){
|
||||
if(Vars.state.rules.unitPayloadsExplode) payloads.each(Payload::destroyed);
|
||||
}
|
||||
|
||||
@@ -241,6 +241,8 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
controller instanceof CommandAI command && command.hasCommand() ? ctrlCommand :
|
||||
0;
|
||||
case payloadCount -> ((Object)this) instanceof Payloadc pay ? pay.payloads().size : 0;
|
||||
case totalPayload -> ((Object)this) instanceof Payloadc pay ? pay.payloadUsed() : 0;
|
||||
case payloadCapacity -> type.payloadCapacity / tilePayload;
|
||||
case size -> hitSize / tilesize;
|
||||
case color -> Color.toDoubleBits(team.color.r, team.color.g, team.color.b, 1f);
|
||||
default -> Float.NaN;
|
||||
@@ -265,6 +267,16 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(content == stack().item) return stack().amount;
|
||||
if(content instanceof UnitType u){
|
||||
return ((Object)this) instanceof Payloadc pay ?
|
||||
(pay.payloads().isEmpty() ? 0 :
|
||||
pay.payloads().count(p -> p instanceof UnitPayload up && up.unit.type == u)) : 0;
|
||||
}
|
||||
if(content instanceof Block b){
|
||||
return ((Object)this) instanceof Payloadc pay ?
|
||||
(pay.payloads().isEmpty() ? 0 :
|
||||
pay.payloads().count(p -> p instanceof BuildPayload bp && bp.build.block == b)) : 0;
|
||||
}
|
||||
return Float.NaN;
|
||||
}
|
||||
|
||||
@@ -695,7 +707,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
type.deathExplosionEffect.at(x, y, bounds() / 2f / 8f);
|
||||
}
|
||||
|
||||
float shake = hitSize / 3f;
|
||||
float shake = type.deathShake < 0 ? hitSize / 3f : type.deathShake;
|
||||
|
||||
if(type.createScorch){
|
||||
Effect.scorch(x, y, (int)(hitSize / 5));
|
||||
|
||||
@@ -34,6 +34,8 @@ public class ParticleEffect extends Effect{
|
||||
public float spin = 0f;
|
||||
/** Controls the initial and final sprite sizes. */
|
||||
public float sizeFrom = 2f, sizeTo = 0f;
|
||||
/** Controls the amount of ticks the effect waits before changing size. */
|
||||
public float sizeChangeStart = 0f;
|
||||
/** Whether the rotation adds with the parent */
|
||||
public boolean useRotation = true;
|
||||
/** Rotation offset. */
|
||||
@@ -51,6 +53,7 @@ public class ParticleEffect extends Effect{
|
||||
@Override
|
||||
public void init(){
|
||||
clip = Math.max(clip, length + Math.max(sizeFrom, sizeTo));
|
||||
sizeChangeStart = Mathf.clamp(sizeChangeStart, 0f, lifetime);
|
||||
if(sizeInterp == null) sizeInterp = interp;
|
||||
}
|
||||
|
||||
@@ -62,7 +65,7 @@ public class ParticleEffect extends Effect{
|
||||
int flip = casingFlip ? -Mathf.sign(e.rotation) : 1;
|
||||
float rawfin = e.fin();
|
||||
float fin = e.fin(interp);
|
||||
float rad = sizeInterp.apply(sizeFrom, sizeTo, rawfin) * 2;
|
||||
float rad = sizeInterp.apply(sizeFrom, sizeTo, Mathf.curve(rawfin, sizeChangeStart / lifetime, 1f)) * 2;
|
||||
float ox = e.x + Angles.trnsx(realRotation, offsetX * flip, offsetY), oy = e.y + Angles.trnsy(realRotation, offsetX * flip, offsetY);
|
||||
|
||||
Draw.color(colorFrom, colorTo, fin);
|
||||
|
||||
@@ -8,7 +8,7 @@ import mindustry.entities.*;
|
||||
/** Renders one particle effect repeatedly at specified angle intervals. */
|
||||
public class RadialEffect extends Effect{
|
||||
public Effect effect = Fx.none;
|
||||
public float rotationSpacing = 90f, rotationOffset = 0f;
|
||||
public float rotationSpacing = 90f, rotationOffset = 0f, effectRotationOffset = 0f;
|
||||
public float lengthOffset = 0f;
|
||||
public int amount = 4;
|
||||
|
||||
@@ -16,14 +16,19 @@ public class RadialEffect extends Effect{
|
||||
clip = 100f;
|
||||
}
|
||||
|
||||
public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset){
|
||||
public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset, float effectRotationOffset){
|
||||
this();
|
||||
this.amount = amount;
|
||||
this.effect = effect;
|
||||
this.effectRotationOffset = effectRotationOffset;
|
||||
this.rotationSpacing = spacing;
|
||||
this.lengthOffset = lengthOffset;
|
||||
}
|
||||
|
||||
public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset){
|
||||
this(effect, amount, spacing, lengthOffset, 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(float x, float y, float rotation, Color color, Object data){
|
||||
if(!shouldCreate()) return;
|
||||
@@ -31,7 +36,7 @@ public class RadialEffect extends Effect{
|
||||
rotation += rotationOffset;
|
||||
|
||||
for(int i = 0; i < amount; i++){
|
||||
effect.create(x + Angles.trnsx(rotation, lengthOffset), y + Angles.trnsy(rotation, lengthOffset), rotation, color, data);
|
||||
effect.create(x + Angles.trnsx(rotation, lengthOffset), y + Angles.trnsy(rotation, lengthOffset), rotation + effectRotationOffset, color, data);
|
||||
rotation += rotationSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public class CampaignRules{
|
||||
public boolean showSpawns;
|
||||
public boolean sectorInvasion;
|
||||
public boolean randomWaveAI;
|
||||
public boolean legacyLaunchPads;
|
||||
|
||||
public void apply(Planet planet, Rules rules){
|
||||
rules.staticFog = rules.fog = fog;
|
||||
|
||||
@@ -38,6 +38,8 @@ public class EventType{
|
||||
teamCoreDamage,
|
||||
socketConfigChanged,
|
||||
update,
|
||||
beforeGameUpdate,
|
||||
afterGameUpdate,
|
||||
unitCommandChange,
|
||||
unitCommandPosition,
|
||||
unitCommandAttack,
|
||||
@@ -80,6 +82,8 @@ public class EventType{
|
||||
public static class BlockInfoEvent{}
|
||||
/** Called *after* all content has been initialized. */
|
||||
public static class ContentInitEvent{}
|
||||
/** Called *after* all mod content has been loaded, but before it has been initialized. */
|
||||
public static class ModContentLoadEvent{}
|
||||
/** Called when the client game is first loaded. */
|
||||
public static class ClientLoadEvent{}
|
||||
/** Called after SoundControl registers its music. */
|
||||
|
||||
@@ -84,6 +84,8 @@ public class Rules{
|
||||
public float unitHealthMultiplier = 1f;
|
||||
/** How much damage unit crash damage deals. (Compounds with unitDamageMultiplier) */
|
||||
public float unitCrashDamageMultiplier = 1f;
|
||||
/** How fast units can mine. */
|
||||
public float unitMineSpeedMultiplier = 1f;
|
||||
/** If true, ghost blocks will appear upon destruction, letting builder blocks/units rebuild them. */
|
||||
public boolean ghostBlocks = true;
|
||||
/** Whether to allow units to build with logic. */
|
||||
@@ -241,6 +243,10 @@ public class Rules{
|
||||
return (this.env & env) != 0;
|
||||
}
|
||||
|
||||
public float buildRadius(Team team){
|
||||
return enemyCoreBuildRadius + teams.get(team).extraCoreBuildRadius;
|
||||
}
|
||||
|
||||
public float unitBuildSpeed(Team team){
|
||||
return unitBuildSpeedMultiplier * teams.get(team).unitBuildSpeedMultiplier;
|
||||
}
|
||||
@@ -262,6 +268,10 @@ public class Rules{
|
||||
return unitDamage(team) * unitCrashDamageMultiplier * teams.get(team).unitCrashDamageMultiplier;
|
||||
}
|
||||
|
||||
public float unitMineSpeed(Team team){
|
||||
return unitMineSpeedMultiplier * teams.get(team).unitMineSpeedMultiplier;
|
||||
}
|
||||
|
||||
public float blockHealth(Team team){
|
||||
return blockHealthMultiplier * teams.get(team).blockHealthMultiplier;
|
||||
}
|
||||
@@ -312,6 +322,8 @@ public class Rules{
|
||||
public float unitDamageMultiplier = 1f;
|
||||
/** How much damage unit crash damage deals. (Compounds with unitDamageMultiplier) */
|
||||
public float unitCrashDamageMultiplier = 1f;
|
||||
/** How fast units can mine. */
|
||||
public float unitMineSpeedMultiplier = 1f;
|
||||
/** Multiplier of resources that units take to build. */
|
||||
public float unitCostMultiplier = 1f;
|
||||
/** How much health units start with. */
|
||||
@@ -322,6 +334,9 @@ public class Rules{
|
||||
public float blockDamageMultiplier = 1f;
|
||||
/** Multiplier for building speed. */
|
||||
public float buildSpeedMultiplier = 1f;
|
||||
/** Extra spacing added to the no-build zone around the core. */
|
||||
public float extraCoreBuildRadius = 0f;
|
||||
|
||||
|
||||
//build cost disabled due to technical complexity
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ public class SectorInfo{
|
||||
public ObjectMap<Item, ExportStat> rawProduction = new ObjectMap<>();
|
||||
/** Export statistics. */
|
||||
public ObjectMap<Item, ExportStat> export = new ObjectMap<>();
|
||||
//TODO: there is an obvious exploit with launch pad redirection here; pads can be redirected after leaving a sector, which doesn't update calculations.
|
||||
/** Import statistics, based on what launch pads are actually receiving. */
|
||||
public ObjectMap<Item, ExportStat> imports = new ObjectMap<>();
|
||||
/** Items stored in all cores. */
|
||||
public ItemSeq items = new ItemSeq();
|
||||
/** The best available core type. */
|
||||
@@ -80,14 +83,18 @@ public class SectorInfo{
|
||||
public int waveVersion = -1;
|
||||
/** Whether this sector was indicated to the player or not. */
|
||||
public boolean shown = false;
|
||||
/** Temporary seq for last imported items. Do not use. */
|
||||
public transient ItemSeq lastImported = new ItemSeq();
|
||||
|
||||
/** Special variables for simulation. */
|
||||
public float sumHealth, sumRps, sumDps, bossHealth, bossDps, curEnemyHealth, curEnemyDps;
|
||||
/** Wave where first boss shows up. */
|
||||
public int bossWave = -1;
|
||||
|
||||
public ObjectFloatMap<Item> importCooldownTimers = new ObjectFloatMap<>();
|
||||
public @Nullable transient float[] importRateCache;
|
||||
|
||||
/** Temporary seq for last imported items. Do not use. */
|
||||
public transient ItemSeq lastImported = new ItemSeq();
|
||||
|
||||
/** Counter refresh state. */
|
||||
private transient Interval time = new Interval();
|
||||
/** Core item storage input/output deltas. */
|
||||
@@ -107,12 +114,6 @@ public class SectorInfo{
|
||||
productionDeltas[item.id] += amount;
|
||||
}
|
||||
|
||||
/** @return the real location items go when launched on this sector */
|
||||
public Sector getRealDestination(){
|
||||
//on multiplayer the destination is, by default, the first captured sector (basically random)
|
||||
return !net.client() || destination != null ? destination : state.rules.sector.planet.sectors.find(Sector::hasBase);
|
||||
}
|
||||
|
||||
/** Updates export statistics. */
|
||||
public void handleItemExport(ItemStack stack){
|
||||
handleItemExport(stack.item, stack.amount);
|
||||
@@ -123,10 +124,43 @@ public class SectorInfo{
|
||||
export.get(item, ExportStat::new).counter += amount;
|
||||
}
|
||||
|
||||
/** Updates import statistics. */
|
||||
public void handleItemImport(Item item, int amount){
|
||||
imports.get(item, ExportStat::new).counter += amount;
|
||||
}
|
||||
|
||||
public float getExport(Item item){
|
||||
return export.get(item, ExportStat::new).mean;
|
||||
}
|
||||
|
||||
public boolean hasExport(Item item){
|
||||
var exp = export.get(item);
|
||||
return exp != null && exp.mean > 0f;
|
||||
}
|
||||
|
||||
public void refreshImportRates(Planet planet){
|
||||
if(importRateCache == null || importRateCache.length != content.items().size){
|
||||
importRateCache = new float[content.items().size];
|
||||
}else{
|
||||
Arrays.fill(importRateCache, 0f);
|
||||
}
|
||||
eachImport(planet, sector -> sector.info.export.each((item, stat) -> {
|
||||
importRateCache[item.id] += stat.mean;
|
||||
}));
|
||||
}
|
||||
|
||||
public float[] getImportRates(Planet planet){
|
||||
if(importRateCache == null){
|
||||
refreshImportRates(planet);
|
||||
}
|
||||
return importRateCache;
|
||||
}
|
||||
|
||||
/** @return the import rate of an item as item/second. This is the *raw* max import rate, not what landing pads are actually using. */
|
||||
public float getImportRate(Planet planet, Item item){
|
||||
return getImportRates(planet)[item.id];
|
||||
}
|
||||
|
||||
/** Write contents of meta into main storage. */
|
||||
public void write(){
|
||||
//enable attack mode when there's a core.
|
||||
@@ -221,19 +255,8 @@ public class SectorInfo{
|
||||
//refresh throughput
|
||||
if(time.get(refreshPeriod)){
|
||||
|
||||
//refresh export
|
||||
export.each((item, stat) -> {
|
||||
//initialize stat after loading
|
||||
if(!stat.loaded){
|
||||
stat.means.fill(stat.mean);
|
||||
stat.loaded = true;
|
||||
}
|
||||
|
||||
//add counter, subtract how many items were taken from the core during this time
|
||||
stat.means.add(Math.max(stat.counter, 0));
|
||||
stat.counter = 0;
|
||||
stat.mean = stat.means.rawMean();
|
||||
});
|
||||
updateStats(export);
|
||||
updateStats(imports);
|
||||
|
||||
if(coreDeltas == null) coreDeltas = new int[content.items().size];
|
||||
if(productionDeltas == null) productionDeltas = new int[content.items().size];
|
||||
@@ -250,6 +273,11 @@ public class SectorInfo{
|
||||
//export can, at most, be the raw items being produced from factories + the items being taken from the core
|
||||
export.get(item).mean = Math.min(export.get(item).mean, rawProduction.get(item).mean + Math.max(-production.get(item).mean, 0));
|
||||
}
|
||||
|
||||
if(imports.containsKey(item)){
|
||||
//import can't exceed max import rate
|
||||
imports.get(item).mean = Math.min(imports.get(item).mean, getImportRate(state.getPlanet(), item));
|
||||
}
|
||||
}
|
||||
|
||||
Arrays.fill(coreDeltas, 0);
|
||||
@@ -257,6 +285,20 @@ public class SectorInfo{
|
||||
}
|
||||
}
|
||||
|
||||
void updateStats(ObjectMap<Item, ExportStat> map){
|
||||
map.each((item, stat) -> {
|
||||
//initialize stat after loading
|
||||
if(!stat.loaded){
|
||||
stat.means.fill(stat.mean);
|
||||
stat.loaded = true;
|
||||
}
|
||||
|
||||
stat.means.add(Math.max(stat.counter, 0));
|
||||
stat.counter = 0;
|
||||
stat.mean = stat.means.rawMean();
|
||||
});
|
||||
}
|
||||
|
||||
void updateDelta(Item item, ObjectMap<Item, ExportStat> map, int[] deltas){
|
||||
ExportStat stat = map.get(item, ExportStat::new);
|
||||
if(!stat.loaded){
|
||||
@@ -293,7 +335,7 @@ public class SectorInfo{
|
||||
/** Iterates through every sector this one imports from. */
|
||||
public void eachImport(Planet planet, Cons<Sector> cons){
|
||||
for(Sector sector : planet.sectors){
|
||||
Sector dest = sector.info.getRealDestination();
|
||||
Sector dest = sector.info.destination;
|
||||
if(sector.hasBase() && sector.info != this && dest != null && dest.info == this && sector.info.anyExports()){
|
||||
cons.get(sector);
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import arc.util.*;
|
||||
import mindustry.game.Rules.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
import mindustry.world.modules.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Team implements Comparable<Team>{
|
||||
public class Team implements Comparable<Team>, Senseable{
|
||||
public final int id;
|
||||
public final Color color;
|
||||
public final Color[] palette;
|
||||
@@ -138,7 +139,7 @@ public class Team implements Comparable<Team>{
|
||||
public String localized(){
|
||||
return Core.bundle.get("team." + name + ".name", name);
|
||||
}
|
||||
|
||||
|
||||
public String coloredName(){
|
||||
return emoji + "[#" + color + "]" + localized() + "[]";
|
||||
}
|
||||
@@ -152,4 +153,10 @@ public class Team implements Comparable<Team>{
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.id) return id;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,19 @@ public class Teams{
|
||||
return Geometry.findClosest(x, y, get(team).cores);
|
||||
}
|
||||
|
||||
public boolean anyEnemyCoresWithinBuildRadius(Team team, float x, float y){
|
||||
for(TeamData data : active){
|
||||
if(team != data.team){
|
||||
for(CoreBuild tile : data.cores){
|
||||
if(tile.within(x, y, state.rules.buildRadius(tile.team) + tilesize)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean anyEnemyCoresWithin(Team team, float x, float y, float radius){
|
||||
for(TeamData data : active){
|
||||
if(team != data.team){
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Schematic.*;
|
||||
import mindustry.game.SectorInfo.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.maps.*;
|
||||
@@ -115,6 +116,11 @@ public class Universe{
|
||||
Core.settings.putJson("launch-resources-seq", lastLaunchResources);
|
||||
}
|
||||
|
||||
/** Updates selected loadout for future deployment. Creates an empty schematic with a single core block. */
|
||||
public void updateLoadout(CoreBlock block){
|
||||
updateLoadout(block, new Schematic(Seq.with(new Stile(block, 0, 0, null, (byte)0)), new StringMap(), block.size, block.size));
|
||||
}
|
||||
|
||||
/** Updates selected loadout for future deployment. */
|
||||
public void updateLoadout(CoreBlock block, Schematic schem){
|
||||
Core.settings.put("lastloadout-" + block.name, schem.file == null ? "" : schem.file.nameWithoutExtension());
|
||||
@@ -157,26 +163,33 @@ public class Universe{
|
||||
continue;
|
||||
}
|
||||
|
||||
//first pass: clear import stats
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase() && !sector.isBeingPlayed()){
|
||||
sector.info.lastImported.clear();
|
||||
}
|
||||
//don't simulate the planet if there is an in-progress mission on that planet
|
||||
if(!planet.allowWaveSimulation && planet.sectors.contains(s -> s.hasBase() && !s.isBeingPlayed() && s.isAttacked())){
|
||||
continue;
|
||||
}
|
||||
|
||||
//second pass: update export & import statistics
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase() && !sector.isBeingPlayed()){
|
||||
if(planet.campaignRules.legacyLaunchPads){
|
||||
//first pass: clear import stats
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase() && !sector.isBeingPlayed()){
|
||||
sector.info.lastImported.clear();
|
||||
}
|
||||
}
|
||||
|
||||
//export to another sector
|
||||
if(sector.info.destination != null){
|
||||
Sector to = sector.info.destination;
|
||||
if(to.hasBase() && to.planet == planet){
|
||||
ItemSeq items = new ItemSeq();
|
||||
//calculated exported items to this sector
|
||||
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * sector.getProductionScale())));
|
||||
to.addItems(items);
|
||||
to.info.lastImported.add(items);
|
||||
//second pass: update export & import statistics
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase() && !sector.isBeingPlayed()){
|
||||
|
||||
//export to another sector
|
||||
if(sector.info.destination != null){
|
||||
Sector to = sector.info.destination;
|
||||
if(to.hasBase() && to.planet == planet){
|
||||
ItemSeq items = new ItemSeq();
|
||||
//calculated exported items to this sector
|
||||
sector.info.export.each((item, stat) -> items.add(item, (int)(stat.mean * newSecondsPassed * sector.getProductionScale())));
|
||||
to.addItems(items);
|
||||
to.info.lastImported.add(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +198,9 @@ public class Universe{
|
||||
//third pass: everything else
|
||||
for(Sector sector : planet.sectors){
|
||||
if(sector.hasBase()){
|
||||
if(sector.info.importRateCache != null){
|
||||
sector.info.refreshImportRates(planet);
|
||||
}
|
||||
|
||||
//if it is being attacked, capture time is 0; otherwise, increment the timer
|
||||
if(sector.isAttacked()){
|
||||
@@ -196,6 +212,8 @@ public class Universe{
|
||||
//increment seconds passed for this sector by the time that just passed with this turn
|
||||
if(!sector.isBeingPlayed()){
|
||||
|
||||
//TODO: if a planet has sectors under attack and simulation is OFF, just don't simulate it
|
||||
|
||||
//increment time if attacked
|
||||
if(sector.isAttacked()){
|
||||
sector.info.secondsPassed += turnDuration/60f;
|
||||
@@ -238,12 +256,15 @@ public class Universe{
|
||||
//add production, making sure that it's capped
|
||||
sector.info.production.each((item, stat) -> sector.info.items.add(item, Math.min((int)(stat.mean * newSecondsPassed * scl), sector.info.storageCapacity - sector.info.items.get(item))));
|
||||
|
||||
sector.info.export.each((item, stat) -> {
|
||||
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0 && stat.mean > 0){
|
||||
//cap export by import when production is negative.
|
||||
stat.mean = Math.min(sector.info.lastImported.get(item) / (float)newSecondsPassed, stat.mean);
|
||||
}
|
||||
});
|
||||
if(planet.campaignRules.legacyLaunchPads){
|
||||
sector.info.export.each((item, stat) -> {
|
||||
if(sector.info.items.get(item) <= 0 && sector.info.production.get(item, ExportStat::new).mean < 0 && stat.mean > 0){
|
||||
//cap export by import when production is negative.
|
||||
//TODO remove
|
||||
stat.mean = Math.min(sector.info.lastImported.get(item) / (float)newSecondsPassed, stat.mean);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//prevent negative values with unloaders
|
||||
sector.info.items.checkNegative();
|
||||
|
||||
@@ -178,11 +178,12 @@ public class OverlayRenderer{
|
||||
}else{
|
||||
state.teams.eachEnemyCore(player.team(), core -> {
|
||||
//it must be clear that there is a core here.
|
||||
if(/*core.wasVisible && */Core.camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(core.x, core.y, state.rules.enemyCoreBuildRadius * 2f))){
|
||||
float br = state.rules.buildRadius(core.team);
|
||||
if(/*core.wasVisible && */Core.camera.bounds(Tmp.r1).overlaps(Tmp.r2.setCentered(core.x, core.y, br * 2f))){
|
||||
Draw.color(Color.darkGray);
|
||||
Lines.circle(core.x, core.y - 2, state.rules.enemyCoreBuildRadius);
|
||||
Lines.circle(core.x, core.y - 2,br);
|
||||
Draw.color(Pal.accent, core.team.color, 0.5f + Mathf.absin(Time.time, 10f, 0.5f));
|
||||
Lines.circle(core.x, core.y, state.rules.enemyCoreBuildRadius);
|
||||
Lines.circle(core.x, core.y, br);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.graphics.*;
|
||||
public class Pal{
|
||||
public static Color
|
||||
|
||||
water = Color.valueOf("596ab8"),
|
||||
darkOutline = Color.valueOf("2d2f39"),
|
||||
thoriumPink = Color.valueOf("f9a3c7"),
|
||||
coalBlack = Color.valueOf("272727"),
|
||||
@@ -107,7 +108,7 @@ public class Pal{
|
||||
redderDust = Color.valueOf("ff7b69"),
|
||||
|
||||
plasticSmoke = Color.valueOf("f1e479"),
|
||||
|
||||
|
||||
adminChat = Color.valueOf("ff4000"),
|
||||
|
||||
neoplasmOutline = Color.valueOf("2e191d"),
|
||||
|
||||
@@ -27,7 +27,7 @@ public class HexSkyMesh extends PlanetMesh{
|
||||
|
||||
@Override
|
||||
public boolean skip(Vec3 position){
|
||||
return Simplex.noise3d(planet.id + seed, octaves, persistence, scl, position.x, position.y * 3f, position.z) >= thresh;
|
||||
return Simplex.noise3d(7 + seed, octaves, persistence, scl, position.x, position.y * 3f, position.z) >= thresh;
|
||||
}
|
||||
}, divisions, false, planet.radius, radius), Shaders.clouds);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public class NoiseMesh extends HexMesh{
|
||||
this.mesh = MeshBuilder.buildHex(new HexMesher(){
|
||||
@Override
|
||||
public float getHeight(Vec3 position){
|
||||
return Simplex.noise3d(planet.id + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag;
|
||||
return Simplex.noise3d(7 + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -31,12 +31,12 @@ public class NoiseMesh extends HexMesh{
|
||||
this.mesh = MeshBuilder.buildHex(new HexMesher(){
|
||||
@Override
|
||||
public float getHeight(Vec3 position){
|
||||
return Simplex.noise3d(planet.id + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag;
|
||||
return Simplex.noise3d(7 + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor(Vec3 position){
|
||||
return Simplex.noise3d(planet.id + seed + 1, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1;
|
||||
return Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1;
|
||||
}
|
||||
}, divisions, false, radius, 0.2f);
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ public class DesktopInput extends InputHandler{
|
||||
}
|
||||
|
||||
//validate commanding units
|
||||
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid());
|
||||
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid() || u.team != player.team());
|
||||
|
||||
if(commandMode && !scene.hasField() && !scene.hasDialog()){
|
||||
if(input.keyTap(Binding.select_all_units)){
|
||||
|
||||
@@ -1096,10 +1096,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
//draw command overlay UI
|
||||
for(Unit unit : selectedUnits){
|
||||
if(unit.isFlying() != flying) continue;
|
||||
|
||||
CommandAI ai = unit.command();
|
||||
Position lastPos = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
|
||||
if(flying && ai.attackTarget != null && ai.currentCommand().drawTarget){
|
||||
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||
}
|
||||
|
||||
if(unit.isFlying() != flying) continue;
|
||||
|
||||
//draw target line
|
||||
if(ai.targetPos != null && ai.currentCommand().drawTarget){
|
||||
Position lineDest = ai.attackTarget != null ? ai.attackTarget : ai.targetPos;
|
||||
@@ -1135,9 +1141,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
//Lines.poly(unit.x, unit.y, sides, rad + 1.5f);
|
||||
Draw.reset();
|
||||
|
||||
if(ai.attackTarget != null && ai.currentCommand().drawTarget){
|
||||
Drawf.target(ai.attackTarget.getX(), ai.attackTarget.getY(), 6f, Pal.remove);
|
||||
}
|
||||
|
||||
|
||||
if(lastPos == null){
|
||||
lastPos = unit;
|
||||
|
||||
@@ -762,7 +762,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}
|
||||
|
||||
//validate commanding units
|
||||
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid());
|
||||
selectedUnits.removeAll(u -> !u.isCommandable() || !u.isValid() || u.team != player.team());
|
||||
|
||||
if(!commandMode){
|
||||
commandBuildings.clear();
|
||||
|
||||
@@ -955,6 +955,7 @@ public class TypeIO{
|
||||
public static void writeTraceInfo(Writes write, TraceInfo trace){
|
||||
writeString(write, trace.ip);
|
||||
writeString(write, trace.uuid);
|
||||
writeString(write, trace.locale);
|
||||
write.b(trace.modded ? (byte)1 : 0);
|
||||
write.b(trace.mobile ? (byte)1 : 0);
|
||||
write.i(trace.timesJoined);
|
||||
@@ -965,7 +966,7 @@ public class TypeIO{
|
||||
}
|
||||
|
||||
public static TraceInfo readTraceInfo(Reads read){
|
||||
return new TraceInfo(readString(read), readString(read), read.b() == 1, read.b() == 1, read.i(), read.i(), readStrings(read), readStrings(read));
|
||||
return new TraceInfo(readString(read), readString(read), readString(read), read.b() == 1, read.b() == 1, read.i(), read.i(), readStrings(read), readStrings(read));
|
||||
}
|
||||
|
||||
public static void writeStrings(Writes write, String[] strings, int maxLen){
|
||||
|
||||
@@ -22,7 +22,7 @@ import static mindustry.Vars.*;
|
||||
/** Stores global logic variables for logic processors. */
|
||||
public class GlobalVars{
|
||||
public static final int ctrlProcessor = 1, ctrlPlayer = 2, ctrlCommand = 3;
|
||||
public static final ContentType[] lookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid};
|
||||
public static final ContentType[] lookableContent = {ContentType.block, ContentType.unit, ContentType.item, ContentType.liquid, ContentType.team};
|
||||
/** Global random state. */
|
||||
public static final Rand rand = new Rand();
|
||||
|
||||
@@ -220,8 +220,13 @@ public class GlobalVars{
|
||||
return varEntries;
|
||||
}
|
||||
|
||||
/** @return a piece of content based on its logic ID. This is not equivalent to content ID. */
|
||||
public @Nullable Content lookupContent(ContentType type, int id){
|
||||
/** @return a piece of content based on its logic ID. This is not equivalent to content ID. In the case of teams, the return value may not be Content. */
|
||||
public @Nullable Object lookupContent(ContentType type, int id){
|
||||
//teams are a special case; they are not technically content, but can be looked up
|
||||
if(type == ContentType.team){
|
||||
return id >= 0 && id < 256 ? Team.all[id] : null;
|
||||
}
|
||||
|
||||
var arr = logicIdToContent[type.ordinal()];
|
||||
return arr != null && id >= 0 && id < arr.length ? arr[id] : null;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ public enum LAccess{
|
||||
name,
|
||||
payloadCount,
|
||||
payloadType,
|
||||
totalPayload,
|
||||
payloadCapacity,
|
||||
id,
|
||||
|
||||
//values with parameters are considered controllable
|
||||
|
||||
@@ -45,9 +45,9 @@ public class LExecutor{
|
||||
public LInstruction[] instructions = {};
|
||||
/** Non-constant variables used for network sync */
|
||||
public LVar[] vars = {};
|
||||
|
||||
|
||||
public LVar counter, unit, thisv, ipt;
|
||||
|
||||
|
||||
public int[] binds;
|
||||
public boolean yield;
|
||||
|
||||
@@ -226,7 +226,7 @@ public class LExecutor{
|
||||
cache.found = false;
|
||||
outFound.setnum(0);
|
||||
}
|
||||
|
||||
|
||||
if(res != null && res.build != null &&
|
||||
(unit.within(res.build.x, res.build.y, Math.max(unit.range(), buildingRange)) || res.build.team == exec.team)){
|
||||
cache.build = res.build;
|
||||
@@ -988,6 +988,29 @@ public class LExecutor{
|
||||
}
|
||||
}
|
||||
|
||||
public static class PrintCharI implements LInstruction{
|
||||
public LVar value;
|
||||
|
||||
public PrintCharI(LVar value){
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
PrintCharI(){}
|
||||
|
||||
@Override
|
||||
public void run(LExecutor exec){
|
||||
|
||||
if(exec.textBuffer.length() >= maxTextBuffer) return;
|
||||
if(value.isobj){
|
||||
if(!(value.objval instanceof UnlockableContent cont)) return;
|
||||
exec.textBuffer.append((char)cont.emojiChar());
|
||||
return;
|
||||
}
|
||||
|
||||
exec.textBuffer.append((char)Math.floor(value.numval));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FormatI implements LInstruction{
|
||||
public LVar value;
|
||||
|
||||
@@ -1489,6 +1512,7 @@ public class LExecutor{
|
||||
case dropZoneRadius -> state.rules.dropZoneRadius = value.numf() * 8f;
|
||||
case unitCap -> state.rules.unitCap = Math.max(value.numi(), 0);
|
||||
case lighting -> state.rules.lighting = value.bool();
|
||||
case canGameOver -> state.rules.canGameOver = value.bool();
|
||||
case mapArea -> {
|
||||
int x = p1.numi(), y = p2.numi(), w = p3.numi(), h = p4.numi();
|
||||
if(!checkMapArea(x, y, w, h, false)){
|
||||
@@ -1515,7 +1539,7 @@ public class LExecutor{
|
||||
state.rules.bannedUnits.remove(u);
|
||||
}
|
||||
}
|
||||
case unitHealth, unitBuildSpeed, unitCost, unitDamage, blockHealth, blockDamage, buildSpeed, rtsMinSquad, rtsMinWeight -> {
|
||||
case unitHealth, unitBuildSpeed, unitMineSpeed, unitCost, unitDamage, blockHealth, blockDamage, buildSpeed, rtsMinSquad, rtsMinWeight -> {
|
||||
Team team = p1.team();
|
||||
if(team != null){
|
||||
float num = value.numf();
|
||||
@@ -1523,6 +1547,7 @@ public class LExecutor{
|
||||
case buildSpeed -> team.rules().buildSpeedMultiplier = Mathf.clamp(num, 0.001f, 50f);
|
||||
case unitHealth -> team.rules().unitHealthMultiplier = Math.max(num, 0.001f);
|
||||
case unitBuildSpeed -> team.rules().unitBuildSpeedMultiplier = Mathf.clamp(num, 0f, 50f);
|
||||
case unitMineSpeed -> team.rules().unitMineSpeedMultiplier = Math.max(num, 0f);
|
||||
case unitCost -> team.rules().unitCostMultiplier = Math.max(num, 0f);
|
||||
case unitDamage -> team.rules().unitDamageMultiplier = Math.max(num, 0f);
|
||||
case blockHealth -> team.rules().blockHealthMultiplier = Math.max(num, 0.001f);
|
||||
@@ -1939,7 +1964,7 @@ public class LExecutor{
|
||||
public void run(LExecutor exec){
|
||||
Sound sound = Sounds.getSound(id.numi());
|
||||
if(sound == null || sound == Sounds.swish) sound = Sounds.none; //no.
|
||||
|
||||
|
||||
if(positional){
|
||||
sound.at(World.unconv(x.numf()), World.unconv(y.numf()), pitch.numf(), Math.min(volume.numf(), 2f), limit.bool());
|
||||
}else{
|
||||
|
||||
@@ -19,6 +19,7 @@ import mindustry.logic.LExecutor.*;
|
||||
import mindustry.logic.LogicFx.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -312,6 +313,46 @@ public class LStatements{
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("printchar")
|
||||
public static class PrintCharStatement extends LStatement{
|
||||
public String value = "65";
|
||||
|
||||
@Override
|
||||
public void build(Table table){
|
||||
table.add(" char ");
|
||||
TextField field = field(table, value, str -> value = str).get();
|
||||
table.button(b -> {
|
||||
b.image(Icon.pencilSmall);
|
||||
b.clicked(() -> showSelectTable(b, (t, hide) -> {
|
||||
t.row();
|
||||
t.table(i -> {
|
||||
i.left();
|
||||
int c = 0;
|
||||
for(char j = 32; j < 127; j++){
|
||||
final int chr = j;
|
||||
i.button(String.valueOf(j), Styles.flatt, () -> {
|
||||
value = Integer.toString(chr);
|
||||
field.setText(value);
|
||||
hide.run();
|
||||
}).size(32f);
|
||||
if(++c % 8 == 0) i.row();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(40f).padLeft(-2).color(table.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LInstruction build(LAssembler builder){
|
||||
return new PrintCharI(builder.var(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LCategory category(){
|
||||
return LCategory.io;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterStatement("format")
|
||||
public static class FormatStatement extends LStatement{
|
||||
public String value = "\"frog\"";
|
||||
@@ -574,6 +615,29 @@ public class LStatements{
|
||||
if(++c % 6 == 0) i.row();
|
||||
}
|
||||
}),
|
||||
new Table(i -> {
|
||||
i.left();
|
||||
int c = 0;
|
||||
for(UnitType item : Vars.content.units()){
|
||||
if(!item.unlockedNow() || item.hidden) continue;
|
||||
i.button(new TextureRegionDrawable(item.uiIcon), Styles.flati, iconSmall, () -> {
|
||||
stype("@" + item.name);
|
||||
hide.run();
|
||||
}).size(40f);
|
||||
|
||||
if(++c % 6 == 0) i.row();
|
||||
}
|
||||
|
||||
for(Block item : Vars.content.blocks()){
|
||||
if(!item.unlockedNow() || item.isHidden()) continue;
|
||||
i.button(new TextureRegionDrawable(item.uiIcon), Styles.flati, iconSmall, () -> {
|
||||
stype("@" + item.name);
|
||||
hide.run();
|
||||
}).size(40f);
|
||||
|
||||
if(++c % 6 == 0) i.row();
|
||||
}
|
||||
}),
|
||||
//sensors
|
||||
new Table(i -> {
|
||||
for(LAccess sensor : LAccess.senseable){
|
||||
@@ -585,7 +649,7 @@ public class LStatements{
|
||||
})
|
||||
};
|
||||
|
||||
Drawable[] icons = {Icon.box, Icon.liquid, Icon.tree};
|
||||
Drawable[] icons = {Icon.box, Icon.liquid, Icon.units, Icon.tree};
|
||||
Stack stack = new Stack(tables[selected]);
|
||||
ButtonGroup<Button> group = new ButtonGroup<>();
|
||||
|
||||
@@ -603,7 +667,7 @@ public class LStatements{
|
||||
}).height(50f).growX().checked(selected == fi).group(group);
|
||||
}
|
||||
t.row();
|
||||
t.add(stack).colspan(3).width(240f).left();
|
||||
t.add(stack).colspan(4).width(240f).left();
|
||||
}));
|
||||
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
|
||||
|
||||
@@ -1493,11 +1557,11 @@ public class LStatements{
|
||||
table.add("natural ");
|
||||
fields(table, natural, str -> natural = str);
|
||||
|
||||
table.add("x ").visible(() -> natural.equals("false"));
|
||||
fields(table, x, str -> x = str).visible(() -> natural.equals("false"));
|
||||
table.add("x ").visible(() -> !natural.equals("true"));
|
||||
fields(table, x, str -> x = str).visible(() -> !natural.equals("true"));
|
||||
|
||||
table.add(" y ").visible(() -> natural.equals("false"));
|
||||
fields(table, y, str -> y = str).visible(() -> natural.equals("false"));
|
||||
table.add(" y ").visible(() -> !natural.equals("true"));
|
||||
fields(table, y, str -> y = str).visible(() -> !natural.equals("true"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1547,7 +1611,7 @@ public class LStatements{
|
||||
fields(table, "w", p3, s -> p3 = s);
|
||||
fields(table, "h", p4, s -> p4 = s);
|
||||
}
|
||||
case buildSpeed, unitHealth, unitBuildSpeed, unitCost, unitDamage, blockHealth, blockDamage, rtsMinSquad, rtsMinWeight -> {
|
||||
case buildSpeed, unitHealth, unitBuildSpeed, unitMineSpeed, unitCost, unitDamage, blockHealth, blockDamage, rtsMinSquad, rtsMinWeight -> {
|
||||
if(p1.equals("0")){
|
||||
p1 = "@sharded";
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public enum LogicRule{
|
||||
unitCap,
|
||||
mapArea,
|
||||
lighting,
|
||||
canGameOver,
|
||||
ambientLight,
|
||||
solarMultiplier,
|
||||
dragMultiplier,
|
||||
@@ -23,6 +24,7 @@ public enum LogicRule{
|
||||
buildSpeed,
|
||||
unitHealth,
|
||||
unitBuildSpeed,
|
||||
unitMineSpeed,
|
||||
unitCost,
|
||||
unitDamage,
|
||||
blockHealth,
|
||||
|
||||
@@ -175,9 +175,13 @@ public class BaseGenerator{
|
||||
if(tiles == null) return;
|
||||
|
||||
for(Tile tile : tiles){
|
||||
if(tile.isCenter() && tile.block() instanceof PowerNode && tile.team() == state.rules.waveTeam){
|
||||
tile.build.configureAny(new Point2[0]);
|
||||
tile.build.placed();
|
||||
if(tile.isCenter() && tile.team() == state.rules.waveTeam){
|
||||
if(tile.block() instanceof PowerNode){
|
||||
tile.build.configureAny(new Point2[0]);
|
||||
tile.build.placed();
|
||||
}else if(tile.block() instanceof Battery){
|
||||
tile.build.power.status = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +216,7 @@ public class BaseGenerator{
|
||||
if(!insanity){
|
||||
for(Stile tile : result.tiles){
|
||||
int realX = tile.x + cx, realY = tile.y + cy;
|
||||
if(isTaken(tile.block, realX, realY)){
|
||||
if(isTaken(tile.block, realX, realY) || (tile.block == Blocks.oilExtractor && tile.block.sumAttribute(Attribute.oil, realX, realY) <= 0.001f)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe
|
||||
if(sector.planet.getSector(other).id == sector.planet.startSector){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(sector.planet.getSector(other).generateEnemyBase){
|
||||
any = false;
|
||||
break;
|
||||
@@ -57,6 +57,11 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe
|
||||
return sector.planet.allowLaunchToNumbered && (sector.hasBase() || sector.near().contains(Sector::hasBase));
|
||||
}
|
||||
|
||||
/** @return whether to allow landing on the specified procedural sector */
|
||||
public boolean allowAcceleratorLanding(Sector sector){
|
||||
return sector.planet.allowLaunchToNumbered;
|
||||
}
|
||||
|
||||
public void addWeather(Sector sector, Rules rules){
|
||||
|
||||
//apply weather based on terrain
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package mindustry.mod;
|
||||
|
||||
import arc.*;
|
||||
import arc.assets.*;
|
||||
import arc.assets.loaders.MusicLoader.*;
|
||||
import arc.assets.loaders.SoundLoader.*;
|
||||
import arc.audio.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
@@ -642,6 +639,19 @@ public class ContentParser{
|
||||
value.remove("sector");
|
||||
value.remove("planet");
|
||||
|
||||
if(value.has("rules")){
|
||||
JsonValue r = value.remove("rules");
|
||||
if(!r.isObject()) throw new RuntimeException("Rules must be an object!");
|
||||
out.rules = rules -> {
|
||||
try{
|
||||
//Use standard JSON, this is not content-parser relevant
|
||||
JsonIO.json.readFields(rules, r);
|
||||
}catch(Throwable e){ //Try not to crash here, as that would be catastrophic and confusing
|
||||
Log.err(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
readFields(out, value);
|
||||
});
|
||||
return out;
|
||||
@@ -698,7 +708,7 @@ public class ContentParser{
|
||||
throw new RuntimeException("Team field missing.");
|
||||
}
|
||||
value.remove("team");
|
||||
|
||||
|
||||
if(locate(ContentType.team, name) != null){
|
||||
entry = locate(ContentType.team, name);
|
||||
readBundle(ContentType.team, name, value);
|
||||
@@ -1214,6 +1224,7 @@ public class ContentParser{
|
||||
}
|
||||
//reparent the node
|
||||
node.parent = parent;
|
||||
node.planet = parent.planet;
|
||||
}
|
||||
}else{
|
||||
Log.warn(unlock.name + " is not a root node, and does not have a `parent: ` property. Ignoring.");
|
||||
|
||||
@@ -432,7 +432,9 @@ public class Mods implements Loadable{
|
||||
}
|
||||
mods.remove(mod);
|
||||
mod.dispose();
|
||||
requiresReload = true;
|
||||
if(mod.state != ModState.disabled){
|
||||
requiresReload = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Scripts getScripts(){
|
||||
@@ -474,13 +476,7 @@ public class Mods implements Loadable{
|
||||
ModMeta meta = null;
|
||||
|
||||
try{
|
||||
Fi zip = file.isDirectory() ? file : new ZipFi(file);
|
||||
|
||||
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
|
||||
zip = zip.list()[0];
|
||||
}
|
||||
|
||||
meta = findMeta(zip);
|
||||
meta = findMeta(resolveRoot(file.isDirectory() ? file : new ZipFi(file)));
|
||||
}catch(Throwable ignored){
|
||||
}
|
||||
|
||||
@@ -505,7 +501,7 @@ public class Mods implements Loadable{
|
||||
if(steam) mod.addSteamID(file.name());
|
||||
}catch(Throwable e){
|
||||
if(e instanceof ClassNotFoundException && e.getMessage().contains("mindustry.plugin.Plugin")){
|
||||
Log.info("Plugin '@' is outdated and needs to be ported to 6.0! Update its main class to inherit from 'mindustry.mod.Plugin'. See https://mindustrygame.github.io/wiki/modding/6-migrationv6/", file.name());
|
||||
Log.warn("Plugin '@' is outdated and needs to be ported to v7! Update its main class to inherit from 'mindustry.mod.Plugin'.", file.name());
|
||||
}else if(steam){
|
||||
Log.err("Failed to load mod workshop file @. Skipping.", file);
|
||||
Log.err(e);
|
||||
@@ -795,6 +791,8 @@ public class Mods implements Loadable{
|
||||
|
||||
//this finishes parsing content fields
|
||||
parser.finishParsing();
|
||||
|
||||
Events.fire(new ModContentLoadEvent());
|
||||
}
|
||||
|
||||
public void handleContentError(Content content, Throwable error){
|
||||
@@ -931,10 +929,10 @@ public class Mods implements Loadable{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Loads a mod file+meta, but does not add it to the list.
|
||||
* Note that directories can be loaded as mods. */
|
||||
private LoadedMod loadMod(Fi sourceFile) throws Exception{
|
||||
return loadMod(sourceFile, false, true);
|
||||
private Fi resolveRoot(Fi fi){
|
||||
if(OS.isMac && (!(fi instanceof ZipFi))) fi.child(".DS_Store").delete();
|
||||
Fi[] files = fi.list();
|
||||
return files.length == 1 && files[0].isDirectory() ? files[0] : fi;
|
||||
}
|
||||
|
||||
/** Loads a mod file+meta, but does not add it to the list.
|
||||
@@ -945,10 +943,7 @@ public class Mods implements Loadable{
|
||||
ZipFi rootZip = null;
|
||||
|
||||
try{
|
||||
Fi zip = sourceFile.isDirectory() ? sourceFile : (rootZip = new ZipFi(sourceFile));
|
||||
if(zip.list().length == 1 && zip.list()[0].isDirectory()){
|
||||
zip = zip.list()[0];
|
||||
}
|
||||
Fi zip = resolveRoot(sourceFile.isDirectory() ? sourceFile : (rootZip = new ZipFi(sourceFile)));
|
||||
|
||||
ModMeta meta = findMeta(zip);
|
||||
|
||||
|
||||
@@ -628,14 +628,15 @@ public class Administration{
|
||||
}
|
||||
|
||||
public static class TraceInfo{
|
||||
public String ip, uuid;
|
||||
public String ip, uuid, locale;
|
||||
public boolean modded, mobile;
|
||||
public int timesJoined, timesKicked;
|
||||
public String[] ips, names;
|
||||
|
||||
public TraceInfo(String ip, String uuid, boolean modded, boolean mobile, int timesJoined, int timesKicked, String[] ips, String[] names){
|
||||
public TraceInfo(String ip, String uuid, String locale, boolean modded, boolean mobile, int timesJoined, int timesKicked, String[] ips, String[] names){
|
||||
this.ip = ip;
|
||||
this.uuid = uuid;
|
||||
this.locale = locale;
|
||||
this.modded = modded;
|
||||
this.mobile = mobile;
|
||||
this.timesJoined = timesJoined;
|
||||
|
||||
@@ -306,14 +306,17 @@ public class Net{
|
||||
* Call to handle a packet being received for the server.
|
||||
*/
|
||||
public void handleServerReceived(NetConnection connection, Packet object){
|
||||
object.handled();
|
||||
|
||||
try{
|
||||
//handle object normally
|
||||
if(serverListeners.get(object.getClass()) != null){
|
||||
serverListeners.get(object.getClass()).get(connection, object);
|
||||
}else{
|
||||
object.handleServer(connection);
|
||||
if(connection.hasConnected || object.getPriority() == Packet.priorityHigh){
|
||||
object.handled();
|
||||
|
||||
//handle object normally
|
||||
if(serverListeners.get(object.getClass()) != null){
|
||||
serverListeners.get(object.getClass()).get(connection, object);
|
||||
}else{
|
||||
object.handleServer(connection);
|
||||
}
|
||||
}
|
||||
}catch(ValidateException e){
|
||||
//ignore invalid actions
|
||||
|
||||
@@ -157,5 +157,10 @@ public class Packets{
|
||||
mods.add(TypeIO.readString(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority(){
|
||||
return priorityHigh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ public class Planet extends UnlockableContent{
|
||||
public boolean allowWaveSimulation = false;
|
||||
/** Whether to simulate sector invasions from enemy bases. */
|
||||
public boolean allowSectorInvasion = false;
|
||||
/** If true, legacy launch pads can be enabled. */
|
||||
public boolean allowLegacyLaunchPads = false;
|
||||
/** If true, sectors saves are cleared when lost. */
|
||||
public boolean clearSectorOnLose = false;
|
||||
/** Multiplier for enemy rebuild speeds; only applied in campaign (not standard rules) */
|
||||
@@ -144,8 +146,10 @@ public class Planet extends UnlockableContent{
|
||||
public Seq<Planet> children = new Seq<>();
|
||||
/** Default root node shown when the tech tree is opened here. */
|
||||
public @Nullable TechNode techTree;
|
||||
/** TODO remove? Planets that can be launched to from this one. Made mutual in init(). */
|
||||
/** Planets that can be launched to from this one. */
|
||||
public Seq<Planet> launchCandidates = new Seq<>();
|
||||
/** Whether interplanetary accelerators can launch to 'any' procedural sector on this planet's surface. */
|
||||
public boolean allowSelfSectorLaunch;
|
||||
/** If true, all content in this planet's tech tree will be assigned this planet in their shownPlanets. */
|
||||
public boolean autoAssignPlanet = true;
|
||||
/** Content (usually planet-specific) that is unlocked upon landing here. */
|
||||
@@ -383,18 +387,6 @@ public class Planet extends UnlockableContent{
|
||||
updateBaseCoverage();
|
||||
}
|
||||
|
||||
//make planet launch candidates mutual.
|
||||
var candidates = launchCandidates.copy();
|
||||
|
||||
for(Planet planet : content.planets()){
|
||||
if(planet.launchCandidates.contains(this)){
|
||||
candidates.addUnique(planet);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO currently, mutual launch candidates are simply a nuisance.
|
||||
//launchCandidates = candidates;
|
||||
|
||||
clipRadius = Math.max(clipRadius, radius + atmosphereRadOut + 0.5f);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ public class SectorPreset extends UnlockableContent{
|
||||
this.planet = planet;
|
||||
sector %= planet.sectors.size;
|
||||
this.sector = planet.sectors.get(sector);
|
||||
inlineDescription = false;
|
||||
|
||||
planet.preset(sector, this);
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ public class StatusEffect extends UnlockableContent{
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
if(damageMultiplier != 1) stats.addPercent(Stat.damageMultiplier, damageMultiplier);
|
||||
if(healthMultiplier != 1) stats.addPercent(Stat.healthMultiplier, healthMultiplier);
|
||||
if(speedMultiplier != 1) stats.addPercent(Stat.speedMultiplier, speedMultiplier);
|
||||
if(reloadMultiplier != 1) stats.addPercent(Stat.reloadMultiplier, reloadMultiplier);
|
||||
if(buildSpeedMultiplier != 1) stats.addPercent(Stat.buildSpeedMultiplier, buildSpeedMultiplier);
|
||||
if(damageMultiplier != 1) stats.addMultModifier(Stat.damageMultiplier, damageMultiplier);
|
||||
if(healthMultiplier != 1) stats.addMultModifier(Stat.healthMultiplier, healthMultiplier);
|
||||
if(speedMultiplier != 1) stats.addMultModifier(Stat.speedMultiplier, speedMultiplier);
|
||||
if(reloadMultiplier != 1) stats.addMultModifier(Stat.reloadMultiplier, reloadMultiplier);
|
||||
if(buildSpeedMultiplier != 1) stats.addMultModifier(Stat.buildSpeedMultiplier, buildSpeedMultiplier);
|
||||
if(damage > 0) stats.add(Stat.damage, damage * 60f, StatUnit.perSecond);
|
||||
if(damage < 0) stats.add(Stat.healing, -damage * 60f, StatUnit.perSecond);
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
accel = 0.5f,
|
||||
/** size of one side of the hitbox square */
|
||||
hitSize = 6f,
|
||||
/** shake on unit death */
|
||||
deathShake = -1f,
|
||||
/** shake on each step for leg/mech units */
|
||||
stepShake = -1f,
|
||||
/** ripple / dust size for legged units */
|
||||
@@ -103,6 +105,8 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
/** for ground units, the layer upon which this unit is drawn */
|
||||
groundLayer = Layer.groundUnit,
|
||||
/** For units that fly, the layer upon which this unit is drawn. If no value is set, defaults to Layer.flyingUnitLow or Layer.flyingUnit depending on lowAltitude */
|
||||
flyingLayer = -1,
|
||||
/** Payload capacity of this unit in world units^2 */
|
||||
payloadCapacity = 8,
|
||||
/** building speed multiplier; <0 to disable. */
|
||||
@@ -439,7 +443,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
public TextureRegion baseRegion, legRegion, region, previewRegion, shadowRegion, cellRegion, itemCircleRegion,
|
||||
softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion, treadRegion,
|
||||
mineLaserRegion, mineLaserEndRegion;
|
||||
public TextureRegion[] wreckRegions, segmentRegions, segmentOutlineRegions;
|
||||
public TextureRegion[] wreckRegions, segmentRegions, segmentCellRegions, segmentOutlineRegions;
|
||||
public TextureRegion[][] treadRegions;
|
||||
|
||||
//INTERNAL REQUIREMENTS
|
||||
@@ -728,6 +732,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
autoFindTarget = !weapons.contains(w -> w.shootStatus.speedMultiplier < 0.99f) || alwaysShootWhenMoving;
|
||||
}
|
||||
|
||||
if(flyingLayer < 0) flyingLayer = lowAltitude ? Layer.flyingUnitLow : Layer.flyingUnit;
|
||||
clipSize = Math.max(clipSize, lightRadius * 1.1f);
|
||||
singleTarget = weapons.size <= 1 && !forceMultiTarget;
|
||||
|
||||
@@ -958,9 +963,11 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
segmentRegions = new TextureRegion[segments];
|
||||
segmentOutlineRegions = new TextureRegion[segments];
|
||||
segmentCellRegions = new TextureRegion[segments];
|
||||
for(int i = 0; i < segments; i++){
|
||||
segmentRegions[i] = Core.atlas.find(name + "-segment" + i);
|
||||
segmentOutlineRegions[i] = Core.atlas.find(name + "-segment-outline" + i);
|
||||
segmentCellRegions[i] = Core.atlas.find(name + "-segment-cell" + i);
|
||||
}
|
||||
|
||||
clipSize = Math.max(region.width * 2f, clipSize);
|
||||
@@ -1183,6 +1190,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
case size -> hitSize / tilesize;
|
||||
case itemCapacity -> itemCapacity;
|
||||
case speed -> speed * 60f / tilesize;
|
||||
case payloadCapacity -> sample instanceof Payloadc ? payloadCapacity / tilePayload : 0f;
|
||||
case id -> getLogicId();
|
||||
default -> Double.NaN;
|
||||
};
|
||||
@@ -1224,7 +1232,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
boolean isPayload = !unit.isAdded();
|
||||
|
||||
Mechc mech = unit instanceof Mechc ? (Mechc)unit : null;
|
||||
float z = isPayload ? Draw.z() : unit.elevation > 0.5f ? (lowAltitude ? Layer.flyingUnitLow : Layer.flyingUnit) : groundLayer + Mathf.clamp(hitSize / 4000f, 0, 0.01f);
|
||||
float z = isPayload ? Draw.z() : (unit.elevation > 0.5f ? flyingLayer : groundLayer) + Mathf.clamp(hitSize / 4000f, 0, 0.01f);
|
||||
|
||||
if(unit.controller().isBeingControlled(player.unit())){
|
||||
drawControl(unit);
|
||||
@@ -1280,7 +1288,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
if(engines.size > 0) drawEngines(unit);
|
||||
Draw.z(z);
|
||||
if(drawBody) drawBody(unit);
|
||||
if(drawCell) drawCell(unit);
|
||||
if(drawCell && !(unit instanceof Crawlc)) drawCell(unit);
|
||||
drawWeapons(unit);
|
||||
if(drawItems) drawItems(unit);
|
||||
if(!isPayload){
|
||||
@@ -1348,6 +1356,7 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
Draw.color(Color.lightGray, Color.white, 1f - flashScl + Mathf.absin(Time.time, 0.5f, flashScl));
|
||||
|
||||
Draw.alpha(Renderer.unitLaserOpacity);
|
||||
Drawf.laser(mineLaserRegion, mineLaserEndRegion, px, py, ex, ey, 0.75f);
|
||||
|
||||
if(unit.isLocal()){
|
||||
@@ -1645,6 +1654,13 @@ public class UnitType extends UnlockableContent implements Senseable{
|
||||
|
||||
//TODO merge outlines?
|
||||
Draw.rect(regions[i], unit.x + tx, unit.y + ty, rot - 90);
|
||||
|
||||
// Draws the cells
|
||||
if(drawCell && p != 0 && segmentCellRegions[i].found()){
|
||||
Draw.color(cellColor(unit));
|
||||
Draw.rect(segmentCellRegions[i], unit.x + tx, unit.y + ty, rot - 90);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class Links{
|
||||
new LinkEntry("f-droid", "https://f-droid.org/packages/io.anuke.mindustry/", Icon.android, Color.valueOf("026aa7")),
|
||||
new LinkEntry("github", "https://github.com/Anuken/Mindustry/", Icon.github, Color.valueOf("24292e")),
|
||||
new LinkEntry("dev-builds", "https://github.com/Anuken/MindustryBuilds", Icon.githubSquare, Color.valueOf("fafbfc")),
|
||||
new LinkEntry("bug", report(), Icon.wrench, Color.valueOf("cbd97f"))
|
||||
new LinkEntry("bug", "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml", Icon.wrench, Color.valueOf("cbd97f"))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,33 +52,4 @@ public class Links{
|
||||
this.title = Core.bundle.get("link." + name + ".title", Strings.capitalize(name.replace("-", " ")));
|
||||
}
|
||||
}
|
||||
|
||||
private static String report(){
|
||||
return "https://github.com/Anuken/Mindustry/issues/new?assignees=&labels=bug&body=" +
|
||||
Strings.encode(Strings.format(
|
||||
"""
|
||||
**Platform**: `@`
|
||||
|
||||
**Build**: `@`
|
||||
|
||||
**Issue**: *Explain your issue in detail.*
|
||||
|
||||
**Steps to reproduce**: *How you happened across the issue, and what exactly you did to make the bug happen.*
|
||||
|
||||
**Link(s) to mod(s) used**: `@`
|
||||
|
||||
**Save file**: *The (zipped) save file you were playing on when the bug happened. THIS IS REQUIRED FOR ANY ISSUE HAPPENING IN-GAME, REGARDLESS OF WHETHER YOU THINK IT HAPPENS EVERYWHERE. DO NOT DELETE OR OMIT THIS LINE UNLESS YOU ARE SURE THAT THE ISSUE DOES NOT HAPPEN IN-GAME.*
|
||||
|
||||
**Crash report**: *The contents of relevant crash report files. REQUIRED if you are reporting a crash.*
|
||||
|
||||
---
|
||||
|
||||
*Place an X (no spaces) between the brackets to confirm that you have read the line below.*
|
||||
- [ ] **I have updated to the latest release (https://github.com/Anuken/Mindustry/releases) to make sure my issue has not been fixed.**
|
||||
- [ ] **I have searched the closed and open issues to make sure that this problem has not already been reported.**
|
||||
""",
|
||||
OS.isAndroid ? "Android " + Core.app.getVersion() : (OS.osName + " x" + OS.osArchBits),
|
||||
Version.combined(),
|
||||
Vars.mods.list().any() ? Vars.mods.list().select(LoadedMod::enabled).map(l -> l.meta.author + "/" + l.name + ":" + l.meta.version) : "none"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ public class CampaignRulesDialog extends BaseDialog{
|
||||
check("@rules.fog", b -> rules.fog = b, () -> rules.fog);
|
||||
check("@rules.showspawns", b -> rules.showSpawns = b, () -> rules.showSpawns);
|
||||
check("@rules.randomwaveai", b -> rules.randomWaveAI = b, () -> rules.randomWaveAI);
|
||||
|
||||
//TODO: this is intentionally hidden until the new mechanics have been well-tested. I don't want people immediately switching to the old mechanics
|
||||
if(planet.allowLegacyLaunchPads){
|
||||
// check("@rules.legacylaunchpads", b -> rules.legacyLaunchPads = b, () -> rules.legacyLaunchPads);
|
||||
}
|
||||
}).growY();
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ public class ColorPicker extends BaseDialog{
|
||||
|
||||
hexField = t.field(current.toString(), value -> {
|
||||
try{
|
||||
current.set(Color.valueOf(value).a(a));
|
||||
Color.valueOf(current, value);
|
||||
current.toHsv(values);
|
||||
h = values[0];
|
||||
s = values[1];
|
||||
|
||||
@@ -83,7 +83,6 @@ public class ContentInfoDialog extends BaseDialog{
|
||||
value.display(inset);
|
||||
inset.add().size(10f);
|
||||
}
|
||||
|
||||
}).fillX().padLeft(10);
|
||||
table.row();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.ui.dialogs;
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.ImageButton.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
@@ -12,6 +11,7 @@ import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.editor.BannedContentDialog;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Rules.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -30,7 +30,8 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
private Table main;
|
||||
private Prov<Rules> resetter;
|
||||
private LoadoutDialog loadoutDialog;
|
||||
|
||||
private BannedContentDialog<Block> bannedBlocks = new BannedContentDialog<>("@bannedblocks", ContentType.block, Block::canBeBuilt);
|
||||
private BannedContentDialog<UnitType> bannedUnits = new BannedContentDialog<>("@bannedunits", ContentType.unit, u -> !u.isHidden());
|
||||
public boolean showRuleEditRule;
|
||||
public Seq<Table> categories;
|
||||
public Table current;
|
||||
@@ -109,84 +110,6 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
requestScroll();
|
||||
}
|
||||
|
||||
private <T extends UnlockableContent> void showBanned(String title, ContentType type, ObjectSet<T> set, Boolf<T> pred){
|
||||
BaseDialog bd = new BaseDialog(title);
|
||||
bd.addCloseButton();
|
||||
|
||||
Runnable[] rebuild = {null};
|
||||
|
||||
rebuild[0] = () -> {
|
||||
float previousScroll = bd.cont.getChildren().isEmpty() ? 0f : ((ScrollPane)bd.cont.getChildren().first()).getScrollY();
|
||||
bd.cont.clear();
|
||||
bd.cont.pane(t -> {
|
||||
t.margin(10f);
|
||||
|
||||
if(set.isEmpty()){
|
||||
t.add("@empty");
|
||||
}
|
||||
|
||||
Seq<T> array = set.toSeq();
|
||||
array.sort();
|
||||
|
||||
int cols = mobile && Core.graphics.isPortrait() ? 1 : mobile ? 2 : 3;
|
||||
int i = 0;
|
||||
|
||||
for(T con : array){
|
||||
t.table(Tex.underline, b -> {
|
||||
b.left().margin(4f);
|
||||
b.image(con.uiIcon).size(iconMed).padRight(3);
|
||||
b.add(con.localizedName).color(Color.lightGray).padLeft(3).growX().left().wrap();
|
||||
|
||||
b.button(Icon.cancel, Styles.emptyi, () -> {
|
||||
set.remove(con);
|
||||
rebuild[0].run();
|
||||
}).size(70f).pad(-4f).padLeft(0f);
|
||||
}).size(300f, 70f).padRight(5);
|
||||
|
||||
if(++i % cols == 0){
|
||||
t.row();
|
||||
}
|
||||
}
|
||||
}).get().setScrollYForce(previousScroll);
|
||||
bd.cont.row();
|
||||
bd.cont.button("@add", Icon.add, () -> {
|
||||
BaseDialog dialog = new BaseDialog("@add");
|
||||
dialog.cont.pane(t -> {
|
||||
t.left().margin(14f);
|
||||
int[] i = {0};
|
||||
content.<T>getBy(type).each(b -> !set.contains(b) && pred.get(b), b -> {
|
||||
int cols = mobile && Core.graphics.isPortrait() ? 4 : 12;
|
||||
t.button(new TextureRegionDrawable(b.uiIcon), Styles.flati, iconMed, () -> {
|
||||
set.add(b);
|
||||
rebuild[0].run();
|
||||
dialog.hide();
|
||||
}).size(60f).tooltip(b.localizedName);
|
||||
|
||||
if(++i[0] % cols == 0){
|
||||
t.row();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dialog.addCloseButton();
|
||||
dialog.show();
|
||||
}).size(300f, 64f).disabled(b -> set.size == content.<T>getBy(type).count(pred));
|
||||
};
|
||||
|
||||
bd.shown(rebuild[0]);
|
||||
bd.buttons.button("@addall", Icon.add, () -> {
|
||||
set.addAll(content.<T>getBy(type).select(pred));
|
||||
rebuild[0].run();
|
||||
}).size(180, 64f);
|
||||
|
||||
bd.buttons.button("@clear", Icon.trash, () -> {
|
||||
set.clear();
|
||||
rebuild[0].run();
|
||||
}).size(180, 64f);
|
||||
|
||||
bd.show();
|
||||
}
|
||||
|
||||
public void show(Rules rules, Prov<Rules> resetter){
|
||||
this.rules = rules;
|
||||
this.resetter = resetter;
|
||||
@@ -253,7 +176,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(Core.bundle.get("bannedblocks").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@bannedblocks", () -> showBanned("@bannedblocks", ContentType.block, rules.bannedBlocks, Block::canBeBuilt)).left().width(300f).row();
|
||||
current.button("@bannedblocks", () -> bannedBlocks.show(rules.bannedBlocks)).left().width(300f).row();
|
||||
}
|
||||
check("@rules.hidebannedblocks", b -> rules.hideBannedBlocks = b, () -> rules.hideBannedBlocks);
|
||||
check("@bannedblocks.whitelist", b -> rules.blockWhitelist = b, () -> rules.blockWhitelist);
|
||||
@@ -265,12 +188,13 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
numberi("@rules.unitcap", f -> rules.unitCap = f, () -> rules.unitCap, -999, 999);
|
||||
number("@rules.unitdamagemultiplier", f -> rules.unitDamageMultiplier = f, () -> rules.unitDamageMultiplier);
|
||||
number("@rules.unitcrashdamagemultiplier", f -> rules.unitCrashDamageMultiplier = f, () -> rules.unitCrashDamageMultiplier);
|
||||
number("@rules.unitminespeedmultiplier", f -> rules.unitMineSpeedMultiplier = f, () -> rules.unitMineSpeedMultiplier);
|
||||
number("@rules.unitbuildspeedmultiplier", f -> rules.unitBuildSpeedMultiplier = f, () -> rules.unitBuildSpeedMultiplier, 0f, 50f);
|
||||
number("@rules.unitcostmultiplier", f -> rules.unitCostMultiplier = f, () -> rules.unitCostMultiplier);
|
||||
number("@rules.unithealthmultiplier", f -> rules.unitHealthMultiplier = f, () -> rules.unitHealthMultiplier);
|
||||
|
||||
if(Core.bundle.get("bannedunits").toLowerCase().contains(ruleSearch)){
|
||||
current.button("@bannedunits", () -> showBanned("@bannedunits", ContentType.unit, rules.bannedUnits, u -> !u.isHidden())).left().width(300f).row();
|
||||
current.button("@bannedunits", () -> bannedUnits.show(rules.bannedUnits)).left().width(300f).row();
|
||||
}
|
||||
check("@bannedunits.whitelist", b -> rules.unitWhitelist = b, () -> rules.unitWhitelist);
|
||||
|
||||
@@ -378,11 +302,14 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
check("@rules.buildai", b -> teams.buildAi = b, () -> teams.buildAi, () -> team != rules.defaultTeam && rules.env != Planets.erekir.defaultEnv && !rules.pvp);
|
||||
number("@rules.buildaitier", false, f -> teams.buildAiTier = f, () -> teams.buildAiTier, () -> teams.buildAi && rules.env != Planets.erekir.defaultEnv && !rules.pvp, 0, 1);
|
||||
|
||||
number("@rules.extracorebuildradius", f -> teams.extraCoreBuildRadius = f * tilesize, () -> Math.min(teams.extraCoreBuildRadius / tilesize, 200), () -> !rules.polygonCoreProtection);
|
||||
|
||||
check("@rules.infiniteresources", b -> teams.infiniteResources = b, () -> teams.infiniteResources);
|
||||
number("@rules.buildspeedmultiplier", f -> teams.buildSpeedMultiplier = f, () -> teams.buildSpeedMultiplier, 0.001f, 50f);
|
||||
|
||||
number("@rules.unitdamagemultiplier", f -> teams.unitDamageMultiplier = f, () -> teams.unitDamageMultiplier);
|
||||
number("@rules.unitcrashdamagemultiplier", f -> teams.unitCrashDamageMultiplier = f, () -> teams.unitCrashDamageMultiplier);
|
||||
number("@rules.unitminespeedmultiplier", f -> teams.unitMineSpeedMultiplier = f, () -> teams.unitMineSpeedMultiplier);
|
||||
number("@rules.unitbuildspeedmultiplier", f -> teams.unitBuildSpeedMultiplier = f, () -> teams.unitBuildSpeedMultiplier, 0.001f, 50f);
|
||||
number("@rules.unitcostmultiplier", f -> teams.unitCostMultiplier = f, () -> teams.unitCostMultiplier);
|
||||
number("@rules.unithealthmultiplier", f -> teams.unitHealthMultiplier = f, () -> teams.unitHealthMultiplier);
|
||||
@@ -456,7 +383,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
|
||||
public void numberi(String text, Intc cons, Intp prov, Boolp condition, int min, int max){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
current.table(t -> {
|
||||
var cell = current.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5)
|
||||
.update(a -> a.setColor(condition.get() ? Color.white : Color.gray));
|
||||
@@ -464,12 +391,14 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.padRight(100f)
|
||||
.valid(f -> Strings.parseInt(f) >= min && Strings.parseInt(f) <= max).width(120f).left();
|
||||
}).padTop(0).row();
|
||||
}).padTop(0);
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
public void number(String text, boolean integer, Floatc cons, Floatp prov, Boolp condition, float min, float max){
|
||||
if(!Core.bundle.get(text.substring(1)).toLowerCase().contains(ruleSearch)) return;
|
||||
current.table(t -> {
|
||||
var cell = current.table(t -> {
|
||||
t.left();
|
||||
t.add(text).left().padRight(5)
|
||||
.update(a -> a.setColor(condition.get() ? Color.white : Color.gray));
|
||||
@@ -478,6 +407,7 @@ public class CustomRulesDialog extends BaseDialog{
|
||||
.update(a -> a.setDisabled(!condition.get()))
|
||||
.valid(f -> Strings.canParsePositiveFloat(f) && Strings.parseFloat(f) >= min && Strings.parseFloat(f) <= max).width(120f).left();
|
||||
}).padTop(0);
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
@@ -487,15 +417,26 @@ 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;
|
||||
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");
|
||||
}
|
||||
var cell = current.check(text, cons).checked(prov.get()).update(a -> a.setDisabled(!condition.get()));
|
||||
cell.get().left();
|
||||
ruleInfo(cell, text);
|
||||
current.row();
|
||||
}
|
||||
|
||||
public void ruleInfo(Cell<?> cell, String text){
|
||||
if(Core.bundle.has(text.substring(1) + ".info")){
|
||||
if(mobile){
|
||||
Table table = new Table();
|
||||
table.add(cell.get()).left().expandX().fillX();
|
||||
cell.clearElement();
|
||||
table.button(Icon.infoSmall, () -> ui.showInfo(text + ".info")).size(32f).padRight(24f).right();
|
||||
cell.setElement(table).left().expandX().fillX();
|
||||
}else{
|
||||
cell.tooltip(text + ".info");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cell<TextField> field(Table table, float value, Floatc setter){
|
||||
return table.field(Strings.autoFixed(value, 2), v -> setter.get(Strings.parseFloat(v)))
|
||||
.valid(Strings::canParsePositiveFloat)
|
||||
|
||||
@@ -86,7 +86,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
all.table(t -> {
|
||||
int i = 0;
|
||||
for(var content : allTabs){
|
||||
t.button(content == Planets.sun ? Icon.eyeSmall : content instanceof Planet ? Icon.planet : new TextureRegionDrawable(content.uiIcon), Styles.clearNoneTogglei, iconMed, () -> {
|
||||
t.button(content == Planets.sun ? Icon.eyeSmall : content instanceof Planet p ? Icon.icons.get(p.icon, Icon.commandRally) : new TextureRegionDrawable(content.uiIcon), Styles.clearNoneTogglei, iconMed, () -> {
|
||||
tab = content;
|
||||
rebuild();
|
||||
}).size(50f).checked(b -> tab == content).tooltip(content == Planets.sun ? "@all" : content.localizedName).with(but -> {
|
||||
@@ -101,7 +101,7 @@ public class DatabaseDialog extends BaseDialog{
|
||||
ContentType type = ContentType.all[j];
|
||||
|
||||
Seq<UnlockableContent> array = allContent[j]
|
||||
.select(c -> c instanceof UnlockableContent u && !u.isHidden() && (tab == Planets.sun || u.allDatabaseTabs || u.databaseTabs.contains(tab)) &&
|
||||
.select(c -> c instanceof UnlockableContent u && !u.isHidden() && !u.hideDatabase && (tab == Planets.sun || u.allDatabaseTabs || u.databaseTabs.contains(tab)) &&
|
||||
(text.isEmpty() || u.localizedName.toLowerCase().contains(text))).as();
|
||||
|
||||
if(array.size == 0) continue;
|
||||
|
||||
@@ -138,7 +138,9 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
refreshLocal();
|
||||
refreshRemote();
|
||||
refreshCommunity();
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
refreshCommunity();
|
||||
}
|
||||
}
|
||||
|
||||
void setupRemote(){
|
||||
@@ -317,7 +319,9 @@ public class JoinDialog extends BaseDialog{
|
||||
|
||||
section(steam ? "@servers.local.steam" : "@servers.local", local, false);
|
||||
section("@servers.remote", remote, false);
|
||||
section("@servers.global", global, true);
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
section("@servers.global", global, true);
|
||||
}
|
||||
|
||||
ScrollPane pane = new ScrollPane(hosts);
|
||||
pane.setFadeScrollBars(false);
|
||||
@@ -631,12 +635,18 @@ public class JoinDialog extends BaseDialog{
|
||||
Core.settings.remove("server-list");
|
||||
}
|
||||
|
||||
var urls = Version.type.equals("bleeding-edge") || Vars.forceBeServers ? serverJsonBeURLs : serverJsonURLs;
|
||||
|
||||
fetchServers(urls, 0);
|
||||
fetchServers();
|
||||
}
|
||||
|
||||
private void fetchServers(String[] urls, int index){
|
||||
public static void fetchServers(){
|
||||
var urls = Version.type.equals("bleeding-edge") || Vars.forceBeServers ? serverJsonBeURLs : serverJsonURLs;
|
||||
|
||||
if(Core.settings.getBool("communityservers", true)){
|
||||
fetchServers(urls, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fetchServers(String[] urls, int index){
|
||||
if(index >= urls.length) return;
|
||||
|
||||
//get servers
|
||||
@@ -667,7 +677,7 @@ public class JoinDialog extends BaseDialog{
|
||||
Core.app.post(() -> {
|
||||
servers.sort(s -> s.name == null ? Integer.MAX_VALUE : s.name.hashCode());
|
||||
defaultServers.addAll(servers);
|
||||
Log.info("Fetched @ community servers.", defaultServers.size);
|
||||
Log.info("Fetched @ community servers.", defaultServers.sum(s -> s.addresses.length));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
public PlanetParams state = new PlanetParams();
|
||||
public float zoom = 1f;
|
||||
public @Nullable Sector selected, hovered, launchSector;
|
||||
/** Must not be null in planet launch mode. */
|
||||
public @Nullable Seq<Planet> launchCandidates;
|
||||
public Mode mode = look;
|
||||
public boolean launching;
|
||||
public Cons<Sector> listener = s -> {};
|
||||
@@ -294,7 +296,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
|
||||
void addTech(){
|
||||
buttons.button("@techtree", Icon.tree, () -> ui.research.show()).size(200f, 54f).pad(2).bottom();
|
||||
buttons.button("@techtree", Icon.tree, () -> ui.research.show()).size(200f, 54f).visible(() -> mode == look).pad(2).bottom();
|
||||
}
|
||||
|
||||
public void showOverview(){
|
||||
@@ -312,16 +314,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
|
||||
//TODO not fully implemented, cutscene needed
|
||||
public void showPlanetLaunch(Sector sector, Cons<Sector> listener){
|
||||
public void showPlanetLaunch(Sector sector, Seq<Planet> launchCandidates, Cons<Sector> listener){
|
||||
selected = null;
|
||||
hovered = null;
|
||||
launching = false;
|
||||
this.listener = listener;
|
||||
this.launchCandidates = (launchCandidates == null ? sector.planet.launchCandidates : launchCandidates);
|
||||
launchSector = sector;
|
||||
|
||||
//automatically select next planets;
|
||||
if(sector.planet.launchCandidates.size == 1){
|
||||
state.planet = sector.planet.launchCandidates.first();
|
||||
if(this.launchCandidates.size == 1){
|
||||
state.planet = this.launchCandidates.first();
|
||||
state.otherCamPos = sector.planet.position;
|
||||
state.otherCamAlpha = 0f;
|
||||
|
||||
@@ -332,8 +335,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
preset.unlock();
|
||||
}
|
||||
selected = destSec;
|
||||
updateSelected();
|
||||
rebuildExpand();
|
||||
}
|
||||
|
||||
//TODO pan over to correct planet
|
||||
@@ -345,6 +346,13 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
mode = planetLaunch;
|
||||
|
||||
updateSelected();
|
||||
rebuildExpand();
|
||||
|
||||
if(sectorTop != null){
|
||||
sectorTop.color.a = 0f;
|
||||
}
|
||||
|
||||
super.show();
|
||||
}
|
||||
|
||||
@@ -382,8 +390,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
boolean canSelect(Sector sector){
|
||||
if(mode == select) return sector.hasBase() && launchSector != null && sector.planet == launchSector.planet;
|
||||
//cannot launch to existing sector w/ accelerator TODO test
|
||||
if(mode == planetLaunch) return sector.id == sector.planet.startSector;
|
||||
|
||||
if(mode == planetLaunch && sector.hasBase()){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(sector.hasBase() || sector.id == sector.planet.startSector) return true;
|
||||
//preset sectors can only be selected once unlocked
|
||||
if(sector.preset != null){
|
||||
@@ -393,11 +404,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
return sector.planet.generator != null ?
|
||||
//use planet impl when possible
|
||||
sector.planet.generator.allowLanding(sector) :
|
||||
sector.hasBase() || sector.near().contains(Sector::hasBase); //near an occupied sector
|
||||
(mode == planetLaunch ? sector.planet.generator.allowAcceleratorLanding(sector) : sector.planet.generator.allowLanding(sector)) :
|
||||
mode == planetLaunch || sector.hasBase() || sector.near().contains(Sector::hasBase); //near an occupied sector
|
||||
}
|
||||
|
||||
Sector findLauncher(Sector to){
|
||||
if(mode == planetLaunch){
|
||||
return launchSector;
|
||||
}
|
||||
|
||||
Sector launchSector = this.launchSector != null && this.launchSector.planet == to.planet && this.launchSector.hasBase() ? this.launchSector : null;
|
||||
//directly nearby.
|
||||
if(to.near().contains(launchSector)) return launchSector;
|
||||
@@ -472,6 +487,10 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
if(mode == planetLaunch && launchSector != null && selected != null && hovered == null){
|
||||
planets.drawArc(planet, launchSector.tile.v, selected.tile.v);
|
||||
}
|
||||
|
||||
if(state.uiAlpha > 0.001f){
|
||||
for(Sector sec : planet.sectors){
|
||||
if(sec.hasBase()){
|
||||
@@ -485,11 +504,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
if(selected != null && selected != sec && selected.hasBase()){
|
||||
//imports
|
||||
if(sec.info.getRealDestination() == selected && sec.info.anyExports()){
|
||||
if(sec.info.destination == selected && sec.info.anyExports()){
|
||||
planets.drawArc(planet, sec.tile.v, selected.tile.v, Color.gray.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25);
|
||||
}
|
||||
//exports
|
||||
if(selected.info.getRealDestination() == sec && selected.info.anyExports()){
|
||||
if(selected.info.destination == sec && selected.info.anyExports()){
|
||||
planets.drawArc(planet, selected.tile.v, sec.tile.v, Pal.place.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25);
|
||||
}
|
||||
}
|
||||
@@ -548,7 +567,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
//TODO what if any sector is selectable?
|
||||
//TODO launch criteria - which planets can be launched to? Where should this be defined? Should planets even be selectable?
|
||||
if(mode == select) return planet == state.planet;
|
||||
if(mode == planetLaunch) return launchSector != null && planet != launchSector.planet && launchSector.planet.launchCandidates.contains(planet);
|
||||
if(mode == planetLaunch) return launchSector != null && (launchCandidates.contains(planet) || (planet == launchSector.planet && planet.allowSelfSectorLaunch));
|
||||
return (planet.alwaysUnlocked && planet.isLandable()) || planet.sectors.contains(Sector::hasBase) || debugSelect;
|
||||
}
|
||||
|
||||
@@ -604,7 +623,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
new Table(t -> {
|
||||
t.touchable = Touchable.disabled;
|
||||
t.top();
|
||||
t.label(() -> mode == select ? "@sectors.select" : "").style(Styles.outlineLabel).color(Pal.accent);
|
||||
t.label(() ->
|
||||
mode == select ? "@sectors.select" :
|
||||
mode == planetLaunch ? "@sectors.launchselect" :
|
||||
""
|
||||
).style(Styles.outlineLabel).color(Pal.accent);
|
||||
}),
|
||||
buttons,
|
||||
|
||||
@@ -615,7 +638,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
t.add(pane).colspan(2).row();
|
||||
t.button("@campaign.difficulty", Icon.bookSmall, () -> {
|
||||
campaignRules.show(state.planet);
|
||||
}).margin(12f).size(208f, 40f).padTop(12f).visible(() -> state.planet.allowCampaignRules).row();
|
||||
}).margin(12f).size(208f, 40f).padTop(12f).visible(() -> state.planet.allowCampaignRules && mode != planetLaunch).row();
|
||||
t.add().height(64f); //padding for close button
|
||||
Table starsTable = new Table(Styles.black);
|
||||
pane.setWidget(starsTable);
|
||||
@@ -634,7 +657,6 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
if(planet.solarSystem == star && selectable(planet)){
|
||||
Button planetButton = planetTable.button(planet.localizedName, Icon.icons.get(planet.icon + "Small", Icon.icons.get(planet.icon, Icon.commandRallySmall)), Styles.flatTogglet, () -> {
|
||||
selected = null;
|
||||
launchSector = null;
|
||||
if(state.planet != planet){
|
||||
newPresets.clear();
|
||||
state.planet = planet;
|
||||
@@ -660,7 +682,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
void rebuildExpand(){
|
||||
Table c = expandTable;
|
||||
c.clear();
|
||||
c.visible(() -> !(graphics.isPortrait() && mobile));
|
||||
c.visible(() -> !(graphics.isPortrait() && mobile) && mode != planetLaunch);
|
||||
if(state.planet.sectors.contains(Sector::hasBase)){
|
||||
int attacked = state.planet.sectors.count(Sector::isAttacked);
|
||||
|
||||
@@ -783,7 +805,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
if(Mathf.equal(state.otherCamAlpha, 1f, 0.01f)){
|
||||
//TODO change zoom too
|
||||
state.camPos.set(Tmp.v31.set(state.otherCamPos).lerp(state.planet.position, state.otherCamAlpha).add(state.camPos).sub(state.planet.position));
|
||||
state.camPos.set(Tmp.v31.set(state.otherCamPos).slerp(state.planet.position, state.otherCamAlpha).add(state.camPos).sub(state.planet.position));
|
||||
|
||||
state.otherCamPos = null;
|
||||
//announce new sector
|
||||
@@ -792,6 +814,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
}
|
||||
|
||||
//fade in sector dialog after panning
|
||||
if(sectorTop != null && state.otherCamPos == null){
|
||||
sectorTop.color.a = Mathf.lerpDelta(sectorTop.color.a, 1f, 0.1f);
|
||||
}
|
||||
|
||||
if(hovered != null && !mobile && state.planet.hasGrid()){
|
||||
addChild(hoverLabel);
|
||||
hoverLabel.toFront();
|
||||
@@ -935,7 +962,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
|
||||
//import
|
||||
if(sector.hasBase()){
|
||||
displayItems(c, 1f, sector.info.importStats(sector.planet), "@sectors.import", t -> {
|
||||
displayItems(c, 1f, sector.info.imports, "@sectors.import", t -> {
|
||||
sector.info.eachImport(sector.planet, other -> {
|
||||
String ic = other.iconChar();
|
||||
t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + other.name()).padLeft(10f).row();
|
||||
@@ -1192,24 +1219,24 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
return;
|
||||
}
|
||||
|
||||
//make sure there are no under-attack sectors (other than this one)
|
||||
for(Planet planet : content.planets()){
|
||||
if(!planet.allowWaveSimulation && !debugSelect && planet.allowWaveSimulation == sector.planet.allowWaveSimulation){
|
||||
//if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock
|
||||
Sector attacked = planet.sectors.find(s -> s.isAttacked() && s != sector);
|
||||
if(attacked != null && planet.sectors.count(s -> s.isAttacked()) < 2){
|
||||
BaseDialog dialog = new BaseDialog("@sector.noswitch.title");
|
||||
dialog.cont.add(bundle.format("sector.noswitch", attacked.name(), attacked.planet.localizedName)).width(400f).labelAlign(Align.center).center().wrap();
|
||||
dialog.addCloseButton();
|
||||
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
|
||||
dialog.hide();
|
||||
lookAt(attacked);
|
||||
selectSector(attacked);
|
||||
});
|
||||
dialog.show();
|
||||
Planet planet = sector.planet;
|
||||
|
||||
return;
|
||||
}
|
||||
//make sure there are no under-attack sectors (other than this one)
|
||||
if(!planet.allowWaveSimulation && !debugSelect){
|
||||
//if there are two or more attacked sectors... something went wrong, don't show the dialog to prevent softlock
|
||||
Sector attacked = planet.sectors.find(s -> s.isAttacked() && s != sector);
|
||||
if(attacked != null && planet.sectors.count(s -> s.isAttacked()) < 2){
|
||||
BaseDialog dialog = new BaseDialog("@sector.noswitch.title");
|
||||
dialog.cont.add(bundle.format("sector.noswitch", attacked.name(), attacked.planet.localizedName)).width(400f).labelAlign(Align.center).center().wrap();
|
||||
dialog.addCloseButton();
|
||||
dialog.buttons.button("@sector.view", Icon.eyeSmall, () -> {
|
||||
dialog.hide();
|
||||
lookAt(attacked);
|
||||
selectSector(attacked);
|
||||
});
|
||||
dialog.show();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1258,9 +1285,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
//allow planet dialog to finish hiding before actually launching
|
||||
Time.runTask(5f, () -> {
|
||||
Runnable doLaunch = () -> {
|
||||
renderer.showLaunch(core, schemCore);
|
||||
renderer.showLaunch(core);
|
||||
//run with less delay, as the loading animation is delayed by several frames
|
||||
Time.runTask(core.landDuration() - 8f, () -> control.playSector(from, sector));
|
||||
Time.runTask(core.launchDuration() - 8f, () -> control.playSector(from, sector));
|
||||
};
|
||||
|
||||
//load launchFrom sector right before launching so animation is correct
|
||||
@@ -1275,15 +1302,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
|
||||
}
|
||||
});
|
||||
}
|
||||
}else if(mode == select){
|
||||
}else if(mode == select || mode == planetLaunch){
|
||||
listener.get(sector);
|
||||
}else if(mode == planetLaunch){ //TODO make sure it doesn't have a base already.
|
||||
//TODO animation
|
||||
//schematic selection and cost handled by listener
|
||||
listener.get(sector);
|
||||
//unlock right before launch
|
||||
sector.planet.unlockedOnLand.each(UnlockableContent::unlock);
|
||||
control.playSector(sector);
|
||||
}else{
|
||||
//sector should have base here
|
||||
control.playSector(sector);
|
||||
|
||||
@@ -333,6 +333,13 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
game.checkPref("crashreport", true);
|
||||
}
|
||||
|
||||
game.checkPref("communityservers", true, val -> {
|
||||
defaultServers.clear();
|
||||
if(val){
|
||||
JoinDialog.fetchServers();
|
||||
}
|
||||
});
|
||||
|
||||
game.checkPref("savecreate", true);
|
||||
game.checkPref("blockreplace", true);
|
||||
game.checkPref("conveyorpathfinding", true);
|
||||
@@ -390,6 +397,7 @@ public class SettingsMenuDialog extends BaseDialog{
|
||||
}
|
||||
return s + "%";
|
||||
});
|
||||
graphics.sliderPref("unitlaseropacity", 100, 0, 100, 5, s -> s + "%");
|
||||
graphics.sliderPref("bridgeopacity", 100, 0, 100, 5, s -> s + "%");
|
||||
|
||||
if(!mobile){
|
||||
|
||||
@@ -34,6 +34,8 @@ public class TraceDialog extends BaseDialog{
|
||||
c.add(Core.bundle.format("trace.playername", player.name)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.ip)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.ip", info.ip)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.locale)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.language", info.locale)).row();
|
||||
c.button(Icon.copySmall, style, () -> copy(info.uuid)).size(s).padRight(4f);
|
||||
c.add(Core.bundle.format("trace.id", info.uuid)).row();
|
||||
}).row();
|
||||
|
||||
@@ -173,7 +173,18 @@ public class ConsoleFragment extends Table{
|
||||
history.insert(1, message);
|
||||
|
||||
addMessage("[lightgray]> " + message.replace("[", "[["));
|
||||
addMessage(mods.getScripts().runConsole(message).replace("[", "[["));
|
||||
addMessage(mods.getScripts().runConsole(injectConsoleVariables() + message).replace("[", "[["));
|
||||
}
|
||||
|
||||
public String injectConsoleVariables(){
|
||||
return
|
||||
"var unit = Vars.player.unit();" +
|
||||
"var team = Vars.player.team();" +
|
||||
"var core = Vars.player.core();" +
|
||||
"var items = Vars.player.team().items();" +
|
||||
"var build = Vars.world.buildWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());" +
|
||||
"var cursor = Vars.world.tileWorld(Core.input.mouseWorldX(), Core.input.mouseWorldY());" +
|
||||
"\n";
|
||||
}
|
||||
|
||||
public void toggle(){
|
||||
|
||||
@@ -290,8 +290,19 @@ public class HudFragment{
|
||||
|
||||
Table wavesMain, editorMain;
|
||||
|
||||
cont.stack(wavesMain = new Table(), editorMain = new Table()).height(wavesMain.getPrefHeight())
|
||||
.name("waves/editor");
|
||||
cont.stack(wavesMain = new Table(), editorMain = new Table(), new Element(){
|
||||
//this may seem insane, but adding an empty element of a specific height to this stack fixes layout issues on mobile.
|
||||
|
||||
{
|
||||
visible = false;
|
||||
touchable = Touchable.disabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPrefHeight(){
|
||||
return Scl.scl(120f);
|
||||
}
|
||||
}).name("waves/editor");
|
||||
|
||||
wavesMain.visible(() -> shown && !state.isEditor());
|
||||
wavesMain.top().left().name = "waves";
|
||||
@@ -326,71 +337,28 @@ public class HudFragment{
|
||||
editorMain.name = "editor";
|
||||
editorMain.table(Tex.buttonEdge4, t -> {
|
||||
t.name = "teams";
|
||||
|
||||
|
||||
t.top().table(teams -> {
|
||||
teams.left();
|
||||
int i = 0;
|
||||
for(Team team : Team.baseTeams){
|
||||
ImageButton button = teams.button(Tex.whiteui, Styles.clearNoneTogglei, 38f, () -> Call.setPlayerTeamEditor(player, team))
|
||||
.size(50f).margin(6f).get();
|
||||
ImageButton button = teams.button(Tex.whiteui, Styles.clearNoneTogglei, 33f, () -> Call.setPlayerTeamEditor(player, team))
|
||||
.size(45f).margin(6f).get();
|
||||
button.getImageCell().grow();
|
||||
button.getStyle().imageUpColor = team.color;
|
||||
button.update(() -> button.setChecked(player.team() == team));
|
||||
|
||||
if(++i % 6 == 0){
|
||||
teams.row();
|
||||
}
|
||||
}
|
||||
}).top().left();
|
||||
|
||||
t.row();
|
||||
teams.button(Icon.downOpen, Styles.emptyi, () -> Core.settings.put("editor-blocks-shown", !Core.settings.getBool("editor-blocks-shown")))
|
||||
.size(45f).update(m -> m.getStyle().imageUp = (Core.settings.getBool("editor-blocks-shown") ? Icon.upOpen : Icon.downOpen));
|
||||
}).top().left().row();
|
||||
|
||||
t.table(control.input::buildPlacementUI).growX().left().with(in -> in.left()).row();
|
||||
t.collapser(this::addBlockSelection, () -> Core.settings.getBool("editor-blocks-shown"));
|
||||
|
||||
//hovering item display
|
||||
t.table(h -> {
|
||||
Runnable rebuild = () -> {
|
||||
h.clear();
|
||||
h.left();
|
||||
|
||||
Displayable hover = blockfrag.hovered();
|
||||
UnlockableContent toDisplay = control.input.block;
|
||||
|
||||
if(toDisplay == null && hover != null){
|
||||
if(hover instanceof Building b){
|
||||
toDisplay = b.block;
|
||||
}else if(hover instanceof Tile tile){
|
||||
toDisplay =
|
||||
tile.block().itemDrop != null ? tile.block() :
|
||||
tile.overlay().itemDrop != null || tile.wallDrop() != null ? tile.overlay() :
|
||||
tile.floor();
|
||||
}else if(hover instanceof Unit u){
|
||||
toDisplay = u.type;
|
||||
}
|
||||
}
|
||||
|
||||
if(toDisplay != null){
|
||||
h.image(toDisplay.uiIcon).scaling(Scaling.fit).size(8 * 4);
|
||||
h.add(toDisplay.localizedName).ellipsis(true).left().growX().padLeft(5);
|
||||
}
|
||||
};
|
||||
|
||||
Object[] hovering = {null};
|
||||
h.update(() -> {
|
||||
Object nextHover = control.input.block != null ? control.input.block : blockfrag.hovered();
|
||||
if(nextHover != hovering[0]){
|
||||
hovering[0] = nextHover;
|
||||
rebuild.run();
|
||||
}
|
||||
});
|
||||
}).growX().left().minHeight(36f).row();
|
||||
|
||||
t.table(blocks -> {
|
||||
addBlockSelection(blocks);
|
||||
}).fillX().left();
|
||||
}).width(dsize * 5 + 4f);
|
||||
}).width(dsize * 5 + 4f).top();
|
||||
if(mobile){
|
||||
editorMain.row().spacerY(() -> {
|
||||
if(control.input instanceof MobileInput mob){
|
||||
if(control.input instanceof MobileInput mob && Core.settings.getBool("editor-blocks-shown")){
|
||||
if(Core.graphics.isPortrait()) return Core.graphics.getHeight() / 2f / Scl.scl(1f);
|
||||
if(mob.hasSchematic()) return 156f;
|
||||
if(mob.showCancel()) return 50f;
|
||||
@@ -398,6 +366,8 @@ public class HudFragment{
|
||||
return 0f;
|
||||
});
|
||||
}
|
||||
|
||||
editorMain.row().add().growY();
|
||||
editorMain.visible(() -> shown && state.isEditor());
|
||||
|
||||
//fps display
|
||||
|
||||
@@ -28,6 +28,7 @@ public class MenuFragment{
|
||||
private Button currentMenu;
|
||||
private MenuRenderer renderer;
|
||||
private Seq<MenuButton> customButtons = new Seq<>();
|
||||
public Seq<MenuButton> desktopButtons = null;
|
||||
|
||||
public void build(Group parent){
|
||||
renderer = new MenuRenderer();
|
||||
@@ -187,22 +188,26 @@ public class MenuFragment{
|
||||
t.defaults().width(width).height(70f);
|
||||
t.name = "buttons";
|
||||
|
||||
buttons(t,
|
||||
new MenuButton("@play", Icon.play,
|
||||
new MenuButton("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new MenuButton("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new MenuButton("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new MenuButton("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
),
|
||||
new MenuButton("@database.button", Icon.menu,
|
||||
new MenuButton("@schematics", Icon.paste, ui.schematics::show),
|
||||
new MenuButton("@database", Icon.book, ui.database::show),
|
||||
new MenuButton("@about.button", Icon.info, ui.about::show)
|
||||
),
|
||||
new MenuButton("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new MenuButton("@workshop", Icon.steam, platform::openWorkshop) : null,
|
||||
new MenuButton("@mods", Icon.book, ui.mods::show),
|
||||
new MenuButton("@settings", Icon.settings, ui.settings::show)
|
||||
);
|
||||
if(desktopButtons == null){
|
||||
desktopButtons = Seq.with(
|
||||
new MenuButton("@play", Icon.play,
|
||||
new MenuButton("@campaign", Icon.play, () -> checkPlay(ui.planet::show)),
|
||||
new MenuButton("@joingame", Icon.add, () -> checkPlay(ui.join::show)),
|
||||
new MenuButton("@customgame", Icon.terrain, () -> checkPlay(ui.custom::show)),
|
||||
new MenuButton("@loadgame", Icon.download, () -> checkPlay(ui.load::show))
|
||||
),
|
||||
new MenuButton("@database.button", Icon.menu,
|
||||
new MenuButton("@schematics", Icon.paste, ui.schematics::show),
|
||||
new MenuButton("@database", Icon.book, ui.database::show),
|
||||
new MenuButton("@about.button", Icon.info, ui.about::show)
|
||||
),
|
||||
new MenuButton("@editor", Icon.terrain, () -> checkPlay(ui.maps::show)), steam ? new MenuButton("@workshop", Icon.steam, platform::openWorkshop) : null,
|
||||
new MenuButton("@mods", Icon.book, ui.mods::show),
|
||||
new MenuButton("@settings", Icon.settings, ui.settings::show)
|
||||
);
|
||||
}
|
||||
|
||||
buttons(t, desktopButtons.toArray(MenuButton.class));
|
||||
buttons(t, customButtons.toArray(MenuButton.class));
|
||||
buttons(t, new MenuButton("@quit", Icon.exit, Core.app::exit));
|
||||
}).width(width).growY();
|
||||
@@ -250,14 +255,14 @@ public class MenuFragment{
|
||||
currentMenu = null;
|
||||
fadeOutMenu();
|
||||
}else{
|
||||
if(b.submenu != null){
|
||||
if(b.submenu != null && b.submenu.any()){
|
||||
currentMenu = out[0];
|
||||
submenu.clearChildren();
|
||||
fadeInMenu();
|
||||
//correctly offset the button
|
||||
submenu.add().height((Core.graphics.getHeight() - Core.scene.marginTop - Core.scene.marginBottom - out[0].getY(Align.topLeft)) / Scl.scl(1f));
|
||||
submenu.row();
|
||||
buttons(submenu, b.submenu);
|
||||
buttons(submenu, b.submenu.toArray());
|
||||
}else{
|
||||
currentMenu = null;
|
||||
fadeOutMenu();
|
||||
@@ -296,7 +301,7 @@ public class MenuFragment{
|
||||
/** Runnable ran when the button is clicked. Ignored on desktop if {@link #submenu} is not null. */
|
||||
public final Runnable runnable;
|
||||
/** Submenu shown when this button is clicked. Used instead of {@link #runnable} on desktop. */
|
||||
public final @Nullable MenuButton[] submenu;
|
||||
public final @Nullable Seq<MenuButton> submenu;
|
||||
|
||||
/** Constructs a simple menu button, which behaves the same way on desktop and mobile. */
|
||||
public MenuButton(String text, Drawable icon, Runnable runnable){
|
||||
@@ -311,7 +316,7 @@ public class MenuFragment{
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = runnable;
|
||||
this.submenu = submenu;
|
||||
this.submenu = submenu != null ? Seq.with(submenu) : null;
|
||||
}
|
||||
|
||||
/** Comstructs a desktop-only button; used internally. */
|
||||
@@ -319,7 +324,7 @@ public class MenuFragment{
|
||||
this.icon = icon;
|
||||
this.text = text;
|
||||
this.runnable = () -> {};
|
||||
this.submenu = submenu;
|
||||
this.submenu = submenu != null ? Seq.with(submenu) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,14 +265,7 @@ public class PlacementFragment{
|
||||
public void build(Group parent){
|
||||
parent.fill(full -> {
|
||||
toggler = full;
|
||||
full.bottom().right().visible(() -> {
|
||||
if(state.rules.editor){
|
||||
//force update the mouse picking, since it otherwise would not happen
|
||||
updatePick(control.input);
|
||||
}
|
||||
|
||||
return ui.hudfrag.shown && !state.rules.editor;
|
||||
});
|
||||
full.bottom().right().visible(() -> ui.hudfrag.shown);
|
||||
|
||||
full.table(frame -> {
|
||||
|
||||
@@ -750,12 +743,10 @@ public class PlacementFragment{
|
||||
|
||||
/** @return the thing being hovered over. */
|
||||
public @Nullable Displayable hovered(){
|
||||
if(!state.rules.editor){
|
||||
Vec2 v = topTable.stageToLocalCoordinates(Core.input.mouse());
|
||||
Vec2 v = topTable.stageToLocalCoordinates(Core.input.mouse());
|
||||
|
||||
//if the mouse intersects the table or the UI has the mouse, no hovering can occur
|
||||
if(Core.scene.hasMouse() || topTable.hit(v.x, v.y, false) != null) return null;
|
||||
}
|
||||
//if the mouse intersects the table or the UI has the mouse, no hovering can occur
|
||||
if(Core.scene.hasMouse(Core.input.mouseX(), Core.input.mouseY()) || topTable.hit(v.x, v.y, false) != null) return null;
|
||||
|
||||
//check for a unit
|
||||
Unit unit = Units.closestOverlap(player.team(), Core.input.mouseWorldX(), Core.input.mouseWorldY(), 5f, u -> !u.isLocal() && u.displayable());
|
||||
|
||||
@@ -196,7 +196,7 @@ public class Build{
|
||||
if(closest != null && closest.team != team){
|
||||
return false;
|
||||
}
|
||||
}else if(state.teams.anyEnemyCoresWithin(team, x * tilesize + type.offset, y * tilesize + type.offset, state.rules.enemyCoreBuildRadius + tilesize)){
|
||||
}else if(state.teams.anyEnemyCoresWithinBuildRadius(team, x * tilesize + type.offset, y * tilesize + type.offset)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
29
core/src/mindustry/world/blocks/LaunchAnimator.java
Normal file
29
core/src/mindustry/world/blocks/LaunchAnimator.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package mindustry.world.blocks;
|
||||
|
||||
import arc.audio.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public interface LaunchAnimator{
|
||||
|
||||
void drawLaunch();
|
||||
|
||||
default void drawLaunchGlobalZ(){}
|
||||
|
||||
void beginLaunch(boolean launching);
|
||||
|
||||
void endLaunch();
|
||||
|
||||
void updateLaunch();
|
||||
|
||||
float launchDuration();
|
||||
|
||||
default Music landMusic(){
|
||||
return Musics.land;
|
||||
}
|
||||
|
||||
default Music launchMusic(){
|
||||
return Musics.launch;
|
||||
}
|
||||
|
||||
float zoomLaunch();
|
||||
}
|
||||
@@ -3,61 +3,140 @@ package mindustry.world.blocks.campaign;
|
||||
import arc.*;
|
||||
import arc.Graphics.*;
|
||||
import arc.Graphics.Cursor.*;
|
||||
import arc.audio.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.actions.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Accelerator extends Block{
|
||||
public @Load("launch-arrow") TextureRegion arrowRegion;
|
||||
public @Load(value = "@-launch-arrow", fallback = "launch-arrow") TextureRegion arrowRegion;
|
||||
public @Load("select-arrow-small") TextureRegion selectArrowRegion;
|
||||
|
||||
//TODO dynamic
|
||||
public Block launching = Blocks.coreNucleus;
|
||||
public int[] capacities = {};
|
||||
/** Core block that is launched. Should match the starting core of the planet being launched to. */
|
||||
public Block launchBlock = Blocks.coreNucleus;
|
||||
public float powerBufferRequirement;
|
||||
/** Override for planets that this block can launch to. If null, the planet's launch candidates are used. */
|
||||
public @Nullable Seq<Planet> launchCandidates;
|
||||
|
||||
//TODO: launching needs audio!
|
||||
|
||||
public Music launchMusic = Musics.coreLaunch;
|
||||
public float launchDuration = 120f;
|
||||
public float chargeDuration = 220f;
|
||||
public float buildDuration = 120f;
|
||||
public Interp landZoomInterp = Interp.pow4In, chargeZoomInterp = Interp.pow4In;
|
||||
public float landZoomFrom = 0.02f, landZoomTo = 4f, chargeZoomTo = 5f;
|
||||
|
||||
public int chargeRings = 4;
|
||||
public float ringRadBase = 60f, ringRadSpacing = 25f, ringRadPow = 1.6f, ringStroke = 3f, ringSpeedup = 1.4f, chargeRingMerge = 2f, ringArrowRad = 3f;
|
||||
public float ringHandleTilt = 0.8f, ringHandleLen = 30f;
|
||||
public Color ringColor = Pal.accent;
|
||||
|
||||
public int launchLightning = 20;
|
||||
public Color lightningColor = Pal.accent;
|
||||
public float lightningDamage = 40;
|
||||
public float lightningOffset = 24f;
|
||||
public int lightningLengthMin = 5, lightningLengthMax = 25;
|
||||
public double lightningLaunchChance = 0.8;
|
||||
|
||||
protected int[] capacities = {};
|
||||
|
||||
public Accelerator(String name){
|
||||
super(name);
|
||||
update = true;
|
||||
solid = true;
|
||||
hasItems = true;
|
||||
hasPower = true;
|
||||
itemCapacity = 8000;
|
||||
configurable = true;
|
||||
emitLight = true;
|
||||
lightRadius = 70f;
|
||||
lightColor = Pal.accent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
itemCapacity = 0;
|
||||
capacities = new int[content.items().size];
|
||||
for(ItemStack stack : launching.requirements){
|
||||
for(ItemStack stack : launchBlock.requirements){
|
||||
capacities[stack.item.id] = stack.amount;
|
||||
itemCapacity += stack.amount;
|
||||
}
|
||||
consumeItems(launching.requirements);
|
||||
consumeItems(launchBlock.requirements);
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
if(powerBufferRequirement > 0f){
|
||||
addBar("powerBufferRequirement", b -> new Bar(
|
||||
() -> Core.bundle.format("bar.powerbuffer",UI.formatAmount((long)b.power.graph.getBatteryStored()), UI.formatAmount((long)powerBufferRequirement)),
|
||||
() -> Pal.powerBar,
|
||||
() -> b.power.graph.getBatteryStored() / powerBufferRequirement
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean outputsItems(){
|
||||
return false;
|
||||
}
|
||||
|
||||
public class AcceleratorBuild extends Building{
|
||||
public class AcceleratorBuild extends Building implements LaunchAnimator{
|
||||
public float heat, statusLerp;
|
||||
public float progress;
|
||||
public float time, launchHeat;
|
||||
public boolean launching;
|
||||
|
||||
protected float cloudSeed;
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
super.updateTile();
|
||||
heat = Mathf.lerpDelta(heat, efficiency, 0.05f);
|
||||
heat = Mathf.lerpDelta(heat, launching ? 1f : efficiency, 0.05f);
|
||||
statusLerp = Mathf.lerpDelta(statusLerp, power.status, 0.05f);
|
||||
|
||||
if(!launching){
|
||||
time += Time.delta * efficiency;
|
||||
}else{
|
||||
time = Mathf.slerpDelta(time, 0f, 0.4f);
|
||||
}
|
||||
|
||||
launchHeat = Mathf.lerpDelta(launchHeat, launching ? 1f : 0f, 0.1f);
|
||||
|
||||
if(efficiency >= 0f){
|
||||
progress += Time.delta * efficiency / buildDuration;
|
||||
progress = Math.min(progress, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float progress(){
|
||||
return progress;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,53 +153,107 @@ public class Accelerator extends Block{
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if(launching){
|
||||
Draw.reset();
|
||||
Draw.rect(launchBlock.fullIcon, x, y);
|
||||
}else{
|
||||
Drawf.shadow(x, y, launchBlock.size * tilesize * 2f, progress);
|
||||
Draw.draw(Layer.blockBuilding, () -> {
|
||||
Draw.color(Pal.accent, heat);
|
||||
|
||||
for(TextureRegion region : launchBlock.getGeneratedIcons()){
|
||||
Shaders.blockbuild.region = region;
|
||||
Shaders.blockbuild.time = time;
|
||||
Shaders.blockbuild.progress = progress;
|
||||
|
||||
Draw.rect(region, x, y);
|
||||
Draw.flush();
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
if(heat < 0.0001f) return;
|
||||
|
||||
float rad = size * tilesize / 2f * 0.74f;
|
||||
float rad = size * tilesize / 2f * 0.74f * Mathf.lerp(1f, 1.3f, launchHeat);
|
||||
float scl = 2f;
|
||||
|
||||
Draw.z(Layer.bullet - 0.0001f);
|
||||
Lines.stroke(1.75f * heat, Pal.accent);
|
||||
Lines.square(x, y, rad * 1.22f, 45f);
|
||||
Lines.square(x, y, rad * 1.22f, Mathf.lerp(45f, 0f, launchHeat));
|
||||
|
||||
//TODO: lock time when launching
|
||||
|
||||
Lines.stroke(3f * heat, Pal.accent);
|
||||
Lines.square(x, y, rad, Time.time / scl);
|
||||
Lines.square(x, y, rad, -Time.time / scl);
|
||||
Lines.square(x, y, rad * Mathf.lerp(1f, 1.3f, launchHeat), 45f + time / scl);
|
||||
Lines.square(x, y, rad * Mathf.lerp(1f, 1.8f, launchHeat), Mathf.lerp(45f, 0f, launchHeat) - time / scl);
|
||||
|
||||
Draw.color(team.color);
|
||||
Draw.alpha(Mathf.clamp(heat * 3f));
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
float rot = i*90f + 45f + (-Time.time /3f)%360f;
|
||||
float length = 26f * heat;
|
||||
float rot = i*90f + 45f + (-time/3f)%360f;
|
||||
float length = 26f * heat * Mathf.lerp(1f, 1.5f, launchHeat);
|
||||
Draw.rect(arrowRegion, x + Angles.trnsx(rot, length), y + Angles.trnsy(rot, length), rot + 180f);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLight(){
|
||||
Drawf.light(x, y, lightRadius, lightColor, launchHeat);
|
||||
}
|
||||
|
||||
public boolean canLaunch(){
|
||||
return isValid() && state.isCampaign() && efficiency > 0f && power.graph.getBatteryStored() >= powerBufferRequirement-0.00001f && progress >= 1f && !launching;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getCursor(){
|
||||
return !state.isCampaign() || efficiency <= 0f ? SystemCursor.arrow : super.getCursor();
|
||||
return canLaunch() ? SystemCursor.hand : super.getCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawSelect(){
|
||||
super.drawSelect();
|
||||
|
||||
if(power.graph.getBatteryStored() < powerBufferRequirement && !launching){
|
||||
drawPlaceText(Core.bundle.get("bar.nobatterypower"), tile.x, tile.y, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildConfiguration(Table table){
|
||||
deselect();
|
||||
|
||||
if(!state.isCampaign() || efficiency <= 0f) return;
|
||||
if(!canLaunch()) return;
|
||||
|
||||
ui.showInfo("This block has been removed from the tech tree as of v7, and no longer has a use.\n\nWill it ever be used for anything? Who knows.");
|
||||
ui.planet.showPlanetLaunch(state.rules.sector, launchCandidates == null ? state.rules.sector.planet.launchCandidates : launchCandidates, sector -> {
|
||||
if(canLaunch()){
|
||||
consume();
|
||||
power.graph.useBatteries(powerBufferRequirement);
|
||||
progress = 0f;
|
||||
|
||||
if(false)
|
||||
ui.planet.showPlanetLaunch(state.rules.sector, sector -> {
|
||||
//TODO cutscene, etc...
|
||||
renderer.showLaunch(this);
|
||||
|
||||
//TODO should consume resources based on destination schem
|
||||
consume();
|
||||
Time.runTask(launchDuration() - 6f, () -> {
|
||||
//unlock right before launch
|
||||
launching = false;
|
||||
sector.planet.unlockedOnLand.each(UnlockableContent::unlock);
|
||||
|
||||
universe.clearLoadoutInfo();
|
||||
universe.updateLoadout(sector.planet.generator.defaultLoadout.findCore(), sector.planet.generator.defaultLoadout);
|
||||
universe.clearLoadoutInfo();
|
||||
universe.updateLoadout((CoreBlock)launchBlock);
|
||||
|
||||
control.playSector(sector);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Events.fire(Trigger.acceleratorUse);
|
||||
@@ -135,5 +268,349 @@ public class Accelerator extends Block{
|
||||
public boolean acceptItem(Building source, Item item){
|
||||
return items.get(item) < getMaximumAccepted(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
write.f(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
|
||||
if(revision >= 1){
|
||||
progress = read.f();
|
||||
}
|
||||
}
|
||||
|
||||
//launch animator stuff:
|
||||
@Override
|
||||
public float launchDuration(){
|
||||
return launchDuration + chargeDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music landMusic(){
|
||||
//unused
|
||||
return launchMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music launchMusic(){
|
||||
return launchMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginLaunch(boolean launching){
|
||||
if(!launching) return;
|
||||
|
||||
this.launching = true;
|
||||
Fx.coreLaunchConstruct.at(x, y, launchBlock.size);
|
||||
|
||||
cloudSeed = Mathf.random(1f);
|
||||
float margin = 30f;
|
||||
|
||||
Image image = new Image();
|
||||
image.color.a = 0f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.delay((launchDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
if(state.isMenu()){
|
||||
image.remove();
|
||||
}
|
||||
});
|
||||
Core.scene.add(image);
|
||||
|
||||
Time.run(chargeDuration, () -> {
|
||||
Fx.coreLaunchConstruct.at(x, y, launchBlock.size);
|
||||
Fx.launchAccelerator.at(x, y);
|
||||
Effect.shake(10f, 14f, this);
|
||||
|
||||
for(int i = 0; i < launchLightning; i++){
|
||||
float a = Mathf.random(360f);
|
||||
Lightning.create(team, lightningColor, lightningDamage, x + Angles.trnsx(a, lightningOffset), y + Angles.trnsy(a, lightningOffset), a, Mathf.random(lightningLengthMin, lightningLengthMax));
|
||||
}
|
||||
|
||||
float spacing = 12f;
|
||||
for(int i = 0; i < 13; i++){
|
||||
int fi = i;
|
||||
Time.run(i * 1.1f, () -> {
|
||||
float radius = block.size/2f + 1 + spacing * fi;
|
||||
int rays = Mathf.ceil(radius * Mathf.PI * 2f / 6f);
|
||||
for(int r = 0; r < rays; r++){
|
||||
if(Mathf.chance(0.7f - fi * 0.02f)){
|
||||
float angle = r * 360f / (float)rays;
|
||||
float ox = Angles.trnsx(angle, radius), oy = Angles.trnsy(angle, radius);
|
||||
Tile t = world.tileWorld(x + ox, y + oy);
|
||||
if(t != null){
|
||||
Fx.coreLandDust.at(t.worldx(), t.worldy(), angle + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.7f + Mathf.range(0.15f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endLaunch(){
|
||||
launching = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float zoomLaunch(){
|
||||
float rawTime = launchDuration() - renderer.getLandTime();
|
||||
|
||||
Core.camera.position.set(this);
|
||||
|
||||
if(rawTime < chargeDuration){
|
||||
float fin = rawTime / chargeDuration;
|
||||
|
||||
return chargeZoomInterp.apply(Scl.scl(landZoomTo), Scl.scl(chargeZoomTo), fin);
|
||||
}else{
|
||||
float rawFin = renderer.getLandTimeIn();
|
||||
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
|
||||
|
||||
return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), fin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLaunch(){
|
||||
float in = renderer.getLandTimeIn() * launchDuration();
|
||||
float tsize = Mathf.sample(CoreBlock.thrusterSizes, (in + 35f) / launchDuration());
|
||||
|
||||
float rawFin = renderer.getLandTimeIn();
|
||||
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
|
||||
float chargeFout = 1f - chargeFin;
|
||||
|
||||
if(in > launchDuration){
|
||||
if(Mathf.chanceDelta(lightningLaunchChance * Interp.pow3In.apply(chargeFout))){
|
||||
float a = Mathf.random(360f);
|
||||
Lightning.create(team, lightningColor, lightningDamage, x + Angles.trnsx(a, lightningOffset), y + Angles.trnsy(a, lightningOffset), a, Mathf.random(lightningLengthMin, lightningLengthMax));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLaunch(){
|
||||
var clouds = Core.assets.get("sprites/clouds.png", Texture.class);
|
||||
|
||||
float rawFin = renderer.getLandTimeIn();
|
||||
float rawTime = launchDuration() - renderer.getLandTime();
|
||||
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
|
||||
|
||||
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
|
||||
float chargeFout = 1f - chargeFin;
|
||||
|
||||
float cameraScl = renderer.getDisplayScale();
|
||||
|
||||
float fout = 1f - fin;
|
||||
float scl = Scl.scl(4f) / cameraScl;
|
||||
float pfin = Interp.pow3Out.apply(fin), pf = Interp.pow2In.apply(fout);
|
||||
|
||||
//draw particles
|
||||
Draw.color(Pal.lightTrail);
|
||||
Angles.randLenVectors(1, pfin, 100, 800f * scl * pfin, (ax, ay, ffin, ffout) -> {
|
||||
Lines.stroke(scl * ffin * pf * 3f);
|
||||
Lines.lineAngle(x + ax, y + ay, Mathf.angle(ax, ay), (ffin * 20 + 1f) * scl);
|
||||
});
|
||||
Draw.color();
|
||||
|
||||
if(rawTime >= chargeDuration){
|
||||
drawLanding(fin, x, y);
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
|
||||
|
||||
//accent tint indicating that the core was just constructed
|
||||
if(renderer.isLaunching()){
|
||||
float f = Mathf.clamp(1f - fout * 12f);
|
||||
if(f > 0.001f){
|
||||
Draw.mixcol(Pal.accent, f);
|
||||
}
|
||||
}
|
||||
|
||||
//draw clouds
|
||||
if(state.rules.cloudColor.a > 0.0001f){
|
||||
float scaling = CoreBlock.cloudScaling;
|
||||
float sscl = Math.max(1f + Mathf.clamp(fin + CoreBlock.cfinOffset) * CoreBlock.cfinScl, 0f) * cameraScl;
|
||||
|
||||
Tmp.tr1.set(clouds);
|
||||
Tmp.tr1.set(
|
||||
(Core.camera.position.x - Core.camera.width/2f * sscl) / scaling,
|
||||
(Core.camera.position.y - Core.camera.height/2f * sscl) / scaling,
|
||||
(Core.camera.position.x + Core.camera.width/2f * sscl) / scaling,
|
||||
(Core.camera.position.y + Core.camera.height/2f * sscl) / scaling);
|
||||
|
||||
Tmp.tr1.scroll(10f * cloudSeed, 10f * cloudSeed);
|
||||
|
||||
Draw.alpha(Mathf.sample(CoreBlock.cloudAlphas, fin + CoreBlock.calphaFinOffset) * CoreBlock.cloudAlpha);
|
||||
Draw.mixcol(state.rules.cloudColor, state.rules.cloudColor.a);
|
||||
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, Core.camera.height);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLaunchGlobalZ(){
|
||||
float rawFin = renderer.getLandTimeIn();
|
||||
|
||||
float chargeFin = 1f - Mathf.clamp((1f - rawFin) / (chargeDuration / (launchDuration + chargeDuration)));
|
||||
float fin = 1f - Mathf.clamp((1f - rawFin) - (chargeDuration / (launchDuration + chargeDuration))) / (1f - (chargeDuration / (launchDuration + chargeDuration)));
|
||||
float fout = 1f - fin;
|
||||
float chargeFout = 1f - chargeFin;
|
||||
|
||||
//fade out rings during launch.
|
||||
chargeFout = Mathf.clamp(chargeFout - fout * 2f);
|
||||
|
||||
float
|
||||
spacing = 1f / (chargeRings + chargeRingMerge);
|
||||
|
||||
for(int i = 0; i < chargeRings; i++){
|
||||
float cfin = Mathf.clamp((chargeFout*ringSpeedup - spacing * i) / (spacing * (1f + chargeRingMerge)));
|
||||
if(cfin > 0){
|
||||
drawRing(ringRadBase + ringRadSpacing * Mathf.pow(i, ringRadPow), cfin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawRing(float radius, float fin){
|
||||
Draw.z(Layer.effect);
|
||||
|
||||
float fout = 1f - fin;
|
||||
float rotate = Interp.pow4In.apply(fout) * 90f;
|
||||
float rad = radius + 20f * Interp.pow4In.apply(fout);
|
||||
|
||||
Lines.stroke(ringStroke * fin, ringColor);
|
||||
|
||||
Draw.color(Pal.command, ringColor, fin);
|
||||
|
||||
//handles
|
||||
for(int i = 0; i < 4; i++){
|
||||
float angle = i * 90f + 45f + rotate;
|
||||
Lines.beginLine();
|
||||
Lines.linePoint(Tmp.v1.trns(angle - ringHandleLen, rad * ringHandleTilt).add(x, y));
|
||||
Lines.linePoint(Tmp.v2.trns(angle, rad).add(x, y));
|
||||
Lines.linePoint(Tmp.v3.trns(angle + ringHandleLen, rad * ringHandleTilt).add(x, y));
|
||||
Lines.endLine(false);
|
||||
|
||||
}
|
||||
|
||||
Draw.scl(fin);
|
||||
|
||||
//selection triangles
|
||||
for(int i = 0; i < 4; i++){
|
||||
float angle = i * 90f + rotate;
|
||||
|
||||
|
||||
Draw.rect(selectArrowRegion, x + Angles.trnsx(angle, rad), y + Angles.trnsy(angle, rad), angle + 180f + 45f);
|
||||
|
||||
//shape variant:
|
||||
//Lines.poly(x + Angles.trnsx(angle, rad), y + Angles.trnsy(angle, rad), 3, ringArrowRad * fin, angle + 180f);
|
||||
}
|
||||
|
||||
Draw.scl();
|
||||
|
||||
}
|
||||
|
||||
protected void drawLanding(float fin, float x, float y){
|
||||
float rawTime = launchDuration() - renderer.getLandTime();
|
||||
float fout = 1f - fin;
|
||||
|
||||
float scl = rawTime < chargeDuration ? 1f : Scl.scl(4f) / renderer.getDisplayScale();
|
||||
float shake = 0f;
|
||||
float s = launchBlock.region.width * launchBlock.region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
|
||||
float rotation = Interp.pow2In.apply(fout) * 135f;
|
||||
x += Mathf.range(shake);
|
||||
y += Mathf.range(shake);
|
||||
float thrustOpen = 0.25f;
|
||||
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
|
||||
float thrusterSize = Mathf.sample(CoreBlock.thrusterSizes, fin);
|
||||
|
||||
//when launching, thrusters stay out the entire time.
|
||||
if(renderer.isLaunching()){
|
||||
Interp i = Interp.pow2Out;
|
||||
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
|
||||
thrusterSize = i.apply(Mathf.clamp(fout*9f));
|
||||
}
|
||||
|
||||
Draw.color(Pal.lightTrail);
|
||||
//TODO spikier heat
|
||||
Draw.rect("circle-shadow", x, y, s, s);
|
||||
|
||||
Draw.scl(scl);
|
||||
|
||||
//draw thruster flame
|
||||
float strength = (1f + (launchBlock.size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
|
||||
float offset = (launchBlock.size - 3) * 3f * scl;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tmp.v1.trns(i * 90 + rotation, 1f);
|
||||
|
||||
Tmp.v1.setLength((launchBlock.size * tilesize/2f + 1f)*scl + strength*2f + offset);
|
||||
Draw.color(team.color);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
|
||||
|
||||
Tmp.v1.setLength((launchBlock.size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
|
||||
Draw.color(Color.white);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
|
||||
}
|
||||
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
|
||||
Drawf.spinSprite(launchBlock.region, x, y, rotation);
|
||||
|
||||
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
Draw.alpha(1f);
|
||||
|
||||
if(launchBlock.teamRegions[team.id] == launchBlock.teamRegion) Draw.color(team.color);
|
||||
|
||||
Drawf.spinSprite(launchBlock.teamRegions[team.id], x, y, rotation);
|
||||
|
||||
Draw.color();
|
||||
Draw.scl();
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
|
||||
CoreBlock core = (CoreBlock)launchBlock;
|
||||
float length = core.thrusterLength * (frame - 1f) - 1f/4f;
|
||||
float alpha = Draw.getColorAlpha();
|
||||
|
||||
//two passes for consistent lighting
|
||||
for(int j = 0; j < 2; j++){
|
||||
for(int i = 0; i < 4; i++){
|
||||
var reg = i >= 2 ? core.thruster2 : core.thruster1;
|
||||
float rot = (i * 90) + rotation % 90f;
|
||||
Tmp.v1.trns(rot, length * Draw.xscl);
|
||||
|
||||
//second pass applies extra layer of shading
|
||||
if(j == 1){
|
||||
Tmp.v1.rotate(-90f);
|
||||
Draw.alpha((rotation % 90f) / 90f * alpha);
|
||||
rot -= 90f;
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}else{
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
Draw.alpha(1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
477
core/src/mindustry/world/blocks/campaign/LandingPad.java
Normal file
477
core/src/mindustry/world/blocks/campaign/LandingPad.java
Normal file
@@ -0,0 +1,477 @@
|
||||
package mindustry.world.blocks.campaign;
|
||||
|
||||
import arc.*;
|
||||
import arc.Graphics.*;
|
||||
import arc.Graphics.Cursor.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.liquid.*;
|
||||
import mindustry.world.consumers.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LandingPad extends Block{
|
||||
static ObjectMap<Item, Seq<LandingPadBuild>> waiting = new ObjectMap<>();
|
||||
static long lastUpdateId = -1;
|
||||
|
||||
static{
|
||||
Events.on(ResetEvent.class, e -> {
|
||||
waiting.clear();
|
||||
lastUpdateId = -1;
|
||||
});
|
||||
}
|
||||
|
||||
public @Load(value = "@-pod", fallback = "advanced-launch-pad-pod") TextureRegion podRegion;
|
||||
public float arrivalDuration = 150f;
|
||||
public float cooldownTime = 150f;
|
||||
public float consumeLiquidAmount = 100f;
|
||||
public Liquid consumeLiquid = Liquids.water;
|
||||
|
||||
public Effect landEffect = Fx.podLandShockwave;
|
||||
public Effect coolingEffect = Fx.none;
|
||||
public float coolingEffectChance = 0.2f;
|
||||
|
||||
public float liquidPad = 2f;
|
||||
public Color bottomColor = Pal.darkerMetal;
|
||||
|
||||
public LandingPad(String name){
|
||||
super(name);
|
||||
|
||||
hasItems = true;
|
||||
hasLiquids = true;
|
||||
solid = true;
|
||||
update = true;
|
||||
configurable = true;
|
||||
acceptsItems = false;
|
||||
canOverdrive = false; //overdriving can't do anything meaningful besides decrease cooldown, which is very small anyway, so don't bother
|
||||
emitLight = true;
|
||||
lightRadius = 90f;
|
||||
|
||||
config(Item.class, (LandingPadBuild build, Item item) -> {
|
||||
if(!build.accessible()) return;
|
||||
|
||||
build.config = item;
|
||||
});
|
||||
configClear((LandingPadBuild build) -> {
|
||||
if(!build.accessible()) return;
|
||||
|
||||
build.config = null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
consume(new ConsumeLiquid(consumeLiquid, consumeLiquidAmount){
|
||||
|
||||
@Override
|
||||
public void build(Building build, Table table){
|
||||
table.add(new ReqImage(liquid.uiIcon, () -> build.liquids.get(liquid) >= amount)).size(iconMed).top().left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiency(Building build){
|
||||
return build.liquids.get(consumeLiquid) >= amount ? 1f : 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(Stats stats){
|
||||
stats.add(Stat.input, liquid, amount, false);
|
||||
}
|
||||
}).update(false);
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBars(){
|
||||
super.setBars();
|
||||
|
||||
addLiquidBar(consumeLiquid);
|
||||
//TODO: does cooldown even need to exist?
|
||||
addBar("cooldown", (LandingPadBuild entity) -> new Bar("bar.cooldown", Pal.lightOrange, () -> entity.cooldown));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean outputsItems(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server)
|
||||
public static void landingPadLanded(Tile tile){
|
||||
if(tile == null || !(tile.build instanceof LandingPadBuild build)) return;
|
||||
build.handleLanding();
|
||||
}
|
||||
|
||||
public class LandingPadBuild extends Building{
|
||||
public @Nullable Item config;
|
||||
//priority collisions are possible, but should be extremely rare
|
||||
public int priority = Mathf.rand.nextInt();
|
||||
public float cooldown = 0f, landParticleTimer;
|
||||
|
||||
public float arrivingTimer = 0f;
|
||||
public @Nullable Item arriving;
|
||||
public float liquidRemoved;
|
||||
|
||||
public void handleLanding(){
|
||||
if(config == null) return;
|
||||
|
||||
cooldown = 1f;
|
||||
arriving = config;
|
||||
arrivingTimer = 0f;
|
||||
liquidRemoved = 0f;
|
||||
|
||||
if(state.isCampaign() && !isFake()){
|
||||
state.rules.sector.info.importCooldownTimers.put(config, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean accessible(){
|
||||
//In custom games, this block can be configured by anyone except the player team; this allows for enemy builder AI to use it
|
||||
return state.rules.editor || state.rules.allowEditWorldProcessors || state.isCampaign() || state.rules.infiniteResources || (team != state.rules.defaultTeam && !state.rules.pvp && team != Team.derelict);
|
||||
}
|
||||
|
||||
public void updateTimers(){
|
||||
if(state.isCampaign() && lastUpdateId != state.updateId){
|
||||
lastUpdateId = state.updateId;
|
||||
|
||||
float[] imports = state.rules.sector.info.getImportRates(state.getPlanet());
|
||||
|
||||
for(Item item : content.items()){
|
||||
float importedPerFrame = imports[item.id]/60f;
|
||||
if(importedPerFrame > 0f){
|
||||
float framesBetweenArrival = itemCapacity / importedPerFrame;
|
||||
|
||||
state.rules.sector.info.importCooldownTimers.increment(item, 0f, 1f / framesBetweenArrival * Time.delta);
|
||||
}else{
|
||||
//nothing is being imported, so reset the timer
|
||||
state.rules.sector.info.importCooldownTimers.put(item, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
waiting.each((item, pads) -> {
|
||||
if(pads.size > 0){
|
||||
pads.sort(p -> p.priority);
|
||||
|
||||
var first = pads.first();
|
||||
var head = pads.peek();
|
||||
|
||||
Call.landingPadLanded(first.tile);
|
||||
|
||||
//swap priorities, moving this block to the end of the list (if there is only one block waiting, this does nothing)
|
||||
var tmp = first.priority;
|
||||
first.priority = head.priority;
|
||||
head.priority = tmp;
|
||||
|
||||
pads.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
if(consumeLiquid != null){
|
||||
Draw.color(bottomColor);
|
||||
Fill.square(x, y, size * tilesize/2f - liquidPad);
|
||||
Draw.color();
|
||||
LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, consumeLiquid, liquids.get(consumeLiquid) / liquidCapacity);
|
||||
}
|
||||
|
||||
super.draw();
|
||||
|
||||
if(arriving != null){
|
||||
float fin = Mathf.clamp(arrivingTimer), fout = 1f - fin;
|
||||
float alpha = Interp.pow5Out.apply(fin);
|
||||
float scale = (1f - alpha) * 1.3f + 1f;
|
||||
float
|
||||
cx = x,
|
||||
cy = y + Interp.pow4In.apply(fout) * (100f + Mathf.randomSeedRange(id() + 2, 30f));
|
||||
|
||||
float rotation = fout * (90f + Mathf.randomSeedRange(id(), 50f));
|
||||
|
||||
Draw.z(Layer.effect + 0.001f);
|
||||
|
||||
Draw.color(Pal.engine);
|
||||
|
||||
float rad = 0.15f + Interp.pow5Out.apply(Mathf.slope(fin));
|
||||
|
||||
Fill.light(cx, cy, 10, 25f * (rad + scale-1f), Tmp.c2.set(Pal.engine).a(alpha), Tmp.c1.set(Pal.engine).a(0f));
|
||||
|
||||
Draw.alpha(alpha);
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(cx, cy, 6f, 40f * (rad + scale-1f), i * 90f + rotation);
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
|
||||
Draw.z(Layer.weather - 1);
|
||||
|
||||
scale *= podRegion.scl();
|
||||
float rw = podRegion.width * scale, rh = podRegion.height * scale;
|
||||
|
||||
Draw.alpha(alpha);
|
||||
Drawf.shadow(cx, cy, size * tilesize, fin);
|
||||
Draw.rect(podRegion, cx, cy, rw, rh, rotation);
|
||||
|
||||
Tmp.v1.trns(225f, Interp.pow3In.apply(fout) * 250f);
|
||||
|
||||
Draw.z(Layer.flyingUnit + 1);
|
||||
Draw.color(0, 0, 0, 0.22f * alpha);
|
||||
|
||||
Draw.rect(podRegion, cx + Tmp.v1.x, cy + Tmp.v1.y, rw, rh, rotation);
|
||||
|
||||
}else if(cooldown > 0f){
|
||||
|
||||
Drawf.shadow(x, y, size * tilesize, cooldown);
|
||||
Draw.alpha(cooldown);
|
||||
Draw.mixcol(Pal.accent, 1f - cooldown);
|
||||
Draw.rect(podRegion, x, y);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLight(){
|
||||
Drawf.light(x, y, lightRadius, Pal.accent, Mathf.clamp(Math.max(cooldown, arrivingTimer * 1.5f)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
updateTimers();
|
||||
|
||||
if(arriving != null){
|
||||
if(!headless){ //pod particles
|
||||
float fin = arrivingTimer;
|
||||
float tsize = Interp.pow5Out.apply(fin);
|
||||
|
||||
landParticleTimer += tsize * Time.delta / 2f;
|
||||
if(landParticleTimer >= 1f){
|
||||
tile.getLinkedTiles(t -> {
|
||||
if(Mathf.chance(0.1f)){
|
||||
Fx.podLandDust.at(t.worldx(), t.worldy(), angleTo(t.worldx(), t.worldy()) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f)));
|
||||
}
|
||||
});
|
||||
|
||||
landParticleTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
arrivingTimer += Time.delta / arrivalDuration;
|
||||
|
||||
float toRemove = Math.min(consumeLiquidAmount / arrivalDuration * Time.delta, consumeLiquidAmount - liquidRemoved);
|
||||
liquidRemoved += toRemove;
|
||||
|
||||
liquids.remove(consumeLiquid, toRemove);
|
||||
|
||||
if(Mathf.chanceDelta(coolingEffectChance * Interp.pow5Out.apply(arrivingTimer))){
|
||||
coolingEffect.at(this);
|
||||
}
|
||||
|
||||
if(arrivingTimer >= 1f){
|
||||
//remove any leftovers to make sure it's precise
|
||||
liquids.remove(consumeLiquid, consumeLiquidAmount - liquidRemoved);
|
||||
|
||||
landEffect.at(this);
|
||||
Effect.shake(3f, 3f, this);
|
||||
|
||||
items.set(arriving, itemCapacity);
|
||||
if(!isFake()){
|
||||
state.getSector().info.handleItemImport(arriving, itemCapacity);
|
||||
}
|
||||
|
||||
arriving = null;
|
||||
arrivingTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
if(items.total() > 0){
|
||||
dumpAccumulate(config == null || items.get(config) != items.total() ? null : config);
|
||||
}
|
||||
|
||||
if(arriving == null){
|
||||
cooldown -= delta() / cooldownTime;
|
||||
cooldown = Mathf.clamp(cooldown);
|
||||
}
|
||||
|
||||
if(config != null && (isFake() || (state.isCampaign() && !state.getPlanet().campaignRules.legacyLaunchPads))){
|
||||
|
||||
if(cooldown <= 0f && efficiency > 0f && items.total() == 0 && (isFake() || (state.rules.sector.info.getImportRate(state.getPlanet(), config) > 0f && state.rules.sector.info.importCooldownTimers.get(config, 0f) >= 1f))){
|
||||
|
||||
if(isFake()){
|
||||
//there is no queue for enemy team blocks, it's all fake
|
||||
Call.landingPadLanded(tile);
|
||||
}else{
|
||||
//queue landing for next frame
|
||||
waiting.get(config, Seq::new).add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether this pad should receive items forever, essentially acting as an item source for maps. */
|
||||
public boolean isFake(){
|
||||
return team != state.rules.defaultTeam || !state.isCampaign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDump(Building to, Item item){
|
||||
//hack: canDump is only ever called right before item offload, so count the item as "produced" before that.
|
||||
produced(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawSelect(){
|
||||
if(config != null){
|
||||
float dx = x - size * tilesize/2f, dy = y + size * tilesize/2f, s = iconSmall / 4f;
|
||||
Draw.mixcol(Color.darkGray, 1f);
|
||||
Draw.rect(config.fullIcon, dx, dy - 1, s, s);
|
||||
Draw.reset();
|
||||
Draw.rect(config.fullIcon, dx, dy, s, s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getCursor(){
|
||||
return !accessible() ? SystemCursor.arrow : super.getCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShowConfigure(Player player){
|
||||
return accessible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onConfigureBuildTapped(Building other){
|
||||
if(this == other || !accessible()){
|
||||
deselect();
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.onConfigureBuildTapped(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildConfiguration(Table table){
|
||||
|
||||
ItemSelection.buildTable(LandingPad.this, table, content.items(), () -> config, this::configure, selectionRows, selectionColumns);
|
||||
|
||||
if(!net.client() && !isFake()){
|
||||
table.row();
|
||||
|
||||
table.table(t -> {
|
||||
t.background(Styles.black6);
|
||||
|
||||
t.button(Icon.downOpen, Styles.clearNonei, 40f, () -> {
|
||||
if(config != null && state.isCampaign()){
|
||||
for(Sector sector : state.getPlanet().sectors){
|
||||
if(sector.hasBase() && sector != state.getSector() && sector.info.destination != state.getSector() && sector.info.hasExport(config)){
|
||||
sector.info.destination = state.getSector();
|
||||
sector.saveInfo();
|
||||
}
|
||||
}
|
||||
state.getSector().info.refreshImportRates(state.getPlanet());
|
||||
}
|
||||
}).disabled(b -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(s -> s.hasBase() && s.info.hasExport(config) && s.info.destination != state.getSector())))
|
||||
.tooltip("@sectors.redirect").get();
|
||||
}).fillX().left();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(Table table){
|
||||
super.display(table);
|
||||
|
||||
if(!state.isCampaign() || net.client() || team != player.team() || isFake()) return;
|
||||
|
||||
table.row();
|
||||
table.label(() -> {
|
||||
if(!state.isCampaign() || isFake()) return "";
|
||||
|
||||
if(state.getPlanet().campaignRules.legacyLaunchPads){
|
||||
return Core.bundle.get("landingpad.legacy.disabled");
|
||||
}
|
||||
|
||||
if(config == null) return "";
|
||||
|
||||
int sources = 0;
|
||||
float perSecond = 0f;
|
||||
for(var s : state.getPlanet().sectors){
|
||||
if(s != state.getSector() && s.hasBase() && s.info.destination == state.getSector()){
|
||||
float amount = s.info.getExport(config);
|
||||
if(amount > 0){
|
||||
sources ++;
|
||||
perSecond += s.info.getExport(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String str = Core.bundle.format("landing.sources", sources == 0 ? Core.bundle.get("none") : sources);
|
||||
if(perSecond > 0){
|
||||
str += "\n" + Core.bundle.format("landing.import", config.emoji(), (int)(perSecond * 60f));
|
||||
}
|
||||
return str;
|
||||
}).pad(4).wrap().width(200f).left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Building source, Item item){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object config(){
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte version(){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(Reads read, byte revision){
|
||||
super.read(read, revision);
|
||||
config = TypeIO.readItem(read);
|
||||
priority = read.i();
|
||||
cooldown = read.f();
|
||||
|
||||
if(revision >= 1){
|
||||
arriving = TypeIO.readItem(read);
|
||||
arrivingTimer = read.f();
|
||||
liquidRemoved = read.f();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
TypeIO.writeItem(write, config);
|
||||
write.i(priority);
|
||||
write.f(cooldown);
|
||||
|
||||
TypeIO.writeItem(write, arriving);
|
||||
write.f(arrivingTimer);
|
||||
write.f(liquidRemoved);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,18 +22,27 @@ import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.liquid.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LaunchPad extends Block{
|
||||
/** Time inbetween launches. */
|
||||
/** Time between launches. */
|
||||
public float launchTime = 1f;
|
||||
public Sound launchSound = Sounds.none;
|
||||
|
||||
public @Load("@-light") TextureRegion lightRegion;
|
||||
public @Load(value = "@-pod", fallback = "launchpod") TextureRegion podRegion;
|
||||
public Color lightColor = Color.valueOf("eab678");
|
||||
public boolean acceptMultipleItems = false;
|
||||
|
||||
public float lightStep = 1f;
|
||||
public int lightSteps = 3;
|
||||
|
||||
public float liquidPad = 2f;
|
||||
public @Nullable Liquid drawLiquid;
|
||||
public Color bottomColor = Pal.darkerMetal;
|
||||
|
||||
public LaunchPad(String name){
|
||||
super(name);
|
||||
@@ -86,20 +95,23 @@ public class LaunchPad extends Block{
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
if(hasLiquids && drawLiquid != null){
|
||||
Draw.color(bottomColor);
|
||||
Fill.square(x, y, size * tilesize/2f - liquidPad);
|
||||
Draw.color();
|
||||
LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, drawLiquid, liquids.get(drawLiquid) / liquidCapacity);
|
||||
}
|
||||
|
||||
if(!state.isCampaign()) return;
|
||||
super.draw();
|
||||
|
||||
if(lightRegion.found()){
|
||||
Draw.color(lightColor);
|
||||
float progress = Math.min((float)items.total() / itemCapacity, launchCounter / launchTime);
|
||||
int steps = 3;
|
||||
float step = 1f;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
for(int j = 0; j < steps; j++){
|
||||
float alpha = Mathf.curve(progress, (float)j / steps, (j+1f) / steps);
|
||||
float offset = -(j - 1f) * step;
|
||||
for(int j = 0; j < lightSteps; j++){
|
||||
float alpha = Mathf.curve(progress, (float)j / lightSteps, (j+1f) / lightSteps);
|
||||
float offset = -(j - 1f) * lightStep;
|
||||
|
||||
Draw.color(Pal.metalGrayDark, lightColor, alpha);
|
||||
Draw.rect(lightRegion, x + Geometry.d8edge(i).x * offset, y + Geometry.d8edge(i).y * offset, i * 90);
|
||||
@@ -109,6 +121,7 @@ public class LaunchPad extends Block{
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
Drawf.shadow(x, y, size * tilesize);
|
||||
Draw.rect(podRegion, x, y);
|
||||
|
||||
Draw.reset();
|
||||
@@ -116,12 +129,11 @@ public class LaunchPad extends Block{
|
||||
|
||||
@Override
|
||||
public boolean acceptItem(Building source, Item item){
|
||||
return items.total() < itemCapacity;
|
||||
return items.total() < itemCapacity && (acceptMultipleItems || items.total() == 0 || items.first() == item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTile(){
|
||||
if(!state.isCampaign()) return;
|
||||
|
||||
//increment launchCounter then launch when full and base conditions are met
|
||||
if((launchCounter += edelta()) >= launchTime && items.total() >= itemCapacity){
|
||||
@@ -149,7 +161,7 @@ public class LaunchPad extends Block{
|
||||
|
||||
table.row();
|
||||
table.label(() -> {
|
||||
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.getRealDestination();
|
||||
Sector dest = state.rules.sector == null ? null : state.rules.sector.info.destination;
|
||||
|
||||
return Core.bundle.format("launch.destination",
|
||||
dest == null || !dest.hasBase() ? Core.bundle.get("sectors.nonelaunch") :
|
||||
@@ -157,6 +169,11 @@ public class LaunchPad extends Block{
|
||||
}).pad(4).wrap().width(200f).left();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShowConfigure(Player player){
|
||||
return state.isCampaign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildConfiguration(Table table){
|
||||
if(!state.isCampaign() || net.client()){
|
||||
@@ -167,7 +184,11 @@ public class LaunchPad extends Block{
|
||||
table.button(Icon.upOpen, Styles.cleari, () -> {
|
||||
ui.planet.showSelect(state.rules.sector, other -> {
|
||||
if(state.isCampaign() && other.planet == state.rules.sector.planet){
|
||||
var prev = state.rules.sector.info.destination;
|
||||
state.rules.sector.info.destination = other;
|
||||
if(prev != null){
|
||||
prev.info.refreshImportRates(state.getPlanet());
|
||||
}
|
||||
}
|
||||
});
|
||||
deselect();
|
||||
@@ -260,26 +281,24 @@ public class LaunchPad extends Block{
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
if(!state.isCampaign()) return;
|
||||
if(!state.isCampaign() || net.client()) return;
|
||||
|
||||
Sector destsec = state.rules.sector.info.getRealDestination();
|
||||
Sector destsec = state.rules.sector.info.destination;
|
||||
|
||||
//actually launch the items upon removal
|
||||
if(team() == state.rules.defaultTeam){
|
||||
if(destsec != null && (destsec != state.rules.sector || net.client())){
|
||||
ItemSeq dest = new ItemSeq();
|
||||
if(team() == state.rules.defaultTeam && destsec != null && destsec != state.rules.sector){
|
||||
ItemSeq dest = new ItemSeq();
|
||||
|
||||
for(ItemStack stack : stacks){
|
||||
dest.add(stack);
|
||||
for(ItemStack stack : stacks){
|
||||
dest.add(stack);
|
||||
|
||||
//update export
|
||||
state.rules.sector.info.handleItemExport(stack);
|
||||
Events.fire(new LaunchItemEvent(stack));
|
||||
}
|
||||
//update export statistics
|
||||
state.rules.sector.info.handleItemExport(stack);
|
||||
Events.fire(new LaunchItemEvent(stack));
|
||||
}
|
||||
|
||||
if(!net.client()){
|
||||
destsec.addItems(dest);
|
||||
}
|
||||
if(state.getPlanet().campaignRules.legacyLaunchPads){
|
||||
destsec.addItems(dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ public class Door extends Wall{
|
||||
public Effect openfx = Fx.dooropen;
|
||||
public Effect closefx = Fx.doorclose;
|
||||
public Sound doorSound = Sounds.door;
|
||||
public boolean chainEffect = false;
|
||||
public @Load("@-open") TextureRegion openRegion;
|
||||
|
||||
public Door(String name){
|
||||
@@ -35,17 +36,23 @@ public class Door extends Wall{
|
||||
consumesTap = true;
|
||||
|
||||
config(Boolean.class, (DoorBuild base, Boolean open) -> {
|
||||
doorSound.at(base);
|
||||
base.effect();
|
||||
if(!world.isGenerating()){
|
||||
doorSound.at(base);
|
||||
base.effect();
|
||||
}
|
||||
|
||||
for(DoorBuild entity : base.chained){
|
||||
doorQueue.clear();
|
||||
doorQueue.add(base);
|
||||
|
||||
for(DoorBuild entity : base.chained.isEmpty() ? doorQueue : base.chained){
|
||||
//skip doors with things in them
|
||||
if((Units.anyEntities(entity.tile) && !open) || entity.open == open){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(chainEffect) entity.effect();
|
||||
entity.open = open;
|
||||
pathfinder.updateTile(entity.tile());
|
||||
if(!world.isGenerating()) pathfinder.updateTile(entity.tile());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ForceProjector extends Block{
|
||||
bullet.absorb();
|
||||
paramEffect.at(bullet);
|
||||
paramEntity.hit = 1f;
|
||||
paramEntity.buildup += bullet.damage;
|
||||
paramEntity.buildup += bullet.type.shieldDamage(bullet);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ public class ForceProjector extends Block{
|
||||
|
||||
if(consItems && itemConsumer instanceof ConsumeItems coni){
|
||||
stats.remove(Stat.booster);
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("+{0} " + StatUnit.shieldHealth.localized(), stats.timePeriod, phaseShieldBoost, phaseRadiusBoost, coni.items, this::consumesItem));
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("+{0} " + StatUnit.shieldHealth.localized(), stats.timePeriod, phaseShieldBoost, phaseRadiusBoost, coni.items));
|
||||
stats.add(Stat.booster, StatValues.speedBoosters("", coolantConsumption, Float.MAX_VALUE, true, this::consumesLiquid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class MendProjector extends Block{
|
||||
stats.add(Stat.booster, StatValues.itemBoosters(
|
||||
"{0}" + StatUnit.timesSpeed.localized(),
|
||||
stats.timePeriod, (phaseBoost + healPercent) / healPercent, phaseRangeBoost,
|
||||
cons.items, this::consumesItem)
|
||||
cons.items)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class OverdriveProjector extends Block{
|
||||
|
||||
if(hasBoost && findConsumer(f -> f instanceof ConsumeItems) instanceof ConsumeItems items){
|
||||
stats.remove(Stat.booster);
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("+{0}%", stats.timePeriod, speedBoostPhase * 100f, phaseRangeBoost, items.items, this::consumesItem));
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("+{0}%", stats.timePeriod, speedBoostPhase * 100f, phaseRangeBoost, items.items));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public class RegenProjector extends Block{
|
||||
stats.add(Stat.booster, StatValues.itemBoosters(
|
||||
"{0}" + StatUnit.timesSpeed.localized(),
|
||||
stats.timePeriod, optionalMultiplier, 0f,
|
||||
cons.items, this::consumesItem)
|
||||
cons.items)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ public class Turret extends ReloadTurret{
|
||||
public float inaccuracy = 0f;
|
||||
/** Fraction of bullet velocity that is random. */
|
||||
public float velocityRnd = 0f;
|
||||
/** Fraction of lifetime that is added to bullets with lifeScale. */
|
||||
public float scaleLifetimeOffset = 0f;
|
||||
/** Maximum angle difference in degrees at which turret will still try to shoot. */
|
||||
public float shootCone = 8f;
|
||||
/** Turret shoot point. */
|
||||
@@ -66,10 +68,12 @@ public class Turret extends ReloadTurret{
|
||||
public float minRange = 0f;
|
||||
/** Minimum warmup needed to fire. */
|
||||
public float minWarmup = 0f;
|
||||
/** If true, this turret will accurately target moving targets with respect to charge time. */
|
||||
/** If true, this turret will accurately target moving targets with respect to shoot.firstShotDelay. */
|
||||
public boolean accurateDelay = true;
|
||||
/** If false, this turret can't move while charging. */
|
||||
public boolean moveWhileCharging = true;
|
||||
/** If false, this turret can't reload while charging */
|
||||
public boolean reloadWhileCharging = true;
|
||||
/** How long warmup is maintained even if this turret isn't shooting. */
|
||||
public float warmupMaintainTime = 0f;
|
||||
/** pattern used for bullets */
|
||||
@@ -157,7 +161,7 @@ public class Turret extends ReloadTurret{
|
||||
super.setStats();
|
||||
|
||||
stats.add(Stat.inaccuracy, (int)inaccuracy, StatUnit.degrees);
|
||||
stats.add(Stat.reload, 60f / (reload) * shoot.shots, StatUnit.perSecond);
|
||||
stats.add(Stat.reload, 60f / (reload + (!reloadWhileCharging ? shoot.firstShotDelay : 0f)) * shoot.shots, StatUnit.perSecond);
|
||||
stats.add(Stat.targetsAir, targetAir);
|
||||
stats.add(Stat.targetsGround, targetGround);
|
||||
if(ammoPerShot != 1) stats.add(Stat.ammoUse, ammoPerShot, StatUnit.perShot);
|
||||
@@ -420,7 +424,10 @@ public class Turret extends ReloadTurret{
|
||||
}
|
||||
|
||||
//turret always reloads regardless of whether it's targeting something
|
||||
updateReload();
|
||||
if(reloadWhileCharging || !charging()){
|
||||
updateReload();
|
||||
updateCooling();
|
||||
}
|
||||
|
||||
if(state.rules.fog){
|
||||
float newRange = hasAmmo() ? peekAmmo().rangeChange : 0f;
|
||||
@@ -476,10 +483,6 @@ public class Turret extends ReloadTurret{
|
||||
updateShooting();
|
||||
}
|
||||
}
|
||||
|
||||
if(coolant != null){
|
||||
updateCooling();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -559,14 +562,14 @@ public class Turret extends ReloadTurret{
|
||||
|
||||
/** @return whether the turret has ammo. */
|
||||
public boolean hasAmmo(){
|
||||
//used for "side-ammo" like gas in some turrets
|
||||
if(!canConsume()) return false;
|
||||
|
||||
//skip first entry if it has less than the required amount of ammo
|
||||
if(ammo.size >= 2 && ammo.peek().amount < ammoPerShot && ammo.get(ammo.size - 2).amount >= ammoPerShot){
|
||||
ammo.swap(ammo.size - 1, ammo.size - 2);
|
||||
}
|
||||
|
||||
//used for "side-ammo" like gas in some turrets
|
||||
if(!canConsume()) return false;
|
||||
|
||||
return ammo.size > 0 && (ammo.peek().amount >= ammoPerShot || cheating());
|
||||
}
|
||||
|
||||
@@ -640,7 +643,7 @@ public class Turret extends ReloadTurret{
|
||||
bulletY = y + Angles.trnsy(rotation - 90, shootX + xOffset + xSpread, shootY + yOffset),
|
||||
shootAngle = rotation + angleOffset + Mathf.range(inaccuracy + type.inaccuracy);
|
||||
|
||||
float lifeScl = type.scaleLife ? Mathf.clamp(Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y) / type.range, minRange / type.range, range() / type.range) : 1f;
|
||||
float lifeScl = type.scaleLife ? Mathf.clamp((1 + scaleLifetimeOffset) * Mathf.dst(bulletX, bulletY, targetPos.x, targetPos.y) / type.range, minRange / type.range, range() / type.range) : 1f;
|
||||
|
||||
//TODO aimX / aimY for multi shot turrets?
|
||||
handleBullet(type.create(this, team, bulletX, bulletY, shootAngle, -1f, (1f - velocityRnd) + Mathf.random(velocityRnd), lifeScl, null, mover, targetPos.x, targetPos.y), xOffset, yOffset, shootAngle - rotation);
|
||||
|
||||
@@ -23,6 +23,7 @@ public class SeaBush extends Prop{
|
||||
|
||||
@Override
|
||||
public void drawBase(Tile tile){
|
||||
Draw.z(layer);
|
||||
rand.setSeed(tile.pos());
|
||||
float offset = rand.random(180f);
|
||||
int lobes = rand.random(lobesMin, lobesMax);
|
||||
|
||||
@@ -23,9 +23,7 @@ public class LiquidRouter extends LiquidBlock{
|
||||
public class LiquidRouterBuild extends LiquidBuild{
|
||||
@Override
|
||||
public void updateTile(){
|
||||
if(liquids.currentAmount() > 0.01f){
|
||||
dumpLiquid(liquids.current());
|
||||
}
|
||||
dumpLiquid(liquids.current());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -82,6 +82,11 @@ public class BuildPayload implements Payload{
|
||||
return build.block.size * tilesize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
build.stopSound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
write.b(payloadBlock);
|
||||
|
||||
@@ -75,6 +75,8 @@ public interface Payload extends Position{
|
||||
return y();
|
||||
}
|
||||
|
||||
default void remove(){}
|
||||
|
||||
static void write(@Nullable Payload payload, Writes write){
|
||||
if(payload == null){
|
||||
write.bool(false);
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
@@ -251,6 +252,13 @@ public class PayloadBlock extends Block{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(payload instanceof UnitPayload up) return up.unit.type == content ? 1 : 0;
|
||||
if(payload instanceof BuildPayload bp) return bp.build.block == content ? 1 : 0;
|
||||
return super.sense(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Writes write){
|
||||
super.write(write);
|
||||
|
||||
@@ -5,6 +5,7 @@ import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
@@ -201,6 +202,13 @@ public class PayloadDeconstructor extends PayloadBlock{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(deconstructing instanceof UnitPayload up) return up.unit.type == content ? 1 : 0;
|
||||
if(deconstructing instanceof BuildPayload bp) return bp.build.block == content ? 1 : 0;
|
||||
return super.sense(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.progress) return progress;
|
||||
|
||||
@@ -118,7 +118,7 @@ public class UnitPayload implements Payload{
|
||||
}
|
||||
|
||||
//cannot dump when there's a lot of overlap going on
|
||||
if(!unit.type.flying && Units.count(unit.x, unit.y, unit.physicSize(), o -> o.isGrounded() && (o.type.allowLegStep == unit.type.allowLegStep)) > 0){
|
||||
if(!unit.type.flying && Units.count(unit.x, unit.y, unit.physicSize() * 1.05f, o -> o.isGrounded() && (o.type.allowLegStep == unit.type.allowLegStep)) > 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package mindustry.world.blocks.power;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -21,6 +22,7 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
public float effectChance = 0.01f;
|
||||
public Effect generateEffect = Fx.none, consumeEffect = Fx.none;
|
||||
public float generateEffectRange = 3f;
|
||||
public float baseLightRadius = 65f;
|
||||
|
||||
public @Nullable LiquidStack outputLiquid;
|
||||
/** If true, this block explodes when outputLiquid exceeds capacity. */
|
||||
@@ -28,6 +30,8 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
|
||||
public @Nullable ConsumeItemFilter filterItem;
|
||||
public @Nullable ConsumeLiquidFilter filterLiquid;
|
||||
/** Multiplies the itemDuration for a given item. */
|
||||
public ObjectFloatMap<Item> itemDurationMultipliers = new ObjectFloatMap<>();
|
||||
|
||||
public ConsumeGenerator(String name){
|
||||
super(name);
|
||||
@@ -47,6 +51,11 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
filterItem = findConsumer(c -> c instanceof ConsumeItemFilter);
|
||||
filterLiquid = findConsumer(c -> c instanceof ConsumeLiquidFilter);
|
||||
|
||||
//pass along the duration multipliers to the consumer, so it can display them properly
|
||||
if(filterItem instanceof ConsumeItemEfficiency eff){
|
||||
eff.itemDurationMultipliers = itemDurationMultipliers;
|
||||
}
|
||||
|
||||
if(outputLiquid != null){
|
||||
outputsLiquid = true;
|
||||
hasLiquids = true;
|
||||
@@ -56,14 +65,14 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
explosionPuddleLiquid = outputLiquid.liquid;
|
||||
}
|
||||
|
||||
//TODO hardcoded
|
||||
emitLight = true;
|
||||
lightRadius = 65f * size;
|
||||
lightRadius = baseLightRadius * size;
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStats(){
|
||||
stats.timePeriod = itemDuration;
|
||||
super.setStats();
|
||||
|
||||
if(hasItems){
|
||||
@@ -76,16 +85,18 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
}
|
||||
|
||||
public class ConsumeGeneratorBuild extends GeneratorBuild{
|
||||
public float warmup, totalTime, efficiencyMultiplier = 1f;
|
||||
public float warmup, totalTime, efficiencyMultiplier = 1f, itemDurationMultiplier = 1;
|
||||
|
||||
@Override
|
||||
public void updateEfficiencyMultiplier(){
|
||||
efficiencyMultiplier = 1f;
|
||||
if(filterItem != null){
|
||||
float m = filterItem.efficiencyMultiplier(this);
|
||||
if(m > 0) efficiencyMultiplier = m;
|
||||
}else if(filterLiquid != null){
|
||||
if(m > 0) efficiencyMultiplier *= m;
|
||||
}
|
||||
if(filterLiquid != null){
|
||||
float m = filterLiquid.efficiencyMultiplier(this);
|
||||
if(m > 0) efficiencyMultiplier = m;
|
||||
if(m > 0) efficiencyMultiplier *= m;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +114,11 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
generateEffect.at(x + Mathf.range(generateEffectRange), y + Mathf.range(generateEffectRange));
|
||||
}
|
||||
|
||||
//make sure the multiplier doesn't change when there is nothing to consume while it's still running
|
||||
if(filterItem != null && valid && itemDurationMultipliers.size > 0 && filterItem.getConsumed(this) != null){
|
||||
itemDurationMultiplier = itemDurationMultipliers.get(filterItem.getConsumed(this), 1);
|
||||
}
|
||||
|
||||
//take in items periodically
|
||||
if(hasItems && valid && generateTime <= 0f){
|
||||
consume();
|
||||
@@ -122,7 +138,7 @@ public class ConsumeGenerator extends PowerGenerator{
|
||||
}
|
||||
|
||||
//generation time always goes down, but only at the end so consumeTriggerValid doesn't assume fake items
|
||||
generateTime -= delta() / itemDuration;
|
||||
generateTime -= delta() / (itemDuration * itemDurationMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -62,20 +62,23 @@ public class PowerDiode extends Block{
|
||||
PowerGraph frontGraph = front().power.graph;
|
||||
if(backGraph == frontGraph) return;
|
||||
|
||||
// 0f - 1f of battery capacity in use
|
||||
float backStored = backGraph.getBatteryStored() / backGraph.getTotalBatteryCapacity();
|
||||
float frontStored = frontGraph.getBatteryStored() / frontGraph.getTotalBatteryCapacity();
|
||||
float backStored = backGraph.getBatteryStored();
|
||||
float backCapacity = backGraph.getTotalBatteryCapacity();
|
||||
float frontStored = frontGraph.getBatteryStored();
|
||||
float frontCapacity = frontGraph.getTotalBatteryCapacity();
|
||||
|
||||
// try to send if the back side has more % capacity stored than the front side
|
||||
if(backStored > frontStored){
|
||||
// send half of the difference
|
||||
float amount = backGraph.getBatteryStored() * (backStored - frontStored) / 2;
|
||||
// prevent sending more than the front can handle
|
||||
amount = Mathf.clamp(amount, 0, frontGraph.getTotalBatteryCapacity() * (1 - frontStored));
|
||||
if(backStored/backCapacity <= frontStored/frontCapacity) return;
|
||||
|
||||
backGraph.transferPower(-amount);
|
||||
frontGraph.transferPower(amount);
|
||||
}
|
||||
float targetPercentage = (frontStored + backStored) / (frontCapacity + backCapacity);
|
||||
|
||||
// send half of the difference
|
||||
float amount = (targetPercentage * frontCapacity - frontStored) / 2;
|
||||
|
||||
// prevent sending more than the front can handle
|
||||
amount = Mathf.clamp(amount, 0, frontCapacity - frontStored);
|
||||
|
||||
backGraph.transferPower(-amount);
|
||||
frontGraph.transferPower(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,10 @@ public class BeamDrill extends Block{
|
||||
|
||||
/** Multipliers of drill speed for each item. Defaults to 1. */
|
||||
public ObjectFloatMap<Item> drillMultipliers = new ObjectFloatMap<>();
|
||||
/** Special exemption item that this drill can't mine. */
|
||||
public @Nullable Item blockedItem;
|
||||
/** Special exemption items that this drill can't mine. */
|
||||
public @Nullable Seq<Item> blockedItems;
|
||||
|
||||
public Color sparkColor = Color.valueOf("fd9e81"), glowColor = Color.white;
|
||||
public float glowIntensity = 0.2f, pulseIntensity = 0.07f;
|
||||
@@ -76,6 +80,9 @@ public class BeamDrill extends Block{
|
||||
public void init(){
|
||||
updateClipRadius((range + 2) * tilesize);
|
||||
super.init();
|
||||
if(blockedItems == null && blockedItem != null){
|
||||
blockedItems = Seq.with(blockedItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +118,10 @@ public class BeamDrill extends Block{
|
||||
public void setStats(){
|
||||
super.setStats();
|
||||
|
||||
stats.add(Stat.drillTier, StatValues.drillables(drillTime, 0f, size, drillMultipliers, b -> (b instanceof Floor f && f.wallOre && f.itemDrop != null && f.itemDrop.hardness <= tier) || (b instanceof StaticWall w && w.itemDrop != null && w.itemDrop.hardness <= tier)));
|
||||
stats.add(Stat.drillTier, StatValues.drillables(drillTime, 0f, size, drillMultipliers, b ->
|
||||
(b instanceof Floor f && f.wallOre && f.itemDrop != null && f.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(f.itemDrop))) ||
|
||||
(b instanceof StaticWall w && w.itemDrop != null && w.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(w.itemDrop)))
|
||||
));
|
||||
|
||||
stats.add(Stat.drillSpeed, 60f / drillTime * size, StatUnit.itemsSecond);
|
||||
|
||||
@@ -142,7 +152,7 @@ public class BeamDrill extends Block{
|
||||
if(other != null && other.solid()){
|
||||
Item drop = other.wallDrop();
|
||||
if(drop != null){
|
||||
if(drop.hardness <= tier){
|
||||
if(drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
|
||||
found = drop;
|
||||
count++;
|
||||
}else{
|
||||
@@ -193,7 +203,7 @@ public class BeamDrill extends Block{
|
||||
Tile other = world.tile(Tmp.p1.x + Geometry.d4x(rotation)*j, Tmp.p1.y + Geometry.d4y(rotation)*j);
|
||||
if(other != null && other.solid()){
|
||||
Item drop = other.wallDrop();
|
||||
if(drop != null && drop.hardness <= tier){
|
||||
if(drop != null && drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -379,7 +389,7 @@ public class BeamDrill extends Block{
|
||||
if(other != null){
|
||||
if(other.solid()){
|
||||
Item drop = other.wallDrop();
|
||||
if(drop != null && drop.hardness <= tier){
|
||||
if(drop != null && drop.hardness <= tier && (blockedItems == null || !blockedItems.contains(drop))){
|
||||
facingAmount ++;
|
||||
if(lastItem != drop && lastItem != null){
|
||||
multiple = true;
|
||||
|
||||
@@ -40,6 +40,8 @@ public class Drill extends Block{
|
||||
public float warmupSpeed = 0.015f;
|
||||
/** Special exemption item that this drill can't mine. */
|
||||
public @Nullable Item blockedItem;
|
||||
/** Special exemption items that this drill can't mine. */
|
||||
public @Nullable Seq<Item> blockedItems;
|
||||
|
||||
//return variables for countOre
|
||||
protected @Nullable Item returnItem;
|
||||
@@ -52,7 +54,7 @@ public class Drill extends Block{
|
||||
/** Drill effect randomness. Block size by default. */
|
||||
public float drillEffectRnd = -1f;
|
||||
/** Chance of displaying the effect. Useful for extremely fast drills. */
|
||||
public float drillEffectChance = 1f;
|
||||
public float drillEffectChance = 0.02f;
|
||||
/** Speed the drill bit rotates at. */
|
||||
public float rotateSpeed = 2f;
|
||||
/** Effect randomly played while drilling. */
|
||||
@@ -89,6 +91,9 @@ public class Drill extends Block{
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
if(blockedItems == null && blockedItem != null){
|
||||
blockedItems = Seq.with(blockedItem);
|
||||
}
|
||||
if(drillEffectRnd < 0) drillEffectRnd = size;
|
||||
}
|
||||
|
||||
@@ -155,7 +160,7 @@ public class Drill extends Block{
|
||||
Draw.color();
|
||||
}
|
||||
}else{
|
||||
Tile to = tile.getLinkedTilesAs(this, tempTiles).find(t -> t.drop() != null && (t.drop().hardness > tier || t.drop() == blockedItem));
|
||||
Tile to = tile.getLinkedTilesAs(this, tempTiles).find(t -> t.drop() != null && (t.drop().hardness > tier || (blockedItems != null && blockedItems.contains(t.drop()))));
|
||||
Item item = to == null ? null : to.drop();
|
||||
if(item != null){
|
||||
drawPlaceText(Core.bundle.get("bar.drilltierreq"), x, y, valid);
|
||||
@@ -172,7 +177,7 @@ public class Drill extends Block{
|
||||
super.setStats();
|
||||
|
||||
stats.add(Stat.drillTier, StatValues.drillables(drillTime, hardnessDrillMultiplier, size * size, drillMultipliers, b -> b instanceof Floor f && !f.wallOre && f.itemDrop != null &&
|
||||
f.itemDrop.hardness <= tier && f.itemDrop != blockedItem && (indexer.isBlockPresent(f) || state.isMenu())));
|
||||
f.itemDrop.hardness <= tier && (blockedItems == null || !blockedItems.contains(f.itemDrop)) && (indexer.isBlockPresent(f) || state.isMenu())));
|
||||
|
||||
stats.add(Stat.drillSpeed, 60f / drillTime * size * size, StatUnit.itemsSecond);
|
||||
|
||||
@@ -227,7 +232,7 @@ public class Drill extends Block{
|
||||
public boolean canMine(Tile tile){
|
||||
if(tile == null || tile.block().isStatic()) return false;
|
||||
Item drops = tile.drop();
|
||||
return drops != null && drops.hardness <= tier && drops != blockedItem;
|
||||
return drops != null && drops.hardness <= tier && (blockedItems == null || !blockedItems.contains(drops));
|
||||
}
|
||||
|
||||
public class DrillBuild extends Building{
|
||||
@@ -315,11 +320,14 @@ public class Drill extends Block{
|
||||
}
|
||||
|
||||
if(dominantItems > 0 && progress >= delay && items.total() < itemCapacity){
|
||||
offload(dominantItem);
|
||||
int amount = (int)(progress / delay);
|
||||
for(int i = 0; i < amount; i++){
|
||||
offload(dominantItem);
|
||||
}
|
||||
|
||||
progress %= delay;
|
||||
|
||||
if(wasVisible && Mathf.chanceDelta(updateEffectChance * warmup)) drillEffect.at(x + Mathf.range(drillEffectRnd), y + Mathf.range(drillEffectRnd), dominantItem.color);
|
||||
if(wasVisible && Mathf.chanceDelta(drillEffectChance * warmup)) drillEffect.at(x + Mathf.range(drillEffectRnd), y + Mathf.range(drillEffectRnd), dominantItem.color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ public class GenericCrafter extends Block{
|
||||
public Effect craftEffect = Fx.none;
|
||||
public Effect updateEffect = Fx.none;
|
||||
public float updateEffectChance = 0.04f;
|
||||
public float updateEffectSpread = 4f;
|
||||
public float warmupSpeed = 0.019f;
|
||||
/** Only used for legacy cultivator blocks. */
|
||||
public boolean legacyReadWarmup = false;
|
||||
@@ -233,7 +234,7 @@ public class GenericCrafter extends Block{
|
||||
}
|
||||
|
||||
if(wasVisible && Mathf.chanceDelta(updateEffectChance)){
|
||||
updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4));
|
||||
updateEffect.at(x + Mathf.range(size * updateEffectSpread), y + Mathf.range(size * updateEffectSpread));
|
||||
}
|
||||
}else{
|
||||
warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class WallCrafter extends Block{
|
||||
|
||||
if(consItems && itemConsumer instanceof ConsumeItems coni){
|
||||
stats.remove(Stat.booster);
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("{0}" + StatUnit.timesSpeed.localized(), stats.timePeriod, itemBoostIntensity, 0f, coni.items, i -> Structs.contains(coni.items, s -> s.item == i)));
|
||||
stats.add(Stat.booster, StatValues.itemBoosters("{0}" + StatUnit.timesSpeed.localized(), stats.timePeriod, itemBoostIntensity, 0f, coni.items));
|
||||
}
|
||||
|
||||
if(liquidBoostIntensity != 1 && findConsumer(f -> f instanceof ConsumeLiquidBase && f.booster) instanceof ConsumeLiquidBase consBase){
|
||||
|
||||
@@ -88,6 +88,7 @@ public class ItemSource extends Block{
|
||||
while(counter >= limit){
|
||||
items.set(outputItem, 1);
|
||||
dump(outputItem);
|
||||
produced(outputItem);
|
||||
items.set(outputItem, 0);
|
||||
counter -= limit;
|
||||
}
|
||||
|
||||
@@ -27,18 +27,19 @@ import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.meta.*;
|
||||
import mindustry.world.modules.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class CoreBlock extends StorageBlock{
|
||||
protected static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f, cloudAlpha = 0.81f;
|
||||
protected static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
|
||||
public static final float cloudScaling = 1700f, cfinScl = -2f, cfinOffset = 0.3f, calphaFinOffset = 0.25f, cloudAlpha = 0.81f;
|
||||
public static final float[] cloudAlphas = {0, 0.5f, 1f, 0.1f, 0, 0f};
|
||||
|
||||
//hacky way to pass item modules between methods
|
||||
private static ItemModule nextItems;
|
||||
protected static final float[] thrusterSizes = {0f, 0f, 0f, 0f, 0.3f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f};
|
||||
public static final float[] thrusterSizes = {0f, 0f, 0f, 0f, 0.3f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 0f};
|
||||
|
||||
public @Load(value = "@-thruster1", fallback = "clear-effect") TextureRegion thruster1; //top right
|
||||
public @Load(value = "@-thruster2", fallback = "clear-effect") TextureRegion thruster2; //bot left
|
||||
@@ -230,113 +231,14 @@ public class CoreBlock extends StorageBlock{
|
||||
}
|
||||
}
|
||||
|
||||
public void drawLanding(CoreBuild build, float x, float y){
|
||||
float fin = renderer.getLandTimeIn();
|
||||
float fout = 1f - fin;
|
||||
|
||||
float scl = Scl.scl(4f) / renderer.getDisplayScale();
|
||||
float shake = 0f;
|
||||
float s = region.width * region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
|
||||
float rotation = Interp.pow2In.apply(fout) * 135f;
|
||||
x += Mathf.range(shake);
|
||||
y += Mathf.range(shake);
|
||||
float thrustOpen = 0.25f;
|
||||
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
|
||||
float thrusterSize = Mathf.sample(thrusterSizes, fin);
|
||||
|
||||
//when launching, thrusters stay out the entire time.
|
||||
if(renderer.isLaunching()){
|
||||
Interp i = Interp.pow2Out;
|
||||
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
|
||||
thrusterSize = i.apply(Mathf.clamp(fout*9f));
|
||||
}
|
||||
|
||||
Draw.color(Pal.lightTrail);
|
||||
//TODO spikier heat
|
||||
Draw.rect("circle-shadow", x, y, s, s);
|
||||
|
||||
Draw.scl(scl);
|
||||
|
||||
//draw thruster flame
|
||||
float strength = (1f + (size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
|
||||
float offset = (size - 3) * 3f * scl;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tmp.v1.trns(i * 90 + rotation, 1f);
|
||||
|
||||
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*2f + offset);
|
||||
Draw.color(build.team.color);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
|
||||
|
||||
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
|
||||
Draw.color(Color.white);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
|
||||
}
|
||||
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
|
||||
Drawf.spinSprite(region, x, y, rotation);
|
||||
|
||||
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
Draw.alpha(1f);
|
||||
|
||||
if(teamRegions[build.team.id] == teamRegion) Draw.color(build.team.color);
|
||||
|
||||
Drawf.spinSprite(teamRegions[build.team.id], x, y, rotation);
|
||||
|
||||
Draw.color();
|
||||
Draw.scl();
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
|
||||
float length = thrusterLength * (frame - 1f) - 1f/4f;
|
||||
float alpha = Draw.getColorAlpha();
|
||||
|
||||
//two passes for consistent lighting
|
||||
for(int j = 0; j < 2; j++){
|
||||
for(int i = 0; i < 4; i++){
|
||||
var reg = i >= 2 ? thruster2 : thruster1;
|
||||
float rot = (i * 90) + rotation % 90f;
|
||||
Tmp.v1.trns(rot, length * Draw.xscl);
|
||||
|
||||
//second pass applies extra layer of shading
|
||||
if(j == 1){
|
||||
Tmp.v1.rotate(-90f);
|
||||
Draw.alpha((rotation % 90f) / 90f * alpha);
|
||||
rot -= 90f;
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}else{
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
Draw.alpha(1f);
|
||||
}
|
||||
|
||||
public class CoreBuild extends Building{
|
||||
public class CoreBuild extends Building implements LaunchAnimator{
|
||||
public int storageCapacity;
|
||||
public boolean noEffect = false;
|
||||
public Team lastDamage = Team.derelict;
|
||||
public float iframes = -1f;
|
||||
public float thrusterTime = 0f;
|
||||
|
||||
protected float cloudSeed;
|
||||
|
||||
//utility methods for less Block-to-CoreBlock casts. also allows for more customization
|
||||
public float landDuration(){
|
||||
return landDuration;
|
||||
}
|
||||
|
||||
public Music landMusic(){
|
||||
return landMusic;
|
||||
}
|
||||
|
||||
public Music launchMusic(){
|
||||
return launchMusic;
|
||||
}
|
||||
protected float cloudSeed, landParticleTimer;
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
@@ -357,11 +259,26 @@ public class CoreBlock extends StorageBlock{
|
||||
}
|
||||
}
|
||||
|
||||
// `launchType` is null if it's landing instead of launching.
|
||||
public void beginLaunch(@Nullable CoreBlock launchType){
|
||||
@Override
|
||||
public float launchDuration(){
|
||||
return landDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music landMusic(){
|
||||
return landMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Music launchMusic(){
|
||||
return launchMusic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginLaunch(boolean launching){
|
||||
cloudSeed = Mathf.random(1f);
|
||||
if(launchType != null){
|
||||
Fx.coreLaunchConstruct.at(x, y, launchType.size);
|
||||
if(launching){
|
||||
Fx.coreLaunchConstruct.at(x, y, size);
|
||||
}
|
||||
|
||||
if(!headless){
|
||||
@@ -373,7 +290,7 @@ public class CoreBlock extends StorageBlock{
|
||||
image.color.a = 0f;
|
||||
image.touchable = Touchable.disabled;
|
||||
image.setFillParent(true);
|
||||
image.actions(Actions.delay((landDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
|
||||
image.actions(Actions.delay((launchDuration() - margin) / 60f), Actions.fadeIn(margin / 60f, Interp.pow2In), Actions.delay(6f / 60f), Actions.remove());
|
||||
image.update(() -> {
|
||||
image.toFront();
|
||||
ui.loadfrag.toFront();
|
||||
@@ -397,7 +314,7 @@ public class CoreBlock extends StorageBlock{
|
||||
});
|
||||
Core.scene.add(image);
|
||||
|
||||
Time.run(landDuration(), () -> {
|
||||
Time.run(launchDuration(), () -> {
|
||||
launchEffect.at(this);
|
||||
Effect.shake(5f, 5f, this);
|
||||
thrusterTime = 1f;
|
||||
@@ -412,9 +329,11 @@ public class CoreBlock extends StorageBlock{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endLaunch(){}
|
||||
|
||||
public void drawLanding(CoreBlock block){
|
||||
@Override
|
||||
public void drawLaunch(){
|
||||
var clouds = Core.assets.get("sprites/clouds.png", Texture.class);
|
||||
|
||||
float fin = renderer.getLandTimeIn();
|
||||
@@ -432,7 +351,7 @@ public class CoreBlock extends StorageBlock{
|
||||
});
|
||||
Draw.color();
|
||||
|
||||
block.drawLanding(this, x, y);
|
||||
drawLanding(x, y);
|
||||
|
||||
Draw.color();
|
||||
Draw.mixcol(Color.white, Interp.pow5In.apply(fout));
|
||||
@@ -466,6 +385,92 @@ public class CoreBlock extends StorageBlock{
|
||||
}
|
||||
}
|
||||
|
||||
public void drawLanding(float x, float y){
|
||||
float fin = renderer.getLandTimeIn();
|
||||
float fout = 1f - fin;
|
||||
|
||||
float scl = Scl.scl(4f) / renderer.getDisplayScale();
|
||||
float shake = 0f;
|
||||
float s = region.width * region.scl() * scl * 3.6f * Interp.pow2Out.apply(fout);
|
||||
float rotation = Interp.pow2In.apply(fout) * 135f;
|
||||
x += Mathf.range(shake);
|
||||
y += Mathf.range(shake);
|
||||
float thrustOpen = 0.25f;
|
||||
float thrusterFrame = fin >= thrustOpen ? 1f : fin / thrustOpen;
|
||||
float thrusterSize = Mathf.sample(thrusterSizes, fin);
|
||||
|
||||
//when launching, thrusters stay out the entire time.
|
||||
if(renderer.isLaunching()){
|
||||
Interp i = Interp.pow2Out;
|
||||
thrusterFrame = i.apply(Mathf.clamp(fout*13f));
|
||||
thrusterSize = i.apply(Mathf.clamp(fout*9f));
|
||||
}
|
||||
|
||||
Draw.color(Pal.lightTrail);
|
||||
//TODO spikier heat
|
||||
Draw.rect("circle-shadow", x, y, s, s);
|
||||
|
||||
Draw.scl(scl);
|
||||
|
||||
//draw thruster flame
|
||||
float strength = (1f + (size - 3)/2.5f) * scl * thrusterSize * (0.95f + Mathf.absin(2f, 0.1f));
|
||||
float offset = (size - 3) * 3f * scl;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tmp.v1.trns(i * 90 + rotation, 1f);
|
||||
|
||||
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*2f + offset);
|
||||
Draw.color(team.color);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 6f * strength);
|
||||
|
||||
Tmp.v1.setLength((size * tilesize/2f + 1f)*scl + strength*0.5f + offset);
|
||||
Draw.color(Color.white);
|
||||
Fill.circle(Tmp.v1.x + x, Tmp.v1.y + y, 3.5f * strength);
|
||||
}
|
||||
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
|
||||
Drawf.spinSprite(region, x, y, rotation);
|
||||
|
||||
Draw.alpha(Interp.pow4In.apply(thrusterFrame));
|
||||
drawLandingThrusters(x, y, rotation, thrusterFrame);
|
||||
Draw.alpha(1f);
|
||||
|
||||
if(teamRegions[team.id] == teamRegion) Draw.color(team.color);
|
||||
|
||||
Drawf.spinSprite(teamRegions[team.id], x, y, rotation);
|
||||
|
||||
Draw.color();
|
||||
Draw.scl();
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
protected void drawLandingThrusters(float x, float y, float rotation, float frame){
|
||||
float length = thrusterLength * (frame - 1f) - 1f/4f;
|
||||
float alpha = Draw.getColorAlpha();
|
||||
|
||||
//two passes for consistent lighting
|
||||
for(int j = 0; j < 2; j++){
|
||||
for(int i = 0; i < 4; i++){
|
||||
var reg = i >= 2 ? thruster2 : thruster1;
|
||||
float rot = (i * 90) + rotation % 90f;
|
||||
Tmp.v1.trns(rot, length * Draw.xscl);
|
||||
|
||||
//second pass applies extra layer of shading
|
||||
if(j == 1){
|
||||
Tmp.v1.rotate(-90f);
|
||||
Draw.alpha((rotation % 90f) / 90f * alpha);
|
||||
rot -= 90f;
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}else{
|
||||
Draw.alpha(alpha);
|
||||
Draw.rect(reg, x + Tmp.v1.x, y + Tmp.v1.y, rot);
|
||||
}
|
||||
}
|
||||
}
|
||||
Draw.alpha(1f);
|
||||
}
|
||||
|
||||
public void drawThrusters(float frame){
|
||||
float length = thrusterLength * (frame - 1f) - 1f/4f;
|
||||
for(int i = 0; i < 4; i++){
|
||||
@@ -545,28 +550,26 @@ public class CoreBlock extends StorageBlock{
|
||||
}
|
||||
|
||||
/** @return Camera zoom while landing or launching. May optionally do other things such as setting camera position to itself. */
|
||||
public float zoomLaunching(){
|
||||
@Override
|
||||
public float zoomLaunch(){
|
||||
Core.camera.position.set(this);
|
||||
return landZoomInterp.apply(Scl.scl(landZoomFrom), Scl.scl(landZoomTo), renderer.getLandTimeIn());
|
||||
}
|
||||
|
||||
public void updateLaunching(){
|
||||
updateLandParticles();
|
||||
}
|
||||
@Override
|
||||
public void updateLaunch(){
|
||||
float in = renderer.getLandTimeIn() * launchDuration();
|
||||
float tsize = Mathf.sample(thrusterSizes, (in + 35f) / launchDuration());
|
||||
|
||||
public void updateLandParticles(){
|
||||
float in = renderer.getLandTimeIn() * landDuration();
|
||||
float tsize = Mathf.sample(thrusterSizes, (in + 35f) / landDuration());
|
||||
|
||||
renderer.setLandPTimer(renderer.getLandPTimer() + tsize * Time.delta);
|
||||
if(renderer.getLandTime() >= 1f){
|
||||
landParticleTimer += tsize * Time.delta;
|
||||
if(landParticleTimer >= 1f){
|
||||
tile.getLinkedTiles(t -> {
|
||||
if(Mathf.chance(0.4f)){
|
||||
Fx.coreLandDust.at(t.worldx(), t.worldy(), angleTo(t.worldx(), t.worldy()) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f)));
|
||||
}
|
||||
});
|
||||
|
||||
renderer.setLandPTimer(0f);
|
||||
landParticleTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package mindustry.world.consumers;
|
||||
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
/** For mods. I don't use this (yet). */
|
||||
public class ConsumeItemCharged extends ConsumeItemFilter{
|
||||
public class ConsumeItemCharged extends ConsumeItemEfficiency{
|
||||
public float minCharge;
|
||||
|
||||
public ConsumeItemCharged(float minCharge){
|
||||
@@ -16,8 +16,7 @@ public class ConsumeItemCharged extends ConsumeItemFilter{
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiencyMultiplier(Building build){
|
||||
var item = getConsumed(build);
|
||||
return item == null ? 0f : item.charge;
|
||||
public float itemEfficiencyMultiplier(Item item){
|
||||
return item.charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package mindustry.world.consumers;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class ConsumeItemEfficiency extends ConsumeItemFilter{
|
||||
/** This has no effect on the consumer itself, but is used for stat display. */
|
||||
public @Nullable ObjectFloatMap<Item> itemDurationMultipliers;
|
||||
|
||||
public ConsumeItemEfficiency(Boolf<Item> item){
|
||||
super(item);
|
||||
}
|
||||
|
||||
public ConsumeItemEfficiency(){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(Stats stats){
|
||||
stats.add(booster ? Stat.booster : Stat.input, StatValues.itemEffMultiplier(this::itemEfficiencyMultiplier, stats.timePeriod, filter, itemDurationMultipliers));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.world.consumers;
|
||||
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class ConsumeItemExplosive extends ConsumeItemFilter{
|
||||
public class ConsumeItemExplosive extends ConsumeItemEfficiency{
|
||||
public float minExplosiveness;
|
||||
|
||||
public ConsumeItemExplosive(float minCharge){
|
||||
@@ -15,8 +15,7 @@ public class ConsumeItemExplosive extends ConsumeItemFilter{
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiencyMultiplier(Building build){
|
||||
var item = getConsumed(build);
|
||||
return item == null ? 0f : item.explosiveness;
|
||||
public float itemEfficiencyMultiplier(Item item){
|
||||
return item.explosiveness;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,16 @@ public class ConsumeItemFilter extends Consume{
|
||||
|
||||
@Override
|
||||
public void display(Stats stats){
|
||||
stats.add(booster ? Stat.booster : Stat.input, stats.timePeriod < 0 ? StatValues.items(filter) : StatValues.items(stats.timePeriod, filter));
|
||||
stats.add(booster ? Stat.booster : Stat.input, StatValues.items(stats.timePeriod, filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiencyMultiplier(Building build){
|
||||
var item = getConsumed(build);
|
||||
return item == null ? 0f : itemEfficiencyMultiplier(item);
|
||||
}
|
||||
|
||||
public float itemEfficiencyMultiplier(Item item){
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.world.consumers;
|
||||
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class ConsumeItemFlammable extends ConsumeItemFilter{
|
||||
public class ConsumeItemFlammable extends ConsumeItemEfficiency{
|
||||
public float minFlammability;
|
||||
|
||||
public ConsumeItemFlammable(float minFlammability){
|
||||
@@ -15,8 +15,7 @@ public class ConsumeItemFlammable extends ConsumeItemFilter{
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiencyMultiplier(Building build){
|
||||
var item = getConsumed(build);
|
||||
return item == null ? 0f : item.flammability;
|
||||
public float itemEfficiencyMultiplier(Item item){
|
||||
return item.flammability;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.world.consumers;
|
||||
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class ConsumeItemRadioactive extends ConsumeItemFilter{
|
||||
public class ConsumeItemRadioactive extends ConsumeItemEfficiency{
|
||||
public float minRadioactivity;
|
||||
|
||||
public ConsumeItemRadioactive(float minRadioactivity){
|
||||
@@ -15,8 +15,7 @@ public class ConsumeItemRadioactive extends ConsumeItemFilter{
|
||||
}
|
||||
|
||||
@Override
|
||||
public float efficiencyMultiplier(Building build){
|
||||
var item = getConsumed(build);
|
||||
return item == null ? 0f : item.radioactivity;
|
||||
public float itemEfficiencyMultiplier(Item item){
|
||||
return item.radioactivity;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user