Merge branch 'master' into new-logic-parser
# Conflicts: # gradle.properties
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"))){
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
40
core/src/mindustry/ai/types/DefenderAI.java
Normal file
40
core/src/mindustry/ai/types/DefenderAI.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 :
|
||||
|
||||
@@ -130,7 +130,7 @@ public class SoundControl{
|
||||
Core.audio.soundBus.play();
|
||||
setupFilters();
|
||||
}else{
|
||||
Core.audio.soundBus.stop();
|
||||
Core.audio.soundBus.replay();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")){{
|
||||
|
||||
@@ -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;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 + "";
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,6 +33,8 @@ public class ShrapnelBulletType extends BulletType{
|
||||
|
||||
@Override
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
Damage.collideLaser(b, length, hitLarge);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
5
core/src/mindustry/entities/comp/Sized.java
Normal file
5
core/src/mindustry/entities/comp/Sized.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
public interface Sized{
|
||||
float hitSize();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ public class FloorRenderer implements Disposable{
|
||||
return;
|
||||
}
|
||||
|
||||
Draw.flush();
|
||||
cbatch.setProjection(Core.camera.mat);
|
||||
cbatch.beginDraw();
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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", ""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mindustry.logic;
|
||||
|
||||
public enum LUnitControl{
|
||||
idle,
|
||||
stop,
|
||||
move("x", "y"),
|
||||
approach("x", "y", "radius"),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user