Merge branch 'master' into new-logic-parser

# Conflicts:
#	gradle.properties
This commit is contained in:
Anuken
2021-03-08 18:03:00 -05:00
305 changed files with 8539 additions and 5490 deletions

View File

@@ -34,6 +34,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void setup(){
String dataDir = OS.env("MINDUSTRY_DATA_DIR");
if(dataDir != null){
Core.settings.setDataDirectory(files.absolute(dataDir));
}
checkLaunch();
loadLogger();
loader = new LoadRenderer();
@@ -145,7 +151,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
finished = true;
Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight());
app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight())))));
app.post(() -> app.post(() -> app.post(() -> app.post(() -> {
super.resize(graphics.getWidth(), graphics.getHeight());
//mark initialization as complete
finishLaunch();
}))));
}
}else{
asyncCore.begin();
@@ -168,6 +179,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
lastTime = Time.nanos();
}
@Override
public void exit(){
//on graceful exit, finish the launch normally.
Vars.finishLaunch();
}
@Override
public void init(){
setup();
@@ -182,6 +199,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
@Override
public void pause(){
//when the user tabs out on mobile, the exit() event doesn't fire reliably - in that case, just assume they're about to kill the app
//this isn't 100% reliable but it should work for most cases
if(mobile){
Vars.finishLaunch();
}
if(finished){
super.pause();
}

View File

@@ -32,12 +32,16 @@ import java.util.*;
import static arc.Core.*;
public class Vars implements Loadable{
/** Whether the game failed to launch last time. */
public static boolean failedToLaunch = false;
/** Whether to load locales.*/
public static boolean loadLocales = true;
/** Whether the logger is loaded. */
public static boolean loadedLogger = false, loadedFileLogger = false;
/** Whether to enable various experimental features (e.g. cliffs) */
public static boolean experimental = false;
/** Name of current Steam player. */
public static String steamPlayerName = "";
/** Maximum extra padding around deployment schematics. */
public static final int maxLoadoutSchematicPad = 5;
/** Maximum schematic size.*/
@@ -50,20 +54,18 @@ public class Vars implements Loadable{
public static final Charset charset = Charset.forName("UTF-8");
/** main application name, capitalized */
public static final String appName = "Mindustry";
/** URL for itch.io donations. */
public static final String donationURL = "https://anuke.itch.io/mindustry/purchase";
/** Github API URL. */
public static final String ghApi = "https://api.github.com";
/** URL for discord invite. */
public static final String discordURL = "https://discord.gg/mindustry";
/** URL for sending crash reports to */
/** URL for sending crash reports to. Currently offline. */
public static final String crashReportURL = "http://192.99.169.18/report";
/** URL the links to the wiki's modding guide.*/
public static final String modGuideURL = "https://mindustrygame.github.io/wiki/modding/1-modding/";
/** URL to the JSON file containing all the global, public servers. Not queried in BE. */
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers.json";
/** URL to the JSON file containing all the BE servers. Only queried in BE. */
public static final String serverJsonBeURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_be.json";
/** URL to the JSON file containing all the BE servers. Only queried in the V6 alpha (will be removed once it's out). */
public static final String serverJsonV6URL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_v6.json";
/** URL to the JSON file containing all the stable servers. */
public static final String serverJsonURL = "https://raw.githubusercontent.com/Anuken/Mindustry/master/servers_v6.json";
/** URL of the github issue report template.*/
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?labels=bug&template=bug_report.md";
/** list of built-in servers.*/
@@ -92,8 +94,8 @@ public class Vars implements Loadable{
public static final float turnDuration = 2 * Time.toMinutes;
/** chance of an invasion per turn, 1 = 100% */
public static final float baseInvasionChance = 1f / 100f;
/** how many turns have to pass before invasions start */
public static final int invasionGracePeriod = 20;
/** how many minutes have to pass before invasions in a *captured* sector start */
public static final float invasionGracePeriod = 20;
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
public static final float minArmorDamage = 0.1f;
/** launch animation duration */
@@ -172,6 +174,8 @@ public class Vars implements Loadable{
public static Fi schematicDirectory;
/** data subdirectory used for bleeding edge build versions */
public static Fi bebuildDirectory;
/** file used to store launch ID */
public static Fi launchIDFile;
/** empty map, indicates no current map */
public static Map emptyMap;
/** map file extension */
@@ -284,6 +288,27 @@ public class Vars implements Loadable{
maps.load();
}
/** Checks if a launch failure occurred.
* If this is the case, failedToLaunch is set to true. */
public static void checkLaunch(){
settings.setAppName(appName);
launchIDFile = settings.getDataDirectory().child("launchid.dat");
if(launchIDFile.exists()){
failedToLaunch = true;
}else{
failedToLaunch = false;
launchIDFile.writeString("go away");
}
}
/** Cleans up after a successful launch. */
public static void finishLaunch(){
if(launchIDFile != null){
launchIDFile.delete();
}
}
public static void loadLogger(){
if(loadedLogger) return;
@@ -345,7 +370,7 @@ public class Vars implements Loadable{
}
public static void loadSettings(){
settings.setJson(JsonIO.json());
settings.setJson(JsonIO.json);
settings.setAppName(appName);
if(steam || (Version.modifier != null && Version.modifier.contains("steam"))){

View File

@@ -209,6 +209,16 @@ public class BaseAI{
}
Tile wtile = world.tile(realX, realY);
if(tile.block instanceof PayloadConveyor || tile.block instanceof PayloadAcceptor){
//near a building
for(Point2 point : Edges.getEdges(tile.block.size)){
var t = world.build(tile.x + point.x, tile.y + point.y);
if(t != null){
return false;
}
}
}
//may intersect AI path
tmpTiles.clear();
if(tile.block.solid && wtile != null && wtile.getLinkedTilesAs(tile.block, tmpTiles).contains(t -> path.contains(t.pos()))){
@@ -265,6 +275,7 @@ public class BaseAI{
if(spawn == null) return;
for(int wx = lastX; wx <= lastX + lastW; wx++){
outer:
for(int wy = lastY; wy <= lastY + lastH; wy++){
Tile tile = world.tile(wx, wy);
@@ -279,12 +290,11 @@ public class BaseAI{
Tile o = world.tile(tile.x + p.x, tile.y + p.y);
if(o != null && (o.block() instanceof PayloadAcceptor || o.block() instanceof PayloadConveyor)){
break;
continue outer;
}
if(o != null && o.team() == data.team && !(o.block() instanceof Wall)){
any = true;
break;
}
}

View File

@@ -269,7 +269,6 @@ public class BlockIndexer{
public Building findTile(Team team, float x, float y, float range, Boolf<Building> pred, boolean usePriority){
Building closest = null;
float dst = 0;
float range2 = range * range;
for(int rx = Math.max((int)((x - range) / tilesize / quadrantSize), 0); rx <= (int)((x + range) / tilesize / quadrantSize) && rx < quadWidth(); rx++){
for(int ry = Math.max((int)((y - range) / tilesize / quadrantSize), 0); ry <= (int)((y + range) / tilesize / quadrantSize) && ry < quadHeight(); ry++){
@@ -282,13 +281,13 @@ public class BlockIndexer{
if(e == null || e.team != team || !pred.get(e) || !e.block.targetable || e.team == Team.derelict) continue;
float ndst = e.dst2(x, y);
if(ndst < range2 && (closest == null ||
float bdst = e.dst(x, y) - e.hitSize() / 2f;
if(bdst < range && (closest == null ||
//this one is closer, and it is at least of equal priority
(ndst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
(bdst < dst && (!usePriority || closest.block.priority.ordinal() <= e.block.priority.ordinal())) ||
//priority is used, and new block has higher priority regardless of range
(usePriority && closest.block.priority.ordinal() < e.block.priority.ordinal()))){
dst = ndst;
dst = bdst;
closest = e;
}
}

View File

@@ -23,7 +23,6 @@ public class Pathfinder implements Runnable{
private static final int updateFPS = 60;
private static final int updateInterval = 1000 / updateFPS;
private static final int impassable = -1;
private static final int fieldTimeout = 1000 * 60 * 2;
public static final int
fieldCore = 0,
@@ -192,31 +191,6 @@ public class Pathfinder implements Runnable{
//total update time no longer than maxUpdate
for(Flowfield data : threadList){
updateFrontier(data, maxUpdate / threadList.size);
//TODO implement timeouts... or don't
/*
//remove flowfields that have 'timed out' so they can be garbage collected and no longer waste space
if(data.refreshRate > 0 && Time.timeSinceMillis(data.lastUpdateTime) > fieldTimeout){
//make sure it doesn't get removed twice
data.lastUpdateTime = Time.millis();
Team team = data.team;
Core.app.post(() -> {
//remove its used state
if(fieldMap[team.id] != null){
fieldMap[team.id].remove(data.target);
fieldMapUsed[team.id].remove(data.target);
}
//remove from main thread list
mainList.remove(data);
});
queue.post(() -> {
//remove from this thread list with a delay
threadList.remove(data);
});
}*/
}
}
@@ -292,7 +266,7 @@ public class Pathfinder implements Runnable{
}
}
if(current == null || tl == impassable) return tile;
if(current == null || tl == impassable || (path.cost == costTypes.items[costGround] && current.dangerous() && !tile.dangerous())) return tile;
return current;
}

View File

@@ -73,7 +73,7 @@ public class WaveSpawner{
for(int i = 0; i < spawned; i++){
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
unit.set(spawnX + Mathf.range(spread), spawnY + Mathf.range(spread));
unit.add();
spawnEffect(unit);
}
});
}else{
@@ -92,7 +92,7 @@ public class WaveSpawner{
}
}
Time.runTask(121f, () -> spawning = false);
Time.run(121f, () -> spawning = false);
}
public void doShockwave(float x, float y){
@@ -148,8 +148,7 @@ public class WaveSpawner{
private void eachFlyerSpawn(Floatc2 cons){
for(Tile tile : spawns){
float angle = Angles.angle(world.width() / 2, world.height() / 2, tile.x, tile.y);
float angle = Angles.angle(world.width() / 2f, world.height() / 2f, tile.x, tile.y);
float trns = Math.max(world.width(), world.height()) * Mathf.sqrt2 * tilesize;
float spawnX = Mathf.clamp(world.width() * tilesize / 2f + Angles.trnsx(angle, trns), -margin, world.width() * tilesize + margin);
float spawnY = Mathf.clamp(world.height() * tilesize / 2f + Angles.trnsy(angle, trns), -margin, world.height() * tilesize + margin);
@@ -168,6 +167,7 @@ public class WaveSpawner{
}
private void reset(){
spawning = false;
spawns.clear();
for(Tile tile : world.tiles){
@@ -178,8 +178,11 @@ public class WaveSpawner{
}
private void spawnEffect(Unit unit){
Call.spawnEffect(unit.x, unit.y, unit.type);
Time.run(30f, unit::add);
unit.rotation = unit.angleTo(world.width()/2f * tilesize, world.height()/2f * tilesize);
unit.apply(StatusEffects.unmoving, 30f);
unit.add();
Call.spawnEffect(unit.x, unit.y, unit.rotation, unit.type);
}
private interface SpawnConsumer{
@@ -187,8 +190,8 @@ public class WaveSpawner{
}
@Remote(called = Loc.server, unreliable = true)
public static void spawnEffect(float x, float y, UnitType type){
Fx.unitSpawn.at(x, y, 0f, type);
public static void spawnEffect(float x, float y, float rotation, UnitType u){
Fx.unitSpawn.at(x, y, rotation, u);
Time.run(30f, () -> Fx.spawn.at(x, y));
}

View File

@@ -13,9 +13,12 @@ import mindustry.world.blocks.ConstructBlock.*;
import static mindustry.Vars.*;
public class BuilderAI extends AIController{
float buildRadius = 1500;
public static float buildRadius = 1500, retreatDst = 110f, fleeRange = 370f, retreatDelay = Time.toSeconds * 2f;
boolean found = false;
@Nullable Unit following;
@Nullable Teamc enemy;
float retreatTimer;
@Override
public void updateMovement(){
@@ -27,6 +30,7 @@ public class BuilderAI extends AIController{
unit.updateBuilding = true;
if(following != null){
retreatTimer = 0f;
//try to follow and mimic someone
//validate follower
@@ -39,9 +43,25 @@ public class BuilderAI extends AIController{
//set to follower's first build plan, whatever that is
unit.plans.clear();
unit.plans.addFirst(following.buildPlan());
}else if(unit.buildPlan() == null){
//not following anyone or building
if(timer.get(timerTarget4, 40)){
enemy = target(unit.x, unit.y, fleeRange, true, true);
}
//fly away from enemy when not doing anything, but only after a delay
if((retreatTimer += Time.delta) >= retreatDelay){
if(enemy != null){
var core = unit.closestCore();
if(core != null && !unit.within(core, retreatDst)){
moveTo(core, retreatDst);
}
}
}
}
if(unit.buildPlan() != null){
retreatTimer = 0f;
//approach request if building
BuildPlan req = unit.buildPlan();

View File

@@ -0,0 +1,40 @@
package mindustry.ai.types;
import arc.math.*;
import mindustry.entities.*;
import mindustry.entities.comp.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.meta.*;
public class DefenderAI extends AIController{
@Override
public void updateMovement(){
if(target != null){
moveTo(target, (target instanceof Sized s ? s.hitSize()/2f * 1.1f : 0f) + unit.hitSize/2f + 15f, 50f);
unit.lookAt(target);
}
}
@Override
protected void updateTargeting(){
if(retarget()) target = findTarget(unit.x, unit.y, unit.range(), true, true);
}
@Override
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){
//find unit to follow if not in rally mode
if(command() != UnitCommand.rally){
//Sort by max health and closer target.
var result = Units.closest(unit.team, x, y, Math.max(range, 400f), u -> !u.dead() && u.type != unit.type, (u, tx, ty) -> -u.maxHealth + Mathf.dst2(u.x, u.y, tx, ty) / 800f);
if(result != null) return result;
}
//find rally point
var block = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
if(block != null) return block;
//return core if found
return unit.closestCore();
}
}

View File

@@ -13,7 +13,7 @@ public class FlyingAI extends AIController{
public void updateMovement(){
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
if(!unit.type.circleTarget){
moveTo(target, unit.range() * 0.8f);
moveTo(target, unit.type.range * 0.8f);
unit.lookAt(target);
}else{
attack(120f);
@@ -49,10 +49,10 @@ public class FlyingAI extends AIController{
float ang = unit.angleTo(target);
float diff = Angles.angleDist(ang, unit.rotation());
if(diff > 100f && vec.len() < circleLength){
if(diff > 70f && vec.len() < circleLength){
vec.setAngle(unit.vel().angle());
}else{
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
vec.setAngle(Angles.moveToward(unit.vel().angle(), vec.angle(), 6f));
}
vec.setLength(unit.speed());

View File

@@ -46,9 +46,9 @@ public class FormationAI extends AIController implements FormationMember{
unit.lookAt(unit.vel.angle());
}
Vec2 realtarget = vec.set(target).add(leader.vel.x, leader.vel.y);
Vec2 realtarget = vec.set(target).add(leader.vel);
float speed = unit.realSpeed() * unit.floorSpeedMultiplier() * Time.delta;
float speed = unit.realSpeed() * Time.delta;
unit.approach(Mathf.arrive(unit.x, unit.y, realtarget.x, realtarget.y, unit.vel, speed, 0f, speed, 1f).scl(1f / Time.delta));
if(unit.canMine() && leader.canMine()){

View File

@@ -19,7 +19,7 @@ public class LogicAI extends AIController{
/** Time after which the unit resets its controlled and reverts to a normal unit. */
public static final float logicControlTimeout = 10f * 60f;
public LUnitControl control = LUnitControl.stop;
public LUnitControl control = LUnitControl.idle;
public float moveX, moveY, moveRad;
public float itemTimer, payTimer, controlTimer = logicControlTimeout, targetTimer;
@Nullable
@@ -128,9 +128,17 @@ public class LogicAI extends AIController{
vec.setZero();
}
//do not move when infinite vectors are used.
if(vec.isNaN() || vec.isInfinite()) return;
unit.approach(vec);
}
@Override
protected boolean checkTarget(Teamc target, float x, float y, float range){
return false;
}
//always retarget
@Override
protected boolean retarget(){

View File

@@ -1,11 +1,16 @@
package mindustry.ai.types;
import arc.util.*;
import mindustry.entities.*;
import mindustry.entities.units.*;
import mindustry.gen.*;
import mindustry.world.blocks.ConstructBlock.*;
public class RepairAI extends AIController{
public static float retreatDst = 160f, fleeRange = 310f, retreatDelay = Time.toSeconds * 3f;
@Nullable Teamc avoid;
float retreatTimer;
@Override
protected void updateMovement(){
@@ -29,6 +34,25 @@ public class RepairAI extends AIController{
unit.lookAt(target);
}
//not repairing
if(!(target instanceof Building)){
if(timer.get(timerTarget4, 40)){
avoid = target(unit.x, unit.y, fleeRange, true, true);
}
if((retreatTimer += Time.delta) >= retreatDelay){
//fly away from enemy when not doing anything
if(avoid != null){
var core = unit.closestCore();
if(core != null && !unit.within(core, retreatDst)){
moveTo(core, retreatDst);
}
}
}
}else{
retreatTimer = 0f;
}
}
@Override

View File

@@ -1,5 +1,6 @@
package mindustry.ai.types;
import arc.math.geom.*;
import mindustry.*;
import mindustry.ai.*;
import mindustry.entities.*;
@@ -47,14 +48,17 @@ public class SuicideAI extends GroundAI{
//raycast for target
boolean blocked = Vars.world.raycast(unit.tileX(), unit.tileY(), target.tileX(), target.tileY(), (x, y) -> {
Tile tile = Vars.world.tile(x, y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{
return tile == null || tile.solid();
for(Point2 p : Geometry.d4c){
Tile tile = Vars.world.tile(x + p.x, y + p.y);
if(tile != null && tile.build == target) return false;
if(tile != null && tile.build != null && tile.build.team != unit.team()){
blockedByBlock = true;
return true;
}else{
return tile == null || tile.solid();
}
}
return false;
});
//shoot when there's an enemy block in the way

View File

@@ -37,8 +37,9 @@ public class PhysicsProcess implements AsyncProcess{
//find Unit without bodies and assign them
for(Unit entity : group){
if(entity.type == null) continue;
if(entity.physref() == null){
if(entity.physref == null){
PhysicsBody body = new PhysicsBody();
body.x = entity.x();
body.y = entity.y();
@@ -48,13 +49,13 @@ public class PhysicsProcess implements AsyncProcess{
PhysicRef ref = new PhysicRef(entity, body);
refs.add(ref);
entity.physref(ref);
entity.physref = ref;
physics.add(body);
}
//save last position
PhysicRef ref = entity.physref();
PhysicRef ref = entity.physref;
ref.body.layer =
entity.type.allowLegStep ? layerLegs :

View File

@@ -130,7 +130,7 @@ public class SoundControl{
Core.audio.soundBus.play();
setupFilters();
}else{
Core.audio.soundBus.stop();
Core.audio.soundBus.replay();
}
}

View File

@@ -1,8 +1,6 @@
package mindustry.content;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.struct.*;
import mindustry.*;
import mindustry.ctype.*;
@@ -97,36 +95,9 @@ public class Blocks implements ContentList{
public void load(){
//region environment
air = new Floor("air"){
{
alwaysReplace = true;
hasShadow = false;
useColor = false;
wall = this;
}
air = new AirBlock("air");
@Override public void drawBase(Tile tile){}
@Override public void load(){}
@Override public void init(){}
@Override public boolean isHidden(){ return true; }
@Override
public TextureRegion[] variantRegions(){
if(variantRegions == null){
variantRegions = new TextureRegion[]{Core.atlas.find("clear")};
}
return variantRegions;
}
};
spawn = new OverlayFloor("spawn"){
{
variants = 0;
needsSurface = false;
}
@Override
public void drawBase(Tile tile){}
};
spawn = new SpawnBlock("spawn");
cliff = new Cliff("cliff"){{
inEditor = false;
@@ -676,10 +647,10 @@ public class Blocks implements ContentList{
separator = new Separator("separator"){{
requirements(Category.crafting, with(Items.copper, 30, Items.titanium, 25));
results = with(
Items.copper, 5,
Items.lead, 3,
Items.graphite, 2,
Items.titanium, 2
Items.copper, 5,
Items.lead, 3,
Items.graphite, 2,
Items.titanium, 2
);
hasPower = true;
craftTime = 35f;
@@ -692,10 +663,10 @@ public class Blocks implements ContentList{
disassembler = new Separator("disassembler"){{
requirements(Category.crafting, with(Items.graphite, 140, Items.titanium, 100, Items.silicon, 150, Items.surgeAlloy, 70));
results = with(
Items.sand, 4,
Items.graphite, 2,
Items.titanium, 2,
Items.thorium, 1
Items.sand, 4,
Items.graphite, 2,
Items.titanium, 2,
Items.thorium, 1
);
hasPower = true;
craftTime = 15f;
@@ -788,6 +759,7 @@ public class Blocks implements ContentList{
health = 130 * wallHealthMultiplier;
insulated = true;
absorbLasers = true;
schematicPriority = 10;
}};
plastaniumWallLarge = new Wall("plastanium-wall-large"){{
@@ -796,6 +768,7 @@ public class Blocks implements ContentList{
size = 2;
insulated = true;
absorbLasers = true;
schematicPriority = 10;
}};
thoriumWall = new Wall("thorium-wall"){{
@@ -1312,7 +1285,7 @@ public class Blocks implements ContentList{
}};
waterExtractor = new SolidPump("water-extractor"){{
requirements(Category.production, with(Items.copper, 25, Items.graphite, 25, Items.lead, 20));
requirements(Category.production, with(Items.metaglass, 30, Items.graphite, 30, Items.lead, 30));
result = Liquids.water;
pumpAmount = 0.11f;
size = 2;
@@ -1397,14 +1370,12 @@ public class Blocks implements ContentList{
requirements(Category.effect, with(Items.titanium, 250, Items.thorium, 125));
size = 3;
itemCapacity = 1000;
flags = EnumSet.of(BlockFlag.storage);
}};
container = new StorageBlock("container"){{
requirements(Category.effect, with(Items.titanium, 100));
size = 2;
itemCapacity = 300;
flags = EnumSet.of(BlockFlag.storage);
}};
unloader = new Unloader("unloader"){{
@@ -1419,10 +1390,10 @@ public class Blocks implements ContentList{
duo = new ItemTurret("duo"){{
requirements(Category.turret, with(Items.copper, 35), true);
ammo(
Items.copper, Bullets.standardCopper,
Items.graphite, Bullets.standardDense,
Items.pyratite, Bullets.standardIncendiary,
Items.silicon, Bullets.standardHoming
Items.copper, Bullets.standardCopper,
Items.graphite, Bullets.standardDense,
Items.pyratite, Bullets.standardIncendiary,
Items.silicon, Bullets.standardHoming
);
spread = 4f;
@@ -1441,9 +1412,9 @@ public class Blocks implements ContentList{
scatter = new ItemTurret("scatter"){{
requirements(Category.turret, with(Items.copper, 85, Items.lead, 45));
ammo(
Items.scrap, Bullets.flakScrap,
Items.lead, Bullets.flakLead,
Items.metaglass, Bullets.flakGlass
Items.scrap, Bullets.flakScrap,
Items.lead, Bullets.flakLead,
Items.metaglass, Bullets.flakGlass
);
reloadTime = 18f;
range = 160f;
@@ -1464,8 +1435,8 @@ public class Blocks implements ContentList{
scorch = new ItemTurret("scorch"){{
requirements(Category.turret, with(Items.copper, 25, Items.graphite, 22));
ammo(
Items.coal, Bullets.basicFlame,
Items.pyratite, Bullets.pyraFlame
Items.coal, Bullets.basicFlame,
Items.pyratite, Bullets.pyraFlame
);
recoilAmount = 0f;
reloadTime = 6f;
@@ -1481,9 +1452,9 @@ public class Blocks implements ContentList{
hail = new ItemTurret("hail"){{
requirements(Category.turret, with(Items.copper, 40, Items.graphite, 17));
ammo(
Items.graphite, Bullets.artilleryDense,
Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary
Items.graphite, Bullets.artilleryDense,
Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary
);
targetAir = false;
reloadTime = 60f;
@@ -1498,10 +1469,10 @@ public class Blocks implements ContentList{
wave = new LiquidTurret("wave"){{
requirements(Category.turret, with(Items.metaglass, 45, Items.lead, 75));
ammo(
Liquids.water, Bullets.waterShot,
Liquids.slag, Bullets.slagShot,
Liquids.cryofluid, Bullets.cryoShot,
Liquids.oil, Bullets.oilShot
Liquids.water, Bullets.waterShot,
Liquids.slag, Bullets.slagShot,
Liquids.cryofluid, Bullets.cryoShot,
Liquids.oil, Bullets.oilShot
);
size = 2;
recoilAmount = 0f;
@@ -1537,7 +1508,7 @@ public class Blocks implements ContentList{
shootSound = Sounds.laser;
shootType = new LaserBulletType(140){{
colors = new Color[]{Pal.lancerLaser.cpy().mul(1f, 1f, 1f, 0.4f), Pal.lancerLaser, Color.white};
colors = new Color[]{Pal.lancerLaser.cpy().a(0.4f), Pal.lancerLaser, Color.white};
hitEffect = Fx.hitLancer;
despawnEffect = Fx.none;
hitSize = 4;
@@ -1587,15 +1558,15 @@ public class Blocks implements ContentList{
swarmer = new ItemTurret("swarmer"){{
requirements(Category.turret, with(Items.graphite, 35, Items.titanium, 35, Items.plastanium, 45, Items.silicon, 30));
ammo(
Items.blastCompound, Bullets.missileExplosive,
Items.pyratite, Bullets.missileIncendiary,
Items.surgeAlloy, Bullets.missileSurge
Items.blastCompound, Bullets.missileExplosive,
Items.pyratite, Bullets.missileIncendiary,
Items.surgeAlloy, Bullets.missileSurge
);
reloadTime = 30f;
shots = 4;
burstSpacing = 5;
inaccuracy = 10f;
range = 190f;
range = 200f;
xRand = 6f;
size = 2;
health = 300 * size * size;
@@ -1605,11 +1576,11 @@ public class Blocks implements ContentList{
salvo = new ItemTurret("salvo"){{
requirements(Category.turret, with(Items.copper, 100, Items.graphite, 90, Items.titanium, 60));
ammo(
Items.copper, Bullets.standardCopper,
Items.graphite, Bullets.standardDense,
Items.pyratite, Bullets.standardIncendiary,
Items.silicon, Bullets.standardHoming,
Items.thorium, Bullets.standardThorium
Items.copper, Bullets.standardCopper,
Items.graphite, Bullets.standardDense,
Items.pyratite, Bullets.standardIncendiary,
Items.silicon, Bullets.standardHoming,
Items.thorium, Bullets.standardThorium
);
size = 2;
@@ -1631,12 +1602,12 @@ public class Blocks implements ContentList{
requirements(Category.turret, with(Items.silicon, 130, Items.thorium, 80, Items.phaseFabric, 40));
health = 250 * size * size;
range = 160f;
range = 180f;
hasPower = true;
consumes.powerCond(8f, (PointDefenseBuild b) -> b.target != null);
size = 2;
shootLength = 5f;
bulletDamage = 25f;
bulletDamage = 30f;
reloadTime = 9f;
}};
@@ -1649,7 +1620,6 @@ public class Blocks implements ContentList{
Liquids.oil, Bullets.heavyOilShot
);
size = 3;
recoilAmount = 0f;
reloadTime = 2f;
shots = 2;
velocityInaccuracy = 0.1f;
@@ -1661,6 +1631,7 @@ public class Blocks implements ContentList{
shootEffect = Fx.shootLiquid;
range = 190f;
health = 250 * size * size;
flags = EnumSet.of(BlockFlag.turret, BlockFlag.extinguisher);
}};
fuse = new ItemTurret("fuse"){{
@@ -1682,31 +1653,31 @@ public class Blocks implements ContentList{
float brange = range + 10f;
ammo(
Items.titanium, new ShrapnelBulletType(){{
length = brange;
damage = 66f;
ammoMultiplier = 4f;
width = 17f;
reloadMultiplier = 1.3f;
}},
Items.thorium, new ShrapnelBulletType(){{
length = brange;
damage = 105f;
ammoMultiplier = 5f;
toColor = Pal.thoriumPink;
shootEffect = smokeEffect = Fx.thoriumShoot;
}}
Items.titanium, new ShrapnelBulletType(){{
length = brange;
damage = 66f;
ammoMultiplier = 4f;
width = 17f;
reloadMultiplier = 1.3f;
}},
Items.thorium, new ShrapnelBulletType(){{
length = brange;
damage = 105f;
ammoMultiplier = 5f;
toColor = Pal.thoriumPink;
shootEffect = smokeEffect = Fx.thoriumShoot;
}}
);
}};
ripple = new ItemTurret("ripple"){{
requirements(Category.turret, with(Items.copper, 150, Items.graphite, 135, Items.titanium, 60));
ammo(
Items.graphite, Bullets.artilleryDense,
Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary,
Items.blastCompound, Bullets.artilleryExplosive,
Items.plastanium, Bullets.artilleryPlastic
Items.graphite, Bullets.artilleryDense,
Items.silicon, Bullets.artilleryHoming,
Items.pyratite, Bullets.artilleryIncendiary,
Items.blastCompound, Bullets.artilleryExplosive,
Items.plastanium, Bullets.artilleryPlastic
);
targetAir = false;
@@ -1732,10 +1703,10 @@ public class Blocks implements ContentList{
cyclone = new ItemTurret("cyclone"){{
requirements(Category.turret, with(Items.copper, 200, Items.titanium, 125, Items.plastanium, 80));
ammo(
Items.metaglass, Bullets.fragGlass,
Items.blastCompound, Bullets.fragExplosive,
Items.plastanium, Bullets.fragPlastic,
Items.surgeAlloy, Bullets.fragSurge
Items.metaglass, Bullets.fragGlass,
Items.blastCompound, Bullets.fragExplosive,
Items.plastanium, Bullets.fragPlastic,
Items.surgeAlloy, Bullets.fragSurge
);
xRand = 4f;
reloadTime = 8f;
@@ -1755,19 +1726,19 @@ public class Blocks implements ContentList{
requirements(Category.turret, with(Items.copper, 1000, Items.metaglass, 600, Items.surgeAlloy, 300, Items.plastanium, 200, Items.silicon, 600));
ammo(
Items.surgeAlloy, new PointBulletType(){{
shootEffect = Fx.instShoot;
hitEffect = Fx.instHit;
smokeEffect = Fx.smokeCloud;
trailEffect = Fx.instTrail;
despawnEffect = Fx.instBomb;
trailSpacing = 20f;
damage = 1350;
buildingDamageMultiplier = 0.3f;
speed = brange;
hitShake = 6f;
ammoMultiplier = 1f;
}}
Items.surgeAlloy, new PointBulletType(){{
shootEffect = Fx.instShoot;
hitEffect = Fx.instHit;
smokeEffect = Fx.smokeCloud;
trailEffect = Fx.instTrail;
despawnEffect = Fx.instBomb;
trailSpacing = 20f;
damage = 1350;
buildingDamageMultiplier = 0.3f;
speed = brange;
hitShake = 6f;
ammoMultiplier = 1f;
}}
);
maxAmmo = 40;
@@ -1796,9 +1767,9 @@ public class Blocks implements ContentList{
spectre = new ItemTurret("spectre"){{
requirements(Category.turret, with(Items.copper, 900, Items.graphite, 300, Items.surgeAlloy, 250, Items.plastanium, 175, Items.thorium, 250));
ammo(
Items.graphite, Bullets.standardDenseBig,
Items.pyratite, Bullets.standardIncendiaryBig,
Items.thorium, Bullets.standardThoriumBig
Items.graphite, Bullets.standardDenseBig,
Items.pyratite, Bullets.standardIncendiaryBig,
Items.thorium, Bullets.standardThoriumBig
);
reloadTime = 6f;
coolantMultiplier = 0.5f;
@@ -1838,6 +1809,7 @@ public class Blocks implements ContentList{
shootType = new ContinuousLaserBulletType(70){{
length = 200f;
hitEffect = Fx.hitMeltdown;
hitColor = Pal.meltdownHit;
drawSize = 420f;
incendChance = 0.4f;
@@ -1992,7 +1964,7 @@ public class Blocks implements ContentList{
powerSource = new PowerSource("power-source"){{
requirements(Category.power, BuildVisibility.sandboxOnly, with());
powerProduction = 100000f / 60f;
powerProduction = 1000000f / 60f;
alwaysUnlocked = true;
}};
@@ -2070,6 +2042,7 @@ public class Blocks implements ContentList{
hasPower = true;
consumes.power(10f);
buildCostMultiplier = 0.5f;
health = size * size * 80;
}};
//endregion campaign

View File

@@ -63,7 +63,7 @@ public class Bullets implements ContentList{
lifetime = 80f;
width = height = 11f;
collidesTiles = false;
splashDamageRadius = 25f;
splashDamageRadius = 25f * 0.75f;
splashDamage = 33f;
}};
@@ -84,7 +84,7 @@ public class Bullets implements ContentList{
lifetime = 80f;
width = height = 13f;
collidesTiles = false;
splashDamageRadius = 35f;
splashDamageRadius = 35f * 0.75f;
splashDamage = 45f;
fragBullet = artilleryPlasticFrag;
fragBullets = 10;
@@ -98,7 +98,7 @@ public class Bullets implements ContentList{
lifetime = 80f;
width = height = 11f;
collidesTiles = false;
splashDamageRadius = 25f;
splashDamageRadius = 25f * 0.75f;
splashDamage = 33f;
reloadMultiplier = 1.2f;
ammoMultiplier = 3f;
@@ -112,7 +112,7 @@ public class Bullets implements ContentList{
lifetime = 80f;
width = height = 13f;
collidesTiles = false;
splashDamageRadius = 25f;
splashDamageRadius = 25f * 0.75f;
splashDamage = 35f;
status = StatusEffects.burning;
frontColor = Pal.lightishOrange;
@@ -128,7 +128,7 @@ public class Bullets implements ContentList{
width = height = 14f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 45f;
splashDamageRadius = 45f * 0.75f;
splashDamage = 50f;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;
@@ -155,7 +155,7 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 27f;
splashDamage = 27f * 1.5f;
splashDamageRadius = 15f;
}};
@@ -167,7 +167,7 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 22f;
splashDamage = 22f * 1.5f;
splashDamageRadius = 24f;
}};
@@ -179,10 +179,10 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 22f;
splashDamage = 22f * 1.5f;
splashDamageRadius = 20f;
fragBullet = flakGlassFrag;
fragBullets = 5;
fragBullets = 6;
}};
fragGlassFrag = new BasicBulletType(3f, 5, "bullet"){{
@@ -212,19 +212,19 @@ public class Bullets implements ContentList{
width = 6f;
height = 8f;
hitEffect = Fx.flakExplosion;
splashDamage = 18f;
splashDamage = 18f * 1.5f;
splashDamageRadius = 16f;
fragBullet = fragGlassFrag;
fragBullets = 3;
fragBullets = 4;
explodeRange = 20f;
collidesGround = true;
}};
fragPlastic = new FlakBulletType(4f, 6){{
splashDamageRadius = 40f;
splashDamage = 25f;
splashDamage = 25f * 1.5f;
fragBullet = fragPlasticFrag;
fragBullets = 5;
fragBullets = 6;
hitEffect = Fx.plasticExplosion;
frontColor = Pal.plastaniumFront;
backColor = Pal.plastaniumBack;
@@ -235,9 +235,9 @@ public class Bullets implements ContentList{
fragExplosive = new FlakBulletType(4f, 5){{
shootEffect = Fx.shootBig;
ammoMultiplier = 4f;
splashDamage = 18f;
splashDamageRadius = 55f;
ammoMultiplier = 5f;
splashDamage = 26f * 1.5f;
splashDamageRadius = 60f;
collidesGround = true;
status = StatusEffects.blasted;
@@ -245,9 +245,9 @@ public class Bullets implements ContentList{
}};
fragSurge = new FlakBulletType(4.5f, 13){{
ammoMultiplier = 4f;
splashDamage = 50f;
splashDamageRadius = 40f;
ammoMultiplier = 5f;
splashDamage = 50f * 1.5f;
splashDamageRadius = 38f;
lightning = 2;
lightningLength = 7;
shootEffect = Fx.shootBig;
@@ -261,7 +261,7 @@ public class Bullets implements ContentList{
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 30f;
splashDamage = 30f;
splashDamage = 30f * 1.5f;
ammoMultiplier = 4f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
@@ -279,7 +279,7 @@ public class Bullets implements ContentList{
drag = -0.01f;
homingPower = 0.08f;
splashDamageRadius = 20f;
splashDamage = 20f;
splashDamage = 20f * 1.5f;
makeFire = true;
hitEffect = Fx.blastExplosion;
status = StatusEffects.burning;
@@ -291,7 +291,7 @@ public class Bullets implements ContentList{
shrinkY = 0f;
drag = -0.01f;
splashDamageRadius = 25f;
splashDamage = 25f;
splashDamage = 25f * 1.5f;
hitEffect = Fx.blastExplosion;
despawnEffect = Fx.blastExplosion;
lightningDamage = 10;

View File

@@ -26,17 +26,23 @@ public class Fx{
none = new Effect(0, 0f, e -> {}),
unitSpawn = new Effect(30f, e -> {
if(!(e.data instanceof UnitType)) return;
alpha(e.fin());
if(!(e.data instanceof UnitType unit)) return;
float scl = 1f + e.fout() * 2f;
UnitType unit = e.data();
TextureRegion region = unit.icon(Cicon.full);
alpha(e.fout());
mixcol(Color.white, e.fin());
rect(region, e.x, e.y, 180f);
reset();
alpha(e.fin());
rect(region, e.x, e.y,
region.width * Draw.scl * scl, region.height * Draw.scl * scl, 180f);
region.width * Draw.scl * scl, region.height * Draw.scl * scl, e.rotation - 90);
}),
@@ -418,6 +424,16 @@ public class Fx{
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
});
}),
hitLaserBlast = new Effect(12, e -> {
color(e.color);
stroke(e.fout() * 1.5f);
randLenVectors(e.id, 8, e.finpow() * 17f, e.rotation, 360f, (x, y) -> {
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
});
}),
hitLancer = new Effect(12, e -> {
color(Color.white);
@@ -429,6 +445,16 @@ public class Fx{
});
}),
hitBeam = new Effect(12, e -> {
color(e.color);
stroke(e.fout() * 2f);
randLenVectors(e.id, 6, e.finpow() * 18f, e.rotation, 360f, (x, y) -> {
float ang = Mathf.angle(x, y);
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
});
}),
hitMeltdown = new Effect(12, e -> {
color(Pal.meltdownHit);
stroke(e.fout() * 2f);
@@ -697,6 +723,19 @@ public class Fx{
stroke(2f * e.fout());
Lines.circle(e.x, e.y, 5f * e.fout());
}),
forceShrink = new Effect(20, e -> {
color(e.color, e.fout());
if(renderer.animateShields){
Fill.poly(e.x, e.y, 6, e.rotation * e.fout());
}else{
stroke(1.5f);
Draw.alpha(0.09f);
Fill.poly(e.x, e.y, 6, e.rotation * e.fout());
Draw.alpha(1f);
Lines.poly(e.x, e.y, 6, e.rotation * e.fout());
}
}).layer(Layer.shields),
flakExplosionBig = new Effect(30, e -> {
color(Pal.bulletYellowBack);
@@ -896,7 +935,7 @@ public class Fx{
});
}),
dynamicExplosion = new Effect(30, e -> {
dynamicExplosion = new Effect(30, 100f, e -> {
float intensity = e.rotation;
e.scaled(5 + intensity * 2, i -> {
@@ -1564,6 +1603,18 @@ public class Fx{
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
}),
rotateBlock = new Effect(30, e -> {
color(Pal.accent);
alpha(e.fout() * 1);
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
}),
lightBlock = new Effect(60, e -> {
color(e.color);
alpha(e.fout() * 1);
Fill.square(e.x, e.y, e.rotation * tilesize / 2f);
}),
overdriveBlockFull = new Effect(60, e -> {
color(e.color);
alpha(e.fslope() * 0.4f);

View File

@@ -74,6 +74,7 @@ public class Items implements ContentList{
surgeAlloy = new Item("surge-alloy", Color.valueOf("f3e979")){{
cost = 1.2f;
charge = 0.75f;
}};
sporePod = new Item("spore-pod", Color.valueOf("7457ce")){{

View File

@@ -12,7 +12,7 @@ import mindustry.graphics.*;
import static mindustry.Vars.*;
public class StatusEffects implements ContentList{
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed;
public static StatusEffect none, burning, freezing, unmoving, slow, wet, muddy, melting, sapped, tarred, overdrive, overclock, shielded, shocked, blasted, corroded, boss, sporeSlowed, disarmed;
@Override
public void load(){
@@ -21,7 +21,7 @@ public class StatusEffects implements ContentList{
burning = new StatusEffect("burning"){{
color = Pal.lightFlame;
damage = 0.12f; //over 8 seconds, this would be 60 damage
damage = 0.12f; //over 8 seconds, this would be ~60 damage
effect = Fx.burning;
init(() -> {
@@ -29,7 +29,7 @@ public class StatusEffects implements ContentList{
trans(tarred, ((unit, time, newTime, result) -> {
unit.damagePierce(8f);
Fx.burning.at(unit.x + Mathf.range(unit.bounds() / 2f), unit.y + Mathf.range(unit.bounds() / 2f));
result.set(this, Math.min(time + newTime, 300f));
result.set(burning, Math.min(time + newTime, 300f));
}));
});
}};
@@ -45,7 +45,7 @@ public class StatusEffects implements ContentList{
trans(blasted, ((unit, time, newTime, result) -> {
unit.damagePierce(18f);
result.set(this, time);
result.set(freezing, time);
}));
});
}};
@@ -72,7 +72,7 @@ public class StatusEffects implements ContentList{
if(unit.team == state.rules.waveTeam){
Events.fire(Trigger.shock);
}
result.set(this, time);
result.set(wet, time);
}));
opposite(burning);
});
@@ -97,7 +97,7 @@ public class StatusEffects implements ContentList{
trans(tarred, ((unit, time, newTime, result) -> {
unit.damagePierce(8f);
Fx.burning.at(unit.x + Mathf.range(unit.bounds() / 2f), unit.y + Mathf.range(unit.bounds() / 2f));
result.set(this, Math.min(time + newTime, 200f));
result.set(melting, Math.min(time + newTime, 200f));
}));
});
}};
@@ -161,15 +161,22 @@ public class StatusEffects implements ContentList{
shocked = new StatusEffect("shocked"){{
color = Pal.lancerLaser;
reactive = true;
}};
blasted = new StatusEffect("blasted"){{
color = Color.valueOf("ff795e");
reactive = true;
}};
corroded = new StatusEffect("corroded"){{
color = Pal.plastanium;
damage = 0.1f;
}};
disarmed = new StatusEffect("disarmed"){{
color = Color.valueOf("e9ead3");
disarm = true;
}};
}
}

View File

@@ -117,7 +117,7 @@ public class UnitTypes implements ContentList{
hitSize = 13f;
rotateSpeed = 3f;
targetAir = false;
health = 790;
health = 800;
armor = 9f;
mechFrontSway = 0.55f;
@@ -137,8 +137,8 @@ public class UnitTypes implements ContentList{
width = height = 14f;
collides = true;
collidesTiles = true;
splashDamageRadius = 24f;
splashDamage = 45f;
splashDamageRadius = 28f;
splashDamage = 54f;
backColor = Pal.bulletYellowBack;
frontColor = Pal.bulletYellow;
}};
@@ -150,7 +150,7 @@ public class UnitTypes implements ContentList{
hitSize = 20f;
rotateSpeed = 2.1f;
health = 9000;
armor = 11f;
armor = 10f;
canDrown = false;
mechFrontSway = 1f;
@@ -173,7 +173,7 @@ public class UnitTypes implements ContentList{
inaccuracy = 3f;
shotDelay = 4f;
bullet = new BasicBulletType(7f, 50){{
bullet = new BasicBulletType(7f, 45){{
width = 11f;
height = 20f;
lifetime = 25f;
@@ -182,7 +182,7 @@ public class UnitTypes implements ContentList{
lightningLength = 6;
lightningColor = Pal.surge;
//standard bullet damage is far too much for lightning
lightningDamage = 20;
lightningDamage = 19;
}};
}},
@@ -230,7 +230,7 @@ public class UnitTypes implements ContentList{
ejectEffect = Fx.casing4;
shootSound = Sounds.bang;
bullet = new BasicBulletType(13f, 60){{
bullet = new BasicBulletType(13f, 65){{
pierce = true;
pierceCap = 10;
width = 14f;
@@ -240,14 +240,14 @@ public class UnitTypes implements ContentList{
fragVelocityMin = 0.4f;
hitEffect = Fx.blastExplosion;
splashDamage = 18f;
splashDamageRadius = 30f;
splashDamage = 16f;
splashDamageRadius = 13f;
fragBullets = 2;
fragLifeMin = 0f;
fragCone = 30f;
fragBullet = new BasicBulletType(9f, 15){{
fragBullet = new BasicBulletType(9f, 18){{
width = 10f;
height = 10f;
pierce = true;
@@ -257,7 +257,7 @@ public class UnitTypes implements ContentList{
lifetime = 20f;
hitEffect = Fx.flakExplosion;
splashDamage = 15f;
splashDamageRadius = 15f;
splashDamageRadius = 10f;
}};
}};
}}
@@ -292,7 +292,7 @@ public class UnitTypes implements ContentList{
shootSound = Sounds.lasershoot;
bullet = new LaserBoltBulletType(5.2f, 14){{
lifetime = 37f;
lifetime = 32f;
healPercent = 5f;
collidesTeam = true;
backColor = Pal.heal;
@@ -322,9 +322,8 @@ public class UnitTypes implements ContentList{
x = 5f;
shake = 2.2f;
y = 0.5f;
shootY = 5f;
shootY = 2.5f;
reload = 38f;
shots = 3;
inaccuracy = 35;
@@ -359,7 +358,6 @@ public class UnitTypes implements ContentList{
quasar = new UnitType("quasar"){{
mineTier = 3;
hitSize = 12f;
boostMultiplier = 2f;
health = 650f;
buildSpeed = 1.7f;
@@ -407,19 +405,20 @@ public class UnitTypes implements ContentList{
rotateSpeed = 1.6f;
canDrown = false;
mechFrontSway = 1f;
buildSpeed = 3f;
mechStepParticles = true;
mechStepShake = 0.15f;
ammoType = AmmoTypes.powerHigh;
speed = 0.35f;
boostMultiplier = 2.1f;
speed = 0.39f;
boostMultiplier = 2.2f;
engineOffset = 12f;
engineSize = 6f;
lowAltitude = true;
health = 7000f;
armor = 7f;
health = 7500f;
armor = 9f;
canBoost = true;
landShake = 4f;
immunities = ObjectSet.with(StatusEffects.burning);
@@ -435,7 +434,7 @@ public class UnitTypes implements ContentList{
firstShotDelay = Fx.greenLaserChargeSmall.lifetime - 1f;
reload = 160f;
reload = 155f;
recoil = 0f;
chargeSound = Sounds.lasercharge2;
shootSound = Sounds.beam;
@@ -443,8 +442,8 @@ public class UnitTypes implements ContentList{
cooldownTime = 200f;
bullet = new ContinuousLaserBulletType(){{
damage = 23f;
length = 160f;
damage = 30f;
length = 175f;
hitEffect = Fx.hitMeltHeal;
drawSize = 420f;
lifetime = 160f;
@@ -454,7 +453,7 @@ public class UnitTypes implements ContentList{
shootEffect = Fx.greenLaserChargeSmall;
incendChance = 0.075f;
incendChance = 0.1f;
incendSpread = 5f;
incendAmount = 1;
@@ -471,7 +470,6 @@ public class UnitTypes implements ContentList{
}};
corvus = new UnitType("corvus"){{
mineTier = 1;
hitSize = 29f;
health = 18000f;
armor = 9f;
@@ -565,9 +563,9 @@ public class UnitTypes implements ContentList{
hitEffect = Fx.pulverize;
lifetime = 10f;
speed = 1f;
splashDamageRadius = 70f;
splashDamageRadius = 58f;
instantDisappear = true;
splashDamage = 80f;
splashDamage = 85f;
killShooter = true;
hittable = false;
collidesAir = true;
@@ -769,7 +767,7 @@ public class UnitTypes implements ContentList{
width = height = 19f;
collidesTiles = true;
ammoMultiplier = 4f;
splashDamageRadius = 95f;
splashDamageRadius = 70f;
splashDamage = 65f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -867,7 +865,7 @@ public class UnitTypes implements ContentList{
width = height = 25f;
collidesTiles = collides = true;
ammoMultiplier = 4f;
splashDamageRadius = 90f;
splashDamageRadius = 80f;
splashDamage = 75f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -888,7 +886,7 @@ public class UnitTypes implements ContentList{
lifetime = 90f;
width = height = 20f;
collidesTiles = false;
splashDamageRadius = 80f;
splashDamageRadius = 70f;
splashDamage = 40f;
backColor = Pal.sapBulletBack;
frontColor = lightningColor = Pal.sapBullet;
@@ -1366,13 +1364,15 @@ public class UnitTypes implements ContentList{
collides = false;
healPercent = 15f;
splashDamage = 230f;
splashDamageRadius = 120f;
splashDamage = 220f;
splashDamageRadius = 80f;
}};
}});
}};
oct = new UnitType("oct"){{
defaultController = DefenderAI::new;
armor = 16f;
health = 24000;
speed = 0.8f;
@@ -1537,7 +1537,7 @@ public class UnitTypes implements ContentList{
width = 15f;
collidesTiles = false;
ammoMultiplier = 4f;
splashDamageRadius = 60f;
splashDamageRadius = 40f;
splashDamage = 80f;
backColor = Pal.missileYellowBack;
frontColor = Pal.missileYellow;
@@ -1590,7 +1590,7 @@ public class UnitTypes implements ContentList{
}};
sei = new UnitType("sei"){{
health = 10000;
health = 10500;
armor = 12f;
speed = 0.73f;

View File

@@ -59,6 +59,15 @@ public class Control implements ApplicationListener, Loadable{
saves = new Saves();
sound = new SoundControl();
//show dialog saying that mod loading was skipped.
Events.on(ClientLoadEvent.class, e -> {
if(Vars.mods.skipModLoading() && Vars.mods.list().any()){
Time.runTask(4f, () -> {
ui.showInfo("@mods.initfailed");
});
}
});
Events.on(StateChangeEvent.class, event -> {
if((event.from == State.playing && event.to == State.menu) || (event.from == State.menu && event.to != State.menu)){
Time.runTask(5f, platform::updateRPC);
@@ -74,7 +83,7 @@ public class Control implements ApplicationListener, Loadable{
Events.on(WorldLoadEvent.class, event -> {
if(Mathf.zero(player.x) && Mathf.zero(player.y)){
Building core = state.teams.closestCore(0, 0, player.team());
Building core = player.bestCore();
if(core != null){
player.set(core);
camera.position.set(core);

View File

@@ -57,6 +57,8 @@ public class Logic implements ApplicationListener{
//when loading a 'damaged' sector, propagate the damage
Events.on(SaveLoadEvent.class, e -> {
if(state.isCampaign()){
state.rules.coreIncinerates = true;
SectorInfo info = state.rules.sector.info;
info.write();
@@ -79,13 +81,6 @@ public class Logic implements ApplicationListener{
state.wavetime = state.rules.waveSpacing;
SectorDamage.applyCalculatedDamage();
//make sure damaged buildings are counted
for(Tile tile : world.tiles){
if(tile.build != null && tile.build.damaged()){
indexer.notifyTileDamaged(tile.build);
}
}
}
//reset values
@@ -107,6 +102,7 @@ public class Logic implements ApplicationListener{
if(!(state.getSector().preset != null && !state.getSector().preset.useAI)){
state.rules.waveTeam.rules().ai = true;
}
state.rules.coreIncinerates = true;
state.rules.waveTeam.rules().aiTier = state.getSector().threat * 0.8f;
state.rules.waveTeam.rules().infiniteResources = true;
@@ -204,7 +200,7 @@ public class Logic implements ApplicationListener{
}
public void skipWave(){
state.wavetime = 0;
runWave();
}
public void runWave(){

View File

@@ -26,6 +26,7 @@ import mindustry.world.*;
import mindustry.world.modules.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import static mindustry.Vars.*;
@@ -65,6 +66,13 @@ public class NetClient implements ApplicationListener{
reset();
//connection after reset
if(!net.client()){
Log.info("Connection canceled.");
disconnectQuietly();
return;
}
ui.loadfrag.hide();
ui.loadfrag.show("@connecting.data");
@@ -73,8 +81,14 @@ public class NetClient implements ApplicationListener{
disconnectQuietly();
});
ConnectPacket c = new ConnectPacket();
String locale = Core.settings.getString("locale");
if(locale.equals("default")){
locale = Locale.getDefault().toString();
}
var c = new ConnectPacket();
c.name = player.name;
c.locale = locale;
c.mods = mods.getModStrings();
c.mobile = mobile;
c.versionType = Version.type;
@@ -174,6 +188,10 @@ public class NetClient implements ApplicationListener{
//called when a server receives a chat message from a player
@Remote(called = Loc.server, targets = Loc.client)
public static void sendChatMessage(Player player, String message){
//do not receive chat messages from clients that are too young or not registered
if(net.server() && player != null && player.con != null && (Time.timeSinceMillis(player.con.connectTime) < 500 || !player.con.hasConnected || !player.isAdded())) return;
if(message.length() > maxTextLength){
throw new ValidateException(player, "Player has sent a message above the text limit.");
}
@@ -184,14 +202,14 @@ public class NetClient implements ApplicationListener{
CommandResponse response = netServer.clientCommands.handleMessage(message, player);
if(response.type == ResponseType.noCommand){ //no command to handle
message = netServer.admins.filterMessage(player, message);
//supress chat message if it's filtered out
//suppress chat message if it's filtered out
if(message == null){
return;
}
//special case; graphical server needs to see its message
if(!headless){
sendMessage(message, colorizeName(player.id(), player.name), player);
sendMessage(message, colorizeName(player.id, player.name), player);
}
//server console logging
@@ -396,7 +414,6 @@ public class NetClient implements ApplicationListener{
netClient.byteStream.setBytes(net.decompressSnapshot(data, dataLen));
DataInputStream input = netClient.dataStream;
//go through each entity
for(int j = 0; j < amount; j++){
int id = input.readInt();
byte typeID = input.readByte();
@@ -445,11 +462,16 @@ public class NetClient implements ApplicationListener{
for(int i = 0; i < amount; i++){
int pos = input.readInt();
short block = input.readShort();
Tile tile = world.tile(pos);
if(tile == null || tile.build == null){
Log.warn("Missing entity at @. Skipping block snapshot.", tile);
break;
}
if(tile.build.block.id != block){
Log.warn("Block ID mismatch at @: @ != @. Skipping block snapshot.", tile, tile.build.block.id, block);
break;
}
tile.build.readAll(Reads.get(input), tile.build.version());
}
}catch(Exception e){
@@ -476,7 +498,7 @@ public class NetClient implements ApplicationListener{
netClient.byteStream.setBytes(net.decompressSnapshot(coreData, coreDataLen));
DataInputStream input = netClient.dataStream;
byte cores = input.readByte();
int cores = input.readInt();
for(int i = 0; i < cores; i++){
int pos = input.readInt();
Tile tile = world.tile(pos);
@@ -615,7 +637,7 @@ public class NetClient implements ApplicationListener{
lastSent++,
uid,
player.dead(),
unit.x, unit.y,
player.dead() ? player.x : unit.x, player.dead() ? player.y : unit.y,
player.unit().aimX(), player.unit().aimY(),
unit.rotation,
unit instanceof Mechc m ? m.baseRotation() : 0,

View File

@@ -35,8 +35,8 @@ import static mindustry.Vars.*;
public class NetServer implements ApplicationListener{
/** note that snapshots are compressed, so the max snapshot size here is above the typical UDP safe limit */
private static final int maxSnapshotSize = 800, timerBlockSync = 0;
private static final float serverSyncTime = 12, blockSyncTime = 60 * 6;
private static final int maxSnapshotSize = 800, timerBlockSync = 0, serverSyncTime = 200;
private static final float blockSyncTime = 60 * 6;
private static final FloatBuffer fbuffer = FloatBuffer.allocate(20);
private static final Vec2 vector = new Vec2();
private static final Rect viewport = new Rect();
@@ -81,6 +81,8 @@ public class NetServer implements ApplicationListener{
public NetServer(){
net.handleServer(Connect.class, (con, connect) -> {
Events.fire(new ConnectionEvent(con));
if(admins.isIPBanned(connect.addressTCP) || admins.isSubnetBanned(connect.addressTCP)){
con.kick(KickReason.banned);
}
@@ -93,10 +95,14 @@ public class NetServer implements ApplicationListener{
});
net.handleServer(ConnectPacket.class, (con, packet) -> {
if(con.kicked) return;
if(con.address.startsWith("steam:")){
packet.uuid = con.address.substring("steam:".length());
}
con.connectTime = Time.millis();
String uuid = packet.uuid;
byte[] buuid = Base64Coder.decode(uuid);
CRC32 crc = new CRC32();
@@ -155,7 +161,7 @@ public class NetServer implements ApplicationListener{
if(!extraMods.isEmpty()){
result.append("Unnecessary mods:[lightgray]\n").append("> ").append(extraMods.toString("\n> "));
}
con.kick(result.toString());
con.kick(result.toString(), 0);
}
if(!admins.isWhitelisted(packet.uuid, packet.usid)){
@@ -195,6 +201,10 @@ public class NetServer implements ApplicationListener{
return;
}
if(packet.locale == null){
packet.locale = "en";
}
String ip = con.address;
admins.updatePlayerJoined(uuid, ip, packet.name);
@@ -215,6 +225,7 @@ public class NetServer implements ApplicationListener{
player.con.uuid = uuid;
player.con.mobile = packet.mobile;
player.name = packet.name;
player.locale = packet.locale;
player.color.set(packet.color).a(1f);
//save admin ID but don't overwrite it
@@ -244,7 +255,8 @@ public class NetServer implements ApplicationListener{
});
net.handleServer(InvokePacket.class, (con, packet) -> {
if(con.player == null) return;
if(con.player == null || con.kicked) return;
try{
RemoteReadServer.readPacket(packet.reader(), packet.type, con.player);
}catch(ValidateException e){
@@ -644,9 +656,6 @@ public class NetServer implements ApplicationListener{
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
float maxSpeed = unit.realSpeed();
if(unit.isGrounded()){
maxSpeed *= unit.floorSpeedMultiplier();
}
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.2f;
@@ -720,6 +729,7 @@ public class NetServer implements ApplicationListener{
//no verification is done, so admins can hypothetically spam waves
//not a real issue, because server owners may want to do just that
logic.skipWave();
info("&lc@ has skipped the wave.", player.name);
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
netServer.admins.banPlayerID(other.con.uuid);
@@ -729,7 +739,8 @@ public class NetServer implements ApplicationListener{
other.kick(KickReason.kick);
info("&lc@ has kicked @.", player.name, other.name);
}else if(action == AdminAction.trace){
TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.con.modclient, other.con.mobile);
PlayerInfo stats = netServer.admins.getInfo(other.uuid());
TraceInfo info = new TraceInfo(other.con.address, other.uuid(), other.con.modclient, other.con.mobile, stats.timesJoined, stats.timesKicked);
if(player.con != null){
Call.traceInfo(player.con, other, info);
}else{
@@ -741,6 +752,8 @@ public class NetServer implements ApplicationListener{
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
if(player.con.kicked) return;
player.add();
if(player.con == null || player.con.hasConnected) return;
@@ -824,6 +837,7 @@ public class NetServer implements ApplicationListener{
sent ++;
dataStream.writeInt(entity.pos());
dataStream.writeShort(entity.block.id);
entity.writeAll(Writes.get(dataStream));
if(syncStream.size() > maxSnapshotSize){
@@ -844,13 +858,15 @@ public class NetServer implements ApplicationListener{
public void writeEntitySnapshot(Player player) throws IOException{
syncStream.reset();
Seq<CoreBuild> cores = state.teams.cores(player.team());
int sum = state.teams.present.sum(t -> t.cores.size);
dataStream.writeByte(cores.size);
dataStream.writeInt(sum);
for(CoreBuild entity : cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(Writes.get(dataStream));
for(TeamData data : state.teams.present){
for(CoreBuild entity : data.cores){
dataStream.writeInt(entity.tile.pos());
entity.items.write(Writes.get(dataStream));
}
}
dataStream.close();
@@ -948,9 +964,11 @@ public class NetServer implements ApplicationListener{
return;
}
NetConnection connection = player.con;
var connection = player.con;
if(!player.timer(0, serverSyncTime) || !connection.hasConnected) return;
if(Time.timeSinceMillis(connection.syncTime) < serverSyncTime || !connection.hasConnected) return;
connection.syncTime = Time.millis();
try{
writeEntitySnapshot(player);

View File

@@ -20,10 +20,9 @@ import static mindustry.Vars.*;
public interface Platform{
/** Dynamically loads a jar file. */
default Class<?> loadJar(Fi jar, String mainClass) throws Exception{
URLClassLoader classLoader = new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, getClass().getClassLoader());
return Class.forName(mainClass, true, classLoader);
/** Dynamically creates a class loader for a jar file. */
default ClassLoader loadJar(Fi jar, String mainClass) throws Exception{
return new URLClassLoader(new URL[]{jar.file().toURI().toURL()}, getClass().getClassLoader());
}
/** Steam: Update lobby visibility.*/

View File

@@ -2,7 +2,6 @@ package mindustry.core;
import arc.*;
import arc.files.*;
import arc.fx.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
@@ -15,6 +14,7 @@ import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.graphics.g3d.*;
import mindustry.ui.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import static arc.Core.*;
import static mindustry.Vars.*;
@@ -32,17 +32,13 @@ public class Renderer implements ApplicationListener{
public @Nullable Bloom bloom;
public FrameBuffer effectBuffer = new FrameBuffer();
public boolean animateShields, drawWeather = true;
public boolean animateShields, drawWeather = true, drawStatus;
/** minZoom = zooming out, maxZoom = zooming in */
public float minZoom = 1.5f, maxZoom = 6f;
//TODO unused
private FxProcessor fx = new FxProcessor();
private @Nullable CoreBuild landCore;
private Color clearColor = new Color(0f, 0f, 0f, 1f);
private float targetscale = Scl.scl(4);
private float camerascale = targetscale;
private float landscale = 0f, landTime, weatherAlpha;
private float minZoomScl = Scl.scl(0.01f);
private float targetscale = Scl.scl(4), camerascale = targetscale, landscale, landTime, weatherAlpha, minZoomScl = Scl.scl(0.01f);
private float shakeIntensity, shaketime;
public Renderer(){
@@ -62,12 +58,15 @@ public class Renderer implements ApplicationListener{
if(settings.getBool("bloom", !ios)){
setupBloom();
}
Events.on(WorldLoadEvent.class, e -> {
landCore = player.bestCore();
});
}
@Override
public void update(){
Color.white.set(1f, 1f, 1f, 1f);
Gl.clear(Gl.stencilBufferBit);
float dest = Mathf.round(targetscale, 0.5f);
camerascale = Mathf.lerpDelta(camerascale, dest, 0.1f);
@@ -75,6 +74,7 @@ public class Renderer implements ApplicationListener{
laserOpacity = settings.getInt("lasersopacity") / 100f;
bridgeOpacity = settings.getInt("bridgeopacity") / 100f;
animateShields = settings.getBool("animatedshields");
drawStatus = Core.settings.getBool("blockstatus");
if(landTime > 0){
landTime -= Time.delta;
@@ -129,11 +129,6 @@ public class Renderer implements ApplicationListener{
Events.fire(new DisposeEvent());
}
@Override
public void resize(int width, int height){
fx.resize(width, height);
}
@Override
public void resume(){
if(settings.getBool("bloom") && bloom != null){
@@ -168,23 +163,6 @@ public class Renderer implements ApplicationListener{
}
}
void beginFx(){
if(!fx.hasEnabledEffects()) return;
Draw.flush();
fx.clear();
fx.begin();
}
void endFx(){
if(!fx.hasEnabledEffects()) return;
Draw.flush();
fx.end();
fx.applyEffects();
fx.render(0, 0, fx.getWidth(), fx.getHeight());
}
void updateShake(float scale){
if(shaketime > 0){
float intensity = shakeIntensity * (settings.getInt("screenshake", 4) / 4f) * scale;
@@ -229,7 +207,7 @@ public class Renderer implements ApplicationListener{
Draw.draw(Layer.background, this::drawBackground);
Draw.draw(Layer.floor, blocks.floor::drawFloor);
Draw.draw(Layer.block - 1, blocks::drawShadows);
Draw.draw(Layer.block, () -> {
Draw.draw(Layer.block - 0.09f, () -> {
blocks.floor.beginDraw();
blocks.floor.drawLayer(CacheLayer.walls);
blocks.floor.endDraw();
@@ -284,25 +262,25 @@ public class Renderer implements ApplicationListener{
}
private void drawLanding(){
if(landTime > 0 && player.closestCore() != null){
CoreBuild entity = landCore == null ? player.bestCore() : landCore;
if(landTime > 0 && entity != null){
float fract = landTime / Fx.coreLand.lifetime;
Building entity = player.closestCore();
TextureRegion reg = entity.block.icon(Cicon.full);
float scl = Scl.scl(4f) / camerascale;
float s = reg.width * Draw.scl * scl * 4f * fract;
Draw.color(Pal.lightTrail);
Draw.rect("circle-shadow", entity.getX(), entity.getY(), s, s);
Draw.rect("circle-shadow", entity.x, entity.y, s, s);
Angles.randLenVectors(1, (1f- fract), 100, 1000f * scl * (1f-fract), (x, y, fin, fout) -> {
Lines.stroke(scl * fin);
Lines.lineAngle(entity.getX() + x, entity.getY() + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
Lines.lineAngle(entity.x + x, entity.y + y, Mathf.angle(x, y), (fin * 20 + 1f) * scl);
});
Draw.color();
Draw.mixcol(Color.white, fract);
Draw.rect(reg, entity.getX(), entity.getY(), reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fract * 135f);
Draw.rect(reg, entity.x, entity.y, reg.width * Draw.scl * scl, reg.height * Draw.scl * scl, fract * 135f);
Draw.reset();
}

View File

@@ -108,7 +108,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));
Tooltips.getInstance().textProvider = text -> new Tooltip(t -> t.background(Styles.black6).margin(4f).add(text));
Core.settings.setErrorHandler(e -> {
Log.err(e);
@@ -214,6 +214,13 @@ public class UI implements ApplicationListener, Loadable{
@Override
public void resize(int width, int height){
if(Core.scene == null) return;
int[] insets = Core.graphics.getSafeInsets();
Core.scene.marginLeft = insets[0];
Core.scene.marginRight = insets[1];
Core.scene.marginTop = insets[2];
Core.scene.marginBottom = insets[3];
Core.scene.resize(width, height);
Events.fire(new ResizeEvent());
}
@@ -363,6 +370,16 @@ public class UI implements ApplicationListener, Loadable{
}}.show();
}
public void showInfoOnHidden(String info, Runnable listener){
new Dialog(""){{
getCell(cont).growX();
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.center, Align.center);
buttons.button("@ok", this::hide).size(110, 50).pad(4);
hidden(listener);
closeOnBack();
}}.show();
}
public void showStartupInfo(String info){
new Dialog(""){{
getCell(cont).growX();
@@ -526,16 +543,15 @@ public class UI implements ApplicationListener, Loadable{
dialog.show();
}
//TODO move?
public static String formatAmount(int number){
if(number >= 1_000_000_000){
int mag = Math.abs(number);
if(mag >= 1_000_000_000){
return Strings.fixed(number / 1_000_000_000f, 1) + "[gray]" + Core.bundle.get("unit.billions") + "[]";
}else if(number >= 1_000_000){
}else if(mag >= 1_000_000){
return Strings.fixed(number / 1_000_000f, 1) + "[gray]" + Core.bundle.get("unit.millions") + "[]";
}else if(number >= 10_000){
}else if(mag >= 10_000){
return number / 1000 + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
}else if(number >= 1000){
}else if(mag >= 1000){
return Strings.fixed(number / 1000f, 1) + "[gray]" + Core.bundle.get("unit.thousands") + "[]";
}else{
return number + "";

View File

@@ -4,6 +4,7 @@ import arc.*;
import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.math.geom.Geometry.*;
import arc.struct.*;
import arc.struct.ObjectIntMap.*;
import arc.util.*;
@@ -554,10 +555,6 @@ public class World{
return dark;
}
public interface Raycaster{
boolean accept(int x, int y);
}
private class Context implements WorldContext{
Context(){

View File

@@ -8,6 +8,7 @@ import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.editor.DrawOperation.*;
import mindustry.entities.units.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.io.*;
@@ -259,7 +260,7 @@ public class MapEditor{
clearOp();
Tiles previous = world.tiles;
int offsetX = -(width - width()) / 2, offsetY = -(height - height()) / 2;
int offsetX = (width() - width) / 2, offsetY = (height() - height) / 2;
loading = true;
Tiles tiles = world.resize(width, height);
@@ -275,7 +276,17 @@ public class MapEditor{
if(tile.build != null && tile.isCenter()){
tile.build.x = x * tilesize + tile.block().offset;
tile.build.y = y * tilesize + tile.block().offset;
//shift links to account for map resize
Object config = tile.build.config();
if(config != null){
Object out = BuildPlan.pointConfig(tile.block(), config, p -> p.sub(offsetX, offsetY));
if(out != config){
tile.build.configureAny(out);
}
}
}
}else{
tiles.set(x, y, new EditorTile(x, y, Blocks.stone.id, (short)0, (short)0));
}

View File

@@ -156,7 +156,10 @@ public class MapEditorDialog extends Dialog implements Disposable{
}
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.tags.containsKey("steamid") ? editor.tags.get("author").equals(player.name) ? "@workshop.listing" : "@view.workshop" : "@editor.publish.workshop"));
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b ->
b.setText(editor.tags.containsKey("steamid") ?
editor.tags.get("author", "").equals(steamPlayerName) ? "@workshop.listing" : "@view.workshop" :
"@editor.publish.workshop"));
menu.cont.row();
}
@@ -263,7 +266,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(player.team().core() == null){
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
player.unit(UnitTypes.alpha.spawn(player.team(), player.x, player.y));
var unit = UnitTypes.alpha.spawn(player.team(), player.x, player.y);
unit.spawnedByCore = true;
player.unit(unit);
}
});
}

View File

@@ -285,29 +285,31 @@ public class MapGenerateDialog extends BaseDialog{
void showAdd(){
BaseDialog selection = new BaseDialog("@add");
selection.setFillParent(false);
selection.cont.defaults().size(210f, 60f);
int i = 0;
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
selection.cont.pane(p -> {
p.marginRight(14);
p.defaults().size(210f, 60f);
int i = 0;
for(Prov<GenerateFilter> gen : filterTypes){
GenerateFilter filter = gen.get();
if((filter.isPost() && applied)) continue;
if((filter.isPost() && applied)) continue;
selection.cont.button(filter.name(), () -> {
filters.add(filter);
p.button(filter.name(), () -> {
filters.add(filter);
rebuildFilters();
update();
selection.hide();
});
if(++i % 2 == 0) p.row();
}
p.button("@filter.defaultores", () -> {
maps.addDefaultOres(filters);
rebuildFilters();
update();
selection.hide();
});
if(++i % 2 == 0) selection.cont.row();
}
selection.cont.button("@filter.defaultores", () -> {
maps.addDefaultOres(filters);
rebuildFilters();
update();
selection.hide();
});
}).get().setScrollingDisabled(true, false);
selection.addCloseButton();
selection.show();

View File

@@ -190,7 +190,7 @@ public class WaveInfoDialog extends BaseDialog{
group.end = never;
updateWaves();
}
}).width(100f).get().setMessageText(Core.bundle.get("waves.never"));
}).width(100f).get().setMessageText("");
});
t.row();
t.table(p -> {

View File

@@ -6,7 +6,6 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.core.*;
import mindustry.game.EventType.*;
@@ -25,11 +24,10 @@ public class Damage{
private static Rect hitrect = new Rect();
private static Vec2 tr = new Vec2(), seg1 = new Vec2(), seg2 = new Vec2();
private static Seq<Unit> units = new Seq<>();
private static GridBits bits = new GridBits(30, 30);
private static IntQueue propagation = new IntQueue();
private static IntSet collidedBlocks = new IntSet();
private static Building tmpBuilding;
private static Unit tmpUnit;
private static IntFloatMap damages = new IntFloatMap();
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage){
@@ -39,9 +37,9 @@ public class Damage{
/** Creates a dynamic explosion based on specified parameters. */
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage, boolean fire, @Nullable Team ignoreTeam){
if(damage){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int)(power / 30), 1, 20);
Time.run(i * 2f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), branches + Mathf.range(2)));
for(int i = 0; i < Mathf.clamp(power / 700, 0, 8); i++){
int length = 5 + Mathf.clamp((int)(power / 500), 1, 20);
Time.run(i * 0.8f + Mathf.random(4f), () -> Lightning.create(Team.derelict, Pal.power, 3, x, y, Mathf.random(360f), length + Mathf.range(2)));
}
if(fire){
@@ -116,7 +114,15 @@ public class Damage{
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large){
length = findLaserLength(hitter, length);
collideLine(hitter, team, effect, x, y, angle, length, large, true);
}
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(Bullet hitter, Team team, Effect effect, float x, float y, float angle, float length, boolean large, boolean laser){
if(laser) length = findLaserLength(hitter, length);
collidedBlocks.clear();
tr.trns(angle, length);
@@ -206,10 +212,10 @@ public class Damage{
*/
public static Healthc linecast(Bullet hitter, float x, float y, float angle, float length){
tr.trns(angle, length);
tmpBuilding = null;
if(hitter.type.collidesGround){
tmpBuilding = null;
world.raycastEachWorld(x, y, x + tr.x, y + tr.y, (cx, cy) -> {
Building tile = world.build(cx, cy);
if(tile != null && tile.team != hitter.team){
@@ -218,8 +224,6 @@ public class Damage{
}
return false;
});
if(tmpBuilding != null) return tmpBuilding;
}
rect.setPosition(x, y).setSize(tr.x, tr.y);
@@ -263,6 +267,14 @@ public class Damage{
Units.nearbyEnemies(hitter.team, rect, cons);
if(tmpBuilding != null && tmpUnit != null){
if(Mathf.dst2(x, y, tmpUnit.getX(), tmpUnit.getY()) <= Mathf.dst2(x, y, tmpBuilding.getX(), tmpBuilding.getY())){
return tmpUnit;
}
}else if(tmpBuilding != null){
return tmpBuilding;
}
return tmpUnit;
}
@@ -351,64 +363,84 @@ public class Damage{
if(ground){
if(!complete){
int trad = (int)(radius / tilesize);
Tile tile = world.tileWorld(x, y);
if(tile != null){
tileDamage(team, tile.x, tile.y, trad, damage);
}
tileDamage(team, World.toTile(x), World.toTile(y), radius / tilesize, damage);
}else{
completeDamage(team, x, y, radius, damage);
}
}
}
public static void tileDamage(Team team, int startx, int starty, int baseRadius, float baseDamage){
//tile damage is posted, so that destroying a block that causes a chain explosion will run in the next frame
//this prevents recursive damage calls from messing up temporary variables
public static void tileDamage(Team team, int x, int y, float baseRadius, float damage){
Core.app.post(() -> {
bits.clear();
propagation.clear();
int bitOffset = bits.width() / 2;
var in = world.build(x, y);
//spawned inside a multiblock. this means that damage needs to be dealt directly.
//why? because otherwise the building would absorb everything in one cell, which means much less damage than a nearby explosion.
//this needs to be compensated
if(in != null && in.team != team && in.block.size > 1 && in.health > damage){
//deal the damage of an entire side, to be equivalent with maximum 'standard' damage
in.damage(damage * Math.min((in.block.size), baseRadius * 0.45f));
//no need to continue with the explosion
return;
}
propagation.addFirst(PropCell.get((byte)0, (byte)0, (short)baseDamage));
//clamp radius to fit bits
int radius = Math.min(baseRadius, bits.width() / 2);
//cap radius to prevent lag
float radius = Math.min(baseRadius, 30), rad2 = radius * radius;
int rays = Mathf.ceil(radius * 2 * Mathf.pi);
double spacing = Math.PI * 2.0 / rays;
damages.clear();
while(!propagation.isEmpty()){
int prop = propagation.removeLast();
int x = PropCell.x(prop);
int y = PropCell.y(prop);
int damage = PropCell.damage(prop);
//manhattan distance used for calculating falloff, results in a diamond pattern
int dst = Math.abs(x) + Math.abs(y);
//raycast from each angle
for(int i = 0; i <= rays; i++){
float dealt = 0f;
int startX = x;
int startY = y;
int endX = x + (int)(Math.cos(spacing * i) * radius), endY = y + (int)(Math.sin(spacing * i) * radius);
int scaledDamage = (int)(damage * (1f - (float)dst / radius));
int xDist = Math.abs(endX - startX);
int yDist = -Math.abs(endY - startY);
int xStep = (startX < endX ? +1 : -1);
int yStep = (startY < endY ? +1 : -1);
int error = xDist + yDist;
bits.set(bitOffset + x, bitOffset + y);
Tile tile = world.tile(startx + x, starty + y);
while(startX != endX || startY != endY){
var build = world.build(startX, startY);
if(build != null && build.team != team){
//damage dealt at circle edge
float edgeScale = 0.6f;
float mult = (1f-(Mathf.dst2(startX, startY, x, y) / rad2) + edgeScale) / (1f + edgeScale);
float next = damage * mult - dealt;
//register damage dealt
int p = Point2.pack(startX, startY);
damages.put(p, Math.max(damages.get(p), next));
//register as hit
dealt += build.health;
if(scaledDamage <= 0 || tile == null) continue;
//apply damage to entity if needed
if(tile.build != null && tile.build.team != team){
int health = (int)(tile.build.health / (tile.block().size * tile.block().size));
if(tile.build.health > 0){
tile.build.damage(scaledDamage);
scaledDamage -= health;
if(scaledDamage <= 0) continue;
if(next - dealt <= 0){
break;
}
}
}
for(Point2 p : Geometry.d4){
if(!bits.get(bitOffset + x + p.x, bitOffset + y + p.y)){
propagation.addFirst(PropCell.get((byte)(x + p.x), (byte)(y + p.y), (short)scaledDamage));
if(2 * error - yDist > xDist - 2 * error){
error += yDist;
startX += xStep;
}else{
error += xDist;
startY += yStep;
}
}
}
});
//apply damage
for(var e : damages){
int cx = Point2.x(e.key), cy = Point2.y(e.key);
var build = world.build(cx, cy);
if(build != null){
build.damage(e.value);
}
}
});
}
private static void completeDamage(Team team, float x, float y, float radius, float damage){
@@ -429,11 +461,4 @@ public class Damage{
float scaled = Mathf.lerp(1f - dist / radius, 1f, falloff);
return damage * scaled;
}
@Struct
static class PropCellStruct{
byte x;
byte y;
short damage;
}
}

View File

@@ -3,6 +3,7 @@ package mindustry.entities;
import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import mindustry.content.*;
import mindustry.gen.*;
import mindustry.world.*;
@@ -126,7 +127,7 @@ public class EntityCollisions{
public static boolean legsSolid(int x, int y){
Tile tile = world.tile(x, y);
return tile == null || tile.staticDarkness() >= 2 || tile.floor().solid;
return tile == null || tile.staticDarkness() >= 2 || (tile.floor().solid && tile.block() == Blocks.air);
}
public static boolean waterSolid(int x, int y){
@@ -190,14 +191,10 @@ public class EntityCollisions{
yInvExit = y2 - (y1 + h1);
}
float xEntry, yEntry;
float xExit, yExit;
xEntry = xInvEntry / vx1;
xExit = xInvExit / vx1;
yEntry = yInvEntry / vy1;
yExit = yInvExit / vy1;
float xEntry = xInvEntry / vx1;
float xExit = xInvExit / vx1;
float yEntry = yInvEntry / vy1;
float yExit = yInvExit / vy1;
float entryTime = Math.max(xEntry, yEntry);
float exitTime = Math.min(xExit, yExit);

View File

@@ -6,6 +6,7 @@ import arc.math.geom.*;
import arc.struct.*;
import mindustry.annotations.Annotations.*;
import mindustry.content.*;
import mindustry.entities.comp.*;
import mindustry.game.*;
import mindustry.game.Teams.*;
import mindustry.gen.*;
@@ -75,7 +76,7 @@ public class Units{
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);
return Math.max(0, state.rules.unitCapVariable ? state.rules.unitCap + indexer.getExtraUnits(team) : state.rules.unitCap);
}
/** @return whether this player can interact with a specific tile. if either of these are null, returns true.*/
@@ -93,7 +94,7 @@ public class Units{
* @return whether the target is invalid
*/
public static boolean invalidateTarget(Posc target, Team team, float x, float y, float range){
return target == null || (range != Float.MAX_VALUE && !target.within(x, y, range)) || (target instanceof Teamc && ((Teamc)target).team() == team) || (target instanceof Healthc && !((Healthc)target).isValid());
return target == null || (range != Float.MAX_VALUE && !target.within(x, y, range + (target instanceof Sized hb ? hb.hitSize()/2f : 0f))) || (target instanceof Teamc t && t.team() == team) || (target instanceof Healthc h && !h.isValid());
}
/** See {@link #invalidateTarget(Posc, Team, float, float, float)} */
@@ -217,7 +218,7 @@ public class Units{
cdist = 0f;
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
if(e.dead() || !predicate.get(e) || !e.within(x, y, range)) return;
if(e.dead() || !predicate.get(e) || !e.within(x, y, range + e.hitSize/2f)) return;
float cost = sort.cost(e, x, y);
if(result == null || cost < cdist){
@@ -247,7 +248,7 @@ public class Units{
return result;
}
/** Returns the closest ally of this team. Filter by predicate. */
/** Returns the closest ally of this team in a range. Filter by predicate. */
public static Unit closest(Team team, float x, float y, float range, Boolf<Unit> predicate){
result = null;
cdist = 0f;
@@ -265,6 +266,24 @@ public class Units{
return result;
}
/** Returns the closest ally of this team in a range. Filter by predicate. */
public static Unit closest(Team team, float x, float y, float range, Boolf<Unit> predicate, Sortf sort){
result = null;
cdist = 0f;
nearby(team, x, y, range, e -> {
if(!predicate.get(e)) return;
float dist = sort.cost(e, x, y);
if(result == null || dist < cdist){
result = e;
cdist = dist;
}
});
return result;
}
/** Returns the closest ally of this team. Filter by predicate.
* Unlike the closest() function, this only guarantees that unit hitboxes overlap the range. */
public static Unit closestOverlap(Team team, float x, float y, float range, Boolf<Unit> predicate){
@@ -292,7 +311,7 @@ public class Units{
/** Iterates over all units in a circle around this position. */
public static void nearby(Team team, float x, float y, float radius, Cons<Unit> cons){
nearby(team, x - radius, y - radius, radius*2f, radius*2f, unit -> {
if(unit.within(x, y, radius)){
if(unit.within(x, y, radius + unit.hitSize/2f)){
cons.get(unit);
}
});

View File

@@ -1,34 +1,57 @@
package mindustry.entities.abilities;
import arc.*;
import arc.audio.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.util.*;
import arc.audio.*;
import mindustry.content.*;
import mindustry.entities.*;
import mindustry.entities.bullet.*;
import mindustry.gen.*;
public class MoveLightningAbility extends Ability{
//Lightning damage
/** Lightning damage */
public float damage = 35f;
//Chance of firing every tick. Set >= 1 to always fire lightning every tick at max speed.
/** Chance of firing every tick. Set >= 1 to always fire lightning every tick at max speed. */
public float chance = 0.15f;
//Length of the lightning
/** Length of the lightning. <= 0 to disable */
public int length = 12;
//Speeds for when to start lightninging and when to stop getting faster
/** Speeds for when to start lightninging and when to stop getting faster */
public float minSpeed = 0.8f, maxSpeed = 1.2f;
//Lightning color
/** Lightning color */
public Color color = Color.valueOf("a9d8ff");
/** Shifts where the lightning spawns along the Y axis */
public float offset = 0f;
/** Jittering heat sprite like the shield on v5 Javelin */
public String heatRegion = "error";
/** Bullet type that is fired. Can be null */
public @Nullable BulletType bullet;
/** Bullet angle parameters */
public float bulletAngle = 0f, bulletSpread = 0f;
public Effect shootEffect = Fx.sparkShoot;
public Sound shootSound = Sounds.spark;
MoveLightningAbility(){}
public MoveLightningAbility(float damage, int length, float chance, float minSpeed, float maxSpeed, Color color){
public MoveLightningAbility(float damage, int length, float chance, float offset, float minSpeed, float maxSpeed, Color color, String heatRegion){
this.damage = damage;
this.length = length;
this.chance = chance;
this.offset = offset;
this.minSpeed = minSpeed;
this.maxSpeed = maxSpeed;
this.color = color;
this.heatRegion = heatRegion;
}
public MoveLightningAbility(float damage, int length, float chance, float offset, float minSpeed, float maxSpeed, Color color){
this.damage = damage;
this.length = length;
this.chance = chance;
this.offset = offset;
this.minSpeed = minSpeed;
this.maxSpeed = maxSpeed;
this.color = color;
@@ -38,9 +61,31 @@ public class MoveLightningAbility extends Ability{
public void update(Unit unit){
float scl = Mathf.clamp((unit.vel().len() - minSpeed) / (maxSpeed - minSpeed));
if(Mathf.chance(Time.delta * chance * scl)){
shootEffect.at(unit.x, unit.y, unit.rotation, color);
Lightning.create(unit.team, color, damage, unit.x + unit.vel.x, unit.y + unit.vel.y, unit.rotation, length);
float x = unit.x + Angles.trnsx(unit.rotation, offset, 0), y = unit.y + Angles.trnsy(unit.rotation, offset, 0);
shootEffect.at(x, y, unit.rotation, color);
shootSound.at(unit);
if(length > 0){
Lightning.create(unit.team, color, damage, x + unit.vel.x, y + unit.vel.y, unit.rotation, length);
}
if(bullet != null){
bullet.create(unit, unit.team, x, y, unit.rotation + bulletAngle + Mathf.range(bulletSpread));
}
}
}
@Override
public void draw(Unit unit){
float scl = Mathf.clamp((unit.vel().len() - minSpeed) / (maxSpeed - minSpeed));
TextureRegion region = Core.atlas.find(heatRegion);
if(Core.atlas.isFound(region) && scl > 0.00001f){
Draw.color(color);
Draw.alpha(scl / 2f);
Draw.blend(Blending.additive);
Draw.rect(region, unit.x + Mathf.range(scl / 2f), unit.y + Mathf.range(scl / 2f), unit.rotation - 90);
Draw.blend();
}
}
}

View File

@@ -15,14 +15,14 @@ import mindustry.ui.*;
import static mindustry.Vars.*;
public class UnitSpawnAbility extends Ability{
public UnitType type;
public UnitType unit;
public float spawnTime = 60f, spawnX, spawnY;
public Effect spawnEffect = Fx.spawn;
protected float timer;
public UnitSpawnAbility(UnitType type, float spawnTime, float spawnX, float spawnY){
this.type = type;
public UnitSpawnAbility(UnitType unit, float spawnTime, float spawnX, float spawnY){
this.unit = unit;
this.spawnTime = spawnTime;
this.spawnX = spawnX;
this.spawnY = spawnY;
@@ -35,10 +35,10 @@ public class UnitSpawnAbility extends Ability{
public void update(Unit unit){
timer += Time.delta * state.rules.unitBuildSpeedMultiplier;
if(timer >= spawnTime && Units.canCreate(unit.team, type)){
if(timer >= spawnTime && Units.canCreate(unit.team, this.unit)){
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
spawnEffect.at(x, y);
Unit u = type.create(unit.team);
Unit u = this.unit.create(unit.team);
u.set(x, y);
u.rotation = unit.rotation;
if(!Vars.net.client()){
@@ -51,16 +51,16 @@ public class UnitSpawnAbility extends Ability{
@Override
public void draw(Unit unit){
if(Units.canCreate(unit.team, type)){
if(Units.canCreate(unit.team, this.unit)){
Draw.draw(Draw.z(), () -> {
float x = unit.x + Angles.trnsx(unit.rotation, spawnY, spawnX), y = unit.y + Angles.trnsy(unit.rotation, spawnY, spawnX);
Drawf.construct(x, y, type.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
Drawf.construct(x, y, this.unit.icon(Cicon.full), unit.rotation - 90, timer / spawnTime, 1f, timer);
});
}
}
@Override
public String localized(){
return Core.bundle.format("ability.unitspawn", type.localizedName);
return Core.bundle.format("ability.unitspawn", unit.localizedName);
}
}

View File

@@ -34,6 +34,10 @@ public abstract class BulletType extends Content{
public Effect smokeEffect = Fx.shootSmallSmoke;
/** Sound made when hitting something or getting removed.*/
public Sound hitSound = Sounds.none;
/** Pitch of the sound made when hitting something*/
public float hitSoundPitch = 1;
/** Volume of the sound made when hitting something*/
public float hitSoundVolume = 1;
/** Extra inaccuracy when firing. */
public float inaccuracy = 0f;
/** How many bullets get created per ammo item/liquid. */
@@ -185,8 +189,9 @@ public abstract class BulletType extends Content{
}
public void hit(Bullet b, float x, float y){
b.hit = true;
hitEffect.at(x, y, b.rotation(), hitColor);
hitSound.at(b);
hitSound.at(x, y, hitSoundPitch, hitSoundVolume);
Effect.shake(hitShake, hitShake, b);
@@ -241,7 +246,7 @@ public abstract class BulletType extends Content{
Effect.shake(despawnShake, despawnShake, b);
if(fragBullet != null || splashDamageRadius > 0 || lightning > 0){
if(!b.hit && (fragBullet != null || splashDamageRadius > 0 || lightning > 0)){
hit(b);
}
}
@@ -268,13 +273,12 @@ public abstract class BulletType extends Content{
if(homingPower > 0.0001f && b.time >= homingDelay){
Teamc target = Units.closestTarget(b.team, b.x, b.y, homingRange, e -> (e.isGrounded() && collidesGround) || (e.isFlying() && collidesAir), t -> collidesGround);
if(target != null){
b.vel.setAngle(Mathf.slerpDelta(b.rotation(), b.angleTo(target), homingPower));
b.vel.setAngle(Angles.moveToward(b.rotation(), b.angleTo(target), homingPower * Time.delta * 50f));
}
}
if(weaveMag > 0){
float scl = Mathf.randomSeed(id, 0.9f, 1.1f);
b.vel.rotate(Mathf.sin(b.time + Mathf.PI * weaveScale/2f * scl, weaveScale * scl, weaveMag) * Time.delta);
b.vel.rotate(Mathf.sin(b.time + Mathf.PI * weaveScale/2f, weaveScale, weaveMag * (Mathf.randomSeed(b.id, 0, 1) == 1 ? -1 : 1)) * Time.delta);
}
if(trailChance > 0){
@@ -334,6 +338,7 @@ public abstract class BulletType extends Content{
bullet.type = this;
bullet.owner = owner;
bullet.team = team;
bullet.time = 0f;
bullet.vel.trns(angle, speed * velocityScl);
if(backMove){
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);

View File

@@ -25,7 +25,7 @@ public class ContinuousLaserBulletType extends BulletType{
public ContinuousLaserBulletType(float damage){
super(0.001f, damage);
hitEffect = Fx.hitMeltdown;
hitEffect = Fx.hitBeam;
despawnEffect = Fx.none;
hitSize = 4;
drawSize = 420f;

View File

@@ -23,7 +23,8 @@ public class LaserBulletType extends BulletType{
public LaserBulletType(float damage){
super(0.01f, damage);
hitEffect = Fx.hitLancer;
hitEffect = Fx.hitLaserBlast;
hitColor = colors[2];
despawnEffect = Fx.none;
shootEffect = Fx.hitLancer;
smokeEffect = Fx.none;

View File

@@ -8,8 +8,8 @@ import mindustry.gen.*;
import mindustry.graphics.*;
public class LightningBulletType extends BulletType{
protected Color lightningColor = Pal.lancerLaser;
protected int lightningLength = 25, lightningLengthRand = 0;
public Color lightningColor = Pal.lancerLaser;
public int lightningLength = 25, lightningLengthRand = 0;
public LightningBulletType(){
super(0.0001f, 1f);

View File

@@ -15,7 +15,7 @@ public class MissileBulletType extends BasicBulletType{
height = 8f;
hitSound = Sounds.explosion;
trailChance = 0.2f;
lifetime = 49f;
lifetime = 52f;
}
public MissileBulletType(float speed, float damage){

View File

@@ -54,7 +54,7 @@ public class RailBulletType extends BulletType{
super.init(b);
b.fdata = length;
Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), length, false);
Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), length, false, false);
float resultLen = b.fdata;
Vec2 nor = Tmp.v1.set(b.vel).nor();

View File

@@ -19,7 +19,7 @@ public class SapBulletType extends BulletType{
public SapBulletType(){
speed = 0.0001f;
despawnEffect = Fx.none;
pierce = false;
pierce = true;
collides = false;
hitSize = 0f;
hittable = false;

View File

@@ -33,6 +33,8 @@ public class ShrapnelBulletType extends BulletType{
@Override
public void init(Bullet b){
super.init(b);
Damage.collideLaser(b, length, hitLarge);
}

View File

@@ -1,6 +1,7 @@
package mindustry.entities.comp;
import arc.*;
import arc.func.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
@@ -25,10 +26,10 @@ import java.util.*;
import static mindustry.Vars.*;
@Component
abstract class BuilderComp implements Posc, Teamc, Rotc{
abstract class BuilderComp implements Posc, Statusc, Teamc, Rotc{
static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
@Import float x, y, rotation;
@Import float x, y, rotation, buildSpeedMultiplier;
@Import UnitType type;
@Import Team team;
@@ -40,7 +41,7 @@ abstract class BuilderComp implements Posc, Teamc, Rotc{
private transient float buildAlpha = 0f;
public boolean canBuild(){
return type.buildSpeed > 0;
return type.buildSpeed > 0 && buildSpeedMultiplier > 0;
}
@Override
@@ -125,9 +126,9 @@ abstract class BuilderComp implements Posc, Teamc, Rotc{
//otherwise, update it.
if(current.breaking){
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier);
entity.deconstruct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * buildSpeedMultiplier * state.rules.buildSpeedMultiplier);
}else{
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * state.rules.buildSpeedMultiplier, current.config);
entity.construct(self(), core, 1f / entity.buildCost * Time.delta * type.buildSpeed * buildSpeedMultiplier * state.rules.buildSpeedMultiplier, current.config);
}
current.stuck = Mathf.equal(current.progress, entity.progress);
@@ -136,10 +137,17 @@ abstract class BuilderComp implements Posc, Teamc, Rotc{
/** Draw all current build plans. Does not draw the beam effect, only the positions. */
void drawBuildPlans(){
Boolf<BuildPlan> skip = plan -> plan.progress > 0.01f || (buildPlan() == plan && plan.initialized && (within(plan.x * tilesize, plan.y * tilesize, buildingRange) || state.isEditor()));
for(BuildPlan plan : plans){
if(plan.progress > 0.01f || (buildPlan() == plan && plan.initialized && (within(plan.x * tilesize, plan.y * tilesize, buildingRange) || state.isEditor()))) continue;
drawPlan(plan, 1f);
for(int i = 0; i < 2; i++){
for(BuildPlan plan : plans){
if(skip.get(plan)) continue;
if(i == 0){
drawPlan(plan, 1f);
}else{
drawPlanTop(plan, 1f);
}
}
}
Draw.reset();
@@ -153,7 +161,11 @@ abstract class BuilderComp implements Posc, Teamc, Rotc{
request.block.drawPlan(request, control.input.allRequests(),
Build.validPlace(request.block, team, request.x, request.y, request.rotation) || control.input.requestMatches(request),
alpha);
}
}
void drawPlanTop(BuildPlan request, float alpha){
if(!request.breaking){
Draw.reset();
Draw.mixcol(Color.white, 0.24f + Mathf.absin(Time.globalTime, 6f, 0.28f));
Draw.alpha(alpha);
@@ -251,6 +263,7 @@ abstract class BuilderComp implements Posc, Teamc, Rotc{
if(core != null && active && !isLocal() && !(tile.block() instanceof ConstructBlock)){
Draw.z(Layer.plans - 1f);
drawPlan(plan, 0.5f);
drawPlanTop(plan, 0.5f);
Draw.z(Layer.flyingUnit);
}

View File

@@ -43,7 +43,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, Senseable, Controllable{
abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc, QuadTreeObject, Displayable, Senseable, Controllable, Sized{
//region vars and initialization
static final float timeToSleep = 60f * 1, timeToUncontrol = 60f * 6;
static final ObjectSet<Building> tmpTiles = new ObjectSet<>();
@@ -147,7 +147,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
write.f(health);
write.b(rotation | 0b10000000);
write.b(team.id);
write.b(0); //extra padding for later use
write.b(1); //version
write.b(enabled ? 1 : 0);
if(items != null) items.write(write);
if(power != null) power.write(write);
if(liquids != null) liquids.write(write);
@@ -162,7 +163,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
rotation = rot & 0b01111111;
boolean legacy = true;
if((rot & 0b10000000) != 0){
read.b(); //padding
byte ver = read.b(); //version of entity save
if(ver == 1){
byte on = read.b();
this.enabled = on == 1;
if(!this.enabled){
enabledControlTime = timeToUncontrol;
}
}
legacy = false;
}
@@ -528,6 +536,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
public void dumpLiquid(Liquid liquid){
dumpLiquid(liquid, 2f);
}
public void dumpLiquid(Liquid liquid, float scaling){
int dump = this.cdump;
if(liquids.get(liquid) <= 0.0001f) return;
@@ -543,10 +555,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
float ofract = other.liquids.get(liquid) / other.block.liquidCapacity;
float fract = liquids.get(liquid) / block.liquidCapacity;
if(ofract < fract) transferLiquid(other, (fract - ofract) * block.liquidCapacity / 2f, liquid);
if(ofract < fract) transferLiquid(other, (fract - ofract) * block.liquidCapacity / scaling, liquid);
}
}
}
public boolean canDumpLiquid(Building to, Liquid liquid){
@@ -770,6 +781,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
}
power.links.clear();
}
public boolean conductsTo(Building other){
return !block.insulated;
}
public Seq<Building> getPowerConnections(Seq<Building> out){
out.clear();
@@ -777,15 +792,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
for(Building other : proximity){
if(other != null && other.power != null
&& other.team == team
&& !(block.consumesPower && other.block.consumesPower && !block.outputsPower && !other.block.outputsPower)
&& !power.links.contains(other.pos())){
&& conductsTo(other) && other.conductsTo(self()) && !power.links.contains(other.pos())){
out.add(other);
}
}
for(int i = 0; i < power.links.size; i++){
Tile link = world.tile(power.links.get(i));
if(link != null && link.build != null && link.build.power != null) out.add(link.build);
if(link != null && link.build != null && link.build.power != null && link.build.team == team) out.add(link.build);
}
return out;
}
@@ -810,14 +826,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void drawStatus(){
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);
float multiplier = block.size > 1 ? 1 : 0.64f;
float brcx = x + (block.size * tilesize / 2f) - (tilesize * multiplier / 2f);
float brcy = y - (block.size * tilesize / 2f) + (tilesize * multiplier / 2f);
Draw.z(Layer.power + 1);
Draw.color(Pal.gray);
Fill.square(brcx, brcy, 2.5f, 45);
Fill.square(brcx, brcy, 2.5f * multiplier, 45);
Draw.color(status().color);
Fill.square(brcx, brcy, 1.5f, 45);
Fill.square(brcx, brcy, 1.5f * multiplier, 45);
Draw.color();
}
}
@@ -893,24 +910,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
public void placed(){
if(net.client()) return;
if(block.consumesPower || block.outputsPower){
int range = 10;
tempTiles.clear();
Geometry.circle(tileX(), tileY(), range, (x, y) -> {
Building other = world.build(x, y);
if(other != null && other.block instanceof PowerNode && ((PowerNode)other.block).linkValid(other, self()) && !PowerNode.insulated(other, self())
&& !other.proximity().contains(this.<Building>self()) &&
!(block.outputsPower && proximity.contains(p -> p.power != null && p.power.graph == other.power.graph))){
tempTiles.add(other.tile);
if((block.consumesPower || block.outputsPower) && block.hasPower){
PowerNode.getNodeLinks(tile, block, team, other -> {
if(!other.power.links.contains(pos())){
other.configureAny(pos());
}
});
tempTiles.sort(Structs.comparingFloat(t -> t.dst2(tile)));
if(!tempTiles.isEmpty()){
Tile toLink = tempTiles.first();
if(!toLink.build.power.links.contains(pos())){
toLink.build.configureAny(pos());
}
}
}
}
@@ -947,6 +952,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
if(block.configurations.containsKey(type)){
block.configurations.get(type).get(this, value);
}else if(value instanceof Building build){
//copy config of another building
var conf = build.config();
if(conf != null && !(conf instanceof Building)){
configured(builder, conf);
}
}
}
@@ -966,12 +977,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
int amount = items.get(item);
explosiveness += item.explosiveness * amount;
flammability += item.flammability * amount;
power += item.charge * amount * 100f;
}
}
if(block.hasLiquids){
flammability += liquids.sum((liquid, amount) -> liquid.explosiveness * amount / 2f);
explosiveness += liquids.sum((liquid, amount) -> liquid.flammability * amount / 2f);
flammability += liquids.sum((liquid, amount) -> liquid.flammability * amount / 2f);
explosiveness += liquids.sum((liquid, amount) -> liquid.explosiveness * amount / 2f);
}
if(block.consumes.hasPower() && block.consumes.getPower().buffered){
@@ -1130,7 +1142,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
/** Returns whether or not a hand cursor should be shown over this block. */
public Cursor getCursor(){
return block.configurable && team == player.team() ? SystemCursor.hand : SystemCursor.arrow;
return block.configurable && interactable(player.team()) ? SystemCursor.hand : SystemCursor.arrow;
}
/**
@@ -1260,6 +1272,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return tile.build == self() && !dead();
}
@Override
public float hitSize(){
return tile.block().size * tilesize;
}
@Replace
@Override
public void kill(){
@@ -1289,10 +1306,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return switch(sensor){
case x -> World.conv(x);
case y -> World.conv(y);
case dead -> !isValid() ? 1 : 0;
case team -> team.id;
case health -> health;
case maxHealth -> maxHealth;
case efficiency -> efficiency();
case timescale -> timeScale;
case range -> this instanceof Ranged r ? r.range() / tilesize : 0;
case rotation -> rotation;
case totalItems -> items == null ? 0 : items.total();
case totalLiquids -> liquids == null ? 0 : liquids.total();
@@ -1305,9 +1325,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
case powerNetStored -> power == null ? 0 : power.graph.getLastPowerStored();
case powerNetCapacity -> power == null ? 0 : power.graph.getLastCapacity();
case enabled -> enabled ? 1 : 0;
case controlled -> this instanceof ControlBlock c ? c.isControlled() ? 1 : 0 : 0;
case controlled -> this instanceof ControlBlock c && c.isControlled() ? GlobalConstants.ctrlPlayer : 0;
case payloadCount -> getPayload() != null ? 1 : 0;
default -> 0;
case size -> block.size;
default -> Float.NaN; //gets converted to null in logic
};
}
@@ -1320,14 +1341,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
default -> noSensed;
};
}
@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;
if(content instanceof Item i && items != null) return items.get(i);
if(content instanceof Liquid l && liquids != null) return liquids.get(l);
return Float.NaN; //invalid sense
}
@Override
@@ -1376,7 +1396,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
timeScale = 1f;
}
if(block.autoResetEnabled){
if(!enabled && block.autoResetEnabled){
noSleep();
enabledControlTime -= Time.delta;
if(enabledControlTime <= 0){

View File

@@ -30,7 +30,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
Object data;
BulletType type;
float fdata;
transient boolean absorbed;
transient boolean absorbed, hit;
@Override
public void getCollisions(Cons<QuadTree> consumer){

View File

@@ -4,6 +4,7 @@ import arc.func.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.ai.formations.*;
import mindustry.ai.types.*;
import mindustry.annotations.Annotations.*;
@@ -29,7 +30,7 @@ abstract class CommanderComp implements Entityc, Posc{
transient float minFormationSpeed;
public void update(){
if(controlling.isEmpty()){
if(controlling.isEmpty() && !Vars.net.client()){
formation = null;
}
@@ -80,6 +81,7 @@ abstract class CommanderComp implements Entityc, Posc{
void command(Formation formation, Seq<Unit> units){
clearCommand();
units.shuffle();
float spacing = hitSize * 0.8f;
minFormationSpeed = type.speed;

View File

@@ -10,6 +10,7 @@ import mindustry.entities.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
@@ -38,13 +39,15 @@ abstract class FireComp implements Timedc, Posc, Firec, Syncc{
control.sound.loop(Sounds.fire, this, 0.07f);
}
time = Mathf.clamp(time + Time.delta, 0, lifetime());
//faster updates -> disappears more quickly
float speedMultiplier = 1f + Math.max(state.envAttrs.get(Attribute.water) * 10f, 0);
time = Mathf.clamp(time + Time.delta * speedMultiplier, 0, lifetime);
if(Vars.net.client()){
return;
}
if(time >= lifetime() || tile == null){
if(time >= lifetime || tile == null){
remove();
return;
}

View File

@@ -8,7 +8,7 @@ import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
@Component
abstract class HitboxComp implements Posc, QuadTreeObject{
abstract class HitboxComp implements Posc, Sized, QuadTreeObject{
@Import float x, y;
transient float lastX, lastY, deltaX, deltaY, hitSize;
@@ -28,6 +28,11 @@ abstract class HitboxComp implements Posc, QuadTreeObject{
updateLastPosition();
}
@Override
public float hitSize(){
return hitSize;
}
void getCollisions(Cons<QuadTree> consumer){
}

View File

@@ -78,16 +78,16 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
@Override
public void moveAt(Vec2 vector, float acceleration){
//mark walking state when moving in a controlled manner
if(!vector.isZero()){
//mark walking state when moving in a controlled manner
walked = true;
}
}
@Override
public void approach(Vec2 vector){
if(!vector.isZero(0.09f)){
//mark walking state when moving in a controlled manner
//mark walking state when moving in a controlled manner
if(!vector.isZero(0.001f)){
walked = true;
}
}

View File

@@ -158,7 +158,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{
pad = (width - (itemSize) * items) / items;
}
for (Payload p : payloads){
for(Payload p : payloads){
table.image(p.icon(Cicon.small)).size(itemSize).padRight(pad);
}
}

View File

@@ -43,6 +43,8 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
String name = "noname";
Color color = new Color();
//locale should not be synced.
transient String locale = "en";
transient float deathTimer;
transient String lastText = "";
transient float textFadeTime;
@@ -59,6 +61,12 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return team.core();
}
/** @return largest/closest core, with largest cores getting priority */
@Nullable
public CoreBuild bestCore(){
return team.cores().min(Structs.comps(Structs.comparingInt(c -> -c.block.size), Structs.comparingFloat(c -> c.dst(x, y))));
}
public TextureRegion icon(){
//display default icon for dead players
if(dead()) return core() == null ? UnitTypes.alpha.icon(Cicon.full) : ((CoreBlock)core().block).unitType.icon(Cicon.full);
@@ -102,8 +110,12 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
unit.aim(mouseX, mouseY);
//this is only necessary when the thing being controlled isn't synced
unit.controlWeapons(shooting, shooting);
//save previous formation to prevent reset
var formation = unit.formation;
//extra precaution, necessary for non-synced things
unit.controller(this);
//keep previous formation
unit.formation = formation;
}
@Override
@@ -112,7 +124,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
clearUnit();
}
CoreBuild core = closestCore();
CoreBuild core;
if(!dead()){
set(unit);
@@ -124,7 +136,7 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
Tile tile = unit.tileOn();
unit.elevation = Mathf.approachDelta(unit.elevation, (tile != null && tile.solid()) || boosting ? 1f : 0f, 0.08f);
}
}else if(core != null){
}else if((core = bestCore()) != null){
//have a small delay before death to prevent the camera from jumping around too quickly
//(this is not for balance, it just looks better this way)
deathTimer += Time.delta;
@@ -191,6 +203,10 @@ abstract class PlayerComp implements UnitController, Entityc, Syncc, Timerc, Dra
return unit.isNull() || !unit.isValid();
}
String ip(){
return con == null ? "localhost" : con.address;
}
String uuid(){
return con == null ? "[LOCAL]" : con.uuid;
}

View File

@@ -0,0 +1,5 @@
package mindustry.entities.comp;
public interface Sized{
float hitSize();
}

View File

@@ -19,7 +19,8 @@ abstract class StatusComp implements Posc, Flyingc{
private Seq<StatusEntry> statuses = new Seq<>();
private transient Bits applied = new Bits(content.getBy(ContentType.status).size);
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1;
@ReadOnly transient float speedMultiplier = 1, damageMultiplier = 1, healthMultiplier = 1, reloadMultiplier = 1, buildSpeedMultiplier = 1;
@ReadOnly transient boolean disarmed = false;
@Import UnitType type;
@@ -55,10 +56,16 @@ abstract class StatusComp implements Posc, Flyingc{
}
}
//otherwise, no opposites found, add direct effect
StatusEntry entry = Pools.obtain(StatusEntry.class, StatusEntry::new);
entry.set(effect, duration);
statuses.add(entry);
if(!effect.reactive){
//otherwise, no opposites found, add direct effect
StatusEntry entry = Pools.obtain(StatusEntry.class, StatusEntry::new);
entry.set(effect, duration);
statuses.add(entry);
}
}
void clearStatuses(){
statuses.clear();
}
/** Removes a status effect. */
@@ -104,7 +111,8 @@ abstract class StatusComp implements Posc, Flyingc{
}
applied.clear();
speedMultiplier = damageMultiplier = healthMultiplier = reloadMultiplier = 1f;
speedMultiplier = damageMultiplier = healthMultiplier = reloadMultiplier = buildSpeedMultiplier = 1f;
disarmed = false;
if(statuses.isEmpty()) return;
@@ -126,6 +134,10 @@ abstract class StatusComp implements Posc, Flyingc{
healthMultiplier *= entry.effect.healthMultiplier;
damageMultiplier *= entry.effect.damageMultiplier;
reloadMultiplier *= entry.effect.reloadMultiplier;
buildSpeedMultiplier *= entry.effect.buildSpeedMultiplier;
disarmed |= entry.effect.disarm;
entry.effect.update(self(), entry.time);
}
}

View File

@@ -28,11 +28,12 @@ import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.payloads.*;
import static mindustry.Vars.*;
import static mindustry.logic.GlobalConstants.*;
@Component(base = true)
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc, Commanderc, Displayable, Senseable, Ranged, Minerc, Builderc{
@Import boolean hovering, dead;
@Import boolean hovering, dead, disarmed;
@Import float x, y, rotation, elevation, maxHealth, drag, armor, hitSize, health, ammo, minFormationSpeed;
@Import Team team;
@Import int id;
@@ -46,13 +47,14 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
transient Seq<Ability> abilities = new Seq<>(0);
private transient float resupplyTime = Mathf.random(10f);
private transient boolean wasPlayer;
public void moveAt(Vec2 vector){
moveAt(vector, type.accel);
}
public void approach(Vec2 vector){
vel.approachDelta(vector, type.accel * realSpeed() * floorSpeedMultiplier());
vel.approachDelta(vector, type.accel * realSpeed());
}
public void aimLook(Position pos){
@@ -81,7 +83,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
/** @return speed with boost multipliers factored in. */
public float realSpeed(){
return Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, elevation) * speed();
return Mathf.lerp(1f, type.canBoost ? type.boostMultiplier : 1f, elevation) * speed() * floorSpeedMultiplier();
}
/** Iterates through this unit and everything it is controlling. */
@@ -104,7 +106,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public float range(){
return type.range;
return type.maxRange;
}
@Replace
@@ -127,18 +129,26 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case ammoCapacity -> type.ammoCapacity;
case x -> World.conv(x);
case y -> World.conv(y);
case dead -> dead || !isAdded() ? 1 : 0;
case team -> team.id;
case shooting -> isShooting() ? 1 : 0;
case boosting -> type.canBoost && isFlying() ? 1 : 0;
case range -> range() / tilesize;
case shootX -> World.conv(aimX());
case shootY -> World.conv(aimY());
case mining -> mining() ? 1 : 0;
case mineX -> mining() ? mineTile.x : -1;
case mineY -> mining() ? mineTile.y : -1;
case flag -> flag;
case controlled -> controller instanceof LogicAI || controller instanceof Player ? 1 : 0;
case commanded -> controller instanceof FormationAI ? 1 : 0;
case controlled -> !isValid() ? 0 :
controller instanceof LogicAI ? ctrlProcessor :
controller instanceof Player ? ctrlPlayer :
controller instanceof FormationAI ? ctrlFormation :
0;
case commanded -> controller instanceof FormationAI && isValid() ? 1 : 0;
case payloadCount -> self() instanceof Payloadc pay ? pay.payloads().size : 0;
default -> 0;
case size -> hitSize / tilesize;
default -> Float.NaN;
};
}
@@ -148,19 +158,19 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
case type -> type;
case name -> controller instanceof Player p ? p.name : null;
case firstItem -> stack().amount == 0 ? null : item();
case controller -> !isValid() ? null : controller instanceof LogicAI log ? log.controller : controller instanceof FormationAI form ? form.leader : this;
case payloadType -> self() instanceof Payloadc pay ?
(pay.payloads().isEmpty() ? null :
pay.payloads().peek() instanceof UnitPayload p1 ? p1.unit.type :
pay.payloads().peek() instanceof BuildPayload p2 ? p2.block() : null) : null;
default -> noSensed;
};
}
@Override
public double sense(Content content){
if(content == stack().item) return stack().amount;
return 0;
return Float.NaN;
}
@Override
@@ -173,7 +183,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Replace
public boolean canShoot(){
//cannot shoot while boosting
return !(type.canBoost && isFlying());
return !disarmed && !(type.canBoost && isFlying());
}
@Override
@@ -272,7 +282,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
team.data().updateCount(type, 1);
//check if over unit cap
if(count() > cap() && !spawnedByCore && !dead){
if(count() > cap() && !spawnedByCore && !dead && !state.rules.editor){
Call.unitCapDeath(self());
team.data().updateCount(type, -1);
}
@@ -409,9 +419,10 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
float explosiveness = 2f + item().explosiveness * stack().amount * 1.53f;
float flammability = item().flammability * stack().amount / 1.9f;
float power = item().charge * stack().amount * 150f;
if(!spawnedByCore){
Damage.dynamicExplosion(x, y, flammability, explosiveness, 0f, bounds() / 2f, state.rules.damageExplosions, item().flammability > 1, team);
Damage.dynamicExplosion(x, y, flammability, explosiveness, power, bounds() / 2f, state.rules.damageExplosions, item().flammability > 1, team);
}
float shake = hitSize / 3f;
@@ -423,7 +434,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
Events.fire(new UnitDestroyEvent(self()));
if(explosiveness > 7f && isLocal()){
if(explosiveness > 7f && (isLocal() || wasPlayer)){
Events.fire(Trigger.suicideBomb);
}
@@ -481,6 +492,7 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I
@Override
public void killed(){
wasPlayer = isLocal();
health = 0;
dead = true;

View File

@@ -1,6 +1,5 @@
package mindustry.entities.comp;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.annotations.Annotations.*;
@@ -22,7 +21,7 @@ abstract class VelComp implements Posc{
@Override
public void update(){
move(vel.x * Time.delta, vel.y * Time.delta);
vel.scl(Mathf.clamp(1f - drag * Time.delta));
vel.scl(Math.max(1f - drag * Time.delta, 0));
}
/** @return function to use for check solid state. if null, no checking is done. */

View File

@@ -16,6 +16,7 @@ import static mindustry.Vars.*;
@Component
abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
@Import float x, y, rotation, reloadMultiplier;
@Import boolean disarmed;
@Import Vec2 vel;
@Import UnitType type;
@@ -81,7 +82,7 @@ abstract class WeaponsComp implements Teamc, Posc, Rotc, Velc, Statusc{
}
boolean canShoot(){
return true;
return !disarmed;
}
@Override

View File

@@ -19,13 +19,19 @@ public class MultiEffect extends Effect{
public void init(){
for(Effect f : effects){
clip = Math.max(clip, f.clip);
lifetime = Math.max(lifetime, f.lifetime);
}
}
@Override
public void render(EffectContainer e){
int index = 0;
for(Effect f : effects){
e.scaled(f.lifetime, f::render);
int i = ++index;
e.scaled(f.lifetime, cont -> {
cont.id = e.id + i;
f.render(cont);
});
clip = Math.max(clip, f.clip);
}
}

View File

@@ -16,6 +16,7 @@ public class ParticleEffect extends Effect{
//region only
public float sizeFrom = 2f, sizeTo = 0f;
public float offset = 0;
public String region = "circle";
//line only
@@ -48,7 +49,7 @@ public class ParticleEffect extends Effect{
});
}else{
Angles.randLenVectors(e.id, particles, length * fin + baseLength, e.rotation, cone, (x, y) -> {
Draw.rect(tex, e.x + x, e.y + y, rad, rad);
Draw.rect(tex, e.x + x, e.y + y, rad, rad, e.rotation + offset);
});
}
}

View File

@@ -15,7 +15,7 @@ import static mindustry.Vars.*;
public class AIController implements UnitController{
protected static final Vec2 vec = new Vec2();
protected static final int timerTarget = 0, timerTarget2 = 1, timerTarget3 = 2;
protected static final int timerTarget = 0, timerTarget2 = 1, timerTarget3 = 2, timerTarget4 = 3;
protected Unit unit;
protected Interval timer = new Interval(4);
@@ -123,7 +123,7 @@ public class AIController implements UnitController{
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())){
if(checkTarget(targets[i], mountX, mountY, weapon.bullet.range())){
targets[i] = null;
}
}
@@ -149,6 +149,10 @@ public class AIController implements UnitController{
}
}
protected boolean checkTarget(Teamc target, float x, float y, float range){
return Units.invalidateTarget(target, unit.team, x, y, range);
}
protected boolean shouldShoot(){
return true;
}
@@ -163,7 +167,7 @@ public class AIController implements UnitController{
}
protected boolean retarget(){
return timer.get(timerTarget, 40);
return timer.get(timerTarget, target == null ? 40 : 90);
}
protected Teamc findTarget(float x, float y, float range, boolean air, boolean ground){

View File

@@ -62,6 +62,10 @@ public class BuildPlan implements Position{
}
public boolean placeable(Team team){
return Build.validPlace(block, team, x, y, rotation);
}
public boolean isRotation(Team team){
if(breaking) return false;
Tile tile = tile();

View File

@@ -68,6 +68,8 @@ public class EventType{
public static class ContentInitEvent{}
/** Called when the client game is first loaded. */
public static class ClientLoadEvent{}
/** Called *after* all the modded files have been added into Vars.tree */
public static class FileTreeInitEvent{}
/** Called when a game begins and the world is loaded. */
public static class WorldLoadEvent{}
@@ -351,10 +353,22 @@ public class EventType{
}
}
/** Called when a unit is created in a reconstructor or factory. */
public static class UnitCreateEvent{
public final Unit unit;
public final Building spawner;
public UnitCreateEvent(Unit unit){
public UnitCreateEvent(Unit unit, Building spawner){
this.unit = unit;
this.spawner = spawner;
}
}
/** Called when a unit is dumped from any payload block. */
public static class UnitUnloadEvent{
public final Unit unit;
public UnitUnloadEvent(Unit unit){
this.unit = unit;
}
}
@@ -369,6 +383,15 @@ public class EventType{
}
}
/** Called when a connection is established to a client. */
public static class ConnectionEvent{
public final NetConnection connection;
public ConnectionEvent(NetConnection connection){
this.connection = connection;
}
}
/** Called after connecting; when a player receives world data and is ready to play.*/
public static class PlayerJoin{
public final Player player;

View File

@@ -31,7 +31,6 @@ public enum Gamemode{
rules.buildCostMultiplier = 1f;
rules.buildSpeedMultiplier = 1f;
rules.unitBuildSpeedMultiplier = 2f;
rules.unitHealthMultiplier = 3f;
rules.attackMode = true;
}, map -> map.teams.size > 1),
editor(true, rules -> {

View File

@@ -44,12 +44,14 @@ public class Rules{
public boolean fire = true;
/** Whether units use and require ammo. */
public boolean unitAmmo = false;
/** Whether cores add to unit limit */
public boolean unitCapVariable = true;
/** How fast unit pads build units. */
public float unitBuildSpeedMultiplier = 1f;
/** How much health units start with. */
public float unitHealthMultiplier = 1f;
/** How much damage any other units deal. */
public float unitDamageMultiplier = 1f;
/** Whether to allow units to build with logic. */
public boolean logicUnitBuild = true;
/** How much health blocks start with. */
public float blockHealthMultiplier = 1f;
/** How much damage blocks (turrets) deal. */
@@ -65,7 +67,7 @@ public class Rules{
/** Radius around enemy wave drop zones.*/
public float dropZoneRadius = 300f;
/** Time between waves in ticks. */
public float waveSpacing = 60 * 60 * 2;
public float waveSpacing = 2 * Time.toMinutes;
/** Wave after which the player 'wins'. Used in sectors. Use a value <= 0 to disable. */
public int winWave = 0;
/** Base unit cap. Can still be increased by blocks. */
@@ -97,6 +99,8 @@ public class Rules{
public Team waveTeam = Team.crux;
/** name of the custom mode that this ruleset describes, or null. */
public @Nullable String modeName;
/** Whether cores incinerate items when full, just like in the campaign. */
public boolean coreIncinerates = false;
/** special tags for additional info. */
public StringMap tags = new StringMap();
@@ -137,7 +141,7 @@ public class Rules{
}
/** A simple map for storing TeamRules in an efficient way without hashing. */
public static class TeamRules implements Serializable{
public static class TeamRules implements JsonSerializable{
final TeamRule[] values = new TeamRule[Team.all.length];
public TeamRule get(Team team){

View File

@@ -27,6 +27,7 @@ import mindustry.input.Placement.*;
import mindustry.io.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.legacy.*;
import mindustry.world.blocks.power.*;
@@ -285,7 +286,7 @@ public class Schematics implements Loadable{
/** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */
public Seq<BuildPlan> toRequests(Schematic schem, int x, int y){
return schem.tiles.map(t -> new BuildPlan(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block, t.config).original(t.x, t.y, schem.width, schem.height))
.removeAll(s -> (!s.block.isVisible() && !(s.block instanceof CoreBlock)) || !s.block.unlockedNow());
.removeAll(s -> (!s.block.isVisible() && !(s.block instanceof CoreBlock)) || !s.block.unlockedNow()).sort(Structs.comparingInt(s -> -s.block.schematicPriority));
}
/** @return all the valid loadouts for a specific core type. */
@@ -357,10 +358,11 @@ public class Schematics implements Loadable{
for(int cx = x; cx <= x2; cx++){
for(int cy = y; cy <= y2; cy++){
Building linked = world.build(cx, cy);
Block realBlock = linked == null ? null : linked instanceof ConstructBuild cons ? cons.cblock : linked.block;
if(linked != null && (linked.block.isVisible() || linked.block() instanceof CoreBlock) && !(linked.block instanceof ConstructBlock)){
int top = linked.block.size/2;
int bot = linked.block.size % 2 == 1 ? -linked.block.size/2 : -(linked.block.size - 1)/2;
if(linked != null && (realBlock.isVisible() || realBlock instanceof CoreBlock)){
int top = realBlock.size/2;
int bot = realBlock.size % 2 == 1 ? -realBlock.size/2 : -(realBlock.size - 1)/2;
minx = Math.min(linked.tileX() + bot, minx);
miny = Math.min(linked.tileY() + bot, miny);
maxx = Math.max(linked.tileX() + top, maxx);
@@ -385,12 +387,13 @@ public class Schematics implements Loadable{
for(int cx = ox; cx <= ox2; cx++){
for(int cy = oy; cy <= oy2; cy++){
Building tile = world.build(cx, cy);
Block realBlock = tile == null ? null : tile instanceof ConstructBuild cons ? cons.cblock : tile.block;
if(tile != null && !counted.contains(tile.pos()) && !(tile.block instanceof ConstructBlock)
&& (tile.block.isVisible() || tile.block instanceof CoreBlock)){
Object config = tile.config();
if(tile != null && !counted.contains(tile.pos())
&& (realBlock.isVisible() || realBlock instanceof CoreBlock)){
Object config = tile instanceof ConstructBuild cons ? cons.lastConfig : tile.config();
tiles.add(new Stile(tile.block, tile.tileX() + offsetX, tile.tileY() + offsetY, config, (byte)tile.rotation));
tiles.add(new Stile(realBlock, tile.tileX() + offsetX, tile.tileY() + offsetY, config, (byte)tile.rotation));
counted.add(tile.pos());
}
}

View File

@@ -63,6 +63,8 @@ public class SectorInfo{
public int spawnPosition;
/** How long the player has been playing elsewhere. */
public float secondsPassed;
/** How many minutes this sector has been captured. */
public float minutesCaptured;
/** Display name. */
public @Nullable String name;
/** Displayed icon. */

View File

@@ -18,7 +18,7 @@ import static mindustry.Vars.*;
* weapon equipped, ammo used, and status effects.
* Each spawn group can have multiple sub-groups spawned in different areas of the map.
*/
public class SpawnGroup implements Serializable{
public class SpawnGroup implements JsonSerializable{
public static final int never = Integer.MAX_VALUE;
/** The unit type spawned */

View File

@@ -123,6 +123,7 @@ public class Universe{
}
/** @return the last selected loadout for this specific core type. */
@Nullable
public Schematic getLoadout(CoreBlock core){
//for tools - schem
if(schematics == null) return Loadouts.basicShard;
@@ -134,7 +135,7 @@ public class Universe{
Seq<Schematic> all = schematics.getLoadouts(core);
Schematic schem = all.find(s -> s.file != null && s.file.nameWithoutExtension().equals(file));
return schem == null ? all.first() : schem;
return schem == null ? all.any() ? all.first() : null : schem;
}
/** Runs possible events. Resets event counter. */
@@ -148,6 +149,13 @@ public class Universe{
for(Sector sector : planet.sectors){
if(sector.hasSave() && sector.hasBase()){
//if it is being attacked, capture time is 0; otherwise, increment the timer
if(sector.isAttacked()){
sector.info.minutesCaptured = 0;
}else{
sector.info.minutesCaptured += turnDuration / 60 / 60;
}
//increment seconds passed for this sector by the time that just passed with this turn
if(!sector.isBeingPlayed()){
@@ -216,9 +224,11 @@ public class Universe{
}
//queue random invasions
if(!sector.isAttacked() && turn > invasionGracePeriod && sector.info.hasSpawns){
if(!sector.isAttacked() && sector.info.minutesCaptured > invasionGracePeriod && sector.info.hasSpawns){
int count = sector.near().count(Sector::hasEnemyBase);
//invasion chance depends on # of nearby bases
if(Mathf.chance(baseInvasionChance * Math.min(sector.near().count(Sector::hasEnemyBase), 1))){
if(count > 0 && Mathf.chance(baseInvasionChance * (0.8f + (count - 1) * 0.3f))){
int waveMax = Math.max(sector.info.winWave, sector.isBeingPlayed() ? state.wave : sector.info.wave + sector.info.wavesPassed) + Mathf.random(2, 4) * 5;
//assign invasion-related things

View File

@@ -39,7 +39,6 @@ public class BlockRenderer implements Disposable{
private Seq<Building> outArray2 = new Seq<>();
private Seq<Tile> shadowEvents = new Seq<>();
private IntSet procEntities = new IntSet(), procLinks = new IntSet(), procLights = new IntSet();
private boolean displayStatus = false;
public BlockRenderer(){
@@ -177,7 +176,6 @@ public class BlockRenderer implements Disposable{
/** Process all blocks to draw. */
public void processBlocks(){
displayStatus = Core.settings.getBool("blockstatus");
int avgx = (int)(camera.position.x / tilesize);
int avgy = (int)(camera.position.y / tilesize);
@@ -275,7 +273,7 @@ public class BlockRenderer implements Disposable{
Draw.z(Layer.block);
}
if(displayStatus && block.consumes.any()){
if(renderer.drawStatus && block.consumes.any()){
entity.drawStatus();
}
}

View File

@@ -17,6 +17,22 @@ import static mindustry.Vars.*;
public class Drawf{
public static void target(float x, float y, float rad, Color color){
target(x, y, rad, 1, color);
}
public static void target(float x, float y, float rad, float alpha, Color color){
Lines.stroke(3f);
Draw.color(Pal.gray, alpha);
Lines.poly(x, y, 4, rad, Time.time * 1.5f);
Lines.spikes(x, y, 3f/7f * rad, 6f/7f * rad, 4, Time.time * 1.5f);
Lines.stroke(1f);
Draw.color(color, alpha);
Lines.poly(x, y, 4, rad, Time.time * 1.5f);
Lines.spikes(x, y, 3f/7f * rad, 6f/7f * rad, 4, Time.time * 1.5f);
Draw.reset();
}
public static float text(){
float z = Draw.z();
if(renderer.pixelator.enabled()){
@@ -95,6 +111,12 @@ public class Drawf{
Draw.rect(region, x, y);
Draw.color();
}
public static void shadow(TextureRegion region, float x, float y, float width, float height, float rotation){
Draw.color(Pal.shadow);
Draw.rect(region, x, y, width, height, rotation);
Draw.color();
}
public static void liquid(TextureRegion region, float x, float y, float alpha, Color color, float rotation){
Draw.color(color, alpha);
@@ -205,9 +227,13 @@ public class Drawf{
}
public static void construct(float x, float y, TextureRegion region, float rotation, float progress, float speed, float time){
construct(x, y, region, Pal.accent, rotation, progress, speed, time);
}
public static void construct(float x, float y, TextureRegion region, Color color, float rotation, float progress, float speed, float time){
Shaders.build.region = region;
Shaders.build.progress = progress;
Shaders.build.color.set(Pal.accent);
Shaders.build.color.set(color);
Shaders.build.color.a = speed;
Shaders.build.time = -time / 20f;
@@ -219,9 +245,13 @@ public class Drawf{
}
public static void construct(Building t, TextureRegion region, float rotation, float progress, float speed, float time){
construct(t, region, Pal.accent, rotation, progress, speed, time);
}
public static void construct(Building t, TextureRegion region, Color color, float rotation, float progress, float speed, float time){
Shaders.build.region = region;
Shaders.build.progress = progress;
Shaders.build.color.set(Pal.accent);
Shaders.build.color.set(color);
Shaders.build.color.a = speed;
Shaders.build.time = -time / 20f;

View File

@@ -119,6 +119,7 @@ public class FloorRenderer implements Disposable{
return;
}
Draw.flush();
cbatch.setProjection(Core.camera.mat);
cbatch.beginDraw();

View File

@@ -54,7 +54,7 @@ public class LoadRenderer implements Disposable{
bars = new Bar[]{
new Bar("s_proc#", OS.cores / 16f, OS.cores < 4),
new Bar("c_aprog", () -> assets != null, () -> assets.getProgress(), () -> false),
new Bar("g_vtype", graphics.getGLVersion().type == Type.GLES ? 0.5f : 1f, graphics.getGLVersion().type == Type.GLES),
new Bar("g_vtype", graphics.getGLVersion().type == GlType.GLES ? 0.5f : 1f, graphics.getGLVersion().type == GlType.GLES),
new Bar("s_mem#", () -> true, () -> Core.app.getJavaHeap() / 1024f / 1024f / 200f, () -> Core.app.getJavaHeap() > 1024 * 1024 * 110),
new Bar("v_ver#", () -> Version.build != 0, () -> Version.build == -1 ? 0.3f : (Version.build - 103f) / 10f, () -> !Version.modifier.equals("release")),
new Bar("s_osv", OS.isWindows ? 0.35f : OS.isLinux ? 0.9f : OS.isMac ? 0.5f : 0.2f, OS.isMac),

View File

@@ -96,7 +96,8 @@ public class MinimapRenderer implements Disposable{
Draw.mixcol(unit.team().color, 1f);
float scale = Scl.scl(1f) / 2f * scaling * 32f;
Draw.rect(unit.type.icon(Cicon.full), x + rx, y + ry, scale, scale, unit.rotation() - 90);
var region = unit.type.icon(Cicon.full);
Draw.rect(region, x + rx, y + ry, scale, scale * (float)region.height / region.width, unit.rotation() - 90);
Draw.reset();
}

View File

@@ -19,6 +19,7 @@ public class OverlayRenderer{
private static final float indicatorLength = 14f;
private static final float spawnerMargin = tilesize*11f;
private static final Rect rect = new Rect();
private float buildFade, unitFade;
private Unit lastSelect;
@@ -88,8 +89,12 @@ public class OverlayRenderer{
Draw.rect(select.type.icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
}
Lines.stroke(unitFade);
Lines.square(select.x, select.y, select.hitSize() * 1.5f, Time.time * 2f);
for(int i = 0; i < 4; i++){
float rot = i * 90f + 45f + (-Time.time) % 360f;
float length = select.hitSize() * 1.5f + (unitFade * 2.5f);
Draw.rect("select-arrow", select.x + Angles.trnsx(rot, length), select.y + Angles.trnsy(rot, length), length / 1.9f, length / 1.9f, rot - 135f);
}
Draw.reset();
}

View File

@@ -15,10 +15,10 @@ import mindustry.type.*;
import static mindustry.Vars.*;
public class Shaders{
public static BlockBuild blockbuild;
public static BlockBuildShader blockbuild;
public static @Nullable ShieldShader shield;
public static BuildBeamShader buildBeam;
public static UnitBuild build;
public static UnitBuildShader build;
public static DarknessShader darkness;
public static LightShader light;
public static SurfaceShader water, mud, tar, slag, space;
@@ -31,7 +31,7 @@ public class Shaders{
public static void init(){
mesh = new MeshShader();
blockbuild = new BlockBuild();
blockbuild = new BlockBuildShader();
try{
shield = new ShieldShader();
}catch(Throwable t){
@@ -40,7 +40,7 @@ public class Shaders{
t.printStackTrace();
}
buildBeam = new BuildBeamShader();
build = new UnitBuild();
build = new UnitBuildShader();
darkness = new DarknessShader();
light = new LightShader();
water = new SurfaceShader("water");
@@ -142,12 +142,16 @@ public class Shaders{
}
}
public static class UnitBuild extends LoadShader{
/** @deprecated transition class for mods; use UnitBuildShader instead. */
@Deprecated
public static class UnitBuild extends UnitBuildShader{}
public static class UnitBuildShader extends LoadShader{
public float progress, time;
public Color color = new Color();
public TextureRegion region;
public UnitBuild(){
public UnitBuildShader(){
super("unitbuild", "default");
}
@@ -162,11 +166,11 @@ public class Shaders{
}
}
public static class BlockBuild extends LoadShader{
public static class BlockBuildShader extends LoadShader{
public float progress;
public TextureRegion region = new TextureRegion();
public BlockBuild(){
public BlockBuildShader(){
super("blockbuild", "default");
}

View File

@@ -3,11 +3,11 @@ package mindustry.input;
import arc.*;
import arc.Graphics.*;
import arc.Graphics.Cursor.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
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.util.*;
@@ -44,35 +44,41 @@ public class DesktopInput extends InputHandler{
public boolean deleting = false, shouldShoot = false, panning = false;
/** Mouse pan speed. */
public float panScale = 0.005f, panSpeed = 4.5f, panBoostSpeed = 11f;
/** Delta time between consecutive clicks. */
public long selectMillis = 0;
/** Previously selected tile. */
public Tile prevSelected;
boolean showHint(){
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectRequests.isEmpty() &&
(!isBuilding && !Core.settings.getBool("buildautopause") || player.unit().isBuilding() || !player.dead() && !player.unit().spawnedByCore());
}
@Override
public void buildUI(Group group){
//respawn hints
//building and respawn hints
group.fill(t -> {
t.visible(() -> Core.settings.getBool("hints") && ui.hudfrag.shown && !player.dead() && !player.unit().spawnedByCore() && !(Core.settings.getBool("hints") && lastSchematic != null && !selectRequests.isEmpty()));
t.color.a = 0f;
t.visible(() -> (t.color.a = Mathf.lerpDelta(t.color.a, Mathf.num(showHint()), 0.15f)) > 0.001f);
t.bottom();
t.table(Styles.black6, b -> {
StringBuilder str = new StringBuilder();
b.defaults().left();
b.label(() -> Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString())).style(Styles.outlineLabel);
}).margin(6f);
});
//building hints
group.fill(t -> {
t.bottom();
t.visible(() -> {
t.color.a = Mathf.lerpDelta(t.color.a, player.unit().isBuilding() ? 1f : 0f, 0.15f);
return ui.hudfrag.shown && Core.settings.getBool("hints") && selectRequests.isEmpty() && t.color.a > 0.01f;
});
t.touchable(() -> t.color.a < 0.1f ? Touchable.disabled : Touchable.childrenOnly);
t.table(Styles.black6, b -> {
b.defaults().left();
b.label(() -> Core.bundle.format(!isBuilding ? "resumebuilding" : "pausebuilding", Core.keybinds.get(Binding.pause_building).key.toString())).style(Styles.outlineLabel);
b.row();
b.label(() -> Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString())).style(Styles.outlineLabel);
b.row();
b.label(() -> Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString())).style(Styles.outlineLabel);
b.label(() -> {
if(!showHint()) return str;
str.setLength(0);
if(!isBuilding && !Core.settings.getBool("buildautopause") && !player.unit().isBuilding()){
str.append(Core.bundle.format("enablebuilding", Core.keybinds.get(Binding.pause_building).key.toString()));
}else if(player.unit().isBuilding()){
str.append(Core.bundle.format(isBuilding ? "pausebuilding" : "resumebuilding", Core.keybinds.get(Binding.pause_building).key.toString()))
.append("\n").append(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.toString()))
.append("\n").append(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.toString()));
}
if(!player.dead() && !player.unit().spawnedByCore()){
str.append(str.length() != 0 ? "\n" : "").append(Core.bundle.format("respawn", Core.keybinds.get(Binding.respawn).key.toString()));
}
return str;
}).style(Styles.outlineLabel);
}).margin(10f);
});
@@ -160,14 +166,17 @@ public class DesktopInput extends InputHandler{
drawArrow(block, cursorX, cursorY, rotation);
}
Draw.color();
boolean valid = validPlace(cursorX, cursorY, block, rotation);
drawRequest(cursorX, cursorY, block, rotation);
block.drawPlace(cursorX, cursorY, rotation, validPlace(cursorX, cursorY, block, rotation));
block.drawPlace(cursorX, cursorY, rotation, valid);
if(block.saveConfig && block.lastConfig != null){
if(block.saveConfig){
Draw.mixcol(!valid ? Pal.breakInvalid : Color.white, (!valid ? 0.4f : 0.24f) + Mathf.absin(Time.globalTime, 6f, 0.28f));
brequest.set(cursorX, cursorY, rotation, block);
brequest.config = block.lastConfig;
block.drawRequestConfig(brequest, allRequests());
brequest.config = null;
Draw.reset();
}
}
}
@@ -200,7 +209,6 @@ public class DesktopInput extends InputHandler{
if(input.keyDown(Binding.mouse_move)){
panCam = true;
}
panning = false;
Core.camera.position.add(Tmp.v1.setZero().add(Core.input.axis(Binding.move_x), Core.input.axis(Binding.move_y)).nor().scl(camSpeed));
}else if(!player.dead() && !panning){
@@ -247,7 +255,7 @@ public class DesktopInput extends InputHandler{
//zoom camera
if((!Core.scene.hasScroll() || Core.input.keyDown(Binding.diagonal_placement)) && !ui.chatfrag.shown() && Math.abs(Core.input.axisTap(Binding.zoom)) > 0
&& !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
&& !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!player.isBuilder() || !isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
@@ -370,7 +378,7 @@ public class DesktopInput extends InputHandler{
int cursorY = tileY(Core.input.mouseY());
int rawCursorX = World.toTile(Core.input.mouseWorld().x), rawCursorY = World.toTile(Core.input.mouseWorld().y);
// automatically pause building if the current build queue is empty
//automatically pause building if the current build queue is empty
if(Core.settings.getBool("buildautopause") && isBuilding && !player.unit().isBuilding()){
isBuilding = false;
buildWasAutoPaused = true;
@@ -485,13 +493,15 @@ public class DesktopInput extends InputHandler{
deleting = true;
}else if(selected != null){
//only begin shooting if there's no cursor event
if(!tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !tileTapped(selected.build) && !player.unit().activelyBuilding() && !droppingItem &&
!tryBeginMine(selected) && player.unit().mineTile == null && !Core.scene.hasKeyboard()){
if(!tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && !tileTapped(selected.build) && !player.unit().activelyBuilding() && !droppingItem
&& !(tryStopMine(selected) || (!settings.getBool("doubletapmine") || selected == prevSelected && Time.timeSinceMillis(selectMillis) < 500) && tryBeginMine(selected)) && !Core.scene.hasKeyboard()){
player.shooting = shouldShoot;
}
}else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine
player.shooting = shouldShoot;
}
selectMillis = Time.millis();
prevSelected = selected;
}else if(Core.input.keyTap(Binding.deselect) && isPlacing()){
block = null;
mode = none;

View File

@@ -30,11 +30,10 @@ import mindustry.net.*;
import mindustry.type.*;
import mindustry.ui.fragments.*;
import mindustry.world.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.ConstructBlock.*;
import mindustry.world.blocks.*;
import mindustry.world.blocks.distribution.*;
import mindustry.world.blocks.payloads.*;
import mindustry.world.blocks.power.*;
import mindustry.world.blocks.storage.CoreBlock.*;
import mindustry.world.meta.*;
@@ -68,6 +67,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public Seq<BuildPlan> lineRequests = new Seq<>();
public Seq<BuildPlan> selectRequests = new Seq<>();
public InputHandler(){
Events.on(UnitDestroyEvent.class, e -> {
if(e.unit != null && e.unit.isPlayer() && e.unit.getPlayer().isLocal() && e.unit.type.weapons.contains(w -> w.bullet.killShooter)){
player.shooting = false;
}
});
}
//methods to override
@Remote(called = Loc.server, unreliable = true)
@@ -129,11 +136,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(called = Loc.server, targets = Loc.both, forward = true)
public static void requestItem(Player player, Building tile, Item item, int amount){
if(player == null || tile == null || !tile.interactable(player.team()) || !player.within(tile, buildingRange) || player.dead()) return;
public static void requestItem(Player player, Building build, Item item, int amount){
if(player == null || build == null || !build.interactable(player.team()) || !player.within(build, buildingRange) || player.dead()) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, tile.tile(), action -> {
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.withdrawItem, build.tile(), action -> {
action.item = item;
action.itemAmount = amount;
}))){
@@ -142,20 +149,20 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//remove item for every controlling unit
player.unit().eachGroup(unit -> {
Call.takeItems(tile, item, unit.maxAccepted(item), unit);
Call.takeItems(build, item, unit.maxAccepted(item), unit);
if(unit == player.unit()){
Events.fire(new WithdrawEvent(tile, player, item, amount));
Events.fire(new WithdrawEvent(build, player, item, amount));
}
});
}
@Remote(targets = Loc.both, forward = true, called = Loc.server)
public static void transferInventory(Player player, Building tile){
if(player == null || tile == null || !player.within(tile, buildingRange) || tile.items == null || player.dead()) return;
public static void transferInventory(Player player, Building build){
if(player == null || build == null || !player.within(build, buildingRange) || build.items == null || player.dead()) return;
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.depositItem, tile.tile, action -> {
if(net.server() && (player.unit().stack.amount <= 0 || !Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.depositItem, build.tile, action -> {
action.itemAmount = player.unit().stack.amount;
action.item = player.unit().item();
}))){
@@ -165,12 +172,12 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
//deposit for every controlling unit
player.unit().eachGroup(unit -> {
Item item = unit.item();
int accepted = tile.acceptStack(item, unit.stack.amount, unit);
int accepted = build.acceptStack(item, unit.stack.amount, unit);
Call.transferItemTo(unit, item, accepted, unit.x, unit.y, tile);
Call.transferItemTo(unit, item, accepted, unit.x, unit.y, build);
if(unit == player.unit()){
Events.fire(new DepositEvent(tile, player, item, accepted));
Events.fire(new DepositEvent(build, player, item, accepted));
}
});
}
@@ -194,22 +201,22 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(targets = Loc.both, called = Loc.server)
public static void requestBuildPayload(Player player, Building tile){
public static void requestBuildPayload(Player player, Building build){
if(player == null) return;
Unit unit = player.unit();
Payloadc pay = (Payloadc)unit;
if(tile != null && tile.team == unit.team
&& unit.within(tile, tilesize * tile.block.size * 1.2f + tilesize * 5f)){
if(build != null && build.team == unit.team
&& unit.within(build, tilesize * build.block.size * 1.2f + tilesize * 5f)){
//pick up block's payload
Payload current = tile.getPayload();
Payload current = build.getPayload();
if(current != null && pay.canPickupPayload(current)){
Call.pickedBuildPayload(unit, tile, false);
Call.pickedBuildPayload(unit, build, false);
//pick up whole building directly
}else if(tile.block.buildVisibility != BuildVisibility.hidden && tile.canPickup() && pay.canPickup(tile)){
Call.pickedBuildPayload(unit, tile, true);
}else if(build.block.buildVisibility != BuildVisibility.hidden && build.canPickup() && pay.canPickup(build)){
Call.pickedBuildPayload(unit, build, true);
}
}
}
@@ -224,29 +231,29 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(targets = Loc.server, called = Loc.server)
public static void pickedBuildPayload(Unit unit, Building tile, boolean onGround){
if(tile != null && unit instanceof Payloadc pay){
public static void pickedBuildPayload(Unit unit, Building build, boolean onGround){
if(build != null && unit instanceof Payloadc pay){
if(onGround){
if(tile.block.buildVisibility != BuildVisibility.hidden && tile.canPickup() && pay.canPickup(tile)){
pay.pickup(tile);
if(build.block.buildVisibility != BuildVisibility.hidden && build.canPickup() && pay.canPickup(build)){
pay.pickup(build);
}else{
Fx.unitPickup.at(tile);
tile.tile.remove();
Fx.unitPickup.at(build);
build.tile.remove();
}
}else{
Payload current = tile.getPayload();
Payload current = build.getPayload();
if(current != null && pay.canPickupPayload(current)){
Payload taken = tile.takePayload();
Payload taken = build.takePayload();
if(taken != null){
pay.addPayload(taken);
Fx.unitPickup.at(tile);
Fx.unitPickup.at(build);
}
}
}
}else if(tile != null && onGround){
Fx.unitPickup.at(tile);
tile.tile.remove();
}else if(build != null && onGround){
Fx.unitPickup.at(build);
build.tile.remove();
}
}
@@ -293,27 +300,28 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
@Remote(targets = Loc.both, called = Loc.server, forward = true, unreliable = true)
public static void rotateBlock(@Nullable Player player, Building tile, boolean direction){
if(tile == null) return;
public static void rotateBlock(@Nullable Player player, Building build, boolean direction){
if(build == null) return;
if(net.server() && (!Units.canInteract(player, tile) ||
!netServer.admins.allowAction(player, ActionType.rotate, tile.tile(), action -> action.rotation = Mathf.mod(tile.rotation + Mathf.sign(direction), 4)))){
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.rotate, build.tile(), action -> action.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4)))){
throw new ValidateException(player, "Player cannot rotate a block.");
}
if(player != null) tile.lastAccessed = player.name;
tile.rotation = Mathf.mod(tile.rotation + Mathf.sign(direction), 4);
tile.updateProximity();
tile.noSleep();
if(player != null) build.lastAccessed = player.name;
build.rotation = Mathf.mod(build.rotation + Mathf.sign(direction), 4);
build.updateProximity();
build.noSleep();
Fx.rotateBlock.at(build.x, build.y, build.block.size);
}
@Remote(targets = Loc.both, called = Loc.both, forward = true)
public static void tileConfig(@Nullable 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 == null || player.dead() ? null : player.unit(), value);
Core.app.post(() -> Events.fire(new ConfigEvent(tile, player, value)));
public static void tileConfig(@Nullable Player player, Building build, @Nullable Object value){
if(build == null) return;
if(net.server() && (!Units.canInteract(player, build) ||
!netServer.admins.allowAction(player, ActionType.configure, build.tile, action -> action.config = value))) throw new ValidateException(player, "Player cannot configure a tile.");
build.configured(player == null || player.dead() ? null : player.unit(), value);
Core.app.post(() -> Events.fire(new ConfigEvent(build, player, value)));
}
//only useful for servers or local mods, and is not replicated across clients
@@ -342,7 +350,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
player.clearUnit();
player.deathTimer = 61f;
player.deathTimer = Player.deathDelay + 1f;
build.requestSpawn(player);
}else if(unit == null){ //just clear the unit (is this used?)
player.clearUnit();
@@ -368,7 +376,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Fx.spawn.at(player);
player.clearUnit();
player.deathTimer = 61f; //for instant respawn
player.deathTimer = Player.deathDelay + 1f; //for instant respawn
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
@@ -461,10 +469,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(target != null){
Call.requestUnitPayload(player, target);
}else{
Building tile = world.buildWorld(pay.x(), pay.y());
Building build = world.buildWorld(pay.x(), pay.y());
if(tile != null && tile.team == unit.team){
Call.requestBuildPayload(player, tile);
if(build != null && build.team == unit.team){
Call.requestBuildPayload(player, build);
}
}
}
@@ -762,7 +770,10 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Draw.reset();
Draw.mixcol(!valid ? Pal.breakInvalid : Color.white, (!valid ? 0.4f : 0.24f) + Mathf.absin(Time.globalTime, 6f, 0.28f));
Draw.alpha(1f);
request.block.drawRequestConfigTop(request, selectRequests);
request.block.drawRequestConfigTop(request, cons -> {
selectRequests.each(cons);
lineRequests.each(cons);
});
Draw.reset();
}
@@ -858,6 +869,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
req.block = replace;
}
});
block.handlePlacementLine(lineRequests);
}
}
@@ -866,8 +879,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
/** Handles tile tap events that are not platform specific. */
boolean tileTapped(@Nullable Building tile){
if(tile == null){
boolean tileTapped(@Nullable Building build){
if(build == null){
frag.inv.hide();
frag.config.hideConfig();
return false;
@@ -875,18 +888,18 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
boolean consumed = false, showedInventory = false;
//check if tapped block is configurable
if(tile.block.configurable && tile.interactable(player.team())){
if(build.block.configurable && build.interactable(player.team())){
consumed = true;
if(((!frag.config.isShown() && tile.shouldShowConfigure(player)) //if the config fragment is hidden, show
if(((!frag.config.isShown() && build.shouldShowConfigure(player)) //if the config fragment is hidden, show
//alternatively, the current selected block can 'agree' to switch config tiles
|| (frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(tile)))){
Sounds.click.at(tile);
frag.config.showConfig(tile);
|| (frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(build)))){
Sounds.click.at(build);
frag.config.showConfig(build);
}
//otherwise...
}else if(!frag.config.hasConfigMouse()){ //make sure a configuration fragment isn't on the cursor
//then, if it's shown and the current block 'agrees' to hide, hide it.
if(frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(tile)){
if(frag.config.isShown() && frag.config.getSelectedTile().onConfigureTileTapped(build)){
consumed = true;
frag.config.hideConfig();
}
@@ -897,16 +910,16 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
//call tapped event
if(!consumed && tile.interactable(player.team())){
tile.tapped();
if(!consumed && build.interactable(player.team())){
build.tapped();
}
//consume tap event if necessary
if(tile.interactable(player.team()) && tile.block.consumesTap){
if(build.interactable(player.team()) && build.block.consumesTap){
consumed = true;
}else if(tile.interactable(player.team()) && tile.block.synthetic() && !consumed){
if(tile.block.hasItems && tile.items.total() > 0){
frag.inv.showFor(tile);
}else if(build.interactable(player.team()) && build.block.synthetic() && !consumed){
if(build.block.hasItems && build.items.total() > 0){
frag.inv.showFor(build);
consumed = true;
showedInventory = true;
}
@@ -935,8 +948,24 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
/** Tries to begin mining a tile, returns true if successful. */
boolean tryBeginMine(Tile tile){
if(canMine(tile)){
//if a block is clicked twice, reset it
player.unit().mineTile = player.unit().mineTile == tile ? null : tile;
player.unit().mineTile = tile;
return true;
}
return false;
}
/** Tries to stop mining, returns true if mining was stopped. */
boolean tryStopMine(){
if(player.unit().mining()){
player.unit().mineTile = null;
return true;
}
return false;
}
boolean tryStopMine(Tile tile){
if(player.unit().mineTile == tile){
player.unit().mineTile = null;
return true;
}
return false;
@@ -1006,8 +1035,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
Building tile = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
if(tile instanceof ControlBlock cont && cont.canControl() && tile.team == player.team()){
Building build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
if(build instanceof ControlBlock cont && cont.canControl() && build.team == player.team()){
return cont.unit();
}
@@ -1071,7 +1100,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return droppingItem && !canTapPlayer(Core.input.mouseWorldX(), Core.input.mouseWorldY());
}
public void tryDropItems(@Nullable Building tile, float x, float y){
public void tryDropItems(@Nullable Building build, float x, float y){
if(!droppingItem || player.unit().stack.amount <= 0 || canTapPlayer(x, y) || state.isPaused() ){
droppingItem = false;
return;
@@ -1081,8 +1110,8 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
ItemStack stack = player.unit().stack;
if(tile != null && tile.acceptStack(stack.item, stack.amount, player.unit()) > 0 && tile.interactable(player.team()) && tile.block.hasItems && player.unit().stack().amount > 0 && tile.interactable(player.team())){
Call.transferInventory(player, tile);
if(build != null && build.acceptStack(stack.item, stack.amount, player.unit()) > 0 && build.interactable(player.team()) && build.block.hasItems && player.unit().stack().amount > 0 && build.interactable(player.team())){
Call.transferInventory(player, build);
}else{
Call.dropItem(player.angleTo(x, y));
}

View File

@@ -71,6 +71,8 @@ public class MobileInput extends InputHandler implements GestureListener{
public Teamc target;
/** Payload target being moved to. Can be a position (for dropping), or a unit/block. */
public Position payloadTarget;
/** Unit last tapped, or null if last tap was not on a unit. */
public Unit unitTapped;
//region utility methods
@@ -314,6 +316,7 @@ public class MobileInput extends InputHandler implements GestureListener{
request.block.drawPlan(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation) && getRequest(request.x, request.y, request.block.size, null) == null);
drawSelected(request.x, request.y, request.block, Pal.accent);
}
lineRequests.each(this::drawOverRequest);
}else if(mode == breaking){
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
}
@@ -374,13 +377,7 @@ public class MobileInput extends InputHandler implements GestureListener{
crosshairScale = Mathf.lerpDelta(crosshairScale, 1f, 0.2f);
Draw.color(Pal.remove);
Lines.stroke(1f);
float radius = Interp.swingIn.apply(crosshairScale);
Lines.poly(target.getX(), target.getY(), 4, 7f * radius, Time.time * 1.5f);
Lines.spikes(target.getX(), target.getY(), 3f * radius, 6f * radius, 4, Time.time * 1.5f);
Drawf.target(target.getX(), target.getY(), 7f * Interp.swingIn.apply(crosshairScale), Pal.remove);
}
Draw.reset();
@@ -429,7 +426,7 @@ public class MobileInput extends InputHandler implements GestureListener{
@Override
public void useSchematic(Schematic schem){
selectRequests.clear();
selectRequests.addAll(schematics.toRequests(schem, player.tileX(), player.tileY()));
selectRequests.addAll(schematics.toRequests(schem, World.toTile(Core.camera.position.x), World.toTile(Core.camera.position.y)));
lastSchematic = schem;
}
@@ -604,11 +601,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//add to selection queue if it's a valid BREAK position
selectRequests.add(new BuildPlan(linked.x, linked.y));
}else{
if(!canTapPlayer(worldx, worldy) && !tileTapped(linked.build)){
tryBeginMine(cursor);
}
//control units.
//control units
if(count == 2){
//reset payload target
payloadTarget = null;
@@ -616,12 +609,20 @@ public class MobileInput extends InputHandler implements GestureListener{
if(!player.dead() && Mathf.within(worldx, worldy, player.unit().x, player.unit().y, player.unit().hitSize * 0.6f + 8f) && player.unit().type.commandLimit > 0){
Call.unitCommand(player);
}else{
//control a unit/block
Unit on = selectedUnit();
if(on != null){
Call.unitControl(player, on);
//control a unit/block detected on first tap of double-tap
if(unitTapped != null){
Call.unitControl(player, unitTapped);
}else if(!tryBeginMine(cursor)){
tileTapped(linked.build);
}
}
return false;
}
unitTapped = selectedUnit();
//prevent mining if placing/breaking blocks
if(!tryStopMine() && !canTapPlayer(worldx, worldy) && !tileTapped(linked.build) && mode == none && !Core.settings.getBool("doubletapmine")){
tryBeginMine(cursor);
}
}
@@ -652,7 +653,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//zoom camera
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && !Core.input.keyDown(Binding.rotateplaced) && (Core.input.keyDown(Binding.diagonal_placement) || ((!player.isBuilder() || !isPlacing() || !block.rotate) && selectRequests.isEmpty()))){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
@@ -721,7 +722,7 @@ public class MobileInput extends InputHandler implements GestureListener{
//When in line mode, pan when near screen edges automatically
if(Core.input.isTouched(0)){
autoPan();
autoPan();
}
int lx = tileX(Core.input.mouseX()), ly = tileY(Core.input.mouseY());

View File

@@ -6,22 +6,24 @@ import arc.math.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.pooling.*;
import mindustry.entities.units.*;
import mindustry.world.*;
import mindustry.world.blocks.distribution.*;
import static mindustry.Vars.*;
public class Placement{
private final static Seq<Point2> tmpPoints = new Seq<>(), tmpPoints2 = new Seq<>();
private static final Seq<BuildPlan> plans1 = new Seq<>();
private static final Seq<Point2> tmpPoints = new Seq<>(), tmpPoints2 = new Seq<>();
private static final NormalizeResult result = new NormalizeResult();
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
private static Bresenham2 bres = new Bresenham2();
private static Seq<Point2> points = new Seq<>();
private static final Bresenham2 bres = new Bresenham2();
private static final Seq<Point2> points = new Seq<>();
//for pathfinding
private static IntFloatMap costs = new IntFloatMap();
private static IntIntMap parents = new IntIntMap();
private static IntSet closed = new IntSet();
private static final IntFloatMap costs = new IntFloatMap();
private static final IntIntMap parents = new IntIntMap();
private static final IntSet closed = new IntSet();
/** Normalize a diagonal line into points. */
public static Seq<Point2> pathfindLine(boolean conveyors, int startX, int startY, int endX, int endY){
@@ -75,7 +77,7 @@ public class Placement{
var base = tmpPoints2;
var result = tmpPoints.clear();
base.selectFrom(points, p -> p == points.first() || p == points.peek() || Build.validPlace(block, player.team(), p.x, p.y, rotation, false));
base.selectFrom(points, p -> p == points.first() || p == points.peek() || Build.validPlace(block, player.team(), p.x, p.y, rotation));
boolean addedLast = false;
outer:
@@ -100,12 +102,73 @@ public class Placement{
i ++;
}
if(!addedLast) result.add(base.peek());
if(!addedLast && !base.isEmpty()) result.add(base.peek());
points.clear();
points.addAll(result);
}
public static void calculateBridges(Seq<BuildPlan> plans, ItemBridge bridge){
//check for orthogonal placement + unlocked state
if(!(plans.first().x == plans.peek().x || plans.first().y == plans.peek().y) || !bridge.unlockedNow()){
return;
}
Boolf<BuildPlan> placeable = plan -> (plan.placeable(player.team())) ||
(plan.tile() != null && plan.tile().block() == plan.block); //don't count the same block as inaccessible
var result = plans1.clear();
var team = player.team();
var rotated = plans.first().tile() != null && plans.first().tile().absoluteRelativeTo(plans.peek().x, plans.peek().y) == Mathf.mod(plans.first().rotation + 2, 4);
outer:
for(int i = 0; i < plans.size;){
var cur = plans.get(i);
result.add(cur);
//gap found
if(i < plans.size - 1 && placeable.get(cur) && !placeable.get(plans.get(i + 1))){
//find the closest valid position within range
for(int j = i + 1; j < plans.size; j++){
var other = plans.get(j);
//out of range now, set to current position and keep scanning forward for next occurrence
if(!bridge.positionsValid(cur.x, cur.y, other.x, other.y)){
//add 'missed' conveyors
for(int k = i + 1; k < j; k++){
result.add(plans.get(k));
}
i = j;
continue outer;
}else if(other.placeable(team)){
//found a link, assign bridges
cur.block = bridge;
other.block = bridge;
if(rotated){
other.config = new Point2(cur.x - other.x, cur.y - other.y);
}else{
cur.config = new Point2(other.x - cur.x, other.y - cur.y);
}
i = j;
continue outer;
}
}
//if it got here, that means nothing was found. this likely means there's a bunch of stuff at the end; add it and bail out
for(int j = i + 1; j < plans.size; j++){
result.add(plans.get(j));
}
break;
}else{
i ++;
}
}
plans.set(result);
}
private static float tileHeuristic(Tile tile, Tile other){
Block block = control.input.block;

View File

@@ -13,8 +13,9 @@ import java.io.*;
@SuppressWarnings("unchecked")
public class JsonIO{
private static CustomJson jsonBase = new CustomJson();
private static Json json = new Json(){
private static final CustomJson jsonBase = new CustomJson();
public static final Json json = new Json(){
{ apply(this); }
@Override
@@ -39,10 +40,6 @@ public class JsonIO{
}
};
public static Json json(){
return json;
}
public static String write(Object object){
return json.toJson(object, object.getClass());
}
@@ -69,7 +66,6 @@ public class JsonIO{
}
static void apply(Json json){
json.setIgnoreUnknownFields(true);
json.setElementType(Rules.class, "spawns", SpawnGroup.class);
json.setElementType(Rules.class, "loadout", ItemStack.class);

View File

@@ -11,6 +11,7 @@ import mindustry.core.*;
import mindustry.game.*;
import mindustry.maps.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.blocks.storage.*;
import java.io.*;
@@ -199,11 +200,16 @@ public class MapIO{
for(Point2 p : Geometry.d4){
Tile other = tiles.get(tile.x + p.x, tile.y + p.y);
if(other != null && other.floor() != Blocks.air){
tile.setFloor(other.floor());
tile.setFloorUnder(other.floor());
break;
}
}
}
//default to stone floor
if(tile.floor() == Blocks.air){
tile.setFloorUnder((Floor)Blocks.stone);
}
}
}
}

View File

@@ -92,6 +92,9 @@ public class TypeIO{
}else if(object instanceof UnitCommand c){
write.b((byte)15);
write.b(c.ordinal());
}else if(object instanceof BuildingBox b){
write.b(12);
write.i(b.pos);
}else{
throw new IllegalArgumentException("Unknown object type: " + object.getClass());
}
@@ -99,6 +102,12 @@ public class TypeIO{
@Nullable
public static Object readObject(Reads read){
return readObjectBoxed(read, false);
}
/** Reads an object, but boxes buildings. */
@Nullable
public static Object readObjectBoxed(Reads read, boolean box){
byte type = read.b();
switch(type){
case 0: return null;
@@ -113,7 +122,7 @@ public class TypeIO{
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 12: return !box ? world.build(read.i()) : new BuildingBox(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;
case 15: return UnitCommand.all[read.b()];
@@ -572,10 +581,12 @@ public class TypeIO{
writeString(write, trace.uuid);
write.b(trace.modded ? (byte)1 : 0);
write.b(trace.mobile ? (byte)1 : 0);
write.i(trace.timesJoined);
write.i(trace.timesKicked);
}
public static TraceInfo readTraceInfo(Reads read){
return new TraceInfo(readString(read), readString(read), read.b() == 1, read.b() == 1);
return new TraceInfo(readString(read), readString(read), read.b() == 1, read.b() == 1, read.i(), read.i());
}
public static void writeStringData(DataOutput buffer, String string) throws IOException{
@@ -598,4 +609,13 @@ public class TypeIO{
return null;
}
}
/** Representes a building that has not been resolved yet. */
public static class BuildingBox{
public int pos;
public BuildingBox(int pos){
this.pos = pos;
}
}
}

View File

@@ -1,12 +1,15 @@
package mindustry.logic;
import arc.util.*;
public enum ConditionOp{
equal("==", (a, b) -> Math.abs(a - b) < 0.000001, (a, b) -> a == b),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001, (a, b) -> a != b),
equal("==", (a, b) -> Math.abs(a - b) < 0.000001, Structs::eq),
notEqual("not", (a, b) -> Math.abs(a - b) >= 0.000001, (a, b) -> !Structs.eq(a, b)),
lessThan("<", (a, b) -> a < b),
lessThanEq("<=", (a, b) -> a <= b),
greaterThan(">", (a, b) -> a > b),
greaterThanEq(">=", (a, b) -> a >= b),
strictEqual("===", (a, b) -> false),
always("always", (a, b) -> true);
public static final ConditionOp[] all = values();

View File

@@ -9,6 +9,8 @@ import mindustry.world.*;
/** Stores global constants for logic processors. */
public class GlobalConstants{
public static final int ctrlProcessor = 1, ctrlPlayer = 2, ctrlFormation = 3;
private ObjectIntMap<String> namesToIds = new ObjectIntMap<>();
private Seq<Var> vars = new Seq<>(Var.class);
@@ -19,6 +21,12 @@ public class GlobalConstants{
put("true", 1);
put("null", null);
//special enums
put("@ctrlProcessor", ctrlProcessor);
put("@ctrlPlayer", ctrlPlayer);
put("@ctrlFormation", ctrlFormation);
//store base content
for(Item item : Vars.content.items()){

View File

@@ -21,12 +21,17 @@ public enum LAccess{
maxHealth,
heat,
efficiency,
timescale,
rotation,
x,
y,
shootX,
shootY,
size,
dead,
range,
shooting,
boosting,
mineX,
mineY,
mining,
@@ -34,6 +39,7 @@ public enum LAccess{
type,
flag,
controlled,
controller,
commanded,
name,
config,
@@ -44,7 +50,8 @@ public enum LAccess{
enabled("to"), //"to" is standard for single parameter access
shoot("x", "y", "shoot"),
shootp(true, "unit", "shoot"),
configure(true, 30, "to");
configure(true, 30, "to"),
color("r", "g", "b");
public final String[] params;
public final boolean isObj;

View File

@@ -13,6 +13,11 @@ public class LAssembler{
public static ObjectMap<String, Func<String[], LStatement>> customParsers = new ObjectMap<>();
public static final int maxTokenLength = 36;
private static final StringMap opNameChanges = StringMap.of(
"atan2", "angle",
"dst", "len"
);
private int lastVar;
/** Maps names to variable IDs. */
public ObjectMap<String, BVar> vars = new ObjectMap<>();
@@ -28,6 +33,8 @@ public class LAssembler{
putConst("@unit", null);
//reference to self
putConst("@this", null);
//global tick
putConst("@tick", 0);
}
public static LAssembler assemble(String data, int maxInstructions){
@@ -61,9 +68,6 @@ public class LAssembler{
String[] lines = data.split("\n");
int index = 0;
for(String line : lines){
//comments
int commentIdx = line.indexOf('#');
if(commentIdx != -1) line = line.substring(0, commentIdx).trim();
if(line.isEmpty()) continue;
//remove trailing semicolons in case someone adds them in for no reason
if(line.endsWith(";")) line = line.substring(0, line.length() - 1);
@@ -74,6 +78,7 @@ public class LAssembler{
try{
String[] arr;
if(line.startsWith("#")) continue;
//yes, I am aware that this can be split with regex, but that's slow and even more incomprehensible
if(line.contains(" ")){
@@ -83,7 +88,9 @@ public class LAssembler{
for(int i = 0; i < line.length() + 1; i++){
char c = i == line.length() ? ' ' : line.charAt(i);
if(c == '"'){
if(c == '#' && !inString){
break;
}else if(c == '"'){
inString = !inString;
}else if(c == ' ' && !inString){
tokens.add(line.substring(lastIdx, Math.min(i, lastIdx + maxTokenLength)));
@@ -96,6 +103,9 @@ public class LAssembler{
arr = new String[]{line};
}
//nothing found
if(arr.length == 0) continue;
String type = arr[0];
//legacy stuff
@@ -122,6 +132,11 @@ public class LAssembler{
}
}
//fix up changed operaiton names
if(type.equals("op")){
arr[1] = opNameChanges.get(arr[1], arr[1]);
}
LStatement st = LogicIO.read(arr);
if(st != null){

View File

@@ -13,6 +13,7 @@ import arc.scene.ui.*;
import arc.scene.ui.layout.*;
import arc.struct.*;
import arc.util.*;
import mindustry.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.ui.*;
@@ -29,10 +30,26 @@ public class LCanvas extends Table{
StatementElem hovered;
float targetWidth;
int jumpCount = 0;
Seq<Tooltip> tooltips = new Seq<>();
public LCanvas(){
canvas = this;
Core.scene.addListener(new InputListener(){
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, KeyCode button){
//hide tooltips on tap
for(var t : tooltips){
t.container.toFront();
}
Core.app.post(() -> {
tooltips.each(Tooltip::hide);
tooltips.clear();
});
return super.touchDown(event, x, y, pointer, button);
}
});
rebuild();
}
@@ -41,6 +58,43 @@ public class LCanvas extends Table{
return Core.graphics.getWidth() < Scl.scl(900f) * 1.2f;
}
public static void tooltip(Cell<?> cell, String key){
String lkey = key.toLowerCase().replace(" ", "");
if(Core.settings.getBool("logichints", true) && Core.bundle.has(lkey)){
var tip = new Tooltip(t -> t.background(Styles.black8).margin(4f).add("[lightgray]" + Core.bundle.get(lkey)).style(Styles.outlineLabel));
//mobile devices need long-press tooltips
if(Vars.mobile){
cell.get().addListener(new ElementGestureListener(20, 0.4f, 0.43f, 0.15f){
@Override
public boolean longPress(Element element, float x, float y){
tip.show(element, x, y);
canvas.tooltips.add(tip);
//prevent touch down for other listeners
for(var list : cell.get().getListeners()){
if(list instanceof ClickListener cl){
cl.cancel();
}
}
return true;
}
});
}else{
cell.get().addListener(tip);
}
}
}
public static void tooltip(Cell<?> cell, Enum<?> key){
String cl = key.getClass().getSimpleName().toLowerCase() + "." + key.name().toLowerCase();
if(Core.bundle.has(cl)){
tooltip(cell, cl);
}else{
tooltip(cell, "lenum." + key.name());
}
}
public void rebuild(){
targetWidth = useRows() ? 400f : 900f;
float s = pane != null ? pane.getScrollPercentY() : 0f;
@@ -283,13 +337,13 @@ public class LCanvas extends Table{
t.add().growX();
t.button(Icon.copy, Styles.logici, () -> {
}).padRight(6).get().tapped(this::copy);
}).size(24f).padRight(6).get().tapped(this::copy);
t.button(Icon.cancel, Styles.logici, () -> {
remove();
dragging = null;
statements.layout();
});
}).size(24f);
t.addListener(new InputListener(){
float lastx, lasty;

View File

@@ -1,5 +1,6 @@
package mindustry.logic;
import arc.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
@@ -35,7 +36,8 @@ public class LExecutor{
varCounter = 0,
varTime = 1,
varUnit = 2,
varThis = 3;
varThis = 3,
varTick = 4;
public static final int
maxGraphicsBuffer = 256,
@@ -60,6 +62,7 @@ public class LExecutor{
public void runOnce(){
//set time
vars[varTime].numval = Time.millis();
vars[varTick].numval = Time.time;
//reset to start
if(vars[varCounter].numval >= instructions.length || vars[varCounter].numval < 0){
@@ -98,6 +101,10 @@ public class LExecutor{
//region utility
private static boolean invalid(double d){
return Double.isNaN(d) || Double.isInfinite(d);
}
public Var var(int index){
//global constants have variable IDs < 0, and they are fetched from the global constants object after being negated
return index < 0 ? constants.get(-index) : vars[index];
@@ -120,12 +127,12 @@ public class LExecutor{
public double num(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : v.numval;
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : v.numval;
}
public float numf(int index){
Var v = var(index);
return v.isobj ? v.objval != null ? 1 : 0 : Double.isNaN(v.numval) || Double.isInfinite(v.numval) ? 0 : (float)v.numval;
return v.isobj ? v.objval != null ? 1 : 0 : invalid(v.numval) ? 0 : (float)v.numval;
}
public int numi(int index){
@@ -139,9 +146,14 @@ public class LExecutor{
public void setnum(int index, double value){
Var v = var(index);
if(v.constant) return;
v.numval = Double.isNaN(value) || Double.isInfinite(value) ? 0 : value;
v.objval = null;
v.isobj = false;
if(invalid(value)){
v.objval = null;
v.isobj = true;
}else{
v.numval = value;
v.objval = null;
v.isobj = false;
}
}
public void setobj(int index, Object value){
@@ -326,17 +338,19 @@ public class LExecutor{
@Nullable
public static LogicAI checkLogicAI(LExecutor exec, Object unitObj){
if(unitObj instanceof Unit unit && exec.obj(varUnit) == unit && unit.team == exec.team && !unit.isPlayer() && !(unit.controller() instanceof FormationAI)){
if(!(unit.controller() instanceof LogicAI)){
unit.controller(new LogicAI());
((LogicAI)unit.controller()).controller = exec.building(varThis);
if(unit.controller() instanceof LogicAI la){
return la;
}else{
var la = new LogicAI();
la.controller = exec.building(varThis);
unit.controller(la);
//clear old state
unit.mineTile = null;
unit.clearBuilding();
return (LogicAI)unit.controller();
return la;
}
return (LogicAI)unit.controller();
}
return null;
}
@@ -433,8 +447,8 @@ public class LExecutor{
}
}
case build -> {
if(unit.canBuild() && exec.obj(p3) instanceof Block block){
int x = World.toTile(x1), y = World.toTile(y1);
if(state.rules.logicUnitBuild && unit.canBuild() && exec.obj(p3) instanceof Block block){
int x = World.toTile(x1 - block.offset/tilesize), y = World.toTile(y1 - block.offset/tilesize);
int rot = exec.numi(p4);
//reset state of last request when necessary
@@ -444,12 +458,14 @@ public class LExecutor{
ai.plan.stuck = false;
}
var conf = exec.obj(p5);
ai.plan.set(x, y, rot, block);
ai.plan.config = exec.obj(p5) instanceof Content c ? c : null;
ai.plan.config = conf instanceof Content c ? c : conf instanceof Building b ? b : null;
unit.clearBuilding();
Tile tile = ai.plan.tile();
if(ai.plan.tile() != null){
if(tile != null && !(tile.block() == block && tile.build != null && tile.build.rotation == rot)){
unit.updateBuilding = true;
unit.addBuild(ai.plan);
}
@@ -569,7 +585,7 @@ public class LExecutor{
int address = exec.numi(position);
Building from = exec.building(target);
if(from instanceof MemoryBuild mem){
if(from instanceof MemoryBuild mem && from.team == exec.team){
exec.setnum(output, address < 0 || address >= mem.memory.length ? 0 : mem.memory[address]);
}
@@ -593,7 +609,7 @@ public class LExecutor{
int address = exec.numi(position);
Building from = exec.building(target);
if(from instanceof MemoryBuild mem){
if(from instanceof MemoryBuild mem && from.team == exec.team){
if(address >= 0 && address < mem.memory.length){
mem.memory[address] = exec.num(value);
@@ -620,23 +636,28 @@ public class LExecutor{
Object target = exec.obj(from);
Object sense = exec.obj(type);
//TODO should remote enemy buildings be senseable?
if(target == null && sense == LAccess.dead){
exec.setnum(to, 1);
return;
}
//note that remote units/buildings can be sensed as well
if(target instanceof Senseable se){
if(sense instanceof Content){
exec.setnum(to, se.sense(((Content)sense)));
}else if(sense instanceof LAccess){
Object objOut = se.senseObject((LAccess)sense);
if(sense instanceof Content co){
exec.setnum(to, se.sense(co));
}else if(sense instanceof LAccess la){
Object objOut = se.senseObject(la);
if(objOut == Senseable.noSensed){
//numeric output
exec.setnum(to, se.sense((LAccess)sense));
exec.setnum(to, se.sense(la));
}else{
//object output
exec.setobj(to, objOut);
}
}
}else{
exec.setnum(to, 0);
exec.setobj(to, null);
}
}
}
@@ -752,7 +773,7 @@ public class LExecutor{
v.objval = f.objval;
v.isobj = true;
}else{
v.numval = Double.isNaN(f.numval) || Double.isInfinite(f.numval) ? 0 : f.numval;
v.numval = invalid(f.numval) ? 0 : f.numval;
v.isobj = false;
}
}
@@ -774,14 +795,17 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
if(op.unary){
if(op == LogicOp.strictEqual){
Var v = exec.var(a), v2 = exec.var(b);
exec.setnum(dest, v.isobj == v2.isobj && ((v.isobj && v.objval == v2.objval) || (!v.isobj && v.numval == v2.numval)) ? 1 : 0);
}else if(op.unary){
exec.setnum(dest, op.function1.get(exec.num(a)));
}else{
Var va = exec.var(a);
Var vb = exec.var(b);
if(op.objFunction2 != null && va.isobj && vb.isobj){
//use object function if provided, and one of the variables is an object
//use object function if both are objects
exec.setnum(dest, op.objFunction2.get(exec.obj(a), exec.obj(b)));
}else{
//otherwise use the numeric function
@@ -857,8 +881,7 @@ public class LExecutor{
//graphics on headless servers are useless.
if(Vars.headless) return;
Building build = exec.building(target);
if(build instanceof LogicDisplayBuild d){
if(exec.building(target) instanceof LogicDisplayBuild d && d.team == exec.team){
if(d.commands.size + exec.graphicsBuffer.size < maxDisplayBuffer){
for(int i = 0; i < exec.graphicsBuffer.size; i++){
d.commands.addLast(exec.graphicsBuffer.items[i]);
@@ -889,6 +912,7 @@ public class LExecutor{
String strValue =
v.objval == null ? "null" :
v.objval instanceof String s ? s :
v.objval == Blocks.stoneWall ? "solid" : //special alias
v.objval instanceof MappableContent content ? content.name :
v.objval instanceof Content ? "[content]" :
v.objval instanceof Building build ? build.block.name :
@@ -920,8 +944,7 @@ public class LExecutor{
@Override
public void run(LExecutor exec){
Building build = exec.building(target);
if(build instanceof MessageBuild d){
if(exec.building(target) instanceof MessageBuild d && d.team == exec.team){
d.message.setLength(0);
d.message.append(exec.textBuffer, 0, Math.min(exec.textBuffer.length(), maxTextBuffer));
@@ -952,8 +975,10 @@ public class LExecutor{
Var vb = exec.var(compare);
boolean cmp;
if(op.objFunction != null && (va.isobj || vb.isobj)){
//use object function if provided, and one of the variables is an object
if(op == ConditionOp.strictEqual){
cmp = va.isobj == vb.isobj && ((va.isobj && va.objval == vb.objval) || (!va.isobj && va.numval == vb.numval));
}else if(op.objFunction != null && va.isobj && vb.isobj){
//use object function if both are objects
cmp = op.objFunction.get(exec.obj(value), exec.obj(compare));
}else{
cmp = op.function.get(exec.num(value), exec.num(compare));
@@ -966,5 +991,37 @@ public class LExecutor{
}
}
public static class WaitI implements LInstruction{
public int value;
public float curTime;
public double wait;
public long frameId;
public WaitI(int value){
this.value = value;
}
public WaitI(){
}
@Override
public void run(LExecutor exec){
if(curTime >= exec.num(value)){
curTime = 0f;
}else{
//skip back to self.
exec.var(varCounter).numval --;
}
if(Core.graphics.getFrameId() != frameId){
curTime += Time.delta / 60f;
frameId = Core.graphics.getFrameId();
}
}
}
//endregion
}

View File

@@ -15,6 +15,8 @@ import mindustry.logic.LCanvas.*;
import mindustry.logic.LExecutor.*;
import mindustry.ui.*;
import static mindustry.logic.LCanvas.*;
/**
* A statement is an intermediate representation of an instruction, to be used mostly in UI.
* Contains all relevant variable information. */
@@ -38,13 +40,18 @@ public abstract class LStatement{
//protected methods are only for internal UI layout utilities
protected void param(Cell<Label> label){
String text = name() + "." + label.get().getText().toString().trim();
tooltip(label, text);
}
protected Cell<TextField> field(Table table, String value, Cons<String> setter){
return table.field(value, Styles.nodeField, setter)
.size(144f, 40f).pad(2f).color(table.color).maxTextLength(LAssembler.maxTokenLength).addInputDialog();
}
protected Cell<TextField> fields(Table table, String desc, String value, Cons<String> setter){
table.add(desc).padLeft(10).left();
table.add(desc).padLeft(10).left().self(this::param);;
return field(table, value, setter).width(85f).padRight(10).left();
}
@@ -58,29 +65,40 @@ public abstract class LStatement{
}
}
protected <T> void showSelect(Button b, T[] values, T current, Cons<T> getter, int cols, Cons<Cell> sizer){
protected <T extends Enum<T>> void showSelect(Button b, T[] values, T current, Cons<T> getter, int cols, Cons<Cell> sizer){
showSelectTable(b, (t, hide) -> {
ButtonGroup<Button> group = new ButtonGroup<>();
int i = 0;
t.defaults().size(56f, 40f);
t.defaults().size(60f, 38f);
for(T p : values){
sizer.get(t.button(p.toString(), Styles.clearTogglet, () -> {
sizer.get(t.button(p.toString(), Styles.logicTogglet, () -> {
getter.get(p);
hide.run();
}).checked(current == p).group(group));
}).self(c -> tooltip(c, p)).checked(current == p).group(group));
if(++i % cols == 0) t.row();
}
});
}
protected <T> void showSelect(Button b, T[] values, T current, Cons<T> getter){
protected <T extends Enum<T>> void showSelect(Button b, T[] values, T current, Cons<T> getter){
showSelect(b, values, current, getter, 4, c -> {});
}
protected void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
Table t = new Table(Tex.button);
Table t = new Table(Tex.paneSolid){
@Override
public float getPrefHeight(){
return Math.min(super.getPrefHeight(), Core.graphics.getHeight());
}
@Override
public float getPrefWidth(){
return Math.min(super.getPrefWidth(), Core.graphics.getWidth());
}
};
t.margin(4);
//triggers events behind the element to simulate deselection
Element hitter = new Element();
@@ -110,14 +128,15 @@ public abstract class LStatement{
if(t.getWidth() > Core.scene.getWidth()) t.setWidth(Core.graphics.getWidth());
if(t.getHeight() > Core.scene.getHeight()) t.setHeight(Core.graphics.getHeight());
t.keepInStage();
t.invalidateHierarchy();
t.pack();
});
t.actions(Actions.alpha(0), Actions.fadeIn(0.3f, Interp.fade));
t.top().pane(inner -> {
inner.marginRight(24f);
inner.top();
hideCons.get(inner, hide);
}).top();
}).pad(0f).top().get().setScrollingDisabled(true, false);
t.pack();
}
@@ -139,4 +158,5 @@ public abstract class LStatement{
public String name(){
return Strings.insertSpaces(getClass().getSimpleName().replace("Statement", ""));
}
}

View File

@@ -15,6 +15,7 @@ import mindustry.type.*;
import mindustry.ui.*;
import mindustry.world.meta.*;
import static mindustry.logic.LCanvas.*;
import static mindustry.world.blocks.logic.LogicDisplay.*;
public class LStatements{
@@ -355,7 +356,7 @@ public class LStatements{
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
table.add(" of ");
table.add(" of ").self(this::param);
field(table, target, v -> target = v);
@@ -394,7 +395,7 @@ public class LStatements{
table.defaults().left();
if(buildFrom()){
table.add(" from ");
table.add(" from ").self(this::param);
fields(table, radar, v -> radar = v);
@@ -405,7 +406,7 @@ public class LStatements{
int fi = i;
Prov<RadarTarget> get = () -> (fi == 0 ? target1 : fi == 1 ? target2 : target3);
table.add(i == 0 ? " target " : " and ");
table.add(i == 0 ? " target " : " and ").self(this::param);
table.button(b -> {
b.label(() -> get.get().name());
@@ -419,13 +420,13 @@ public class LStatements{
}
}
table.add(" order ");
table.add(" order ").self(this::param);
fields(table, sortOrder, v -> sortOrder = v);
table.row();
table.add(" sort ");
table.add(" sort ").self(this::param);
table.button(b -> {
b.label(() -> sort.name());
@@ -434,7 +435,7 @@ public class LStatements{
}, 2, cell -> cell.size(100, 50)));
}, Styles.logict, () -> {}).size(90, 40).color(table.color).left().padLeft(2);
table.add(" output ");
table.add(" output ").self(this::param);
fields(table, output, v -> output = v);
}
@@ -511,7 +512,7 @@ public class LStatements{
i.button(sensor.name(), Styles.cleart, () -> {
stype("@" + sensor.name());
hide.run();
}).size(240f, 40f).row();
}).size(240f, 40f).self(c -> tooltip(c, sensor)).row();
}
})
};
@@ -531,14 +532,14 @@ public class LStatements{
t.parent.parent.pack();
t.parent.parent.invalidateHierarchy();
}).size(80f, 50f).growX().checked(selected == fi).group(group);
}).height(50f).growX().checked(selected == fi).group(group);
}
t.row();
t.add(stack).colspan(3).width(240f).left();
}));
}, Styles.logict, () -> {}).size(40f).padLeft(-1).color(table.color);
table.add(" in ");
table.add(" in ").self(this::param);
field(table, from, str -> from = str);
}
@@ -602,28 +603,51 @@ public class LStatements{
table.add(" = ");
if(op.unary){
opButton(table);
opButton(table, table);
field(table, a, str -> a = str);
}else{
row(table);
field(table, a, str -> a = str);
//"function"-type operations have the name at the left and arguments on the right
if(op.func){
if(LCanvas.useRows()){
table.left();
table.row();
table.table(c -> {
c.color.set(color());
c.left();
funcs(c, table);
}).colspan(2).left();
}else{
funcs(table, table);
}
}else{
field(table, a, str -> a = str);
opButton(table);
opButton(table, table);
field(table, b, str -> b = str);
field(table, b, str -> b = str);
}
}
}
void opButton(Table table){
void funcs(Table table, Table parent){
opButton(table, parent);
field(table, a, str -> a = str);
field(table, b, str -> b = str);
}
void opButton(Table table, Table parent){
table.button(b -> {
b.label(() -> op.symbol);
b.clicked(() -> showSelect(b, LogicOp.all, op, o -> {
op = o;
rebuild(table);
rebuild(parent);
}));
}, Styles.logict, () -> {}).size(60f, 40f).pad(4f).color(table.color);
}, Styles.logict, () -> {}).size(64f, 40f).pad(4f).color(table.color);
}
@Override
@@ -637,6 +661,28 @@ public class LStatements{
}
}
//TODO untested
//@RegisterStatement("wait")
public static class WaitStatement extends LStatement{
public String value = "0.5";
@Override
public void build(Table table){
field(table, value, str -> value = str);
table.add(" sec");
}
@Override
public Color color(){
return Pal.logicOperations;
}
@Override
public LInstruction build(LAssembler builder){
return new WaitI(builder.var(value));
}
}
@RegisterStatement("end")
public static class EndStatement extends LStatement{
@Override
@@ -783,7 +829,11 @@ public class LStatements{
table.button(b -> {
b.label(() -> type.name());
b.clicked(() -> showSelect(b, LUnitControl.all, type, t -> {
type = t;
if(t == LUnitControl.build && !Vars.state.rules.logicUnitBuild){
Vars.ui.showInfo("@logic.nounitbuild");
}else{
type = t;
}
rebuild(table);
}, 2, cell -> cell.size(120, 50)));
}, Styles.logict, () -> {}).size(120, 40).color(table.color).left().padLeft(2);
@@ -819,6 +869,10 @@ public class LStatements{
@RegisterStatement("uradar")
public static class UnitRadarStatement extends RadarStatement{
public UnitRadarStatement(){
radar = "0";
}
@Override
public boolean buildFrom(){
//do not build the "from" section
@@ -851,7 +905,7 @@ public class LStatements{
void rebuild(Table table){
table.clearChildren();
table.add(" find ").left();
table.add(" find ").left().self(this::param);;
table.button(b -> {
b.label(() -> locate.name());
@@ -864,14 +918,14 @@ public class LStatements{
switch(locate){
case building -> {
row(table);
table.add(" type ").left();
table.add(" group ").left().self(this::param);;
table.button(b -> {
b.label(() -> flag.name());
b.clicked(() -> showSelect(b, BlockFlag.all, flag, t -> flag = t, 2, cell -> cell.size(110, 50)));
b.clicked(() -> showSelect(b, BlockFlag.allLogic, flag, t -> flag = t, 2, cell -> cell.size(110, 50)));
}, Styles.logict, () -> {}).size(110, 40).color(table.color).left().padLeft(2);
row(table);
table.add(" enemy ").left();
table.add(" enemy ").left().self(this::param);;
fields(table, enemy, str -> enemy = str);
@@ -879,7 +933,7 @@ public class LStatements{
}
case ore -> {
table.add(" ore ").left();
table.add(" ore ").left().self(this::param);
table.table(ts -> {
ts.color.set(table.color);
@@ -916,19 +970,19 @@ public class LStatements{
}
}
table.add(" outX ").left();
table.add(" outX ").left().self(this::param);
fields(table, outX, str -> outX = str);
table.add(" outY ").left();
table.add(" outY ").left().self(this::param);
fields(table, outY, str -> outY = str);
row(table);
table.add(" found ").left();
table.add(" found ").left().self(this::param);
fields(table, outFound, str -> outFound = str);
if(locate != LLocate.ore){
table.add(" building ").left();
table.add(" building ").left().self(this::param);
fields(table, outBuild, str -> outBuild = str);
}

View File

@@ -1,6 +1,7 @@
package mindustry.logic;
public enum LUnitControl{
idle,
stop,
move("x", "y"),
approach("x", "y", "radius"),

View File

@@ -10,6 +10,7 @@ import mindustry.ui.*;
import mindustry.ui.dialogs.*;
import static mindustry.Vars.*;
import static mindustry.logic.LCanvas.*;
public class LogicDialog extends BaseDialog{
public LCanvas canvas;
@@ -35,6 +36,11 @@ public class LogicDialog extends BaseDialog{
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
t.row();
t.button("@schematic.copy.import", Icon.download, style, () -> {
dialog.hide();
@@ -44,11 +50,6 @@ public class LogicDialog extends BaseDialog{
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null);
t.row();
t.button("@schematic.copy", Icon.copy, style, () -> {
dialog.hide();
Core.app.setClipboardText(canvas.save());
}).marginLeft(12f);
});
});
@@ -72,7 +73,7 @@ public class LogicDialog extends BaseDialog{
t.button(example.name(), style, () -> {
canvas.add(prov.get());
dialog.hide();
}).size(140f, 50f);
}).size(140f, 50f).self(c -> tooltip(c, "lst." + example.name()));
if(++i % 2 == 0) t.row();
}
});

View File

@@ -19,6 +19,7 @@ public enum LogicOp{
lessThanEq("<=", (a, b) -> a <= b ? 1 : 0),
greaterThan(">", (a, b) -> a > b ? 1 : 0),
greaterThanEq(">=", (a, b) -> a >= b ? 1 : 0),
strictEqual("===", (a, b) -> 0), //this lambda is not actually used
shl("<<", (a, b) -> (long)a << (long)b),
shr(">>", (a, b) -> (long)a >> (long)b),
@@ -27,11 +28,11 @@ public enum LogicOp{
xor("xor", (a, b) -> (long)a ^ (long)b),
not("flip", a -> ~(long)(a)),
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)),
noise("noise", LExecutor.noise::rawNoise2D),
max("max", true, Math::max),
min("min", true, Math::min),
angle("angle", true, (x, y) -> Angles.angle((float)x, (float)y)),
len("len", true, (x, y) -> Mathf.dst((float)x, (float)y)),
noise("noise", true, LExecutor.noise::rawNoise2D),
abs("abs", a -> Math.abs(a)),
log("log", Math::log),
log10("log10", Math::log10),
@@ -48,19 +49,29 @@ public enum LogicOp{
public final OpObjLambda2 objFunction2;
public final OpLambda2 function2;
public final OpLambda1 function1;
public final boolean unary;
public final boolean unary, func;
public final String symbol;
LogicOp(String symbol, OpLambda2 function){
this(symbol, function, null);
}
LogicOp(String symbol, boolean func, OpLambda2 function){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
this.objFunction2 = null;
this.func = func;
}
LogicOp(String symbol, OpLambda2 function, OpObjLambda2 objFunction){
this.symbol = symbol;
this.function2 = function;
this.function1 = null;
this.unary = false;
this.objFunction2 = objFunction;
this.func = false;
}
LogicOp(String symbol, OpLambda1 function){
@@ -69,6 +80,7 @@ public enum LogicOp{
this.function2 = null;
this.unary = true;
this.objFunction2 = null;
this.func = false;
}
@Override

View File

@@ -62,7 +62,7 @@ public class Map implements Comparable<Map>, Publishable{
}
public int getHightScore(){
return Core.settings.getInt("hiscore" + file.nameWithoutExtension(), 0);
return Core.settings.getInt("hiscore" + file.nameWithoutExtension() + tags.get("steamid", ""), 0);
}
public Texture safeTexture(){
@@ -78,7 +78,7 @@ public class Map implements Comparable<Map>, Publishable{
}
public void setHighScore(int score){
Core.settings.put("hiscore" + file.nameWithoutExtension(), score);
Core.settings.put("hiscore" + file.nameWithoutExtension() + tags.get("steamid", ""), score);
}
/** Returns the result of applying this map's rules to the specified gamemode.*/

View File

@@ -24,7 +24,7 @@ public class SectorDamage{
public static final int maxRetWave = 40, maxWavesSimulated = 50;
//direct damage is for testing only
private static final boolean direct = false, rubble = true;
private static final boolean rubble = true;
/** @return calculated capture progress of the enemy */
public static float getDamage(SectorInfo info){
@@ -225,7 +225,6 @@ public class SectorDamage{
//create sparse tile array for fast range query
int sparseSkip = 5, sparseSkip2 = 3;
//TODO if this is slow, use a quadtree
Seq<Tile> sparse = new Seq<>(path.size / sparseSkip + 1);
Seq<Tile> sparse2 = new Seq<>(path.size / sparseSkip2 + 1);
@@ -363,13 +362,11 @@ public class SectorDamage{
info.waveDpsBase = reg.intercept;
info.waveDpsSlope = reg.slope;
//enemy units like to aim for a lot of non-essential things, so increase resulting health slightly
info.sumHealth = sumHealth * 1.05f;
//players tend to have longer range units/turrets, so assume DPS is higher
info.sumDps = sumDps * 1.05f;
info.sumHealth = sumHealth * 0.9f;
info.sumDps = sumDps;
info.sumRps = sumRps;
float cmult = 1.5f;
float cmult = 1.6f;
info.curEnemyDps = curEnemyDps*cmult;
info.curEnemyHealth = curEnemyHealth*cmult;
@@ -487,23 +484,21 @@ public class SectorDamage{
if(other.build != null && other.team() != state.rules.waveTeam){
resultDamage -= other.build.health();
if(direct){
other.build.damage(currDamage);
}else{ //indirect damage happens at game load time
other.build.health -= currDamage;
//don't kill the core!
if(other.block() instanceof CoreBlock) other.build.health = Math.max(other.build.health, 1f);
other.build.health -= currDamage;
//don't kill the core!
if(other.block() instanceof CoreBlock) other.build.health = Math.max(other.build.health, 1f);
//remove the block when destroyed
if(other.build.health < 0){
//rubble
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
Effect.rubble(other.build.x, other.build.y, other.block().size);
}
other.build.addPlan(false);
other.remove();
//remove the block when destroyed
if(other.build.health < 0){
//rubble
if(rubble && !other.floor().solid && !other.floor().isLiquid && Mathf.chance(0.4)){
Effect.rubble(other.build.x, other.build.y, other.block().size);
}
other.build.addPlan(false);
other.remove();
}else{
indexer.notifyTileDamaged(other.build);
}
}else if(other.solid() && !other.synthetic()){ //skip damage propagation through solid blocks
@@ -524,7 +519,7 @@ public class SectorDamage{
static float cost(Tile tile){
return 1f +
(tile.block().isStatic() && tile.solid() ? 200f : 0f) +
(tile.build != null ? tile.build.health / 40f : 0f) +
(tile.build != null ? tile.build.health / (tile.build.block.size * tile.build.block.size) / 20f : 0f) +
(tile.floor().isLiquid ? 10f : 0f);
}
}

View File

@@ -68,8 +68,8 @@ public class MirrorFilter extends GenerateFilter{
void mirror(Vec2 p, float x0, float y0, float x1, float y1){
//special case: uneven map mirrored at 45 degree angle
if(in.width != in.height && angle % 90 != 0){
p.x = (p.x - in.width/2f) * -1 + in.width/2f;
p.y = (p.y - in.height/2f) * -1 + in.height/2f;
p.x = in.width - p.x - 1;
p.y = in.height - p.y - 1;
}else{
float dx = x1 - x0;
float dy = y1 - y0;

View File

@@ -338,7 +338,7 @@ public class SerpuloPlanetGenerator extends PlanetGenerator{
}
}else if(floor != Blocks.basalt && floor != Blocks.ice && floor.asFloor().hasSurface()){
float noise = noise(x + 782, y, 5, 0.75f, 260f, 1f);
if(noise > 0.67f && !enemies.contains(e -> Mathf.within(x, y, e.x, e.y, 8))){
if(noise > 0.67f && !roomseq.contains(e -> Mathf.within(x, y, e.x, e.y, 14))){
if(noise > 0.72f){
floor = noise > 0.78f ? Blocks.taintedWater : (floor == Blocks.sand ? Blocks.sandWater : Blocks.darksandTaintedWater);
}else{

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