Merge branch 'master' into schematic
This commit is contained in:
@@ -33,6 +33,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
|
||||
@Override
|
||||
public void setup(){
|
||||
loadLogger();
|
||||
|
||||
loader = new LoadRenderer();
|
||||
Events.fire(new ClientCreateEvent());
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public class Vars implements Loadable{
|
||||
/** Maximum schematic size.*/
|
||||
public static final int maxSchematicSize = 32;
|
||||
/** All schematic base64 starts with this string.*/
|
||||
public static final String schematicBaseStart ="bXNjaAB";
|
||||
public static final String schematicBaseStart ="bXNjaA";
|
||||
/** IO buffer size. */
|
||||
public static final int bufferSize = 8192;
|
||||
/** global charset, since Android doesn't support the Charsets class */
|
||||
|
||||
@@ -5,11 +5,13 @@ import arc.func.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -41,6 +43,8 @@ public class WaveSpawner{
|
||||
spawning = true;
|
||||
|
||||
for(SpawnGroup group : state.rules.spawns){
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
||||
|
||||
if(group.type.flying){
|
||||
@@ -125,14 +129,18 @@ public class WaveSpawner{
|
||||
}
|
||||
|
||||
private void spawnEffect(Unit unit){
|
||||
Fx.unitSpawn.at(unit.x(), unit.y(), 0f, unit);
|
||||
Time.run(30f, () -> {
|
||||
unit.add();
|
||||
Fx.spawn.at(unit);
|
||||
});
|
||||
Call.spawnEffect(unit.x, unit.y, unit.type());
|
||||
Time.run(30f, unit::add);
|
||||
}
|
||||
|
||||
private interface SpawnConsumer{
|
||||
void accept(float x, float y, boolean shockwave);
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
public static void spawnEffect(float x, float y, UnitType type){
|
||||
Fx.unitSpawn.at(x, y, 0f, type);
|
||||
|
||||
Time.run(30f, () -> Fx.spawn.at(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,6 @@ import arc.math.geom.*;
|
||||
public interface FormationMember{
|
||||
/** Returns the target location of this formation member. */
|
||||
Vec3 formationPos();
|
||||
|
||||
float formationSize();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import arc.math.geom.*;
|
||||
*/
|
||||
public abstract class FormationPattern{
|
||||
public int slots;
|
||||
/** Spacing between members. */
|
||||
public float spacing = 20f;
|
||||
|
||||
/** Returns the location of the given slot index. */
|
||||
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class ArrowFormation extends FormationPattern{
|
||||
//total triangular numbers
|
||||
private static final int totalTris = 30;
|
||||
//triangular number table
|
||||
private static final int[] triTable = new int[totalTris];
|
||||
|
||||
//calculat triangular numbers
|
||||
static{
|
||||
int sum = 0;
|
||||
for(int i = 0; i < totalTris; i++){
|
||||
triTable[i] = sum;
|
||||
sum += (i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
//TODO
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class SquareFormation extends FormationPattern{
|
||||
public float spacing = 20;
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -22,6 +19,8 @@ public class BuilderAI extends AIController{
|
||||
builder.lookAt(builder.vel().angle());
|
||||
}
|
||||
|
||||
builder.updateBuilding(true);
|
||||
|
||||
//approach request if building
|
||||
if(builder.buildPlan() != null){
|
||||
BuildPlan req = builder.buildPlan();
|
||||
@@ -63,19 +62,4 @@ public class BuilderAI extends AIController{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void moveTo(Position target, float circleLength){
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,49 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class FlyingAI extends AIController{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
public void updateMovement(){
|
||||
if(unit.moving()){
|
||||
unit.rotation(unit.vel().angle());
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
|
||||
if(unit.isFlying()){
|
||||
unit.wobble();
|
||||
}
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y())){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
|
||||
if(target == null) targetClosestEnemyFlag(BlockFlag.producer);
|
||||
if(target == null) targetClosestEnemyFlag(BlockFlag.turret);
|
||||
}
|
||||
|
||||
boolean shoot = false;
|
||||
|
||||
if(target != null && unit.hasWeapons()){
|
||||
if(unit.type().weapons.first().rotate){
|
||||
moveTo(unit.range() * 0.85f);
|
||||
moveTo(target, unit.range() * 0.8f);
|
||||
unit.lookAt(target);
|
||||
}else{
|
||||
attack(80f);
|
||||
}
|
||||
|
||||
shoot = unit.inRange(target);
|
||||
|
||||
if(shoot && unit.type().hasWeapons()){
|
||||
Vec2 to = Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed);
|
||||
unit.aim(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unit.controlWeapons(shoot, shoot);
|
||||
@Override
|
||||
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
|
||||
if(result != null) return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO clean up
|
||||
|
||||
protected void circle(float circleLength){
|
||||
circle(circleLength, unit.type().speed);
|
||||
}
|
||||
|
||||
protected void circle(float circleLength, float speed){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
if(vec.len() < circleLength){
|
||||
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
|
||||
}
|
||||
|
||||
vec.setLength(speed * Time.delta);
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
protected void moveTo(float circleLength){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
protected void attack(float circleLength){
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
|
||||
@@ -60,6 +60,15 @@ public class FormationAI extends AIController implements FormationMember{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float formationSize(){
|
||||
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
|
||||
//TODO return formation size
|
||||
//eturn ((Commanderc)unit).formation().
|
||||
}
|
||||
return unit.hitSize * 1.7f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBeingControlled(Unit player){
|
||||
return leader == player;
|
||||
|
||||
@@ -12,15 +12,7 @@ import static mindustry.Vars.pathfinder;
|
||||
public class GroundAI extends AIController{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
}
|
||||
public void updateMovement(){
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
@@ -34,20 +26,13 @@ public class GroundAI extends AIController{
|
||||
}
|
||||
}
|
||||
|
||||
boolean rotate = false, shoot = false;
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range())){
|
||||
rotate = true;
|
||||
shoot = unit.within(target, unit.range());
|
||||
|
||||
if(unit.type().hasWeapons()){
|
||||
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
}
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel().angle());
|
||||
}
|
||||
|
||||
unit.controlWeapons(rotate, shoot);
|
||||
}
|
||||
|
||||
protected void moveToCore(FlagTarget path){
|
||||
|
||||
88
core/src/mindustry/ai/types/MinerAI.java
Normal file
88
core/src/mindustry/ai/types/MinerAI.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MinerAI extends AIController{
|
||||
boolean mining = true;
|
||||
Item targetItem;
|
||||
Tile ore;
|
||||
|
||||
@Override
|
||||
protected void updateMovement(){
|
||||
if(unit.moving()){
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
|
||||
if(unit.isFlying()){
|
||||
unit.wobble();
|
||||
}
|
||||
|
||||
Building core = unit.closestCore();
|
||||
|
||||
if(!(unit instanceof Minerc) || core == null) return;
|
||||
|
||||
Minerc miner = (Minerc)unit;
|
||||
|
||||
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
|
||||
miner.mineTile(null);
|
||||
}
|
||||
|
||||
if(mining){
|
||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
||||
|
||||
//core full of the target item, do nothing
|
||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||
unit.clearItem();
|
||||
return;
|
||||
}
|
||||
|
||||
//if inventory is full, drop it off.
|
||||
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
||||
mining = false;
|
||||
}else{
|
||||
if(retarget() && targetItem != null){
|
||||
ore = indexer.findClosestOre(unit.x, unit.y, targetItem);
|
||||
}
|
||||
|
||||
if(ore != null){
|
||||
moveTo(ore, unit.type().range / 1.5f);
|
||||
|
||||
if(unit.within(ore, unit.type().range)){
|
||||
miner.mineTile(ore);
|
||||
}
|
||||
|
||||
if(ore.block() != Blocks.air){
|
||||
mining = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(unit.stack.amount == 0){
|
||||
mining = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.within(core, unit.type().range)){
|
||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
}
|
||||
|
||||
unit.clearItem();
|
||||
mining = true;
|
||||
}
|
||||
|
||||
circle(core, unit.type().range / 1.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTargeting(){
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class SuicideAI extends GroundAI{
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
targetClosest();
|
||||
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
||||
}
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
@@ -68,6 +68,8 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
PhysicRef ref = entity.physref();
|
||||
|
||||
if(ref.wasGround != grounded){
|
||||
if(ref.body.getFixtureList().isEmpty()) continue;
|
||||
|
||||
//set correct filter
|
||||
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
|
||||
ref.wasGround = grounded;
|
||||
|
||||
@@ -18,6 +18,8 @@ import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.experimental.*;
|
||||
import mindustry.world.blocks.legacy.*;
|
||||
import mindustry.world.blocks.liquid.*;
|
||||
import mindustry.world.blocks.logic.*;
|
||||
import mindustry.world.blocks.logic.MessageBlock;
|
||||
import mindustry.world.blocks.power.*;
|
||||
import mindustry.world.blocks.production.*;
|
||||
import mindustry.world.blocks.sandbox.*;
|
||||
@@ -48,7 +50,7 @@ public class Blocks implements ContentList{
|
||||
melter, separator, disassembler, sporePress, pulverizer, incinerator, coalCentrifuge,
|
||||
|
||||
//sandbox
|
||||
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, message, illuminator,
|
||||
powerSource, powerVoid, itemSource, itemVoid, liquidSource, liquidVoid, illuminator,
|
||||
|
||||
//defense
|
||||
copperWall, copperWallLarge, titaniumWall, titaniumWallLarge, plastaniumWall, plastaniumWallLarge, thoriumWall, thoriumWallLarge, door, doorLarge,
|
||||
@@ -80,8 +82,11 @@ public class Blocks implements ContentList{
|
||||
additiveReconstructor, multiplicativeReconstructor, exponentialReconstructor, tetrativeReconstructor,
|
||||
repairPoint, resupplyPoint,
|
||||
|
||||
//logic
|
||||
message, switchBlock, microProcessor, logicProcessor, logicDisplay, memoryCell,
|
||||
|
||||
//campaign
|
||||
launchPad, launchPadLarge, dataProcessor,
|
||||
launchPad, launchPadLarge,
|
||||
|
||||
//misc experimental
|
||||
blockForge, blockLoader, blockUnloader;
|
||||
@@ -134,6 +139,7 @@ public class Blocks implements ContentList{
|
||||
speedMultiplier = 0.2f;
|
||||
variants = 0;
|
||||
liquidDrop = Liquids.water;
|
||||
liquidMultiplier = 1.5f;
|
||||
isLiquid = true;
|
||||
status = StatusEffects.wet;
|
||||
statusDuration = 120f;
|
||||
@@ -246,11 +252,13 @@ public class Blocks implements ContentList{
|
||||
sand = new Floor("sand"){{
|
||||
itemDrop = Items.sand;
|
||||
playerUnmineable = true;
|
||||
attributes.set(Attribute.oil, 0.7f);
|
||||
}};
|
||||
|
||||
darksand = new Floor("darksand"){{
|
||||
itemDrop = Items.sand;
|
||||
playerUnmineable = true;
|
||||
attributes.set(Attribute.oil, 1.5f);
|
||||
}};
|
||||
|
||||
((ShallowLiquid)darksandTaintedWater).set(Blocks.taintedWater, Blocks.darksand);
|
||||
@@ -267,7 +275,8 @@ public class Blocks implements ContentList{
|
||||
|
||||
salt = new Floor("salt"){{
|
||||
variants = 0;
|
||||
attributes.set(Attribute.water, -0.2f);
|
||||
attributes.set(Attribute.water, -0.25f);
|
||||
attributes.set(Attribute.oil, 0.3f);
|
||||
}};
|
||||
|
||||
snow = new Floor("snow"){{
|
||||
@@ -355,7 +364,7 @@ public class Blocks implements ContentList{
|
||||
|
||||
shale = new Floor("shale"){{
|
||||
variants = 3;
|
||||
attributes.set(Attribute.oil, 0.15f);
|
||||
attributes.set(Attribute.oil, 1f);
|
||||
}};
|
||||
|
||||
shaleRocks = new StaticWall("shalerocks"){{
|
||||
@@ -498,8 +507,8 @@ public class Blocks implements ContentList{
|
||||
siliconCrucible = new AttributeSmelter("silicon-crucible"){{
|
||||
requirements(Category.crafting, with(Items.titanium, 120, Items.metaglass, 80, Items.plastanium, 35, Items.silicon, 60));
|
||||
craftEffect = Fx.smeltsmoke;
|
||||
outputItem = new ItemStack(Items.silicon, 5);
|
||||
craftTime = 140f;
|
||||
outputItem = new ItemStack(Items.silicon, 6);
|
||||
craftTime = 90f;
|
||||
size = 3;
|
||||
hasPower = true;
|
||||
hasLiquids = false;
|
||||
@@ -885,10 +894,10 @@ public class Blocks implements ContentList{
|
||||
requirements(Category.effect, with(Items.lead, 25, Items.silicon, 12));
|
||||
hasShadow = false;
|
||||
health = 40;
|
||||
damage = 11;
|
||||
damage = 23;
|
||||
tileDamage = 7f;
|
||||
length = 10;
|
||||
tendrils = 5;
|
||||
tendrils = 4;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
@@ -1270,6 +1279,8 @@ public class Blocks implements ContentList{
|
||||
size = 3;
|
||||
liquidCapacity = 30f;
|
||||
attribute = Attribute.oil;
|
||||
baseEfficiency = 0f;
|
||||
itemUseTime = 60f;
|
||||
|
||||
consumes.item(Items.sand);
|
||||
consumes.power(3f);
|
||||
@@ -1299,18 +1310,18 @@ public class Blocks implements ContentList{
|
||||
itemCapacity = 9000;
|
||||
size = 4;
|
||||
|
||||
unitCapModifier = 16;
|
||||
unitCapModifier = 14;
|
||||
}};
|
||||
|
||||
coreNucleus = new CoreBlock("core-nucleus"){{
|
||||
requirements(Category.effect, with(Items.copper, 1000, Items.lead, 1000));
|
||||
requirements(Category.effect, with(Items.copper, 8000, Items.lead, 8000, Items.silicon, 5000, Items.thorium, 4000));
|
||||
|
||||
unitType = UnitTypes.gamma;
|
||||
health = 4000;
|
||||
itemCapacity = 13000;
|
||||
size = 5;
|
||||
|
||||
unitCapModifier = 24;
|
||||
unitCapModifier = 20;
|
||||
}};
|
||||
|
||||
vault = new StorageBlock("vault"){{
|
||||
@@ -1441,7 +1452,7 @@ public class Blocks implements ContentList{
|
||||
recoilAmount = 2f;
|
||||
reloadTime = 90f;
|
||||
cooldown = 0.03f;
|
||||
powerUse = 2.5f;
|
||||
powerUse = 6f;
|
||||
shootShake = 2f;
|
||||
shootEffect = Fx.lancerLaserShoot;
|
||||
smokeEffect = Fx.none;
|
||||
@@ -1460,6 +1471,7 @@ public class Blocks implements ContentList{
|
||||
hitSize = 4;
|
||||
lifetime = 16f;
|
||||
drawSize = 400f;
|
||||
collidesAir = false;
|
||||
}};
|
||||
}};
|
||||
|
||||
@@ -1473,7 +1485,7 @@ public class Blocks implements ContentList{
|
||||
reloadTime = 35f;
|
||||
shootCone = 40f;
|
||||
rotatespeed = 8f;
|
||||
powerUse = 1.5f;
|
||||
powerUse = 5f;
|
||||
targetAir = false;
|
||||
range = 90f;
|
||||
shootEffect = Fx.lightningShoot;
|
||||
@@ -1496,7 +1508,7 @@ public class Blocks implements ContentList{
|
||||
health = 160 * size * size;
|
||||
rotateSpeed = 10;
|
||||
|
||||
consumes.power(3f);
|
||||
consumes.powerCond(3f, (TractorBeamEntity e) -> e.target != null);
|
||||
}};
|
||||
|
||||
swarmer = new ItemTurret("swarmer"){{
|
||||
@@ -1690,7 +1702,7 @@ public class Blocks implements ContentList{
|
||||
requirements(Category.units, with(Items.copper, 50, Items.lead, 120, Items.silicon, 80));
|
||||
plans = new UnitPlan[]{
|
||||
new UnitPlan(UnitTypes.dagger, 60f * 20, with(Items.silicon, 10, Items.lead, 10)),
|
||||
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.blastCompound, 10)),
|
||||
new UnitPlan(UnitTypes.crawler, 60f * 15, with(Items.silicon, 10, Items.coal, 20)),
|
||||
new UnitPlan(UnitTypes.nova, 60f * 40, with(Items.silicon, 30, Items.lead, 20, Items.titanium, 20)),
|
||||
};
|
||||
size = 3;
|
||||
@@ -1700,7 +1712,7 @@ public class Blocks implements ContentList{
|
||||
airFactory = new UnitFactory("air-factory"){{
|
||||
requirements(Category.units, with(Items.copper, 30, Items.lead, 70));
|
||||
plans = new UnitPlan[]{
|
||||
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 10)),
|
||||
new UnitPlan(UnitTypes.flare, 60f * 15, with(Items.silicon, 15)),
|
||||
new UnitPlan(UnitTypes.mono, 60f * 35, with(Items.silicon, 30, Items.lead, 15)),
|
||||
};
|
||||
size = 3;
|
||||
@@ -1836,10 +1848,6 @@ public class Blocks implements ContentList{
|
||||
alwaysUnlocked = true;
|
||||
}};
|
||||
|
||||
message = new MessageBlock("message"){{
|
||||
requirements(Category.effect, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
illuminator = new LightBlock("illuminator"){{
|
||||
requirements(Category.effect, BuildVisibility.lightingOnly, with(Items.graphite, 12, Items.silicon, 8));
|
||||
brightness = 0.67f;
|
||||
@@ -1877,14 +1885,50 @@ public class Blocks implements ContentList{
|
||||
consumes.power(6f);
|
||||
}};
|
||||
|
||||
dataProcessor = new ResearchBlock("data-processor"){{
|
||||
//requirements(Category.effect, BuildVisibility.campaignOnly, with(Items.copper, 200, Items.lead, 100));
|
||||
//endregion campaign
|
||||
//region logic
|
||||
|
||||
size = 3;
|
||||
alwaysUnlocked = true;
|
||||
message = new MessageBlock("message"){{
|
||||
requirements(Category.logic, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
//endregion campaign
|
||||
switchBlock = new SwitchBlock("switch"){{
|
||||
requirements(Category.logic, with(Items.graphite, 5));
|
||||
}};
|
||||
|
||||
microProcessor = new LogicBlock("micro-processor"){{
|
||||
requirements(Category.logic, with(Items.copper, 80, Items.lead, 50, Items.silicon, 60));
|
||||
|
||||
instructionsPerTick = 2;
|
||||
|
||||
size = 1;
|
||||
}};
|
||||
|
||||
logicProcessor = new LogicBlock("logic-processor"){{
|
||||
requirements(Category.logic, with(Items.lead, 320, Items.silicon, 140, Items.graphite, 80, Items.thorium, 70));
|
||||
|
||||
instructionsPerTick = 5;
|
||||
|
||||
range = 16 * 10;
|
||||
|
||||
size = 2;
|
||||
}};
|
||||
|
||||
logicDisplay = new LogicDisplay("logic-display"){{
|
||||
requirements(Category.logic, with(Items.copper, 200, Items.lead, 120, Items.silicon, 100, Items.metaglass, 50));
|
||||
|
||||
displaySize = 80;
|
||||
|
||||
size = 3;
|
||||
}};
|
||||
|
||||
memoryCell = new MemoryBlock("memory-cell"){{
|
||||
requirements(Category.logic, with(Items.graphite, 40, Items.silicon, 40));
|
||||
|
||||
memoryCapacity = 64;
|
||||
}};
|
||||
|
||||
//endregion
|
||||
//region experimental
|
||||
|
||||
blockForge = new BlockForge("block-forge"){{
|
||||
|
||||
@@ -191,7 +191,7 @@ public class Bullets implements ContentList{
|
||||
|
||||
fragGlass = new FlakBulletType(4f, 3){{
|
||||
lifetime = 70f;
|
||||
ammoMultiplier = 5f;
|
||||
ammoMultiplier = 3f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
reloadMultiplier = 0.8f;
|
||||
width = 6f;
|
||||
@@ -221,8 +221,8 @@ public class Bullets implements ContentList{
|
||||
fragExplosive = new FlakBulletType(4f, 5){{
|
||||
shootEffect = Fx.shootBig;
|
||||
ammoMultiplier = 4f;
|
||||
splashDamage = 15f;
|
||||
splashDamageRadius = 34f;
|
||||
splashDamage = 18f;
|
||||
splashDamageRadius = 55f;
|
||||
collidesGround = true;
|
||||
|
||||
status = StatusEffects.blasted;
|
||||
@@ -230,7 +230,8 @@ public class Bullets implements ContentList{
|
||||
}};
|
||||
|
||||
fragSurge = new FlakBulletType(4.5f, 13){{
|
||||
splashDamage = 45f;
|
||||
ammoMultiplier = 4f;
|
||||
splashDamage = 50f;
|
||||
splashDamageRadius = 40f;
|
||||
lightning = 2;
|
||||
lightningLength = 7;
|
||||
@@ -239,7 +240,7 @@ public class Bullets implements ContentList{
|
||||
explodeRange = 20f;
|
||||
}};
|
||||
|
||||
missileExplosive = new MissileBulletType(2.7f, 10, "missile"){{
|
||||
missileExplosive = new MissileBulletType(3f, 10){{
|
||||
width = 8f;
|
||||
height = 8f;
|
||||
shrinkY = 0f;
|
||||
@@ -247,7 +248,6 @@ public class Bullets implements ContentList{
|
||||
splashDamageRadius = 30f;
|
||||
splashDamage = 30f;
|
||||
ammoMultiplier = 4f;
|
||||
lifetime = 150f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
|
||||
@@ -255,7 +255,7 @@ public class Bullets implements ContentList{
|
||||
statusDuration = 60f;
|
||||
}};
|
||||
|
||||
missileIncendiary = new MissileBulletType(2.9f, 12, "missile"){{
|
||||
missileIncendiary = new MissileBulletType(3f, 12){{
|
||||
frontColor = Pal.lightishOrange;
|
||||
backColor = Pal.lightOrange;
|
||||
width = 7f;
|
||||
@@ -265,26 +265,24 @@ public class Bullets implements ContentList{
|
||||
homingPower = 0.08f;
|
||||
splashDamageRadius = 20f;
|
||||
splashDamage = 20f;
|
||||
lifetime = 160f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
status = StatusEffects.burning;
|
||||
}};
|
||||
|
||||
missileSurge = new MissileBulletType(4.4f, 20, "bullet"){{
|
||||
missileSurge = new MissileBulletType(3f, 20){{
|
||||
width = 8f;
|
||||
height = 8f;
|
||||
shrinkY = 0f;
|
||||
drag = -0.01f;
|
||||
splashDamageRadius = 28f;
|
||||
splashDamage = 40f;
|
||||
lifetime = 150f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
lightning = 2;
|
||||
lightningLength = 14;
|
||||
}};
|
||||
|
||||
standardCopper = new BasicBulletType(2.5f, 9, "bullet"){{
|
||||
standardCopper = new BasicBulletType(2.5f, 9){{
|
||||
width = 7f;
|
||||
height = 9f;
|
||||
lifetime = 60f;
|
||||
@@ -293,7 +291,7 @@ public class Bullets implements ContentList{
|
||||
ammoMultiplier = 2;
|
||||
}};
|
||||
|
||||
standardDense = new BasicBulletType(3.5f, 18, "bullet"){{
|
||||
standardDense = new BasicBulletType(3.5f, 18){{
|
||||
width = 9f;
|
||||
height = 12f;
|
||||
reloadMultiplier = 0.6f;
|
||||
@@ -430,34 +428,25 @@ public class Bullets implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
basicFlame = new BulletType(3f, 15f){
|
||||
{
|
||||
ammoMultiplier = 3f;
|
||||
hitSize = 7f;
|
||||
lifetime = 42f;
|
||||
pierce = true;
|
||||
drag = 0.05f;
|
||||
statusDuration = 60f * 4;
|
||||
shootEffect = Fx.shootSmallFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.burning;
|
||||
keepVelocity = false;
|
||||
hittable = false;
|
||||
}
|
||||
basicFlame = new BulletType(3.35f, 15f){{
|
||||
ammoMultiplier = 3f;
|
||||
hitSize = 7f;
|
||||
lifetime = 18f;
|
||||
pierce = true;
|
||||
statusDuration = 60f * 4;
|
||||
shootEffect = Fx.shootSmallFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.burning;
|
||||
keepVelocity = false;
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return 50f;
|
||||
}
|
||||
};
|
||||
|
||||
pyraFlame = new BulletType(3.3f, 22f){{
|
||||
pyraFlame = new BulletType(3.35f, 22f){{
|
||||
ammoMultiplier = 4f;
|
||||
hitSize = 7f;
|
||||
lifetime = 42f;
|
||||
lifetime = 18f;
|
||||
pierce = true;
|
||||
drag = 0.05f;
|
||||
statusDuration = 60f * 6;
|
||||
shootEffect = Fx.shootPyraFlame;
|
||||
hitEffect = Fx.hitFlameSmall;
|
||||
|
||||
@@ -26,15 +26,15 @@ public class Fx{
|
||||
none = new Effect(0, 0f, e -> {}),
|
||||
|
||||
unitSpawn = new Effect(30f, e -> {
|
||||
if(!(e.data instanceof Unit)) return;
|
||||
if(!(e.data instanceof UnitType)) return;
|
||||
|
||||
alpha(e.fin());
|
||||
|
||||
float scl = 1f + e.fout() * 2f;
|
||||
|
||||
Unit unit = e.data();
|
||||
rect(unit.type().region, e.x, e.y,
|
||||
unit.type().region.getWidth() * Draw.scl * scl, unit.type().region.getHeight() * Draw.scl * scl, 180f);
|
||||
UnitType unit = e.data();
|
||||
rect(unit.region, e.x, e.y,
|
||||
unit.region.getWidth() * Draw.scl * scl, unit.region.getHeight() * Draw.scl * scl, 180f);
|
||||
|
||||
}),
|
||||
|
||||
@@ -63,15 +63,15 @@ public class Fx{
|
||||
}),
|
||||
|
||||
unitDespawn = new Effect(100f, e -> {
|
||||
if(!(e.data instanceof Unitc)) return;
|
||||
if(!(e.data instanceof Unit) || e.<Unit>data().type() == null) return;
|
||||
|
||||
Unitc select = (Unitc)e.data;
|
||||
Unit select = e.data();
|
||||
float scl = e.fout(Interp.pow2Out);
|
||||
float p = Draw.scl;
|
||||
Draw.scl *= scl;
|
||||
|
||||
mixcol(Pal.accent, 1f);
|
||||
rect(select.type().icon(Cicon.full), select.x(), select.y(), select.rotation() - 90f);
|
||||
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
||||
reset();
|
||||
|
||||
Draw.scl = p;
|
||||
@@ -96,13 +96,13 @@ public class Fx{
|
||||
Fill.square(x, y, 1f * size, 45f);
|
||||
}),
|
||||
|
||||
itemTransfer = new Effect(10f, e -> {
|
||||
itemTransfer = new Effect(12f, e -> {
|
||||
if(!(e.data instanceof Position)) return;
|
||||
Position to = e.data();
|
||||
Tmp.v1.set(e.x, e.y).interpolate(Tmp.v2.set(to), e.fin(), Interp.pow3)
|
||||
.add(Tmp.v2.sub(e.x, e.y).nor().rotate90(1).scl(Mathf.randomSeedRange(e.id, 1f) * e.fslope() * 10f));
|
||||
float x = Tmp.v1.x, y = Tmp.v1.y;
|
||||
float size = Math.min(0.8f + e.rotation / 5f, 2);
|
||||
float size = 1f;
|
||||
|
||||
stroke(e.fslope() * 2f * size, Pal.accent);
|
||||
Lines.circle(x, y, e.fslope() * 2f * size);
|
||||
@@ -369,7 +369,7 @@ public class Fx{
|
||||
hitLiquid = new Effect(16, e -> {
|
||||
color(e.color);
|
||||
|
||||
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation + 180f, 60f, (x, y) -> {
|
||||
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 60f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
|
||||
});
|
||||
|
||||
@@ -654,13 +654,11 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
wet = new Effect(40f, e -> {
|
||||
wet = new Effect(80f, e -> {
|
||||
color(Liquids.water.color);
|
||||
alpha(Mathf.clamp(e.fin() * 2f));
|
||||
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
|
||||
});
|
||||
|
||||
Fill.circle(e.x, e.y, e.fout() * 1f);
|
||||
}),
|
||||
|
||||
sapped = new Effect(40f, e -> {
|
||||
@@ -672,6 +670,12 @@ public class Fx{
|
||||
|
||||
}),
|
||||
|
||||
sporeSlowed = new Effect(40f, e -> {
|
||||
color(Pal.spore);
|
||||
|
||||
Fill.circle(e.x, e.y, e.fslope() * 1.1f);
|
||||
}),
|
||||
|
||||
oily = new Effect(42f, e -> {
|
||||
color(Liquids.oil.color);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import mindustry.type.*;
|
||||
public class Planets implements ContentList{
|
||||
public static Planet
|
||||
sun,
|
||||
starter; //TODO rename
|
||||
serpulo;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
@@ -31,9 +31,8 @@ public class Planets implements ContentList{
|
||||
);
|
||||
}};
|
||||
|
||||
//TODO rename
|
||||
starter = new Planet("TODO", sun, 3, 1){{
|
||||
generator = new TODOPlanetGenerator();
|
||||
serpulo = new Planet("serpulo", sun, 3, 1){{
|
||||
generator = new SerpuloPlanetGenerator();
|
||||
meshLoader = () -> new HexMesh(this, 6);
|
||||
atmosphereColor = Color.valueOf("3c1b8f");
|
||||
startSector = 15;
|
||||
|
||||
@@ -1,162 +1,64 @@
|
||||
package mindustry.content;
|
||||
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
import static arc.struct.Seq.*;
|
||||
import static mindustry.content.Planets.*;
|
||||
|
||||
public class SectorPresets implements ContentList{
|
||||
public static SectorPreset
|
||||
groundZero,
|
||||
craters, frozenForest, ruinousShores, stainedMountains, tarFields, fungalPass,
|
||||
saltFlats, overgrowth, impact0078, crags,
|
||||
saltFlats, overgrowth,
|
||||
desolateRift, nuclearComplex;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
|
||||
groundZero = new SectorPreset("groundZero", starter, 15){{
|
||||
groundZero = new SectorPreset("groundZero", serpulo, 15){{
|
||||
alwaysUnlocked = true;
|
||||
conditionWave = 5;
|
||||
launchPeriod = 5;
|
||||
rules = r -> {
|
||||
r.winWave = 20;
|
||||
};
|
||||
captureWave = 10;
|
||||
}};
|
||||
|
||||
saltFlats = new SectorPreset("saltFlats", starter, 101){{
|
||||
conditionWave = 10;
|
||||
launchPeriod = 5;
|
||||
requirements = with(
|
||||
new SectorWave(groundZero, 60),
|
||||
//new Unlock(Blocks.daggerFactory),
|
||||
//new Unlock(Blocks.draugFactory),
|
||||
new Research(Blocks.door),
|
||||
new Research(Blocks.waterExtractor)
|
||||
);
|
||||
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
|
||||
|
||||
}};
|
||||
|
||||
frozenForest = new SectorPreset("frozenForest", starter, 86){{
|
||||
conditionWave = 10;
|
||||
requirements = with(
|
||||
new SectorWave(groundZero, 10),
|
||||
new Research(Blocks.junction),
|
||||
new Research(Blocks.router)
|
||||
);
|
||||
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
|
||||
captureWave = 40;
|
||||
}};
|
||||
|
||||
craters = new SectorPreset("craters", starter, 18){{
|
||||
conditionWave = 10;
|
||||
requirements = with(
|
||||
new SectorWave(frozenForest, 10),
|
||||
new Research(Blocks.mender),
|
||||
new Research(Blocks.combustionGenerator)
|
||||
);
|
||||
craters = new SectorPreset("craters", serpulo, 18){{
|
||||
captureWave = 40;
|
||||
}};
|
||||
|
||||
ruinousShores = new SectorPreset("ruinousShores", starter, 19){{
|
||||
conditionWave = 20;
|
||||
launchPeriod = 20;
|
||||
requirements = with(
|
||||
new SectorWave(groundZero, 20),
|
||||
new SectorWave(craters, 15),
|
||||
new Research(Blocks.graphitePress),
|
||||
new Research(Blocks.combustionGenerator),
|
||||
new Research(Blocks.kiln),
|
||||
new Research(Blocks.mechanicalPump)
|
||||
);
|
||||
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
|
||||
captureWave = 40;
|
||||
}};
|
||||
|
||||
stainedMountains = new SectorPreset("stainedMountains", starter, 20){{
|
||||
conditionWave = 10;
|
||||
launchPeriod = 10;
|
||||
requirements = with(
|
||||
new SectorWave(frozenForest, 15),
|
||||
new Research(Blocks.pneumaticDrill),
|
||||
new Research(Blocks.powerNode),
|
||||
new Research(Blocks.turbineGenerator)
|
||||
);
|
||||
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
|
||||
captureWave = 30;
|
||||
}};
|
||||
|
||||
fungalPass = new SectorPreset("fungalPass", starter, 21){{
|
||||
requirements = with(
|
||||
new SectorWave(stainedMountains, 15),
|
||||
//new Unlock(Blocks.daggerFactory),
|
||||
//new Unlock(Blocks.crawlerFactory),
|
||||
new Research(Blocks.door),
|
||||
new Research(Blocks.siliconSmelter)
|
||||
);
|
||||
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
|
||||
|
||||
}};
|
||||
|
||||
overgrowth = new SectorPreset("overgrowth", starter, 22){{
|
||||
conditionWave = 12;
|
||||
launchPeriod = 4;
|
||||
requirements = with(
|
||||
new SectorWave(craters, 40),
|
||||
new Launched(fungalPass),
|
||||
new Research(Blocks.cultivator),
|
||||
new Research(Blocks.sporePress)
|
||||
//new Unlock(Blocks.titanFactory),
|
||||
//new Unlock(Blocks.wraithFactory)
|
||||
);
|
||||
overgrowth = new SectorPreset("overgrowth", serpulo, 22){{
|
||||
|
||||
}};
|
||||
|
||||
tarFields = new SectorPreset("tarFields", starter, 23){{
|
||||
conditionWave = 15;
|
||||
launchPeriod = 10;
|
||||
requirements = with(
|
||||
new SectorWave(ruinousShores, 20),
|
||||
new Research(Blocks.coalCentrifuge),
|
||||
new Research(Blocks.conduit),
|
||||
new Research(Blocks.wave)
|
||||
);
|
||||
tarFields = new SectorPreset("tarFields", serpulo, 23){{
|
||||
captureWave = 40;
|
||||
}};
|
||||
|
||||
desolateRift = new SectorPreset("desolateRift", starter, 123){{
|
||||
conditionWave = 3;
|
||||
launchPeriod = 2;
|
||||
requirements = with(
|
||||
new SectorWave(tarFields, 20),
|
||||
new Research(Blocks.thermalGenerator),
|
||||
new Research(Blocks.thoriumReactor)
|
||||
);
|
||||
desolateRift = new SectorPreset("desolateRift", serpulo, 123){{
|
||||
captureWave = 40;
|
||||
}};
|
||||
|
||||
|
||||
nuclearComplex = new SectorPreset("nuclearComplex", starter, 130){{
|
||||
conditionWave = 30;
|
||||
launchPeriod = 15;
|
||||
requirements = with(
|
||||
new Launched(fungalPass),
|
||||
new Research(Blocks.thermalGenerator),
|
||||
new Research(Blocks.laserDrill)
|
||||
);
|
||||
nuclearComplex = new SectorPreset("nuclearComplex", serpulo, 130){{
|
||||
captureWave = 60;
|
||||
}};
|
||||
|
||||
/*
|
||||
crags = new Zone("crags", new MapGenerator("crags").dist(2f)){{
|
||||
loadout = Loadouts.basicFoundation;
|
||||
baseLaunchCost = ItemStack.with();
|
||||
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
|
||||
conditionWave = 3;
|
||||
launchPeriod = 2;
|
||||
requirements = with(stainedMountains, 40);
|
||||
blockRequirements = new Block[]{Blocks.thermalGenerator};
|
||||
resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.sand};
|
||||
}};
|
||||
|
||||
|
||||
impact0078 = new SectorPreset("impact0078"){{
|
||||
loadout = Loadouts.basicNucleus;
|
||||
baseLaunchCost = ItemStack.list();
|
||||
startingItems = ItemStack.list(Items.copper, 2000, Items.lead, 2000, Items.graphite, 500, Items.titanium, 500, Items.silicon, 500);
|
||||
conditionWave = 3;
|
||||
launchPeriod = 2;
|
||||
//requirements = with(nuclearComplex, 40);
|
||||
//blockRequirements = new Block[]{Blocks.thermalGenerator};
|
||||
//resources = Array.with(Items.copper, Items.scrap, Items.lead, Items.coal, Items.titanium, Items.thorium};
|
||||
}};*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import mindustry.type.StatusEffect;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class StatusEffects implements ContentList{
|
||||
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss;
|
||||
public static StatusEffect none, burning, freezing, wet, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
@@ -17,7 +17,7 @@ public class StatusEffects implements ContentList{
|
||||
none = new StatusEffect("none");
|
||||
|
||||
burning = new StatusEffect("burning"){{
|
||||
damage = 0.08f; //over 10 seconds, this would be 48 damage
|
||||
damage = 0.12f; //over 8 seconds, this would be 60 damage
|
||||
effect = Fx.burning;
|
||||
|
||||
init(() -> {
|
||||
@@ -47,8 +47,9 @@ public class StatusEffects implements ContentList{
|
||||
|
||||
wet = new StatusEffect("wet"){{
|
||||
color = Color.royal;
|
||||
speedMultiplier = 0.9f;
|
||||
speedMultiplier = 0.94f;
|
||||
effect = Fx.wet;
|
||||
effectChance = 0.09f;
|
||||
|
||||
init(() -> {
|
||||
trans(shocked, ((unit, time, newTime, result) -> {
|
||||
@@ -81,6 +82,12 @@ public class StatusEffects implements ContentList{
|
||||
effectChance = 0.1f;
|
||||
}};
|
||||
|
||||
sporeSlowed = new StatusEffect("spore-slowed"){{
|
||||
speedMultiplier = 0.8f;
|
||||
effect = Fx.sapped;
|
||||
effectChance = 0.04f;
|
||||
}};
|
||||
|
||||
tarred = new StatusEffect("tarred"){{
|
||||
speedMultiplier = 0.6f;
|
||||
effect = Fx.oily;
|
||||
|
||||
@@ -11,6 +11,8 @@ import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.content.Blocks.*;
|
||||
import static mindustry.content.SectorPresets.*;
|
||||
import static mindustry.content.SectorPresets.craters;
|
||||
import static mindustry.content.UnitTypes.*;
|
||||
import static mindustry.type.ItemStack.*;
|
||||
|
||||
@@ -110,7 +112,7 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
|
||||
node(Items.coal, with(Items.lead, 3000), () -> {
|
||||
node(Items.graphite, with(Items.coal, 3000), () -> {
|
||||
node(Items.graphite, with(Items.coal, 1000), () -> {
|
||||
node(graphitePress, () -> {
|
||||
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
|
||||
node(pneumaticDrill, () -> {
|
||||
@@ -405,17 +407,87 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
|
||||
//TODO research sectors
|
||||
/*
|
||||
node(SectorPresets.groundZero, () -> {
|
||||
node(SectorPresets.nuclearComplex, () -> {
|
||||
node(SectorPresets.craters, () -> {
|
||||
node(SectorPresets.saltFlats, () -> {
|
||||
|
||||
node(groundZero, () -> {
|
||||
node(frozenForest, Seq.with(
|
||||
new SectorComplete(groundZero),
|
||||
new Research(junction),
|
||||
new Research(router)
|
||||
), () -> {
|
||||
node(craters, Seq.with(
|
||||
new SectorComplete(frozenForest),
|
||||
new Research(mender),
|
||||
new Research(combustionGenerator)
|
||||
), () -> {
|
||||
node(ruinousShores, Seq.with(
|
||||
new SectorComplete(craters),
|
||||
new Research(graphitePress),
|
||||
new Research(combustionGenerator),
|
||||
new Research(kiln),
|
||||
new Research(mechanicalPump)
|
||||
), () -> {
|
||||
|
||||
node(tarFields, Seq.with(
|
||||
new SectorComplete(ruinousShores),
|
||||
new Research(coalCentrifuge),
|
||||
new Research(conduit),
|
||||
new Research(wave)
|
||||
), () -> {
|
||||
node(desolateRift, Seq.with(
|
||||
new SectorComplete(tarFields),
|
||||
new Research(thermalGenerator),
|
||||
new Research(thoriumReactor)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(saltFlats, Seq.with(
|
||||
new SectorComplete(ruinousShores),
|
||||
new Research(groundFactory),
|
||||
new Research(airFactory),
|
||||
new Research(door),
|
||||
new Research(waterExtractor)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(overgrowth, Seq.with(
|
||||
new SectorComplete(craters),
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(cultivator),
|
||||
new Research(sporePress),
|
||||
new Research(UnitTypes.mace),
|
||||
new Research(UnitTypes.flare)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(stainedMountains, Seq.with(
|
||||
new SectorComplete(frozenForest),
|
||||
new Research(pneumaticDrill),
|
||||
new Research(powerNode),
|
||||
new Research(turbineGenerator)
|
||||
), () -> {
|
||||
node(fungalPass, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
new Research(groundFactory),
|
||||
new Research(door),
|
||||
new Research(siliconSmelter)
|
||||
), () -> {
|
||||
node(nuclearComplex, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(thermalGenerator),
|
||||
new Research(laserDrill)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,6 +520,12 @@ public class TechTree implements ContentList{
|
||||
return new TechNode(content, requirements, children);
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
TechNode node = new TechNode(content, empty, children);
|
||||
node.objectives = objectives;
|
||||
return node;
|
||||
}
|
||||
|
||||
private static TechNode node(UnlockableContent block){
|
||||
return node(block, () -> {});
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class UnitTypes implements ContentList{
|
||||
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class, Payloadc.class}) UnitType mega;
|
||||
|
||||
//air + building + mining
|
||||
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class, Trailc.class}) UnitType alpha, beta, gamma;
|
||||
public static @EntityDef({Unitc.class, Builderc.class, Minerc.class}) UnitType alpha, beta, gamma;
|
||||
|
||||
//water
|
||||
public static @EntityDef({Unitc.class, WaterMovec.class, Commanderc.class}) UnitType risso, minke, bryde;
|
||||
@@ -193,7 +193,7 @@ public class UnitTypes implements ContentList{
|
||||
|
||||
bullet = new LightningBulletType(){{
|
||||
lightningColor = hitColor = Pal.heal;
|
||||
damage = 11f;
|
||||
damage = 12f;
|
||||
lightningLength = 7;
|
||||
lightningLengthRand = 7;
|
||||
shootEffect = Fx.shootHeal;
|
||||
@@ -263,7 +263,7 @@ public class UnitTypes implements ContentList{
|
||||
speed = 1f;
|
||||
splashDamageRadius = 55f;
|
||||
instantDisappear = true;
|
||||
splashDamage = 40f;
|
||||
splashDamage = 45f;
|
||||
killShooter = true;
|
||||
hittable = false;
|
||||
collidesAir = true;
|
||||
@@ -527,10 +527,10 @@ public class UnitTypes implements ContentList{
|
||||
}};
|
||||
|
||||
antumbra = new UnitType("antumbra"){{
|
||||
speed = 1.25f;
|
||||
speed = 1.13f;
|
||||
accel = 0.035f;
|
||||
drag = 0.05f;
|
||||
rotateSpeed = 3.5f;
|
||||
rotateSpeed = 1.9f;
|
||||
flying = true;
|
||||
lowAltitude = true;
|
||||
health = 9000;
|
||||
@@ -539,20 +539,70 @@ public class UnitTypes implements ContentList{
|
||||
engineSize = 5.3f;
|
||||
hitsize = 58f;
|
||||
|
||||
weapons.add(new Weapon(){{
|
||||
y = 1.5f;
|
||||
reload = 28f;
|
||||
BulletType missiles = new MissileBulletType(2.7f, 10){{
|
||||
width = 8f;
|
||||
height = 8f;
|
||||
shrinkY = 0f;
|
||||
drag = -0.01f;
|
||||
splashDamageRadius = 40f;
|
||||
splashDamage = 40f;
|
||||
ammoMultiplier = 4f;
|
||||
lifetime = 80f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
|
||||
status = StatusEffects.blasted;
|
||||
statusDuration = 60f;
|
||||
}};
|
||||
|
||||
weapons.add(
|
||||
new Weapon("missiles-mount"){{
|
||||
y = 8f;
|
||||
x = 17f;
|
||||
reload = 20f;
|
||||
ejectEffect = Fx.shellEjectSmall;
|
||||
bullet = Bullets.standardCopper;
|
||||
rotateSpeed = 8f;
|
||||
bullet = missiles;
|
||||
shootSound = Sounds.shoot;
|
||||
}});
|
||||
rotate = true;
|
||||
occlusion = 6f;
|
||||
}},
|
||||
new Weapon("missiles-mount"){{
|
||||
y = -8f;
|
||||
x = 17f;
|
||||
reload = 35;
|
||||
rotateSpeed = 8f;
|
||||
ejectEffect = Fx.shellEjectSmall;
|
||||
bullet = missiles;
|
||||
shootSound = Sounds.shoot;
|
||||
rotate = true;
|
||||
occlusion = 6f;
|
||||
}},
|
||||
new Weapon("large-bullet-mount"){{
|
||||
y = 2f;
|
||||
x = 10f;
|
||||
shootY = 12f;
|
||||
reload = 10;
|
||||
shake = 1f;
|
||||
rotateSpeed = 2f;
|
||||
ejectEffect = Fx.shellEjectSmall;
|
||||
shootSound = Sounds.shootBig;
|
||||
rotate = true;
|
||||
occlusion = 8f;
|
||||
bullet = new BasicBulletType(7f, 60){{
|
||||
width = 12f;
|
||||
height = 18f;
|
||||
shootEffect = Fx.shootBig;
|
||||
}};
|
||||
}}
|
||||
);
|
||||
}};
|
||||
|
||||
eclipse = new UnitType("eclipse"){{
|
||||
speed = 1.1f;
|
||||
speed = 1.09f;
|
||||
accel = 0.02f;
|
||||
drag = 0.05f;
|
||||
rotateSpeed = 2.5f;
|
||||
rotateSpeed = 1f;
|
||||
flying = true;
|
||||
lowAltitude = true;
|
||||
health = 18000;
|
||||
@@ -562,12 +612,74 @@ public class UnitTypes implements ContentList{
|
||||
destructibleWreck = false;
|
||||
armor = 13f;
|
||||
|
||||
weapons.add(new Weapon(){{
|
||||
y = 1.5f;
|
||||
reload = 28f;
|
||||
weapons.add(
|
||||
new Weapon("large-laser-mount"){{
|
||||
shake = 4f;
|
||||
shootY = 9f;
|
||||
x = 18f;
|
||||
y = 5f;
|
||||
rotateSpeed = 2f;
|
||||
reload = 50f;
|
||||
recoil = 4f;
|
||||
shootSound = Sounds.laser;
|
||||
occlusion = 20f;
|
||||
rotate = true;
|
||||
|
||||
bullet = new LaserBulletType(){{
|
||||
damage = 75f;
|
||||
sideAngle = 20f;
|
||||
sideWidth = 1.5f;
|
||||
sideLength = 80f;
|
||||
width = 25f;
|
||||
length = 200f;
|
||||
shootEffect = Fx.shockwave;
|
||||
colors = new Color[]{Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
|
||||
}};
|
||||
}},
|
||||
new Weapon("missiles-mount"){{
|
||||
x = 11f;
|
||||
y = 27f;
|
||||
rotateSpeed = 2f;
|
||||
reload = 4f;
|
||||
shootSound = Sounds.flame;
|
||||
occlusion = 7f;
|
||||
rotate = true;
|
||||
recoil = 0.5f;
|
||||
|
||||
bullet = Bullets.pyraFlame;
|
||||
}},
|
||||
new Weapon("large-artillery"){{
|
||||
y = -13f;
|
||||
x = 20f;
|
||||
reload = 18f;
|
||||
ejectEffect = Fx.shellEjectSmall;
|
||||
bullet = Bullets.standardCopper;
|
||||
rotateSpeed = 7f;
|
||||
shake = 1f;
|
||||
shootSound = Sounds.shoot;
|
||||
rotate = true;
|
||||
occlusion = 12f;
|
||||
bullet = new ArtilleryBulletType(3.2f, 12){{
|
||||
trailMult = 0.8f;
|
||||
hitEffect = Fx.massiveExplosion;
|
||||
knockback = 1.5f;
|
||||
lifetime = 140f;
|
||||
height = 12f;
|
||||
width = 12f;
|
||||
collidesTiles = false;
|
||||
ammoMultiplier = 4f;
|
||||
splashDamageRadius = 60f;
|
||||
splashDamage = 60f;
|
||||
backColor = Pal.missileYellowBack;
|
||||
frontColor = Pal.missileYellow;
|
||||
trailEffect = Fx.artilleryTrail;
|
||||
trailSize = 6f;
|
||||
hitShake = 4f;
|
||||
|
||||
shootEffect = Fx.shootBig2;
|
||||
|
||||
status = StatusEffects.blasted;
|
||||
statusDuration = 60f;
|
||||
}};
|
||||
}});
|
||||
}};
|
||||
|
||||
@@ -575,13 +687,17 @@ public class UnitTypes implements ContentList{
|
||||
//region air support
|
||||
|
||||
mono = new UnitType("mono"){{
|
||||
defaultController = MinerAI::new;
|
||||
|
||||
flying = true;
|
||||
drag = 0.05f;
|
||||
accel = 0.15f;
|
||||
speed = 2f;
|
||||
drag = 0.06f;
|
||||
accel = 0.12f;
|
||||
speed = 1.1f;
|
||||
health = 100;
|
||||
engineSize = 1.8f;
|
||||
engineOffset = 5.7f;
|
||||
itemCapacity = 30;
|
||||
range = 50f;
|
||||
|
||||
mineTier = 1;
|
||||
mineSpeed = 2.5f;
|
||||
@@ -592,7 +708,7 @@ public class UnitTypes implements ContentList{
|
||||
|
||||
flying = true;
|
||||
drag = 0.05f;
|
||||
speed = 2f;
|
||||
speed = 1.9f;
|
||||
rotateSpeed = 15f;
|
||||
accel = 0.1f;
|
||||
range = 70f;
|
||||
@@ -611,7 +727,7 @@ public class UnitTypes implements ContentList{
|
||||
weapons.add(new Weapon("heal-weapon-mount"){{
|
||||
y = -2.5f;
|
||||
x = 3.5f;
|
||||
reload = 34f;
|
||||
reload = 30f;
|
||||
ejectEffect = Fx.none;
|
||||
recoil = 2f;
|
||||
shootSound = Sounds.pew;
|
||||
@@ -620,11 +736,11 @@ public class UnitTypes implements ContentList{
|
||||
inaccuracy = 15f;
|
||||
alternate = true;
|
||||
|
||||
bullet = new MissileBulletType(4f, 10){{
|
||||
bullet = new MissileBulletType(4f, 12){{
|
||||
homingPower = 0.08f;
|
||||
weaveMag = 4;
|
||||
weaveScale = 4;
|
||||
lifetime = 50f;
|
||||
lifetime = 56f;
|
||||
keepVelocity = false;
|
||||
shootEffect = Fx.shootHeal;
|
||||
smokeEffect = Fx.hitLaser;
|
||||
@@ -792,7 +908,6 @@ public class UnitTypes implements ContentList{
|
||||
|
||||
shots = 1;
|
||||
inaccuracy = 3f;
|
||||
|
||||
ejectEffect = Fx.shellEjectBig;
|
||||
|
||||
bullet = new ArtilleryBulletType(3.2f, 12){{
|
||||
@@ -859,7 +974,6 @@ public class UnitTypes implements ContentList{
|
||||
//region core
|
||||
|
||||
alpha = new UnitType("alpha"){{
|
||||
//TODO maybe these should be changed
|
||||
defaultController = BuilderAI::new;
|
||||
isCounted = false;
|
||||
|
||||
@@ -872,12 +986,12 @@ public class UnitTypes implements ContentList{
|
||||
rotateSpeed = 15f;
|
||||
accel = 0.1f;
|
||||
itemCapacity = 30;
|
||||
health = 80f;
|
||||
health = 120f;
|
||||
engineOffset = 6f;
|
||||
hitsize = 8f;
|
||||
|
||||
weapons.add(new Weapon("small-basic-weapon"){{
|
||||
reload = 20f;
|
||||
reload = 17f;
|
||||
x = 2.75f;
|
||||
y = 1f;
|
||||
|
||||
@@ -887,13 +1001,12 @@ public class UnitTypes implements ContentList{
|
||||
lifetime = 60f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
smokeEffect = Fx.shootSmallSmoke;
|
||||
tileDamageMultiplier = 0.1f;
|
||||
tileDamageMultiplier = 0.95f;
|
||||
}};
|
||||
}});
|
||||
}};
|
||||
|
||||
beta = new UnitType("beta"){{
|
||||
//TODO maybe these should be changed
|
||||
defaultController = BuilderAI::new;
|
||||
isCounted = false;
|
||||
|
||||
@@ -906,7 +1019,7 @@ public class UnitTypes implements ContentList{
|
||||
rotateSpeed = 17f;
|
||||
accel = 0.1f;
|
||||
itemCapacity = 50;
|
||||
health = 120f;
|
||||
health = 150f;
|
||||
engineOffset = 6f;
|
||||
hitsize = 9f;
|
||||
rotateShooting = false;
|
||||
@@ -933,7 +1046,6 @@ public class UnitTypes implements ContentList{
|
||||
}};
|
||||
|
||||
gamma = new UnitType("gamma"){{
|
||||
//TODO maybe these should be changed
|
||||
defaultController = BuilderAI::new;
|
||||
isCounted = false;
|
||||
|
||||
@@ -946,7 +1058,7 @@ public class UnitTypes implements ContentList{
|
||||
rotateSpeed = 19f;
|
||||
accel = 0.11f;
|
||||
itemCapacity = 70;
|
||||
health = 160f;
|
||||
health = 190f;
|
||||
engineOffset = 6f;
|
||||
hitsize = 10f;
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ public class Weathers implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
//TODO should apply wet effect
|
||||
rain = new Weather("rain"){
|
||||
float yspeed = 5f, xspeed = 1.5f, padding = 16f, size = 40f, density = 1200f;
|
||||
TextureRegion[] splashes = new TextureRegion[12];
|
||||
@@ -78,6 +77,7 @@ public class Weathers implements ContentList{
|
||||
{
|
||||
attrs.set(Attribute.light, -0.2f);
|
||||
attrs.set(Attribute.water, 0.2f);
|
||||
status = StatusEffects.wet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -255,6 +255,8 @@ public class Weathers implements ContentList{
|
||||
{
|
||||
attrs.set(Attribute.spores, 0.5f);
|
||||
attrs.set(Attribute.light, -0.1f);
|
||||
status = StatusEffects.sporeSlowed;
|
||||
statusGround = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,7 +94,6 @@ public class Control implements ApplicationListener, Loadable{
|
||||
tutorial.reset();
|
||||
|
||||
hiscore = false;
|
||||
|
||||
saves.resetSave();
|
||||
});
|
||||
|
||||
@@ -109,20 +108,24 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
Events.on(GameOverEvent.class, event -> {
|
||||
state.stats.wavesLasted = state.wave;
|
||||
Effects.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
Effect.shake(5, 6, Core.camera.position.x, Core.camera.position.y);
|
||||
//the restart dialog can show info for any number of scenarios
|
||||
Call.gameOver(event.winner);
|
||||
});
|
||||
|
||||
//add player when world loads regardless
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
player.add();
|
||||
});
|
||||
|
||||
//autohost for pvp maps
|
||||
Events.on(WorldLoadEvent.class, event -> app.post(() -> {
|
||||
player.add();
|
||||
if(state.rules.pvp && !net.active()){
|
||||
try{
|
||||
net.host(port);
|
||||
player.admin(true);
|
||||
}catch(IOException e){
|
||||
ui.showException("$server.error", e);
|
||||
ui.showException("@server.error", e);
|
||||
state.set(State.menu);
|
||||
}
|
||||
}
|
||||
@@ -171,7 +174,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(Trigger.newGame, () -> {
|
||||
Events.run(Trigger.newGame, () -> {
|
||||
Building core = player.closestCore();
|
||||
|
||||
if(core == null) return;
|
||||
@@ -188,7 +191,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block()));
|
||||
Time.run(Fx.coreLand.lifetime, () -> {
|
||||
Fx.launch.at(core);
|
||||
Effects.shake(5f, 5f, core);
|
||||
Effect.shake(5f, 5f, core);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -257,7 +260,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
//TODO move
|
||||
public void handleLaunch(CoreEntity tile){
|
||||
public void handleLaunch(CoreBuild tile){
|
||||
LaunchCorec ent = LaunchCore.create();
|
||||
ent.set(tile);
|
||||
ent.block(Blocks.coreShard);
|
||||
@@ -312,7 +315,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}catch(SaveException e){
|
||||
Log.err(e);
|
||||
sector.save = null;
|
||||
Time.runTask(10f, () -> ui.showErrorMessage("$save.corrupted"));
|
||||
Time.runTask(10f, () -> ui.showErrorMessage("@save.corrupted"));
|
||||
slot.delete();
|
||||
playSector(origin, sector);
|
||||
}
|
||||
@@ -434,6 +437,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
ui.showStartupInfo("[accent]v6[] is currently in [accent]pre-alpha[].\n" +
|
||||
"[lightgray]This means:[]\n" +
|
||||
"- Content is missing\n" +
|
||||
"- [scarlet]Mobile[] is not supported.\n" +
|
||||
"- Most [scarlet]Unit AI[] does not work\n" +
|
||||
"- Many units are [scarlet]missing[] or unfinished\n" +
|
||||
"- The campaign is completely unfinished\n" +
|
||||
@@ -450,7 +454,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
//display UI scale changed dialog
|
||||
if(Core.settings.getBool("uiscalechanged", false)){
|
||||
Core.app.post(() -> Core.app.post(() -> {
|
||||
BaseDialog dialog = new BaseDialog("$confirm");
|
||||
BaseDialog dialog = new BaseDialog("@confirm");
|
||||
dialog.setFillParent(true);
|
||||
|
||||
float[] countdown = {60 * 11};
|
||||
@@ -469,9 +473,9 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}).pad(10f).expand().center();
|
||||
|
||||
dialog.buttons.defaults().size(200f, 60f);
|
||||
dialog.buttons.button("$uiscale.cancel", exit);
|
||||
dialog.buttons.button("@uiscale.cancel", exit);
|
||||
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
Core.settings.put("uiscalechanged", false);
|
||||
dialog.hide();
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
@@ -88,27 +87,35 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
//when loading a 'damaged' sector, propagate the damage
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
if(state.isCampaign() && state.rules.sector.getSecondsPassed() > 0 && state.rules.sector.hasBase()){
|
||||
if(state.isCampaign()){
|
||||
long seconds = state.rules.sector.getSecondsPassed();
|
||||
CoreEntity core = state.rules.defaultTeam.core();
|
||||
|
||||
//update correct storage capacity
|
||||
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
|
||||
CoreBuild core = state.rules.defaultTeam.core();
|
||||
|
||||
//apply fractional damage based on how many turns have passed for this sector
|
||||
float turnsPassed = seconds / (turnDuration / 60f);
|
||||
|
||||
if(state.rules.sector.hasWaves()){
|
||||
if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
|
||||
SectorDamage.apply(turnsPassed / sectorDestructionTurns);
|
||||
}
|
||||
|
||||
//add resources based on turns passed
|
||||
if(state.rules.sector.save != null && core != null){
|
||||
//add new items recieved
|
||||
state.rules.sector.calculateRecievedItems().each((item, amount) -> core.items.add(item, amount));
|
||||
//update correct storage capacity
|
||||
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
|
||||
|
||||
//add new items received
|
||||
state.rules.sector.calculateReceivedItems().each((item, amount) -> core.items.add(item, amount));
|
||||
|
||||
//clear received items
|
||||
state.rules.sector.setReceivedItems(new Seq<>());
|
||||
state.rules.sector.setExtraItems(new ItemSeq());
|
||||
|
||||
//validation
|
||||
for(Item item : content.items()){
|
||||
//ensure positive items
|
||||
if(core.items.get(item) < 0) core.items.set(item, 0);
|
||||
//cap the items
|
||||
if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
state.rules.sector.setSecondsPassed(0);
|
||||
@@ -262,15 +269,15 @@ public class Logic implements ApplicationListener{
|
||||
Time.runTask(30f, () -> {
|
||||
Sector origin = sector.save.meta.secinfo.origin;
|
||||
if(origin != null){
|
||||
Seq<ItemStack> stacks = origin.getReceivedItems();
|
||||
ItemSeq stacks = origin.getExtraItems();
|
||||
|
||||
//add up all items into list
|
||||
for(Building entity : state.teams.playerCores()){
|
||||
entity.items.each((item, amount) -> ItemStack.insert(stacks, item, amount));
|
||||
entity.items.each(stacks::add);
|
||||
}
|
||||
|
||||
//save received items
|
||||
origin.setReceivedItems(stacks);
|
||||
origin.setExtraItems(stacks);
|
||||
}
|
||||
|
||||
//remove all the cores
|
||||
@@ -324,7 +331,9 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
|
||||
if(!state.isPaused()){
|
||||
state.secinfo.update();
|
||||
if(state.isCampaign()){
|
||||
state.secinfo.update();
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
universe.update();
|
||||
|
||||
@@ -2,6 +2,7 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
@@ -11,6 +12,7 @@ import arc.util.serialization.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -63,7 +65,7 @@ public class NetClient implements ApplicationListener{
|
||||
reset();
|
||||
|
||||
ui.loadfrag.hide();
|
||||
ui.loadfrag.show("$connecting.data");
|
||||
ui.loadfrag.show("@connecting.data");
|
||||
|
||||
ui.loadfrag.setButton(() -> {
|
||||
ui.loadfrag.hide();
|
||||
@@ -80,7 +82,7 @@ public class NetClient implements ApplicationListener{
|
||||
c.uuid = platform.getUUID();
|
||||
|
||||
if(c.uuid == null){
|
||||
ui.showErrorMessage("$invalidid");
|
||||
ui.showErrorMessage("@invalidid");
|
||||
ui.loadfrag.hide();
|
||||
disconnectQuietly();
|
||||
return;
|
||||
@@ -104,14 +106,14 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
if(packet.reason != null){
|
||||
if(packet.reason.equals("closed")){
|
||||
ui.showSmall("$disconnect", "$disconnect.closed");
|
||||
ui.showSmall("@disconnect", "@disconnect.closed");
|
||||
}else if(packet.reason.equals("timeout")){
|
||||
ui.showSmall("$disconnect", "$disconnect.timeout");
|
||||
ui.showSmall("@disconnect", "@disconnect.timeout");
|
||||
}else if(packet.reason.equals("error")){
|
||||
ui.showSmall("$disconnect", "$disconnect.error");
|
||||
ui.showSmall("@disconnect", "@disconnect.error");
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage("$disconnect");
|
||||
ui.showErrorMessage("@disconnect");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -261,7 +263,7 @@ public class NetClient implements ApplicationListener{
|
||||
if(reason.extraText() != null){
|
||||
ui.showText(reason.toString(), reason.extraText());
|
||||
}else{
|
||||
ui.showText("$disconnect", reason.toString());
|
||||
ui.showText("@disconnect", reason.toString());
|
||||
}
|
||||
}
|
||||
ui.loadfrag.hide();
|
||||
@@ -271,7 +273,7 @@ public class NetClient implements ApplicationListener{
|
||||
public static void kick(String reason){
|
||||
netClient.disconnectQuietly();
|
||||
logic.reset();
|
||||
ui.showText("$disconnect", reason, Align.left);
|
||||
ui.showText("@disconnect", reason, Align.left);
|
||||
ui.loadfrag.hide();
|
||||
}
|
||||
|
||||
@@ -314,7 +316,6 @@ public class NetClient implements ApplicationListener{
|
||||
ui.showLabel(message, duration, worldx, worldy);
|
||||
}
|
||||
|
||||
/*
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
|
||||
if(effect == null) return;
|
||||
@@ -325,7 +326,7 @@ public class NetClient implements ApplicationListener{
|
||||
@Remote(variants = Variant.both)
|
||||
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
|
||||
onEffect(effect, x, y, rotation, color);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoToast(String message, float duration){
|
||||
@@ -344,10 +345,11 @@ public class NetClient implements ApplicationListener{
|
||||
Groups.clear();
|
||||
netClient.removed.clear();
|
||||
logic.reset();
|
||||
netClient.connecting = true;
|
||||
|
||||
net.setClientLoaded(false);
|
||||
|
||||
ui.loadfrag.show("$connecting.data");
|
||||
ui.loadfrag.show("@connecting.data");
|
||||
|
||||
ui.loadfrag.setButton(() -> {
|
||||
ui.loadfrag.hide();
|
||||
@@ -481,7 +483,7 @@ public class NetClient implements ApplicationListener{
|
||||
Log.err("Failed to load data!");
|
||||
ui.loadfrag.hide();
|
||||
quiet = true;
|
||||
ui.showErrorMessage("$disconnect.data");
|
||||
ui.showErrorMessage("@disconnect.data");
|
||||
net.disconnect();
|
||||
timeoutTime = 0f;
|
||||
}
|
||||
@@ -552,7 +554,7 @@ public class NetClient implements ApplicationListener{
|
||||
void sync(){
|
||||
if(timer.get(0, playerSyncTime)){
|
||||
BuildPlan[] requests = null;
|
||||
if(player.isBuilder() && control.input.isBuilding){
|
||||
if(player.isBuilder()){
|
||||
//limit to 10 to prevent buffer overflows
|
||||
int usedRequests = Math.min(player.builder().plans().size, 10);
|
||||
|
||||
@@ -564,7 +566,7 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
Unit unit = player.dead() ? Nulls.unit : player.unit();
|
||||
|
||||
Call.clientShapshot(lastSent++,
|
||||
Call.clientSnapshot(lastSent++,
|
||||
player.dead(),
|
||||
unit.x, unit.y,
|
||||
player.unit().aimX(), player.unit().aimY(),
|
||||
@@ -572,7 +574,7 @@ public class NetClient implements ApplicationListener{
|
||||
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
|
||||
unit.vel.x, unit.vel.y,
|
||||
player.miner().mineTile(),
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(),
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
|
||||
requests,
|
||||
Core.camera.position.x, Core.camera.position.y,
|
||||
Core.camera.width * viewScale, Core.camera.height * viewScale);
|
||||
|
||||
@@ -6,8 +6,8 @@ import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.CommandHandler.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
@@ -332,6 +332,8 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
|
||||
target.name, votes, votesRequired()));
|
||||
|
||||
checkPass();
|
||||
}
|
||||
|
||||
boolean checkPass(){
|
||||
@@ -526,7 +528,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.client, unreliable = true)
|
||||
public static void clientShapshot(
|
||||
public static void clientSnapshot(
|
||||
Player player,
|
||||
int snapshotID,
|
||||
boolean dead,
|
||||
@@ -535,7 +537,7 @@ public class NetServer implements ApplicationListener{
|
||||
float rotation, float baseRotation,
|
||||
float xVelocity, float yVelocity,
|
||||
Tile mining,
|
||||
boolean boosting, boolean shooting, boolean chatting,
|
||||
boolean boosting, boolean shooting, boolean chatting, boolean building,
|
||||
@Nullable BuildPlan[] requests,
|
||||
float viewX, float viewY, float viewWidth, float viewHeight
|
||||
){
|
||||
@@ -556,6 +558,10 @@ public class NetServer implements ApplicationListener{
|
||||
shooting = false;
|
||||
}
|
||||
|
||||
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
|
||||
boosting = false;
|
||||
}
|
||||
|
||||
//TODO these need to be assigned elsewhere
|
||||
player.mouseX = pointerX;
|
||||
player.mouseY = pointerY;
|
||||
@@ -568,6 +574,7 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
if(player.isBuilder()){
|
||||
player.builder().clearBuilding();
|
||||
player.builder().updateBuilding(building);
|
||||
}
|
||||
|
||||
if(player.isMiner()){
|
||||
@@ -607,7 +614,10 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
unit.vel.set(xVelocity, yVelocity).limit(unit.type().speed);
|
||||
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
|
||||
float maxSpeed = player.unit().type().speed;
|
||||
float maxSpeed = (boosting ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
|
||||
if(unit.isGrounded()){
|
||||
maxSpeed *= unit.floorSpeedMultiplier();
|
||||
}
|
||||
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
|
||||
|
||||
if(con.lastUnit != unit){
|
||||
@@ -687,6 +697,7 @@ public class NetServer implements ApplicationListener{
|
||||
logic.skipWave();
|
||||
}else if(action == AdminAction.ban){
|
||||
netServer.admins.banPlayerIP(other.con.address);
|
||||
netServer.admins.banPlayerID(other.con.uuid);
|
||||
other.kick(KickReason.banned);
|
||||
Log.info("&lc@ has banned @.", player.name, other.name);
|
||||
}else if(action == AdminAction.kick){
|
||||
@@ -705,13 +716,15 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
public static void connectConfirm(Player player){
|
||||
player.add();
|
||||
|
||||
if(player.con == null || player.con.hasConnected) return;
|
||||
|
||||
player.add();
|
||||
player.con.hasConnected = true;
|
||||
|
||||
if(Config.showConnectMessages.bool()){
|
||||
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
|
||||
Log.info("&lm[@] &y@ has connected. ", player.uuid(), player.name);
|
||||
Log.info("&lm[@] &y@ has connected.", player.uuid(), player.name);
|
||||
}
|
||||
|
||||
if(!Config.motd.string().equalsIgnoreCase("off")){
|
||||
@@ -739,7 +752,7 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
if(!headless && !closing && net.server() && state.isMenu()){
|
||||
closing = true;
|
||||
ui.loadfrag.show("$server.closing");
|
||||
ui.loadfrag.show("@server.closing");
|
||||
Time.runTask(5f, () -> {
|
||||
net.closeServer();
|
||||
ui.loadfrag.hide();
|
||||
@@ -777,7 +790,7 @@ public class NetServer implements ApplicationListener{
|
||||
syncStream.reset();
|
||||
|
||||
short sent = 0;
|
||||
for(Building entity : Groups.tile){
|
||||
for(Building entity : Groups.build){
|
||||
if(!entity.block().sync) continue;
|
||||
sent ++;
|
||||
|
||||
@@ -802,11 +815,11 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
public void writeEntitySnapshot(Player player) throws IOException{
|
||||
syncStream.reset();
|
||||
Seq<CoreEntity> cores = state.teams.cores(player.team());
|
||||
Seq<CoreBuild> cores = state.teams.cores(player.team());
|
||||
|
||||
dataStream.writeByte(cores.size);
|
||||
|
||||
for(CoreEntity entity : cores){
|
||||
for(CoreBuild entity : cores){
|
||||
dataStream.writeInt(entity.tile().pos());
|
||||
entity.items.write(Writes.get(dataStream));
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public interface Platform{
|
||||
* @param extension File extension to filter
|
||||
*/
|
||||
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
|
||||
new FileChooser(open ? "$open" : "$save", file -> file.extEquals(extension), open, file -> {
|
||||
new FileChooser(open ? "@open" : "@save", file -> file.extEquals(extension), open, file -> {
|
||||
if(!open){
|
||||
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
|
||||
}else{
|
||||
@@ -129,7 +129,7 @@ public interface Platform{
|
||||
if(mobile){
|
||||
showFileChooser(true, extensions[0], cons);
|
||||
}else{
|
||||
new FileChooser("$open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
new FileChooser("@open", file -> Structs.contains(extensions, file.extension().toLowerCase()), true, cons).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ public class Renderer implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLanding(){
|
||||
return landTime > 0;
|
||||
}
|
||||
|
||||
public float weatherAlpha(){
|
||||
return weatherAlpha;
|
||||
}
|
||||
@@ -134,7 +138,7 @@ public class Renderer implements ApplicationListener{
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
settings.put("bloom", false);
|
||||
ui.showErrorMessage("$error.bloom");
|
||||
ui.showErrorMessage("@error.bloom");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +318,7 @@ public class Renderer implements ApplicationListener{
|
||||
int memory = w * h * 4 / 1024 / 1024;
|
||||
|
||||
if(memory >= 65){
|
||||
ui.showInfo("$screenshot.invalid");
|
||||
ui.showInfo("@screenshot.invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import mindustry.editor.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.LogicDialog;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.ui.fragments.*;
|
||||
@@ -67,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public SchematicsDialog schematics;
|
||||
public ModsDialog mods;
|
||||
public ColorPicker picker;
|
||||
public LogicDialog logic;
|
||||
|
||||
public Cursor drillCursor, unloadCursor;
|
||||
|
||||
@@ -85,7 +87,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Fonts.def.getData().markupEnabled = true;
|
||||
Fonts.def.setOwnsTexture(false);
|
||||
|
||||
Core.assets.getAll(BitmapFont.class, new Seq<>()).each(font -> font.setUseIntegerPositions(true));
|
||||
Core.assets.getAll(Font.class, new Seq<>()).each(font -> font.setUseIntegerPositions(true));
|
||||
Core.scene = new Scene();
|
||||
Core.input.addProcessor(Core.scene);
|
||||
|
||||
@@ -99,6 +101,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Dialog.setHideAction(() -> sequence(fadeOut(0.1f)));
|
||||
|
||||
Tooltips.getInstance().animations = false;
|
||||
Tooltips.getInstance().textProvider = text -> new Tooltip(t -> t.background(Styles.black5).margin(4f).add(text));
|
||||
|
||||
Core.settings.setErrorHandler(e -> {
|
||||
e.printStackTrace();
|
||||
@@ -118,7 +121,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
@Override
|
||||
public Seq<AssetDescriptor> getDependencies(){
|
||||
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", BitmapFont.class), new AssetDescriptor<>("default", BitmapFont.class), new AssetDescriptor<>("chat", BitmapFont.class));
|
||||
return Seq.with(new AssetDescriptor<>(Control.class), new AssetDescriptor<>("outline", Font.class), new AssetDescriptor<>("default", Font.class), new AssetDescriptor<>("chat", Font.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,6 +181,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
research = new ResearchDialog();
|
||||
mods = new ModsDialog();
|
||||
schematics = new SchematicsDialog();
|
||||
logic = new LogicDialog();
|
||||
|
||||
Group group = Core.scene.root;
|
||||
|
||||
@@ -224,7 +228,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
}
|
||||
|
||||
public void loadAnd(Runnable call){
|
||||
loadAnd("$loading", call);
|
||||
loadAnd("@loading", call);
|
||||
}
|
||||
|
||||
public void loadAnd(String text, Runnable call){
|
||||
@@ -238,7 +242,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public void showTextInput(String titleText, String dtext, int textLength, String def, boolean inumeric, Cons<String> confirmed){
|
||||
if(mobile){
|
||||
Core.input.getTextInput(new TextInput(){{
|
||||
this.title = (titleText.startsWith("$") ? Core.bundle.get(titleText.substring(1)) : titleText);
|
||||
this.title = (titleText.startsWith("@") ? Core.bundle.get(titleText.substring(1)) : titleText);
|
||||
this.text = def;
|
||||
this.numeric = inumeric;
|
||||
this.maxLength = textLength;
|
||||
@@ -251,11 +255,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
|
||||
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@ok", () -> {
|
||||
confirmed.get(field.getText());
|
||||
hide();
|
||||
}).disabled(b -> field.getText().isEmpty());
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("@cancel", this::hide);
|
||||
keyDown(KeyCode.enter, () -> {
|
||||
String text = field.getText();
|
||||
if(!text.isEmpty()){
|
||||
@@ -282,6 +286,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
public void showInfoFade(String info){
|
||||
Table table = new Table();
|
||||
table.touchable = Touchable.disabled;
|
||||
table.setFillParent(true);
|
||||
table.actions(Actions.fadeOut(7f, Interp.fade), Actions.remove());
|
||||
table.top().add(info).style(Styles.outlineLabel).padTop(10);
|
||||
@@ -340,7 +345,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
getCell(cont).growX();
|
||||
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@ok", () -> {
|
||||
hide();
|
||||
listener.run();
|
||||
}).size(110, 50).pad(4);
|
||||
@@ -351,7 +356,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
getCell(cont).growX();
|
||||
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.left);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -359,13 +364,13 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(""){{
|
||||
setFillParent(true);
|
||||
cont.margin(15f);
|
||||
cont.add("$error.title");
|
||||
cont.add("@error.title");
|
||||
cont.row();
|
||||
cont.image().width(300f).pad(2).height(4f).color(Color.scarlet);
|
||||
cont.row();
|
||||
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
cont.button("$ok", this::hide).size(120, 50).pad(4);
|
||||
cont.button("@ok", this::hide).size(120, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -380,17 +385,17 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
setFillParent(true);
|
||||
cont.margin(15);
|
||||
cont.add("$error.title").colspan(2);
|
||||
cont.add("@error.title").colspan(2);
|
||||
cont.row();
|
||||
cont.image().width(300f).pad(2).colspan(2).height(4f).color(Color.scarlet);
|
||||
cont.row();
|
||||
cont.add((text.startsWith("$") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
|
||||
cont.add((text.startsWith("@") ? Core.bundle.get(text.substring(1)) : text) + (message == null ? "" : "\n[lightgray](" + message + ")")).colspan(2).wrap().growX().center().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
|
||||
Collapser col = new Collapser(base -> base.pane(t -> t.margin(14f).add(Strings.neatError(exc)).color(Color.lightGray).left()), true);
|
||||
|
||||
cont.button("$details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
|
||||
cont.button("$ok", this::hide).size(110, 50).fillX().left();
|
||||
cont.button("@details", Styles.togglet, col::toggle).size(180f, 50f).checked(b -> !col.isCollapsed()).fillX().right();
|
||||
cont.button("@ok", this::hide).size(110, 50).fillX().left();
|
||||
cont.row();
|
||||
cont.add(col).colspan(2).pad(2);
|
||||
}}.show();
|
||||
@@ -407,14 +412,14 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.row();
|
||||
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
|
||||
cont.row();
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
public void showInfoText(String titleText, String text){
|
||||
new Dialog(titleText){{
|
||||
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -423,7 +428,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.margin(10).add(text);
|
||||
titleTable.row();
|
||||
titleTable.image().color(Pal.accent).height(3f).growX().pad(2f);
|
||||
buttons.button("$ok", this::hide).size(110, 50).pad(4);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -436,8 +441,8 @@ public class UI implements ApplicationListener, Loadable{
|
||||
dialog.cont.add(text).width(mobile ? 400f : 500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
|
||||
dialog.buttons.defaults().size(200f, 54f).pad(2f);
|
||||
dialog.setFillParent(false);
|
||||
dialog.buttons.button("$cancel", dialog::hide);
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@cancel", dialog::hide);
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
dialog.hide();
|
||||
confirmed.run();
|
||||
});
|
||||
@@ -477,10 +482,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
public void announce(String text){
|
||||
Table t = new Table();
|
||||
t.touchable = Touchable.disabled;
|
||||
t.background(Styles.black3).margin(8f)
|
||||
.add(text).style(Styles.outlineLabel);
|
||||
t.update(() -> t.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Align.center));
|
||||
t.actions(Actions.fadeOut(3, Interp.pow4In));
|
||||
t.actions(Actions.fadeOut(3, Interp.pow4In), Actions.remove());
|
||||
Core.scene.add(t);
|
||||
}
|
||||
|
||||
@@ -489,7 +495,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
dialog.cont.add(text).width(500f).wrap().pad(4f).get().setAlignment(Align.center, Align.center);
|
||||
dialog.buttons.defaults().size(200f, 54f).pad(2f);
|
||||
dialog.setFillParent(false);
|
||||
dialog.buttons.button("$ok", () -> {
|
||||
dialog.buttons.button("@ok", () -> {
|
||||
dialog.hide();
|
||||
confirmed.run();
|
||||
});
|
||||
|
||||
@@ -277,7 +277,7 @@ public class World{
|
||||
}catch(Throwable e){
|
||||
Log.err(e);
|
||||
if(!headless){
|
||||
ui.showErrorMessage("$map.invalid");
|
||||
ui.showErrorMessage("@map.invalid");
|
||||
Core.app.post(() -> state.set(State.menu));
|
||||
invalidMap = true;
|
||||
}
|
||||
@@ -291,17 +291,17 @@ public class World{
|
||||
|
||||
if(!headless){
|
||||
if(state.teams.playerCores().size == 0 && !checkRules.pvp){
|
||||
ui.showErrorMessage("$map.nospawn");
|
||||
ui.showErrorMessage("@map.nospawn");
|
||||
invalidMap = true;
|
||||
}else if(checkRules.pvp){ //pvp maps need two cores to be valid
|
||||
if(state.teams.getActive().count(TeamData::hasCore) < 2){
|
||||
invalidMap = true;
|
||||
ui.showErrorMessage("$map.nospawn.pvp");
|
||||
ui.showErrorMessage("@map.nospawn.pvp");
|
||||
}
|
||||
}else if(checkRules.attackMode){ //attack maps need two cores to be valid
|
||||
invalidMap = state.teams.get(state.rules.waveTeam).noCores();
|
||||
if(invalidMap){
|
||||
ui.showErrorMessage("$map.nospawn.attack");
|
||||
ui.showErrorMessage("@map.nospawn.attack");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -137,14 +137,11 @@ public class MapEditor{
|
||||
if(isFloor){
|
||||
tile.setFloor(drawBlock.asFloor());
|
||||
}else{
|
||||
tile.setBlock(drawBlock);
|
||||
if(drawBlock.synthetic()){
|
||||
tile.setTeam(drawTeam);
|
||||
}
|
||||
if(drawBlock.rotate && tile.build != null && tile.build.rotation != rotation){
|
||||
addTileOp(TileOp.get(tile.x, tile.y, (byte)OpType.rotation.ordinal(), (byte)rotation));
|
||||
tile.build.rotation = (byte)rotation;
|
||||
}
|
||||
|
||||
tile.setBlock(drawBlock, drawTeam, rotation);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
infoDialog = new MapInfoDialog(editor);
|
||||
generateDialog = new MapGenerateDialog(editor, true);
|
||||
|
||||
menu = new BaseDialog("$menu");
|
||||
menu = new BaseDialog("@menu");
|
||||
menu.addCloseButton();
|
||||
|
||||
float swidth = 180f;
|
||||
@@ -66,41 +66,41 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
menu.cont.table(t -> {
|
||||
t.defaults().size(swidth, 60f).padBottom(5).padRight(5).padLeft(5);
|
||||
|
||||
t.button("$editor.savemap", Icon.save, this::save);
|
||||
t.button("@editor.savemap", Icon.save, this::save);
|
||||
|
||||
t.button("$editor.mapinfo", Icon.pencil, () -> {
|
||||
t.button("@editor.mapinfo", Icon.pencil, () -> {
|
||||
infoDialog.show();
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.row();
|
||||
|
||||
t.button("$editor.generate", Icon.terrain, () -> {
|
||||
t.button("@editor.generate", Icon.terrain, () -> {
|
||||
generateDialog.show(generateDialog::applyToEditor);
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.button("$editor.resize", Icon.resize, () -> {
|
||||
t.button("@editor.resize", Icon.resize, () -> {
|
||||
resizeDialog.show();
|
||||
menu.hide();
|
||||
});
|
||||
|
||||
t.row();
|
||||
|
||||
t.button("$editor.import", Icon.download, () -> createDialog("$editor.import",
|
||||
"$editor.importmap", "$editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
|
||||
"$editor.importfile", "$editor.importfile.description", Icon.file, (Runnable)() ->
|
||||
t.button("@editor.import", Icon.download, () -> createDialog("@editor.import",
|
||||
"@editor.importmap", "@editor.importmap.description", Icon.download, (Runnable)loadDialog::show,
|
||||
"@editor.importfile", "@editor.importfile.description", Icon.file, (Runnable)() ->
|
||||
platform.showFileChooser(true, mapExtension, file -> ui.loadAnd(() -> {
|
||||
maps.tryCatchMapError(() -> {
|
||||
if(MapIO.isImage(file)){
|
||||
ui.showInfo("$editor.errorimage");
|
||||
ui.showInfo("@editor.errorimage");
|
||||
}else{
|
||||
editor.beginEdit(MapIO.createMap(file, true));
|
||||
}
|
||||
});
|
||||
})),
|
||||
|
||||
"$editor.importimage", "$editor.importimage.description", Icon.fileImage, (Runnable)() ->
|
||||
"@editor.importimage", "@editor.importimage.description", Icon.fileImage, (Runnable)() ->
|
||||
platform.showFileChooser(true, "png", file ->
|
||||
ui.loadAnd(() -> {
|
||||
try{
|
||||
@@ -108,16 +108,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
editor.beginEdit(pixmap);
|
||||
pixmap.dispose();
|
||||
}catch(Exception e){
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
Log.err(e);
|
||||
}
|
||||
})))
|
||||
);
|
||||
|
||||
t.button("$editor.export", Icon.upload, () -> createDialog("$editor.export",
|
||||
"$editor.exportfile", "$editor.exportfile.description", Icon.file,
|
||||
t.button("@editor.export", Icon.upload, () -> createDialog("@editor.export",
|
||||
"@editor.exportfile", "@editor.exportfile.description", Icon.file,
|
||||
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), mapExtension, file -> MapIO.writeMap(file, editor.createMap(file))),
|
||||
"$editor.exportimage", "$editor.exportimage.description", Icon.fileImage,
|
||||
"@editor.exportimage", "@editor.exportimage.description", Icon.fileImage,
|
||||
(Runnable)() -> platform.export(editor.getTags().get("name", "unknown"), "png", file -> {
|
||||
Pixmap out = MapIO.writeImage(editor.tiles());
|
||||
file.writePNG(out);
|
||||
@@ -128,7 +128,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
menu.cont.row();
|
||||
|
||||
if(steam){
|
||||
menu.cont.button("$editor.publish.workshop", Icon.link, () -> {
|
||||
menu.cont.button("@editor.publish.workshop", Icon.link, () -> {
|
||||
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
|
||||
|
||||
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
|
||||
@@ -146,26 +146,26 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
if(map == null) return;
|
||||
|
||||
if(map.tags.get("description", "").length() < 4){
|
||||
ui.showErrorMessage("$editor.nodescription");
|
||||
ui.showErrorMessage("@editor.nodescription");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Structs.contains(Gamemode.all, g -> g.valid(map))){
|
||||
ui.showErrorMessage("$map.nospawn");
|
||||
ui.showErrorMessage("@map.nospawn");
|
||||
return;
|
||||
}
|
||||
|
||||
platform.publish(map);
|
||||
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
|
||||
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "@workshop.listing" : "@view.workshop" : "@editor.publish.workshop"));
|
||||
|
||||
menu.cont.row();
|
||||
}
|
||||
|
||||
menu.cont.button("$editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
menu.cont.button("@editor.ingame", Icon.right, this::playtest).padTop(!steam ? -3 : 1).size(swidth * 2f + 10, 60f);
|
||||
|
||||
menu.cont.row();
|
||||
|
||||
menu.cont.button("$quit", Icon.exit, () -> {
|
||||
menu.cont.button("@quit", Icon.exit, () -> {
|
||||
tryExit();
|
||||
menu.hide();
|
||||
}).size(swidth * 2f + 10, 60f);
|
||||
@@ -182,7 +182,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
try{
|
||||
editor.beginEdit(map);
|
||||
}catch(Exception e){
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
Log.err(e);
|
||||
}
|
||||
}));
|
||||
@@ -280,14 +280,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
if(name.isEmpty()){
|
||||
infoDialog.show();
|
||||
Core.app.post(() -> ui.showErrorMessage("$editor.save.noname"));
|
||||
Core.app.post(() -> ui.showErrorMessage("@editor.save.noname"));
|
||||
}else{
|
||||
Map map = maps.all().find(m -> m.name().equals(name));
|
||||
if(map != null && !map.custom){
|
||||
handleSaveBuiltin(map);
|
||||
}else{
|
||||
returned = maps.saveMap(editor.getTags());
|
||||
ui.showInfoFade("$editor.saved");
|
||||
ui.showInfoFade("@editor.saved");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
/** Called when a built-in map save is attempted.*/
|
||||
protected void handleSaveBuiltin(Map map){
|
||||
ui.showErrorMessage("$editor.save.overwrite");
|
||||
ui.showErrorMessage("@editor.save.overwrite");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +368,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
show();
|
||||
}catch(Exception e){
|
||||
Log.err(e);
|
||||
ui.showException("$editor.errorload", e);
|
||||
ui.showException("@editor.errorload", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -521,7 +521,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
|
||||
tools.row();
|
||||
|
||||
tools.table(Tex.underline, t -> t.add("$editor.teams"))
|
||||
tools.table(Tex.underline, t -> t.add("@editor.teams"))
|
||||
.colspan(3).height(40).width(size * 3f + 3f).padBottom(3);
|
||||
|
||||
tools.row();
|
||||
@@ -557,7 +557,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
}
|
||||
|
||||
t.top();
|
||||
t.add("$editor.brush");
|
||||
t.add("@editor.brush");
|
||||
t.row();
|
||||
t.add(slider).width(size * 3f - 20).padTop(4f);
|
||||
}).padTop(5).growX().top();
|
||||
@@ -649,11 +649,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
}
|
||||
|
||||
private void tryExit(){
|
||||
if(!saved){
|
||||
ui.showConfirm("$confirm", "$editor.unsaved", this::hide);
|
||||
}else{
|
||||
hide();
|
||||
}
|
||||
ui.showConfirm("@confirm", "@editor.unsaved", this::hide);
|
||||
}
|
||||
|
||||
private void addBlockSelection(Table table){
|
||||
|
||||
@@ -58,34 +58,34 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
|
||||
/** @param applied whether or not to use the applied in-game mode. */
|
||||
public MapGenerateDialog(MapEditor editor, boolean applied){
|
||||
super("$editor.generate");
|
||||
super("@editor.generate");
|
||||
this.editor = editor;
|
||||
this.applied = applied;
|
||||
|
||||
shown(this::setup);
|
||||
addCloseButton();
|
||||
if(applied){
|
||||
buttons.button("$editor.apply", () -> {
|
||||
buttons.button("@editor.apply", () -> {
|
||||
ui.loadAnd(() -> {
|
||||
apply();
|
||||
hide();
|
||||
});
|
||||
}).size(160f, 64f);
|
||||
}else{
|
||||
buttons.button("$settings.reset", () -> {
|
||||
buttons.button("@settings.reset", () -> {
|
||||
filters.set(maps.readFilters(""));
|
||||
rebuildFilters();
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
}
|
||||
buttons.button("$editor.randomize", () -> {
|
||||
buttons.button("@editor.randomize", () -> {
|
||||
for(GenerateFilter filter : filters){
|
||||
filter.randomize();
|
||||
}
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
|
||||
buttons.button("$add", Icon.add, this::showAdd).height(64f).width(140f);
|
||||
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(140f);
|
||||
|
||||
if(!applied){
|
||||
hidden(this::apply);
|
||||
@@ -221,41 +221,45 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
for(GenerateFilter filter : filters){
|
||||
|
||||
//main container
|
||||
filterTable.table(Tex.button, c -> {
|
||||
filterTable.table(Tex.pane, c -> {
|
||||
c.margin(0);
|
||||
|
||||
//icons to perform actions
|
||||
c.table(t -> {
|
||||
t.top();
|
||||
t.add(filter.name()).padTop(5).color(Pal.accent).growX().left();
|
||||
c.table(Tex.whiteui, t -> {
|
||||
t.setColor(Pal.gray);
|
||||
|
||||
t.row();
|
||||
t.top().left();
|
||||
t.add(filter.name()).left().padLeft(6);
|
||||
|
||||
t.table(b -> {
|
||||
ImageButtonStyle style = Styles.cleari;
|
||||
b.defaults().size(50f);
|
||||
b.button(Icon.refresh, style, () -> {
|
||||
filter.randomize();
|
||||
update();
|
||||
});
|
||||
t.add().growX();
|
||||
|
||||
b.button(Icon.upOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.max(0, idx - 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
b.button(Icon.downOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
b.button(Icon.trash, style, () -> {
|
||||
filters.remove(filter);
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
ImageButtonStyle style = Styles.geni;
|
||||
t.defaults().size(42f);
|
||||
|
||||
t.button(Icon.refresh, style, () -> {
|
||||
filter.randomize();
|
||||
update();
|
||||
});
|
||||
}).fillX();
|
||||
|
||||
t.button(Icon.upOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.max(0, idx - 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
t.button(Icon.downOpen, style, () -> {
|
||||
int idx = filters.indexOf(filter);
|
||||
filters.swap(idx, Math.min(filters.size - 1, idx + 1));
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
t.button(Icon.cancel, style, () -> {
|
||||
filters.remove(filter);
|
||||
rebuildFilters();
|
||||
update();
|
||||
});
|
||||
}).growX();
|
||||
|
||||
c.row();
|
||||
//all the options
|
||||
c.table(f -> {
|
||||
@@ -269,7 +273,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}).growX().left();
|
||||
f.row();
|
||||
}
|
||||
}).grow().left().pad(2).top();
|
||||
}).grow().left().pad(6).top();
|
||||
}).width(280f).pad(3).top().left().fillY();
|
||||
if(++i % cols == 0){
|
||||
filterTable.row();
|
||||
@@ -277,12 +281,12 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(filters.isEmpty()){
|
||||
filterTable.add("$filters.empty").wrap().width(200f);
|
||||
filterTable.add("@filters.empty").wrap().width(200f);
|
||||
}
|
||||
}
|
||||
|
||||
void showAdd(){
|
||||
BaseDialog selection = new BaseDialog("$add");
|
||||
BaseDialog selection = new BaseDialog("@add");
|
||||
selection.setFillParent(false);
|
||||
selection.cont.defaults().size(210f, 60f);
|
||||
int i = 0;
|
||||
@@ -300,7 +304,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
if(++i % 2 == 0) selection.cont.row();
|
||||
}
|
||||
|
||||
selection.cont.button("$filter.defaultores", () -> {
|
||||
selection.cont.button("@filter.defaultores", () -> {
|
||||
maps.addDefaultOres(filters);
|
||||
rebuildFilters();
|
||||
update();
|
||||
|
||||
@@ -16,7 +16,7 @@ public class MapInfoDialog extends BaseDialog{
|
||||
private final CustomRulesDialog ruleInfo = new CustomRulesDialog();
|
||||
|
||||
public MapInfoDialog(MapEditor editor){
|
||||
super("$editor.mapinfo");
|
||||
super("@editor.mapinfo");
|
||||
this.editor = editor;
|
||||
this.waveInfo = new WaveInfoDialog(editor);
|
||||
this.generate = new MapGenerateDialog(editor, false);
|
||||
@@ -32,47 +32,47 @@ public class MapInfoDialog extends BaseDialog{
|
||||
ObjectMap<String, String> tags = editor.getTags();
|
||||
|
||||
cont.pane(t -> {
|
||||
t.add("$editor.mapname").padRight(8).left();
|
||||
t.add("@editor.mapname").padRight(8).left();
|
||||
t.defaults().padTop(15);
|
||||
|
||||
TextField name = t.field(tags.get("name", ""), text -> {
|
||||
tags.put("name", text);
|
||||
}).size(400, 55f).addInputDialog(50).get();
|
||||
name.setMessageText("$unknown");
|
||||
name.setMessageText("@unknown");
|
||||
|
||||
t.row();
|
||||
t.add("$editor.description").padRight(8).left();
|
||||
t.add("@editor.description").padRight(8).left();
|
||||
|
||||
TextArea description = t.area(tags.get("description", ""), Styles.areaField, text -> {
|
||||
tags.put("description", text);
|
||||
}).size(400f, 140f).addInputDialog(1000).get();
|
||||
|
||||
t.row();
|
||||
t.add("$editor.author").padRight(8).left();
|
||||
t.add("@editor.author").padRight(8).left();
|
||||
|
||||
TextField author = t.field(tags.get("author", Core.settings.getString("mapAuthor", "")), text -> {
|
||||
tags.put("author", text);
|
||||
Core.settings.put("mapAuthor", text);
|
||||
}).size(400, 55f).addInputDialog(50).get();
|
||||
author.setMessageText("$unknown");
|
||||
author.setMessageText("@unknown");
|
||||
|
||||
t.row();
|
||||
t.add("$editor.rules").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
t.add("@editor.rules").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
ruleInfo.show(Vars.state.rules, () -> Vars.state.rules = new Rules());
|
||||
hide();
|
||||
}).left().width(200f);
|
||||
|
||||
t.row();
|
||||
t.add("$editor.waves").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
t.add("@editor.waves").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
waveInfo.show();
|
||||
hide();
|
||||
}).left().width(200f);
|
||||
|
||||
t.row();
|
||||
t.add("$editor.generation").padRight(8).left();
|
||||
t.button("$edit", () -> {
|
||||
t.add("@editor.generation").padRight(8).left();
|
||||
t.button("@edit", () -> {
|
||||
generate.show(Vars.maps.readFilters(editor.getTags().get("genfilters", "")),
|
||||
filters -> editor.getTags().put("genfilters", JsonIO.write(filters)));
|
||||
hide();
|
||||
|
||||
@@ -14,11 +14,11 @@ public class MapLoadDialog extends BaseDialog{
|
||||
private Map selected = null;
|
||||
|
||||
public MapLoadDialog(Cons<Map> loader){
|
||||
super("$editor.loadmap");
|
||||
super("@editor.loadmap");
|
||||
|
||||
shown(this::rebuild);
|
||||
|
||||
TextButton button = new TextButton("$load");
|
||||
TextButton button = new TextButton("@load");
|
||||
button.setDisabled(() -> selected == null);
|
||||
button.clicked(() -> {
|
||||
if(selected != null){
|
||||
@@ -28,7 +28,7 @@ public class MapLoadDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
buttons.defaults().size(200f, 50f);
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("@cancel", this::hide);
|
||||
buttons.add(button);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ public class MapLoadDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
if(maps.all().size == 0){
|
||||
table.add("$maps.none").center();
|
||||
table.add("@maps.none").center();
|
||||
}else{
|
||||
cont.add("$editor.loadmap");
|
||||
cont.add("@editor.loadmap");
|
||||
}
|
||||
|
||||
cont.row();
|
||||
|
||||
@@ -12,7 +12,7 @@ public class MapResizeDialog extends BaseDialog{
|
||||
int width, height;
|
||||
|
||||
public MapResizeDialog(MapEditor editor, Intc2 cons){
|
||||
super("$editor.resizemap");
|
||||
super("@editor.resizemap");
|
||||
shown(() -> {
|
||||
cont.clear();
|
||||
width = editor.width();
|
||||
@@ -21,7 +21,7 @@ public class MapResizeDialog extends BaseDialog{
|
||||
Table table = new Table();
|
||||
|
||||
for(boolean w : Mathf.booleans){
|
||||
table.add(w ? "$width" : "$height").padRight(8f);
|
||||
table.add(w ? "@width" : "@height").padRight(8f);
|
||||
table.defaults().height(60f).padTop(8);
|
||||
|
||||
table.field((w ? width : height) + "", TextFieldFilter.digitsOnly, value -> {
|
||||
@@ -37,8 +37,8 @@ public class MapResizeDialog extends BaseDialog{
|
||||
});
|
||||
|
||||
buttons.defaults().size(200f, 50f);
|
||||
buttons.button("$cancel", this::hide);
|
||||
buttons.button("$ok", () -> {
|
||||
buttons.button("@cancel", this::hide);
|
||||
buttons.button("@ok", () -> {
|
||||
cons.get(width, height);
|
||||
hide();
|
||||
});
|
||||
|
||||
@@ -213,9 +213,9 @@ public class MapView extends Element implements GestureListener{
|
||||
y = (y - getHeight() / 2 + sclheight / 2 - offsety * zoom) / sclheight * editor.height();
|
||||
|
||||
if(editor.drawBlock.size % 2 == 0 && tool != EditorTool.eraser){
|
||||
return Tmp.g1.set((int)(x - 0.5f), (int)(y - 0.5f));
|
||||
return Tmp.p1.set((int)(x - 0.5f), (int)(y - 0.5f));
|
||||
}else{
|
||||
return Tmp.g1.set((int)x, (int)y);
|
||||
return Tmp.p1.set((int)x, (int)y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class WaveGraph extends Table{
|
||||
Lines.precise(true);
|
||||
|
||||
GlyphLayout lay = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
|
||||
BitmapFont font = Fonts.outline;
|
||||
Font font = Fonts.outline;
|
||||
|
||||
lay.setText(font, "1");
|
||||
|
||||
@@ -137,7 +137,7 @@ public class WaveGraph extends Table{
|
||||
ButtonGroup<Button> group = new ButtonGroup<>();
|
||||
|
||||
for(Mode m : Mode.all){
|
||||
t.button("$wavemode." + m.name(), Styles.fullTogglet, () -> {
|
||||
t.button("@wavemode." + m.name(), Styles.fullTogglet, () -> {
|
||||
mode = m;
|
||||
}).group(group).height(35f).update(b -> b.setChecked(m == mode)).width(130f);
|
||||
}
|
||||
@@ -173,22 +173,25 @@ public class WaveGraph extends Table{
|
||||
|
||||
colors.clear();
|
||||
colors.left();
|
||||
for(UnitType type : used){
|
||||
colors.button(b -> {
|
||||
Color tcolor = color(type).cpy();
|
||||
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
|
||||
b.image(type.icon(Cicon.medium)).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
|
||||
b.margin(0f);
|
||||
}, Styles.fullTogglet, () -> {
|
||||
if(!hidden.add(type)){
|
||||
hidden.remove(type);
|
||||
}
|
||||
colors.pane(t -> {
|
||||
t.left();
|
||||
for(UnitType type : used){
|
||||
t.button(b -> {
|
||||
Color tcolor = color(type).cpy();
|
||||
b.image().size(32f).update(i -> i.setColor(b.isChecked() ? Tmp.c1.set(tcolor).mul(0.5f) : tcolor)).get().act(1);
|
||||
b.image(type.icon(Cicon.medium)).padRight(20).update(i -> i.setColor(b.isChecked() ? Color.gray : Color.white)).get().act(1);
|
||||
b.margin(0f);
|
||||
}, Styles.fullTogglet, () -> {
|
||||
if(!hidden.add(type)){
|
||||
hidden.remove(type);
|
||||
}
|
||||
|
||||
used.clear();
|
||||
used.addAll(usedCopy);
|
||||
for(UnitType o : hidden) used.remove(o);
|
||||
}).update(b -> b.setChecked(hidden.contains(type)));
|
||||
}
|
||||
used.clear();
|
||||
used.addAll(usedCopy);
|
||||
for(UnitType o : hidden) used.remove(o);
|
||||
}).update(b -> b.setChecked(hidden.contains(type)));
|
||||
}
|
||||
}).get().setScrollingDisabled(false, true);
|
||||
|
||||
for(UnitType type : hidden){
|
||||
used.remove(type);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
private WaveGraph graph = new WaveGraph();
|
||||
|
||||
public WaveInfoDialog(MapEditor editor){
|
||||
super("$waves.title");
|
||||
super("@waves.title");
|
||||
|
||||
shown(this::setup);
|
||||
hidden(() -> {
|
||||
@@ -48,29 +48,29 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
onResize(this::setup);
|
||||
addCloseButton();
|
||||
|
||||
buttons.button("$waves.edit", () -> {
|
||||
BaseDialog dialog = new BaseDialog("$waves.edit");
|
||||
buttons.button("@waves.edit", () -> {
|
||||
BaseDialog dialog = new BaseDialog("@waves.edit");
|
||||
dialog.addCloseButton();
|
||||
dialog.setFillParent(false);
|
||||
dialog.cont.defaults().size(210f, 64f);
|
||||
dialog.cont.button("$waves.copy", () -> {
|
||||
ui.showInfoFade("$waves.copied");
|
||||
dialog.cont.button("@waves.copy", () -> {
|
||||
ui.showInfoFade("@waves.copied");
|
||||
Core.app.setClipboardText(maps.writeWaves(groups));
|
||||
dialog.hide();
|
||||
}).disabled(b -> groups == null);
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("$waves.load", () -> {
|
||||
dialog.cont.button("@waves.load", () -> {
|
||||
try{
|
||||
groups = maps.readWaves(Core.app.getClipboardText());
|
||||
buildGroups();
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
ui.showErrorMessage("$waves.invalid");
|
||||
ui.showErrorMessage("@waves.invalid");
|
||||
}
|
||||
dialog.hide();
|
||||
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("$settings.reset", () -> ui.showConfirm("$confirm", "$settings.clear.confirm", () -> {
|
||||
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
|
||||
groups = JsonIO.copy(defaultWaves.get());
|
||||
buildGroups();
|
||||
dialog.hide();
|
||||
@@ -130,12 +130,12 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
cont.stack(new Table(Tex.clear, main -> {
|
||||
main.pane(t -> table = t).growX().growY().padRight(8f).get().setScrollingDisabled(true, false);
|
||||
main.row();
|
||||
main.button("$add", () -> {
|
||||
main.button("@add", () -> {
|
||||
if(groups == null) groups = new Seq<>();
|
||||
groups.add(new SpawnGroup(lastType));
|
||||
buildGroups();
|
||||
}).growX().height(70f);
|
||||
}), new Label("$waves.none"){{
|
||||
}), new Label("@waves.none"){{
|
||||
visible(() -> groups.isEmpty());
|
||||
this.touchable = Touchable.disabled;
|
||||
setWrap(true);
|
||||
@@ -180,7 +180,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(100f);
|
||||
spawns.add("$waves.to").padLeft(4).padRight(4);
|
||||
spawns.add("@waves.to").padLeft(4).padRight(4);
|
||||
spawns.field(group.end == never ? "" : (group.end + 1) + "", TextFieldFilter.digitsOnly, text -> {
|
||||
if(Strings.canParsePositiveInt(text)){
|
||||
group.end = Strings.parseInt(text) - 1;
|
||||
@@ -193,14 +193,14 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
});
|
||||
t.row();
|
||||
t.table(p -> {
|
||||
p.add("$waves.every").padRight(4);
|
||||
p.add("@waves.every").padRight(4);
|
||||
p.field(group.spacing + "", TextFieldFilter.digitsOnly, text -> {
|
||||
if(Strings.canParsePositiveInt(text) && Strings.parseInt(text) > 0){
|
||||
group.spacing = Strings.parseInt(text);
|
||||
updateWaves();
|
||||
}
|
||||
}).width(100f);
|
||||
p.add("$waves.waves").padLeft(4);
|
||||
p.add("@waves.waves").padLeft(4);
|
||||
});
|
||||
|
||||
t.row();
|
||||
@@ -219,7 +219,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(80f);
|
||||
a.add("$waves.perspawn").padLeft(4);
|
||||
a.add("@waves.perspawn").padLeft(4);
|
||||
});
|
||||
t.row();
|
||||
t.table(a -> {
|
||||
@@ -237,17 +237,17 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
updateWaves();
|
||||
}
|
||||
}).width(80f);
|
||||
a.add("$waves.shields").padLeft(4);
|
||||
a.add("@waves.shields").padLeft(4);
|
||||
});
|
||||
|
||||
t.row();
|
||||
t.check("$waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
|
||||
t.check("@waves.guardian", b -> group.effect = (b ? StatusEffects.boss : null)).padTop(4).update(b -> b.setChecked(group.effect == StatusEffects.boss)).padBottom(8f);
|
||||
}).width(340f).pad(8);
|
||||
|
||||
table.row();
|
||||
}
|
||||
}else{
|
||||
table.add("$editor.default");
|
||||
table.add("@editor.default");
|
||||
}
|
||||
|
||||
updateWaves();
|
||||
|
||||
@@ -59,7 +59,7 @@ public class Damage{
|
||||
}
|
||||
|
||||
float shake = Math.min(explosiveness / 4f + 3f, 9f);
|
||||
Effects.shake(shake, shake, x, y);
|
||||
Effect.shake(shake, shake, x, y);
|
||||
Fx.dynamicExplosion.at(x, y, radius / 8f);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Effect{
|
||||
private static final float shakeFalloff = 10000f;
|
||||
private static final EffectContainer container = new EffectContainer();
|
||||
private static int lastid = 0;
|
||||
private static final Seq<Effect> all = new Seq<>();
|
||||
|
||||
public final int id;
|
||||
public final Cons<EffectContainer> renderer;
|
||||
@@ -21,10 +32,11 @@ public class Effect{
|
||||
public float groundDuration;
|
||||
|
||||
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
|
||||
this.id = lastid++;
|
||||
this.id = all.size;
|
||||
this.lifetime = life;
|
||||
this.renderer = renderer;
|
||||
this.size = clipsize;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public Effect(float life, Cons<EffectContainer> renderer){
|
||||
@@ -43,35 +55,35 @@ public class Effect{
|
||||
}
|
||||
|
||||
public void at(Position pos){
|
||||
Effects.create(this, pos.getX(), pos.getY(), 0, Color.white, null);
|
||||
create(this, pos.getX(), pos.getY(), 0, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(Position pos, float rotation){
|
||||
Effects.create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
|
||||
create(this, pos.getX(), pos.getY(), rotation, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y){
|
||||
Effects.create(this, x, y, 0, Color.white, null);
|
||||
create(this, x, y, 0, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation){
|
||||
Effects.create(this, x, y, rotation, Color.white, null);
|
||||
create(this, x, y, rotation, Color.white, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Color color){
|
||||
Effects.create(this, x, y, rotation, color, null);
|
||||
create(this, x, y, rotation, color, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, Color color){
|
||||
Effects.create(this, x, y, 0, color, null);
|
||||
create(this, x, y, 0, color, null);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Color color, Object data){
|
||||
Effects.create(this, x, y, rotation, color, data);
|
||||
create(this, x, y, rotation, color, data);
|
||||
}
|
||||
|
||||
public void at(float x, float y, float rotation, Object data){
|
||||
Effects.create(this, x, y, rotation, Color.white, data);
|
||||
create(this, x, y, rotation, Color.white, data);
|
||||
}
|
||||
|
||||
public void render(int id, Color color, float life, float rotation, float x, float y, Object data){
|
||||
@@ -81,6 +93,84 @@ public class Effect{
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public static @Nullable Effect get(int id){
|
||||
return id >= all.size || id < 0 ? null : all.get(id);
|
||||
}
|
||||
|
||||
private static void shake(float intensity, float duration){
|
||||
if(!headless){
|
||||
Vars.renderer.shake(intensity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, float x, float y){
|
||||
if(Core.camera == null) return;
|
||||
|
||||
float distance = Core.camera.position.dst(x, y);
|
||||
if(distance < 1) distance = 1;
|
||||
|
||||
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, Position loc){
|
||||
shake(intensity, duration, loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
|
||||
if(headless || effect == Fx.none) return;
|
||||
if(Core.settings.getBool("effects")){
|
||||
Rect view = Core.camera.bounds(Tmp.r1);
|
||||
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
|
||||
|
||||
if(view.overlaps(pos)){
|
||||
EffectState entity = EffectState.create();
|
||||
entity.effect(effect);
|
||||
entity.rotation(rotation);
|
||||
entity.data(data);
|
||||
entity.lifetime(effect.lifetime);
|
||||
entity.set(x, y);
|
||||
entity.color().set(color);
|
||||
if(data instanceof Posc) entity.parent((Posc)data);
|
||||
entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation){
|
||||
decal(region, x, y, rotation, 3600f, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
|
||||
if(headless || region == null || !Core.atlas.isFound(region)) return;
|
||||
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile == null || tile.floor().isLiquid) return;
|
||||
|
||||
Decal decal = Decal.create();
|
||||
decal.set(x, y);
|
||||
decal.rotation(rotation);
|
||||
decal.lifetime(lifetime);
|
||||
decal.color().set(color);
|
||||
decal.region(region);
|
||||
decal.add();
|
||||
}
|
||||
|
||||
public static void scorch(float x, float y, int size){
|
||||
if(headless) return;
|
||||
|
||||
size = Mathf.clamp(size, 0, 9);
|
||||
|
||||
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void rubble(float x, float y, int blockSize){
|
||||
if(headless) return;
|
||||
|
||||
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static class EffectContainer implements Scaled{
|
||||
public float x, y, time, lifetime, rotation;
|
||||
public Color color;
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Effects{
|
||||
private static final float shakeFalloff = 10000f;
|
||||
|
||||
private static void shake(float intensity, float duration){
|
||||
if(!headless){
|
||||
renderer.shake(intensity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, float x, float y){
|
||||
if(Core.camera == null) return;
|
||||
|
||||
float distance = Core.camera.position.dst(x, y);
|
||||
if(distance < 1) distance = 1;
|
||||
|
||||
shake(Mathf.clamp(1f / (distance * distance / shakeFalloff)) * intensity, duration);
|
||||
}
|
||||
|
||||
public static void shake(float intensity, float duration, Position loc){
|
||||
shake(intensity, duration, loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
public static void create(Effect effect, float x, float y, float rotation, Color color, Object data){
|
||||
if(headless || effect == Fx.none) return;
|
||||
if(Core.settings.getBool("effects")){
|
||||
Rect view = Core.camera.bounds(Tmp.r1);
|
||||
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
|
||||
|
||||
if(view.overlaps(pos)){
|
||||
EffectState entity = EffectState.create();
|
||||
entity.effect(effect);
|
||||
entity.rotation(rotation);
|
||||
entity.data(data);
|
||||
entity.lifetime(effect.lifetime);
|
||||
entity.set(x, y);
|
||||
entity.color().set(color);
|
||||
if(data instanceof Posc) entity.parent((Posc)data);
|
||||
entity.add();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation){
|
||||
decal(region, x, y, rotation, 3600f, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void decal(TextureRegion region, float x, float y, float rotation, float lifetime, Color color){
|
||||
if(headless || region == null || !Core.atlas.isFound(region)) return;
|
||||
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile == null || tile.floor().isLiquid) return;
|
||||
|
||||
Decal decal = Decal.create();
|
||||
decal.set(x, y);
|
||||
decal.rotation(rotation);
|
||||
decal.lifetime(lifetime);
|
||||
decal.color().set(color);
|
||||
decal.region(region);
|
||||
decal.add();
|
||||
}
|
||||
|
||||
public static void scorch(float x, float y, int size){
|
||||
if(headless) return;
|
||||
|
||||
size = Mathf.clamp(size, 0, 9);
|
||||
|
||||
TextureRegion region = Core.atlas.find("scorch-" + size + "-" + Mathf.random(2));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
|
||||
public static void rubble(float x, float y, int blockSize){
|
||||
if(headless) return;
|
||||
|
||||
TextureRegion region = Core.atlas.find("rubble-" + blockSize + "-" + (Core.atlas.has("rubble-" + blockSize + "-1") ? Mathf.random(0, 1) : "0"));
|
||||
decal(region, x, y, Mathf.random(4) * 90, 3600, Pal.rubble);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class GroupDefs<G>{
|
||||
@GroupDef(value = Playerc.class, mapping = true) G player;
|
||||
@GroupDef(value = Bulletc.class, spatial = true, collide = true) G bullet;
|
||||
@GroupDef(value = Unitc.class, spatial = true, mapping = true) G unit;
|
||||
@GroupDef(value = Buildingc.class) G tile;
|
||||
@GroupDef(value = Buildingc.class) G build;
|
||||
@GroupDef(value = Syncc.class, mapping = true) G sync;
|
||||
@GroupDef(value = Drawc.class) G draw;
|
||||
@GroupDef(value = WeatherStatec.class) G weather;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class Puddles{
|
||||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
|
||||
Fires.create(tile);
|
||||
if(Mathf.chance(0.006 * amount)){
|
||||
Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
|
||||
Bullets.fireball.createNet(Team.derelict, x, y, Mathf.random(360f), -1f, 1f, 1f);
|
||||
}
|
||||
}else if(dest.temperature > 0.7f && liquid.temperature < 0.55f){ //cold liquid poured onto hot Puddle
|
||||
if(Mathf.chance(0.5f * amount)){
|
||||
|
||||
@@ -36,7 +36,7 @@ public class Units{
|
||||
|
||||
public static int getCap(Team team){
|
||||
//wave team has no cap
|
||||
if((team == state.rules.waveTeam && state.rules.waves) || (state.isCampaign() && team == state.rules.waveTeam)){
|
||||
if((team == state.rules.waveTeam && !state.rules.pvp) || (state.isCampaign() && team == state.rules.waveTeam)){
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return state.rules.unitCap + indexer.getExtraUnits(team);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
@@ -70,8 +71,18 @@ public class ForceFieldAbility implements Ability{
|
||||
|
||||
if(unit.shield > 0){
|
||||
Draw.z(Layer.shields);
|
||||
|
||||
Draw.color(unit.team.color, Color.white, Mathf.clamp(unit.shieldAlpha));
|
||||
Fill.poly(unit.x, unit.y, 6, realRad);
|
||||
|
||||
if(Core.settings.getBool("animatedshields")){
|
||||
Fill.poly(unit.x, unit.y, 6, realRad);
|
||||
}else{
|
||||
Lines.stroke(1.5f);
|
||||
Draw.alpha(0.09f);
|
||||
Fill.poly(unit.x, unit.y, 6, radius);
|
||||
Draw.alpha(1f);
|
||||
Lines.poly(unit.x, unit.y, 6, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public abstract class BulletType extends Content{
|
||||
/** Status effect applied on hit. */
|
||||
public StatusEffect status = StatusEffects.none;
|
||||
/** Intensity of applied status effect in terms of duration. */
|
||||
public float statusDuration = 60 * 10f;
|
||||
public float statusDuration = 60 * 8f;
|
||||
/** Whether this bullet type collides with tiles. */
|
||||
public boolean collidesTiles = true;
|
||||
/** Whether this bullet type collides with tiles that are of the same team. */
|
||||
@@ -142,7 +142,7 @@ public abstract class BulletType extends Content{
|
||||
hitEffect.at(x, y, b.rotation(), hitColor);
|
||||
hitSound.at(b);
|
||||
|
||||
Effects.shake(hitShake, hitShake, b);
|
||||
Effect.shake(hitShake, hitShake, b);
|
||||
|
||||
if(fragBullet != null){
|
||||
for(int i = 0; i < fragBullets; i++){
|
||||
@@ -270,7 +270,7 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
public void createNet(Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
|
||||
Call.createBullet(this, team, x, y, damage, angle, velocityScl, lifetimeScl);
|
||||
Call.createBullet(this, team, x, y, angle, damage, velocityScl, lifetimeScl);
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
|
||||
@@ -53,7 +53,7 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
}
|
||||
|
||||
if(shake > 0){
|
||||
Effects.shake(shake, shake, b);
|
||||
Effect.shake(shake, shake, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
for(int i = 0; i < tscales.length; i++){
|
||||
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
|
||||
Lines.stroke((9f + Mathf.absin(Time.time(), 0.8f, 1.5f)) * b.fout() * strokes[s] * tscales[i]);
|
||||
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], CapStyle.none);
|
||||
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class LaserBulletType extends BulletType{
|
||||
for(Color color : colors){
|
||||
Draw.color(color);
|
||||
Lines.stroke((cwidth *= lengthFalloff) * b.fout());
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, CapStyle.none);
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen, false);
|
||||
Tmp.v1.trns(b.rotation(), baseLen);
|
||||
Drawf.tri(b.x + Tmp.v1.x, b.y + Tmp.v1.y, Lines.getStroke() * 1.22f, cwidth * 2f + width / 2f, b.rotation());
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class LiquidBulletType extends BulletType{
|
||||
}
|
||||
|
||||
lifetime = 74f;
|
||||
statusDuration = 90f;
|
||||
statusDuration = 60f * 2f;
|
||||
despawnEffect = Fx.none;
|
||||
hitEffect = Fx.hitLiquid;
|
||||
smokeEffect = Fx.none;
|
||||
@@ -69,7 +69,7 @@ public class LiquidBulletType extends BulletType{
|
||||
super.despawned(b);
|
||||
|
||||
//don't create liquids when the projectile despawns
|
||||
hitEffect.at(b.x, b.y, liquid.color);
|
||||
hitEffect.at(b.x, b.y, b.rotation(), liquid.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ public class MissileBulletType extends BasicBulletType{
|
||||
height = 8f;
|
||||
hitSound = Sounds.explosion;
|
||||
trailChance = 0.2f;
|
||||
lifetime = 49f;
|
||||
}
|
||||
|
||||
public MissileBulletType(float speed, float damage){
|
||||
|
||||
@@ -29,13 +29,7 @@ abstract class BuilderComp implements Unitc{
|
||||
@Import float x, y, rotation;
|
||||
|
||||
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
|
||||
transient boolean updateBuilding = true;
|
||||
|
||||
@Override
|
||||
public void controller(UnitController next){
|
||||
//reset building state so AI controlled units will always start off building
|
||||
updateBuilding = true;
|
||||
}
|
||||
@SyncLocal transient boolean updateBuilding = true;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -76,7 +70,7 @@ abstract class BuilderComp implements Unitc{
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
|
||||
if(!within(tile, finalPlaceDst)){
|
||||
if(within(tile, finalPlaceDst)){
|
||||
rotation = Mathf.slerpDelta(rotation, angleTo(tile), 0.4f);
|
||||
}
|
||||
|
||||
@@ -85,12 +79,12 @@ abstract class BuilderComp implements Unitc{
|
||||
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
|
||||
|
||||
if(hasAll){
|
||||
Build.beginPlace(current.block, team(), current.x, current.y, current.rotation);
|
||||
Call.beginPlace(current.block, team(), current.x, current.y, current.rotation);
|
||||
}else{
|
||||
current.stuck = true;
|
||||
}
|
||||
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
|
||||
Build.beginBreak(team(), current.x, current.y);
|
||||
Call.beginBreak(team(), current.x, current.y);
|
||||
}else{
|
||||
plans.removeFirst();
|
||||
return;
|
||||
@@ -200,6 +194,10 @@ abstract class BuilderComp implements Unitc{
|
||||
}
|
||||
}
|
||||
|
||||
boolean activelyBuilding(){
|
||||
return isBuilding() && updateBuilding;
|
||||
}
|
||||
|
||||
/** Return the build request currently active, or the one at the top of the queue.*/
|
||||
@Nullable
|
||||
BuildPlan buildPlan(){
|
||||
|
||||
@@ -18,11 +18,13 @@ import arc.util.io.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
@@ -37,7 +39,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
@EntityDef(value = {Buildingc.class}, isFinal = false, genio = false, serialize = false)
|
||||
@Component(base = true)
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable{
|
||||
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable{
|
||||
//region vars and initialization
|
||||
static final float timeToSleep = 60f * 1;
|
||||
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
|
||||
@@ -54,6 +56,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
transient boolean updateFlow;
|
||||
transient byte dump;
|
||||
transient int rotation;
|
||||
transient boolean enabled = true;
|
||||
|
||||
PowerModule power;
|
||||
ItemModule items;
|
||||
@@ -97,7 +100,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
|
||||
}
|
||||
|
||||
health(block.health);
|
||||
health = block.health;
|
||||
maxHealth(block.health);
|
||||
timer(new Interval(block.timers));
|
||||
|
||||
@@ -141,7 +144,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public final void readBase(Reads read){
|
||||
health = read.f();
|
||||
rotation(read.b());
|
||||
rotation = read.b();
|
||||
team = Team.get(read.b());
|
||||
if(items != null) items.read(read);
|
||||
if(power != null) power.read(read);
|
||||
@@ -294,6 +297,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
/** Base efficiency. If this entity has non-buffered power, returns the power %, otherwise returns 1. */
|
||||
public float efficiency(){
|
||||
//disabled -> 0 efficiency
|
||||
if(!enabled) return 0;
|
||||
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
|
||||
}
|
||||
|
||||
@@ -325,6 +330,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//endregion
|
||||
//region handler methods
|
||||
|
||||
/** This is for logic blocks. */
|
||||
public void handleString(Object value){
|
||||
|
||||
}
|
||||
|
||||
public void created(){}
|
||||
|
||||
public boolean shouldConsume(){
|
||||
@@ -715,11 +725,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void drawStatus(){
|
||||
if(block.consumes.any()){
|
||||
if(block.enableDrawStatus && block.consumes.any()){
|
||||
float brcx = tile.drawx() + (block.size * tilesize / 2f) - (tilesize / 2f);
|
||||
float brcy = tile.drawy() - (block.size * tilesize / 2f) + (tilesize / 2f);
|
||||
|
||||
Draw.z(Layer.blockOver);
|
||||
Draw.z(Layer.power + 1);
|
||||
Draw.color(Pal.gray);
|
||||
Fill.square(brcx, brcy, 2.5f, 45);
|
||||
Draw.color(cons.status().color);
|
||||
@@ -833,8 +843,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the block is tapped.*/
|
||||
public void tapped(Player player){
|
||||
/** Called when the block is tapped by the local player. */
|
||||
public void tapped(){
|
||||
|
||||
}
|
||||
|
||||
@@ -879,7 +889,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame);
|
||||
if(!floor().solid && !floor().isLiquid){
|
||||
Effects.rubble(x, y, block.size);
|
||||
Effect.rubble(x, y, block.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,6 +1070,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canPickup(){
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeFromProximity(){
|
||||
onProximityRemoved();
|
||||
tmpTiles.clear();
|
||||
@@ -1157,6 +1171,41 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.x) return x;
|
||||
if(sensor == LAccess.y) return y;
|
||||
if(sensor == LAccess.team) return team.id;
|
||||
if(sensor == LAccess.health) return health;
|
||||
if(sensor == LAccess.efficiency) return efficiency();
|
||||
if(sensor == LAccess.rotation) return rotation;
|
||||
if(sensor == LAccess.totalItems && items != null) return items.total();
|
||||
if(sensor == LAccess.totalLiquids && liquids != null) return liquids.total();
|
||||
if(sensor == LAccess.totalPower && power != null && block.consumes.hasPower()) return power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
|
||||
if(sensor == LAccess.itemCapacity) return block.itemCapacity;
|
||||
if(sensor == LAccess.liquidCapacity) return block.liquidCapacity;
|
||||
if(sensor == LAccess.powerCapacity) return block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
|
||||
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getPowerProduced();
|
||||
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getPowerNeeded();
|
||||
if(sensor == LAccess.powerNetStored && power != null) return power.graph.getLastPowerStored();
|
||||
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getBatteryCapacity();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(content instanceof Item && items != null) return items.get((Item)content);
|
||||
if(content instanceof Liquid && liquids != null) return liquids.get((Liquid)content);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void control(LAccess type, double p1, double p2, double p3, double p4){
|
||||
if(type == LAccess.enabled){
|
||||
enabled = !Mathf.zero((float)p1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
if(sound != null){
|
||||
@@ -1189,7 +1238,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
loops.play(block.idleSound, base(), block.idleSoundVolume);
|
||||
}
|
||||
|
||||
updateTile();
|
||||
if(enabled || !block.noUpdateDisabled){
|
||||
updateTile();
|
||||
}
|
||||
|
||||
if(items != null){
|
||||
items.update(updateFlow);
|
||||
|
||||
@@ -17,6 +17,8 @@ abstract class CommanderComp implements Unitc{
|
||||
|
||||
transient @Nullable Formation formation;
|
||||
transient Seq<Unit> controlling = new Seq<>();
|
||||
/** minimum speed of any unit in the formation. */
|
||||
transient float minFormationSpeed;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -45,18 +47,26 @@ abstract class CommanderComp implements Unitc{
|
||||
void command(Formation formation, Seq<Unit> units){
|
||||
clearCommand();
|
||||
|
||||
float spacing = hitSize() * 1.7f;
|
||||
minFormationSpeed = type().speed;
|
||||
|
||||
controlling.addAll(units);
|
||||
for(Unit unit : units){
|
||||
unit.controller(new FormationAI(base(), formation));
|
||||
FormationAI ai;
|
||||
unit.controller(ai = new FormationAI(base(), formation));
|
||||
spacing = Math.max(spacing, ai.formationSize());
|
||||
minFormationSpeed = Math.min(minFormationSpeed, unit.type().speed);
|
||||
}
|
||||
this.formation = formation;
|
||||
|
||||
//update formation spacing based on max size
|
||||
formation.pattern.spacing = spacing;
|
||||
|
||||
members.clear();
|
||||
for(Unitc u : units){
|
||||
members.add((FormationAI)u.controller());
|
||||
}
|
||||
|
||||
|
||||
//TODO doesn't handle units that don't fit a formation
|
||||
formation.addMembers(members);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package mindustry.entities.comp;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.collisions;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class ElevationMoveComp implements Velc, Posc, Flyingc, Hitboxc{
|
||||
|
||||
@@ -6,7 +6,7 @@ import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
@Component
|
||||
abstract class HealthComp implements Entityc{
|
||||
abstract class HealthComp implements Entityc, Posc{
|
||||
static final float hitDuration = 9f;
|
||||
|
||||
float health;
|
||||
|
||||
@@ -11,7 +11,7 @@ import mindustry.gen.*;
|
||||
abstract class HitboxComp implements Posc, QuadTreeObject{
|
||||
@Import float x, y;
|
||||
|
||||
transient float lastX, lastY, hitSize;
|
||||
transient float lastX, lastY, deltaX, deltaY, hitSize;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
@@ -33,6 +33,8 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
|
||||
}
|
||||
|
||||
void updateLastPosition(){
|
||||
deltaX = x - lastX;
|
||||
deltaY = y - lastY;
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
@@ -41,20 +43,12 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
|
||||
|
||||
}
|
||||
|
||||
float deltaX(){
|
||||
return x - lastX;
|
||||
}
|
||||
|
||||
float deltaY(){
|
||||
return y - lastY;
|
||||
}
|
||||
|
||||
float deltaLen(){
|
||||
return Mathf.len(deltaX(), deltaY());
|
||||
return Mathf.len(deltaX, deltaY);
|
||||
}
|
||||
|
||||
float deltaAngle(){
|
||||
return Mathf.angle(deltaX(), deltaY());
|
||||
return Mathf.angle(deltaX, deltaY);
|
||||
}
|
||||
|
||||
boolean collides(Hitboxc other){
|
||||
|
||||
@@ -100,7 +100,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{
|
||||
|
||||
//shake when legs contact ground
|
||||
if(type.landShake > 0){
|
||||
Effects.shake(type.landShake, type.landShake, l.base);
|
||||
Effect.shake(type.landShake, type.landShake, l.base);
|
||||
}
|
||||
|
||||
if(type.legSplashDamage > 0){
|
||||
|
||||
@@ -40,19 +40,19 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
|
||||
public void update(){
|
||||
Building core = closestCore();
|
||||
|
||||
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange)){
|
||||
if(core != null && mineTile != null && mineTile.drop() != null && !acceptsItem(mineTile.drop()) && within(core, mineTransferRange) && !offloadImmediately()){
|
||||
int accepted = core.acceptStack(item(), stack().amount, this);
|
||||
if(accepted > 0){
|
||||
Call.transferItemTo(item(), accepted,
|
||||
mineTile.worldx() + Mathf.range(tilesize / 2f),
|
||||
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
|
||||
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
|
||||
clearItem();
|
||||
}
|
||||
}
|
||||
|
||||
if(mineTile == null || core == null || mineTile.block() != Blocks.air || dst(mineTile.worldx(), mineTile.worldy()) > miningRange
|
||||
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).isBuilding())
|
||||
|| mineTile.drop() == null || !acceptsItem(mineTile.drop()) || !canMine(mineTile.drop())){
|
||||
|| (((Object)this) instanceof Builderc && ((Builderc)(Object)this).activelyBuilding())
|
||||
|| mineTile.drop() == null || !canMine(mineTile.drop())){
|
||||
mineTile = null;
|
||||
mineTimer = 0f;
|
||||
}else{
|
||||
@@ -60,25 +60,30 @@ abstract class MinerComp implements Itemsc, Posc, Teamc, Rotc, Drawc, Unitc{
|
||||
rotation(Mathf.slerpDelta(rotation(), angleTo(mineTile.worldx(), mineTile.worldy()), 0.4f));
|
||||
mineTimer += Time.delta *type.mineSpeed;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if(mineTimer >= 50f + item.hardness*10f){
|
||||
mineTimer = 0;
|
||||
|
||||
if(within(core, mineTransferRange) && core.acceptStack(item, 1, this) == 1 && offloadImmediately()){
|
||||
Call.transferItemTo(item, 1,
|
||||
mineTile.worldx() + Mathf.range(tilesize / 2f),
|
||||
mineTile.worldy() + Mathf.range(tilesize / 2f), core.tile());
|
||||
mineTile.worldy() + Mathf.range(tilesize / 2f), core);
|
||||
}else if(acceptsItem(item)){
|
||||
//this is clientside, since items are synced anyway
|
||||
InputHandler.transferItemToUnit(item,
|
||||
mineTile.worldx() + Mathf.range(tilesize / 2f),
|
||||
mineTile.worldy() + Mathf.range(tilesize / 2f),
|
||||
this);
|
||||
}else{
|
||||
mineTile = null;
|
||||
mineTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
@@ -69,17 +70,22 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc{
|
||||
|
||||
boolean dropUnit(UnitPayload payload){
|
||||
Unit u = payload.unit;
|
||||
Fx.unitDrop.at(this);
|
||||
|
||||
//can't drop ground units
|
||||
if((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f){
|
||||
if(((tileOn() == null || tileOn().solid()) && u.elevation < 0.1f) || (!floorOn().isLiquid && u instanceof WaterMovec)){
|
||||
return false;
|
||||
}
|
||||
|
||||
//clients do not drop payloads
|
||||
if(Vars.net.client()) return true;
|
||||
|
||||
u.set(this);
|
||||
u.trns(Tmp.v1.rnd(Mathf.random(2f)));
|
||||
u.rotation(rotation);
|
||||
//reset the ID to a new value to make sure it's synced
|
||||
u.id = EntityGroup.nextId();
|
||||
u.add();
|
||||
Fx.unitDrop.at(u);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,11 +54,13 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
||||
return unit instanceof Minerc;
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity closestCore(){
|
||||
public @Nullable
|
||||
CoreBuild closestCore(){
|
||||
return state.teams.closestCore(x, y, team);
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity core(){
|
||||
public @Nullable
|
||||
CoreBuild core(){
|
||||
return team.core();
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
||||
clearUnit();
|
||||
}
|
||||
|
||||
CoreEntity core = closestCore();
|
||||
CoreBuild core = closestCore();
|
||||
|
||||
if(!dead()){
|
||||
set(unit);
|
||||
@@ -114,7 +116,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
||||
//update some basic state to sync things
|
||||
if(unit.type().canBoost){
|
||||
Tile tile = unit.tileOn();
|
||||
unit.elevation(Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f));
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
|
||||
}
|
||||
}else if(core != null){
|
||||
//have a small delay before death to prevent the camera from jumping around too quickly
|
||||
@@ -205,7 +207,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
|
||||
Draw.z(Layer.playerName);
|
||||
float z = Drawf.text();
|
||||
|
||||
BitmapFont font = Fonts.def;
|
||||
Font font = Fonts.def;
|
||||
GlyphLayout layout = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
|
||||
final float nameHeight = 11;
|
||||
final float textHeight = 15;
|
||||
|
||||
@@ -9,12 +9,14 @@ import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
@@ -23,7 +25,7 @@ import mindustry.world.blocks.environment.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component(base = true)
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable{
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Displayable, Senseable{
|
||||
|
||||
@Import boolean hovering;
|
||||
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health;
|
||||
@@ -50,15 +52,43 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
lookAt(x, y);
|
||||
}
|
||||
|
||||
public boolean inRange(Position other){
|
||||
return within(other, type.range);
|
||||
}
|
||||
|
||||
public boolean hasWeapons(){
|
||||
return type.hasWeapons();
|
||||
}
|
||||
|
||||
public float range(){
|
||||
return type.range;
|
||||
}
|
||||
|
||||
@Replace
|
||||
public float clipSize(){
|
||||
return type.region.getWidth() * 2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.totalItems) return stack().amount;
|
||||
if(sensor == LAccess.rotation) return rotation;
|
||||
if(sensor == LAccess.health) return health;
|
||||
if(sensor == LAccess.x) return x;
|
||||
if(sensor == LAccess.y) return y;
|
||||
if(sensor == LAccess.team) return team.id;
|
||||
if(sensor == LAccess.shooting) return isShooting() ? 1 : 0;
|
||||
if(sensor == LAccess.shootX) return aimX();
|
||||
if(sensor == LAccess.shootY) return aimY();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double sense(Content content){
|
||||
if(content == stack().item) return stack().amount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int itemCapacity(){
|
||||
return type.itemCapacity;
|
||||
@@ -130,7 +160,6 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
this.type = type;
|
||||
this.maxHealth = type.health;
|
||||
this.drag = type.drag;
|
||||
this.elevation = type.flying ? 1f : 0;
|
||||
this.armor = type.armor;
|
||||
this.hitSize = type.hitsize;
|
||||
this.hovering = type.hovering;
|
||||
@@ -174,7 +203,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
@Override
|
||||
public void landed(){
|
||||
if(type.landShake > 0f){
|
||||
Effects.shake(type.landShake, type.landShake, this);
|
||||
Effect.shake(type.landShake, type.landShake, this);
|
||||
}
|
||||
|
||||
type.landed(base());
|
||||
@@ -245,6 +274,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
if(floor.damageTaken > 0f){
|
||||
damageContinuous(floor.damageTaken);
|
||||
}
|
||||
|
||||
if(!net.client() && tile.solid()){
|
||||
if(type.canBoost){
|
||||
elevation = 1f;
|
||||
}else{
|
||||
kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//AI only updates on the server
|
||||
@@ -252,6 +289,11 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
controller.updateUnit();
|
||||
}
|
||||
|
||||
//clear controller when it becomes invalid
|
||||
if(!controller.isValidController()){
|
||||
resetController();
|
||||
}
|
||||
|
||||
//do not control anything when deactivated
|
||||
if(deactivated){
|
||||
controlWeapons(false, false);
|
||||
@@ -276,9 +318,9 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
|
||||
float shake = hitSize / 3f;
|
||||
|
||||
Effects.scorch(x, y, (int)(hitSize / 5));
|
||||
Effect.scorch(x, y, (int)(hitSize / 5));
|
||||
Fx.explosion.at(this);
|
||||
Effects.shake(shake, shake, this);
|
||||
Effect.shake(shake, shake, this);
|
||||
type.deathSound.at(this);
|
||||
|
||||
Events.fire(new UnitDestroyEvent(base()));
|
||||
@@ -297,7 +339,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
|
||||
if(type.wreckRegions[i].found()){
|
||||
float range = type.hitsize/4f;
|
||||
Tmp.v1.rnd(range);
|
||||
Effects.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
|
||||
Effect.decal(type.wreckRegions[i], x + Tmp.v1.x, y + Tmp.v1.y, rotation - 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -35,6 +36,27 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public void lookAt(float angle){
|
||||
if(onLiquid()){
|
||||
rotation = Angles.moveToward(rotation, angle, type.rotateSpeed * Time.delta);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Replace
|
||||
public boolean canShoot(){
|
||||
return onLiquid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(){
|
||||
tleft.clear();
|
||||
tright.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
float z = Draw.z();
|
||||
|
||||
@@ -72,5 +94,10 @@ abstract class WaterMoveComp implements Posc, Velc, Hitboxc, Flyingc, Unitc{
|
||||
Floor on = isFlying() ? Blocks.air.asFloor() : floorOn();
|
||||
return on.isDeep() ? 1.3f : 1f;
|
||||
}
|
||||
|
||||
public boolean onLiquid(){
|
||||
Tile tile = tileOn();
|
||||
return tile != null && tile.floor().isLiquid;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
|
||||
|
||||
/** weapon mount array, never null */
|
||||
@SyncLocal WeaponMount[] mounts = {};
|
||||
@ReadOnly transient float range, aimX, aimY;
|
||||
@ReadOnly transient float aimX, aimY;
|
||||
@ReadOnly transient boolean isRotate;
|
||||
boolean isShooting;
|
||||
float ammo;
|
||||
@@ -35,16 +35,10 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
|
||||
}
|
||||
}
|
||||
|
||||
boolean inRange(Position other){
|
||||
return within(other, range);
|
||||
}
|
||||
|
||||
void setupWeapons(UnitType def){
|
||||
mounts = new WeaponMount[def.weapons.size];
|
||||
range = def.range;
|
||||
for(int i = 0; i < mounts.length; i++){
|
||||
mounts[i] = new WeaponMount(def.weapons.get(i));
|
||||
range = Math.max(range, def.weapons.get(i).bullet.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +76,15 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
|
||||
aimY = y;
|
||||
}
|
||||
|
||||
boolean canShoot(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Update shooting and rotation for this unit. */
|
||||
@Override
|
||||
public void update(){
|
||||
boolean can = canShoot();
|
||||
|
||||
for(WeaponMount mount : mounts){
|
||||
Weapon weapon = mount.weapon;
|
||||
mount.reload = Math.max(mount.reload - Time.delta * reloadMultiplier, 0);
|
||||
@@ -97,19 +97,20 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
|
||||
}
|
||||
|
||||
//rotate if applicable
|
||||
if(weapon.rotate && (mount.rotate || mount.shoot)){
|
||||
if(weapon.rotate && (mount.rotate || mount.shoot) && can){
|
||||
float axisX = this.x + Angles.trnsx(rotation - 90, weapon.x, weapon.y),
|
||||
axisY = this.y + Angles.trnsy(rotation - 90, weapon.x, weapon.y);
|
||||
|
||||
mount.targetRotation = Angles.angle(axisX, axisY, mount.aimX, mount.aimY) - rotation;
|
||||
mount.rotation = Angles.moveToward(mount.rotation, mount.targetRotation, weapon.rotateSpeed * Time.delta);
|
||||
}else{
|
||||
}else if(!weapon.rotate){
|
||||
mount.rotation = 0;
|
||||
mount.targetRotation = angleTo(mount.aimX, mount.aimY);
|
||||
}
|
||||
|
||||
//shoot if applicable
|
||||
if(mount.shoot && //must be shooting
|
||||
can && //must be able to shoot
|
||||
(ammo > 0 || !state.rules.unitAmmo || team().rules().infiniteAmmo) && //check ammo
|
||||
(!weapon.alternate || mount.side == weapon.flipSprite) &&
|
||||
vel.len() >= mount.weapon.minShootVelocity && //check velocity requirements
|
||||
@@ -162,7 +163,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc{
|
||||
}
|
||||
boolean parentize = ammo.keepVelocity;
|
||||
|
||||
Effects.shake(weapon.shake, weapon.shake, x, y);
|
||||
Effect.shake(weapon.shake, weapon.shake, x, y);
|
||||
weapon.ejectEffect.at(x, y, rotation * side);
|
||||
ammo.shootEffect.at(x, y, rotation, parentize ? this : null);
|
||||
ammo.smokeEffect.at(x, y, rotation, parentize ? this : null);
|
||||
|
||||
@@ -5,48 +5,151 @@ import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.indexer;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class AIController implements UnitController{
|
||||
protected static final Vec2 vec = new Vec2();
|
||||
protected static final int timerTarget = 0;
|
||||
|
||||
protected Unit unit;
|
||||
protected Teamc target;
|
||||
protected Interval timer = new Interval(4);
|
||||
|
||||
/** main target that is being faced */
|
||||
protected Teamc target;
|
||||
/** targets for each weapon */
|
||||
protected Teamc[] targets = {};
|
||||
|
||||
{
|
||||
timer.reset(0, Mathf.random(40f));
|
||||
}
|
||||
|
||||
protected void targetClosestAllyFlag(BlockFlag flag){
|
||||
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getAllied(unit.team, flag));
|
||||
if(target != null) this.target = target.build;
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
updateTargeting();
|
||||
updateMovement();
|
||||
}
|
||||
|
||||
protected void targetClosestEnemyFlag(BlockFlag flag){
|
||||
Tile target = Geometry.findClosest(unit.x, unit.y, indexer.getEnemy(unit.team, flag));
|
||||
if(target != null) this.target = target.build;
|
||||
protected void updateMovement(){
|
||||
|
||||
}
|
||||
|
||||
protected void updateTargeting(){
|
||||
if(unit.hasWeapons()){
|
||||
|
||||
updateWeapons();
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateWeapons(){
|
||||
if(targets.length != unit.mounts.length) targets = new Teamc[unit.mounts.length];
|
||||
|
||||
float rotation = unit.rotation - 90;
|
||||
boolean ret = retarget();
|
||||
|
||||
if(ret){
|
||||
target = findTarget(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
||||
}
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y)){
|
||||
target = null;
|
||||
}
|
||||
|
||||
for(int i = 0; i < targets.length; i++){
|
||||
WeaponMount mount = unit.mounts[i];
|
||||
Weapon weapon = mount.weapon;
|
||||
|
||||
float mountX = unit.x + Angles.trnsx(rotation, weapon.x, weapon.y),
|
||||
mountY = unit.y + Angles.trnsy(rotation, weapon.x, weapon.y);
|
||||
|
||||
if(unit.type().singleTarget){
|
||||
targets[i] = target;
|
||||
}else{
|
||||
if(ret){
|
||||
targets[i] = findTarget(mountX, mountY, weapon.bullet.range(), weapon.bullet.collidesAir, weapon.bullet.collidesGround);
|
||||
}
|
||||
|
||||
if(Units.invalidateTarget(targets[i], unit.team, mountX, mountY, weapon.bullet.range())){
|
||||
targets[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean shoot = false;
|
||||
|
||||
if(targets[i] != null){
|
||||
shoot = targets[i].within(mountX, mountY, weapon.bullet.range());
|
||||
|
||||
if(shoot){
|
||||
Vec2 to = Predict.intercept(unit, targets[i], weapon.bullet.speed);
|
||||
mount.aimX = to.x;
|
||||
mount.aimY = to.y;
|
||||
}
|
||||
}
|
||||
|
||||
mount.shoot = shoot;
|
||||
mount.rotate = shoot;
|
||||
}
|
||||
}
|
||||
|
||||
protected Teamc targetFlag(float x, float y, BlockFlag flag, boolean enemy){
|
||||
Tile target = Geometry.findClosest(x, y, enemy ? indexer.getEnemy(unit.team, flag) : indexer.getAllied(unit.team, flag));
|
||||
return target == null ? null : target.build;
|
||||
}
|
||||
|
||||
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
|
||||
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground);
|
||||
}
|
||||
|
||||
protected boolean retarget(){
|
||||
return timer.get(timerTarget, 30);
|
||||
}
|
||||
|
||||
protected void targetClosest(){
|
||||
Teamc newTarget = Units.closestTarget(unit.team, unit.x, unit.y, Math.max(unit.range(), unit.type().range), u -> (unit.type().targetAir && u.isFlying()) || (unit.type().targetGround && !u.isFlying()));
|
||||
if(newTarget != null){
|
||||
target = newTarget;
|
||||
}
|
||||
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
|
||||
return target(x, y, range, air, ground);
|
||||
}
|
||||
|
||||
protected void init(){
|
||||
|
||||
}
|
||||
|
||||
protected void circle(Position target, float circleLength){
|
||||
circle(target, circleLength, unit.type().speed);
|
||||
}
|
||||
|
||||
protected void circle(Position target, float circleLength, float speed){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
if(vec.len() < circleLength){
|
||||
vec.rotate((circleLength - vec.len()) / circleLength * 180f);
|
||||
}
|
||||
|
||||
vec.setLength(speed * Time.delta);
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
protected void moveTo(Position target, float circleLength){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / 100f, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.type().speed * Time.delta * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unit(Unit unit){
|
||||
if(this.unit == unit) return;
|
||||
|
||||
@@ -53,7 +53,8 @@ public class BuildPlan{
|
||||
|
||||
}
|
||||
|
||||
public static Object pointConfig(Object config, Cons<Point2> cons){
|
||||
/** Transforms the internal position of this config using the specified function, and return the result. */
|
||||
public static Object pointConfig(Block block, Object config, Cons<Point2> cons){
|
||||
if(config instanceof Point2){
|
||||
config = ((Point2)config).cpy();
|
||||
cons.get((Point2)config);
|
||||
@@ -65,14 +66,15 @@ public class BuildPlan{
|
||||
cons.get(result[i++]);
|
||||
}
|
||||
config = result;
|
||||
}else if(block != null){
|
||||
config = block.pointConfig(config, cons);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/** If this requests's config is a Point2 or an array of Point2s, this returns a copy of them for transformation.
|
||||
* Otherwise does nothing. */
|
||||
/** Transforms the internal position of this config using the specified function. */
|
||||
public void pointConfig(Cons<Point2> cons){
|
||||
this.config = pointConfig(this.config, cons);
|
||||
this.config = pointConfig(block, this.config, cons);
|
||||
}
|
||||
|
||||
public BuildPlan copy(){
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package mindustry.entities.units;
|
||||
|
||||
public class StateMachine{
|
||||
private UnitState state;
|
||||
|
||||
public void update(){
|
||||
if(state != null) state.update();
|
||||
}
|
||||
|
||||
public void set(UnitState next){
|
||||
if(next == state) return;
|
||||
if(state != null) state.exited();
|
||||
this.state = next;
|
||||
if(next != null) next.entered();
|
||||
}
|
||||
|
||||
public UnitState current(){
|
||||
return state;
|
||||
}
|
||||
|
||||
public boolean is(UnitState state){
|
||||
return this.state == state;
|
||||
}
|
||||
|
||||
public interface UnitState{
|
||||
default void entered(){
|
||||
}
|
||||
|
||||
default void exited(){
|
||||
}
|
||||
|
||||
default void update(){
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package mindustry.entities.units;
|
||||
import arc.*;
|
||||
|
||||
public enum UnitCommand{
|
||||
attack, retreat, rally;
|
||||
attack, retreat, rally, idle;
|
||||
|
||||
private final String localized;
|
||||
public static final UnitCommand[] all = values();
|
||||
|
||||
@@ -5,6 +5,7 @@ import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
@@ -87,6 +88,14 @@ public class EventType{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientPreConnectEvent{
|
||||
public final Host host;
|
||||
|
||||
public ClientPreConnectEvent(Host host){
|
||||
this.host = host;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlayerChatEvent{
|
||||
public final Player player;
|
||||
public final String message;
|
||||
@@ -157,24 +166,13 @@ public class EventType{
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the player taps a block. */
|
||||
public static class TapEvent{
|
||||
public final Building tile;
|
||||
public final Player player;
|
||||
|
||||
public TapEvent(Building tile, Player player){
|
||||
this.tile = tile;
|
||||
this.player = player;
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the player sets a specific block. */
|
||||
public static class TapConfigEvent{
|
||||
/** Called when the configures sets a specific block. */
|
||||
public static class ConfigEvent{
|
||||
public final Building tile;
|
||||
public final Player player;
|
||||
public final Object value;
|
||||
|
||||
public TapConfigEvent(Building tile, Player player, Object value){
|
||||
public ConfigEvent(Building tile, Player player, Object value){
|
||||
this.tile = tile;
|
||||
this.player = player;
|
||||
this.value = value;
|
||||
|
||||
@@ -29,46 +29,26 @@ public class Objectives{
|
||||
}
|
||||
}
|
||||
|
||||
public static class SectorWave extends SectorObjective{
|
||||
public int wave;
|
||||
public static class SectorComplete extends SectorObjective{
|
||||
|
||||
public SectorWave(SectorPreset zone, int wave){
|
||||
public SectorComplete(SectorPreset zone){
|
||||
this.preset = zone;
|
||||
this.wave = wave;
|
||||
}
|
||||
|
||||
protected SectorWave(){}
|
||||
protected SectorComplete(){}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return preset.bestWave() >= wave;
|
||||
return preset.sector.isCaptured();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String display(){
|
||||
return Core.bundle.format("requirement.wave", wave, preset.localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Launched extends SectorObjective{
|
||||
|
||||
public Launched(SectorPreset zone){
|
||||
this.preset = zone;
|
||||
}
|
||||
|
||||
protected Launched(){}
|
||||
|
||||
@Override
|
||||
public boolean complete(){
|
||||
return preset.hasLaunched();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String display(){
|
||||
return Core.bundle.format("requirement.core", preset.localizedName);
|
||||
return Core.bundle.format("requirement.capture", preset.localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO merge
|
||||
public abstract static class SectorObjective implements Objective{
|
||||
public @NonNull SectorPreset preset;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ public class Rules{
|
||||
public Team defaultTeam = Team.sharded;
|
||||
/** team of the enemy in waves/sectors */
|
||||
public Team waveTeam = Team.crux;
|
||||
/** name of the custom mode that this ruleset describes, or null. */
|
||||
public @Nullable String modeName;
|
||||
/** special tags for additional info */
|
||||
public StringMap tags = new StringMap();
|
||||
|
||||
|
||||
@@ -165,9 +165,10 @@ public class Saves{
|
||||
}
|
||||
|
||||
public void deleteAll(){
|
||||
saves.clear();
|
||||
for(Fi file : saveDirectory.list()){
|
||||
file.delete();
|
||||
for(SaveSlot slot : saves.copy()){
|
||||
if(!slot.isSector()){
|
||||
slot.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +188,7 @@ public class Saves{
|
||||
current = this;
|
||||
totalPlaytime = meta.timePlayed;
|
||||
savePreview();
|
||||
}catch(Exception e){
|
||||
}catch(Throwable e){
|
||||
throw new SaveException(e);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +271,7 @@ public class Saves{
|
||||
mods.removeAll(Vars.mods.getModStrings());
|
||||
|
||||
if(!mods.isEmpty()){
|
||||
ui.showConfirm("$warning", Core.bundle.format("mod.missing", mods.toString("\n")), run);
|
||||
ui.showConfirm("@warning", Core.bundle.format("mod.missing", mods.toString("\n")), run);
|
||||
}else{
|
||||
run.run();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ import static mindustry.Vars.*;
|
||||
public class Schematics implements Loadable{
|
||||
private static final Schematic tmpSchem = new Schematic(new Seq<>(), new StringMap(), 0, 0);
|
||||
private static final Schematic tmpSchem2 = new Schematic(new Seq<>(), new StringMap(), 0, 0);
|
||||
public static final String base64Header = "bXNjaAB";
|
||||
|
||||
private static final byte[] header = {'m', 's', 'c', 'h'};
|
||||
private static final byte version = 1;
|
||||
@@ -584,7 +583,7 @@ public class Schematics implements Loadable{
|
||||
int ox = schem.width/2, oy = schem.height/2;
|
||||
|
||||
schem.tiles.each(req -> {
|
||||
req.config = BuildPlan.pointConfig(req.config, p -> {
|
||||
req.config = BuildPlan.pointConfig(req.block, req.config, p -> {
|
||||
int cx = p.x, cy = p.y;
|
||||
int lx = cx;
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SectorInfo{
|
||||
//update core items
|
||||
coreItems.clear();
|
||||
|
||||
CoreEntity entity = state.rules.defaultTeam.core();
|
||||
CoreBuild entity = state.rules.defaultTeam.core();
|
||||
|
||||
if(entity != null){
|
||||
ItemModule items = entity.items;
|
||||
@@ -97,7 +97,7 @@ public class SectorInfo{
|
||||
updateCoreDeltas();
|
||||
}
|
||||
|
||||
CoreEntity ent = state.rules.defaultTeam.core();
|
||||
CoreBuild ent = state.rules.defaultTeam.core();
|
||||
|
||||
//refresh throughput
|
||||
if(time.get(refreshPeriod)){
|
||||
@@ -141,7 +141,7 @@ public class SectorInfo{
|
||||
}
|
||||
|
||||
private void updateCoreDeltas(){
|
||||
CoreEntity ent = state.rules.defaultTeam.core();
|
||||
CoreBuild ent = state.rules.defaultTeam.core();
|
||||
for(int i = 0; i < lastCoreItems.length; i++){
|
||||
lastCoreItems[i] = ent == null ? 0 : ent.items.get(i);
|
||||
}
|
||||
@@ -160,5 +160,9 @@ public class SectorInfo{
|
||||
|
||||
/** mean in terms of items produced per refresh rate (currently, per second) */
|
||||
public float mean;
|
||||
|
||||
public String toString(){
|
||||
return mean + "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,12 @@ public class Team implements Comparable<Team>{
|
||||
blue = new Team(5, "blue", Color.royal.cpy());
|
||||
|
||||
static{
|
||||
Mathf.random.setSeed(8);
|
||||
Mathf.rand.setSeed(8);
|
||||
//create the whole 256 placeholder teams
|
||||
for(int i = 6; i < all.length; i++){
|
||||
new Team(i, "team#" + i, Color.HSVtoRGB(360f * Mathf.random(), 100f * Mathf.random(0.6f, 1f), 100f * Mathf.random(0.8f, 1f), 1f));
|
||||
}
|
||||
Mathf.random.setSeed(new Rand().nextLong());
|
||||
Mathf.rand.setSeed(new Rand().nextLong());
|
||||
}
|
||||
|
||||
public static Team get(int id){
|
||||
@@ -91,7 +91,7 @@ public class Team implements Comparable<Team>{
|
||||
return state.teams.get(this);
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity core(){
|
||||
public @Nullable CoreBuild core(){
|
||||
return data().core();
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public class Team implements Comparable<Team>{
|
||||
return state.teams.areEnemies(this, other);
|
||||
}
|
||||
|
||||
public Seq<CoreEntity> cores(){
|
||||
public Seq<CoreBuild> cores(){
|
||||
return state.teams.cores(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
@@ -21,15 +24,15 @@ public class Teams{
|
||||
active.add(get(Team.crux));
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity closestEnemyCore(float x, float y, Team team){
|
||||
public @Nullable CoreBuild closestEnemyCore(float x, float y, Team team){
|
||||
for(Team enemy : team.enemies()){
|
||||
CoreEntity tile = Geometry.findClosest(x, y, enemy.cores());
|
||||
CoreBuild tile = Geometry.findClosest(x, y, enemy.cores());
|
||||
if(tile != null) return tile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity closestCore(float x, float y, Team team){
|
||||
public @Nullable CoreBuild closestCore(float x, float y, Team team){
|
||||
return Geometry.findClosest(x, y, get(team).cores);
|
||||
}
|
||||
|
||||
@@ -37,10 +40,10 @@ public class Teams{
|
||||
return get(team).enemies;
|
||||
}
|
||||
|
||||
public boolean eachEnemyCore(Team team, Boolf<CoreEntity> ret){
|
||||
public boolean eachEnemyCore(Team team, Boolf<CoreBuild> ret){
|
||||
for(TeamData data : active){
|
||||
if(areEnemies(team, data.team)){
|
||||
for(CoreEntity tile : data.cores){
|
||||
for(CoreBuild tile : data.cores){
|
||||
if(ret.get(tile)){
|
||||
return true;
|
||||
}
|
||||
@@ -68,12 +71,12 @@ public class Teams{
|
||||
return map[team.id];
|
||||
}
|
||||
|
||||
public Seq<CoreEntity> playerCores(){
|
||||
public Seq<CoreBuild> playerCores(){
|
||||
return get(state.rules.defaultTeam).cores;
|
||||
}
|
||||
|
||||
/** Do not modify! */
|
||||
public Seq<CoreEntity> cores(Team team){
|
||||
public Seq<CoreBuild> cores(Team team){
|
||||
return get(team).cores;
|
||||
}
|
||||
|
||||
@@ -98,7 +101,7 @@ public class Teams{
|
||||
return active;
|
||||
}
|
||||
|
||||
public void registerCore(CoreEntity core){
|
||||
public void registerCore(CoreBuild core){
|
||||
TeamData data = get(core.team());
|
||||
//add core if not present
|
||||
if(!data.cores.contains(core)){
|
||||
@@ -113,7 +116,7 @@ public class Teams{
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterCore(CoreEntity entity){
|
||||
public void unregisterCore(CoreBuild entity){
|
||||
TeamData data = get(entity.team());
|
||||
//remove core
|
||||
data.cores.remove(entity);
|
||||
@@ -143,11 +146,17 @@ public class Teams{
|
||||
}
|
||||
|
||||
public class TeamData{
|
||||
public final Seq<CoreEntity> cores = new Seq<>();
|
||||
public final Seq<CoreBuild> cores = new Seq<>();
|
||||
public final Team team;
|
||||
public final BaseAI ai;
|
||||
|
||||
public Team[] enemies = {};
|
||||
/** Planned blocks for drones. This is usually only blocks that have been broken. */
|
||||
public Queue<BlockPlan> blocks = new Queue<>();
|
||||
/** The current command for units to follow. */
|
||||
public UnitCommand command = UnitCommand.attack;
|
||||
/** Target items to mine. */
|
||||
public Seq<Item> mineItems = Seq.with(Items.copper, Items.lead, Items.titanium, Items.thorium);
|
||||
|
||||
public TeamData(Team team){
|
||||
this.team = team;
|
||||
@@ -166,7 +175,7 @@ public class Teams{
|
||||
return cores.isEmpty();
|
||||
}
|
||||
|
||||
public @Nullable CoreEntity core(){
|
||||
public @Nullable CoreBuild core(){
|
||||
return cores.isEmpty() ? null : cores.first();
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ public class Drawf{
|
||||
|
||||
Lines.stroke(12f * scale);
|
||||
Lines.precise(true);
|
||||
Lines.line(line, x + Tmp.v1.x, y + Tmp.v1.y, x2 - Tmp.v1.x, y2 - Tmp.v1.y, CapStyle.none, 0f);
|
||||
Lines.line(line, x + Tmp.v1.x, y + Tmp.v1.y, x2 - Tmp.v1.x, y2 - Tmp.v1.y, false, 0f);
|
||||
Lines.precise(false);
|
||||
Lines.stroke(1f);
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ public class LoadRenderer implements Disposable{
|
||||
Lines.poly(w/2, h/2, 4, rad2);
|
||||
|
||||
if(assets.isLoaded("tech")){
|
||||
BitmapFont font = assets.get("tech");
|
||||
Font font = assets.get("tech");
|
||||
font.getData().markupEnabled = true;
|
||||
|
||||
int panei = 0;
|
||||
@@ -461,10 +461,10 @@ public class LoadRenderer implements Disposable{
|
||||
String key = name.contains("script") ? "scripts" : name.contains("content") ? "content" : name.contains("mod") ? "mods" : name.contains("msav") ||
|
||||
name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system";
|
||||
|
||||
BitmapFont font = assets.get("tech");
|
||||
Font font = assets.get("tech");
|
||||
font.setColor(Pal.accent);
|
||||
Draw.color(Color.black);
|
||||
font.draw(red + "[[[[ " +key + " ]]\n\n"+orange+"<" + Version.modifier + " " + (Version.build == 0 ? " [init]" : Version.build == -1 ? " custom" : " " + Version.build) + ">", w/2f, h/2f + 110*s, Align.center);
|
||||
font.draw(red + "[[[[ " + key + " ]]\n"+orange+"<" + Version.modifier + " " + (Version.build == 0 ? " [init]" : Version.build == -1 ? " custom" : " " + Version.build) + ">", w/2f, h/2f + 110*s, Align.center);
|
||||
}
|
||||
|
||||
Lines.precise(false);
|
||||
|
||||
@@ -172,7 +172,7 @@ public class MinimapRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void drawLabel(float x, float y, String text, Color color){
|
||||
BitmapFont font = Fonts.outline;
|
||||
Font font = Fonts.outline;
|
||||
GlyphLayout l = Pools.obtain(GlyphLayout.class, GlyphLayout::new);
|
||||
boolean ints = font.usesIntegerPositions();
|
||||
font.getData().setScale(1 / 1.5f / Scl.scl(1f));
|
||||
|
||||
@@ -42,10 +42,10 @@ public class OverlayRenderer{
|
||||
if(!rect.setSize(Core.camera.width * 0.9f, Core.camera.height * 0.9f)
|
||||
.setCenter(Core.camera.position.x, Core.camera.position.y).contains(player.x, player.y)){
|
||||
|
||||
Tmp.v1.set(player.x, player.y).sub(player).setLength(indicatorLength);
|
||||
Tmp.v1.set(player).sub(Vars.player).setLength(indicatorLength);
|
||||
|
||||
Lines.stroke(2f, player.team().color);
|
||||
Lines.lineAngle(player.x + Tmp.v1.x, player.y + Tmp.v1.y, Tmp.v1.angle(), 4f);
|
||||
Lines.stroke(2f, Vars.player.team().color);
|
||||
Lines.lineAngle(Vars.player.x + Tmp.v1.x, Vars.player.y + Tmp.v1.y, Tmp.v1.angle(), 4f);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ public class Pal{
|
||||
|
||||
sap = Color.valueOf("665c9f"),
|
||||
|
||||
spore = Color.valueOf("7457ce"),
|
||||
|
||||
shield = Color.valueOf("ffd37f").a(0.7f),
|
||||
shieldIn = Color.black.cpy().a(0f),
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ public class Shaders{
|
||||
public static PlanetShader planet;
|
||||
public static PlanetGridShader planetGrid;
|
||||
public static AtmosphereShader atmosphere;
|
||||
public static MeshShader mesh = new MeshShader();
|
||||
public static MeshShader mesh;
|
||||
public static Shader unlit;
|
||||
public static Shader screenspace;
|
||||
|
||||
public static void init(){
|
||||
mesh = new MeshShader();
|
||||
blockbuild = new BlockBuild();
|
||||
try{
|
||||
shield = new ShieldShader();
|
||||
|
||||
@@ -18,6 +18,10 @@ public class Trail{
|
||||
points = new Seq<>(length);
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
points.clear();
|
||||
}
|
||||
|
||||
public void draw(Color color, float width){
|
||||
Draw.color(color);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class PlanetRenderer implements Disposable{
|
||||
/** The sun/main planet of the solar system from which everything is rendered. */
|
||||
public final Planet solarSystem = Planets.sun;
|
||||
/** Planet being looked at. */
|
||||
public Planet planet = Planets.starter;
|
||||
public Planet planet = Planets.serpulo;
|
||||
/** Camera used for rendering. */
|
||||
public Camera3D cam = new Camera3D();
|
||||
/** Raw vertex batch. */
|
||||
|
||||
@@ -13,7 +13,6 @@ import arc.scene.ui.layout.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -74,7 +73,7 @@ public class DesktopInput extends InputHandler{
|
||||
Core.keybinds.get(Binding.schematic_flip_y).key.toString())).style(Styles.outlineLabel);
|
||||
b.row();
|
||||
b.table(a -> {
|
||||
a.button("$schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
|
||||
a.button("@schematic.add", Icon.save, this::showSchematicSave).colspan(2).size(250f, 50f).disabled(f -> lastSchematic == null || lastSchematic.file != null);
|
||||
});
|
||||
}).margin(6f);
|
||||
});
|
||||
@@ -177,7 +176,7 @@ public class DesktopInput extends InputHandler{
|
||||
public void update(){
|
||||
super.update();
|
||||
|
||||
if(net.active() && Core.input.keyTap(Binding.player_list)){
|
||||
if(net.active() && Core.input.keyTap(Binding.player_list) && (scene.getKeyboardFocus() == null || scene.getKeyboardFocus().isDescendantOf(ui.listfrag.content))){
|
||||
ui.listfrag.toggle();
|
||||
}
|
||||
|
||||
@@ -197,7 +196,7 @@ public class DesktopInput extends InputHandler{
|
||||
Core.camera.position.lerpDelta(player, Core.settings.getBool("smoothcamera") ? 0.08f : 1f);
|
||||
}
|
||||
|
||||
shouldShoot = true;
|
||||
shouldShoot = !scene.hasMouse();
|
||||
|
||||
if(!scene.hasMouse()){
|
||||
if(Core.input.keyDown(Binding.control) && Core.input.keyTap(Binding.select)){
|
||||
@@ -330,20 +329,20 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
table.button(Icon.paste, Styles.clearPartiali, () -> {
|
||||
ui.schematics.show();
|
||||
});
|
||||
}).tooltip("Schematics");
|
||||
|
||||
table.button(Icon.tree, Styles.clearPartiali, () -> {
|
||||
ui.research.show();
|
||||
}).visible(() -> state.isCampaign());
|
||||
}).visible(() -> state.isCampaign()).tooltip("Research");
|
||||
|
||||
table.button(Icon.map, Styles.clearPartiali, () -> {
|
||||
ui.planet.show();
|
||||
}).visible(() -> state.isCampaign());
|
||||
}).visible(() -> state.isCampaign()).tooltip("Planet Map");
|
||||
|
||||
table.button(Icon.up, Styles.clearPartiali, () -> {
|
||||
ui.planet.show(state.getSector(), player.team().core());
|
||||
}).visible(() -> state.isCampaign())
|
||||
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements));
|
||||
.disabled(b -> player.team().core() == null || !player.team().core().items.has(player.team().core().block.requirements)).tooltip("Launch Core");
|
||||
}
|
||||
|
||||
void pollInput(){
|
||||
@@ -567,10 +566,18 @@ public class DesktopInput extends InputHandler{
|
||||
|
||||
protected void updateMovement(Unit unit){
|
||||
boolean omni = !(unit instanceof WaterMovec);
|
||||
boolean legs = unit.isGrounded();
|
||||
boolean ground = unit.isGrounded();
|
||||
|
||||
float strafePenalty = legs ? 1f : Mathf.lerp(1f, unit.type().strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
|
||||
float speed = unit.type().speed * Mathf.lerp(1f, unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation()) * strafePenalty;
|
||||
float strafePenalty = ground ? 1f : Mathf.lerp(1f, unit.type().strafePenalty, Angles.angleDist(unit.vel().angle(), unit.rotation()) / 180f);
|
||||
float baseSpeed = unit.type().speed;
|
||||
|
||||
//limit speed to minimum formation speed to preserve formation
|
||||
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
|
||||
//add a tiny multiplier to let units catch up just in case
|
||||
baseSpeed = ((Commanderc)unit).minFormationSpeed() * 0.98f;
|
||||
}
|
||||
|
||||
float speed = baseSpeed * Mathf.lerp(1f, unit.type().canBoost ? unit.type().boostMultiplier : 1f, unit.elevation) * strafePenalty;
|
||||
float xa = Core.input.axis(Binding.move_x);
|
||||
float ya = Core.input.axis(Binding.move_y);
|
||||
boolean boosted = (unit instanceof Mechc && unit.isFlying());
|
||||
@@ -595,8 +602,8 @@ public class DesktopInput extends InputHandler{
|
||||
unit.moveAt(movement);
|
||||
}else{
|
||||
unit.moveAt(Tmp.v2.trns(unit.rotation, movement.len()));
|
||||
if(!movement.isZero() && legs){
|
||||
unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed * Time.delta);
|
||||
if(!movement.isZero() && ground){
|
||||
unit.vel.rotateTo(movement.angle(), unit.type().rotateSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,22 +618,12 @@ public class DesktopInput extends InputHandler{
|
||||
if(unit instanceof Payloadc){
|
||||
Payloadc pay = (Payloadc)unit;
|
||||
|
||||
if(Core.input.keyTap(Binding.pickupCargo) && pay.payloads().size < unit.type().payloadCapacity){
|
||||
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && u.mass() < unit.mass() && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
||||
if(target != null){
|
||||
Call.pickupUnitPayload(player, target);
|
||||
}else if(!pay.hasPayload()){
|
||||
Building tile = world.buildWorld(pay.x(), pay.y());
|
||||
|
||||
if(tile != null && tile.team() == unit.team){
|
||||
Call.pickupBlockPayload(player, tile);
|
||||
}
|
||||
}
|
||||
if(Core.input.keyTap(Binding.pickupCargo)){
|
||||
tryPickupPayload();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.dropCargo)){
|
||||
Call.dropPayload(player, player.x, player.y);
|
||||
pay.dropLastPayload();
|
||||
tryDropPayload();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,12 +86,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
|
||||
if(tile == null || tile.build == null || tile.build.items == null) return;
|
||||
public static void transferItemTo(Item item, int amount, float x, float y, Building build){
|
||||
if(build == null || build.items == null) return;
|
||||
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
|
||||
Time.run(i * 3, () -> createItemTransfer(item, amount, x, y, tile, () -> {}));
|
||||
Time.run(i * 3, () -> createItemTransfer(item, amount, x, y, build, () -> {}));
|
||||
}
|
||||
tile.build.items.add(item, amount);
|
||||
build.items.add(item, amount);
|
||||
}
|
||||
|
||||
public static void createItemTransfer(Item item, int amount, float x, float y, Position to, Runnable done){
|
||||
@@ -126,7 +126,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
if(tile != null && tile.team() == unit.team && pay.payloads().size < unit.type().payloadCapacity
|
||||
&& unit.within(tile, tilesize * tile.block.size * 1.2f)){
|
||||
//pick up block directly
|
||||
if(tile.block().buildVisibility != BuildVisibility.hidden && tile.block().size <= 2){
|
||||
if(tile.block().buildVisibility != BuildVisibility.hidden && tile.block().size <= 2 && tile.canPickup()){
|
||||
pay.pickup(tile);
|
||||
}else{ //pick up block payload
|
||||
Payload current = tile.getPayload();
|
||||
@@ -206,35 +206,30 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
);
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
public static void tileTapped(Player player, Building tile){
|
||||
if(tile == null || player == null) return;
|
||||
if(net.server() && (!Units.canInteract(player, tile) ||
|
||||
!netServer.admins.allowAction(player, ActionType.tapTile, tile.tile(), action -> {}))) throw new ValidateException(player, "Player cannot tap a tile.");
|
||||
tile.tapped(player);
|
||||
Core.app.post(() -> Events.fire(new TapEvent(tile, player)));
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.both, forward = true)
|
||||
public static void tileConfig(Player player, Building tile, @Nullable Object value){
|
||||
if(tile == null) return;
|
||||
if(net.server() && (!Units.canInteract(player, tile) ||
|
||||
!netServer.admins.allowAction(player, ActionType.configure, tile.tile(), action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile.");
|
||||
tile.configured(player, value);
|
||||
Core.app.post(() -> Events.fire(new TapConfigEvent(tile, player, value)));
|
||||
Core.app.post(() -> Events.fire(new ConfigEvent(tile, player, value)));
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
@Remote(targets = Loc.both, called = Loc.both, forward = true)
|
||||
public static void unitControl(Player player, @Nullable Unit unit){
|
||||
//clear player unit when they possess a core
|
||||
if((unit instanceof BlockUnitc && ((BlockUnitc)unit).tile() instanceof CoreEntity)){
|
||||
if((unit instanceof BlockUnitc && ((BlockUnitc)unit).tile() instanceof CoreBuild)){
|
||||
Fx.spawn.at(player);
|
||||
if(net.client()){
|
||||
control.input.controlledType = null;
|
||||
}
|
||||
|
||||
player.clearUnit();
|
||||
player.deathTimer(60f); //for instant respawn
|
||||
player.deathTimer = 61f;
|
||||
}else if(unit == null){ //just clear the unit (is this used?)
|
||||
player.clearUnit();
|
||||
//make sure it's AI controlled, so players can't overwrite each other
|
||||
}else if(unit.isAI() && unit.team == player.team() && !unit.deactivated){
|
||||
}else if(unit.isAI() && unit.team == player.team() && !unit.deactivated()){
|
||||
player.unit(unit);
|
||||
Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
|
||||
if(!player.dead()){
|
||||
@@ -243,19 +238,19 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
@Remote(targets = Loc.both, called = Loc.both, forward = true)
|
||||
public static void unitClear(Player player){
|
||||
//no free core teleports?
|
||||
if(!player.dead() && player.unit().spawnedByCore) return;
|
||||
if(player == null || !player.dead() && player.unit().spawnedByCore) return;
|
||||
|
||||
Fx.spawn.at(player);
|
||||
player.clearUnit();
|
||||
player.deathTimer(60f); //for instant respawn
|
||||
player.deathTimer = 61f; //for instant respawn
|
||||
}
|
||||
|
||||
@Remote(targets = Loc.both, called = Loc.server, forward = true)
|
||||
public static void unitCommand(Player player){
|
||||
if(player.dead() || !(player.unit() instanceof Commanderc)) return;
|
||||
if(player == null || player.dead() || !(player.unit() instanceof Commanderc)) return;
|
||||
|
||||
Commanderc commander = (Commanderc)player.unit();
|
||||
|
||||
@@ -311,7 +306,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
if(controlledType != null && player.dead()){
|
||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated);
|
||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated());
|
||||
|
||||
if(unit != null){
|
||||
Call.unitControl(player, unit);
|
||||
@@ -321,7 +316,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
public void checkUnit(){
|
||||
if(controlledType != null){
|
||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated);
|
||||
Unit unit = Units.closest(player.team(), player.x, player.y, u -> !u.isPlayer() && u.type() == controlledType && !u.deactivated());
|
||||
if(unit == null && controlledType == UnitTypes.block){
|
||||
unit = world.buildWorld(player.x, player.y) instanceof ControlBlock ? ((ControlBlock)world.buildWorld(player.x, player.y)).unit() : null;
|
||||
}
|
||||
@@ -336,6 +331,34 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
}
|
||||
|
||||
public void tryPickupPayload(){
|
||||
Unit unit = player.unit();
|
||||
if(!(unit instanceof Payloadc)) return;
|
||||
Payloadc pay = (Payloadc)unit;
|
||||
|
||||
if(pay.payloads().size >= unit.type().payloadCapacity) return;
|
||||
|
||||
Unit target = Units.closest(player.team(), pay.x(), pay.y(), unit.type().hitsize * 2.5f, u -> u.isAI() && u.isGrounded() && u.mass() < unit.mass() && u.within(unit, u.hitSize + unit.hitSize * 1.2f));
|
||||
if(target != null){
|
||||
Call.pickupUnitPayload(player, target);
|
||||
}else if(!pay.hasPayload()){
|
||||
Building tile = world.buildWorld(pay.x(), pay.y());
|
||||
|
||||
if(tile != null && tile.team == unit.team){
|
||||
Call.pickupBlockPayload(player, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void tryDropPayload(){
|
||||
Unit unit = player.unit();
|
||||
if(!(unit instanceof Payloadc)) return;
|
||||
Payloadc pay = (Payloadc)unit;
|
||||
|
||||
Call.dropPayload(player, player.x, player.y);
|
||||
pay.dropLastPayload();
|
||||
}
|
||||
|
||||
public float getMouseX(){
|
||||
return Core.input.mouseX();
|
||||
}
|
||||
@@ -396,18 +419,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
protected void showSchematicSave(){
|
||||
if(lastSchematic == null) return;
|
||||
|
||||
ui.showTextInput("$schematic.add", "$name", "", text -> {
|
||||
ui.showTextInput("@schematic.add", "@name", "", text -> {
|
||||
Schematic replacement = schematics.all().find(s -> s.name().equals(text));
|
||||
if(replacement != null){
|
||||
ui.showConfirm("$confirm", "$schematic.replace", () -> {
|
||||
ui.showConfirm("@confirm", "@schematic.replace", () -> {
|
||||
schematics.overwrite(replacement, lastSchematic);
|
||||
ui.showInfoFade("$schematic.saved");
|
||||
ui.showInfoFade("@schematic.saved");
|
||||
ui.schematics.showInfo(replacement);
|
||||
});
|
||||
}else{
|
||||
lastSchematic.tags.put("name", text);
|
||||
schematics.add(lastSchematic);
|
||||
ui.showInfoFade("$schematic.saved");
|
||||
ui.showInfoFade("@schematic.saved");
|
||||
ui.schematics.showInfo(lastSchematic);
|
||||
}
|
||||
});
|
||||
@@ -746,7 +769,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
|
||||
//call tapped event
|
||||
if(!consumed && tile.interactable(player.team())){
|
||||
Call.tileTapped(player, tile);
|
||||
tile.tapped();
|
||||
}
|
||||
|
||||
//consume tap event if necessary
|
||||
@@ -844,7 +867,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
public @Nullable Unit selectedUnit(){
|
||||
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && !u.deactivated);
|
||||
Unit unit = Units.closest(player.team(), Core.input.mouseWorld().x, Core.input.mouseWorld().y, 40f, u -> u.isAI() && !u.deactivated());
|
||||
if(unit != null){
|
||||
unit.hitbox(Tmp.r1);
|
||||
Tmp.r1.grow(6f);
|
||||
@@ -901,7 +924,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
}
|
||||
|
||||
public boolean canShoot(){
|
||||
return block == null && !Core.scene.hasMouse() && !onConfigurable() && !isDroppingItem() && !(player.builder().updateBuilding() && player.builder().isBuilding());
|
||||
return block == null && !onConfigurable() && !isDroppingItem() && !(player.builder().updateBuilding() && player.builder().isBuilding()) &&
|
||||
!(player.unit() instanceof Mechc && player.unit().isFlying());
|
||||
}
|
||||
|
||||
public boolean onConfigurable(){
|
||||
@@ -1074,192 +1098,4 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
|
||||
public int x, y, rotation;
|
||||
public boolean last;
|
||||
}
|
||||
|
||||
//TODO implement all of this!
|
||||
/*
|
||||
protected void updateKeyboard(){
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
boolean canMove = !Core.scene.hasKeyboard() || ui.minimapfrag.shown();
|
||||
|
||||
isBoosting = Core.input.keyDown(Binding.dash) && !mech.flying;
|
||||
|
||||
//if player is in solid block
|
||||
if(tile != null && tile.solid()){
|
||||
isBoosting = true;
|
||||
}
|
||||
|
||||
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
|
||||
|
||||
if(mech.flying){
|
||||
//prevent strafing backwards, have a penalty for doing so
|
||||
float penalty = 0.2f; //when going 180 degrees backwards, reduce speed to 0.2x
|
||||
speed *= Mathf.lerp(1f, penalty, Angles.angleDist(rotation, velocity.angle()) / 180f);
|
||||
}
|
||||
|
||||
movement.setZero();
|
||||
|
||||
float xa = Core.input.axis(Binding.move_x);
|
||||
float ya = Core.input.axis(Binding.move_y);
|
||||
if(!(Core.scene.getKeyboardFocus() instanceof TextField)){
|
||||
movement.y += ya * speed;
|
||||
movement.x += xa * speed;
|
||||
}
|
||||
|
||||
if(Core.input.keyDown(Binding.mouse_move)){
|
||||
movement.x += Mathf.clamp((Core.input.mouseX() - Core.graphics.getWidth() / 2f) * 0.005f, -1, 1) * speed;
|
||||
movement.y += Mathf.clamp((Core.input.mouseY() - Core.graphics.getHeight() / 2f) * 0.005f, -1, 1) * speed;
|
||||
}
|
||||
|
||||
Vec2 vec = Core.input.mouseWorld(control.input.getMouseX(), control.input.getMouseY());
|
||||
pointerX = vec.x;
|
||||
pointerY = vec.y;
|
||||
updateShooting();
|
||||
|
||||
movement.limit(speed).scl(Time.delta());
|
||||
|
||||
if(canMove){
|
||||
velocity.add(movement.x, movement.y);
|
||||
}else{
|
||||
isShooting = false;
|
||||
}
|
||||
float prex = x, prey = y;
|
||||
updateVelocityStatus();
|
||||
moved = dst(prex, prey) > 0.001f;
|
||||
|
||||
if(canMove){
|
||||
float baseLerp = mech.getRotationAlpha(this);
|
||||
if(!isShooting() || !mech.faceTarget){
|
||||
if(!movement.isZero()){
|
||||
rotation = Mathf.slerpDelta(rotation, mech.flying ? velocity.angle() : movement.angle(), 0.13f * baseLerp);
|
||||
}
|
||||
}else{
|
||||
float angle = control.input.mouseAngle(x, y);
|
||||
this.rotation = Mathf.slerpDelta(this.rotation, angle, 0.1f * baseLerp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateShooting(){
|
||||
if(!state.isEditor() && isShooting() && mech.canShoot(this)){
|
||||
weapons.update(this);
|
||||
//if(!mech.turnCursor){
|
||||
//shoot forward ignoring cursor
|
||||
//mech.weapon.update(this, x + Angles.trnsx(rotation, mech.weapon.targetDistance), y + Angles.trnsy(rotation, mech.weapon.targetDistance));
|
||||
//}else{
|
||||
//mech.weapon.update(this, pointerX, pointerY);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateTouch(){
|
||||
if(Units.invalidateTarget(target, this) &&
|
||||
!(target instanceof Building && ((Building)target).damaged() && target.isValid() && target.team() == team && mech.canHeal && dst(target) < mech.range && !(((Building)target).block instanceof BuildBlock))){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(state.isEditor()){
|
||||
target = null;
|
||||
}
|
||||
|
||||
float targetX = Core.camera.position.x, targetY = Core.camera.position.y;
|
||||
float attractDst = 15f;
|
||||
float speed = isBoosting && !mech.flying ? mech.boostSpeed : mech.speed;
|
||||
|
||||
if(moveTarget != null && !moveTarget.dead()){
|
||||
targetX = moveTarget.getX();
|
||||
targetY = moveTarget.getY();
|
||||
boolean tapping = moveTarget instanceof Building && moveTarget.team() == team;
|
||||
attractDst = 0f;
|
||||
|
||||
if(tapping){
|
||||
velocity.setAngle(angleTo(moveTarget));
|
||||
}
|
||||
|
||||
if(dst(moveTarget) <= 2f * Time.delta()){
|
||||
if(tapping && !dead()){
|
||||
Tile tile = ((Building)moveTarget).tile;
|
||||
tile.tapped(this);
|
||||
}
|
||||
|
||||
moveTarget = null;
|
||||
}
|
||||
}else{
|
||||
moveTarget = null;
|
||||
}
|
||||
|
||||
movement.set((targetX - x) / Time.delta(), (targetY - y) / Time.delta()).limit(speed);
|
||||
movement.setAngle(Mathf.slerp(movement.angle(), velocity.angle(), 0.05f));
|
||||
|
||||
if(dst(targetX, targetY) < attractDst){
|
||||
movement.setZero();
|
||||
}
|
||||
|
||||
float expansion = 3f;
|
||||
|
||||
hitbox(rect);
|
||||
rect.x -= expansion;
|
||||
rect.y -= expansion;
|
||||
rect.width += expansion * 2f;
|
||||
rect.height += expansion * 2f;
|
||||
|
||||
isBoosting = collisions.overlapsTile(rect) || dst(targetX, targetY) > 85f;
|
||||
|
||||
velocity.add(movement.scl(Time.delta()));
|
||||
|
||||
if(velocity.len() <= 0.2f && mech.flying){
|
||||
rotation += Mathf.sin(Time.time() + id * 99, 10f, 1f);
|
||||
}else if(target == null){
|
||||
rotation = Mathf.slerpDelta(rotation, velocity.angle(), velocity.len() / 10f);
|
||||
}
|
||||
|
||||
float lx = x, ly = y;
|
||||
updateVelocityStatus();
|
||||
moved = dst(lx, ly) > 0.001f;
|
||||
|
||||
if(mech.flying){
|
||||
//hovering effect
|
||||
x += Mathf.sin(Time.time() + id * 999, 25f, 0.08f);
|
||||
y += Mathf.cos(Time.time() + id * 999, 25f, 0.08f);
|
||||
}
|
||||
|
||||
//update shooting if not building, not mining and there's ammo left
|
||||
if(!isBuilding() && mineTile() == null){
|
||||
|
||||
//autofire
|
||||
if(target == null){
|
||||
isShooting = false;
|
||||
if(Core.settings.getBool("autotarget")){
|
||||
target = Units.closestTarget(team, x, y, mech.range, u -> u.team() != Team.derelict, u -> u.team() != Team.derelict);
|
||||
|
||||
if(mech.canHeal && target == null){
|
||||
target = Geometry.findClosest(x, y, indexer.getDamaged(Team.sharded));
|
||||
if(target != null && dst(target) > mech.range){
|
||||
target = null;
|
||||
}else if(target != null){
|
||||
target = ((Tile)target).entity;
|
||||
}
|
||||
}
|
||||
|
||||
if(target != null){
|
||||
mineTile(null);
|
||||
}
|
||||
}
|
||||
}else if(target.isValid() || (target instanceof Building && ((Building)target).damaged() && target.team() == team && mech.canHeal && dst(target) < mech.range)){
|
||||
//rotate toward and shoot the target
|
||||
if(mech.faceTarget){
|
||||
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.2f);
|
||||
}
|
||||
|
||||
Vec2 intercept = Predict.intercept(this, target, getWeapon().bullet.speed);
|
||||
|
||||
pointerX = intercept.x;
|
||||
pointerY = intercept.y;
|
||||
|
||||
updateShooting();
|
||||
isShooting = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
group.fill(t -> {
|
||||
t.visible(() -> (player.builder().isBuilding() || block != null || mode == breaking || !selectRequests.isEmpty()) && !schem.get());
|
||||
t.bottom().left();
|
||||
t.button("$cancel", Icon.cancel, () -> {
|
||||
t.button("@cancel", Icon.cancel, () -> {
|
||||
player.builder().clearBuilding();
|
||||
selectRequests.clear();
|
||||
mode = none;
|
||||
@@ -422,7 +422,6 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
lastSchematic = schem;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean touchDown(int screenX, int screenY, int pointer, KeyCode button){
|
||||
if(state.isMenu()) return false;
|
||||
@@ -497,9 +496,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}else{
|
||||
Tile tile = tileAt(screenX, screenY);
|
||||
|
||||
if(tile == null || tile.build == null) return false;
|
||||
|
||||
tryDropItems(tile.build, Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
tryDropItems(tile == null ? null : tile.build, Core.input.mouseWorld(screenX, screenY).x, Core.input.mouseWorld(screenX, screenY).y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -535,10 +532,10 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
lineMode = true;
|
||||
|
||||
if(mode == breaking){
|
||||
Fx.tapBlock.at(cursor.worldx(), cursor.worldy(), 1f);
|
||||
if(!state.isPaused()) Fx.tapBlock.at(cursor.worldx(), cursor.worldy(), 1f);
|
||||
}else if(block != null){
|
||||
updateLine(lineStartX, lineStartY, cursor.x, cursor.y);
|
||||
Fx.tapBlock.at(cursor.worldx() + block.offset, cursor.worldy() + block.offset, block.size);
|
||||
if(!state.isPaused()) Fx.tapBlock.at(cursor.worldx() + block.offset, cursor.worldy() + block.offset, block.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,9 +572,18 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
}
|
||||
|
||||
//apply command on double tap
|
||||
if(count == 2 && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 2f) &&
|
||||
player.unit() instanceof Commanderc){
|
||||
Call.unitCommand(player);
|
||||
if(count == 2 && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 2f)){
|
||||
if(player.unit() instanceof Commanderc){
|
||||
Call.unitCommand(player);
|
||||
}
|
||||
|
||||
if(player.unit() instanceof Payloadc){
|
||||
if(((Payloadc)player.unit()).hasPayload()){
|
||||
tryDropPayload();
|
||||
}else{
|
||||
tryPickupPayload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +761,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
shiftDeltaX %= tilesize;
|
||||
shiftDeltaY %= tilesize;
|
||||
}
|
||||
}else{
|
||||
}else if(!renderer.isLanding()){
|
||||
//pan player
|
||||
Core.camera.position.x -= deltaX;
|
||||
Core.camera.position.y -= deltaY;
|
||||
@@ -788,6 +794,8 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
Rect rect = Tmp.r3;
|
||||
|
||||
UnitType type = unit.type();
|
||||
if(type == null) return;
|
||||
|
||||
boolean flying = type.flying;
|
||||
boolean omni = !(unit instanceof WaterMovec);
|
||||
boolean legs = unit.isGrounded();
|
||||
@@ -806,7 +814,16 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
targetPos.set(Core.camera.position);
|
||||
float attractDst = 15f;
|
||||
float strafePenalty = legs ? 1f : Mathf.lerp(1f, type.strafePenalty, Angles.angleDist(unit.vel.angle(), unit.rotation) / 180f);
|
||||
float speed = type.speed * Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, unit.elevation()) * strafePenalty;
|
||||
|
||||
float baseSpeed = unit.type().speed;
|
||||
|
||||
//limit speed to minimum formation speed to preserve formation
|
||||
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
|
||||
//add a tiny multiplier to let units catch up just in case
|
||||
baseSpeed = ((Commanderc)unit).minFormationSpeed() * 0.98f;
|
||||
}
|
||||
|
||||
float speed = baseSpeed * Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, unit.elevation) * strafePenalty;
|
||||
float range = unit.hasWeapons() ? unit.range() : 0f;
|
||||
float bulletSpeed = unit.hasWeapons() ? type.weapons.first().bullet.speed : 0f;
|
||||
float mouseAngle = unit.angleTo(unit.aimX(), unit.aimY());
|
||||
@@ -898,7 +915,7 @@ public class MobileInput extends InputHandler implements GestureListener{
|
||||
|
||||
player.mouseX = intercept.x;
|
||||
player.mouseY = intercept.y;
|
||||
player.shooting = true;
|
||||
player.shooting = !boosted;
|
||||
|
||||
unit.aim(player.mouseX, player.mouseY);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public abstract class SaveFileReader{
|
||||
"command-center", "legacy-command-center"
|
||||
);
|
||||
|
||||
protected int lastRegionLength;
|
||||
|
||||
protected void region(String name, DataInput stream, CounterInputStream counter, IORunner<DataInput> cons) throws IOException{
|
||||
counter.resetCount();
|
||||
int length;
|
||||
@@ -90,6 +92,7 @@ public abstract class SaveFileReader{
|
||||
/** Reads a chunk of some length. Use the runner for reading to catch more descriptive errors. */
|
||||
public int readChunk(DataInput input, boolean isShort, IORunner<DataInput> runner) throws IOException{
|
||||
int length = isShort ? input.readUnsignedShort() : input.readInt();
|
||||
lastRegionLength = length;
|
||||
runner.accept(input);
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,6 @@ public class SaveMeta{
|
||||
this.mods = JsonIO.read(String[].class, tags.get("mods", "[]"));
|
||||
this.secinfo = secinfo;
|
||||
|
||||
secinfo.exportRates().each(e -> hasProduction |= e.value > 0.001f);
|
||||
secinfo.production.each((e, amount) -> hasProduction |= amount.mean > 0.001f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,11 @@ public abstract class SaveVersion extends SaveFileReader{
|
||||
for(int j = 0; j < amount; j++){
|
||||
readChunk(stream, true, in -> {
|
||||
byte typeid = in.readByte();
|
||||
if(EntityMapping.map(typeid) == null){
|
||||
in.skipBytes(lastRegionLength - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Entityc entity = (Entityc)EntityMapping.map(typeid).get();
|
||||
entity.read(Reads.get(in));
|
||||
entity.add();
|
||||
|
||||
@@ -3,6 +3,7 @@ package mindustry.io;
|
||||
import arc.graphics.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.pooling.*;
|
||||
import mindustry.ai.types.*;
|
||||
@@ -10,10 +11,12 @@ import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.net.Administration.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.type.*;
|
||||
@@ -46,7 +49,6 @@ public class TypeIO{
|
||||
}else if(object instanceof String){
|
||||
write.b((byte)4);
|
||||
writeString(write, (String)object);
|
||||
writeString(write, (String)object);
|
||||
}else if(object instanceof Content){
|
||||
Content map = (Content)object;
|
||||
write.b((byte)5);
|
||||
@@ -74,11 +76,28 @@ public class TypeIO{
|
||||
write.b(9);
|
||||
write.b((byte)map.content.getContentType().ordinal());
|
||||
write.s(map.content.id);
|
||||
}else if(object instanceof Boolean){
|
||||
write.b((byte)10);
|
||||
write.bool((Boolean)object);
|
||||
}else if(object instanceof Double){
|
||||
write.b((byte)11);
|
||||
write.d((Double)object);
|
||||
}else if(object instanceof Building){
|
||||
write.b((byte)12);
|
||||
write.i(((Building)object).pos());
|
||||
}else if(object instanceof LAccess){
|
||||
write.b((byte)13);
|
||||
write.s(((LAccess)object).ordinal());
|
||||
}else if(object instanceof byte[]){
|
||||
write.b((byte)14);
|
||||
write.i(((byte[])object).length);
|
||||
write.b((byte[])object);
|
||||
}else{
|
||||
throw new IllegalArgumentException("Unknown object type: " + object.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Object readObject(Reads read){
|
||||
byte type = read.b();
|
||||
switch(type){
|
||||
@@ -92,6 +111,11 @@ public class TypeIO{
|
||||
case 7: return new Point2(read.i(), read.i());
|
||||
case 8: byte len = read.b(); Point2[] out = new Point2[len]; for(int i = 0; i < len; i ++) out[i] = Point2.unpack(read.i()); return out;
|
||||
case 9: return TechTree.getNotNull(content.getByID(ContentType.all[read.b()], read.s()));
|
||||
case 10: return read.bool();
|
||||
case 11: return read.d();
|
||||
case 12: return world.build(read.i());
|
||||
case 13: return LAccess.all[read.s()];
|
||||
case 14: int blen = read.i(); byte[] bytes = new byte[blen]; read.b(bytes); return bytes;
|
||||
default: throw new IllegalArgumentException("Unknown object type: " + type);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +307,7 @@ public class TypeIO{
|
||||
//make sure player exists
|
||||
if(player == null) return prev;
|
||||
return player;
|
||||
}else if(type == 1){
|
||||
}else if(type == 1){ //formation controller
|
||||
int id = read.i();
|
||||
return prev instanceof FormationAI ? prev : new FormationAI(Groups.unit.getByID(id), null);
|
||||
}else{
|
||||
@@ -380,14 +404,22 @@ public class TypeIO{
|
||||
return AdminAction.values()[read.b()];
|
||||
}
|
||||
|
||||
public static void writeUnitDef(Writes write, UnitType effect){
|
||||
public static void writeUnitType(Writes write, UnitType effect){
|
||||
write.s(effect.id);
|
||||
}
|
||||
|
||||
public static UnitType readUnitDef(Reads read){
|
||||
public static UnitType readUnitType(Reads read){
|
||||
return content.getByID(ContentType.unit, read.s());
|
||||
}
|
||||
|
||||
public static void writeEffect(Writes write, Effect effect){
|
||||
write.s(effect.id);
|
||||
}
|
||||
|
||||
public static Effect readEffect(Reads read){
|
||||
return Effect.get(read.us());
|
||||
}
|
||||
|
||||
public static void writeColor(Writes write, Color color){
|
||||
write.i(color.rgba());
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public abstract class LegacySaveVersion extends SaveVersion{
|
||||
readChunk(stream, true, in -> {
|
||||
byte version = in.readByte();
|
||||
//legacy impl of Building#read()
|
||||
tile.build.health(stream.readUnsignedShort());
|
||||
tile.build.health = stream.readUnsignedShort();
|
||||
byte packedrot = stream.readByte();
|
||||
byte team = Pack.leftByte(packedrot) == 8 ? stream.readByte() : Pack.leftByte(packedrot);
|
||||
byte rotation = Pack.rightByte(packedrot);
|
||||
|
||||
46
core/src/mindustry/logic/BinaryOp.java
Normal file
46
core/src/mindustry/logic/BinaryOp.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.math.*;
|
||||
|
||||
public enum BinaryOp{
|
||||
add("+", (a, b) -> a + b),
|
||||
sub("-", (a, b) -> a - b),
|
||||
mul("*", (a, b) -> a * b),
|
||||
div("/", (a, b) -> a / b),
|
||||
mod("%", (a, b) -> a % b),
|
||||
equal("==", (a, b) -> Math.abs(a - b) < 0.000001 ? 1 : 0),
|
||||
notEqual("not", (a, b) -> Math.abs(a - b) < 0.000001 ? 0 : 1),
|
||||
lessThan("<", (a, b) -> a < b ? 1 : 0),
|
||||
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
|
||||
greaterThan(">", (a, b) -> a > b ? 1 : 0),
|
||||
greaterThanEq(">=", (a, b) -> a >= b ? 1 : 0),
|
||||
pow("^", Math::pow),
|
||||
shl(">>", (a, b) -> (int)a >> (int)b),
|
||||
shr("<<", (a, b) -> (int)a << (int)b),
|
||||
or("or", (a, b) -> (int)a | (int)b),
|
||||
and("and", (a, b) -> (int)a & (int)b),
|
||||
xor("xor", (a, b) -> (int)a ^ (int)b),
|
||||
max("max", Math::max),
|
||||
min("min", Math::min),
|
||||
atan2("atan2", (x, y) -> Mathf.atan2((float)x, (float)y) * Mathf.radDeg),
|
||||
dst("dst", (x, y) -> Mathf.dst((float)x, (float)y));
|
||||
|
||||
public static final BinaryOp[] all = values();
|
||||
|
||||
public final OpLambda function;
|
||||
public final String symbol;
|
||||
|
||||
BinaryOp(String symbol, OpLambda function){
|
||||
this.symbol = symbol;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return symbol;
|
||||
}
|
||||
|
||||
interface OpLambda{
|
||||
double get(double a, double b);
|
||||
}
|
||||
}
|
||||
29
core/src/mindustry/logic/ConditionOp.java
Normal file
29
core/src/mindustry/logic/ConditionOp.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public enum ConditionOp{
|
||||
equal("==", (a, b) -> Math.abs(a - b) < 0.000001),
|
||||
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001),
|
||||
lessThan("<", (a, b) -> a < b),
|
||||
lessThanEq("<=", (a, b) -> a <= b),
|
||||
greaterThan(">", (a, b) -> a > b),
|
||||
greaterThanEq(">=", (a, b) -> a >= b);
|
||||
|
||||
public static final ConditionOp[] all = values();
|
||||
|
||||
public final CondOpLambda function;
|
||||
public final String symbol;
|
||||
|
||||
ConditionOp(String symbol, CondOpLambda function){
|
||||
this.symbol = symbol;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return symbol;
|
||||
}
|
||||
|
||||
interface CondOpLambda{
|
||||
boolean get(double a, double b);
|
||||
}
|
||||
}
|
||||
6
core/src/mindustry/logic/Controllable.java
Normal file
6
core/src/mindustry/logic/Controllable.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package mindustry.logic;
|
||||
|
||||
/** An object that can be controlled with logic. */
|
||||
public interface Controllable{
|
||||
void control(LAccess type, double p1, double p2, double p3, double p4);
|
||||
}
|
||||
24
core/src/mindustry/logic/DataType.java
Normal file
24
core/src/mindustry/logic/DataType.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.graphics.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
/** The types of data a node field can be. */
|
||||
public enum DataType{
|
||||
/** A double. Used for integer calculations as well. */
|
||||
number(Pal.place),
|
||||
/** Any type of content, e.g. item. */
|
||||
content(Color.cyan),
|
||||
/** A building of a tile. */
|
||||
building(Pal.items),
|
||||
/** A unit on the map. */
|
||||
unit(Pal.health),
|
||||
/** Java string */
|
||||
string(Color.royal);
|
||||
|
||||
public final Color color;
|
||||
|
||||
DataType(Color color){
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
41
core/src/mindustry/logic/LAccess.java
Normal file
41
core/src/mindustry/logic/LAccess.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
/** Setter/getter enum for logic-controlled objects. */
|
||||
public enum LAccess{
|
||||
totalItems,
|
||||
totalLiquids,
|
||||
totalPower,
|
||||
itemCapacity,
|
||||
liquidCapacity,
|
||||
powerCapacity,
|
||||
powerNetStored,
|
||||
powerNetCapacity,
|
||||
powerNetIn,
|
||||
powerNetOut,
|
||||
health,
|
||||
heat,
|
||||
efficiency,
|
||||
rotation,
|
||||
x,
|
||||
y,
|
||||
shootX,
|
||||
shootY,
|
||||
shooting,
|
||||
team,
|
||||
|
||||
//values with parameters are considered controllable
|
||||
enabled("to"), //"to" is standard for single parameter access
|
||||
shoot("x", "y", "shoot"),;
|
||||
|
||||
public final String[] parameters;
|
||||
|
||||
public static final LAccess[] all = values();
|
||||
public static final LAccess[] senseable = Seq.select(all, t -> t.parameters.length <= 1).toArray(LAccess.class);
|
||||
public static final LAccess[] controls = Seq.select(all, t -> t.parameters.length > 0).toArray(LAccess.class);
|
||||
|
||||
LAccess(String... parameters){
|
||||
this.parameters = parameters;
|
||||
}
|
||||
}
|
||||
195
core/src/mindustry/logic/LAssembler.java
Normal file
195
core/src/mindustry/logic/LAssembler.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.LExecutor.*;
|
||||
import mindustry.logic.LStatements.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.logic.*;
|
||||
|
||||
/** "Compiles" a sequence of statements into instructions. */
|
||||
public class LAssembler{
|
||||
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
|
||||
|
||||
private int lastVar;
|
||||
/** Maps names to variable IDs. */
|
||||
ObjectMap<String, BVar> vars = new ObjectMap<>();
|
||||
/** All instructions to be executed. */
|
||||
LInstruction[] instructions;
|
||||
|
||||
public LAssembler(){
|
||||
putVar("@counter").value = 0;
|
||||
putConst("@time", 0);
|
||||
|
||||
//add default constants
|
||||
putConst("false", 0);
|
||||
putConst("true", 1);
|
||||
putConst("null", null);
|
||||
|
||||
//store base content (TODO hacky?)
|
||||
|
||||
for(Item item : Vars.content.items()){
|
||||
putConst("@" + item.name, item);
|
||||
}
|
||||
|
||||
for(Liquid liquid : Vars.content.liquids()){
|
||||
putConst("@" + liquid.name, liquid);
|
||||
}
|
||||
|
||||
//store sensor constants
|
||||
|
||||
for(LAccess sensor : LAccess.all){
|
||||
putConst("@" + sensor.name(), sensor);
|
||||
}
|
||||
}
|
||||
|
||||
public static LAssembler assemble(String data, int maxInstructions){
|
||||
LAssembler asm = new LAssembler();
|
||||
|
||||
Seq<LStatement> st = read(data, maxInstructions);
|
||||
|
||||
asm.instructions = st.map(l -> l.build(asm)).filter(l -> l != null).toArray(LInstruction.class);
|
||||
return asm;
|
||||
}
|
||||
|
||||
public static String write(Seq<LStatement> statements){
|
||||
StringBuilder out = new StringBuilder();
|
||||
for(LStatement s : statements){
|
||||
s.write(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public static Seq<LStatement> read(String data){
|
||||
return read(data, LogicBlock.maxInstructions);
|
||||
}
|
||||
|
||||
public static Seq<LStatement> read(String data, int max){
|
||||
//empty data check
|
||||
if(data == null || data.isEmpty()) return new Seq<>();
|
||||
|
||||
Seq<LStatement> statements = new Seq<>();
|
||||
String[] lines = data.split("[;\n]+");
|
||||
int index = 0;
|
||||
for(String line : lines){
|
||||
//comments
|
||||
if(line.startsWith("#")) continue;
|
||||
|
||||
if(index++ > max) break;
|
||||
|
||||
try{
|
||||
String[] arr;
|
||||
|
||||
//yes, I am aware that this can be split with regex, but that's slow and even more incomprehensible
|
||||
if(line.contains(" ")){
|
||||
Seq<String> tokens = new Seq<>();
|
||||
boolean inString = false;
|
||||
int lastIdx = 0;
|
||||
|
||||
for(int i = 0; i < line.length() + 1; i++){
|
||||
char c = i == line.length() ? ' ' : line.charAt(i);
|
||||
if(c == '"'){
|
||||
inString = !inString;
|
||||
}else if(c == ' ' && !inString){
|
||||
tokens.add(line.substring(lastIdx, i).replace("\\n", "\n"));
|
||||
lastIdx = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
arr = tokens.toArray(String.class);
|
||||
}else{
|
||||
arr = new String[]{line};
|
||||
}
|
||||
|
||||
LStatement st = LogicIO.read(arr);
|
||||
|
||||
if(st != null){
|
||||
statements.add(st);
|
||||
}else{
|
||||
//attempt parsing using custom parser if a match is found - this is for mods
|
||||
String first = arr[0];
|
||||
if(customParsers.containsKey(first)){
|
||||
statements.add(customParsers.get(first).get(arr));
|
||||
}else{
|
||||
//unparseable statement
|
||||
statements.add(new InvalidStatement());
|
||||
}
|
||||
}
|
||||
}catch(Exception parseFailed){
|
||||
//when parsing fails, add a dummy invalid statement
|
||||
statements.add(new InvalidStatement());
|
||||
}
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
/** @return a variable ID by name.
|
||||
* This may be a constant variable referring to a number or object. */
|
||||
public int var(String symbol){
|
||||
symbol = symbol.trim();
|
||||
|
||||
//string case
|
||||
if(symbol.startsWith("\"") && symbol.endsWith("\"")){
|
||||
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1)).id;
|
||||
}
|
||||
|
||||
try{
|
||||
double value = Double.parseDouble(symbol);
|
||||
//this creates a hidden const variable with the specified value
|
||||
String key = "___" + value;
|
||||
return putConst(key, value).id;
|
||||
}catch(NumberFormatException e){
|
||||
return putVar(symbol).id;
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a constant value by name. */
|
||||
public BVar putConst(String name, Object value){
|
||||
BVar var = putVar(name);
|
||||
var.constant = true;
|
||||
var.value = value;
|
||||
return var;
|
||||
}
|
||||
|
||||
/** Registers a variable name mapping. */
|
||||
public BVar putVar(String name){
|
||||
if(vars.containsKey(name)){
|
||||
return vars.get(name);
|
||||
}else{
|
||||
BVar var = new BVar(lastVar++);
|
||||
vars.put(name, var);
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable BVar getVar(String name){
|
||||
return vars.get(name);
|
||||
}
|
||||
|
||||
/** A variable "builder". */
|
||||
public static class BVar{
|
||||
public int id;
|
||||
public boolean constant;
|
||||
public Object value;
|
||||
|
||||
public BVar(int id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
BVar(){}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "BVar{" +
|
||||
"id=" + id +
|
||||
", constant=" + constant +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
441
core/src/mindustry/logic/LCanvas.java
Normal file
441
core/src/mindustry/logic/LCanvas.java
Normal file
@@ -0,0 +1,441 @@
|
||||
package mindustry.logic;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.blocks.logic.*;
|
||||
|
||||
public class LCanvas extends Table{
|
||||
private static final Color backgroundCol = Pal.darkMetal.cpy().mul(0.1f), gridCol = Pal.darkMetal.cpy().mul(0.5f);
|
||||
private static Seq<Runnable> postDraw = new Seq<>();
|
||||
private Vec2 offset = new Vec2();
|
||||
|
||||
DragLayout statements;
|
||||
StatementElem dragging;
|
||||
ScrollPane pane;
|
||||
float targetWidth;
|
||||
|
||||
public LCanvas(){
|
||||
rebuild();
|
||||
}
|
||||
|
||||
/** @return if statement elements should have rows. */
|
||||
public static boolean useRows(){
|
||||
return Core.graphics.getWidth() < Scl.scl(900f) * 1.2f;
|
||||
}
|
||||
|
||||
public void rebuild(){
|
||||
targetWidth = useRows() ? 400f : 900f;
|
||||
float s = pane != null ? pane.getScrollPercentY() : 0f;
|
||||
String toLoad = statements != null ? save() : null;
|
||||
|
||||
clear();
|
||||
|
||||
statements = new DragLayout();
|
||||
|
||||
pane = pane(t -> {
|
||||
t.center();
|
||||
t.add(statements).pad(2f).center().width(targetWidth);
|
||||
}).grow().get();
|
||||
pane.setClip(false);
|
||||
pane.setFlickScroll(false);
|
||||
|
||||
//load old scroll percent
|
||||
Core.app.post(() -> {
|
||||
pane.setScrollPercentY(s);
|
||||
pane.updateVisualScroll();
|
||||
});
|
||||
|
||||
if(toLoad != null){
|
||||
load(toLoad);
|
||||
}
|
||||
}
|
||||
|
||||
void add(LStatement statement){
|
||||
statements.addChild(new StatementElem(statement));
|
||||
}
|
||||
|
||||
String save(){
|
||||
Seq<LStatement> st = statements.getChildren().<StatementElem>as().map(s -> s.st);
|
||||
st.each(LStatement::saveUI);
|
||||
|
||||
return LAssembler.write(st);
|
||||
}
|
||||
|
||||
void load(String asm){
|
||||
Seq<LStatement> statements = LAssembler.read(asm);
|
||||
statements.truncate(LogicBlock.maxInstructions);
|
||||
this.statements.clearChildren();
|
||||
for(LStatement st : statements){
|
||||
add(st);
|
||||
}
|
||||
|
||||
for(LStatement st : statements){
|
||||
st.setupUI();
|
||||
}
|
||||
|
||||
this.statements.layout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
postDraw.clear();
|
||||
super.draw();
|
||||
postDraw.each(Runnable::run);
|
||||
}
|
||||
|
||||
public class DragLayout extends WidgetGroup{
|
||||
float space = Scl.scl(10f), prefWidth, prefHeight;
|
||||
Seq<Element> seq = new Seq<>();
|
||||
int insertPosition = 0;
|
||||
|
||||
{
|
||||
setTransform(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layout(){
|
||||
float cy = 0;
|
||||
seq.clear();
|
||||
|
||||
float totalHeight = getChildren().sumf(e -> e.getHeight() + space);
|
||||
|
||||
height = prefHeight = totalHeight;
|
||||
width = prefWidth = Scl.scl(targetWidth);
|
||||
|
||||
//layout everything normally
|
||||
for(int i = 0; i < getChildren().size; i++){
|
||||
Element e = getChildren().get(i);
|
||||
|
||||
//ignore the dragged element
|
||||
if(dragging == e) continue;
|
||||
|
||||
e.setSize(width, e.getPrefHeight());
|
||||
e.setPosition(0, height - cy, Align.topLeft);
|
||||
|
||||
cy += e.getPrefHeight() + space;
|
||||
seq.add(e);
|
||||
}
|
||||
|
||||
//insert the dragged element if necessary
|
||||
if(dragging != null){
|
||||
//find real position of dragged element top
|
||||
float realY = dragging.getY(Align.top) + dragging.translation.y;
|
||||
|
||||
insertPosition = 0;
|
||||
|
||||
for(int i = 0; i < seq.size; i++){
|
||||
Element cur = seq.get(i);
|
||||
//find fit point
|
||||
if(realY < cur.y && (i == seq.size - 1 || realY > seq.get(i + 1).y)){
|
||||
insertPosition = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float shiftAmount = dragging.getHeight() + space;
|
||||
|
||||
//shift elements below insertion point down
|
||||
for(int i = insertPosition; i < seq.size; i++){
|
||||
seq.get(i).y -= shiftAmount;
|
||||
}
|
||||
}
|
||||
|
||||
invalidateHierarchy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPrefWidth(){
|
||||
return prefWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getPrefHeight(){
|
||||
return prefHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
Draw.alpha(parentAlpha);
|
||||
|
||||
//draw selection box indicating placement position
|
||||
if(dragging != null && insertPosition <= seq.size){
|
||||
float shiftAmount = dragging.getHeight();
|
||||
float lastX = x;
|
||||
float lastY = insertPosition == 0 ? height + y : seq.get(insertPosition - 1).y + y - space;
|
||||
|
||||
Tex.pane.draw(lastX, lastY - shiftAmount, width, dragging.getHeight());
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
void finishLayout(){
|
||||
if(dragging != null){
|
||||
//reset translation first
|
||||
for(Element child : getChildren()){
|
||||
child.setTranslation(0, 0);
|
||||
}
|
||||
clearChildren();
|
||||
|
||||
//reorder things
|
||||
for(int i = 0; i <= insertPosition - 1 && i < seq.size; i++){
|
||||
addChild(seq.get(i));
|
||||
}
|
||||
|
||||
addChild(dragging);
|
||||
|
||||
for(int i = insertPosition; i < seq.size; i++){
|
||||
addChild(seq.get(i));
|
||||
}
|
||||
|
||||
dragging = null;
|
||||
}
|
||||
|
||||
layout();
|
||||
}
|
||||
}
|
||||
|
||||
public class StatementElem extends Table{
|
||||
LStatement st;
|
||||
|
||||
public StatementElem(LStatement st){
|
||||
this.st = st;
|
||||
st.elem = this;
|
||||
|
||||
background(Tex.whitePane);
|
||||
setColor(st.category().color);
|
||||
margin(0f);
|
||||
touchable = Touchable.enabled;
|
||||
|
||||
table(Tex.whiteui, t -> {
|
||||
t.color.set(color);
|
||||
t.addListener(new HandCursorListener());
|
||||
|
||||
t.margin(6f);
|
||||
t.touchable = Touchable.enabled;
|
||||
|
||||
t.add(st.name()).style(Styles.outlineLabel).color(color).padRight(8);
|
||||
t.add().growX();
|
||||
|
||||
t.button(Icon.copy, Styles.logici, () -> {
|
||||
}).padRight(6).get().tapped(() -> copy());
|
||||
|
||||
t.button(Icon.cancel, Styles.logici, () -> {
|
||||
remove();
|
||||
dragging = null;
|
||||
statements.layout();
|
||||
});
|
||||
|
||||
t.addListener(new InputListener(){
|
||||
float lastx, lasty;
|
||||
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
|
||||
if(button == KeyCode.mouseMiddle){
|
||||
copy();
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
|
||||
lastx = v.x;
|
||||
lasty = v.y;
|
||||
dragging = StatementElem.this;
|
||||
toFront();
|
||||
statements.layout();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchDragged(InputEvent event, float x, float y, int pointer){
|
||||
Vec2 v = localToStageCoordinates(Tmp.v1.set(x, y));
|
||||
|
||||
translation.add(v.x - lastx, v.y - lasty);
|
||||
lastx = v.x;
|
||||
lasty = v.y;
|
||||
|
||||
statements.layout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode button){
|
||||
statements.finishLayout();
|
||||
}
|
||||
});
|
||||
}).growX();
|
||||
|
||||
row();
|
||||
|
||||
table(t -> {
|
||||
t.left();
|
||||
t.marginLeft(4);
|
||||
t.setColor(color);
|
||||
st.build(t);
|
||||
}).pad(4).padTop(2).left().grow();
|
||||
|
||||
marginBottom(7);
|
||||
}
|
||||
|
||||
void copy(){
|
||||
LStatement copy = st.copy();
|
||||
if(copy != null){
|
||||
StatementElem s = new StatementElem(copy);
|
||||
|
||||
statements.addChildAfter(StatementElem.this,s);
|
||||
statements.layout();
|
||||
copy.elem = s;
|
||||
copy.setupUI();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
float pad = 5f;
|
||||
Fill.dropShadow(x + width/2f, y + height/2f, width + pad, height + pad, 10f, 0.9f * parentAlpha);
|
||||
|
||||
Draw.color(0, 0, 0, 0.3f * parentAlpha);
|
||||
Fill.crect(x, y, width, height);
|
||||
Draw.reset();
|
||||
|
||||
super.draw();
|
||||
}
|
||||
}
|
||||
|
||||
public static class JumpButton extends ImageButton{
|
||||
@NonNull Prov<StatementElem> to;
|
||||
boolean selecting;
|
||||
float mx, my;
|
||||
|
||||
public JumpButton(Color color, @NonNull Prov<StatementElem> getter, Cons<StatementElem> setter){
|
||||
super(Tex.logicNode, Styles.colori);
|
||||
|
||||
to = getter;
|
||||
|
||||
getStyle().imageUpColor = color;
|
||||
|
||||
addListener(new InputListener(){
|
||||
@Override
|
||||
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode code){
|
||||
selecting = true;
|
||||
setter.get(null);
|
||||
mx = x;
|
||||
my = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchDragged(InputEvent event, float x, float y, int pointer){
|
||||
mx = x;
|
||||
my = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touchUp(InputEvent event, float x, float y, int pointer, KeyCode code){
|
||||
localToStageCoordinates(Tmp.v1.set(x, y));
|
||||
StatementElem elem = hovered();
|
||||
|
||||
if(elem != null && !isDescendantOf(elem)){
|
||||
setter.get(elem);
|
||||
}else{
|
||||
setter.get(null);
|
||||
}
|
||||
selecting = false;
|
||||
}
|
||||
});
|
||||
|
||||
update(() -> {
|
||||
if(to.get() != null && to.get().parent == null){
|
||||
setter.get(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
super.draw();
|
||||
|
||||
postDraw.add(() -> {
|
||||
Element hover = to.get() == null && selecting ? hovered() : to.get();
|
||||
float tx = 0, ty = 0;
|
||||
boolean draw = false;
|
||||
//capture coordinates for use in lambda
|
||||
float rx = x + translation.x, ry = y + translation.y;
|
||||
|
||||
Element p = parent;
|
||||
while(p != null){
|
||||
rx += p.x + p.translation.x;
|
||||
ry += p.y + p.translation.y;
|
||||
p = p.parent;
|
||||
}
|
||||
|
||||
if(hover != null){
|
||||
tx = hover.getX(Align.right) + hover.translation.x;
|
||||
ty = hover.getY(Align.right) + hover.translation.y;
|
||||
|
||||
Element op = hover.parent;
|
||||
while(op != null){
|
||||
tx += op.x + op.translation.x;
|
||||
ty += op.y + op.translation.y;
|
||||
op = op.parent;
|
||||
}
|
||||
|
||||
draw = true;
|
||||
}else if(selecting){
|
||||
tx = rx + mx;
|
||||
ty = ry + my;
|
||||
draw = true;
|
||||
}
|
||||
|
||||
if(draw){
|
||||
drawCurve(rx + width/2f, ry + height/2f, tx, ty, color);
|
||||
|
||||
float s = width;
|
||||
Tex.logicNode.draw(tx + s*0.75f, ty - s/2f, -s, s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
StatementElem hovered(){
|
||||
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
|
||||
if(e != null){
|
||||
while(e != null && !(e instanceof StatementElem)){
|
||||
e = e.parent;
|
||||
}
|
||||
}
|
||||
if(e == null || isDescendantOf(e)) return null;
|
||||
return (StatementElem)e;
|
||||
}
|
||||
|
||||
void drawCurve(float x, float y, float x2, float y2, Color color){
|
||||
Lines.stroke(4f, color);
|
||||
Draw.alpha(parentAlpha);
|
||||
|
||||
float dist = 100f;
|
||||
|
||||
Lines.curve(
|
||||
x, y,
|
||||
x + dist, y,
|
||||
x2 + dist, y2,
|
||||
x2, y2,
|
||||
Math.max(20, (int)(Mathf.dst(x, y, x2, y2) / 5))
|
||||
);
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user