Merge branch 'master' into port-field
This commit is contained in:
@@ -9,6 +9,7 @@ import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.util.async.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
@@ -74,7 +75,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
Fonts.loadDefaultFont();
|
||||
|
||||
//load fallback atlas if max texture size is below 4096
|
||||
assets.load(new AssetDescriptor<>(Gl.getInt(Gl.maxTextureSize) >= 4096 ? "sprites/sprites.atlas" : "sprites/fallback/sprites.atlas", TextureAtlas.class)).loaded = t -> {
|
||||
assets.load(new AssetDescriptor<>(Gl.getInt(Gl.maxTextureSize) >= 4096 ? "sprites/sprites.atlas" : "sprites/fallback/sprites.atlas", TextureAtlas.class)).loaded = t -> {
|
||||
atlas = (TextureAtlas)t;
|
||||
Fonts.mergeFontAtlas(atlas);
|
||||
};
|
||||
@@ -103,6 +104,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
assets.load(schematics);
|
||||
|
||||
assets.loadRun("contentinit", ContentLoader.class, () -> content.init(), () -> content.load());
|
||||
assets.loadRun("baseparts", BaseRegistry.class, () -> {}, () -> bases.load());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,8 +112,8 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
|
||||
super.add(module);
|
||||
|
||||
//autoload modules when necessary
|
||||
if(module instanceof Loadable){
|
||||
assets.load((Loadable)module);
|
||||
if(module instanceof Loadable l){
|
||||
assets.load(l);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,14 @@ import arc.util.*;
|
||||
import arc.util.Log.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.async.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.maps.Map;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.mod.*;
|
||||
@@ -36,6 +36,8 @@ public class Vars implements Loadable{
|
||||
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;
|
||||
/** Maximum extra padding around deployment schematics. */
|
||||
public static final int maxLoadoutSchematicPad = 5;
|
||||
/** Maximum schematic size.*/
|
||||
@@ -55,7 +57,7 @@ public class Vars implements Loadable{
|
||||
/** URL for sending crash reports to */
|
||||
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/";
|
||||
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. */
|
||||
@@ -63,17 +65,17 @@ public class Vars implements Loadable{
|
||||
/** 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 of the github issue report template.*/
|
||||
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?template=bug_report.md";
|
||||
public static final String reportIssueURL = "https://github.com/Anuken/Mindustry/issues/new?labels=bug&template=bug_report.md";
|
||||
/** list of built-in servers.*/
|
||||
public static final Seq<String> defaultServers = Seq.with();
|
||||
public static final Seq<ServerGroup> defaultServers = Seq.with();
|
||||
/** maximum size of any block, do not change unless you know what you're doing */
|
||||
public static final int maxBlockSize = 16;
|
||||
/** maximum distance between mine and core that supports automatic transferring */
|
||||
public static final float mineTransferRange = 220f;
|
||||
/** max chat message length */
|
||||
public static final int maxTextLength = 150;
|
||||
/** max player name length in bytes */
|
||||
public static final int maxNameLength = 40;
|
||||
/** shadow color for turrets */
|
||||
public static final float turretShadowColor = Color.toFloatBits(0, 0, 0, 0.22f);
|
||||
/** displayed item size when ingame. */
|
||||
public static final float itemSize = 5f;
|
||||
/** units outside of this bound will die instantly */
|
||||
@@ -84,10 +86,14 @@ public class Vars implements Loadable{
|
||||
public static final float buildingRange = 220f;
|
||||
/** range for moving items */
|
||||
public static final float itemTransferRange = 220f;
|
||||
/** range for moving items for logic units */
|
||||
public static final float logicItemTransferRange = 45f;
|
||||
/** duration of time between turns in ticks */
|
||||
public static final float turnDuration = 20 * Time.toMinutes;
|
||||
/** turns needed to destroy a sector completely */
|
||||
public static final float sectorDestructionTurns = 3f;
|
||||
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;
|
||||
/** min armor fraction damage; e.g. 0.05 = at least 5% damage */
|
||||
public static final float minArmorDamage = 0.1f;
|
||||
/** launch animation duration */
|
||||
@@ -183,15 +189,14 @@ public class Vars implements Loadable{
|
||||
public static ContentLoader content;
|
||||
public static GameState state;
|
||||
public static EntityCollisions collisions;
|
||||
public static DefaultWaves defaultWaves;
|
||||
public static mindustry.audio.LoopControl loops;
|
||||
public static Waves waves;
|
||||
public static Platform platform = new Platform(){};
|
||||
public static Mods mods;
|
||||
public static Schematics schematics;
|
||||
public static BeControl becontrol;
|
||||
public static AsyncCore asyncCore;
|
||||
public static TeamIndexProcess teamIndex;
|
||||
public static BaseRegistry bases;
|
||||
public static GlobalConstants constants;
|
||||
|
||||
public static Universe universe;
|
||||
public static World world;
|
||||
@@ -232,6 +237,7 @@ public class Vars implements Loadable{
|
||||
}
|
||||
|
||||
Arrays.sort(locales, Structs.comparing(l -> l.getDisplayName(l), String.CASE_INSENSITIVE_ORDER));
|
||||
locales = Seq.with(locales).and(new Locale("router")).toArray(Locale.class);
|
||||
}
|
||||
|
||||
Version.init();
|
||||
@@ -252,8 +258,7 @@ public class Vars implements Loadable{
|
||||
if(mods == null) mods = new Mods();
|
||||
|
||||
content = new ContentLoader();
|
||||
loops = new LoopControl();
|
||||
defaultWaves = new DefaultWaves();
|
||||
waves = new Waves();
|
||||
collisions = new EntityCollisions();
|
||||
world = new World();
|
||||
universe = new Universe();
|
||||
@@ -265,6 +270,7 @@ public class Vars implements Loadable{
|
||||
indexer = new BlockIndexer();
|
||||
pathfinder = new Pathfinder();
|
||||
bases = new BaseRegistry();
|
||||
constants = new GlobalConstants();
|
||||
|
||||
state = new GameState();
|
||||
|
||||
@@ -282,10 +288,10 @@ public class Vars implements Loadable{
|
||||
if(loadedLogger) return;
|
||||
|
||||
String[] tags = {"[green][D][]", "[royal][I][]", "[yellow][W][]", "[scarlet][E][]", ""};
|
||||
String[] stags = {"&lc&fb[D]", "&lg&fb[I]", "&ly&fb[W]", "&lr&fb[E]", ""};
|
||||
String[] stags = {"&lc&fb[D]", "&lb&fb[I]", "&ly&fb[W]", "&lr&fb[E]", ""};
|
||||
|
||||
Seq<String> logBuffer = new Seq<>();
|
||||
Log.setLogger((level, text) -> {
|
||||
Log.logger = (level, text) -> {
|
||||
String result = text;
|
||||
String rawText = Log.format(stags[level.ordinal()] + "&fr " + text);
|
||||
System.out.println(rawText);
|
||||
@@ -301,9 +307,9 @@ public class Vars implements Loadable{
|
||||
}
|
||||
}
|
||||
|
||||
ui.scriptfrag.addMessage(Log.removeCodes(result));
|
||||
ui.scriptfrag.addMessage(Log.removeColors(result));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Events.on(ClientLoadEvent.class, e -> logBuffer.each(ui.scriptfrag::addMessage));
|
||||
|
||||
@@ -315,19 +321,25 @@ public class Vars implements Loadable{
|
||||
|
||||
settings.setAppName(appName);
|
||||
|
||||
Writer writer = settings.getDataDirectory().child("last_log.txt").writer(false);
|
||||
LogHandler log = Log.getLogger();
|
||||
Log.setLogger((level, text) -> {
|
||||
log.log(level, text);
|
||||
try{
|
||||
Writer writer = settings.getDataDirectory().child("last_log.txt").writer(false);
|
||||
LogHandler log = Log.logger;
|
||||
//ignore it
|
||||
Log.logger = (level, text) -> {
|
||||
log.log(level, text);
|
||||
|
||||
try{
|
||||
writer.write("[" + Character.toUpperCase(level.name().charAt(0)) +"] " + Log.removeCodes(text) + "\n");
|
||||
writer.flush();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
//ignore it
|
||||
}
|
||||
});
|
||||
try{
|
||||
writer.write("[" + Character.toUpperCase(level.name().charAt(0)) +"] " + Log.removeColors(text) + "\n");
|
||||
writer.flush();
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
//ignore it
|
||||
}
|
||||
};
|
||||
}catch(Exception e){
|
||||
//handle log file not being found
|
||||
Log.err(e);
|
||||
}
|
||||
|
||||
loadedFileLogger = true;
|
||||
}
|
||||
@@ -383,6 +395,11 @@ public class Vars implements Loadable{
|
||||
|
||||
Locale.setDefault(locale);
|
||||
Core.bundle = I18NBundle.createBundle(handle, locale);
|
||||
|
||||
//router
|
||||
if(locale.getDisplayName().equals("router")){
|
||||
bundle.debug("router");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.world;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Astar{
|
||||
public static final DistanceHeuristic manhattan = (x1, y1, x2, y2) -> Math.abs(x1 - x2) + Math.abs(y1 - y2);
|
||||
|
||||
@@ -7,6 +7,7 @@ import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.BaseRegistry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Schematic.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -14,46 +15,129 @@ import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.defense.*;
|
||||
import mindustry.world.blocks.distribution.*;
|
||||
import mindustry.world.blocks.production.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BaseAI{
|
||||
private static final Vec2 axis = new Vec2(), rotator = new Vec2();
|
||||
private static final float correctPercent = 0.5f;
|
||||
private static final float step = 5;
|
||||
private static final int attempts = 5;
|
||||
private static final int attempts = 4;
|
||||
private static final float emptyChance = 0.01f;
|
||||
private static final int timerStep = 0, timerSpawn = 1;
|
||||
private static final int timerStep = 0, timerSpawn = 1, timerRefreshPath = 2;
|
||||
private static final int pathStep = 50;
|
||||
private static final Seq<Tile> tmpTiles = new Seq<>();
|
||||
|
||||
private static int correct = 0, incorrect = 0;
|
||||
|
||||
private int lastX, lastY, lastW, lastH;
|
||||
private boolean triedWalls;
|
||||
private boolean triedWalls, foundPath;
|
||||
|
||||
TeamData data;
|
||||
Interval timer = new Interval(4);
|
||||
|
||||
IntSet path = new IntSet();
|
||||
IntSet calcPath = new IntSet();
|
||||
@Nullable Tile calcTile;
|
||||
boolean calculating, startedCalculating;
|
||||
int calcCount = 0;
|
||||
int totalCalcs = 0;
|
||||
|
||||
public BaseAI(TeamData data){
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void update(){
|
||||
if(timer.get(timerSpawn, 60) && data.hasCore()){
|
||||
if(data.team.rules().aiCoreSpawn && timer.get(timerSpawn, 60 * 2.5f) && data.hasCore()){
|
||||
CoreBlock block = (CoreBlock)data.core().block;
|
||||
int coreUnits = Groups.unit.count(u -> u.team == data.team && u.type == block.unitType);
|
||||
|
||||
//create AI core unit
|
||||
if(!Groups.unit.contains(u -> u.team() == data.team && u.type() == block.unitType)){
|
||||
//create AI core unit(s)
|
||||
if(!state.isEditor() && coreUnits < data.cores.size){
|
||||
Unit unit = block.unitType.create(data.team);
|
||||
unit.set(data.core());
|
||||
unit.set(data.cores.random());
|
||||
unit.add();
|
||||
Fx.spawn.at(unit);
|
||||
}
|
||||
}
|
||||
|
||||
//refresh path
|
||||
if(!calculating && (timer.get(timerRefreshPath, 3f * Time.toMinutes) || !startedCalculating) && data.hasCore()){
|
||||
calculating = true;
|
||||
startedCalculating = true;
|
||||
calcPath.clear();
|
||||
}
|
||||
|
||||
//didn't find tile in time
|
||||
if(calculating && calcCount >= world.width() * world.height()){
|
||||
calculating = false;
|
||||
calcCount = 0;
|
||||
calcPath.clear();
|
||||
totalCalcs ++;
|
||||
}
|
||||
|
||||
//calculate path for units so schematics are not placed on it
|
||||
if(calculating){
|
||||
if(calcTile == null){
|
||||
Vars.spawner.eachGroundSpawn((x, y) -> calcTile = world.tile(x, y));
|
||||
if(calcTile == null){
|
||||
calculating = false;
|
||||
}
|
||||
}else{
|
||||
var field = pathfinder.getField(state.rules.waveTeam, Pathfinder.costGround, Pathfinder.fieldCore);
|
||||
|
||||
int[][] weights = field.weights;
|
||||
for(int i = 0; i < pathStep; i++){
|
||||
int minCost = Integer.MAX_VALUE;
|
||||
int cx = calcTile.x, cy = calcTile.y;
|
||||
boolean foundAny = false;
|
||||
for(Point2 p : Geometry.d4){
|
||||
int nx = cx + p.x, ny = cy + p.y;
|
||||
|
||||
Tile other = world.tile(nx, ny);
|
||||
if(other != null && weights[nx][ny] < minCost && weights[nx][ny] != -1){
|
||||
minCost = weights[nx][ny];
|
||||
calcTile = other;
|
||||
foundAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
//didn't find anything, break out of loop, this will trigger a clear later
|
||||
if(!foundAny){
|
||||
calcCount = Integer.MAX_VALUE;
|
||||
break;
|
||||
}
|
||||
|
||||
calcPath.add(calcTile.pos());
|
||||
for(Point2 p : Geometry.d8){
|
||||
calcPath.add(Point2.pack(p.x + calcTile.x, p.y + calcTile.y));
|
||||
}
|
||||
|
||||
//found the end.
|
||||
if(calcTile.build instanceof CoreBuild b && b.team == state.rules.defaultTeam){
|
||||
//clean up calculations and flush results
|
||||
calculating = false;
|
||||
calcCount = 0;
|
||||
path.clear();
|
||||
path.addAll(calcPath);
|
||||
calcPath.clear();
|
||||
calcTile = null;
|
||||
totalCalcs ++;
|
||||
foundPath = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
calcCount ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//only schedule when there's something to build.
|
||||
if(data.blocks.isEmpty() && timer.get(timerStep, step)){
|
||||
if(foundPath && data.blocks.isEmpty() && timer.get(timerStep, Mathf.lerp(20f, 4f, data.team.rules().aiTier))){
|
||||
if(!triedWalls){
|
||||
tryWalls();
|
||||
triedWalls = true;
|
||||
@@ -68,9 +152,14 @@ public class BaseAI{
|
||||
if(pos == null) return;
|
||||
|
||||
Tmp.v1.rnd(Mathf.random(range));
|
||||
int wx = (int)(world.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(world.toTile(pos.getY()) + Tmp.v1.y);
|
||||
int wx = (int)(World.toTile(pos.getX()) + Tmp.v1.x), wy = (int)(World.toTile(pos.getY()) + Tmp.v1.y);
|
||||
Tile tile = world.tiles.getc(wx, wy);
|
||||
|
||||
//try not to block the spawn point
|
||||
if(spawner.getSpawns().contains(t -> t.within(tile, tilesize * 40f))){
|
||||
continue;
|
||||
}
|
||||
|
||||
Seq<BasePart> parts = null;
|
||||
|
||||
//pick a completely random base part, and place it a random location
|
||||
@@ -117,6 +206,13 @@ public class BaseAI{
|
||||
if(!Build.validPlace(tile.block, data.team, realX, realY, tile.rotation)){
|
||||
return false;
|
||||
}
|
||||
Tile wtile = world.tile(realX, realY);
|
||||
|
||||
//may intersect AI path
|
||||
tmpTiles.clear();
|
||||
if(tile.block.solid && wtile != null && wtile.getLinkedTilesAs(tile.block, tmpTiles).contains(t -> path.contains(t.pos()))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//make sure at least X% of resource requirements are met
|
||||
@@ -179,13 +275,18 @@ 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;
|
||||
}
|
||||
|
||||
if(o != null && o.team() == data.team && !(o.block() instanceof Wall)){
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(any && Build.validPlace(wall, data.team, tile.x, tile.y, 0)){
|
||||
tmpTiles.clear();
|
||||
if(any && Build.validPlace(wall, data.team, tile.x, tile.y, 0) && !tile.getLinkedTilesAs(wall, tmpTiles).contains(t -> path.contains(t.pos()))){
|
||||
data.blocks.add(new BlockPlan(tile.x, tile.y, (short)0, wall.id, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package mindustry.ai;
|
||||
import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Schematic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.production.*;
|
||||
import mindustry.world.blocks.sandbox.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
@@ -17,12 +17,16 @@ import mindustry.world.meta.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class BaseRegistry{
|
||||
/** cores, sorted by tier */
|
||||
public Seq<BasePart> cores = new Seq<>();
|
||||
/** parts with no requirement */
|
||||
public Seq<BasePart> parts = new Seq<>();
|
||||
public ObjectMap<Content, Seq<BasePart>> reqParts = new ObjectMap<>();
|
||||
public ObjectMap<Item, OreBlock> ores = new ObjectMap<>();
|
||||
public ObjectMap<Item, Floor> oreFloors = new ObjectMap<>();
|
||||
|
||||
public Seq<BasePart> forResource(Content item){
|
||||
return reqParts.get(item, Seq::new);
|
||||
@@ -33,6 +37,15 @@ public class BaseRegistry{
|
||||
parts.clear();
|
||||
reqParts.clear();
|
||||
|
||||
//load ore types and corresponding items
|
||||
for(Block block : content.blocks()){
|
||||
if(block instanceof OreBlock && block.asFloor().itemDrop != null){
|
||||
ores.put(block.asFloor().itemDrop, (OreBlock)block);
|
||||
}else if(block.isFloor() && block.asFloor().itemDrop != null && !oreFloors.containsKey(block.asFloor().itemDrop)){
|
||||
oreFloors.put(block.asFloor().itemDrop, block.asFloor());
|
||||
}
|
||||
}
|
||||
|
||||
String[] names = Core.files.internal("basepartnames").readString().split("\n");
|
||||
|
||||
for(String name : names){
|
||||
@@ -69,7 +82,7 @@ public class BaseRegistry{
|
||||
}
|
||||
schem.tiles.removeAll(s -> s.block.buildVisibility == BuildVisibility.sandboxOnly);
|
||||
|
||||
part.tier = schem.tiles.sumf(s -> Mathf.pow(s.block.buildCost / s.block.buildCostMultiplier, 1.2f));
|
||||
part.tier = schem.tiles.sumf(s -> Mathf.pow(s.block.buildCost / s.block.buildCostMultiplier, 1.4f));
|
||||
|
||||
if(part.core != null){
|
||||
cores.add(part);
|
||||
@@ -86,7 +99,9 @@ public class BaseRegistry{
|
||||
part.centerY = part.schematic.height/2;
|
||||
}
|
||||
|
||||
if(part.required != null) reqParts.get(part.required, Seq::new).add(part);
|
||||
if(part.required != null && part.core == null){
|
||||
reqParts.get(part.required, Seq::new).add(part);
|
||||
}
|
||||
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -6,10 +6,12 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.EnumSet;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@@ -54,18 +56,7 @@ public class BlockIndexer{
|
||||
|
||||
public BlockIndexer(){
|
||||
Events.on(TileChangeEvent.class, event -> {
|
||||
if(typeMap.get(event.tile.pos()) != null){
|
||||
TileIndex index = typeMap.get(event.tile.pos());
|
||||
for(BlockFlag flag : index.flags){
|
||||
getFlagged(index.team)[flag.ordinal()].remove(event.tile);
|
||||
}
|
||||
|
||||
if(index.flags.contains(BlockFlag.unitModifier)){
|
||||
updateCap(index.team);
|
||||
}
|
||||
}
|
||||
process(event.tile);
|
||||
updateQuadrant(event.tile);
|
||||
updateIndices(event.tile);
|
||||
});
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
@@ -109,6 +100,21 @@ public class BlockIndexer{
|
||||
});
|
||||
}
|
||||
|
||||
public void updateIndices(Tile tile){
|
||||
if(typeMap.get(tile.pos()) != null){
|
||||
TileIndex index = typeMap.get(tile.pos());
|
||||
for(BlockFlag flag : index.flags){
|
||||
getFlagged(index.team)[flag.ordinal()].remove(tile);
|
||||
}
|
||||
|
||||
if(index.flags.contains(BlockFlag.unitModifier)){
|
||||
updateCap(index.team);
|
||||
}
|
||||
}
|
||||
process(tile);
|
||||
updateQuadrant(tile);
|
||||
}
|
||||
|
||||
private TileArray[] getFlagged(Team team){
|
||||
return flagMap[team.id];
|
||||
}
|
||||
@@ -178,8 +184,8 @@ public class BlockIndexer{
|
||||
public boolean eachBlock(Team team, float wx, float wy, float range, Boolf<Building> pred, Cons<Building> cons){
|
||||
intSet.clear();
|
||||
|
||||
int tx = world.toTile(wx);
|
||||
int ty = world.toTile(wy);
|
||||
int tx = World.toTile(wx);
|
||||
int ty = World.toTile(wy);
|
||||
|
||||
int tileRange = (int)(range / tilesize + 1);
|
||||
boolean any = false;
|
||||
@@ -192,7 +198,7 @@ public class BlockIndexer{
|
||||
|
||||
if(other == null) continue;
|
||||
|
||||
if(other.team == team && pred.get(other) && intSet.add(other.pos())){
|
||||
if((team == null || other.team == team) && pred.get(other) && intSet.add(other.pos())){
|
||||
cons.get(other);
|
||||
any = true;
|
||||
}
|
||||
@@ -205,8 +211,22 @@ public class BlockIndexer{
|
||||
/** Get all enemy blocks with a flag. */
|
||||
public Seq<Tile> getEnemy(Team team, BlockFlag type){
|
||||
returnArray.clear();
|
||||
for(Team enemy : team.enemies()){
|
||||
if(state.teams.isActive(enemy)){
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
//when team data is not initialized, scan through every team. this is terrible
|
||||
if(data.isEmpty()){
|
||||
for(Team enemy : Team.all){
|
||||
if(enemy == team) continue;
|
||||
TileArray set = getFlagged(enemy)[type.ordinal()];
|
||||
if(set != null){
|
||||
for(Tile tile : set){
|
||||
returnArray.add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(int i = 0; i < data.size; i++){
|
||||
Team enemy = data.items[i].team;
|
||||
if(enemy == team) continue;
|
||||
TileArray set = getFlagged(enemy)[type.ordinal()];
|
||||
if(set != null){
|
||||
for(Tile tile : set){
|
||||
@@ -215,12 +235,13 @@ public class BlockIndexer{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
public void notifyTileDamaged(Building entity){
|
||||
if(damagedTiles[entity.team.id] == null){
|
||||
damagedTiles[entity.team.id] = new ObjectSet<Building>();
|
||||
damagedTiles[entity.team.id] = new ObjectSet<>();
|
||||
}
|
||||
|
||||
damagedTiles[entity.team.id].add(entity);
|
||||
@@ -230,7 +251,7 @@ public class BlockIndexer{
|
||||
for(int i = 0; i < activeTeams.size; i++){
|
||||
Team enemy = activeTeams.items[i];
|
||||
|
||||
if(enemy == team) continue;
|
||||
if(enemy == team || team == Team.derelict) continue;
|
||||
|
||||
Building entity = indexer.findTile(enemy, x, y, range, pred, true);
|
||||
if(entity != null){
|
||||
@@ -259,10 +280,7 @@ public class BlockIndexer{
|
||||
for(int ty = ry * quadrantSize; ty < (ry + 1) * quadrantSize && ty < world.height(); ty++){
|
||||
Building e = world.build(tx, ty);
|
||||
|
||||
if(e == null) continue;
|
||||
|
||||
if(e.team != team || !pred.get(e) || !e.block.targetable)
|
||||
continue;
|
||||
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 ||
|
||||
@@ -309,6 +327,11 @@ public class BlockIndexer{
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Find the closest ore block relative to a position. */
|
||||
public Tile findClosestOre(Unit unit, Item item){
|
||||
return findClosestOre(unit.x, unit.y, item);
|
||||
}
|
||||
|
||||
/** @return extra unit cap of a team. This is added onto the base value. */
|
||||
public int getExtraUnits(Team team){
|
||||
return unitCaps[team.id];
|
||||
@@ -454,8 +477,8 @@ public class BlockIndexer{
|
||||
}
|
||||
|
||||
public static class TileArray implements Iterable<Tile>{
|
||||
private Seq<Tile> tiles = new Seq<>(false, 16);
|
||||
private IntSet contained = new IntSet();
|
||||
Seq<Tile> tiles = new Seq<>(false, 16);
|
||||
IntSet contained = new IntSet();
|
||||
|
||||
public void add(Tile tile){
|
||||
if(contained.add(tile.pos())){
|
||||
|
||||
@@ -4,20 +4,22 @@ import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import arc.util.async.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Pathfinder implements Runnable{
|
||||
private static final long maxUpdate = Time.millisToNanos(6);
|
||||
private static final long maxUpdate = Time.millisToNanos(7);
|
||||
private static final int updateFPS = 60;
|
||||
private static final int updateInterval = 1000 / updateFPS;
|
||||
private static final int impassable = -1;
|
||||
@@ -35,7 +37,7 @@ public class Pathfinder implements Runnable{
|
||||
public static final int
|
||||
costGround = 0,
|
||||
costLegs = 1,
|
||||
costWater = 2;
|
||||
costNaval = 2;
|
||||
|
||||
public static final Seq<PathCost> costTypes = Seq.with(
|
||||
//ground
|
||||
@@ -43,7 +45,7 @@ public class Pathfinder implements Runnable{
|
||||
PathTile.health(tile) * 5 +
|
||||
(PathTile.nearSolid(tile) ? 2 : 0) +
|
||||
(PathTile.nearLiquid(tile) ? 6 : 0) +
|
||||
(PathTile.deep(tile) ? 70 : 0) +
|
||||
(PathTile.deep(tile) ? 6000 : 0) +
|
||||
(PathTile.damages(tile) ? 30 : 0),
|
||||
|
||||
//legs
|
||||
@@ -51,7 +53,7 @@ public class Pathfinder implements Runnable{
|
||||
(PathTile.solid(tile) ? 5 : 0),
|
||||
|
||||
//water
|
||||
(team, tile) -> PathTile.solid(tile) || !PathTile.liquid(tile) ? 200 : 2 + //TODO cannot go through blocks - pathfinding isn't great
|
||||
(team, tile) -> PathTile.solid(tile) || !PathTile.liquid(tile) ? 200 : 2 +
|
||||
(PathTile.nearGround(tile) || PathTile.nearSolid(tile) ? 14 : 0) +
|
||||
(PathTile.deep(tile) ? -1 : 0) +
|
||||
(PathTile.damages(tile) ? 35 : 0)
|
||||
@@ -86,9 +88,13 @@ public class Pathfinder implements Runnable{
|
||||
tiles[tile.x][tile.y] = packTile(tile);
|
||||
}
|
||||
|
||||
//special preset which may help speed things up; this is optional
|
||||
preloadPath(getField(state.rules.waveTeam, costGround, fieldCore));
|
||||
|
||||
//preload water on naval maps
|
||||
if(spawner.getSpawns().contains(t -> t.floor().isLiquid)){
|
||||
preloadPath(getField(state.rules.waveTeam, costNaval, fieldCore));
|
||||
}
|
||||
|
||||
start();
|
||||
});
|
||||
|
||||
@@ -103,11 +109,10 @@ public class Pathfinder implements Runnable{
|
||||
|
||||
/** Packs a tile into its internal representation. */
|
||||
private int packTile(Tile tile){
|
||||
//TODO nearGround is just the inverse of nearLiquid?
|
||||
boolean nearLiquid = false, nearSolid = false, nearGround = false;
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Tile other = tile.getNearby(i);
|
||||
Tile other = tile.nearby(i);
|
||||
if(other != null){
|
||||
if(other.floor().isLiquid) nearLiquid = true;
|
||||
if(other.solid()) nearSolid = true;
|
||||
@@ -116,11 +121,11 @@ public class Pathfinder implements Runnable{
|
||||
}
|
||||
|
||||
return PathTile.get(
|
||||
tile.build == null ? 0 : Math.min((int)(tile.build.health / 40), 127),
|
||||
tile.build == null || !tile.solid() || tile.block() instanceof CoreBlock ? 0 : Math.min((int)(tile.build.health / 40), 80),
|
||||
tile.getTeamID(),
|
||||
tile.solid(),
|
||||
tile.floor().isLiquid,
|
||||
tile.staticDarkness() >= 2,
|
||||
tile.staticDarkness() >= 2 || (tile.floor().solid && tile.block() == Blocks.air),
|
||||
nearLiquid,
|
||||
nearGround,
|
||||
nearSolid,
|
||||
@@ -188,6 +193,8 @@ public class Pathfinder implements Runnable{
|
||||
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
|
||||
@@ -196,12 +203,11 @@ public class Pathfinder implements Runnable{
|
||||
Team team = data.team;
|
||||
|
||||
Core.app.post(() -> {
|
||||
//TODO ?????
|
||||
//remove its used state
|
||||
//if(fieldMap[team.id] != null){
|
||||
// fieldMap[team.id].remove(data.target);
|
||||
// fieldMapUsed[team.id].remove(data.target);
|
||||
//}
|
||||
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);
|
||||
});
|
||||
@@ -210,7 +216,7 @@ public class Pathfinder implements Runnable{
|
||||
//remove from this thread list with a delay
|
||||
threadList.remove(data);
|
||||
});
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,13 +359,7 @@ public class Pathfinder implements Runnable{
|
||||
threadList.add(path);
|
||||
|
||||
//add to main thread's list of paths
|
||||
Core.app.post(() -> {
|
||||
mainList.add(path);
|
||||
//TODO
|
||||
//if(fieldMap[team.id] != null){
|
||||
// fieldMap[team.id].put(target, path);
|
||||
//}
|
||||
});
|
||||
Core.app.post(() -> mainList.add(path));
|
||||
|
||||
//fill with impassables by default
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
@@ -445,16 +445,15 @@ public class Pathfinder implements Runnable{
|
||||
|
||||
@Override
|
||||
public void getPositions(IntSeq out){
|
||||
out.add(Point2.pack(world.toTile(position.getX()), world.toTile(position.getY())));
|
||||
out.add(Point2.pack(World.toTile(position.getX()), World.toTile(position.getY())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for a flow field to some set of destinations.
|
||||
* Concrete subclasses must specify a way to fetch costs and destinations.
|
||||
* */
|
||||
static abstract class Flowfield{
|
||||
*/
|
||||
public static abstract class Flowfield{
|
||||
/** Refresh rate in milliseconds. Return any number <= 0 to disable. */
|
||||
protected int refreshRate;
|
||||
/** Team this path is for. Set before using. */
|
||||
@@ -463,13 +462,13 @@ public class Pathfinder implements Runnable{
|
||||
protected PathCost cost = costTypes.get(costGround);
|
||||
|
||||
/** costs of getting to a specific tile */
|
||||
int[][] weights;
|
||||
public int[][] weights;
|
||||
/** search IDs of each position - the highest, most recent search is prioritized and overwritten */
|
||||
int[][] searches;
|
||||
public int[][] searches;
|
||||
/** search frontier, these are Pos objects */
|
||||
IntQueue frontier = new IntQueue();
|
||||
/** all target positions; these positions have a cost of 0, and must be synchronized on! */
|
||||
IntSeq targets = new IntSeq();
|
||||
final IntSeq targets = new IntSeq();
|
||||
/** current search ID */
|
||||
int search = 1;
|
||||
/** last updated time */
|
||||
|
||||
@@ -8,6 +8,7 @@ import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
@@ -23,11 +24,21 @@ public class WaveSpawner{
|
||||
private Seq<Tile> spawns = new Seq<>();
|
||||
private boolean spawning = false;
|
||||
private boolean any = false;
|
||||
private Tile firstSpawn = null;
|
||||
|
||||
public WaveSpawner(){
|
||||
Events.on(WorldLoadEvent.class, e -> reset());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Tile getFirstSpawn(){
|
||||
firstSpawn = null;
|
||||
eachGroundSpawn((cx, cy) -> {
|
||||
firstSpawn = world.tile(cx, cy);
|
||||
});
|
||||
return firstSpawn;
|
||||
}
|
||||
|
||||
public int countSpawns(){
|
||||
return spawns.size;
|
||||
}
|
||||
@@ -38,16 +49,22 @@ public class WaveSpawner{
|
||||
|
||||
/** @return true if the player is near a ground spawn point. */
|
||||
public boolean playerNear(){
|
||||
return !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
||||
return state.hasSpawns() && !player.dead() && spawns.contains(g -> Mathf.dst(g.x * tilesize, g.y * tilesize, player.x, player.y) < state.rules.dropZoneRadius && player.team() != state.rules.waveTeam);
|
||||
}
|
||||
|
||||
public void spawnEnemies(){
|
||||
spawning = true;
|
||||
|
||||
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
|
||||
if(doShockwave){
|
||||
doShockwave(spawnX, spawnY);
|
||||
}
|
||||
});
|
||||
|
||||
for(SpawnGroup group : state.rules.spawns){
|
||||
if(group.type == null) continue;
|
||||
|
||||
int spawned = group.getUnitsSpawned(state.wave - 1);
|
||||
int spawned = group.getSpawned(state.wave - 1);
|
||||
|
||||
if(group.type.flying){
|
||||
float spread = margin / 1.5f;
|
||||
@@ -69,25 +86,29 @@ public class WaveSpawner{
|
||||
|
||||
Unit unit = group.createUnit(state.rules.waveTeam, state.wave - 1);
|
||||
unit.set(spawnX + Tmp.v1.x, spawnY + Tmp.v1.y);
|
||||
Time.run(Math.min(i * 5, 60 * 2), () -> spawnEffect(unit));
|
||||
spawnEffect(unit);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
eachGroundSpawn((spawnX, spawnY, doShockwave) -> {
|
||||
if(doShockwave){
|
||||
Time.run(20f, () -> Fx.spawnShockwave.at(spawnX, spawnY, state.rules.dropZoneRadius));
|
||||
Time.run(40f, () -> Damage.damage(state.rules.waveTeam, spawnX, spawnY, state.rules.dropZoneRadius, 99999999f, true));
|
||||
}
|
||||
});
|
||||
|
||||
Time.runTask(121f, () -> spawning = false);
|
||||
}
|
||||
|
||||
public void doShockwave(float x, float y){
|
||||
Fx.spawnShockwave.at(x, y, state.rules.dropZoneRadius);
|
||||
Damage.damage(state.rules.waveTeam, x, y, state.rules.dropZoneRadius, 99999999f, true);
|
||||
}
|
||||
|
||||
public void eachGroundSpawn(Intc2 cons){
|
||||
eachGroundSpawn((x, y, shock) -> cons.get(World.toTile(x), World.toTile(y)));
|
||||
}
|
||||
|
||||
private void eachGroundSpawn(SpawnConsumer cons){
|
||||
for(Tile spawn : spawns){
|
||||
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||
if(state.hasSpawns()){
|
||||
for(Tile spawn : spawns){
|
||||
cons.accept(spawn.worldx(), spawn.worldy(), true);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.rules.attackMode && state.teams.isActive(state.rules.waveTeam) && !state.teams.playerCores().isEmpty()){
|
||||
@@ -100,7 +121,7 @@ public class WaveSpawner{
|
||||
|
||||
//keep moving forward until the max step amount is reached
|
||||
while(steps++ < maxSteps){
|
||||
int tx = world.toTile(core.x + Tmp.v1.x), ty = world.toTile(core.y + Tmp.v1.y);
|
||||
int tx = World.toTile(core.x + Tmp.v1.x), ty = World.toTile(core.y + Tmp.v1.y);
|
||||
any = false;
|
||||
Geometry.circle(tx, ty, world.width(), world.height(), 3, (x, y) -> {
|
||||
if(world.solid(x, y)){
|
||||
@@ -157,7 +178,7 @@ public class WaveSpawner{
|
||||
}
|
||||
|
||||
private void spawnEffect(Unit unit){
|
||||
Call.spawnEffect(unit.x, unit.y, unit.type());
|
||||
Call.spawnEffect(unit.x, unit.y, unit.type);
|
||||
Time.run(30f, unit::add);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,8 @@ import arc.struct.*;
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Formation{
|
||||
|
||||
/** A list of slots assignments. */
|
||||
public Seq<SlotAssignment> slotAssignments;
|
||||
|
||||
/** The anchor point of this formation. */
|
||||
public Vec3 anchor;
|
||||
/** The formation pattern */
|
||||
@@ -138,7 +136,6 @@ public class Formation{
|
||||
* @return {@code false} if no more slots are available; {@code true} otherwise.
|
||||
*/
|
||||
public boolean addMember(FormationMember member){
|
||||
|
||||
// Check if the pattern supports one more slot
|
||||
if(pattern.supportsSlots(slotAssignments.size + 1)){
|
||||
// Add a new slot assignment
|
||||
|
||||
@@ -16,8 +16,9 @@ public class FreeSlotAssignmentStrategy implements SlotAssignmentStrategy{
|
||||
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
|
||||
// A very simple assignment algorithm: we simply go through
|
||||
// each assignment in the list and assign sequential slot numbers
|
||||
for(int i = 0; i < assignments.size; i++)
|
||||
for(int i = 0; i < assignments.size; i++){
|
||||
assignments.get(i).slotNumber = i;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,7 +51,6 @@ public class SoftRoleSlotAssignmentStrategy extends BoundedSlotAssignmentStrateg
|
||||
|
||||
@Override
|
||||
public void updateSlotAssignments(Seq<SlotAssignment> assignments){
|
||||
|
||||
// Holds a list of member and slot data for each member.
|
||||
Seq<MemberAndSlots> memberData = new Seq<>();
|
||||
|
||||
@@ -125,7 +124,6 @@ public class SoftRoleSlotAssignmentStrategy extends BoundedSlotAssignmentStrateg
|
||||
// Some sensible action should be taken, such as reporting to the player.
|
||||
throw new ArcRuntimeException("SoftRoleSlotAssignmentStrategy cannot find valid slot assignment for member " + memberDatum.member);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CostAndSlot implements Comparable<CostAndSlot>{
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class ArrowFormation extends FormationPattern{
|
||||
//total triangular numbers
|
||||
private static final int totalTris = 30;
|
||||
//triangular number table
|
||||
private static final int[] triTable = new int[totalTris];
|
||||
|
||||
//calculat triangular numbers
|
||||
static{
|
||||
int sum = 0;
|
||||
for(int i = 0; i < totalTris; i++){
|
||||
triTable[i] = sum;
|
||||
sum += (i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
//TODO
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ public class CircleFormation extends FormationPattern{
|
||||
float radius = spacing / (float)Math.sin(180f / slots * Mathf.degRad);
|
||||
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
|
||||
}else{
|
||||
outLocation.set(0, 0, 360f * slotNumber);
|
||||
outLocation.set(0, spacing * 1.1f, 360f * slotNumber);
|
||||
}
|
||||
|
||||
outLocation.z += angleOffset;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -14,17 +15,16 @@ import static mindustry.Vars.*;
|
||||
public class BuilderAI extends AIController{
|
||||
float buildRadius = 1500;
|
||||
boolean found = false;
|
||||
@Nullable Builderc following;
|
||||
@Nullable Unit following;
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
Builderc builder = (Builderc)unit;
|
||||
public void updateMovement(){
|
||||
|
||||
if(builder.moving()){
|
||||
builder.lookAt(builder.vel().angle());
|
||||
if(target != null && shouldShoot()){
|
||||
unit.lookAt(target);
|
||||
}
|
||||
|
||||
builder.updateBuilding(true);
|
||||
unit.updateBuilding = true;
|
||||
|
||||
if(following != null){
|
||||
//try to follow and mimic someone
|
||||
@@ -32,21 +32,31 @@ public class BuilderAI extends AIController{
|
||||
//validate follower
|
||||
if(!following.isValid() || !following.activelyBuilding()){
|
||||
following = null;
|
||||
builder.plans().clear();
|
||||
unit.plans.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
//set to follower's first build plan, whatever that is
|
||||
builder.plans().clear();
|
||||
builder.plans().addFirst(following.buildPlan());
|
||||
unit.plans.clear();
|
||||
unit.plans.addFirst(following.buildPlan());
|
||||
}
|
||||
|
||||
if(builder.buildPlan() != null){
|
||||
if(unit.buildPlan() != null){
|
||||
//approach request if building
|
||||
BuildPlan req = builder.buildPlan();
|
||||
BuildPlan req = unit.buildPlan();
|
||||
|
||||
//clear break plan if another player is breaking something.
|
||||
if(!req.breaking && timer.get(timerTarget2, 40f)){
|
||||
for(Player player : Groups.player){
|
||||
if(player.isBuilder() && player.unit().activelyBuilding() && player.unit().buildPlan().samePos(req) && player.unit().buildPlan().breaking){
|
||||
unit.plans.removeFirst();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean valid =
|
||||
(req.tile().build instanceof ConstructBuild && req.tile().<ConstructBuild>bc().cblock == req.block) ||
|
||||
(req.tile() != null && req.tile().build instanceof ConstructBuild cons && cons.cblock == req.block) ||
|
||||
(req.breaking ?
|
||||
Build.validBreak(unit.team(), req.x, req.y) :
|
||||
Build.validPlace(req.block, unit.team(), req.x, req.y, req.rotation));
|
||||
@@ -56,7 +66,7 @@ public class BuilderAI extends AIController{
|
||||
moveTo(req.tile(), buildingRange - 20f);
|
||||
}else{
|
||||
//discard invalid request
|
||||
builder.plans().removeFirst();
|
||||
unit.plans.removeFirst();
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -67,18 +77,16 @@ public class BuilderAI extends AIController{
|
||||
Units.nearby(unit.team, unit.x, unit.y, buildRadius, u -> {
|
||||
if(found) return;
|
||||
|
||||
if(u instanceof Builderc && u != unit && ((Builderc)u).activelyBuilding()){
|
||||
Builderc b = (Builderc)u;
|
||||
BuildPlan plan = b.buildPlan();
|
||||
if(u.canBuild() && u != unit && u.activelyBuilding()){
|
||||
BuildPlan plan = u.buildPlan();
|
||||
|
||||
Building build = world.build(plan.x, plan.y);
|
||||
if(build instanceof ConstructBuild){
|
||||
ConstructBuild cons = (ConstructBuild)build;
|
||||
if(build instanceof ConstructBuild cons){
|
||||
float dist = Math.min(cons.dst(unit) - buildingRange, 0);
|
||||
|
||||
//make sure you can reach the request in time
|
||||
if(dist / unit.type().speed < cons.buildCost * 0.9f){
|
||||
following = b;
|
||||
if(dist / unit.speed() < cons.buildCost * 0.9f){
|
||||
following = u;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
@@ -86,9 +94,11 @@ public class BuilderAI extends AIController{
|
||||
});
|
||||
}
|
||||
|
||||
float rebuildTime = (unit.team.rules().ai ? Mathf.lerp(15f, 2f, unit.team.rules().aiTier) : 2f) * 60f;
|
||||
|
||||
//find new request
|
||||
if(!unit.team().data().blocks.isEmpty() && following == null && timer.get(timerTarget3, 60 * 2f)){
|
||||
Queue<BlockPlan> blocks = unit.team().data().blocks;
|
||||
if(!unit.team.data().blocks.isEmpty() && following == null && timer.get(timerTarget3, rebuildTime)){
|
||||
Queue<BlockPlan> blocks = unit.team.data().blocks;
|
||||
BlockPlan block = blocks.first();
|
||||
|
||||
//check if it's already been placed
|
||||
@@ -96,14 +106,30 @@ public class BuilderAI extends AIController{
|
||||
blocks.removeFirst();
|
||||
}else if(Build.validPlace(content.block(block.block), unit.team(), block.x, block.y, block.rotation)){ //it's valid.
|
||||
//add build request.
|
||||
builder.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
|
||||
unit.addBuild(new BuildPlan(block.x, block.y, block.rotation, content.block(block.block), block.config));
|
||||
//shift build plan to tail so next unit builds something else.
|
||||
blocks.addLast(blocks.removeFirst());
|
||||
}else{
|
||||
//shift head of queue to tail, try something else next time
|
||||
blocks.removeFirst();
|
||||
blocks.addLast(block);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AIController fallback(){
|
||||
return unit.type.flying ? new FlyingAI() : new GroundAI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useFallback(){
|
||||
return state.rules.waves && unit.team == state.rules.waveTeam && !unit.team.rules().ai;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShoot(){
|
||||
return !unit.isBuilding();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ public class FlyingAI extends AIController{
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
if(target != null && unit.hasWeapons() && command() == UnitCommand.attack){
|
||||
if(unit.type().weapons.first().rotate){
|
||||
if(!unit.type.circleTarget){
|
||||
moveTo(target, unit.range() * 0.8f);
|
||||
unit.lookAt(target);
|
||||
}else{
|
||||
attack(80f);
|
||||
attack(120f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,15 @@ public class FlyingAI extends AIController{
|
||||
Teamc result = target(x, y, range, air, ground);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.producer, true);
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.generator, true);
|
||||
if(result != null) return result;
|
||||
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.turret, true);
|
||||
if(ground) result = targetFlag(x, y, BlockFlag.core, true);
|
||||
if(result != null) return result;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO clean up
|
||||
|
||||
protected void attack(float circleLength){
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
@@ -57,7 +55,7 @@ public class FlyingAI extends AIController{
|
||||
vec.setAngle(Mathf.slerpDelta(unit.vel().angle(), vec.angle(), 0.6f));
|
||||
}
|
||||
|
||||
vec.setLength(unit.type().speed);
|
||||
vec.setLength(unit.speed());
|
||||
|
||||
unit.moveAt(vec);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
public class FormationAI extends AIController implements FormationMember{
|
||||
public Unit leader;
|
||||
@@ -26,36 +26,52 @@ public class FormationAI extends AIController implements FormationMember{
|
||||
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
UnitType type = unit.type();
|
||||
|
||||
if(leader.dead){
|
||||
if(leader == null || leader.dead){
|
||||
unit.resetController();
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.type().canBoost && unit.canPassOn()){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||
if(unit.type.canBoost){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, unit.onSolid() ? 1f : leader.type.canBoost ? leader.elevation : 0f, 0.08f);
|
||||
}
|
||||
|
||||
unit.controlWeapons(true, leader.isShooting);
|
||||
// unit.moveAt(Tmp.v1.set(deltaX, deltaY).limit(unit.type().speed));
|
||||
|
||||
unit.aim(leader.aimX(), leader.aimY());
|
||||
|
||||
if(unit.type().rotateShooting){
|
||||
if(unit.type.rotateShooting){
|
||||
unit.lookAt(leader.aimX(), leader.aimY());
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel.angle());
|
||||
}
|
||||
|
||||
Vec2 realtarget = vec.set(target);
|
||||
Vec2 realtarget = vec.set(target).add(leader.vel.x, leader.vel.y);
|
||||
|
||||
float margin = 3f;
|
||||
float speed = unit.realSpeed() * unit.floorSpeedMultiplier() * 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.dst(realtarget) <= margin){
|
||||
unit.vel.approachDelta(Vec2.ZERO, type.speed * type.accel / 2f);
|
||||
}else{
|
||||
unit.moveAt(realtarget.sub(unit).limit(type.speed));
|
||||
if(unit.canMine() && leader.canMine()){
|
||||
if(leader.mineTile != null && unit.validMine(leader.mineTile)){
|
||||
unit.mineTile(leader.mineTile);
|
||||
|
||||
CoreBuild core = unit.team.core();
|
||||
|
||||
if(core != null && leader.mineTile.drop() != null && unit.within(core, unit.type.range) && !unit.acceptsItem(leader.mineTile.drop())){
|
||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||
Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
|
||||
unit.clearItem();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
unit.mineTile(null);
|
||||
}
|
||||
}
|
||||
|
||||
if(unit.canBuild() && leader.canBuild() && leader.activelyBuilding()){
|
||||
unit.clearBuilding();
|
||||
unit.addBuild(leader.buildPlan());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,11 +85,7 @@ public class FormationAI extends AIController implements FormationMember{
|
||||
|
||||
@Override
|
||||
public float formationSize(){
|
||||
if(unit instanceof Commanderc && ((Commanderc)unit).isCommanding()){
|
||||
//TODO return formation size
|
||||
//eturn ((Commanderc)unit).formation().
|
||||
}
|
||||
return unit.hitSize * 1f;
|
||||
return unit.hitSize * 1.1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.util.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class GroundAI extends AIController{
|
||||
//static final float commandCooldown = 60f * 10;
|
||||
//float commandTimer = 60*3;
|
||||
|
||||
@Override
|
||||
public void updateMovement(){
|
||||
@@ -34,55 +32,28 @@ public class GroundAI extends AIController{
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
|
||||
}
|
||||
|
||||
if(move) moveTo(Pathfinder.fieldCore);
|
||||
if(move) pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
moveTo(Pathfinder.fieldRally);
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
}
|
||||
}
|
||||
|
||||
if(unit.type().canBoost && unit.canPassOn()){
|
||||
if(unit.type.canBoost && !unit.onSolid()){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, 0f, 0.08f);
|
||||
}
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type().rotateShooting){
|
||||
if(unit.type().hasWeapons()){
|
||||
//TODO certain units should not look at the target, e.g. ships
|
||||
unit.lookAt(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.type.rotateShooting){
|
||||
if(unit.type.hasWeapons()){
|
||||
unit.lookAt(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
|
||||
}
|
||||
}else if(unit.moving()){
|
||||
unit.lookAt(unit.vel().angle());
|
||||
}
|
||||
|
||||
//auto-command works but it's very buggy
|
||||
/*
|
||||
if(unit instanceof Commanderc){
|
||||
Commanderc c = (Commanderc)unit;
|
||||
//try to command when missing members
|
||||
if(c.controlling().size <= unit.type().commandLimit/2){
|
||||
commandTimer -= Time.delta;
|
||||
|
||||
if(commandTimer <= 0){
|
||||
c.commandNearby(new SquareFormation(), u -> !(u.controller() instanceof FormationAI) && !(u instanceof Commanderc));
|
||||
commandTimer = commandCooldown;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
protected void moveTo(int pathTarget){
|
||||
int costType = unit.pathType();
|
||||
|
||||
Tile tile = unit.tileOn();
|
||||
if(tile == null) return;
|
||||
Tile targetTile = pathfinder.getTargetTile(tile, pathfinder.getField(unit.team, costType, pathTarget));
|
||||
|
||||
if(tile == targetTile || (costType == Pathfinder.costWater && !targetTile.floor().isLiquid)) return;
|
||||
|
||||
unit.moveAt(vec.trns(unit.angleTo(targetTile), unit.type().speed));
|
||||
}
|
||||
}
|
||||
|
||||
159
core/src/mindustry/ai/types/LogicAI.java
Normal file
159
core/src/mindustry/ai/types/LogicAI.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ai.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LogicAI extends AIController{
|
||||
/** Minimum delay between item transfers. */
|
||||
public static final float transferDelay = 60f * 2f;
|
||||
/** 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 float moveX, moveY, moveRad;
|
||||
public float itemTimer, payTimer, controlTimer = logicControlTimeout, targetTimer;
|
||||
@Nullable
|
||||
public Building controller;
|
||||
public BuildPlan plan = new BuildPlan();
|
||||
|
||||
//special cache for instruction to store data
|
||||
public ObjectMap<Object, Object> execCache = new ObjectMap<>();
|
||||
|
||||
//type of aiming to use
|
||||
public LUnitControl aimControl = LUnitControl.stop;
|
||||
|
||||
//whether to use the boost (certain units only)
|
||||
public boolean boost;
|
||||
//main target set for shootP
|
||||
public Teamc mainTarget;
|
||||
//whether to shoot at all
|
||||
public boolean shoot;
|
||||
//target shoot positions for manual aiming
|
||||
public PosTeam posTarget = PosTeam.create();
|
||||
|
||||
private ObjectSet<Object> radars = new ObjectSet<>();
|
||||
|
||||
@Override
|
||||
protected void updateMovement(){
|
||||
if(itemTimer >= 0) itemTimer -= Time.delta;
|
||||
if(payTimer >= 0) payTimer -= Time.delta;
|
||||
|
||||
if(targetTimer > 0f){
|
||||
targetTimer -= Time.delta;
|
||||
}else{
|
||||
radars.clear();
|
||||
targetTimer = 40f;
|
||||
}
|
||||
|
||||
//timeout when not controlled by logic for a while
|
||||
if(controlTimer > 0 && controller != null && controller.isValid()){
|
||||
controlTimer -= Time.delta;
|
||||
}else{
|
||||
unit.resetController();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(control){
|
||||
case move -> {
|
||||
moveTo(Tmp.v1.set(moveX, moveY), 1f, 30f);
|
||||
}
|
||||
case approach -> {
|
||||
moveTo(Tmp.v1.set(moveX, moveY), moveRad - 7f, 7);
|
||||
}
|
||||
case pathfind -> {
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
if((core == null || !unit.within(core, unit.range() * 0.5f)) && command() == UnitCommand.attack){
|
||||
boolean move = true;
|
||||
|
||||
if(state.rules.waves && unit.team == state.rules.defaultTeam){
|
||||
Tile spawner = getClosestSpawner();
|
||||
if(spawner != null && unit.within(spawner, state.rules.dropZoneRadius + 120f)) move = false;
|
||||
}
|
||||
|
||||
if(move) pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(command() == UnitCommand.rally){
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
}
|
||||
}
|
||||
}
|
||||
case stop -> {
|
||||
unit.clearBuilding();
|
||||
}
|
||||
}
|
||||
|
||||
if(unit.type.canBoost && !unit.type.flying){
|
||||
unit.elevation = Mathf.approachDelta(unit.elevation, Mathf.num(boost || unit.onSolid()), 0.08f);
|
||||
}
|
||||
|
||||
//look where moving if there's nothing to aim at
|
||||
if(!shoot){
|
||||
unit.lookAt(unit.prefRotation());
|
||||
}else if(unit.hasWeapons() && unit.mounts.length > 0){ //if there is, look at the object
|
||||
unit.lookAt(unit.mounts[0].aimX, unit.mounts[0].aimY);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkTargetTimer(Object radar){
|
||||
return radars.add(radar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveTo(Position target, float circleLength, float smooth){
|
||||
if(target == null) return;
|
||||
|
||||
vec.set(target).sub(unit);
|
||||
|
||||
float length = circleLength <= 0.001f ? 1f : Mathf.clamp((unit.dst(target) - circleLength) / smooth, -1f, 1f);
|
||||
|
||||
vec.setLength(unit.realSpeed() * length);
|
||||
if(length < -0.5f){
|
||||
vec.rotate(180f);
|
||||
}else if(length < 0){
|
||||
vec.setZero();
|
||||
}
|
||||
|
||||
unit.approach(vec);
|
||||
}
|
||||
|
||||
//always retarget
|
||||
@Override
|
||||
protected boolean retarget(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean invalid(Teamc target){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShoot(){
|
||||
return shoot && !(unit.type.canBoost && boost);
|
||||
}
|
||||
|
||||
//always aim for the main target
|
||||
@Override
|
||||
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
|
||||
return switch(aimControl){
|
||||
case target -> posTarget;
|
||||
case targetp -> mainTarget;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -17,37 +17,37 @@ public class MinerAI extends AIController{
|
||||
protected void updateMovement(){
|
||||
Building core = unit.closestCore();
|
||||
|
||||
if(!(unit instanceof Minerc) || core == null) return;
|
||||
if(!(unit.canMine()) || core == null) return;
|
||||
|
||||
Minerc miner = (Minerc)unit;
|
||||
|
||||
if(miner.mineTile() != null && !miner.mineTile().within(unit, unit.type().range)){
|
||||
miner.mineTile(null);
|
||||
if(unit.mineTile != null && !unit.mineTile.within(unit, unit.type.range)){
|
||||
unit.mineTile(null);
|
||||
}
|
||||
|
||||
if(mining){
|
||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && miner.canMine(i), i -> core.items.get(i));
|
||||
if(timer.get(timerTarget2, 60 * 4) || targetItem == null){
|
||||
targetItem = unit.team.data().mineItems.min(i -> indexer.hasOre(i) && unit.canMine(i), i -> core.items.get(i));
|
||||
}
|
||||
|
||||
//core full of the target item, do nothing
|
||||
if(targetItem != null && core.acceptStack(targetItem, 1, unit) == 0){
|
||||
unit.clearItem();
|
||||
miner.mineTile(null);
|
||||
unit.mineTile(null);
|
||||
return;
|
||||
}
|
||||
|
||||
//if inventory is full, drop it off.
|
||||
if(unit.stack.amount >= unit.type().itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
||||
if(unit.stack.amount >= unit.type.itemCapacity || (targetItem != null && !unit.acceptsItem(targetItem))){
|
||||
mining = false;
|
||||
}else{
|
||||
if(retarget() && targetItem != null){
|
||||
ore = indexer.findClosestOre(unit.x, unit.y, targetItem);
|
||||
if(timer.get(timerTarget, 60) && targetItem != null){
|
||||
ore = indexer.findClosestOre(unit, targetItem);
|
||||
}
|
||||
|
||||
if(ore != null){
|
||||
moveTo(ore, unit.type().range / 2f);
|
||||
moveTo(ore, unit.type.range / 2f, 20f);
|
||||
|
||||
if(unit.within(ore, unit.type().range)){
|
||||
miner.mineTile(ore);
|
||||
if(unit.within(ore, unit.type.range)){
|
||||
unit.mineTile = ore;
|
||||
}
|
||||
|
||||
if(ore.block() != Blocks.air){
|
||||
@@ -56,28 +56,27 @@ public class MinerAI extends AIController{
|
||||
}
|
||||
}
|
||||
}else{
|
||||
miner.mineTile(null);
|
||||
unit.mineTile = null;
|
||||
|
||||
if(unit.stack.amount == 0){
|
||||
mining = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(unit.within(core, unit.type().range)){
|
||||
if(unit.within(core, unit.type.range)){
|
||||
if(core.acceptStack(unit.stack.item, unit.stack.amount, unit) > 0){
|
||||
Call.transferItemTo(unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
Call.transferItemTo(unit, unit.stack.item, unit.stack.amount, unit.x, unit.y, core);
|
||||
}
|
||||
|
||||
unit.clearItem();
|
||||
mining = true;
|
||||
}
|
||||
|
||||
circle(core, unit.type().range / 1.8f);
|
||||
circle(core, unit.type.range / 1.8f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTargeting(){
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,33 +2,45 @@ package mindustry.ai.types;
|
||||
|
||||
import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
|
||||
//note that repair AI doesn't attack anything even if it theoretically can
|
||||
public class RepairAI extends AIController{
|
||||
|
||||
@Override
|
||||
protected void updateMovement(){
|
||||
boolean shoot = false;
|
||||
if(target instanceof Building){
|
||||
boolean shoot = false;
|
||||
|
||||
if(target != null){
|
||||
if(!target.within(unit, unit.type().range * 0.8f)){
|
||||
moveTo(target, unit.type().range * 0.8f);
|
||||
}
|
||||
|
||||
if(target.within(unit, unit.type().range)){
|
||||
if(target.within(unit, unit.type.range)){
|
||||
unit.aim(target);
|
||||
shoot = true;
|
||||
}
|
||||
|
||||
unit.controlWeapons(shoot);
|
||||
}else if(target == null){
|
||||
unit.controlWeapons(false);
|
||||
}
|
||||
|
||||
unit.controlWeapons(shoot);
|
||||
if(target != null){
|
||||
if(!target.within(unit, unit.type.range * 0.65f) && target instanceof Building b && b.team == unit.team){
|
||||
moveTo(target, unit.type.range * 0.65f);
|
||||
}
|
||||
|
||||
unit.lookAt(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTargeting(){
|
||||
target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
||||
Building target = Units.findDamagedTile(unit.team, unit.x, unit.y);
|
||||
|
||||
if(target instanceof ConstructBuild) target = null;
|
||||
|
||||
if(target == null){
|
||||
super.updateTargeting();
|
||||
}else{
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import mindustry.entities.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.distribution.*;
|
||||
import mindustry.world.blocks.liquid.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
public class SuicideAI extends GroundAI{
|
||||
@@ -14,52 +16,58 @@ public class SuicideAI extends GroundAI{
|
||||
@Override
|
||||
public void updateUnit(){
|
||||
|
||||
if(Units.invalidateTarget(target, unit.team(), unit.x(), unit.y(), Float.MAX_VALUE)){
|
||||
if(Units.invalidateTarget(target, unit.team, unit.x, unit.y, Float.MAX_VALUE)){
|
||||
target = null;
|
||||
}
|
||||
|
||||
if(retarget()){
|
||||
target = target(unit.x, unit.y, unit.range(), unit.type().targetAir, unit.type().targetGround);
|
||||
target = target(unit.x, unit.y, unit.range(), unit.type.targetAir, unit.type.targetGround);
|
||||
}
|
||||
|
||||
Building core = unit.closestEnemyCore();
|
||||
|
||||
boolean rotate = false, shoot = false, moveToTarget = false;
|
||||
|
||||
if(!Units.invalidateTarget(target, unit, unit.range())){
|
||||
if(!Units.invalidateTarget(target, unit, unit.range()) && unit.hasWeapons()){
|
||||
rotate = true;
|
||||
shoot = unit.within(target, unit.type().weapons.first().bullet.range() +
|
||||
(target instanceof Building ? ((Building)target).block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||
shoot = unit.within(target, unit.type.weapons.first().bullet.range() +
|
||||
(target instanceof Building b ? b.block.size * Vars.tilesize / 2f : ((Hitboxc)target).hitSize() / 2f));
|
||||
|
||||
if(unit.type().hasWeapons()){
|
||||
unit.aimLook(Predict.intercept(unit, target, unit.type().weapons.first().bullet.speed));
|
||||
if(unit.type.hasWeapons()){
|
||||
unit.aimLook(Predict.intercept(unit, target, unit.type.weapons.first().bullet.speed));
|
||||
}
|
||||
|
||||
blockedByBlock = false;
|
||||
//do not move toward walls or transport blocks
|
||||
if(!(target instanceof Building build && (
|
||||
build.block.group == BlockGroup.walls ||
|
||||
build.block.group == BlockGroup.liquids ||
|
||||
build.block.group == BlockGroup.transportation
|
||||
))){
|
||||
blockedByBlock = false;
|
||||
|
||||
//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();
|
||||
//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();
|
||||
}
|
||||
});
|
||||
|
||||
//shoot when there's an enemy block in the way
|
||||
if(blockedByBlock){
|
||||
shoot = true;
|
||||
}
|
||||
});
|
||||
|
||||
//shoot when there's an enemy block in the way
|
||||
if(blockedByBlock){
|
||||
shoot = true;
|
||||
if(!blocked){
|
||||
moveToTarget = true;
|
||||
//move towards target directly
|
||||
unit.moveAt(vec.set(target).sub(unit).limit(unit.speed()));
|
||||
}
|
||||
}
|
||||
|
||||
if(!blocked){
|
||||
moveToTarget = true;
|
||||
//move towards target directly
|
||||
unit.moveAt(vec.set(target).sub(unit).limit(unit.type().speed));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(!moveToTarget){
|
||||
@@ -67,10 +75,10 @@ public class SuicideAI extends GroundAI{
|
||||
Teamc target = targetFlag(unit.x, unit.y, BlockFlag.rally, false);
|
||||
|
||||
if(target != null && !unit.within(target, 70f)){
|
||||
moveTo(Pathfinder.fieldRally);
|
||||
pathfind(Pathfinder.fieldRally);
|
||||
}
|
||||
}else if(command() == UnitCommand.attack && core != null){
|
||||
moveTo(Pathfinder.fieldCore);
|
||||
pathfind(Pathfinder.fieldCore);
|
||||
}
|
||||
|
||||
if(unit.moving()) unit.lookAt(unit.vel().angle());
|
||||
@@ -78,4 +86,10 @@ public class SuicideAI extends GroundAI{
|
||||
|
||||
unit.controlWeapons(rotate, shoot);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Teamc target(float x, float y, float range, boolean air, boolean ground){
|
||||
return Units.closestTarget(unit.team, x, y, range, u -> u.checkTarget(air, ground), t -> ground &&
|
||||
!(t.block instanceof Conveyor || t.block instanceof Conduit)); //do not target conveyors/conduits
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,16 @@ package mindustry.async;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.EventType.*;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static mindustry.Vars.state;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class AsyncCore{
|
||||
//all processes to be executed each frame
|
||||
private final Seq<AsyncProcess> processes = Seq.with(
|
||||
new PhysicsProcess(),
|
||||
Vars.teamIndex = new TeamIndexProcess()
|
||||
new PhysicsProcess()
|
||||
);
|
||||
|
||||
//futures to be awaited
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
package mindustry.async;
|
||||
|
||||
import arc.box2d.*;
|
||||
import arc.box2d.BodyDef.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.math.geom.QuadTree.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.async.PhysicsProcess.PhysicsWorld.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class PhysicsProcess implements AsyncProcess{
|
||||
private Physics physics;
|
||||
private static final int
|
||||
layers = 3,
|
||||
layerGround = 0,
|
||||
layerLegs = 1,
|
||||
layerFlying = 2;
|
||||
|
||||
private PhysicsWorld physics;
|
||||
private Seq<PhysicRef> refs = new Seq<>(false);
|
||||
private BodyDef def;
|
||||
|
||||
private EntityGroup<? extends Physicsc> group;
|
||||
private Filter flying = new Filter(){{
|
||||
maskBits = categoryBits = 2;
|
||||
}}, ground = new Filter(){{
|
||||
maskBits = categoryBits = 1;
|
||||
}};
|
||||
|
||||
public PhysicsProcess(){
|
||||
def = new BodyDef();
|
||||
def.type = BodyType.dynamicBody;
|
||||
|
||||
//currently only enabled for units
|
||||
group = Groups.unit;
|
||||
}
|
||||
//currently only enabled for units
|
||||
private EntityGroup<Unit> group = Groups.unit;
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
@@ -34,47 +28,39 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
//remove stale entities
|
||||
refs.removeAll(ref -> {
|
||||
if(!ref.entity.isAdded()){
|
||||
physics.destroyBody(ref.body);
|
||||
physics.remove(ref.body);
|
||||
ref.entity.physref(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
//find entities without bodies and assign them
|
||||
for(Physicsc entity : group){
|
||||
boolean grounded = entity.isGrounded();
|
||||
int bits = grounded ? ground.maskBits : flying.maskBits;
|
||||
//find Unit without bodies and assign them
|
||||
for(Unit entity : group){
|
||||
|
||||
if(entity.physref() == null){
|
||||
//add bodies to entities that have none
|
||||
FixtureDef fd = new FixtureDef();
|
||||
fd.shape = new CircleShape(entity.hitSize() / 2f);
|
||||
fd.density = 5f;
|
||||
fd.restitution = 0.0f;
|
||||
fd.filter.maskBits = fd.filter.categoryBits = (grounded ? ground : flying).maskBits;
|
||||
|
||||
def.position.set(entity);
|
||||
|
||||
Body body = physics.createBody(def);
|
||||
body.createFixture(fd);
|
||||
PhysicsBody body = new PhysicsBody();
|
||||
body.x = entity.x();
|
||||
body.y = entity.y();
|
||||
body.mass = entity.mass();
|
||||
body.radius = entity.hitSize() / 2f;
|
||||
|
||||
PhysicRef ref = new PhysicRef(entity, body);
|
||||
refs.add(ref);
|
||||
|
||||
entity.physref(ref);
|
||||
|
||||
physics.add(body);
|
||||
}
|
||||
|
||||
//save last position
|
||||
PhysicRef ref = entity.physref();
|
||||
|
||||
if(ref.body.getFixtureList().any() && ref.body.getFixtureList().first().getFilterData().categoryBits != bits){
|
||||
//set correct filter
|
||||
ref.body.getFixtureList().first().setFilterData(grounded ? ground : flying);
|
||||
}
|
||||
|
||||
ref.velocity.set(entity.deltaX(), entity.deltaY());
|
||||
ref.position.set(entity);
|
||||
ref.body.layer =
|
||||
entity.type.allowLegStep ? layerLegs :
|
||||
entity.isGrounded() ? layerGround : layerFlying;
|
||||
ref.x = entity.x();
|
||||
ref.y = entity.y();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,26 +71,11 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
//get last position vectors before step
|
||||
for(PhysicRef ref : refs){
|
||||
//force set target position
|
||||
ref.body.setPosition(ref.position.x, ref.position.y);
|
||||
|
||||
//save last position for delta
|
||||
ref.lastPosition.set(ref.body.getPosition());
|
||||
|
||||
//write velocity
|
||||
ref.body.setLinearVelocity(ref.velocity);
|
||||
|
||||
ref.lastVelocity.set(ref.velocity);
|
||||
ref.body.x = ref.x;
|
||||
ref.body.y = ref.y;
|
||||
}
|
||||
|
||||
physics.step(1f/45f, 5, 8);
|
||||
|
||||
//get delta vectors
|
||||
for(PhysicRef ref : refs){
|
||||
//get delta vector
|
||||
ref.delta.set(ref.body.getPosition()).sub(ref.lastPosition);
|
||||
|
||||
ref.velocity.set(ref.body.getLinearVelocity());
|
||||
}
|
||||
physics.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,13 +86,8 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
for(PhysicRef ref : refs){
|
||||
Physicsc entity = ref.entity;
|
||||
|
||||
entity.move(ref.delta.x, ref.delta.y);
|
||||
|
||||
//save last position
|
||||
ref.position.set(entity);
|
||||
|
||||
//add delta velocity - this doesn't work very well yet
|
||||
//entity.vel().add(ref.velocity).sub(ref.lastVelocity);
|
||||
//move by delta
|
||||
entity.move(ref.body.x - ref.x, ref.body.y - ref.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +95,6 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
public void reset(){
|
||||
if(physics != null){
|
||||
refs.clear();
|
||||
physics.dispose();
|
||||
physics = null;
|
||||
}
|
||||
}
|
||||
@@ -138,17 +103,95 @@ public class PhysicsProcess implements AsyncProcess{
|
||||
public void init(){
|
||||
reset();
|
||||
|
||||
physics = new Physics(new Vec2(), true);
|
||||
physics = new PhysicsWorld(Vars.world.getQuadBounds(new Rect()));
|
||||
}
|
||||
|
||||
public static class PhysicRef{
|
||||
public Physicsc entity;
|
||||
public Body body;
|
||||
public Vec2 lastPosition = new Vec2(), delta = new Vec2(), velocity = new Vec2(), lastVelocity = new Vec2(), position = new Vec2();
|
||||
public PhysicsBody body;
|
||||
public float x, y;
|
||||
|
||||
public PhysicRef(Physicsc entity, Body body){
|
||||
public PhysicRef(Physicsc entity, PhysicsBody body){
|
||||
this.entity = entity;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
//world for simulating physics in a different thread
|
||||
public static class PhysicsWorld{
|
||||
//how much to soften movement by
|
||||
private static final float scl = 1.25f;
|
||||
|
||||
private final QuadTree<PhysicsBody>[] trees = new QuadTree[layers];
|
||||
private final Seq<PhysicsBody> bodies = new Seq<>(false, 16, PhysicsBody.class);
|
||||
private final Seq<PhysicsBody> seq = new Seq<>(PhysicsBody.class);
|
||||
private final Rect rect = new Rect();
|
||||
private final Vec2 vec = new Vec2();
|
||||
|
||||
public PhysicsWorld(Rect bounds){
|
||||
for(int i = 0; i < layers; i++){
|
||||
trees[i] = new QuadTree<>(new Rect(bounds));
|
||||
}
|
||||
}
|
||||
|
||||
public void add(PhysicsBody body){
|
||||
bodies.add(body);
|
||||
}
|
||||
|
||||
public void remove(PhysicsBody body){
|
||||
bodies.remove(body);
|
||||
}
|
||||
|
||||
public void update(){
|
||||
for(int i = 0; i < layers; i++){
|
||||
trees[i].clear();
|
||||
}
|
||||
|
||||
for(int i = 0; i < bodies.size; i++){
|
||||
PhysicsBody body = bodies.items[i];
|
||||
body.collided = false;
|
||||
trees[body.layer].insert(body);
|
||||
}
|
||||
|
||||
for(int i = 0; i < bodies.size; i++){
|
||||
PhysicsBody body = bodies.items[i];
|
||||
body.hitbox(rect);
|
||||
|
||||
seq.size = 0;
|
||||
trees[body.layer].intersect(rect, seq);
|
||||
|
||||
for(int j = 0; j < seq.size; j++){
|
||||
PhysicsBody other = seq.items[j];
|
||||
|
||||
if(other == body || other.collided) continue;
|
||||
|
||||
float rs = body.radius + other.radius;
|
||||
float dst = Mathf.dst(body.x, body.y, other.x, other.y);
|
||||
|
||||
if(dst < rs){
|
||||
vec.set(body.x - other.x, body.y - other.y).setLength(rs - dst);
|
||||
float ms = body.mass + other.mass;
|
||||
float m1 = other.mass / ms, m2 = body.mass / ms;
|
||||
|
||||
body.x += vec.x * m1 / scl;
|
||||
body.y += vec.y * m1 / scl;
|
||||
other.x -= vec.x * m2 / scl;
|
||||
other.y -= vec.y * m2 / scl;
|
||||
}
|
||||
}
|
||||
body.collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PhysicsBody implements QuadTreeObject{
|
||||
public float x, y, radius, mass;
|
||||
public int layer = 0;
|
||||
public boolean collided = false;
|
||||
|
||||
@Override
|
||||
public void hitbox(Rect out){
|
||||
out.setCentered(x, y, radius * 2, radius * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package mindustry.async;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/** Creates quadtrees per unit team. */
|
||||
public class TeamIndexProcess implements AsyncProcess{
|
||||
private QuadTree<Unit>[] trees = new QuadTree[Team.all.length];
|
||||
private int[] counts = new int[Team.all.length];
|
||||
private int[][] typeCounts = new int[Team.all.length][0];
|
||||
|
||||
public QuadTree<Unit> tree(Team team){
|
||||
if(trees[team.id] == null) trees[team.id] = new QuadTree<>(Vars.world.getQuadBounds(new Rect()));
|
||||
|
||||
return trees[team.id];
|
||||
}
|
||||
|
||||
public int count(Team team){
|
||||
return counts[team.id];
|
||||
}
|
||||
|
||||
public int countType(Team team, UnitType type){
|
||||
return typeCounts[team.id].length <= type.id ? 0 : typeCounts[team.id][type.id];
|
||||
}
|
||||
|
||||
public void updateCount(Team team, UnitType type, int amount){
|
||||
counts[team.id] += amount;
|
||||
if(typeCounts[team.id].length <= type.id){
|
||||
typeCounts[team.id] = new int[Vars.content.units().size];
|
||||
}
|
||||
typeCounts[team.id][type.id] += amount;
|
||||
}
|
||||
|
||||
private void count(Unit unit){
|
||||
updateCount(unit.team, unit.type(), 1);
|
||||
|
||||
if(unit instanceof Payloadc){
|
||||
((Payloadc)unit).payloads().each(p -> {
|
||||
if(p instanceof UnitPayload){
|
||||
count(((UnitPayload)p).unit);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(){
|
||||
counts = new int[Team.all.length];
|
||||
trees = new QuadTree[Team.all.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin(){
|
||||
|
||||
for(Team team : Team.all){
|
||||
if(trees[team.id] != null){
|
||||
trees[team.id].clear();
|
||||
}
|
||||
|
||||
Arrays.fill(typeCounts[team.id], 0);
|
||||
}
|
||||
|
||||
Arrays.fill(counts, 0);
|
||||
|
||||
for(Unit unit : Groups.unit){
|
||||
tree(unit.team).insert(unit);
|
||||
|
||||
count(unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldProcess(){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package mindustry.audio;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.struct.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.*;
|
||||
|
||||
public class LoopControl{
|
||||
private ObjectMap<Sound, SoundData> sounds = new ObjectMap<>();
|
||||
|
||||
public void play(Sound sound, Position pos, float volume){
|
||||
if(Vars.headless) return;
|
||||
|
||||
float baseVol = sound.calcFalloff(pos.getX(), pos.getY());
|
||||
float vol = baseVol * volume;
|
||||
|
||||
SoundData data = sounds.get(sound, SoundData::new);
|
||||
data.volume += vol;
|
||||
data.volume = Mathf.clamp(data.volume, 0f, 1f);
|
||||
data.total += baseVol;
|
||||
data.sum.add(pos.getX() * baseVol, pos.getY() * baseVol);
|
||||
}
|
||||
|
||||
public void update(){
|
||||
float avol = Core.settings.getInt("ambientvol", 100) / 100f;
|
||||
|
||||
sounds.each((sound, data) -> {
|
||||
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.2f);
|
||||
|
||||
boolean play = data.curVolume > 0.01f;
|
||||
float pan = Mathf.zero(data.total, 0.0001f) ? 0f : sound.calcPan(data.sum.x / data.total, data.sum.y / data.total);
|
||||
if(data.soundID <= 0){
|
||||
if(play){
|
||||
data.soundID = sound.loop(data.curVolume, 1f, pan);
|
||||
}
|
||||
}else{
|
||||
if(data.curVolume <= 0.01f){
|
||||
sound.stop();
|
||||
data.soundID = -1;
|
||||
return;
|
||||
}
|
||||
sound.setPan(data.soundID, pan, data.curVolume);
|
||||
}
|
||||
|
||||
data.volume = 0f;
|
||||
data.total = 0f;
|
||||
data.sum.setZero();
|
||||
});
|
||||
}
|
||||
|
||||
private static class SoundData{
|
||||
float volume;
|
||||
float total;
|
||||
Vec2 sum = new Vec2();
|
||||
|
||||
int soundID;
|
||||
float curVolume;
|
||||
}
|
||||
}
|
||||
@@ -2,46 +2,99 @@ package mindustry.audio;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.audio.Filters.*;
|
||||
import arc.files.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Controls playback of multiple music tracks.*/
|
||||
public class MusicControl{
|
||||
protected static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.6f, musicWaveChance = 0.5f;
|
||||
/** Controls playback of multiple audio tracks.*/
|
||||
public class SoundControl{
|
||||
protected static final float finTime = 120f, foutTime = 120f, musicInterval = 60 * 60 * 3f, musicChance = 0.6f, musicWaveChance = 0.46f;
|
||||
|
||||
/** normal, ambient music, plays at any time */
|
||||
public Seq<Music> ambientMusic = Seq.with();
|
||||
/** darker music, used in times of conflict */
|
||||
public Seq<Music> darkMusic = Seq.with();
|
||||
/** music used explicitly after boss spawns */
|
||||
public Seq<Music> bossMusic = Seq.with();
|
||||
|
||||
protected Music lastRandomPlayed;
|
||||
protected Interval timer = new Interval();
|
||||
protected Interval timer = new Interval(4);
|
||||
protected @Nullable Music current;
|
||||
protected float fade;
|
||||
protected boolean silenced;
|
||||
|
||||
public MusicControl(){
|
||||
protected AudioBus uiBus = new AudioBus();
|
||||
protected boolean wasPlaying;
|
||||
protected AudioFilter filter = new BiquadFilter(){{
|
||||
set(0, 500, 1);
|
||||
}};
|
||||
|
||||
protected ObjectMap<Sound, SoundData> sounds = new ObjectMap<>();
|
||||
|
||||
public SoundControl(){
|
||||
Events.on(ClientLoadEvent.class, e -> reload());
|
||||
|
||||
//only run music 10 seconds after a wave spawns
|
||||
Events.on(WaveEvent.class, e -> Time.run(60f * 10f, () -> {
|
||||
if(Mathf.chance(musicWaveChance)){
|
||||
Events.on(WaveEvent.class, e -> Time.run(Mathf.random(8f, 15f) * 60f, () -> {
|
||||
boolean boss = state.rules.spawns.contains(group -> group.getSpawned(state.wave - 2) > 0 && group.effect == StatusEffects.boss);
|
||||
|
||||
if(boss){
|
||||
playOnce(bossMusic.random(lastRandomPlayed));
|
||||
}else if(Mathf.chance(musicWaveChance)){
|
||||
playRandom();
|
||||
}
|
||||
}));
|
||||
|
||||
setupFilters();
|
||||
}
|
||||
|
||||
protected void setupFilters(){
|
||||
Core.audio.soundBus.setFilter(0, filter);
|
||||
Core.audio.soundBus.setFilterParam(0, Filters.paramWet, 0f);
|
||||
}
|
||||
|
||||
protected void reload(){
|
||||
current = null;
|
||||
fade = 0f;
|
||||
ambientMusic = Seq.with(Musics.game1, Musics.game3, Musics.game4, Musics.game6);
|
||||
darkMusic = Seq.with(Musics.game2, Musics.game5, Musics.game7);
|
||||
ambientMusic = Seq.with(Musics.game1, Musics.game3, Musics.game6, Musics.game8, Musics.game9);
|
||||
darkMusic = Seq.with(Musics.game2, Musics.game5, Musics.game7, Musics.game4);
|
||||
bossMusic = Seq.with(Musics.boss1, Musics.boss2, Musics.game2, Musics.game5);
|
||||
|
||||
//setup UI bus for all sounds that are in the UI folder
|
||||
for(var sound : Core.assets.getAll(Sound.class, new Seq<>())){
|
||||
var file = Fi.get(Core.assets.getAssetFileName(sound));
|
||||
if(file.parent().name().equals("ui")){
|
||||
sound.setBus(uiBus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loop(Sound sound, float volume){
|
||||
if(Vars.headless) return;
|
||||
|
||||
loop(sound, Core.camera.position, volume);
|
||||
}
|
||||
|
||||
public void loop(Sound sound, Position pos, float volume){
|
||||
if(Vars.headless) return;
|
||||
|
||||
float baseVol = sound.calcFalloff(pos.getX(), pos.getY());
|
||||
float vol = baseVol * volume;
|
||||
|
||||
SoundData data = sounds.get(sound, SoundData::new);
|
||||
data.volume += vol;
|
||||
data.volume = Mathf.clamp(data.volume, 0f, 1f);
|
||||
data.total += baseVol;
|
||||
data.sum.add(pos.getX() * baseVol, pos.getY() * baseVol);
|
||||
}
|
||||
|
||||
public void stop(){
|
||||
@@ -55,6 +108,32 @@ public class MusicControl{
|
||||
|
||||
/** Update and play the right music track.*/
|
||||
public void update(){
|
||||
boolean paused = state.isGame() && Core.scene.hasDialog();
|
||||
boolean playing = state.isGame();
|
||||
|
||||
//check if current track is finished
|
||||
if(current != null && !current.isPlaying()){
|
||||
current = null;
|
||||
fade = 0f;
|
||||
}
|
||||
|
||||
//fade the lowpass filter in/out, poll every 30 ticks just in case performance is an issue
|
||||
if(timer.get(1, 30f)){
|
||||
Core.audio.soundBus.fadeFilterParam(0, Filters.paramWet, paused ? 1f : 0f, 0.4f);
|
||||
}
|
||||
|
||||
//play/stop ordinary effects
|
||||
if(playing != wasPlaying){
|
||||
wasPlaying = playing;
|
||||
|
||||
if(playing){
|
||||
Core.audio.soundBus.play();
|
||||
setupFilters();
|
||||
}else{
|
||||
Core.audio.soundBus.stop();
|
||||
}
|
||||
}
|
||||
|
||||
if(state.isMenu()){
|
||||
silenced = false;
|
||||
if(ui.planet.isShown()){
|
||||
@@ -79,10 +158,46 @@ public class MusicControl{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLoops();
|
||||
}
|
||||
|
||||
protected void updateLoops(){
|
||||
//clear loops when in menu
|
||||
if(!state.isGame()){
|
||||
sounds.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
float avol = Core.settings.getInt("ambientvol", 100) / 100f;
|
||||
|
||||
sounds.each((sound, data) -> {
|
||||
data.curVolume = Mathf.lerpDelta(data.curVolume, data.volume * avol, 0.2f);
|
||||
|
||||
boolean play = data.curVolume > 0.01f;
|
||||
float pan = Mathf.zero(data.total, 0.0001f) ? 0f : sound.calcPan(data.sum.x / data.total, data.sum.y / data.total);
|
||||
if(data.soundID <= 0 || !Core.audio.isPlaying(data.soundID)){
|
||||
if(play){
|
||||
data.soundID = sound.loop(data.curVolume, 1f, pan);
|
||||
Core.audio.protect(data.soundID, true);
|
||||
}
|
||||
}else{
|
||||
if(data.curVolume <= 0.001f){
|
||||
sound.stop();
|
||||
data.soundID = -1;
|
||||
return;
|
||||
}
|
||||
Core.audio.set(data.soundID, pan, data.curVolume);
|
||||
}
|
||||
|
||||
data.volume = 0f;
|
||||
data.total = 0f;
|
||||
data.sum.setZero();
|
||||
});
|
||||
}
|
||||
|
||||
/** Plays a random track.*/
|
||||
protected void playRandom(){
|
||||
public void playRandom(){
|
||||
if(isDark()){
|
||||
playOnce(darkMusic.random(lastRandomPlayed));
|
||||
}else{
|
||||
@@ -171,12 +286,6 @@ public class MusicControl{
|
||||
current = music;
|
||||
current.setVolume(1f);
|
||||
current.setLooping(false);
|
||||
current.setCompletionListener(m -> {
|
||||
if(current == m){
|
||||
current = null;
|
||||
fade = 0f;
|
||||
}
|
||||
});
|
||||
current.play();
|
||||
}
|
||||
|
||||
@@ -188,4 +297,13 @@ public class MusicControl{
|
||||
protected void silence(){
|
||||
play(null);
|
||||
}
|
||||
|
||||
protected static class SoundData{
|
||||
float volume;
|
||||
float total;
|
||||
Vec2 sum = new Vec2();
|
||||
|
||||
int soundID;
|
||||
float curVolume;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.audio;
|
||||
|
||||
import arc.*;
|
||||
import arc.audio.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
@@ -18,7 +19,7 @@ public class SoundLoop{
|
||||
}
|
||||
|
||||
public void update(float x, float y, boolean play){
|
||||
if(baseVolume < 0) return;
|
||||
if(baseVolume <= 0) return;
|
||||
|
||||
if(id < 0){
|
||||
if(play){
|
||||
@@ -31,18 +32,19 @@ public class SoundLoop{
|
||||
}else{
|
||||
volume = Mathf.clamp(volume - fadeSpeed * Time.delta);
|
||||
if(volume <= 0.001f){
|
||||
sound.stop(id);
|
||||
Core.audio.stop(id);
|
||||
id = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
sound.setPan(id, sound.calcPan(x, y), sound.calcVolume(x, y) * volume * baseVolume);
|
||||
|
||||
Core.audio.set(id, sound.calcPan(x, y), sound.calcVolume(x, y) * volume * baseVolume);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop(){
|
||||
if(id != -1){
|
||||
sound.stop(id);
|
||||
Core.audio.stop(id);
|
||||
id = -1;
|
||||
volume = baseVolume = -1;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,18 +30,33 @@ public class Bullets implements ContentList{
|
||||
missileExplosive, missileIncendiary, missileSurge,
|
||||
|
||||
//standard
|
||||
standardCopper, standardDense, standardThorium, standardHoming, standardIncendiary, standardMechSmall,
|
||||
standardGlaive, standardDenseBig, standardThoriumBig, standardIncendiaryBig,
|
||||
standardCopper, standardDense, standardThorium, standardHoming, standardIncendiary,
|
||||
standardDenseBig, standardThoriumBig, standardIncendiaryBig,
|
||||
|
||||
//liquid
|
||||
waterShot, cryoShot, slagShot, oilShot,
|
||||
waterShot, cryoShot, slagShot, oilShot, heavyWaterShot, heavyCryoShot, heavySlagShot, heavyOilShot,
|
||||
|
||||
//environment, misc.
|
||||
damageLightning, damageLightningGround, fireball, basicFlame, pyraFlame, driverBolt, healBullet, healBulletBig, frag;
|
||||
damageLightning, damageLightningGround, fireball, basicFlame, pyraFlame, driverBolt;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
|
||||
//lightning bullets need to be initialized first.
|
||||
damageLightning = new BulletType(0.0001f, 0f){{
|
||||
lifetime = Fx.lightning.lifetime;
|
||||
hitEffect = Fx.hitLancer;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.shocked;
|
||||
statusDuration = 10f;
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
//this is just a copy of the damage lightning bullet that doesn't damage air units
|
||||
damageLightningGround = new BulletType(0.0001f, 0f){};
|
||||
JsonIO.copy(damageLightning, damageLightningGround);
|
||||
damageLightningGround.collidesAir = false;
|
||||
|
||||
artilleryDense = new ArtilleryBulletType(3f, 20, "shell"){{
|
||||
hitEffect = Fx.flakExplosion;
|
||||
knockback = 0.8f;
|
||||
@@ -102,6 +117,7 @@ public class Bullets implements ContentList{
|
||||
status = StatusEffects.burning;
|
||||
frontColor = Pal.lightishOrange;
|
||||
backColor = Pal.lightOrange;
|
||||
makeFire = true;
|
||||
trailEffect = Fx.incendTrail;
|
||||
}};
|
||||
|
||||
@@ -156,7 +172,7 @@ public class Bullets implements ContentList{
|
||||
}};
|
||||
|
||||
flakGlass = new FlakBulletType(4f, 3){{
|
||||
lifetime = 70f;
|
||||
lifetime = 60f;
|
||||
ammoMultiplier = 5f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
reloadMultiplier = 0.8f;
|
||||
@@ -190,7 +206,6 @@ public class Bullets implements ContentList{
|
||||
}};
|
||||
|
||||
fragGlass = new FlakBulletType(4f, 3){{
|
||||
lifetime = 70f;
|
||||
ammoMultiplier = 3f;
|
||||
shootEffect = Fx.shootSmall;
|
||||
reloadMultiplier = 0.8f;
|
||||
@@ -265,6 +280,7 @@ public class Bullets implements ContentList{
|
||||
homingPower = 0.08f;
|
||||
splashDamageRadius = 20f;
|
||||
splashDamage = 20f;
|
||||
makeFire = true;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
status = StatusEffects.burning;
|
||||
}};
|
||||
@@ -278,6 +294,7 @@ public class Bullets implements ContentList{
|
||||
splashDamage = 25f;
|
||||
hitEffect = Fx.blastExplosion;
|
||||
despawnEffect = Fx.blastExplosion;
|
||||
lightningDamage = 10;
|
||||
lightning = 2;
|
||||
lightningLength = 10;
|
||||
}};
|
||||
@@ -323,27 +340,11 @@ public class Bullets implements ContentList{
|
||||
frontColor = Pal.lightishOrange;
|
||||
backColor = Pal.lightOrange;
|
||||
status = StatusEffects.burning;
|
||||
makeFire = true;
|
||||
inaccuracy = 3f;
|
||||
lifetime = 60f;
|
||||
}};
|
||||
|
||||
standardGlaive = new BasicBulletType(4f, 7.5f, "bullet"){{
|
||||
width = 10f;
|
||||
height = 12f;
|
||||
frontColor = Color.valueOf("feb380");
|
||||
backColor = Color.valueOf("ea8878");
|
||||
status = StatusEffects.burning;
|
||||
lifetime = 60f;
|
||||
}};
|
||||
|
||||
standardMechSmall = new BasicBulletType(4f, 9, "bullet"){{
|
||||
width = 11f;
|
||||
height = 14f;
|
||||
lifetime = 40f;
|
||||
inaccuracy = 5f;
|
||||
despawnEffect = Fx.hitBulletSmall;
|
||||
}};
|
||||
|
||||
standardDenseBig = new BasicBulletType(7f, 55, "bullet"){{
|
||||
width = 15f;
|
||||
height = 21f;
|
||||
@@ -354,6 +355,9 @@ public class Bullets implements ContentList{
|
||||
width = 16f;
|
||||
height = 23f;
|
||||
shootEffect = Fx.shootBig;
|
||||
pierceCap = 2;
|
||||
pierceBuilding = true;
|
||||
knockback = 0.7f;
|
||||
}};
|
||||
|
||||
standardIncendiaryBig = new BasicBulletType(7f, 60, "bullet"){{
|
||||
@@ -363,29 +367,10 @@ public class Bullets implements ContentList{
|
||||
backColor = Pal.lightOrange;
|
||||
status = StatusEffects.burning;
|
||||
shootEffect = Fx.shootBig;
|
||||
}};
|
||||
|
||||
damageLightning = new BulletType(0.0001f, 0f){{
|
||||
lifetime = Fx.lightning.lifetime;
|
||||
hitEffect = Fx.hitLancer;
|
||||
despawnEffect = Fx.none;
|
||||
status = StatusEffects.shocked;
|
||||
statusDuration = 10f;
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
//this is just a copy of the damage lightning bullet that doesn't damage air units
|
||||
damageLightningGround = new BulletType(0.0001f, 0f){{
|
||||
collidesAir = false;
|
||||
}};
|
||||
JsonIO.copy(damageLightning, damageLightningGround);
|
||||
|
||||
healBullet = new HealBulletType(5.2f, 13){{
|
||||
healPercent = 3f;
|
||||
}};
|
||||
|
||||
healBulletBig = new HealBulletType(5.2f, 15){{
|
||||
healPercent = 5.5f;
|
||||
makeFire = true;
|
||||
pierceCap = 2;
|
||||
pierceBuilding = true;
|
||||
knockback = 0.7f;
|
||||
}};
|
||||
|
||||
fireball = new BulletType(1f, 4){
|
||||
@@ -428,7 +413,7 @@ public class Bullets implements ContentList{
|
||||
}
|
||||
};
|
||||
|
||||
basicFlame = new BulletType(3.35f, 15f){{
|
||||
basicFlame = new BulletType(3.35f, 16f){{
|
||||
ammoMultiplier = 3f;
|
||||
hitSize = 7f;
|
||||
lifetime = 18f;
|
||||
@@ -443,7 +428,7 @@ public class Bullets implements ContentList{
|
||||
hittable = false;
|
||||
}};
|
||||
|
||||
pyraFlame = new BulletType(3.35f, 22f){{
|
||||
pyraFlame = new BulletType(3.35f, 25f){{
|
||||
ammoMultiplier = 4f;
|
||||
hitSize = 7f;
|
||||
lifetime = 18f;
|
||||
@@ -459,29 +444,70 @@ public class Bullets implements ContentList{
|
||||
|
||||
waterShot = new LiquidBulletType(Liquids.water){{
|
||||
knockback = 0.7f;
|
||||
drag = 0.01f;
|
||||
}};
|
||||
|
||||
cryoShot = new LiquidBulletType(Liquids.cryofluid){{
|
||||
|
||||
drag = 0.01f;
|
||||
}};
|
||||
|
||||
slagShot = new LiquidBulletType(Liquids.slag){{
|
||||
damage = 4;
|
||||
drag = 0.03f;
|
||||
drag = 0.01f;
|
||||
}};
|
||||
|
||||
oilShot = new LiquidBulletType(Liquids.oil){{
|
||||
drag = 0.03f;
|
||||
drag = 0.01f;
|
||||
}};
|
||||
|
||||
heavyWaterShot = new LiquidBulletType(Liquids.water){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.7f;
|
||||
puddleSize = 8f;
|
||||
orbSize = 4f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 0.4f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.2f;
|
||||
}};
|
||||
|
||||
heavyCryoShot = new LiquidBulletType(Liquids.cryofluid){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
orbSize = 4f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 0.4f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.2f;
|
||||
}};
|
||||
|
||||
heavySlagShot = new LiquidBulletType(Liquids.slag){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
orbSize = 4f;
|
||||
damage = 4.75f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 0.4f;
|
||||
statusDuration = 60f * 4f;
|
||||
}};
|
||||
|
||||
heavyOilShot = new LiquidBulletType(Liquids.oil){{
|
||||
lifetime = 49f;
|
||||
speed = 4f;
|
||||
knockback = 1.3f;
|
||||
puddleSize = 8f;
|
||||
orbSize = 4f;
|
||||
drag = 0.001f;
|
||||
ammoMultiplier = 0.4f;
|
||||
statusDuration = 60f * 4f;
|
||||
damage = 0.2f;
|
||||
}};
|
||||
|
||||
driverBolt = new MassDriverBolt();
|
||||
|
||||
frag = new BasicBulletType(5f, 8, "bullet"){{
|
||||
width = 8f;
|
||||
height = 9f;
|
||||
shrinkY = 0.5f;
|
||||
lifetime = 50f;
|
||||
drag = 0.04f;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import static arc.graphics.g2d.Draw.rect;
|
||||
import static arc.graphics.g2d.Draw.*;
|
||||
import static arc.graphics.g2d.Lines.*;
|
||||
import static arc.math.Angles.*;
|
||||
import static mindustry.Vars.tilesize;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Fx{
|
||||
public static final Effect
|
||||
@@ -56,9 +56,9 @@ public class Fx{
|
||||
|
||||
mixcol(Pal.accent, 1f);
|
||||
alpha(e.fout());
|
||||
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type().icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
|
||||
rect(block ? ((BlockUnitc)select).tile().block.icon(Cicon.full) : select.type.icon(Cicon.full), select.x, select.y, block ? 0f : select.rotation - 90f);
|
||||
alpha(1f);
|
||||
Lines.stroke(e.fslope() * 1f);
|
||||
Lines.stroke(e.fslope());
|
||||
Lines.square(select.x, select.y, e.fout() * select.hitSize * 2f, 45);
|
||||
Lines.stroke(e.fslope() * 2f);
|
||||
Lines.square(select.x, select.y, e.fout() * select.hitSize * 3f, 45f);
|
||||
@@ -66,7 +66,7 @@ public class Fx{
|
||||
}),
|
||||
|
||||
unitDespawn = new Effect(100f, e -> {
|
||||
if(!(e.data instanceof Unit) || e.<Unit>data().type() == null) return;
|
||||
if(!(e.data instanceof Unit) || e.<Unit>data().type == null) return;
|
||||
|
||||
Unit select = e.data();
|
||||
float scl = e.fout(Interp.pow2Out);
|
||||
@@ -74,7 +74,7 @@ public class Fx{
|
||||
Draw.scl *= scl;
|
||||
|
||||
mixcol(Pal.accent, 1f);
|
||||
rect(select.type().icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
||||
rect(select.type.icon(Cicon.full), select.x, select.y, select.rotation - 90f);
|
||||
reset();
|
||||
|
||||
Draw.scl = p;
|
||||
@@ -96,7 +96,7 @@ public class Fx{
|
||||
x = Tmp.v1.x;
|
||||
y = Tmp.v1.y;
|
||||
|
||||
Fill.square(x, y, 1f * size, 45f);
|
||||
Fill.square(x, y, size, 45f);
|
||||
}),
|
||||
|
||||
itemTransfer = new Effect(12f, e -> {
|
||||
@@ -127,7 +127,7 @@ public class Fx{
|
||||
|
||||
pointHit = new Effect(8f, e -> {
|
||||
color(Color.white, e.color, e.fin());
|
||||
stroke(e.fout() * 1f + 0.2f);
|
||||
stroke(e.fout() + 0.2f);
|
||||
Lines.circle(e.x, e.y, e.fin() * 6f);
|
||||
}),
|
||||
|
||||
@@ -138,12 +138,13 @@ public class Fx{
|
||||
stroke(3f * e.fout());
|
||||
color(e.color, Color.white, e.fin());
|
||||
|
||||
beginLine();
|
||||
lines.each(Lines::linePoint);
|
||||
linePoint(e.x, e.y);
|
||||
endLine();
|
||||
for(int i = 0; i < lines.size - 1; i++){
|
||||
Vec2 cur = lines.get(i);
|
||||
Vec2 next = lines.get(i + 1);
|
||||
|
||||
Lines.line(cur.x, cur.y, next.x, next.y, false);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for(Vec2 p : lines){
|
||||
Fill.circle(p.x, p.y, Lines.getStroke() / 2f);
|
||||
}
|
||||
@@ -256,33 +257,33 @@ public class Fx{
|
||||
randLenVectors(e.id, 9, 3 + 20f * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.4f);
|
||||
});
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
unitLand = new Effect(30, e -> {
|
||||
color(Tmp.c1.set(e.color).mul(1.1f));
|
||||
randLenVectors(e.id, 6, 17f * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 4f + 0.3f);
|
||||
});
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
unitLandSmall = new Effect(30, e -> {
|
||||
color(Tmp.c1.set(e.color).mul(1.1f));
|
||||
randLenVectors(e.id, (int)(6 * e.rotation), 12f * e.finpow() * e.rotation, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.1f);
|
||||
});
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
unitPickup = new Effect(18, e -> {
|
||||
color(Pal.lightishGray);
|
||||
stroke(e.fin() * 2f);
|
||||
Lines.poly(e.x, e.y, 4, 13f * e.fout());
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
landShock = new Effect(12, e -> {
|
||||
color(Pal.lancerLaser);
|
||||
stroke(e.fout() * 3f);
|
||||
Lines.poly(e.x, e.y, 12, 20f * e.fout());
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
pickup = new Effect(18, e -> {
|
||||
color(Pal.lightishGray);
|
||||
@@ -388,7 +389,6 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitBulletBig = new Effect(13, e -> {
|
||||
@@ -399,7 +399,6 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitFlameSmall = new Effect(14, e -> {
|
||||
@@ -410,7 +409,6 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 3 + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitLiquid = new Effect(16, e -> {
|
||||
@@ -419,7 +417,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 5, e.fin() * 15f, e.rotation, 60f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitLancer = new Effect(12, e -> {
|
||||
@@ -430,7 +427,6 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitMeltdown = new Effect(12, e -> {
|
||||
@@ -441,7 +437,6 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
hitMeltHeal = new Effect(12, e -> {
|
||||
@@ -452,7 +447,79 @@ public class Fx{
|
||||
float ang = Mathf.angle(x, y);
|
||||
lineAngle(e.x + x, e.y + y, ang, e.fout() * 4 + 1f);
|
||||
});
|
||||
}),
|
||||
|
||||
instBomb = new Effect(15f, 100f, e -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
stroke(e.fout() * 4f);
|
||||
Lines.circle(e.x, e.y, 4f + e.finpow() * 20f);
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 6f, 80f * e.fout(), i*90 + 45);
|
||||
}
|
||||
|
||||
color();
|
||||
for(int i = 0; i < 4; i++){
|
||||
Drawf.tri(e.x, e.y, 3f, 30f * e.fout(), i*90 + 45);
|
||||
}
|
||||
}),
|
||||
|
||||
instTrail = new Effect(30, e -> {
|
||||
for(int i = 0; i < 2; i++){
|
||||
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
|
||||
|
||||
float m = i == 0 ? 1f : 0.5f;
|
||||
|
||||
float rot = e.rotation + 180f;
|
||||
float w = 15f * e.fout() * m;
|
||||
Drawf.tri(e.x, e.y, w, (30f + Mathf.randomSeedRange(e.id, 15f)) * m, rot);
|
||||
Drawf.tri(e.x, e.y, w, 10f * m, rot + 180f);
|
||||
}
|
||||
}),
|
||||
|
||||
instShoot = new Effect(24f, e -> {
|
||||
e.scaled(10f, b -> {
|
||||
color(Color.white, Pal.bulletYellowBack, b.fin());
|
||||
stroke(b.fout() * 3f + 0.2f);
|
||||
Lines.circle(b.x, b.y, b.fin() * 50f);
|
||||
});
|
||||
|
||||
color(Pal.bulletYellowBack);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 13f * e.fout(), 85f, e.rotation + 90f * i);
|
||||
Drawf.tri(e.x, e.y, 13f * e.fout(), 50f, e.rotation + 20f * i);
|
||||
}
|
||||
}),
|
||||
|
||||
instHit = new Effect(20f, 200f, e -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
|
||||
for(int i = 0; i < 2; i++){
|
||||
color(i == 0 ? Pal.bulletYellowBack : Pal.bulletYellow);
|
||||
|
||||
float m = i == 0 ? 1f : 0.5f;
|
||||
|
||||
for(int j = 0; j < 5; j++){
|
||||
float rot = e.rotation + Mathf.randomSeedRange(e.id + j, 50f);
|
||||
float w = 23f * e.fout() * m;
|
||||
Drawf.tri(e.x, e.y, w, (80f + Mathf.randomSeedRange(e.id + j, 40f)) * m, rot);
|
||||
Drawf.tri(e.x, e.y, w, 20f * m, rot + 180f);
|
||||
}
|
||||
}
|
||||
|
||||
e.scaled(10f, c -> {
|
||||
color(Pal.bulletYellow);
|
||||
stroke(c.fout() * 2f + 0.2f);
|
||||
Lines.circle(e.x, e.y, c.fin() * 30f);
|
||||
});
|
||||
|
||||
e.scaled(12f, c -> {
|
||||
color(Pal.bulletYellowBack);
|
||||
randLenVectors(e.id, 25, 5f + e.fin() * 80f, e.rotation, 60f, (x, y) -> {
|
||||
Fill.square(e.x + x, e.y + y, c.fout() * 3f, 45f);
|
||||
});
|
||||
});
|
||||
}),
|
||||
|
||||
hitLaser = new Effect(8, e -> {
|
||||
@@ -479,8 +546,8 @@ public class Fx{
|
||||
}),
|
||||
|
||||
flakExplosion = new Effect(20, e -> {
|
||||
|
||||
color(Pal.bulletYellow);
|
||||
|
||||
e.scaled(6, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 10f);
|
||||
@@ -493,17 +560,16 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.lighterOrange);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
plasticExplosion = new Effect(24, e -> {
|
||||
|
||||
color(Pal.plastaniumFront);
|
||||
|
||||
e.scaled(7, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 24f);
|
||||
@@ -516,17 +582,16 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.plastaniumBack);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 4, 1f + 25f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
plasticExplosionFlak = new Effect(28, e -> {
|
||||
|
||||
color(Pal.plastaniumFront);
|
||||
|
||||
e.scaled(7, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 34f);
|
||||
@@ -539,17 +604,16 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.plastaniumBack);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 4, 1f + 30f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
blastExplosion = new Effect(22, e -> {
|
||||
|
||||
color(Pal.missileYellow);
|
||||
|
||||
e.scaled(6, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 15f);
|
||||
@@ -562,17 +626,16 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.missileYellowBack);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
sapExplosion = new Effect(25, e -> {
|
||||
|
||||
color(Pal.sapBullet);
|
||||
|
||||
e.scaled(6, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 80f);
|
||||
@@ -585,17 +648,16 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.sapBulletBack);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 8, 1f + 60f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
massiveExplosion = new Effect(30, e -> {
|
||||
|
||||
color(Pal.missileYellow);
|
||||
|
||||
e.scaled(7, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 4f + i.fin() * 30f);
|
||||
@@ -608,12 +670,11 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.missileYellowBack);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 6, 1f + 29f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 4f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
artilleryTrail = new Effect(50, e -> {
|
||||
@@ -638,8 +699,8 @@ public class Fx{
|
||||
}),
|
||||
|
||||
flakExplosionBig = new Effect(30, e -> {
|
||||
|
||||
color(Pal.bulletYellowBack);
|
||||
|
||||
e.scaled(6, i -> {
|
||||
stroke(3f * i.fout());
|
||||
Lines.circle(e.x, e.y, 3f + i.fin() * 25f);
|
||||
@@ -652,12 +713,11 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.bulletYellow);
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
|
||||
randLenVectors(e.id + 1, 4, 1f + 23f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
burning = new Effect(35f, e -> {
|
||||
@@ -666,7 +726,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 3, 2f + e.fin() * 7f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.1f + e.fout() * 1.4f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
fire = new Effect(50f, e -> {
|
||||
@@ -687,7 +746,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
steam = new Effect(35f, e -> {
|
||||
@@ -696,7 +754,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.2f + e.fslope() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
fireballsmoke = new Effect(25f, e -> {
|
||||
@@ -705,7 +762,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 1, 2f + e.fin() * 7f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.2f + e.fout() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
ballfire = new Effect(25f, e -> {
|
||||
@@ -714,7 +770,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 2f + e.fin() * 7f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.2f + e.fout() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
freezing = new Effect(40f, e -> {
|
||||
@@ -723,7 +778,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1.2f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
melting = new Effect(40f, e -> {
|
||||
@@ -732,21 +786,20 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 3f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, .2f + e.fout() * 1.2f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
wet = new Effect(80f, e -> {
|
||||
color(Liquids.water.color);
|
||||
alpha(Mathf.clamp(e.fin() * 2f));
|
||||
|
||||
Fill.circle(e.x, e.y, e.fout() * 1f);
|
||||
Fill.circle(e.x, e.y, e.fout());
|
||||
}),
|
||||
|
||||
|
||||
muddy = new Effect(80f, e -> {
|
||||
color(Color.valueOf("432722"));
|
||||
alpha(Mathf.clamp(e.fin() * 2f));
|
||||
|
||||
Fill.circle(e.x, e.y, e.fout() * 1f);
|
||||
Fill.circle(e.x, e.y, e.fout());
|
||||
}),
|
||||
|
||||
sapped = new Effect(40f, e -> {
|
||||
@@ -755,7 +808,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.square(e.x + x, e.y + y, e.fslope() * 1.1f, 45f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
sporeSlowed = new Effect(40f, e -> {
|
||||
@@ -768,9 +820,8 @@ public class Fx{
|
||||
color(Liquids.oil.color);
|
||||
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
|
||||
Fill.circle(e.x + x, e.y + y, e.fout());
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
overdriven = new Effect(20f, e -> {
|
||||
@@ -779,7 +830,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + e.fin() * 2f, (x, y) -> {
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 2.3f + 0.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
overclocked = new Effect(50f, e -> {
|
||||
@@ -835,7 +885,7 @@ public class Fx{
|
||||
|
||||
randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout());
|
||||
});
|
||||
|
||||
color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
|
||||
@@ -879,7 +929,7 @@ public class Fx{
|
||||
|
||||
randLenVectors(e.id, 6, 2f + 19f * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 3f + 0.5f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout());
|
||||
});
|
||||
|
||||
color(Pal.lighterOrange, Pal.lightOrange, Color.gray, e.fin());
|
||||
@@ -888,7 +938,6 @@ public class Fx{
|
||||
randLenVectors(e.id + 1, 9, 1f + 23f * e.finpow(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), 1f + e.fout() * 3f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
blockExplosionSmoke = new Effect(30, e -> {
|
||||
@@ -896,9 +945,8 @@ public class Fx{
|
||||
|
||||
randLenVectors(e.id, 6, 4f + 30f * e.finpow(), (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 3f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout() * 1f);
|
||||
Fill.circle(e.x + x / 2f, e.y + y / 2f, e.fout());
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootSmall = new Effect(8, e -> {
|
||||
@@ -928,7 +976,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 5, e.finpow() * 6f, e.rotation, 20f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootBig = new Effect(9, e -> {
|
||||
@@ -951,7 +998,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 8, e.finpow() * 19f, e.rotation, 10f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2f + 0.2f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootBigSmoke2 = new Effect(18f, e -> {
|
||||
@@ -960,7 +1006,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 9, e.finpow() * 23f, e.rotation, 20f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 2.4f + 0.2f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootSmallFlame = new Effect(32f, 80f, e -> {
|
||||
@@ -969,7 +1014,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 8, e.finpow() * 60f, e.rotation, 10f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.65f + e.fout() * 1.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootPyraFlame = new Effect(33f, 80f, e -> {
|
||||
@@ -978,7 +1022,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 10, e.finpow() * 70f, e.rotation, 10f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.65f + e.fout() * 1.6f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shootLiquid = new Effect(40f, 80f, e -> {
|
||||
@@ -987,69 +1030,104 @@ public class Fx{
|
||||
randLenVectors(e.id, 6, e.finpow() * 60f, e.rotation, 11f, (x, y) -> {
|
||||
Fill.circle(e.x + x, e.y + y, 0.5f + e.fout() * 2.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
shellEjectSmall = new Effect(30f, e -> {
|
||||
casing1 = new Effect(30f, e -> {
|
||||
color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
|
||||
alpha(e.fout(0.3f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
|
||||
int i = Mathf.sign(e.rotation);
|
||||
int i = -Mathf.sign(e.rotation);
|
||||
|
||||
float len = (2f + e.finpow() * 6f) * i;
|
||||
float lr = rot + e.fin() * 30f * i;
|
||||
Fill.rect(e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
1f, 2f, rot + e.fin() * 50f * i);
|
||||
Fill.rect(
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
1f, 2f, rot + e.fin() * 50f * i
|
||||
);
|
||||
|
||||
}).ground(400f),
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
shellEjectMedium = new Effect(34f, e -> {
|
||||
casing2 = new Effect(34f, e -> {
|
||||
color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
|
||||
float rot = e.rotation + 90f;
|
||||
alpha(e.fout(0.5f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
int i = -Mathf.sign(e.rotation);
|
||||
float len = (2f + e.finpow() * 10f) * i;
|
||||
float lr = rot + e.fin() * 20f * i;
|
||||
rect(Core.atlas.find("casing"),
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
2f, 3f, rot + e.fin() * 50f * i
|
||||
);
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
casing3 = new Effect(40f, e -> {
|
||||
color(Pal.lightOrange, Pal.lightishGray, Pal.lightishGray, e.fin());
|
||||
alpha(e.fout(0.5f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
int i = -Mathf.sign(e.rotation);
|
||||
float len = (4f + e.finpow() * 9f) * i;
|
||||
float lr = rot + Mathf.randomSeedRange(e.id + i + 6, 20f * e.fin()) * i;
|
||||
|
||||
rect(Core.atlas.find("casing"),
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
2.5f, 4f,
|
||||
rot + e.fin() * 50f * i
|
||||
);
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
casing4 = new Effect(45f, e -> {
|
||||
color(Pal.lightOrange, Pal.lightishGray, Pal.lightishGray, e.fin());
|
||||
alpha(e.fout(0.5f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
int i = -Mathf.sign(e.rotation);
|
||||
float len = (4f + e.finpow() * 9f) * i;
|
||||
float lr = rot + Mathf.randomSeedRange(e.id + i + 6, 20f * e.fin()) * i;
|
||||
|
||||
rect(Core.atlas.find("casing"),
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
3f, 6f,
|
||||
rot + e.fin() * 50f * i
|
||||
);
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
casing2Double = new Effect(34f, e -> {
|
||||
color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
|
||||
alpha(e.fout(0.5f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
for(int i : Mathf.signs){
|
||||
float len = (2f + e.finpow() * 10f) * i;
|
||||
float lr = rot + e.fin() * 20f * i;
|
||||
rect(Core.atlas.find("casing"),
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
2f, 3f, rot);
|
||||
2f, 3f, rot + e.fin() * 50f * i
|
||||
);
|
||||
}
|
||||
|
||||
color(Color.lightGray, Color.gray, e.fin());
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
casing3Double = new Effect(40f, e -> {
|
||||
color(Pal.lightOrange, Pal.lightishGray, Pal.lightishGray, e.fin());
|
||||
alpha(e.fout(0.5f));
|
||||
float rot = Math.abs(e.rotation) + 90f;
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
float ex = e.x, ey = e.y, fout = e.fout();
|
||||
randLenVectors(e.id, 4, 1f + e.finpow() * 11f, e.rotation + 90f * i, 20f, (x, y) -> {
|
||||
Fill.circle(ex + x, ey + y, fout * 1.5f);
|
||||
});
|
||||
}
|
||||
|
||||
}).ground(400f),
|
||||
|
||||
shellEjectBig = new Effect(22f, e -> {
|
||||
color(Pal.lightOrange, Color.lightGray, Pal.lightishGray, e.fin());
|
||||
float rot = e.rotation + 90f;
|
||||
for(int i : Mathf.signs){
|
||||
float len = (4f + e.finpow() * 8f) * i;
|
||||
float len = (4f + e.finpow() * 9f) * i;
|
||||
float lr = rot + Mathf.randomSeedRange(e.id + i + 6, 20f * e.fin()) * i;
|
||||
|
||||
rect(Core.atlas.find("casing"),
|
||||
e.x + trnsx(lr, len) + Mathf.randomSeedRange(e.id + i + 7, 3f * e.fin()),
|
||||
e.y + trnsy(lr, len) + Mathf.randomSeedRange(e.id + i + 8, 3f * e.fin()),
|
||||
2.5f, 4f,
|
||||
rot + e.fin() * 30f * i + Mathf.randomSeedRange(e.id + i + 9, 40f * e.fin()));
|
||||
rot + e.fin() * 50f * i
|
||||
);
|
||||
}
|
||||
|
||||
color(Color.lightGray);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
float ex = e.x, ey = e.y, fout = e.fout();
|
||||
randLenVectors(e.id, 4, -e.finpow() * 15f, e.rotation + 90f * i, 25f, (x, y) -> {
|
||||
Fill.circle(ex + x, ey + y, fout * 2f);
|
||||
});
|
||||
}
|
||||
|
||||
}).ground(400f),
|
||||
}).layer(Layer.bullet),
|
||||
|
||||
railShoot = new Effect(24f, e -> {
|
||||
e.scaled(10f, b -> {
|
||||
@@ -1074,28 +1152,11 @@ public class Fx{
|
||||
}),
|
||||
|
||||
railHit = new Effect(18f, 200f, e -> {
|
||||
if(true){
|
||||
color(Pal.orangeSpark);
|
||||
color(Pal.orangeSpark);
|
||||
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 10f * e.fout(), 60f, e.rotation + 140f * i);
|
||||
}
|
||||
}else{
|
||||
e.scaled(7f, b -> {
|
||||
color(Color.white, Color.lightGray, b.fin());
|
||||
stroke(b.fout() * 2f + 0.2f);
|
||||
Lines.circle(b.x, b.y, b.fin() * 28f);
|
||||
});
|
||||
|
||||
color(Pal.orangeSpark);
|
||||
float rot = e.rotation + Mathf.randomSeedRange(e.id, 20f);
|
||||
float w = 9f * e.fout();
|
||||
|
||||
Drawf.tri(e.x, e.y, w, 100f, rot);
|
||||
Drawf.tri(e.x, e.y, w, 10f, rot + 180f);
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 10f * e.fout(), 60f, e.rotation + 140f * i);
|
||||
}
|
||||
|
||||
|
||||
}),
|
||||
|
||||
lancerLaserShoot = new Effect(21f, e -> {
|
||||
@@ -1104,17 +1165,15 @@ public class Fx{
|
||||
for(int i : Mathf.signs){
|
||||
Drawf.tri(e.x, e.y, 4f * e.fout(), 29f, e.rotation + 90f * i);
|
||||
}
|
||||
|
||||
}),
|
||||
|
||||
lancerLaserShootSmoke = new Effect(26f, e -> {
|
||||
color(Color.white);
|
||||
float length = e.data == null || !(e.data instanceof Float) ? 70f : (Float)e.data;
|
||||
float length = !(e.data instanceof Float) ? 70f : (Float)e.data;
|
||||
|
||||
randLenVectors(e.id, 7, length, e.rotation, 0f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fout() * 9f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
lancerLaserCharge = new Effect(38f, e -> {
|
||||
@@ -1123,7 +1182,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + 20f * e.fout(), e.rotation, 120f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fslope() * 3f + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
lancerLaserChargeBegin = new Effect(60f, e -> {
|
||||
@@ -1140,7 +1198,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 2, 1f + 20f * e.fout(), e.rotation, 120f, (x, y) -> {
|
||||
Drawf.tri(e.x + x, e.y + y, e.fslope() * 3f + 1, e.fslope() * 3f + 1, Mathf.angle(x, y));
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
sparkShoot = new Effect(12f, e -> {
|
||||
@@ -1150,7 +1207,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 7, 25f * e.finpow(), e.rotation, 3f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fslope() * 5f + 0.5f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
lightningShoot = new Effect(12f, e -> {
|
||||
@@ -1160,7 +1216,15 @@ public class Fx{
|
||||
randLenVectors(e.id, 7, 25f * e.finpow(), e.rotation, 50f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fin() * 5f + 2f);
|
||||
});
|
||||
}),
|
||||
|
||||
thoriumShoot = new Effect(12f, e -> {
|
||||
color(Color.white, Pal.thoriumPink, e.fin());
|
||||
stroke(e.fout() * 1.2f + 0.5f);
|
||||
|
||||
randLenVectors(e.id, 7, 25f * e.finpow(), e.rotation, 50f, (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fin() * 5f + 2f);
|
||||
});
|
||||
}),
|
||||
|
||||
reactorsmoke = new Effect(17, e -> {
|
||||
@@ -1236,10 +1300,18 @@ public class Fx{
|
||||
});
|
||||
}),
|
||||
|
||||
coreBurn = new Effect(23, e -> {
|
||||
randLenVectors(e.id, 5, e.fin() * 9f, (x, y) -> {
|
||||
float len = e.fout() * 4f;
|
||||
color(Pal.accent, Color.gray, e.fin());
|
||||
Fill.circle(e.x + x, e.y + y, len/2f);
|
||||
});
|
||||
}),
|
||||
|
||||
plasticburn = new Effect(40, e -> {
|
||||
randLenVectors(e.id, 5, 3f + e.fin() * 5f, (x, y) -> {
|
||||
color(Color.valueOf("e9ead3"), Color.gray, e.fin());
|
||||
Fill.circle(e.x + x, e.y + y, e.fout() * 1f);
|
||||
Fill.circle(e.x + x, e.y + y, e.fout());
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -1263,21 +1335,21 @@ public class Fx{
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 2.5f + 0.5f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
pulverizeSmall = new Effect(30, e -> {
|
||||
randLenVectors(e.id, 3, e.fin() * 5f, (x, y) -> {
|
||||
color(Pal.stoneGray);
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 1f + 0.5f, 45);
|
||||
Fill.square(e.x + x, e.y + y, e.fout() + 0.5f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
pulverizeMedium = new Effect(30, e -> {
|
||||
randLenVectors(e.id, 5, 3f + e.fin() * 8f, (x, y) -> {
|
||||
color(Pal.stoneGray);
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 1f + 0.5f, 45);
|
||||
Fill.square(e.x + x, e.y + y, e.fout() + 0.5f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
producesmoke = new Effect(12, e -> {
|
||||
randLenVectors(e.id, 8, 4f + e.fin() * 18f, (x, y) -> {
|
||||
color(Color.white, Pal.accent, e.fin());
|
||||
@@ -1292,21 +1364,21 @@ public class Fx{
|
||||
Fill.circle(e.x + x, e.y + y, 0.5f + fout * 4f);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
smeltsmoke = new Effect(15, e -> {
|
||||
randLenVectors(e.id, 6, 4f + e.fin() * 5f, (x, y) -> {
|
||||
color(Color.white, e.color, e.fin());
|
||||
Fill.square(e.x + x, e.y + y, 0.5f + e.fout() * 2f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
formsmoke = new Effect(40, e -> {
|
||||
randLenVectors(e.id, 6, 5f + e.fin() * 8f, (x, y) -> {
|
||||
color(Pal.plasticSmoke, Color.lightGray, e.fin());
|
||||
Fill.square(e.x + x, e.y + y, 0.2f + e.fout() * 2f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
blastsmoke = new Effect(26, e -> {
|
||||
randLenVectors(e.id, 12, 1f + e.fin() * 23f, (x, y) -> {
|
||||
float size = 2f + e.fout() * 6f;
|
||||
@@ -1314,7 +1386,7 @@ public class Fx{
|
||||
Fill.circle(e.x + x, e.y + y, size/2f);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
lava = new Effect(18, e -> {
|
||||
randLenVectors(e.id, 3, 1f + e.fin() * 10f, (x, y) -> {
|
||||
float size = e.fslope() * 4f;
|
||||
@@ -1322,50 +1394,58 @@ public class Fx{
|
||||
Fill.circle(e.x + x, e.y + y, size/2f);
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
dooropen = new Effect(10, e -> {
|
||||
stroke(e.fout() * 1.6f);
|
||||
Lines.square(e.x, e.y, tilesize / 2f + e.fin() * 2f);
|
||||
}),
|
||||
|
||||
|
||||
doorclose = new Effect(10, e -> {
|
||||
stroke(e.fout() * 1.6f);
|
||||
Lines.square(e.x, e.y, tilesize / 2f + e.fout() * 2f);
|
||||
}),
|
||||
|
||||
dooropenlarge = new Effect(10, e -> {
|
||||
stroke(e.fout() * 1.6f);
|
||||
Lines.square(e.x, e.y, tilesize + e.fin() * 2f);
|
||||
}),
|
||||
|
||||
doorcloselarge = new Effect(10, e -> {
|
||||
stroke(e.fout() * 1.6f);
|
||||
Lines.square(e.x, e.y, tilesize + e.fout() * 2f);
|
||||
}),
|
||||
|
||||
purify = new Effect(10, e -> {
|
||||
color(Color.royal, Color.gray, e.fin());
|
||||
stroke(2f);
|
||||
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
|
||||
}),
|
||||
|
||||
purifyoil = new Effect(10, e -> {
|
||||
color(Color.black, Color.gray, e.fin());
|
||||
stroke(2f);
|
||||
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
|
||||
}),
|
||||
|
||||
purifystone = new Effect(10, e -> {
|
||||
color(Color.orange, Color.gray, e.fin());
|
||||
stroke(2f);
|
||||
Lines.spikes(e.x, e.y, e.fin() * 4f, 2, 6);
|
||||
}),
|
||||
|
||||
generate = new Effect(11, e -> {
|
||||
color(Color.orange, Color.yellow, e.fin());
|
||||
stroke(1f);
|
||||
Lines.spikes(e.x, e.y, e.fin() * 5f, 2, 8);
|
||||
}),
|
||||
|
||||
mine = new Effect(20, e -> {
|
||||
randLenVectors(e.id, 6, 3f + e.fin() * 6f, (x, y) -> {
|
||||
color(e.color, Color.lightGray, e.fin());
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 2f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
mineBig = new Effect(30, e -> {
|
||||
randLenVectors(e.id, 6, 4f + e.fin() * 8f, (x, y) -> {
|
||||
color(e.color, Color.lightGray, e.fin());
|
||||
@@ -1379,12 +1459,14 @@ public class Fx{
|
||||
Fill.square(e.x + x, e.y + y, e.fout() * 2f + 0.5f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
smelt = new Effect(20, e -> {
|
||||
randLenVectors(e.id, 6, 2f + e.fin() * 5f, (x, y) -> {
|
||||
color(Color.white, e.color, e.fin());
|
||||
Fill.square(e.x + x, e.y + y, 0.5f + e.fout() * 2f, 45);
|
||||
});
|
||||
}),
|
||||
|
||||
teleportActivate = new Effect(50, e -> {
|
||||
color(e.color);
|
||||
|
||||
@@ -1398,8 +1480,8 @@ public class Fx{
|
||||
randLenVectors(e.id, 30, 4f + 40f * e.fin(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fin() * 4f + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
teleport = new Effect(60, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fin() * 2f);
|
||||
@@ -1408,8 +1490,8 @@ public class Fx{
|
||||
randLenVectors(e.id, 20, 6f + 20f * e.fout(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fin() * 4f + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
teleportOut = new Effect(20, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fout() * 2f);
|
||||
@@ -1418,7 +1500,6 @@ public class Fx{
|
||||
randLenVectors(e.id, 20, 4f + 20f * e.fin(), (x, y) -> {
|
||||
lineAngle(e.x + x, e.y + y, Mathf.angle(x, y), e.fslope() * 4f + 1f);
|
||||
});
|
||||
|
||||
}),
|
||||
|
||||
ripple = new Effect(30, e -> {
|
||||
@@ -1427,7 +1508,7 @@ public class Fx{
|
||||
color(Tmp.c1.set(e.color).mul(1.5f));
|
||||
stroke(e.fout() * 1.4f);
|
||||
Lines.circle(e.x, e.y, (2f + e.fin() * 4f) * e.rotation);
|
||||
}).ground(),
|
||||
}).layer(Layer.debris),
|
||||
|
||||
bubble = new Effect(20, e -> {
|
||||
color(Tmp.c1.set(e.color).shiftValue(0.1f));
|
||||
@@ -1467,7 +1548,7 @@ public class Fx{
|
||||
|
||||
overdriveWave = new Effect(50, e -> {
|
||||
color(e.color);
|
||||
stroke(e.fout() * 1f);
|
||||
stroke(e.fout());
|
||||
Lines.circle(e.x, e.y, e.finpow() * e.rotation);
|
||||
}),
|
||||
|
||||
@@ -1502,7 +1583,6 @@ public class Fx{
|
||||
|
||||
float radius = unit.hitSize() * 1.3f;
|
||||
|
||||
|
||||
e.scaled(16f, c -> {
|
||||
color(Pal.shield);
|
||||
stroke(c.fout() * 2f + 0.1f);
|
||||
@@ -1513,7 +1593,7 @@ public class Fx{
|
||||
});
|
||||
|
||||
color(Pal.shield, e.fout());
|
||||
stroke(1f * e.fout());
|
||||
stroke(e.fout());
|
||||
Lines.circle(e.x, e.y, radius);
|
||||
}),
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import mindustry.ctype.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class Items implements ContentList{
|
||||
public static Item scrap, copper, lead, graphite, coal, titanium, thorium, silicon, plastanium, phasefabric, surgealloy,
|
||||
public static Item scrap, copper, lead, graphite, coal, titanium, thorium, silicon, plastanium, phaseFabric, surgeAlloy,
|
||||
sporePod, sand, blastCompound, pyratite, metaglass;
|
||||
|
||||
@Override
|
||||
@@ -32,6 +32,7 @@ public class Items implements ContentList{
|
||||
|
||||
sand = new Item("sand", Color.valueOf("f7cba4")){{
|
||||
alwaysUnlocked = true;
|
||||
lowPriority = true;
|
||||
}};
|
||||
|
||||
coal = new Item("coal", Color.valueOf("272727")){{
|
||||
@@ -66,12 +67,13 @@ public class Items implements ContentList{
|
||||
cost = 1.3f;
|
||||
}};
|
||||
|
||||
phasefabric = new Item("phase-fabric", Color.valueOf("f4ba6e")){{
|
||||
phaseFabric = new Item("phase-fabric", Color.valueOf("f4ba6e")){{
|
||||
cost = 1.3f;
|
||||
radioactivity = 0.6f;
|
||||
}};
|
||||
|
||||
surgealloy = new Item("surge-alloy", Color.valueOf("f3e979")){{
|
||||
surgeAlloy = new Item("surge-alloy", Color.valueOf("f3e979")){{
|
||||
cost = 1.2f;
|
||||
}};
|
||||
|
||||
sporePod = new Item("spore-pod", Color.valueOf("7457ce")){{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.content;
|
||||
|
||||
import arc.graphics.Color;
|
||||
import mindustry.ctype.ContentList;
|
||||
import mindustry.type.Liquid;
|
||||
import arc.graphics.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class Liquids implements ContentList{
|
||||
public static Liquid water, slag, oil, cryofluid;
|
||||
@@ -12,13 +12,13 @@ public class Liquids implements ContentList{
|
||||
|
||||
water = new Liquid("water", Color.valueOf("596ab8")){{
|
||||
heatCapacity = 0.4f;
|
||||
effect = StatusEffects.wet;
|
||||
alwaysUnlocked = true;
|
||||
effect = StatusEffects.wet;
|
||||
}};
|
||||
|
||||
slag = new Liquid("slag", Color.valueOf("ffa166")){{
|
||||
temperature = 1f;
|
||||
viscosity = 0.8f;
|
||||
viscosity = 0.7f;
|
||||
effect = StatusEffects.melting;
|
||||
lightColor = Color.valueOf("f0511d").a(0.4f);
|
||||
}};
|
||||
|
||||
@@ -9,12 +9,14 @@ import mindustry.type.*;
|
||||
public class Planets implements ContentList{
|
||||
public static Planet
|
||||
sun,
|
||||
//tantros,
|
||||
serpulo;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
sun = new Planet("sun", null, 0, 2){{
|
||||
bloom = true;
|
||||
accessible = false;
|
||||
|
||||
//lightColor = Color.valueOf("f4ee8e");
|
||||
|
||||
@@ -31,10 +33,21 @@ public class Planets implements ContentList{
|
||||
);
|
||||
}};
|
||||
|
||||
/*tantros = new Planet("tantros", sun, 2, 0.8f){{
|
||||
generator = new TantrosPlanetGenerator();
|
||||
meshLoader = () -> new HexMesh(this, 4);
|
||||
atmosphereColor = Color.valueOf("3db899");
|
||||
startSector = 10;
|
||||
atmosphereRadIn = -0.01f;
|
||||
atmosphereRadOut = 0.3f;
|
||||
}};*/
|
||||
|
||||
serpulo = new Planet("serpulo", sun, 3, 1){{
|
||||
generator = new SerpuloPlanetGenerator();
|
||||
meshLoader = () -> new HexMesh(this, 6);
|
||||
atmosphereColor = Color.valueOf("3c1b8f");
|
||||
atmosphereRadIn = 0.02f;
|
||||
atmosphereRadOut = 0.3f;
|
||||
startSector = 15;
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -8,57 +8,92 @@ import static mindustry.content.Planets.*;
|
||||
public class SectorPresets implements ContentList{
|
||||
public static SectorPreset
|
||||
groundZero,
|
||||
craters, frozenForest, ruinousShores, stainedMountains, tarFields, fungalPass,
|
||||
saltFlats, overgrowth,
|
||||
desolateRift, nuclearComplex;
|
||||
craters, biomassFacility, frozenForest, ruinousShores, windsweptIslands, stainedMountains, tarFields,
|
||||
fungalPass, extractionOutpost, saltFlats, overgrowth,
|
||||
impact0078, desolateRift, nuclearComplex, planetaryTerminal;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
|
||||
groundZero = new SectorPreset("groundZero", serpulo, 15){{
|
||||
alwaysUnlocked = true;
|
||||
addStartingItems = true;
|
||||
captureWave = 10;
|
||||
difficulty = 1;
|
||||
}};
|
||||
|
||||
saltFlats = new SectorPreset("saltFlats", serpulo, 101){{
|
||||
|
||||
difficulty = 5;
|
||||
useAI = false;
|
||||
}};
|
||||
|
||||
frozenForest = new SectorPreset("frozenForest", serpulo, 86){{
|
||||
captureWave = 40;
|
||||
captureWave = 15;
|
||||
difficulty = 2;
|
||||
}};
|
||||
|
||||
biomassFacility = new SectorPreset("biomassFacility", serpulo, 81){{
|
||||
captureWave = 20;
|
||||
difficulty = 3;
|
||||
}};
|
||||
|
||||
craters = new SectorPreset("craters", serpulo, 18){{
|
||||
captureWave = 40;
|
||||
captureWave = 20;
|
||||
difficulty = 2;
|
||||
}};
|
||||
|
||||
ruinousShores = new SectorPreset("ruinousShores", serpulo, 19){{
|
||||
captureWave = 40;
|
||||
ruinousShores = new SectorPreset("ruinousShores", serpulo, 213){{
|
||||
captureWave = 30;
|
||||
difficulty = 3;
|
||||
}};
|
||||
|
||||
windsweptIslands = new SectorPreset("windsweptIslands", serpulo, 246){{
|
||||
captureWave = 30;
|
||||
difficulty = 4;
|
||||
}};
|
||||
|
||||
stainedMountains = new SectorPreset("stainedMountains", serpulo, 20){{
|
||||
captureWave = 30;
|
||||
difficulty = 3;
|
||||
}};
|
||||
|
||||
extractionOutpost = new SectorPreset("extractionOutpost", serpulo, 165){{
|
||||
difficulty = 5;
|
||||
useAI = false;
|
||||
}};
|
||||
|
||||
fungalPass = new SectorPreset("fungalPass", serpulo, 21){{
|
||||
|
||||
difficulty = 4;
|
||||
useAI = false;
|
||||
}};
|
||||
|
||||
overgrowth = new SectorPreset("overgrowth", serpulo, 22){{
|
||||
|
||||
overgrowth = new SectorPreset("overgrowth", serpulo, 134){{
|
||||
difficulty = 5;
|
||||
useAI = false;
|
||||
}};
|
||||
|
||||
tarFields = new SectorPreset("tarFields", serpulo, 23){{
|
||||
captureWave = 40;
|
||||
difficulty = 5;
|
||||
}};
|
||||
|
||||
impact0078 = new SectorPreset("impact0078", serpulo, 227){{
|
||||
captureWave = 45;
|
||||
difficulty = 7;
|
||||
}};
|
||||
|
||||
desolateRift = new SectorPreset("desolateRift", serpulo, 123){{
|
||||
captureWave = 40;
|
||||
captureWave = 18;
|
||||
difficulty = 8;
|
||||
}};
|
||||
|
||||
|
||||
nuclearComplex = new SectorPreset("nuclearComplex", serpulo, 130){{
|
||||
captureWave = 60;
|
||||
captureWave = 50;
|
||||
difficulty = 7;
|
||||
}};
|
||||
|
||||
planetaryTerminal = new SectorPreset("planetaryTerminal", serpulo, 93){{
|
||||
difficulty = 10;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package mindustry.content;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.Mathf;
|
||||
import mindustry.ctype.ContentList;
|
||||
import arc.math.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.type.StatusEffect;
|
||||
import mindustry.type.*;
|
||||
import mindustry.graphics.*;
|
||||
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class StatusEffects implements ContentList{
|
||||
@@ -17,22 +20,24 @@ public class StatusEffects implements ContentList{
|
||||
none = new StatusEffect("none");
|
||||
|
||||
burning = new StatusEffect("burning"){{
|
||||
color = Pal.lightFlame;
|
||||
damage = 0.12f; //over 8 seconds, this would be 60 damage
|
||||
effect = Fx.burning;
|
||||
|
||||
init(() -> {
|
||||
opposite(wet,freezing);
|
||||
opposite(wet, freezing);
|
||||
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));
|
||||
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));
|
||||
}));
|
||||
});
|
||||
}};
|
||||
|
||||
freezing = new StatusEffect("freezing"){{
|
||||
color = Color.valueOf("6ecdec");
|
||||
speedMultiplier = 0.6f;
|
||||
armorMultiplier = 0.8f;
|
||||
healthMultiplier = 0.8f;
|
||||
effect = Fx.freezing;
|
||||
|
||||
init(() -> {
|
||||
@@ -46,10 +51,12 @@ public class StatusEffects implements ContentList{
|
||||
}};
|
||||
|
||||
unmoving = new StatusEffect("unmoving"){{
|
||||
color = Pal.gray;
|
||||
speedMultiplier = 0.001f;
|
||||
}};
|
||||
|
||||
slow = new StatusEffect("slow"){{
|
||||
color = Pal.lightishGray;
|
||||
speedMultiplier = 0.4f;
|
||||
}};
|
||||
|
||||
@@ -62,7 +69,7 @@ public class StatusEffects implements ContentList{
|
||||
init(() -> {
|
||||
trans(shocked, ((unit, time, newTime, result) -> {
|
||||
unit.damagePierce(14f);
|
||||
if(unit.team() == state.rules.waveTeam){
|
||||
if(unit.team == state.rules.waveTeam){
|
||||
Events.fire(Trigger.shock);
|
||||
}
|
||||
result.set(this, time);
|
||||
@@ -79,42 +86,51 @@ public class StatusEffects implements ContentList{
|
||||
}};
|
||||
|
||||
melting = new StatusEffect("melting"){{
|
||||
color = Color.valueOf("ffa166");
|
||||
speedMultiplier = 0.8f;
|
||||
armorMultiplier = 0.8f;
|
||||
healthMultiplier = 0.8f;
|
||||
damage = 0.3f;
|
||||
effect = Fx.melting;
|
||||
|
||||
init(() -> {
|
||||
trans(tarred, ((unit, time, newTime, result) -> result.set(this, Math.min(time + newTime / 2f, 140f))));
|
||||
opposite(wet, freezing);
|
||||
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));
|
||||
}));
|
||||
});
|
||||
}};
|
||||
|
||||
sapped = new StatusEffect("sapped"){{
|
||||
color = Pal.sap;
|
||||
speedMultiplier = 0.7f;
|
||||
armorMultiplier = 0.8f;
|
||||
healthMultiplier = 0.8f;
|
||||
effect = Fx.sapped;
|
||||
effectChance = 0.1f;
|
||||
}};
|
||||
|
||||
sporeSlowed = new StatusEffect("spore-slowed"){{
|
||||
color = Pal.spore;
|
||||
speedMultiplier = 0.8f;
|
||||
effect = Fx.sapped;
|
||||
effectChance = 0.04f;
|
||||
}};
|
||||
|
||||
tarred = new StatusEffect("tarred"){{
|
||||
color = Color.valueOf("313131");
|
||||
speedMultiplier = 0.6f;
|
||||
effect = Fx.oily;
|
||||
|
||||
init(() -> {
|
||||
trans(melting, ((unit, time, newTime, result) -> result.set(burning, newTime + time)));
|
||||
trans(melting, ((unit, time, newTime, result) -> result.set(melting, newTime + time)));
|
||||
trans(burning, ((unit, time, newTime, result) -> result.set(burning, newTime + time)));
|
||||
});
|
||||
}};
|
||||
|
||||
overdrive = new StatusEffect("overdrive"){{
|
||||
armorMultiplier = 0.95f;
|
||||
color = Pal.accent;
|
||||
healthMultiplier = 0.95f;
|
||||
speedMultiplier = 1.15f;
|
||||
damageMultiplier = 1.4f;
|
||||
damage = -0.01f;
|
||||
@@ -123,6 +139,7 @@ public class StatusEffects implements ContentList{
|
||||
}};
|
||||
|
||||
overclock = new StatusEffect("overclock"){{
|
||||
color = Pal.accent;
|
||||
speedMultiplier = 1.15f;
|
||||
damageMultiplier = 1.15f;
|
||||
reloadMultiplier = 1.25f;
|
||||
@@ -131,20 +148,27 @@ public class StatusEffects implements ContentList{
|
||||
}};
|
||||
|
||||
shielded = new StatusEffect("shielded"){{
|
||||
armorMultiplier = 3f;
|
||||
color = Pal.accent;
|
||||
healthMultiplier = 3f;
|
||||
}};
|
||||
|
||||
boss = new StatusEffect("boss"){{
|
||||
color = Pal.health;
|
||||
permanent = true;
|
||||
damageMultiplier = 1.5f;
|
||||
armorMultiplier = 1.5f;
|
||||
damageMultiplier = 1.3f;
|
||||
healthMultiplier = 1.5f;
|
||||
}};
|
||||
|
||||
shocked = new StatusEffect("shocked");
|
||||
shocked = new StatusEffect("shocked"){{
|
||||
color = Pal.lancerLaser;
|
||||
}};
|
||||
|
||||
blasted = new StatusEffect("blasted");
|
||||
blasted = new StatusEffect("blasted"){{
|
||||
color = Color.valueOf("ff795e");
|
||||
}};
|
||||
|
||||
corroded = new StatusEffect("corroded"){{
|
||||
color = Pal.plastanium;
|
||||
damage = 0.1f;
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package mindustry.content;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.type.*;
|
||||
@@ -11,10 +11,10 @@ import static mindustry.content.Blocks.*;
|
||||
import static mindustry.content.SectorPresets.craters;
|
||||
import static mindustry.content.SectorPresets.*;
|
||||
import static mindustry.content.UnitTypes.*;
|
||||
import static mindustry.type.ItemStack.*;
|
||||
|
||||
public class TechTree implements ContentList{
|
||||
static ObjectMap<UnlockableContent, TechNode> map = new ObjectMap<>();
|
||||
static TechNode context = null;
|
||||
|
||||
public static Seq<TechNode> all;
|
||||
public static TechNode root;
|
||||
@@ -29,7 +29,10 @@ public class TechTree implements ContentList{
|
||||
|
||||
node(junction, () -> {
|
||||
node(router, () -> {
|
||||
node(launchPad, () -> {
|
||||
node(launchPad, Seq.with(new SectorComplete(extractionOutpost)), () -> {
|
||||
node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(distributor);
|
||||
@@ -39,15 +42,15 @@ public class TechTree implements ContentList{
|
||||
node(underflowGate);
|
||||
});
|
||||
});
|
||||
node(container, () -> {
|
||||
node(container, Seq.with(new SectorComplete(biomassFacility)), () -> {
|
||||
node(unloader);
|
||||
node(vault, () -> {
|
||||
node(vault, Seq.with(new SectorComplete(stainedMountains)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(itemBridge, () -> {
|
||||
node(titaniumConveyor, () -> {
|
||||
node(titaniumConveyor, Seq.with(new SectorComplete(craters)), () -> {
|
||||
node(phaseConveyor, () -> {
|
||||
node(massDriver, () -> {
|
||||
|
||||
@@ -87,7 +90,7 @@ public class TechTree implements ContentList{
|
||||
|
||||
node(bridgeConduit);
|
||||
|
||||
node(pulseConduit, () -> {
|
||||
node(pulseConduit, Seq.with(new SectorComplete(windsweptIslands)), () -> {
|
||||
node(phaseConduit, () -> {
|
||||
|
||||
});
|
||||
@@ -95,11 +98,11 @@ public class TechTree implements ContentList{
|
||||
node(platedConduit, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(rotaryPump, () -> {
|
||||
node(thermalPump, () -> {
|
||||
node(rotaryPump, () -> {
|
||||
node(thermalPump, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -107,183 +110,155 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.coal, with(Items.lead, 3000), () -> {
|
||||
node(Items.graphite, with(Items.coal, 1000), () -> {
|
||||
node(graphitePress, () -> {
|
||||
node(Items.titanium, with(Items.graphite, 6000, Items.copper, 10000, Items.lead, 10000), () -> {
|
||||
node(pneumaticDrill, () -> {
|
||||
node(Items.sporePod, with(Items.coal, 5000, Items.graphite, 5000, Items.lead, 5000), () -> {
|
||||
node(cultivator, () -> {
|
||||
node(graphitePress, () -> {
|
||||
node(pneumaticDrill, Seq.with(new SectorComplete(frozenForest)), () -> {
|
||||
node(cultivator, Seq.with(new SectorComplete(biomassFacility)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.thorium, with(Items.titanium, 10000, Items.lead, 15000, Items.copper, 30000), () -> {
|
||||
node(laserDrill, () -> {
|
||||
node(blastDrill, () -> {
|
||||
node(laserDrill, () -> {
|
||||
node(blastDrill, Seq.with(new SectorComplete(nuclearComplex)), () -> {
|
||||
|
||||
});
|
||||
|
||||
node(waterExtractor, () -> {
|
||||
node(oilExtractor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.pyratite, with(Items.coal, 6000, Items.lead, 10000, Items.sand, 5000), () -> {
|
||||
node(pyratiteMixer, () -> {
|
||||
node(Items.blastCompound, with(Items.pyratite, 3000, Items.sporePod, 3000), () -> {
|
||||
node(blastMixer, () -> {
|
||||
node(waterExtractor, Seq.with(new SectorComplete(saltFlats)), () -> {
|
||||
node(oilExtractor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.silicon, with(Items.coal, 4000, Items.sand, 4000), () -> {
|
||||
node(siliconSmelter, () -> {
|
||||
|
||||
node(Liquids.oil, with(Items.coal, 8000, Items.pyratite, 6000, Items.sand, 20000), () -> {
|
||||
node(sporePress, () -> {
|
||||
node(coalCentrifuge, () -> {
|
||||
node(multiPress, () -> {
|
||||
node(siliconCrucible, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.plastanium, with(Items.titanium, 10000, Items.silicon, 10000), () -> {
|
||||
node(plastaniumCompressor, () -> {
|
||||
node(Items.phasefabric, with(Items.thorium, 15000, Items.sand, 30000, Items.silicon, 5000), () -> {
|
||||
node(phaseWeaver, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Items.metaglass, with(Items.sand, 6000, Items.lead, 10000), () -> {
|
||||
node(kiln, () -> {
|
||||
node(incinerator, () -> {
|
||||
node(Items.scrap, with(Items.copper, 20000, Items.sand, 10000), () -> {
|
||||
node(Liquids.slag, with(Items.scrap, 4000), () -> {
|
||||
node(melter, () -> {
|
||||
node(Items.surgealloy, with(Items.thorium, 20000, Items.silicon, 30000, Items.lead, 40000), () -> {
|
||||
node(surgeSmelter, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(separator, () -> {
|
||||
node(pulverizer, () -> {
|
||||
node(disassembler, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(Liquids.cryofluid, with(Items.titanium, 8000, Items.metaglass, 5000), () -> {
|
||||
node(cryofluidMixer, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(microProcessor, () -> {
|
||||
node(switchBlock, () -> {
|
||||
node(message, () -> {
|
||||
node(logicDisplay, () -> {
|
||||
node(largeLogicDisplay, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(memoryCell, () -> {
|
||||
node(memoryBank, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(logicProcessor, () -> {
|
||||
node(hyperProcessor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(pyratiteMixer, () -> {
|
||||
node(blastMixer, () -> {
|
||||
|
||||
node(combustionGenerator, () -> {
|
||||
node(powerNode, () -> {
|
||||
node(powerNodeLarge, () -> {
|
||||
node(diode, () -> {
|
||||
node(surgeTower, () -> {
|
||||
});
|
||||
});
|
||||
|
||||
node(siliconSmelter, () -> {
|
||||
|
||||
node(sporePress, () -> {
|
||||
node(coalCentrifuge, () -> {
|
||||
node(multiPress, () -> {
|
||||
node(siliconCrucible, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(battery, () -> {
|
||||
node(batteryLarge, () -> {
|
||||
node(plastaniumCompressor, Seq.with(new SectorComplete(windsweptIslands)), () -> {
|
||||
node(phaseWeaver, Seq.with(new SectorComplete(tarFields)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(mender, () -> {
|
||||
node(mendProjector, () -> {
|
||||
node(forceProjector, () -> {
|
||||
node(overdriveProjector, () -> {
|
||||
node(overdriveDome, () -> {
|
||||
node(kiln, Seq.with(new SectorComplete(craters)), () -> {
|
||||
node(pulverizer, () -> {
|
||||
node(incinerator, () -> {
|
||||
node(melter, () -> {
|
||||
node(surgeSmelter, () -> {
|
||||
|
||||
});
|
||||
|
||||
node(separator, () -> {
|
||||
node(disassembler, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(repairPoint, () -> {
|
||||
node(cryofluidMixer, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(steamGenerator, () -> {
|
||||
node(thermalGenerator, () -> {
|
||||
node(differentialGenerator, () -> {
|
||||
node(thoriumReactor, Seq.with(new Research(Liquids.cryofluid)), () -> {
|
||||
node(impactReactor, () -> {
|
||||
|
||||
});
|
||||
|
||||
node(rtgGenerator, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(solarPanel, () -> {
|
||||
node(largeSolarPanel, () -> {
|
||||
node(microProcessor, () -> {
|
||||
node(switchBlock, () -> {
|
||||
node(message, () -> {
|
||||
node(logicDisplay, () -> {
|
||||
node(largeLogicDisplay, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(memoryCell, () -> {
|
||||
node(memoryBank, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(logicProcessor, () -> {
|
||||
node(hyperProcessor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(illuminator, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
node(combustionGenerator, Seq.with(new Research(Items.coal)), () -> {
|
||||
node(powerNode, () -> {
|
||||
node(powerNodeLarge, () -> {
|
||||
node(diode, () -> {
|
||||
node(surgeTower, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(battery, () -> {
|
||||
node(batteryLarge, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(mender, () -> {
|
||||
node(mendProjector, () -> {
|
||||
node(forceProjector, Seq.with(new SectorComplete(impact0078)), () -> {
|
||||
node(overdriveProjector, Seq.with(new SectorComplete(impact0078)), () -> {
|
||||
node(overdriveDome, Seq.with(new SectorComplete(impact0078)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(repairPoint, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(steamGenerator, Seq.with(new SectorComplete(craters)), () -> {
|
||||
node(thermalGenerator, () -> {
|
||||
node(differentialGenerator, () -> {
|
||||
node(thoriumReactor, Seq.with(new Research(Liquids.cryofluid)), () -> {
|
||||
node(impactReactor, () -> {
|
||||
|
||||
});
|
||||
|
||||
node(rtgGenerator, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(solarPanel, () -> {
|
||||
node(largeSolarPanel, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -316,12 +291,11 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
|
||||
node(scatter, () -> {
|
||||
node(hail, () -> {
|
||||
|
||||
node(hail, Seq.with(new SectorComplete(craters)), () -> {
|
||||
node(salvo, () -> {
|
||||
node(swarmer, () -> {
|
||||
node(cyclone, () -> {
|
||||
node(spectre, () -> {
|
||||
node(spectre, Seq.with(new SectorComplete(nuclearComplex)), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -344,11 +318,17 @@ public class TechTree implements ContentList{
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(tsunami, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(lancer, () -> {
|
||||
node(meltdown, () -> {
|
||||
node(foreshadow, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(shockMine, () -> {
|
||||
@@ -425,7 +405,7 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
node(navalFactory, () -> {
|
||||
node(navalFactory, Seq.with(new SectorComplete(ruinousShores)), () -> {
|
||||
node(risso, () -> {
|
||||
node(minke, () -> {
|
||||
node(bryde, () -> {
|
||||
@@ -440,10 +420,11 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
node(additiveReconstructor, () -> {
|
||||
node(additiveReconstructor, Seq.with(new SectorComplete(biomassFacility)), () -> {
|
||||
node(multiplicativeReconstructor, () -> {
|
||||
node(exponentialReconstructor, () -> {
|
||||
node(exponentialReconstructor, Seq.with(new SectorComplete(overgrowth)), () -> {
|
||||
node(tetrativeReconstructor, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -468,30 +449,75 @@ public class TechTree implements ContentList{
|
||||
new Research(kiln),
|
||||
new Research(mechanicalPump)
|
||||
), () -> {
|
||||
|
||||
node(tarFields, Seq.with(
|
||||
node(windsweptIslands, Seq.with(
|
||||
new SectorComplete(ruinousShores),
|
||||
new Research(coalCentrifuge),
|
||||
new Research(conduit),
|
||||
new Research(wave)
|
||||
new Research(pneumaticDrill),
|
||||
new Research(hail),
|
||||
new Research(siliconSmelter),
|
||||
new Research(steamGenerator)
|
||||
), () -> {
|
||||
node(desolateRift, Seq.with(
|
||||
new SectorComplete(tarFields),
|
||||
new Research(thermalGenerator),
|
||||
new Research(thoriumReactor)
|
||||
node(tarFields, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(coalCentrifuge),
|
||||
new Research(conduit),
|
||||
new Research(wave)
|
||||
), () -> {
|
||||
node(impact0078, Seq.with(
|
||||
new SectorComplete(tarFields),
|
||||
new Research(Items.thorium),
|
||||
new Research(lancer),
|
||||
new Research(salvo),
|
||||
new Research(coreFoundation)
|
||||
), () -> {
|
||||
node(desolateRift, Seq.with(
|
||||
new SectorComplete(impact0078),
|
||||
new Research(thermalGenerator),
|
||||
new Research(thoriumReactor),
|
||||
new Research(coreNucleus)
|
||||
), () -> {
|
||||
node(planetaryTerminal, Seq.with(
|
||||
new SectorComplete(desolateRift),
|
||||
new SectorComplete(nuclearComplex),
|
||||
new SectorComplete(overgrowth),
|
||||
new SectorComplete(extractionOutpost),
|
||||
new SectorComplete(saltFlats),
|
||||
new Research(risso),
|
||||
new Research(minke),
|
||||
new Research(bryde),
|
||||
new Research(spectre),
|
||||
new Research(launchPad),
|
||||
new Research(massDriver),
|
||||
new Research(impactReactor),
|
||||
new Research(additiveReconstructor),
|
||||
new Research(exponentialReconstructor)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
node(extractionOutpost, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(groundFactory),
|
||||
new Research(nova),
|
||||
new Research(airFactory),
|
||||
new Research(mono)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
node(saltFlats, Seq.with(
|
||||
new SectorComplete(ruinousShores),
|
||||
new Research(groundFactory),
|
||||
new Research(airFactory),
|
||||
new Research(door),
|
||||
new Research(waterExtractor)
|
||||
), () -> {
|
||||
node(saltFlats, Seq.with(
|
||||
new SectorComplete(windsweptIslands),
|
||||
new Research(commandCenter),
|
||||
new Research(groundFactory),
|
||||
new Research(additiveReconstructor),
|
||||
new Research(airFactory),
|
||||
new Research(door)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -500,6 +526,7 @@ public class TechTree implements ContentList{
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(cultivator),
|
||||
new Research(sporePress),
|
||||
new Research(additiveReconstructor),
|
||||
new Research(UnitTypes.mace),
|
||||
new Research(UnitTypes.flare)
|
||||
), () -> {
|
||||
@@ -507,23 +534,92 @@ public class TechTree implements ContentList{
|
||||
});
|
||||
});
|
||||
|
||||
node(stainedMountains, Seq.with(
|
||||
node(biomassFacility, Seq.with(
|
||||
new SectorComplete(frozenForest),
|
||||
new Research(pneumaticDrill),
|
||||
new Research(powerNode),
|
||||
new Research(steamGenerator)
|
||||
new Research(steamGenerator),
|
||||
new Research(scatter),
|
||||
new Research(graphitePress)
|
||||
), () -> {
|
||||
node(fungalPass, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
new Research(groundFactory),
|
||||
new Research(door),
|
||||
node(stainedMountains, Seq.with(
|
||||
new SectorComplete(biomassFacility),
|
||||
new Research(pneumaticDrill),
|
||||
new Research(siliconSmelter)
|
||||
), () -> {
|
||||
node(nuclearComplex, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(thermalGenerator),
|
||||
new Research(laserDrill)
|
||||
node(fungalPass, Seq.with(
|
||||
new SectorComplete(stainedMountains),
|
||||
new Research(groundFactory),
|
||||
new Research(door),
|
||||
new Research(siliconSmelter)
|
||||
), () -> {
|
||||
node(nuclearComplex, Seq.with(
|
||||
new SectorComplete(fungalPass),
|
||||
new Research(thermalGenerator),
|
||||
new Research(laserDrill),
|
||||
new Research(Items.plastanium),
|
||||
new Research(swarmer)
|
||||
), () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.copper, () -> {
|
||||
nodeProduce(Liquids.water, () -> {
|
||||
|
||||
});
|
||||
|
||||
nodeProduce(Items.lead, () -> {
|
||||
nodeProduce(Items.titanium, () -> {
|
||||
nodeProduce(Liquids.cryofluid, () -> {
|
||||
|
||||
});
|
||||
|
||||
nodeProduce(Items.thorium, () -> {
|
||||
nodeProduce(Items.surgeAlloy, () -> {
|
||||
|
||||
});
|
||||
|
||||
nodeProduce(Items.phaseFabric, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.metaglass, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.sand, () -> {
|
||||
nodeProduce(Items.scrap, () -> {
|
||||
nodeProduce(Liquids.slag, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.coal, () -> {
|
||||
nodeProduce(Items.graphite, () -> {
|
||||
nodeProduce(Items.silicon, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.pyratite, () -> {
|
||||
nodeProduce(Items.blastCompound, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
nodeProduce(Items.sporePod, () -> {
|
||||
|
||||
});
|
||||
|
||||
nodeProduce(Liquids.oil, () -> {
|
||||
nodeProduce(Items.plastanium, () -> {
|
||||
|
||||
});
|
||||
});
|
||||
@@ -534,35 +630,54 @@ public class TechTree implements ContentList{
|
||||
}
|
||||
|
||||
public static void setup(){
|
||||
TechNode.context = null;
|
||||
context = null;
|
||||
map = new ObjectMap<>();
|
||||
all = new Seq<>();
|
||||
}
|
||||
|
||||
public static TechNode node(UnlockableContent content, Runnable children){
|
||||
//all the "node" methods are hidden, because they are for internal context-dependent use only
|
||||
//for custom research, just use the TechNode constructor
|
||||
|
||||
static TechNode node(UnlockableContent content, Runnable children){
|
||||
return node(content, content.researchRequirements(), children);
|
||||
}
|
||||
|
||||
public static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
return new TechNode(content, requirements, children);
|
||||
static TechNode node(UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
return node(content, requirements, null, children);
|
||||
}
|
||||
|
||||
public static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
TechNode node = new TechNode(content, content.researchRequirements(), children);
|
||||
node.objectives = objectives;
|
||||
static TechNode node(UnlockableContent content, ItemStack[] requirements, Seq<Objective> objectives, Runnable children){
|
||||
TechNode node = new TechNode(context, content, requirements);
|
||||
if(objectives != null){
|
||||
node.objectives.addAll(objectives);
|
||||
}
|
||||
|
||||
TechNode prev = context;
|
||||
context = node;
|
||||
children.run();
|
||||
context = prev;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public static TechNode node(UnlockableContent block){
|
||||
static TechNode node(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
return node(content, content.researchRequirements(), objectives, children);
|
||||
}
|
||||
|
||||
static TechNode node(UnlockableContent block){
|
||||
return node(block, () -> {});
|
||||
}
|
||||
|
||||
public static TechNode create(UnlockableContent parent, UnlockableContent block){
|
||||
TechNode.context = all.find(t -> t.content == parent);
|
||||
return node(block, () -> {});
|
||||
static TechNode nodeProduce(UnlockableContent content, Seq<Objective> objectives, Runnable children){
|
||||
return node(content, content.researchRequirements(), objectives.and(new Produce(content)), children);
|
||||
}
|
||||
|
||||
public static @Nullable TechNode get(UnlockableContent content){
|
||||
static TechNode nodeProduce(UnlockableContent content, Runnable children){
|
||||
return nodeProduce(content, new Seq<>(), children);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TechNode get(UnlockableContent content){
|
||||
return map.get(content);
|
||||
}
|
||||
|
||||
@@ -571,8 +686,6 @@ public class TechTree implements ContentList{
|
||||
}
|
||||
|
||||
public static class TechNode{
|
||||
static TechNode context;
|
||||
|
||||
/** Depth in tech tree. */
|
||||
public int depth;
|
||||
/** Requirement node. */
|
||||
@@ -585,19 +698,16 @@ public class TechTree implements ContentList{
|
||||
public final ItemStack[] finishedRequirements;
|
||||
/** Extra objectives needed to research this. */
|
||||
public Seq<Objective> objectives = new Seq<>();
|
||||
/** Time required to research this content, in seconds. */
|
||||
public float time;
|
||||
/** Nodes that depend on this node. */
|
||||
public final Seq<TechNode> children = new Seq<>();
|
||||
|
||||
TechNode(@Nullable TechNode ccontext, UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
if(ccontext != null) ccontext.children.add(this);
|
||||
public TechNode(@Nullable TechNode parent, UnlockableContent content, ItemStack[] requirements){
|
||||
if(parent != null) parent.children.add(this);
|
||||
|
||||
this.parent = ccontext;
|
||||
this.parent = parent;
|
||||
this.content = content;
|
||||
this.requirements = requirements;
|
||||
this.depth = parent == null ? 0 : parent.depth + 1;
|
||||
this.time = Seq.with(requirements).mapFloat(i -> i.item.cost * i.amount).sum() * 10;
|
||||
this.finishedRequirements = new ItemStack[requirements.length];
|
||||
|
||||
//load up the requirements that have been finished if settings are available
|
||||
@@ -605,18 +715,33 @@ public class TechTree implements ContentList{
|
||||
finishedRequirements[i] = new ItemStack(requirements[i].item, Core.settings == null ? 0 : Core.settings.getInt("req-" + content.name + "-" + requirements[i].item.name));
|
||||
}
|
||||
|
||||
var used = new ObjectSet<Content>();
|
||||
|
||||
//add dependencies as objectives.
|
||||
content.getDependencies(d -> objectives.add(new Research(d)));
|
||||
content.getDependencies(d -> {
|
||||
if(used.add(d)){
|
||||
objectives.add(new Research(d));
|
||||
}
|
||||
});
|
||||
|
||||
map.put(content, this);
|
||||
context = this;
|
||||
children.run();
|
||||
context = ccontext;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
TechNode(UnlockableContent content, ItemStack[] requirements, Runnable children){
|
||||
this(context, content, requirements, children);
|
||||
/** Resets finished requirements and saves. */
|
||||
public void reset(){
|
||||
for(ItemStack stack : finishedRequirements){
|
||||
stack.amount = 0;
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
/** Removes this node from the tech tree. */
|
||||
public void remove(){
|
||||
all.remove(this);
|
||||
if(parent != null){
|
||||
parent.children.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Flushes research progress to settings. */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,337 +1,106 @@
|
||||
package mindustry.content;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.type.weather.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Weathers implements ContentList{
|
||||
public static Weather
|
||||
rain,
|
||||
snow,
|
||||
sandstorm,
|
||||
sporestorm;
|
||||
sporestorm,
|
||||
fog;
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
snow = new Weather("snow"){
|
||||
TextureRegion region;
|
||||
float yspeed = 2f, xspeed = 0.25f, padding = 16f, size = 12f, density = 1200f;
|
||||
snow = new ParticleWeather("snow"){{
|
||||
particleRegion = "particle";
|
||||
sizeMax = 13f;
|
||||
sizeMin = 2.6f;
|
||||
density = 1200f;
|
||||
attrs.set(Attribute.light, -0.15f);
|
||||
|
||||
{
|
||||
attrs.set(Attribute.light, -0.15f);
|
||||
}
|
||||
sound = Sounds.windhowl;
|
||||
soundVol = 0f;
|
||||
soundVolOscMag = 1.5f;
|
||||
soundVolOscScl = 1100f;
|
||||
soundVolMin = 0.02f;
|
||||
}};
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
super.load();
|
||||
rain = new RainWeather("rain"){{
|
||||
attrs.set(Attribute.light, -0.2f);
|
||||
attrs.set(Attribute.water, 0.2f);
|
||||
status = StatusEffects.wet;
|
||||
sound = Sounds.rain;
|
||||
soundVol = 0.25f;
|
||||
}};
|
||||
|
||||
region = Core.atlas.find("circle-shadow");
|
||||
}
|
||||
sandstorm = new ParticleWeather("sandstorm"){{
|
||||
color = noiseColor = Color.valueOf("f7cba4");
|
||||
particleRegion = "particle";
|
||||
drawNoise = true;
|
||||
useWindVector = true;
|
||||
sizeMax = 140f;
|
||||
sizeMin = 70f;
|
||||
minAlpha = 0f;
|
||||
maxAlpha = 0.2f;
|
||||
density = 1500f;
|
||||
baseSpeed = 5.4f;
|
||||
attrs.set(Attribute.light, -0.1f);
|
||||
attrs.set(Attribute.water, -0.1f);
|
||||
opacityMultiplier = 0.35f;
|
||||
force = 0.1f;
|
||||
sound = Sounds.wind;
|
||||
soundVol = 0.8f;
|
||||
duration = 7f * Time.toMinutes;
|
||||
}};
|
||||
|
||||
@Override
|
||||
public void drawOver(WeatherState state){
|
||||
rand.setSeed(0);
|
||||
Tmp.r1.setCentered(Core.camera.position.x, Core.camera.position.y, Core.graphics.getWidth() / renderer.minScale(), Core.graphics.getHeight() / renderer.minScale());
|
||||
Tmp.r1.grow(padding);
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / density * state.intensity());
|
||||
sporestorm = new ParticleWeather("sporestorm"){{
|
||||
color = noiseColor = Color.valueOf("7457ce");
|
||||
particleRegion = "circle-small";
|
||||
drawNoise = true;
|
||||
statusGround = false;
|
||||
useWindVector = true;
|
||||
sizeMax = 5f;
|
||||
sizeMin = 2.5f;
|
||||
minAlpha = 0.1f;
|
||||
maxAlpha = 0.8f;
|
||||
density = 2000f;
|
||||
baseSpeed = 4.3f;
|
||||
attrs.set(Attribute.spores, 1f);
|
||||
attrs.set(Attribute.light, -0.15f);
|
||||
status = StatusEffects.sporeSlowed;
|
||||
opacityMultiplier = 0.5f;
|
||||
force = 0.1f;
|
||||
sound = Sounds.wind;
|
||||
soundVol = 0.7f;
|
||||
duration = 7f * Time.toMinutes;
|
||||
}};
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.2f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
|
||||
|
||||
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
|
||||
|
||||
x -= Tmp.r1.x;
|
||||
y -= Tmp.r1.y;
|
||||
x = Mathf.mod(x, Tmp.r1.width);
|
||||
y = Mathf.mod(y, Tmp.r1.height);
|
||||
x += Tmp.r1.x;
|
||||
y += Tmp.r1.y;
|
||||
|
||||
if(Tmp.r3.setCentered(x, y, size * sscl).overlaps(Tmp.r2)){
|
||||
Draw.rect(region, x, y, size * sscl, size * sscl);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rain = new Weather("rain"){
|
||||
float yspeed = 5f, xspeed = 1.5f, padding = 16f, size = 40f, density = 1200f;
|
||||
TextureRegion[] splashes = new TextureRegion[12];
|
||||
|
||||
{
|
||||
attrs.set(Attribute.light, -0.2f);
|
||||
attrs.set(Attribute.water, 0.2f);
|
||||
status = StatusEffects.wet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
super.load();
|
||||
|
||||
for(int i = 0; i < splashes.length; i++){
|
||||
splashes[i] = Core.atlas.find("splash-" + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOver(WeatherState state){
|
||||
Tmp.r1.setCentered(Core.camera.position.x, Core.camera.position.y, Core.graphics.getWidth() / renderer.minScale(), Core.graphics.getHeight() / renderer.minScale());
|
||||
Tmp.r1.grow(padding);
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / density * state.intensity());
|
||||
Lines.stroke(0.75f);
|
||||
float alpha = Draw.getColor().a;
|
||||
Draw.color(Color.royal, Color.white, 0.3f);
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.2f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * xspeed * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) - Time.time() * yspeed * scl);
|
||||
float tint = rand.random(1f) * alpha;
|
||||
|
||||
x -= Tmp.r1.x;
|
||||
y -= Tmp.r1.y;
|
||||
x = Mathf.mod(x, Tmp.r1.width);
|
||||
y = Mathf.mod(y, Tmp.r1.height);
|
||||
x += Tmp.r1.x;
|
||||
y += Tmp.r1.y;
|
||||
|
||||
if(Tmp.r3.setCentered(x, y, size * sscl).overlaps(Tmp.r2)){
|
||||
Draw.alpha(tint);
|
||||
Lines.lineAngle(x, y, Angles.angle(xspeed * scl2, - yspeed * scl), size*sscl/2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawUnder(WeatherState state){
|
||||
Tmp.r1.setCentered(Core.camera.position.x, Core.camera.position.y, Core.graphics.getWidth() / renderer.minScale(), Core.graphics.getHeight() / renderer.minScale());
|
||||
Tmp.r1.grow(padding);
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / density * state.intensity()) / 2;
|
||||
Lines.stroke(0.75f);
|
||||
|
||||
float t = Time.time() / 22f;
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
float offset = rand.random(0f, 1f);
|
||||
float time = t + offset;
|
||||
|
||||
int pos = (int)((time));
|
||||
float life = time % 1f;
|
||||
float x = (rand.random(0f, world.unitWidth()) + pos*953);
|
||||
float y = (rand.random(0f, world.unitHeight()) - pos*453);
|
||||
|
||||
x -= Tmp.r1.x;
|
||||
y -= Tmp.r1.y;
|
||||
x = Mathf.mod(x, Tmp.r1.width);
|
||||
y = Mathf.mod(y, Tmp.r1.height);
|
||||
x += Tmp.r1.x;
|
||||
y += Tmp.r1.y;
|
||||
|
||||
if(Tmp.r3.setCentered(x, y, life * 4f).overlaps(Tmp.r2)){
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
|
||||
if(tile != null && tile.floor().liquidDrop == Liquids.water){
|
||||
Draw.color(Tmp.c1.set(tile.floor().mapColor).mul(1.5f).a(state.opacity()));
|
||||
Draw.rect(splashes[(int)(life * (splashes.length - 1))], x, y);
|
||||
}else{
|
||||
Draw.color(Color.royal, Color.white, 0.3f);
|
||||
Draw.alpha(Mathf.slope(life) * state.opacity());
|
||||
|
||||
float space = 45f;
|
||||
for(int j : new int[]{-1, 1}){
|
||||
Tmp.v1.trns(90f + j*space, 1f + 5f * life);
|
||||
Lines.lineAngle(x + Tmp.v1.x, y + Tmp.v1.y, 90f + j*space, 3f * (1f - life));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sandstorm = new Weather("sandstorm"){
|
||||
TextureRegion region;
|
||||
float size = 140f, padding = size, invDensity = 1500f, baseSpeed = 6.1f;
|
||||
float force = 0.45f;
|
||||
Color color = Color.valueOf("f7cba4");
|
||||
Texture noise;
|
||||
|
||||
{
|
||||
attrs.set(Attribute.light, -0.1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
region = Core.atlas.find("circle-shadow");
|
||||
noise = new Texture("sprites/noiseAlpha.png");
|
||||
noise.setWrap(TextureWrap.repeat);
|
||||
noise.setFilter(TextureFilter.linear);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
noise.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(WeatherState state){
|
||||
float speed = force * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
for(Unit unit : Groups.unit){
|
||||
unit.impulse(windx, windy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOver(WeatherState state){
|
||||
Draw.tint(color);
|
||||
float speed = baseSpeed * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
float scale = 1f / 2000f;
|
||||
float scroll = Time.time() * scale;
|
||||
Tmp.tr1.texture = noise;
|
||||
Core.camera.bounds(Tmp.r1);
|
||||
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
|
||||
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
|
||||
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
|
||||
|
||||
rand.setSeed(0);
|
||||
Tmp.r1.setCentered(Core.camera.position.x, Core.camera.position.y, Core.graphics.getWidth() / renderer.minScale(), Core.graphics.getHeight() / renderer.minScale());
|
||||
Tmp.r1.grow(padding);
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / invDensity * state.intensity());
|
||||
Draw.tint(color);
|
||||
float baseAlpha = Draw.getColor().a;
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.5f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
|
||||
float alpha = rand.random(0.2f);
|
||||
|
||||
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
|
||||
|
||||
x -= Tmp.r1.x;
|
||||
y -= Tmp.r1.y;
|
||||
x = Mathf.mod(x, Tmp.r1.width);
|
||||
y = Mathf.mod(y, Tmp.r1.height);
|
||||
x += Tmp.r1.x;
|
||||
y += Tmp.r1.y;
|
||||
|
||||
if(Tmp.r3.setCentered(x, y, size * sscl).overlaps(Tmp.r2)){
|
||||
Draw.alpha(alpha * baseAlpha);
|
||||
Draw.rect(region, x, y, size * sscl, size * sscl);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sporestorm = new Weather("sporestorm"){
|
||||
TextureRegion region;
|
||||
float size = 5f, padding = size, invDensity = 2000f, baseSpeed = 4.3f, force = 0.28f;
|
||||
Color color = Color.valueOf("7457ce");
|
||||
Texture noise;
|
||||
|
||||
{
|
||||
attrs.set(Attribute.spores, 1f);
|
||||
attrs.set(Attribute.light, -0.15f);
|
||||
status = StatusEffects.sporeSlowed;
|
||||
statusGround = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
region = Core.atlas.find("circle-shadow");
|
||||
noise = new Texture("sprites/noiseAlpha.png");
|
||||
noise.setWrap(TextureWrap.repeat);
|
||||
noise.setFilter(TextureFilter.linear);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(WeatherState state){
|
||||
float speed = force * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
for(Unit unit : Groups.unit){
|
||||
unit.impulse(windx, windy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
noise.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawOver(WeatherState state){
|
||||
Draw.alpha(state.opacity * 0.8f);
|
||||
Draw.tint(color);
|
||||
|
||||
float speed = baseSpeed * state.intensity;
|
||||
float windx = state.windVector.x * speed, windy = state.windVector.y * speed;
|
||||
|
||||
float scale = 1f / 2000f;
|
||||
float scroll = Time.time() * scale;
|
||||
Tmp.tr1.texture = noise;
|
||||
Core.camera.bounds(Tmp.r1);
|
||||
Tmp.tr1.set(Tmp.r1.x*scale, Tmp.r1.y*scale, (Tmp.r1.x + Tmp.r1.width)*scale, (Tmp.r1.y + Tmp.r1.height)*scale);
|
||||
Tmp.tr1.scroll(-windx * scroll, windy * scroll);
|
||||
Draw.rect(Tmp.tr1, Core.camera.position.x, Core.camera.position.y, Core.camera.width, -Core.camera.height);
|
||||
|
||||
rand.setSeed(0);
|
||||
Tmp.r1.setCentered(Core.camera.position.x, Core.camera.position.y, Core.graphics.getWidth() / renderer.minScale(), Core.graphics.getHeight() / renderer.minScale());
|
||||
Tmp.r1.grow(padding);
|
||||
Core.camera.bounds(Tmp.r2);
|
||||
int total = (int)(Tmp.r1.area() / invDensity * state.intensity());
|
||||
Draw.tint(color);
|
||||
float baseAlpha = state.opacity;
|
||||
Draw.alpha(baseAlpha);
|
||||
|
||||
for(int i = 0; i < total; i++){
|
||||
float scl = rand.random(0.5f, 1f);
|
||||
float scl2 = rand.random(0.5f, 1f);
|
||||
float sscl = rand.random(0.5f, 1f);
|
||||
float x = (rand.random(0f, world.unitWidth()) + Time.time() * windx * scl2);
|
||||
float y = (rand.random(0f, world.unitHeight()) + Time.time() * windy * scl);
|
||||
float alpha = rand.random(0.1f, 0.8f);
|
||||
|
||||
x += Mathf.sin(y, rand.random(30f, 80f), rand.random(1f, 7f));
|
||||
|
||||
x -= Tmp.r1.x;
|
||||
y -= Tmp.r1.y;
|
||||
x = Mathf.mod(x, Tmp.r1.width);
|
||||
y = Mathf.mod(y, Tmp.r1.height);
|
||||
x += Tmp.r1.x;
|
||||
y += Tmp.r1.y;
|
||||
|
||||
if(Tmp.r3.setCentered(x, y, size * sscl).overlaps(Tmp.r2)){
|
||||
Draw.alpha(alpha * baseAlpha);
|
||||
Fill.circle(x, y, size * sscl / 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
fog = new ParticleWeather("fog"){{
|
||||
duration = 15f * Time.toMinutes;
|
||||
noiseLayers = 3;
|
||||
noiseLayerSclM = 0.8f;
|
||||
noiseLayerAlphaM = 0.7f;
|
||||
noiseLayerSpeedM = 2f;
|
||||
noiseLayerSclM = 0.6f;
|
||||
baseSpeed = 0.05f;
|
||||
color = noiseColor = Color.grays(0.4f);
|
||||
noiseScale = 1100f;
|
||||
noisePath = "fog";
|
||||
drawParticles = false;
|
||||
drawNoise = true;
|
||||
useWindVector = false;
|
||||
xspeed = 1f;
|
||||
yspeed = 0.01f;
|
||||
attrs.set(Attribute.light, -0.3f);
|
||||
attrs.set(Attribute.water, 0.05f);
|
||||
opacityMultiplier = 0.47f;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.mod.Mods.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static arc.Core.files;
|
||||
import static mindustry.Vars.mods;
|
||||
import static arc.Core.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/**
|
||||
* Loads all game content.
|
||||
@@ -97,6 +98,8 @@ public class ContentLoader{
|
||||
/** Calls Content#init() on everything. Use only after all modules have been created.*/
|
||||
public void init(){
|
||||
initialize(Content::init);
|
||||
if(constants != null) constants.init();
|
||||
Events.fire(new ContentInitEvent());
|
||||
}
|
||||
|
||||
/** Calls Content#load() on everything. Use only after all modules have been created on the client.*/
|
||||
@@ -161,8 +164,8 @@ public class ContentLoader{
|
||||
public void removeLast(){
|
||||
if(lastAdded != null && contentMap[lastAdded.getContentType().ordinal()].peek() == lastAdded){
|
||||
contentMap[lastAdded.getContentType().ordinal()].pop();
|
||||
if(lastAdded instanceof MappableContent){
|
||||
contentNameMap[lastAdded.getContentType().ordinal()].remove(((MappableContent)lastAdded).name);
|
||||
if(lastAdded instanceof MappableContent c){
|
||||
contentNameMap[lastAdded.getContentType().ordinal()].remove(c.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,18 @@ import arc.audio.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.Objectives.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -23,10 +25,12 @@ import mindustry.input.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.io.SaveIO.*;
|
||||
import mindustry.maps.Map;
|
||||
import mindustry.maps.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
@@ -38,14 +42,13 @@ import static mindustry.Vars.*;
|
||||
|
||||
/**
|
||||
* Control module.
|
||||
* Handles all input, saving, keybinds and keybinds.
|
||||
* Handles all input, saving and keybinds.
|
||||
* Should <i>not</i> handle any logic-critical state.
|
||||
* This class is not created in the headless server.
|
||||
*/
|
||||
public class Control implements ApplicationListener, Loadable{
|
||||
public Saves saves;
|
||||
public MusicControl music;
|
||||
public Tutorial tutorial;
|
||||
public SoundControl sound;
|
||||
public InputHandler input;
|
||||
|
||||
private Interval timer = new Interval(2);
|
||||
@@ -54,15 +57,11 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
public Control(){
|
||||
saves = new Saves();
|
||||
tutorial = new Tutorial();
|
||||
music = new MusicControl();
|
||||
sound = new SoundControl();
|
||||
|
||||
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);
|
||||
for(Sound sound : assets.getAll(Sound.class, new Seq<>())){
|
||||
sound.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -91,7 +90,6 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
Events.on(ResetEvent.class, event -> {
|
||||
player.reset();
|
||||
tutorial.reset();
|
||||
|
||||
hiscore = false;
|
||||
saves.resetSave();
|
||||
@@ -131,7 +129,23 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
}));
|
||||
|
||||
Events.on(UnlockEvent.class, e -> ui.hudfrag.showUnlock(e.content));
|
||||
Events.on(UnlockEvent.class, e -> {
|
||||
ui.hudfrag.showUnlock(e.content);
|
||||
|
||||
checkAutoUnlocks();
|
||||
|
||||
if(e.content instanceof SectorPreset){
|
||||
for(TechNode node : TechTree.all){
|
||||
if(!node.content.unlocked() && node.objectives.contains(o -> o instanceof SectorComplete sec && sec.preset == e.content) && !node.objectives.contains(o -> !o.complete())){
|
||||
ui.hudfrag.showToast(new TextureRegionDrawable(node.content.icon(Cicon.large)), bundle.get("available"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(SectorCaptureEvent.class, e -> {
|
||||
checkAutoUnlocks();
|
||||
});
|
||||
|
||||
Events.on(BlockBuildEndEvent.class, e -> {
|
||||
if(e.team == player.team()){
|
||||
@@ -159,11 +173,9 @@ public class Control implements ApplicationListener, Loadable{
|
||||
Events.on(GameOverEvent.class, e -> {
|
||||
if(state.isCampaign() && !net.client() && !headless){
|
||||
|
||||
//delete the save, it is gone.
|
||||
if(saves.getCurrent() != null && !state.rules.tutorial){
|
||||
Sector sector = state.getSector();
|
||||
sector.save = null;
|
||||
saves.getCurrent().delete();
|
||||
//save gameover sate immediately
|
||||
if(saves.getCurrent() != null){
|
||||
saves.getCurrent().save();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -183,18 +195,23 @@ public class Control implements ApplicationListener, Loadable{
|
||||
app.post(() -> ui.hudfrag.showLand());
|
||||
renderer.zoomIn(Fx.coreLand.lifetime);
|
||||
app.post(() -> Fx.coreLand.at(core.getX(), core.getY(), 0, core.block));
|
||||
camera.position.set(core);
|
||||
player.set(core);
|
||||
|
||||
Time.run(Fx.coreLand.lifetime, () -> {
|
||||
Fx.launch.at(core);
|
||||
Effect.shake(5f, 5f, core);
|
||||
|
||||
if(state.isCampaign()){
|
||||
ui.announce("[accent]" + state.rules.sector.name() + "\n" +
|
||||
(state.rules.sector.info.resources.any() ? "[lightgray]" + bundle.get("sectors.resources") + "[white] " +
|
||||
state.rules.sector.info.resources.toString(" ", u -> u.emoji()) : ""), 5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void resetCamera(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAsync(){
|
||||
Draw.scl = 1f / Core.atlas.find("scale_marker").width;
|
||||
@@ -213,6 +230,17 @@ public class Control implements ApplicationListener, Loadable{
|
||||
saves.load();
|
||||
}
|
||||
|
||||
/** Automatically unlocks things with no requirements. */
|
||||
void checkAutoUnlocks(){
|
||||
if(net.client()) return;
|
||||
|
||||
for(TechNode node : TechTree.all){
|
||||
if(!node.content.unlocked() && node.requirements.length == 0 && !node.objectives.contains(o -> !o.complete())){
|
||||
node.content.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void createPlayer(){
|
||||
player = Player.create();
|
||||
player.name = Core.settings.getString("name");
|
||||
@@ -257,59 +285,96 @@ public class Control implements ApplicationListener, Loadable{
|
||||
});
|
||||
}
|
||||
|
||||
//TODO move
|
||||
public void handleLaunch(CoreBuild tile){
|
||||
LaunchCorec ent = LaunchCore.create();
|
||||
ent.set(tile);
|
||||
ent.block(Blocks.coreShard);
|
||||
ent.lifetime(Vars.launchDuration);
|
||||
ent.add();
|
||||
|
||||
//remove schematic requirements from core
|
||||
tile.items.remove(universe.getLastLoadout().requirements());
|
||||
tile.items.remove(universe.getLaunchResources());
|
||||
}
|
||||
|
||||
public void playSector(Sector sector){
|
||||
playSector(sector, sector);
|
||||
}
|
||||
|
||||
public void playSector(@Nullable Sector origin, Sector sector){
|
||||
playSector(origin, sector, new WorldReloader());
|
||||
}
|
||||
|
||||
void playSector(@Nullable Sector origin, Sector sector, WorldReloader reloader){
|
||||
ui.loadAnd(() -> {
|
||||
if(saves.getCurrent() != null && state.isGame()){
|
||||
control.saves.getCurrent().save();
|
||||
control.saves.resetSave();
|
||||
}
|
||||
|
||||
ui.planet.hide();
|
||||
SaveSlot slot = sector.save;
|
||||
sector.planet.setLastSector(sector);
|
||||
if(slot != null && !clearSectors){
|
||||
|
||||
try{
|
||||
net.reset();
|
||||
reloader.begin();
|
||||
slot.load();
|
||||
slot.setAutosave(true);
|
||||
state.rules.sector = sector;
|
||||
|
||||
//if there is no base, simulate a new game and place the right loadout at the spawn position
|
||||
//TODO this is broken?
|
||||
if(state.rules.defaultTeam.cores().isEmpty()){
|
||||
|
||||
//kill all friendly units, since they should be dead anwyay
|
||||
for(Unit unit : Groups.unit){
|
||||
if(unit.team() == state.rules.defaultTeam){
|
||||
unit.remove();
|
||||
}
|
||||
//no spawn set -> delete the sector save
|
||||
if(sector.info.spawnPosition == 0){
|
||||
//delete old save
|
||||
sector.save = null;
|
||||
slot.delete();
|
||||
//play again
|
||||
playSector(origin, sector, reloader);
|
||||
return;
|
||||
}
|
||||
|
||||
Tile spawn = world.tile(sector.getSpawnPosition());
|
||||
//TODO PLACE CORRECT LOADOUT
|
||||
Schematics.placeLoadout(universe.getLastLoadout(), spawn.x, spawn.y);
|
||||
//set spawn for sector damage to use
|
||||
Tile spawn = world.tile(sector.info.spawnPosition);
|
||||
spawn.setBlock(Blocks.coreShard, state.rules.defaultTeam);
|
||||
|
||||
//add extra damage.
|
||||
SectorDamage.apply(1f);
|
||||
|
||||
//reset wave so things are more fair
|
||||
state.wave = 1;
|
||||
//set up default wave time
|
||||
state.wavetime = state.rules.waveSpacing * 2f;
|
||||
//reset captured state
|
||||
sector.info.wasCaptured = false;
|
||||
//re-enable waves
|
||||
state.rules.waves = true;
|
||||
|
||||
//reset win wave??
|
||||
state.rules.winWave = state.rules.attackMode ? -1 : sector.preset != null && sector.preset.captureWave > 0 ? sector.preset.captureWave : state.rules.winWave > state.wave ? state.rules.winWave : 30;
|
||||
|
||||
//if there's still an enemy base left, fix it
|
||||
if(state.rules.attackMode){
|
||||
//replace all broken blocks
|
||||
for(var plan : state.rules.waveTeam.data().blocks){
|
||||
Tile tile = world.tile(plan.x, plan.y);
|
||||
if(tile != null){
|
||||
tile.setBlock(content.block(plan.block), state.rules.waveTeam, plan.rotation);
|
||||
if(plan.config != null && tile.build != null){
|
||||
tile.build.configureAny(plan.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.rules.waveTeam.data().blocks.clear();
|
||||
}
|
||||
|
||||
//kill all units, since they should be dead anyway
|
||||
Groups.unit.clear();
|
||||
Groups.fire.clear();
|
||||
Groups.puddle.clear();
|
||||
|
||||
Schematics.placeLaunchLoadout(spawn.x, spawn.y);
|
||||
|
||||
//set up camera/player locations
|
||||
player.set(spawn.x * tilesize, spawn.y * tilesize);
|
||||
camera.position.set(player);
|
||||
|
||||
Events.fire(new SectorLaunchEvent(sector));
|
||||
Events.fire(Trigger.newGame);
|
||||
}
|
||||
|
||||
state.set(State.playing);
|
||||
reloader.end();
|
||||
|
||||
}catch(SaveException e){
|
||||
Log.err(e);
|
||||
@@ -320,78 +385,21 @@ public class Control implements ApplicationListener, Loadable{
|
||||
}
|
||||
ui.planet.hide();
|
||||
}else{
|
||||
net.reset();
|
||||
logic.reset();
|
||||
reloader.begin();
|
||||
world.loadSector(sector);
|
||||
state.rules.sector = sector;
|
||||
//assign origin when launching
|
||||
state.secinfo.origin = origin;
|
||||
state.secinfo.destination = origin;
|
||||
sector.info.origin = origin;
|
||||
sector.info.destination = origin;
|
||||
logic.play();
|
||||
control.saves.saveSector(sector);
|
||||
Events.fire(new SectorLaunchEvent(sector));
|
||||
Events.fire(Trigger.newGame);
|
||||
reloader.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void playTutorial(){
|
||||
ui.showInfo("@indev.notready");
|
||||
//TODO implement
|
||||
//ui.showInfo("death");
|
||||
/*
|
||||
Zone zone = Zones.groundZero;
|
||||
ui.loadAnd(() -> {
|
||||
logic.reset();
|
||||
net.reset();
|
||||
|
||||
world.beginMapLoad();
|
||||
|
||||
world.resize(zone.generator.width, zone.generator.height);
|
||||
zone.generator.generate(world.tiles);
|
||||
|
||||
Tile coreb = null;
|
||||
|
||||
out:
|
||||
for(int x = 0; x < world.width(); x++){
|
||||
for(int y = 0; y < world.height(); y++){
|
||||
if(world.rawTile(x, y).block() instanceof CoreBlock){
|
||||
coreb = world.rawTile(x, y);
|
||||
break out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Geometry.circle(coreb.x, coreb.y, 10, (cx, cy) -> {
|
||||
Tile tile = world.ltile(cx, cy);
|
||||
if(tile != null && tile.team() == state.rules.defaultTeam && !(tile.block() instanceof CoreBlock)){
|
||||
tile.remove();
|
||||
}
|
||||
});
|
||||
|
||||
Geometry.circle(coreb.x, coreb.y, 5, (cx, cy) -> world.tile(cx, cy).clearOverlay());
|
||||
|
||||
world.endMapLoad();
|
||||
|
||||
zone.rules.get(state.rules);
|
||||
//TODO assign zone!!
|
||||
//state.rules.zone = zone;
|
||||
for(Building core : state.teams.playerCores()){
|
||||
for(ItemStack stack : zone.getStartingItems()){
|
||||
core.items.add(stack.item, stack.amount);
|
||||
}
|
||||
}
|
||||
Building core = state.teams.playerCores().first();
|
||||
core.items.clear();
|
||||
|
||||
logic.play();
|
||||
state.rules.waveTimer = false;
|
||||
state.rules.waveSpacing = 60f * 30;
|
||||
state.rules.buildCostMultiplier = 0.3f;
|
||||
state.rules.tutorial = true;
|
||||
Events.fire(Trigger.newGame);
|
||||
});*/
|
||||
}
|
||||
|
||||
public boolean isHighScore(){
|
||||
return hiscore;
|
||||
}
|
||||
@@ -403,27 +411,33 @@ public class Control implements ApplicationListener, Loadable{
|
||||
try{
|
||||
SaveIO.save(control.saves.getCurrent().file);
|
||||
Log.info("Saved on exit.");
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
}catch(Throwable t){
|
||||
Log.err(t);
|
||||
}
|
||||
}
|
||||
|
||||
for(Music music : assets.getAll(Music.class, new Seq<>())){
|
||||
music.stop();
|
||||
}
|
||||
|
||||
content.dispose();
|
||||
net.dispose();
|
||||
Musics.dispose();
|
||||
Sounds.dispose();
|
||||
ui.editor.dispose();
|
||||
if(ui != null && ui.editor != null) ui.editor.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause(){
|
||||
wasPaused = state.is(State.paused);
|
||||
if(state.is(State.playing)) state.set(State.paused);
|
||||
if(settings.getBool("backgroundpause", true)){
|
||||
wasPaused = state.is(State.paused);
|
||||
if(state.is(State.playing)) state.set(State.paused);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(){
|
||||
if(state.is(State.paused) && !wasPaused){
|
||||
if(state.is(State.paused) && !wasPaused && settings.getBool("backgroundpause", true)){
|
||||
state.set(State.playing);
|
||||
}
|
||||
}
|
||||
@@ -432,19 +446,6 @@ public class Control implements ApplicationListener, Loadable{
|
||||
public void init(){
|
||||
platform.updateRPC();
|
||||
|
||||
//just a regular reminder
|
||||
if(!OS.prop("user.name").equals("anuke") && !OS.hasEnv("iknowwhatimdoing")){
|
||||
app.post(() -> app.post(() -> {
|
||||
ui.showStartupInfo("@indev.popup");
|
||||
}));
|
||||
}
|
||||
|
||||
//play tutorial on start
|
||||
//TODO no tutorial right now
|
||||
if(!settings.getBool("playedtutorial", false)){
|
||||
//Core.app.post(() -> Core.app.post(this::playTutorial));
|
||||
}
|
||||
|
||||
//display UI scale changed dialog
|
||||
if(Core.settings.getBool("uiscalechanged", false)){
|
||||
Core.app.post(() -> Core.app.post(() -> {
|
||||
@@ -477,15 +478,11 @@ public class Control implements ApplicationListener, Loadable{
|
||||
dialog.show();
|
||||
}));
|
||||
}
|
||||
|
||||
if(android){
|
||||
Sounds.empty.loop(0f, 1f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
//TODO find out why this happens on Android
|
||||
//this happens on Android and nobody knows why
|
||||
if(assets == null) return;
|
||||
|
||||
saves.update();
|
||||
@@ -498,8 +495,7 @@ public class Control implements ApplicationListener, Loadable{
|
||||
|
||||
input.updateState();
|
||||
|
||||
music.update();
|
||||
loops.update();
|
||||
sound.update();
|
||||
|
||||
if(Core.input.keyTap(Binding.fullscreen)){
|
||||
boolean full = settings.getBool("fullscreen");
|
||||
@@ -511,19 +507,28 @@ public class Control implements ApplicationListener, Loadable{
|
||||
settings.put("fullscreen", !full);
|
||||
}
|
||||
|
||||
if(Float.isNaN(Vars.player.x) || Float.isNaN(Vars.player.y)){
|
||||
player.set(0, 0);
|
||||
if(!player.dead()) player.unit().kill();
|
||||
}
|
||||
if(Float.isNaN(camera.position.x)) camera.position.x = world.unitWidth()/2f;
|
||||
if(Float.isNaN(camera.position.y)) camera.position.y = world.unitHeight()/2f;
|
||||
|
||||
if(state.isGame()){
|
||||
input.update();
|
||||
|
||||
if(state.rules.tutorial){
|
||||
tutorial.update();
|
||||
}
|
||||
|
||||
//auto-update rpc every 5 seconds
|
||||
if(timer.get(0, 60 * 5)){
|
||||
platform.updateRPC();
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.pause) && !state.isOutOfTime() && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
|
||||
//unlock core items
|
||||
var core = state.rules.defaultTeam.core();
|
||||
if(!net.client() && core != null && state.isCampaign()){
|
||||
core.items.each((i, a) -> i.unlock());
|
||||
}
|
||||
|
||||
if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){
|
||||
state.set(state.is(State.playing) ? State.paused : State.playing);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.assets.loaders.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
|
||||
/** Handles files in a modded context. */
|
||||
public class FileTree implements FileHandleResolver{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -17,17 +17,15 @@ public class GameState{
|
||||
/** Wave countdown in ticks. */
|
||||
public float wavetime;
|
||||
/** Whether the game is in game over state. */
|
||||
public boolean gameOver = false, launched = false, serverPaused = false, wasTimeout;
|
||||
public boolean gameOver = false, serverPaused = false, wasTimeout;
|
||||
/** Map that is currently being played on. */
|
||||
public @NonNull Map map = emptyMap;
|
||||
public Map map = emptyMap;
|
||||
/** The current game rules. */
|
||||
public Rules rules = new Rules();
|
||||
/** Statistics for this save/game. Displayed after game over. */
|
||||
public Stats stats = new Stats();
|
||||
public GameStats stats = new GameStats();
|
||||
/** Global attributes of the environment, calculated by weather. */
|
||||
public Attributes envAttrs = new Attributes();
|
||||
/** Sector information. Only valid in the campaign. */
|
||||
public SectorInfo secinfo = new SectorInfo();
|
||||
/** Team data. Gets reset every new game. */
|
||||
public Teams teams = new Teams();
|
||||
/** Number of enemies in the game; only used clientside in servers. */
|
||||
@@ -35,26 +33,27 @@ public class GameState{
|
||||
/** Current game state. */
|
||||
private State state = State.menu;
|
||||
|
||||
//TODO optimize
|
||||
public Unit boss(){
|
||||
return Groups.unit.find(u -> u.isBoss() && u.team == rules.waveTeam);
|
||||
return teams.boss;
|
||||
}
|
||||
|
||||
public void set(State astate){
|
||||
//cannot pause when in multiplayer
|
||||
if(astate == State.paused && net.active()) return;
|
||||
|
||||
Events.fire(new StateChangeEvent(state, astate));
|
||||
state = astate;
|
||||
}
|
||||
|
||||
public boolean hasSpawns(){
|
||||
return rules.waves && !(isCampaign() && rules.attackMode);
|
||||
}
|
||||
|
||||
/** Note that being in a campaign does not necessarily mean having a sector. */
|
||||
public boolean isCampaign(){
|
||||
return rules.sector != null;
|
||||
}
|
||||
|
||||
/** @return whether the player is in a campaign and they are out of sector time */
|
||||
public boolean isOutOfTime(){
|
||||
return isCampaign() && isGame() && getSector().getTimeSpent() >= turnDuration;
|
||||
}
|
||||
|
||||
public boolean hasSector(){
|
||||
return rules.sector != null;
|
||||
}
|
||||
@@ -69,11 +68,11 @@ public class GameState{
|
||||
}
|
||||
|
||||
public boolean isPaused(){
|
||||
return (is(State.paused) && !net.active()) || (gameOver && !net.active()) || (serverPaused && !isMenu());
|
||||
return (is(State.paused) && !net.active()) || (gameOver && (!net.active() || isCampaign())) || (serverPaused && !isMenu());
|
||||
}
|
||||
|
||||
public boolean isPlaying(){
|
||||
return state == State.playing;
|
||||
return (state == State.playing) || (state == State.paused && !isPaused());
|
||||
}
|
||||
|
||||
/** @return whether the current state is *not* the menu. */
|
||||
|
||||
@@ -4,8 +4,8 @@ import arc.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
@@ -14,9 +14,6 @@ import mindustry.maps.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.type.Weather.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.blocks.storage.CoreBlock.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -37,36 +34,10 @@ public class Logic implements ApplicationListener{
|
||||
Events.on(BlockDestroyEvent.class, event -> {
|
||||
//blocks that get broken are appended to the team's broken block queue
|
||||
Tile tile = event.tile;
|
||||
Block block = tile.block();
|
||||
//skip null entities or un-rebuildables, for obvious reasons; also skip client since they can't modify these requests
|
||||
if(tile.build == null || !tile.block().rebuildable || net.client()) return;
|
||||
|
||||
if(block instanceof ConstructBlock){
|
||||
|
||||
ConstructBuild entity = tile.bc();
|
||||
|
||||
//update block to reflect the fact that something was being constructed
|
||||
if(entity.cblock != null && entity.cblock.synthetic()){
|
||||
block = entity.cblock;
|
||||
}else{
|
||||
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TeamData data = state.teams.get(tile.team());
|
||||
|
||||
//remove existing blocks that have been placed here.
|
||||
//painful O(n) iteration + copy
|
||||
for(int i = 0; i < data.blocks.size; i++){
|
||||
BlockPlan b = data.blocks.get(i);
|
||||
if(b.x == tile.x && b.y == tile.y){
|
||||
data.blocks.removeIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)tile.build.rotation, block.id, tile.build.config()));
|
||||
tile.build.addPlan(true);
|
||||
});
|
||||
|
||||
Events.on(BlockBuildEndEvent.class, event -> {
|
||||
@@ -83,51 +54,117 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(LaunchItemEvent.class, e -> state.secinfo.handleItemExport(e.stack));
|
||||
|
||||
//when loading a 'damaged' sector, propagate the damage
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
Events.on(SaveLoadEvent.class, e -> {
|
||||
if(state.isCampaign()){
|
||||
long seconds = state.rules.sector.getSecondsPassed();
|
||||
CoreBuild core = state.rules.defaultTeam.core();
|
||||
SectorInfo info = state.rules.sector.info;
|
||||
info.write();
|
||||
|
||||
//apply fractional damage based on how many turns have passed for this sector
|
||||
float turnsPassed = seconds / (turnDuration / 60f);
|
||||
//how much wave time has passed
|
||||
int wavesPassed = info.wavesPassed;
|
||||
|
||||
if(state.rules.sector.hasWaves() && turnsPassed > 0 && state.rules.sector.hasBase()){
|
||||
SectorDamage.apply(turnsPassed / sectorDestructionTurns);
|
||||
//wave has passed, remove all enemies, they are assumed to be dead
|
||||
if(wavesPassed > 0){
|
||||
Groups.unit.each(u -> {
|
||||
if(u.team == state.rules.waveTeam){
|
||||
u.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//add resources based on turns passed
|
||||
if(state.rules.sector.save != null && core != null){
|
||||
//update correct storage capacity
|
||||
state.rules.sector.save.meta.secinfo.storageCapacity = core.storageCapacity;
|
||||
//simulate passing of waves
|
||||
if(wavesPassed > 0){
|
||||
//simulate wave counter moving forward
|
||||
state.wave += wavesPassed;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
|
||||
//add new items received
|
||||
state.rules.sector.calculateReceivedItems().each((item, amount) -> core.items.add(item, amount));
|
||||
SectorDamage.applyCalculatedDamage();
|
||||
|
||||
//clear received items
|
||||
state.rules.sector.setExtraItems(new ItemSeq());
|
||||
|
||||
//validation
|
||||
for(Item item : content.items()){
|
||||
//ensure positive items
|
||||
if(core.items.get(item) < 0) core.items.set(item, 0);
|
||||
//cap the items
|
||||
if(core.items.get(item) > core.storageCapacity) core.items.set(item, core.storageCapacity);
|
||||
//make sure damaged buildings are counted
|
||||
for(Tile tile : world.tiles){
|
||||
if(tile.build != null && tile.build.damaged()){
|
||||
indexer.notifyTileDamaged(tile.build);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.rules.sector.setSecondsPassed(0);
|
||||
}
|
||||
//reset values
|
||||
info.damage = 0f;
|
||||
info.wavesPassed = 0;
|
||||
info.hasCore = true;
|
||||
info.secondsPassed = 0;
|
||||
|
||||
state.rules.sector.saveInfo();
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(WorldLoadEvent.class, e -> {
|
||||
//enable infinite ammo for wave team by default
|
||||
state.rules.waveTeam.rules().infiniteAmmo = true;
|
||||
|
||||
if(state.isCampaign()){
|
||||
//enable building AI on campaign unless the preset disables it
|
||||
if(!(state.getSector().preset != null && !state.getSector().preset.useAI)){
|
||||
state.rules.waveTeam.rules().ai = true;
|
||||
}
|
||||
state.rules.waveTeam.rules().aiTier = state.getSector().threat * 0.8f;
|
||||
state.rules.waveTeam.rules().infiniteResources = true;
|
||||
|
||||
//fill enemy cores by default.
|
||||
for(var core : state.rules.waveTeam.cores()){
|
||||
for(Item item : content.items()){
|
||||
core.items.set(item, core.block.itemCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//save settings
|
||||
Core.settings.manualSave();
|
||||
});
|
||||
|
||||
//sync research
|
||||
Events.on(UnlockEvent.class, e -> {
|
||||
if(net.server()){
|
||||
Call.researched(e.content);
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(SectorCaptureEvent.class, e -> {
|
||||
if(!net.client() && e.sector == state.getSector() && e.sector.isBeingPlayed()){
|
||||
for(Tile tile : world.tiles){
|
||||
//convert all blocks to neutral, randomly killing them
|
||||
if(tile.isCenter() && tile.build != null && tile.build.team == state.rules.waveTeam){
|
||||
Building b = tile.build;
|
||||
Call.setTeam(b, Team.derelict);
|
||||
Time.run(Mathf.random(0f, 60f * 6f), () -> {
|
||||
if(Mathf.chance(0.25)){
|
||||
b.kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//kill all units
|
||||
Groups.unit.each(u -> {
|
||||
if(u.team == state.rules.waveTeam){
|
||||
Time.run(Mathf.random(0f, 60f * 5f), u::kill);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//send out items to each client
|
||||
Events.on(TurnEvent.class, e -> {
|
||||
if(net.server() && state.isCampaign()){
|
||||
int[] out = new int[content.items().size];
|
||||
state.getSector().info.production.each((item, stat) -> {
|
||||
out[item.id] = Math.max(0, (int)(stat.mean * turnDuration / 60));
|
||||
});
|
||||
|
||||
Call.sectorProduced(out);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/** Adds starting items, resets wave time, and sets state to playing. */
|
||||
@@ -167,18 +204,13 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
|
||||
public void skipWave(){
|
||||
if(state.isCampaign()){
|
||||
//warp time spent forward because the wave was just skipped.
|
||||
state.secinfo.internalTimeSpent += state.wavetime;
|
||||
}
|
||||
|
||||
state.wavetime = 0;
|
||||
}
|
||||
|
||||
public void runWave(){
|
||||
spawner.spawnEnemies();
|
||||
state.wave++;
|
||||
state.wavetime = state.hasSector() && state.getSector().isLaunchWave(state.wave) ? state.rules.waveSpacing * state.rules.launchWaveMultiplier : state.rules.waveSpacing;
|
||||
state.wavetime = state.rules.waveSpacing;
|
||||
|
||||
Events.fire(new WaveEvent());
|
||||
}
|
||||
@@ -199,36 +231,23 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
|
||||
//if there's a "win" wave and no enemies are present, win automatically
|
||||
if(state.rules.waves && state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()){
|
||||
//the sector has been conquered - waves get disabled
|
||||
state.rules.waves = false;
|
||||
if(state.rules.waves && (state.enemies == 0 && state.rules.winWave > 0 && state.wave >= state.rules.winWave && !spawner.isSpawning()) ||
|
||||
(state.rules.attackMode && state.rules.waveTeam.cores().isEmpty())){
|
||||
|
||||
//fire capture event
|
||||
Events.fire(new SectorCaptureEvent(state.rules.sector));
|
||||
|
||||
//save, just in case
|
||||
if(!headless){
|
||||
control.saves.saveSector(state.rules.sector);
|
||||
}
|
||||
Call.sectorCapture();
|
||||
}
|
||||
}else{
|
||||
if(!state.rules.attackMode && state.teams.playerCores().size == 0 && !state.gameOver){
|
||||
state.gameOver = true;
|
||||
Events.fire(new GameOverEvent(state.rules.waveTeam));
|
||||
}else if(state.rules.attackMode){
|
||||
Team alive = null;
|
||||
//count # of teams alive
|
||||
int countAlive = state.teams.getActive().count(TeamData::hasCore);
|
||||
|
||||
for(TeamData team : state.teams.getActive()){
|
||||
if(team.hasCore()){
|
||||
if(alive != null){
|
||||
return;
|
||||
}
|
||||
alive = team.team;
|
||||
}
|
||||
}
|
||||
|
||||
if(alive != null && !state.gameOver){
|
||||
Events.fire(new GameOverEvent(alive));
|
||||
if((countAlive <= 1 || (!state.rules.pvp && state.rules.defaultTeam.core() == null)) && !state.gameOver){
|
||||
//find team that won
|
||||
TeamData left = state.teams.getActive().find(TeamData::hasCore);
|
||||
Events.fire(new GameOverEvent(left == null ? Team.derelict : left.team));
|
||||
state.gameOver = true;
|
||||
}
|
||||
}
|
||||
@@ -236,14 +255,15 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
|
||||
private void updateWeather(){
|
||||
state.rules.weather.removeAll(w -> w.weather == null);
|
||||
|
||||
for(WeatherEntry entry : state.rules.weather){
|
||||
//update cooldown
|
||||
entry.cooldown -= Time.delta;
|
||||
|
||||
//create new event when not active
|
||||
if(entry.cooldown < 0 && !entry.weather.isActive()){
|
||||
float duration = Mathf.random(entry.minDuration, entry.maxDuration);
|
||||
if((entry.cooldown < 0 || entry.always) && !entry.weather.isActive()){
|
||||
float duration = entry.always ? Float.POSITIVE_INFINITY : Mathf.random(entry.minDuration, entry.maxDuration);
|
||||
entry.cooldown = duration + Mathf.random(entry.minFrequency, entry.maxFrequency);
|
||||
Tmp.v1.setToRandomDirection();
|
||||
Call.createWeather(entry.weather, entry.intensity, duration, Tmp.v1.x, Tmp.v1.y);
|
||||
@@ -251,56 +271,29 @@ public class Logic implements ApplicationListener{
|
||||
}
|
||||
}
|
||||
|
||||
@Remote(called = Loc.both)
|
||||
public static void launchZone(){
|
||||
if(!state.isCampaign()) return;
|
||||
@Remote(called = Loc.server)
|
||||
public static void sectorCapture(){
|
||||
//the sector has been conquered - waves get disabled
|
||||
state.rules.waves = false;
|
||||
|
||||
if(!headless){
|
||||
ui.hudfrag.showLaunch();
|
||||
if(state.rules.sector == null){
|
||||
//disable attack mode
|
||||
state.rules.attackMode = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO better core launch effect
|
||||
for(Building tile : state.teams.playerCores()){
|
||||
Fx.launch.at(tile);
|
||||
state.rules.sector.info.wasCaptured = true;
|
||||
|
||||
//fire capture event
|
||||
Events.fire(new SectorCaptureEvent(state.rules.sector));
|
||||
|
||||
//disable attack mode
|
||||
state.rules.attackMode = false;
|
||||
|
||||
//save, just in case
|
||||
if(!headless && !net.client()){
|
||||
control.saves.saveSector(state.rules.sector);
|
||||
}
|
||||
|
||||
Sector sector = state.rules.sector;
|
||||
|
||||
//TODO containers must be launched too
|
||||
Time.runTask(30f, () -> {
|
||||
Sector origin = sector.save.meta.secinfo.origin;
|
||||
if(origin != null){
|
||||
ItemSeq stacks = origin.getExtraItems();
|
||||
|
||||
//add up all items into list
|
||||
for(Building entity : state.teams.playerCores()){
|
||||
entity.items.each(stacks::add);
|
||||
}
|
||||
|
||||
//save received items
|
||||
origin.setExtraItems(stacks);
|
||||
}
|
||||
|
||||
//remove all the cores
|
||||
state.teams.playerCores().each(b -> b.tile.remove());
|
||||
|
||||
state.launched = true;
|
||||
state.gameOver = true;
|
||||
|
||||
//save over the data w/o the cores
|
||||
sector.save.save();
|
||||
|
||||
//run a turn, since launching takes up a turn
|
||||
universe.runTurn();
|
||||
|
||||
//TODO apply extra damage to sector
|
||||
//sector.setTurnsPassed(sector.getTurnsPassed() + 3);
|
||||
|
||||
//TODO load the sector that was launched from
|
||||
Events.fire(new LaunchEvent());
|
||||
//manually fire game over event now
|
||||
Events.fire(new GameOverEvent(state.rules.defaultTeam));
|
||||
});
|
||||
}
|
||||
|
||||
@Remote(called = Loc.both)
|
||||
@@ -315,6 +308,53 @@ public class Logic implements ApplicationListener{
|
||||
netClient.setQuiet();
|
||||
}
|
||||
|
||||
//called when the remote server researches something
|
||||
@Remote
|
||||
public static void researched(Content content){
|
||||
if(!(content instanceof UnlockableContent u)) return;
|
||||
|
||||
var node = u.node();
|
||||
|
||||
//unlock all direct dependencies on client, permanently
|
||||
while(node != null){
|
||||
node.content.unlock();
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
state.rules.researched.add(u.name);
|
||||
}
|
||||
|
||||
//called when the remote server runs a turn and produces something
|
||||
@Remote
|
||||
public static void sectorProduced(int[] amounts){
|
||||
if(!state.isCampaign()) return;
|
||||
Planet planet = state.rules.sector.planet;
|
||||
boolean any = false;
|
||||
|
||||
for(Item item : content.items()){
|
||||
int am = amounts[item.id];
|
||||
if(am > 0){
|
||||
int sumMissing = planet.sectors.sum(s -> s.hasBase() ? s.info.storageCapacity - s.info.items.get(item) : 0);
|
||||
if(sumMissing == 0) continue;
|
||||
//how much % to add
|
||||
double percent = Math.min((double)am / sumMissing, 1);
|
||||
for(Sector sec : planet.sectors){
|
||||
if(sec.hasBase()){
|
||||
int added = (int)Math.ceil(((sec.info.storageCapacity - sec.info.items.get(item)) * percent));
|
||||
sec.info.items.add(item, added);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(any){
|
||||
for(Sector sec : planet.sectors){
|
||||
sec.saveInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
//save the settings before quitting
|
||||
@@ -332,24 +372,14 @@ public class Logic implements ApplicationListener{
|
||||
|
||||
if(state.isGame()){
|
||||
if(!net.client()){
|
||||
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type().isCounted);
|
||||
}
|
||||
|
||||
//force pausing when the player is out of sector time
|
||||
if(state.isOutOfTime()){
|
||||
if(!state.wasTimeout){
|
||||
universe.displayTimeEnd();
|
||||
state.wasTimeout = true;
|
||||
}
|
||||
//if no turn was run.
|
||||
if(state.isOutOfTime()){
|
||||
state.set(State.paused);
|
||||
}
|
||||
state.enemies = Groups.unit.count(u -> u.team() == state.rules.waveTeam && u.type.isCounted);
|
||||
}
|
||||
|
||||
if(!state.isPaused()){
|
||||
state.teams.updateTeamStats();
|
||||
|
||||
if(state.isCampaign()){
|
||||
state.secinfo.update();
|
||||
state.rules.sector.info.update();
|
||||
}
|
||||
|
||||
if(state.isCampaign()){
|
||||
@@ -358,7 +388,7 @@ public class Logic implements ApplicationListener{
|
||||
Time.update();
|
||||
|
||||
//weather is serverside
|
||||
if(!net.client()){
|
||||
if(!net.client() && !state.isEditor()){
|
||||
updateWeather();
|
||||
|
||||
for(TeamData data : state.teams.getActive()){
|
||||
@@ -395,5 +425,4 @@ public class Logic implements ApplicationListener{
|
||||
public boolean isWaitingWave(){
|
||||
return (state.rules.waitEnemies || (state.wave >= state.rules.winWave && state.rules.winWave > 0)) && state.enemies > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import mindustry.net.Administration.*;
|
||||
import mindustry.net.Net.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Packets.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.modules.*;
|
||||
|
||||
@@ -32,7 +33,7 @@ import static mindustry.Vars.*;
|
||||
public class NetClient implements ApplicationListener{
|
||||
private static final float dataTimeout = 60 * 18;
|
||||
private static final float playerSyncTime = 2;
|
||||
public final static float viewScale = 2f;
|
||||
public static final float viewScale = 2f;
|
||||
|
||||
private long ping;
|
||||
private Interval timer = new Interval(5);
|
||||
@@ -40,7 +41,7 @@ public class NetClient implements ApplicationListener{
|
||||
private boolean connecting = false;
|
||||
/** If true, no message will be shown on disconnect. */
|
||||
private boolean quiet = false;
|
||||
/** Whether to supress disconnect events completely.*/
|
||||
/** Whether to suppress disconnect events completely.*/
|
||||
private boolean quietReset = false;
|
||||
/** Counter for data timeout. */
|
||||
private float timeoutTime = 0f;
|
||||
@@ -105,12 +106,10 @@ public class NetClient implements ApplicationListener{
|
||||
Time.runTask(3f, ui.loadfrag::hide);
|
||||
|
||||
if(packet.reason != null){
|
||||
if(packet.reason.equals("closed")){
|
||||
ui.showSmall("@disconnect", "@disconnect.closed");
|
||||
}else if(packet.reason.equals("timeout")){
|
||||
ui.showSmall("@disconnect", "@disconnect.timeout");
|
||||
}else if(packet.reason.equals("error")){
|
||||
ui.showSmall("@disconnect", "@disconnect.error");
|
||||
switch(packet.reason){
|
||||
case "closed" -> ui.showSmall("@disconnect", "@disconnect.closed");
|
||||
case "timeout" -> ui.showSmall("@disconnect", "@disconnect.timeout");
|
||||
case "error" -> ui.showSmall("@disconnect", "@disconnect.error");
|
||||
}
|
||||
}else{
|
||||
ui.showErrorMessage("@disconnect");
|
||||
@@ -196,14 +195,14 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
//server console logging
|
||||
Log.info("&y@: &lb@", player.name, message);
|
||||
Log.info("&fi@: @", "&lc" + player.name, "&lw" + message);
|
||||
|
||||
//invoke event for all clients but also locally
|
||||
//this is required so other clients get the correct name even if they don't know who's sending it yet
|
||||
Call.sendMessage(message, colorizeName(player.id(), player.name), player);
|
||||
}else{
|
||||
//log command to console but with brackets
|
||||
Log.info("<&y@: &lm@&lg>", player.name, message);
|
||||
Log.info("<&fi@: @&fr>", "&lk" + player.name, "&lw" + message);
|
||||
|
||||
//a command was sent, now get the output
|
||||
if(response.type != ResponseType.valid){
|
||||
@@ -236,7 +235,7 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
ui.join.connect(ip, port);
|
||||
}
|
||||
|
||||
|
||||
@Remote(targets = Loc.client)
|
||||
public static void ping(Player player, long time){
|
||||
Call.pingResponse(player.con, time);
|
||||
@@ -258,6 +257,11 @@ public class NetClient implements ApplicationListener{
|
||||
public static void kick(KickReason reason){
|
||||
netClient.disconnectQuietly();
|
||||
logic.reset();
|
||||
|
||||
if(reason == KickReason.serverRestarting){
|
||||
ui.join.reconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!reason.quiet){
|
||||
if(reason.extraText() != null){
|
||||
@@ -295,6 +299,13 @@ public class NetClient implements ApplicationListener{
|
||||
setHudText(message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void announce(String message){
|
||||
if(message == null) return;
|
||||
|
||||
ui.announce(message);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void infoMessage(String message){
|
||||
if(message == null) return;
|
||||
@@ -317,15 +328,15 @@ public class NetClient implements ApplicationListener{
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both, unreliable = true)
|
||||
public static void onEffect(Effect effect, float x, float y, float rotation, Color color){
|
||||
public static void effect(Effect effect, float x, float y, float rotation, Color color){
|
||||
if(effect == null) return;
|
||||
|
||||
effect.at(x, y, rotation, color);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void onEffectReliable(Effect effect, float x, float y, float rotation, Color color){
|
||||
onEffect(effect, x, y, rotation, color);
|
||||
public static void effectReliable(Effect effect, float x, float y, float rotation, Color color){
|
||||
effect(effect, x, y, rotation, color);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
@@ -335,6 +346,13 @@ public class NetClient implements ApplicationListener{
|
||||
ui.showInfoToast(message, duration);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void warningToast(int unicode, String text){
|
||||
if(text == null || Fonts.icon.getData().getGlyph((char)unicode) == null) return;
|
||||
|
||||
ui.hudfrag.showToast(Fonts.getGlyph(Fonts.icon, (char)unicode), text);
|
||||
}
|
||||
|
||||
@Remote(variants = Variant.both)
|
||||
public static void setRules(Rules rules){
|
||||
state.rules = rules;
|
||||
@@ -435,7 +453,7 @@ public class NetClient implements ApplicationListener{
|
||||
tile.build.readAll(Reads.get(input), tile.build.version());
|
||||
}
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,19 +584,19 @@ public class NetClient implements ApplicationListener{
|
||||
BuildPlan[] requests = null;
|
||||
if(player.isBuilder()){
|
||||
//limit to 10 to prevent buffer overflows
|
||||
int usedRequests = Math.min(player.builder().plans().size, 10);
|
||||
int usedRequests = Math.min(player.unit().plans().size, 10);
|
||||
|
||||
int totalLength = 0;
|
||||
|
||||
//prevent buffer overflow by checking config length
|
||||
for(int i = 0; i < usedRequests; i++){
|
||||
BuildPlan plan = player.builder().plans().get(i);
|
||||
if(plan.config instanceof byte[]){
|
||||
int length = ((byte[])plan.config).length;
|
||||
BuildPlan plan = player.unit().plans().get(i);
|
||||
if(plan.config instanceof byte[] b){
|
||||
int length = b.length;
|
||||
totalLength += length;
|
||||
}
|
||||
|
||||
if(totalLength > 2048){
|
||||
if(totalLength > 1024){
|
||||
usedRequests = i + 1;
|
||||
break;
|
||||
}
|
||||
@@ -586,7 +604,7 @@ public class NetClient implements ApplicationListener{
|
||||
|
||||
requests = new BuildPlan[usedRequests];
|
||||
for(int i = 0; i < usedRequests; i++){
|
||||
requests[i] = player.builder().plans().get(i);
|
||||
requests[i] = player.unit().plans().get(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,9 +618,9 @@ public class NetClient implements ApplicationListener{
|
||||
unit.x, unit.y,
|
||||
player.unit().aimX(), player.unit().aimY(),
|
||||
unit.rotation,
|
||||
unit instanceof Mechc ? ((Mechc)unit).baseRotation() : 0,
|
||||
unit instanceof Mechc m ? m.baseRotation() : 0,
|
||||
unit.vel.x, unit.vel.y,
|
||||
player.miner().mineTile(),
|
||||
player.unit().mineTile,
|
||||
player.boosting, player.shooting, ui.chatfrag.shown(), control.input.isBuilding,
|
||||
requests,
|
||||
Core.camera.position.x, Core.camera.position.y,
|
||||
|
||||
@@ -7,7 +7,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.CommandHandler.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.serialization.*;
|
||||
@@ -19,6 +18,7 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.net.Administration.*;
|
||||
import mindustry.net.Packets.*;
|
||||
@@ -41,7 +41,7 @@ public class NetServer implements ApplicationListener{
|
||||
private static final Vec2 vector = new Vec2();
|
||||
private static final Rect viewport = new Rect();
|
||||
/** If a player goes away of their server-side coordinates by this distance, they get teleported back. */
|
||||
private static final float correctDist = 16f;
|
||||
private static final float correctDist = tilesize * 12f;
|
||||
|
||||
public final Administration admins = new Administration();
|
||||
public final CommandHandler clientCommands = new CommandHandler("/");
|
||||
@@ -164,7 +164,7 @@ public class NetServer implements ApplicationListener{
|
||||
info.id = packet.uuid;
|
||||
admins.save();
|
||||
Call.infoMessage(con, "You are not whitelisted here.");
|
||||
Log.info("&lcDo &lywhitelist-add @&lc to whitelist the player &lb'@'", packet.uuid, packet.name);
|
||||
info("&lcDo &lywhitelist-add @&lc to whitelist the player &lb'@'", packet.uuid, packet.name);
|
||||
con.kick(KickReason.whitelist);
|
||||
return;
|
||||
}
|
||||
@@ -226,8 +226,8 @@ public class NetServer implements ApplicationListener{
|
||||
writeBuffer.reset();
|
||||
player.write(outputBuffer);
|
||||
}catch(Throwable t){
|
||||
t.printStackTrace();
|
||||
con.kick(KickReason.nameEmpty);
|
||||
err(t);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,11 +248,10 @@ public class NetServer implements ApplicationListener{
|
||||
try{
|
||||
RemoteReadServer.readPacket(packet.reader(), packet.type, con.player);
|
||||
}catch(ValidateException e){
|
||||
Log.debug("Validation failed for '@': @", e.player, e.getMessage());
|
||||
debug("Validation failed for '@': @", e.player, e.getMessage());
|
||||
}catch(RuntimeException e){
|
||||
if(e.getCause() instanceof ValidateException){
|
||||
ValidateException v = (ValidateException)e.getCause();
|
||||
Log.debug("Validation failed for '@': @", v.player, v.getMessage());
|
||||
if(e.getCause() instanceof ValidateException v){
|
||||
debug("Validation failed for '@': @", v.player, v.getMessage());
|
||||
}else{
|
||||
throw e;
|
||||
}
|
||||
@@ -285,7 +284,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(Strings.format("[orange]-- Commands Page[lightgray] @[gray]/[lightgray]@[orange] --\n\n", (page+1), pages));
|
||||
result.append(Strings.format("[orange]-- Commands Page[lightgray] @[gray]/[lightgray]@[orange] --\n\n", (page + 1), pages));
|
||||
|
||||
for(int i = commandsPerPage * page; i < Math.min(commandsPerPage * (page + 1), clientCommands.getCommandList().size); i++){
|
||||
Command command = clientCommands.getCommandList().get(i);
|
||||
@@ -301,6 +300,15 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
});
|
||||
|
||||
clientCommands.<Player>register("a", "<message...>", "Send a message only to admins.", (args, player) -> {
|
||||
if(!player.admin){
|
||||
player.sendMessage("[scarlet]You must be admin to use this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
Groups.player.each(Player::admin, a -> a.sendMessage(args[0], player, "[#" + Pal.adminChat.toString() + "]<A>" + NetClient.colorizeName(player.id, player.name)));
|
||||
});
|
||||
|
||||
//duration of a a kick in seconds
|
||||
int kickDuration = 60 * 60;
|
||||
//voting round duration in seconds
|
||||
@@ -331,16 +339,16 @@ public class NetServer implements ApplicationListener{
|
||||
votes += d;
|
||||
voted.addAll(player.uuid(), admins.getInfo(player.uuid()).lastIP);
|
||||
|
||||
Call.sendMessage(Strings.format("[lightgray]A player has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
|
||||
target.name, votes, votesRequired()));
|
||||
Call.sendMessage(Strings.format("[lightgray]@[lightgray] has voted on kicking[orange] @[].[accent] (@/@)\n[lightgray]Type[orange] /vote <y/n>[] to agree.",
|
||||
player.name, target.name, votes, votesRequired()));
|
||||
|
||||
checkPass();
|
||||
}
|
||||
|
||||
boolean checkPass(){
|
||||
if(votes >= votesRequired()){
|
||||
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] @[orange] will be banned from the server for @ minutes.", target.name, (kickDuration/60)));
|
||||
target.getInfo().lastKicked = Time.millis() + kickDuration*1000;
|
||||
Call.sendMessage(Strings.format("[orange]Vote passed.[scarlet] @[orange] will be banned from the server for @ minutes.", target.name, (kickDuration / 60)));
|
||||
target.getInfo().lastKicked = Time.millis() + kickDuration * 1000;
|
||||
Groups.player.each(p -> p.uuid().equals(target.uuid()), p -> p.kick(KickReason.vote));
|
||||
map[0] = null;
|
||||
task.cancel();
|
||||
@@ -410,11 +418,11 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
VoteSession session = new VoteSession(currentlyKicking, found);
|
||||
session.vote(player, 1);
|
||||
vtime.reset();
|
||||
vtime.reset();
|
||||
currentlyKicking[0] = session;
|
||||
}
|
||||
}else{
|
||||
player.sendMessage("[scarlet]No player[orange]'" + args[0] + "'[scarlet] found.");
|
||||
player.sendMessage("[scarlet]No player [orange]'" + args[0] + "'[scarlet] found.");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -439,12 +447,17 @@ public class NetServer implements ApplicationListener{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!arg[0].toLowerCase().equals("y") && !arg[0].toLowerCase().equals("n")){
|
||||
if(currentlyKicking[0].target.team() != player.team()){
|
||||
player.sendMessage("[scarlet]You can't vote for other teams.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!arg[0].equalsIgnoreCase("y") && !arg[0].equalsIgnoreCase("n")){
|
||||
player.sendMessage("[scarlet]Vote either 'y' (yes) or 'n' (no).");
|
||||
return;
|
||||
}
|
||||
|
||||
int sign = arg[0].toLowerCase().equals("y") ? 1 : -1;
|
||||
int sign = arg[0].equalsIgnoreCase("y") ? 1 : -1;
|
||||
currentlyKicking[0].vote(player, sign);
|
||||
}
|
||||
});
|
||||
@@ -485,7 +498,7 @@ public class NetServer implements ApplicationListener{
|
||||
data.stream = new ByteArrayInputStream(stream.toByteArray());
|
||||
player.con.sendStream(data);
|
||||
|
||||
Log.debug("Packed @ bytes of world data.", stream.size());
|
||||
debug("Packed @ bytes of world data.", stream.size());
|
||||
}
|
||||
|
||||
public void addPacketHandler(String type, Cons2<Player, String> handler){
|
||||
@@ -497,7 +510,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
public static void onDisconnect(Player player, String reason){
|
||||
//singleplayer multiplayer wierdness
|
||||
//singleplayer multiplayer weirdness
|
||||
if(player.con == null){
|
||||
player.remove();
|
||||
return;
|
||||
@@ -510,7 +523,8 @@ public class NetServer implements ApplicationListener{
|
||||
Call.playerDisconnect(player.id());
|
||||
}
|
||||
|
||||
if(Config.showConnectMessages.bool()) Log.info("&lm[@] &lc@ has disconnected. &lg&fi(@)", player.uuid(), player.name, reason);
|
||||
String message = Strings.format("&lb@&fi&lk has disconnected. &fi&lk[&lb@&fi&lk] (@)", player.name, player.uuid(), reason);
|
||||
if(Config.showConnectMessages.bool()) info(message);
|
||||
}
|
||||
|
||||
player.remove();
|
||||
@@ -530,7 +544,7 @@ public class NetServer implements ApplicationListener{
|
||||
public static void serverPacketUnreliable(Player player, String type, String contents){
|
||||
serverPacketReliable(player, type, contents);
|
||||
}
|
||||
|
||||
|
||||
private static boolean invalid(float f){
|
||||
return Float.isInfinite(f) || Float.isNaN(f);
|
||||
}
|
||||
@@ -577,7 +591,7 @@ public class NetServer implements ApplicationListener{
|
||||
shooting = false;
|
||||
}
|
||||
|
||||
if(!player.dead() && (player.unit().type().flying || !player.unit().type().canBoost)){
|
||||
if(!player.dead() && (player.unit().type.flying || !player.unit().type.canBoost)){
|
||||
boosting = false;
|
||||
}
|
||||
|
||||
@@ -591,8 +605,8 @@ public class NetServer implements ApplicationListener{
|
||||
player.unit().aim(pointerX, pointerY);
|
||||
|
||||
if(player.isBuilder()){
|
||||
player.builder().clearBuilding();
|
||||
player.builder().updateBuilding(building);
|
||||
player.unit().clearBuilding();
|
||||
player.unit().updateBuilding(building);
|
||||
|
||||
if(requests != null){
|
||||
for(BuildPlan req : requests){
|
||||
@@ -616,14 +630,12 @@ public class NetServer implements ApplicationListener{
|
||||
con.rejectedRequests.add(req);
|
||||
continue;
|
||||
}
|
||||
player.builder().plans().addLast(req);
|
||||
player.unit().plans().addLast(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(player.isMiner()){
|
||||
player.miner().mineTile(mining);
|
||||
}
|
||||
player.unit().mineTile = mining;
|
||||
|
||||
con.rejectedRequests.clear();
|
||||
|
||||
@@ -631,12 +643,12 @@ public class NetServer implements ApplicationListener{
|
||||
Unit unit = player.unit();
|
||||
|
||||
long elapsed = Time.timeSinceMillis(con.lastReceivedClientTime);
|
||||
float maxSpeed = ((player.unit().type().canBoost && player.unit().isFlying()) ? player.unit().type().boostMultiplier : 1f) * player.unit().type().speed;
|
||||
float maxSpeed = unit.realSpeed();
|
||||
if(unit.isGrounded()){
|
||||
maxSpeed *= unit.floorSpeedMultiplier();
|
||||
}
|
||||
|
||||
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.1f;
|
||||
float maxMove = elapsed / 1000f * 60f * maxSpeed * 1.2f;
|
||||
|
||||
//ignore the position if the player thinks they're dead, or the unit is wrong
|
||||
boolean ignorePosition = dead || unit.id != unitID;
|
||||
@@ -693,15 +705,14 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
@Remote(targets = Loc.client, called = Loc.server)
|
||||
public static void adminRequest(Player player, Player other, AdminAction action){
|
||||
|
||||
if(!player.admin){
|
||||
Log.warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.",
|
||||
player.name, player.con.address, action.name(), other == null ? null : other.name);
|
||||
if(!player.admin && !player.isLocal()){
|
||||
warn("ACCESS DENIED: Player @ / @ attempted to perform admin action '@' on '@' without proper security access.",
|
||||
player.name, player.con == null ? "null" : player.con.address, action.name(), other == null ? null : other.name);
|
||||
return;
|
||||
}
|
||||
|
||||
if(other == null || ((other.admin && !player.isLocal()) && other != player)){
|
||||
Log.warn("@ attempted to perform admin action on nonexistant or admin player.", player.name);
|
||||
warn("@ attempted to perform admin action on nonexistant or admin player.", player.name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -713,10 +724,10 @@ public class NetServer implements ApplicationListener{
|
||||
netServer.admins.banPlayerIP(other.con.address);
|
||||
netServer.admins.banPlayerID(other.con.uuid);
|
||||
other.kick(KickReason.banned);
|
||||
Log.info("&lc@ has banned @.", player.name, other.name);
|
||||
info("&lc@ has banned @.", player.name, other.name);
|
||||
}else if(action == AdminAction.kick){
|
||||
other.kick(KickReason.kick);
|
||||
Log.info("&lc@ has kicked @.", player.name, other.name);
|
||||
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);
|
||||
if(player.con != null){
|
||||
@@ -724,7 +735,7 @@ public class NetServer implements ApplicationListener{
|
||||
}else{
|
||||
NetClient.traceInfo(other, info);
|
||||
}
|
||||
Log.info("&lc@ has requested trace info of @.", player.name, other.name);
|
||||
info("&lc@ has requested trace info of @.", player.name, other.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,7 +749,8 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
if(Config.showConnectMessages.bool()){
|
||||
Call.sendMessage("[accent]" + player.name + "[accent] has connected.");
|
||||
Log.info("&lm[@] &y@ has connected.", player.uuid(), player.name);
|
||||
String message = Strings.format("&lb@&fi&lk has connected. &fi&lk[&lb@&fi&lk]", player.name, player.uuid());
|
||||
info(message);
|
||||
}
|
||||
|
||||
if(!Config.motd.string().equalsIgnoreCase("off")){
|
||||
@@ -763,7 +775,6 @@ public class NetServer implements ApplicationListener{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
||||
if(!headless && !closing && net.server() && state.isMenu()){
|
||||
closing = true;
|
||||
ui.loadfrag.show("@server.closing");
|
||||
@@ -787,9 +798,9 @@ public class NetServer implements ApplicationListener{
|
||||
public void openServer(){
|
||||
try{
|
||||
net.host(Config.port.num());
|
||||
info("&lcOpened a server on port @.", Config.port.num());
|
||||
info("Opened a server on port @.", Config.port.num());
|
||||
}catch(BindException e){
|
||||
Log.err("Unable to host: Port already in use! Make sure no other servers are running on the same port in your network.");
|
||||
err("Unable to host: Port already in use! Make sure no other servers are running on the same port in your network.");
|
||||
state.set(State.menu);
|
||||
}catch(IOException e){
|
||||
err(e);
|
||||
@@ -905,7 +916,6 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
String checkColor(String str){
|
||||
|
||||
for(int i = 1; i < str.length(); i++){
|
||||
if(str.charAt(i) == ']'){
|
||||
String color = str.substring(1, i);
|
||||
@@ -931,7 +941,6 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
void sync(){
|
||||
|
||||
try{
|
||||
Groups.player.each(p -> !p.isLocal(), player -> {
|
||||
if(player.con == null || !player.con.isConnected()){
|
||||
@@ -955,7 +964,7 @@ public class NetServer implements ApplicationListener{
|
||||
}
|
||||
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,18 @@ import mindustry.type.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import rhino.*;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** Steam: Update lobby visibility.*/
|
||||
default void updateLobby(){}
|
||||
|
||||
@@ -109,9 +117,10 @@ public interface Platform{
|
||||
* @param cons Selection listener
|
||||
* @param open Whether to open or save files
|
||||
* @param extension File extension to filter
|
||||
* @param title The title of the native dialog
|
||||
*/
|
||||
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
|
||||
new FileChooser(open ? "@open" : "@save", file -> file.extEquals(extension), open, file -> {
|
||||
default void showFileChooser(boolean open, String title, String extension, Cons<Fi> cons){
|
||||
new FileChooser(title, file -> file.extEquals(extension), open, file -> {
|
||||
if(!open){
|
||||
cons.get(file.parent().child(file.nameWithoutExtension() + "." + extension));
|
||||
}else{
|
||||
@@ -120,8 +129,12 @@ public interface Platform{
|
||||
}).show();
|
||||
}
|
||||
|
||||
default void showFileChooser(boolean open, String extension, Cons<Fi> cons){
|
||||
showFileChooser(open, open ? "@open": "@save", extension, cons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a file chooser for multiple file types. Only supported on desktop.
|
||||
* Show a file chooser for multiple file types.
|
||||
* @param cons Selection listener
|
||||
* @param extensions File extensions to filter
|
||||
*/
|
||||
|
||||
@@ -27,10 +27,14 @@ public class Renderer implements ApplicationListener{
|
||||
public final Pixelator pixelator = new Pixelator();
|
||||
public PlanetRenderer planets;
|
||||
|
||||
public @Nullable Bloom bloom;
|
||||
public FrameBuffer effectBuffer = new FrameBuffer();
|
||||
public float laserOpacity = 1f;
|
||||
public boolean animateShields, drawWeather = true;
|
||||
/** minZoom = zooming out, maxZoom = zooming in */
|
||||
public float minZoom = 1.5f, maxZoom = 6f;
|
||||
|
||||
private Bloom bloom;
|
||||
//TODO unused
|
||||
private FxProcessor fx = new FxProcessor();
|
||||
private Color clearColor = new Color(0f, 0f, 0f, 1f);
|
||||
private float targetscale = Scl.scl(4);
|
||||
@@ -53,7 +57,7 @@ public class Renderer implements ApplicationListener{
|
||||
public void init(){
|
||||
planets = new PlanetRenderer();
|
||||
|
||||
if(settings.getBool("bloom")){
|
||||
if(settings.getBool("bloom", !ios)){
|
||||
setupBloom();
|
||||
}
|
||||
}
|
||||
@@ -63,8 +67,11 @@ public class Renderer implements ApplicationListener{
|
||||
Color.white.set(1f, 1f, 1f, 1f);
|
||||
Gl.clear(Gl.stencilBufferBit);
|
||||
|
||||
camerascale = Mathf.lerpDelta(camerascale, targetscale, 0.1f);
|
||||
laserOpacity = Core.settings.getInt("lasersopacity") / 100f;
|
||||
float dest = Mathf.round(targetscale, 0.5f);
|
||||
camerascale = Mathf.lerpDelta(camerascale, dest, 0.1f);
|
||||
if(Mathf.equal(camerascale, dest, 0.001f)) camerascale = dest;
|
||||
laserOpacity = settings.getInt("lasersopacity") / 100f;
|
||||
animateShields = settings.getBool("animatedshields");
|
||||
|
||||
if(landTime > 0){
|
||||
landTime -= Time.delta;
|
||||
@@ -108,7 +115,10 @@ public class Renderer implements ApplicationListener{
|
||||
minimap.dispose();
|
||||
effectBuffer.dispose();
|
||||
blocks.dispose();
|
||||
planets.dispose();
|
||||
if(planets != null){
|
||||
planets.dispose();
|
||||
planets = null;
|
||||
}
|
||||
if(bloom != null){
|
||||
bloom.dispose();
|
||||
bloom = null;
|
||||
@@ -118,10 +128,6 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height){
|
||||
if(settings.getBool("bloom")){
|
||||
setupBloom();
|
||||
}
|
||||
|
||||
fx.resize(width, height);
|
||||
}
|
||||
|
||||
@@ -140,9 +146,9 @@ public class Renderer implements ApplicationListener{
|
||||
}
|
||||
bloom = new Bloom(true);
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
settings.put("bloom", false);
|
||||
ui.showErrorMessage("@error.bloom");
|
||||
Log.err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +206,7 @@ public class Renderer implements ApplicationListener{
|
||||
graphics.clear(clearColor);
|
||||
Draw.reset();
|
||||
|
||||
if(Core.settings.getBool("animatedwater") || Core.settings.getBool("animatedshields")){
|
||||
if(Core.settings.getBool("animatedwater") || animateShields){
|
||||
effectBuffer.resize(graphics.getWidth(), graphics.getHeight());
|
||||
}
|
||||
|
||||
@@ -217,8 +223,6 @@ public class Renderer implements ApplicationListener{
|
||||
pixelator.register();
|
||||
}
|
||||
|
||||
//TODO fx
|
||||
|
||||
Draw.draw(Layer.background, this::drawBackground);
|
||||
Draw.draw(Layer.floor, blocks.floor::drawFloor);
|
||||
Draw.draw(Layer.block - 1, blocks::drawShadows);
|
||||
@@ -239,17 +243,23 @@ public class Renderer implements ApplicationListener{
|
||||
}
|
||||
|
||||
if(bloom != null){
|
||||
bloom.resize(graphics.getWidth() / 4, graphics.getHeight() / 4);
|
||||
Draw.draw(Layer.bullet - 0.01f, bloom::capture);
|
||||
Draw.draw(Layer.effect + 0.01f, bloom::render);
|
||||
}
|
||||
|
||||
Draw.draw(Layer.plans, overlays::drawBottom);
|
||||
|
||||
if(settings.getBool("animatedshields") && Shaders.shield != null){
|
||||
if(animateShields && Shaders.shield != null){
|
||||
Draw.drawRange(Layer.shields, 1f, () -> effectBuffer.begin(Color.clear), () -> {
|
||||
effectBuffer.end();
|
||||
effectBuffer.blit(Shaders.shield);
|
||||
});
|
||||
|
||||
Draw.drawRange(Layer.buildBeam, 1f, () -> effectBuffer.begin(Color.clear), () -> {
|
||||
effectBuffer.end();
|
||||
effectBuffer.blit(Shaders.buildBeam);
|
||||
});
|
||||
}
|
||||
|
||||
Draw.draw(Layer.overlayUI, overlays::drawTop);
|
||||
@@ -301,12 +311,19 @@ public class Renderer implements ApplicationListener{
|
||||
}
|
||||
|
||||
public void clampScale(){
|
||||
float s = Scl.scl(1f);
|
||||
targetscale = Mathf.clamp(targetscale, minScale(), Math.round(s * 6));
|
||||
targetscale = Mathf.clamp(targetscale, minScale(), maxScale());
|
||||
}
|
||||
|
||||
public float getDisplayScale(){
|
||||
return camerascale;
|
||||
}
|
||||
|
||||
public float minScale(){
|
||||
return Scl.scl(1.5f);
|
||||
return Scl.scl(minZoom);
|
||||
}
|
||||
|
||||
public float maxScale(){
|
||||
return Mathf.round(Scl.scl(maxZoom));
|
||||
}
|
||||
|
||||
public float getScale(){
|
||||
@@ -334,6 +351,7 @@ public class Renderer implements ApplicationListener{
|
||||
|
||||
FrameBuffer buffer = new FrameBuffer(w, h);
|
||||
|
||||
drawWeather = false;
|
||||
float vpW = camera.width, vpH = camera.height, px = camera.position.x, py = camera.position.y;
|
||||
disableUI = true;
|
||||
camera.width = w;
|
||||
@@ -359,8 +377,8 @@ public class Renderer implements ApplicationListener{
|
||||
PixmapIO.writePNG(file, fullPixmap);
|
||||
fullPixmap.dispose();
|
||||
ui.showInfoFade(Core.bundle.format("screenshot", file.toString()));
|
||||
drawWeather = true;
|
||||
|
||||
buffer.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import mindustry.editor.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.LogicDialog;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
import mindustry.ui.fragments.*;
|
||||
@@ -42,6 +42,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public MinimapFragment minimapfrag;
|
||||
public PlayerListFragment listfrag;
|
||||
public LoadingFragment loadfrag;
|
||||
public HintsFragment hints;
|
||||
|
||||
public WidgetGroup menuGroup, hudGroup;
|
||||
|
||||
@@ -91,6 +92,12 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Core.scene = new Scene();
|
||||
Core.input.addProcessor(Core.scene);
|
||||
|
||||
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];
|
||||
|
||||
Tex.load();
|
||||
Icon.load();
|
||||
Styles.load();
|
||||
@@ -104,7 +111,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
Tooltips.getInstance().textProvider = text -> new Tooltip(t -> t.background(Styles.black5).margin(4f).add(text));
|
||||
|
||||
Core.settings.setErrorHandler(e -> {
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
Core.app.post(() -> showErrorMessage("Failed to access local storage.\nSettings will not be saved."));
|
||||
});
|
||||
|
||||
@@ -140,12 +147,6 @@ public class UI implements ApplicationListener, Loadable{
|
||||
}
|
||||
}
|
||||
|
||||
//draw overlay for buttons
|
||||
if(state.rules.tutorial){
|
||||
control.tutorial.draw();
|
||||
Draw.flush();
|
||||
}
|
||||
|
||||
Events.fire(Trigger.uiDrawEnd);
|
||||
}
|
||||
|
||||
@@ -156,6 +157,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
menufrag = new MenuFragment();
|
||||
hudfrag = new HudFragment();
|
||||
hints = new HintsFragment();
|
||||
chatfrag = new ChatFragment();
|
||||
minimapfrag = new MinimapFragment();
|
||||
listfrag = new PlayerListFragment();
|
||||
@@ -262,11 +264,11 @@ public class UI implements ApplicationListener, Loadable{
|
||||
TextField field = cont.field(def, t -> {}).size(330f, 50f).get();
|
||||
field.setFilter((f, c) -> field.getText().length() < textLength && filter.acceptChar(f, c));
|
||||
buttons.defaults().size(120, 54).pad(4);
|
||||
buttons.button("@cancel", this::hide);
|
||||
buttons.button("@ok", () -> {
|
||||
confirmed.get(field.getText());
|
||||
hide();
|
||||
}).disabled(b -> field.getText().isEmpty());
|
||||
buttons.button("@cancel", this::hide);
|
||||
keyDown(KeyCode.enter, () -> {
|
||||
String text = field.getText();
|
||||
if(!text.isEmpty()){
|
||||
@@ -328,20 +330,21 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
/** Shows a label in the world. This label is behind everything. Does not fade. */
|
||||
public void showLabel(String info, float duration, float worldx, float worldy){
|
||||
Table table = new Table();
|
||||
table.setFillParent(true);
|
||||
var table = new Table(Styles.black3).margin(4);
|
||||
table.touchable = Touchable.disabled;
|
||||
table.update(() -> {
|
||||
if(state.isMenu()) table.remove();
|
||||
Vec2 v = Core.camera.project(worldx, worldy);
|
||||
table.setPosition(v.x, v.y, Align.center);
|
||||
});
|
||||
table.actions(Actions.delay(duration), Actions.remove());
|
||||
table.align(Align.center).table(Styles.black3, t -> t.margin(4).add(info).style(Styles.outlineLabel)).update(t -> {
|
||||
Vec2 v = Core.camera.project(worldx, worldy);
|
||||
t.setPosition(v.x, v.y, Align.center);
|
||||
});
|
||||
table.add(info).style(Styles.outlineLabel);
|
||||
table.pack();
|
||||
table.act(0f);
|
||||
//make sure it's at the back
|
||||
Core.scene.root.addChildAt(0, table);
|
||||
|
||||
table.getChildren().first().act(0f);
|
||||
}
|
||||
|
||||
public void showInfo(String info){
|
||||
@@ -356,6 +359,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
hide();
|
||||
listener.run();
|
||||
}).size(110, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -364,6 +368,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
getCell(cont).growX();
|
||||
cont.margin(15).add(info).width(400f).wrap().get().setAlignment(Align.left);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -378,6 +383,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.add(text).pad(2f).growX().wrap().get().setAlignment(Align.center);
|
||||
cont.row();
|
||||
cont.button("@ok", this::hide).size(120, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -388,7 +394,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
public void showException(String text, Throwable exc){
|
||||
loadfrag.hide();
|
||||
new Dialog(""){{
|
||||
String message = Strings.getFinalMesage(exc);
|
||||
String message = Strings.getFinalMessage(exc);
|
||||
|
||||
setFillParent(true);
|
||||
cont.margin(15);
|
||||
@@ -405,6 +411,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.button("@ok", this::hide).size(110, 50).fillX().left();
|
||||
cont.row();
|
||||
cont.add(col).colspan(2).pad(2);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -420,6 +427,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
cont.add(text).width(400f).wrap().get().setAlignment(align, align);
|
||||
cont.row();
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -427,6 +435,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
new Dialog(titleText){{
|
||||
cont.margin(15).add(text).width(400f).wrap().left().get().setAlignment(Align.left, Align.left);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -436,6 +445,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
titleTable.row();
|
||||
titleTable.image().color(Pal.accent).height(3f).growX().pad(2f);
|
||||
buttons.button("@ok", this::hide).size(110, 50).pad(4);
|
||||
closeOnBack();
|
||||
}}.show();
|
||||
}
|
||||
|
||||
@@ -487,13 +497,20 @@ public class UI implements ApplicationListener, Loadable{
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/** Display text in the middle of the screen, then fade out. */
|
||||
public void announce(String text){
|
||||
Table t = new Table();
|
||||
announce(text, 3);
|
||||
}
|
||||
|
||||
/** Display text in the middle of the screen, then fade out. */
|
||||
public void announce(String text, float duration){
|
||||
Table t = new Table(Styles.black3);
|
||||
t.touchable = Touchable.disabled;
|
||||
t.background(Styles.black3).margin(8f)
|
||||
.add(text).style(Styles.outlineLabel).labelAlign(Align.center);
|
||||
t.margin(8f).add(text).style(Styles.outlineLabel).labelAlign(Align.center);
|
||||
t.update(() -> t.setPosition(Core.graphics.getWidth()/2f, Core.graphics.getHeight()/2f, Align.center));
|
||||
t.actions(Actions.fadeOut(3, Interp.pow4In), Actions.remove());
|
||||
t.actions(Actions.fadeOut(duration, Interp.pow4In), Actions.remove());
|
||||
t.pack();
|
||||
t.act(0.1f);
|
||||
Core.scene.add(t);
|
||||
}
|
||||
|
||||
@@ -511,7 +528,7 @@ public class UI implements ApplicationListener, Loadable{
|
||||
|
||||
//TODO move?
|
||||
|
||||
public static String formatAmount(long number){
|
||||
public static String formatAmount(int number){
|
||||
if(number >= 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){
|
||||
|
||||
@@ -2,8 +2,8 @@ package mindustry.core;
|
||||
|
||||
import arc.*;
|
||||
import arc.Files.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
|
||||
@@ -46,6 +46,23 @@ public class Version{
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether the version is greater than the specified version string, e.g. "120.1"*/
|
||||
public static boolean isAtLeast(String str){
|
||||
if(build <= 0 || str == null || str.isEmpty()) return true;
|
||||
|
||||
int dot = str.indexOf('.');
|
||||
if(dot != -1){
|
||||
int major = Strings.parseInt(str.substring(0, dot), 0), minor = Strings.parseInt(str.substring(dot + 1), 0);
|
||||
return build > major || (build == major && revision >= minor);
|
||||
}else{
|
||||
return build >= Strings.parseInt(str, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildString(){
|
||||
return build < 0 ? "custom" : build + (revision == 0 ? "" : "." + revision);
|
||||
}
|
||||
|
||||
/** get menu version without colors */
|
||||
public static String combined(){
|
||||
if(build == -1){
|
||||
|
||||
@@ -6,7 +6,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.struct.ObjectIntMap.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.content.*;
|
||||
@@ -31,7 +30,7 @@ import static mindustry.Vars.*;
|
||||
public class World{
|
||||
public final Context context = new Context();
|
||||
|
||||
public @NonNull Tiles tiles = new Tiles(0, 0);
|
||||
public Tiles tiles = new Tiles(0, 0);
|
||||
|
||||
private boolean generating, invalidMap;
|
||||
private ObjectMap<Map, Runnable> customMapLoaders = new ObjectMap<>();
|
||||
@@ -66,6 +65,11 @@ public class World{
|
||||
return tile == null || tile.block().solid;
|
||||
}
|
||||
|
||||
public boolean wallSolidFull(int x, int y){
|
||||
Tile tile = tile(x, y);
|
||||
return tile == null || (tile.block().solid && tile.block().fillsTile);
|
||||
}
|
||||
|
||||
public boolean isAccessible(int x, int y){
|
||||
return !wallSolid(x, y - 1) || !wallSolid(x, y + 1) || !wallSolid(x - 1, y) || !wallSolid(x + 1, y);
|
||||
}
|
||||
@@ -86,13 +90,11 @@ public class World{
|
||||
return height()*tilesize;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Floor floor(int x, int y){
|
||||
Tile tile = tile(x, y);
|
||||
return tile == null ? Blocks.air.asFloor() : tile.floor();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Floor floorWorld(float x, float y){
|
||||
Tile tile = tileWorld(x, y);
|
||||
return tile == null ? Blocks.air.asFloor() : tile.floor();
|
||||
@@ -132,7 +134,6 @@ public class World{
|
||||
return tile.build;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Tile rawTile(int x, int y){
|
||||
return tiles.getn(x, y);
|
||||
}
|
||||
@@ -147,7 +148,17 @@ public class World{
|
||||
return build(Math.round(x / tilesize), Math.round(y / tilesize));
|
||||
}
|
||||
|
||||
public int toTile(float coord){
|
||||
/** Convert from world to logic tile coordinates. Whole numbers are at centers of tiles. */
|
||||
public static float conv(float coord){
|
||||
return coord / tilesize;
|
||||
}
|
||||
|
||||
/** Convert from tile to world coordinates. */
|
||||
public static float unconv(float coord){
|
||||
return coord * tilesize;
|
||||
}
|
||||
|
||||
public static int toTile(float coord){
|
||||
return Math.round(coord / tilesize);
|
||||
}
|
||||
|
||||
@@ -175,22 +186,22 @@ public class World{
|
||||
|
||||
/**
|
||||
* Call to signify the beginning of map loading.
|
||||
* BuildinghangeEvents will not be fired until endMapLoad().
|
||||
* TileEvents will not be fired until endMapLoad().
|
||||
*/
|
||||
public void beginMapLoad(){
|
||||
generating = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world.
|
||||
* Call to signify the end of map loading. Updates tile proximities and sets up physics for the world.
|
||||
* A WorldLoadEvent will be fire.
|
||||
*/
|
||||
public void endMapLoad(){
|
||||
|
||||
for(Tile tile : tiles){
|
||||
//remove legacy blocks; they need to stop existing
|
||||
if(tile.block() instanceof LegacyBlock){
|
||||
tile.remove();
|
||||
if(tile.block() instanceof LegacyBlock l){
|
||||
l.removeSelf(tile);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -252,12 +263,12 @@ public class World{
|
||||
setSectorRules(sector);
|
||||
|
||||
if(state.rules.defaultTeam.core() != null){
|
||||
sector.setSpawnPosition(state.rules.defaultTeam.core().pos());
|
||||
sector.info.spawnPosition = state.rules.defaultTeam.core().pos();
|
||||
}
|
||||
}
|
||||
|
||||
private void setSectorRules(Sector sector){
|
||||
state.map = new Map(StringMap.of("name", sector.planet.localizedName + "; Sector " + sector.id));
|
||||
state.map = new Map(StringMap.of("name", sector.preset == null ? sector.planet.localizedName + "; Sector " + sector.id : sector.preset.localizedName));
|
||||
state.rules.sector = sector;
|
||||
|
||||
state.rules.weather.clear();
|
||||
@@ -266,8 +277,6 @@ public class World{
|
||||
ObjectIntMap<Block> floorc = new ObjectIntMap<>();
|
||||
ObjectSet<UnlockableContent> content = new ObjectSet<>();
|
||||
|
||||
float waterFloors = 0, totalFloors = 0;
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
if(world.getDarkness(tile.x, tile.y) >= 3){
|
||||
continue;
|
||||
@@ -279,10 +288,6 @@ public class World{
|
||||
if(liquid != null) content.add(liquid);
|
||||
|
||||
if(!tile.block().isStatic()){
|
||||
totalFloors ++;
|
||||
if(liquid == Liquids.water){
|
||||
waterFloors += tile.floor().isDeep() ? 1f : 0.7f;
|
||||
}
|
||||
floorc.increment(tile.floor());
|
||||
if(tile.overlay() != Blocks.air){
|
||||
floorc.increment(tile.overlay());
|
||||
@@ -297,16 +302,14 @@ public class World{
|
||||
entries.removeAll(e -> e.value < 30);
|
||||
|
||||
Block[] floors = new Block[entries.size];
|
||||
int[] floorCounts = new int[entries.size];
|
||||
for(int i = 0; i < entries.size; i++){
|
||||
floorCounts[i] = entries.get(i).value;
|
||||
floors[i] = entries.get(i).key;
|
||||
}
|
||||
|
||||
//TODO bad code
|
||||
boolean hasSnow = floors[0].name.contains("ice") || floors[0].name.contains("snow");
|
||||
boolean hasRain = !hasSnow && floors[0].name.contains("water");
|
||||
boolean hasDesert = !hasSnow && !hasRain && floors[0].name.contains("sand");
|
||||
boolean hasRain = !hasSnow && content.contains(Liquids.water) && !floors[0].name.contains("sand");
|
||||
boolean hasDesert = !hasSnow && !hasRain && floors[0] == Blocks.sand;
|
||||
boolean hasSpores = floors[0].name.contains("spore") || floors[0].name.contains("moss") || floors[0].name.contains("tainted");
|
||||
|
||||
if(hasSnow){
|
||||
@@ -315,6 +318,7 @@ public class World{
|
||||
|
||||
if(hasRain){
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.rain));
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.fog));
|
||||
}
|
||||
|
||||
if(hasDesert){
|
||||
@@ -325,9 +329,9 @@ public class World{
|
||||
state.rules.weather.add(new WeatherEntry(Weathers.sporestorm));
|
||||
}
|
||||
|
||||
state.secinfo.resources = content.asArray();
|
||||
state.secinfo.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
|
||||
sector.info.resources = content.asArray();
|
||||
sector.info.resources.sort(Structs.comps(Structs.comparing(Content::getContentType), Structs.comparingInt(c -> c.id)));
|
||||
sector.saveInfo();
|
||||
}
|
||||
|
||||
public Context filterContext(Map map){
|
||||
@@ -439,7 +443,6 @@ public class World{
|
||||
int err = dx - dy;
|
||||
int e2;
|
||||
while(true){
|
||||
|
||||
if(cons.accept(x0, y0)) return true;
|
||||
if(x0 == x1 && y0 == y1) return false;
|
||||
|
||||
@@ -539,7 +542,7 @@ public class World{
|
||||
|
||||
int circleDst = (int)(rawDst - (length - circleBlend));
|
||||
if(circleDst > 0){
|
||||
dark = Math.max(circleDst / 1f, dark);
|
||||
dark = Math.max(circleDst, dark);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,6 +613,7 @@ public class World{
|
||||
GenerateInput input = new GenerateInput();
|
||||
|
||||
for(GenerateFilter filter : filters){
|
||||
filter.randomize();
|
||||
input.begin(filter, width(), height(), (x, y) -> tiles.getn(x, y));
|
||||
filter.apply(tiles, input);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package mindustry.ctype;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.mod.Mods.*;
|
||||
|
||||
@@ -10,8 +9,7 @@ import mindustry.mod.Mods.*;
|
||||
public abstract class Content implements Comparable<Content>, Disposable{
|
||||
public final short id;
|
||||
/** Info on which mod this content was loaded from. */
|
||||
public @NonNull ModContentInfo minfo = new ModContentInfo();
|
||||
|
||||
public ModContentInfo minfo = new ModContentInfo();
|
||||
|
||||
public Content(){
|
||||
this.id = (short)Vars.content.getBy(getContentType()).size;
|
||||
@@ -33,7 +31,7 @@ public abstract class Content implements Comparable<Content>, Disposable{
|
||||
*/
|
||||
public void load(){}
|
||||
|
||||
/** @return whether an error ocurred during mod loading. */
|
||||
/** @return whether an error occurred during mod loading. */
|
||||
public boolean hasErrored(){
|
||||
return minfo.error != null;
|
||||
}
|
||||
|
||||
@@ -4,25 +4,34 @@ import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.content.TechTree.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.meta.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Base interface for an unlockable content type. */
|
||||
public abstract class UnlockableContent extends MappableContent{
|
||||
/** Stat storage for this content. Initialized on demand. */
|
||||
public Stats stats = new Stats();
|
||||
/** Localized, formal name. Never null. Set to internal name if not found in bundle. */
|
||||
public String localizedName;
|
||||
/** Localized description. May be null. */
|
||||
public @Nullable String description;
|
||||
/** Localized description & details. May be null. */
|
||||
public @Nullable String description, details;
|
||||
/** Whether this content is always unlocked in the tech tree. */
|
||||
public boolean alwaysUnlocked = false;
|
||||
/** Whether to show the description in the research dialog preview. */
|
||||
public boolean inlineDescription = true;
|
||||
/** Special logic icon ID. */
|
||||
public int iconId = 0;
|
||||
/** Icons by Cicon ID.*/
|
||||
protected TextureRegion[] cicons = new TextureRegion[mindustry.ui.Cicon.all.length];
|
||||
protected TextureRegion[] cicons = new TextureRegion[Cicon.all.length];
|
||||
/** Unlock state. Loaded from settings. Do not modify outside of the constructor. */
|
||||
protected boolean unlocked;
|
||||
|
||||
@@ -31,13 +40,31 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
|
||||
this.localizedName = Core.bundle.get(getContentType() + "." + this.name + ".name", this.name);
|
||||
this.description = Core.bundle.getOrNull(getContentType() + "." + this.name + ".description");
|
||||
this.details = Core.bundle.getOrNull(getContentType() + "." + this.name + ".details");
|
||||
this.unlocked = Core.settings != null && Core.settings.getBool(this.name + "-unlocked", false);
|
||||
}
|
||||
|
||||
/** @return the tech node for this content. may be null. */
|
||||
public @Nullable TechNode node(){
|
||||
return TechTree.get(this);
|
||||
}
|
||||
|
||||
public String displayDescription(){
|
||||
return minfo.mod == null ? description : description + "\n" + Core.bundle.format("mod.display", minfo.mod.meta.displayName());
|
||||
}
|
||||
|
||||
/** Checks stat initialization state. Call before displaying stats. */
|
||||
public void checkStats(){
|
||||
if(!stats.intialized){
|
||||
setStats();
|
||||
stats.intialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes stats on demand. Should only be called once. Only called before something is displayed. */
|
||||
public void setStats(){
|
||||
}
|
||||
|
||||
/** Generate any special icons for this content. Called asynchronously.*/
|
||||
@CallSuper
|
||||
public void createIcons(MultiPacker packer){
|
||||
@@ -59,9 +86,11 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
cicons[icon.ordinal()] =
|
||||
Core.atlas.find(getContentType().name() + "-" + name + "-" + icon.name(),
|
||||
Core.atlas.find(getContentType().name() + "-" + name + "-full",
|
||||
Core.atlas.find(name + "-" + icon.name(),
|
||||
Core.atlas.find(name + "-full",
|
||||
Core.atlas.find(name,
|
||||
Core.atlas.find(getContentType().name() + "-" + name,
|
||||
Core.atlas.find(name + "1")))));
|
||||
Core.atlas.find(name + "1")))))));
|
||||
}
|
||||
return cicons[icon.ordinal()];
|
||||
}
|
||||
@@ -73,7 +102,9 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
}
|
||||
|
||||
/** This should show all necessary info about this content in the specified table. */
|
||||
public abstract void displayInfo(Table table);
|
||||
public void display(Table table){
|
||||
|
||||
}
|
||||
|
||||
/** Called when this content is unlocked. Use this to unlock other related content. */
|
||||
public void onUnlock(){
|
||||
@@ -86,7 +117,7 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
|
||||
/** Makes this piece of content unlocked; if it already unlocked, nothing happens. */
|
||||
public void unlock(){
|
||||
if(!unlocked()){
|
||||
if(!unlocked && !alwaysUnlocked){
|
||||
unlocked = true;
|
||||
Core.settings.put(name + "-unlocked", true);
|
||||
|
||||
@@ -95,16 +126,33 @@ public abstract class UnlockableContent extends MappableContent{
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean unlocked(){
|
||||
/** Unlocks this content, but does not fire any events. */
|
||||
public void quietUnlock(){
|
||||
if(!unlocked()){
|
||||
unlocked = true;
|
||||
Core.settings.put(name + "-unlocked", true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unlocked(){
|
||||
if(net != null && net.client()) return unlocked || alwaysUnlocked || state.rules.researched.contains(name);
|
||||
return unlocked || alwaysUnlocked;
|
||||
}
|
||||
|
||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||
public final boolean unlockedNow(){
|
||||
return unlocked || alwaysUnlocked || !state.isCampaign();
|
||||
/** Locks this content again. */
|
||||
public void clearUnlock(){
|
||||
if(unlocked){
|
||||
unlocked = false;
|
||||
Core.settings.put(name + "-unlocked", false);
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean locked(){
|
||||
/** @return whether this content is unlocked, or the player is in a custom (non-campaign) game. */
|
||||
public boolean unlockedNow(){
|
||||
return unlocked() || !state.isCampaign();
|
||||
}
|
||||
|
||||
public boolean locked(){
|
||||
return !unlocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import arc.struct.LongSeq;
|
||||
import mindustry.game.Team;
|
||||
import mindustry.gen.TileOp;
|
||||
import mindustry.world.Block;
|
||||
import mindustry.world.Tile;
|
||||
import mindustry.world.blocks.environment.Floor;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import static mindustry.Vars.content;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class DrawOperation{
|
||||
private MapEditor editor;
|
||||
private LongSeq array = new LongSeq();
|
||||
|
||||
public DrawOperation(MapEditor editor) {
|
||||
public DrawOperation(MapEditor editor){
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
@@ -38,7 +37,7 @@ public class DrawOperation{
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTile(int i) {
|
||||
private void updateTile(int i){
|
||||
long l = array.get(i);
|
||||
array.set(i, TileOp.get(TileOp.x(l), TileOp.y(l), TileOp.type(l), getTile(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l))));
|
||||
setTile(editor.tile(TileOp.x(l), TileOp.y(l)), TileOp.type(l), TileOp.value(l));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.func.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.editor.DrawOperation.*;
|
||||
import mindustry.game.*;
|
||||
@@ -19,7 +18,7 @@ public class EditorTile extends Tile{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloor(@NonNull Floor type){
|
||||
public void setFloor(Floor type){
|
||||
if(skip()){
|
||||
super.setFloor(type);
|
||||
return;
|
||||
@@ -27,7 +26,7 @@ public class EditorTile extends Tile{
|
||||
|
||||
if(type instanceof OverlayFloor){
|
||||
//don't place on liquids
|
||||
if(!floor.isLiquid){
|
||||
if(floor.hasSurface() || !type.needsSurface){
|
||||
setOverlayID(type.id);
|
||||
}
|
||||
return;
|
||||
@@ -51,9 +50,19 @@ public class EditorTile extends Tile{
|
||||
return;
|
||||
}
|
||||
|
||||
op(OpType.block, block.id);
|
||||
if(rotation != 0) op(OpType.rotation, (byte)rotation);
|
||||
if(team != Team.derelict) op(OpType.team, (byte)team.id);
|
||||
if(!isCenter()){
|
||||
EditorTile cen = (EditorTile)build.tile;
|
||||
cen.op(OpType.rotation, (byte)build.rotation);
|
||||
cen.op(OpType.team, (byte)build.team.id);
|
||||
cen.op(OpType.block, block.id);
|
||||
update();
|
||||
}else{
|
||||
if(build != null) op(OpType.rotation, (byte)build.rotation);
|
||||
if(build != null) op(OpType.team, (byte)build.team.id);
|
||||
op(OpType.block, block.id);
|
||||
|
||||
}
|
||||
|
||||
super.setBlock(type, team, rotation);
|
||||
}
|
||||
|
||||
@@ -67,6 +76,8 @@ public class EditorTile extends Tile{
|
||||
if(getTeamID() == team.id) return;
|
||||
op(OpType.team, (byte)getTeamID());
|
||||
super.setTeam(team);
|
||||
|
||||
getLinkedTiles(t -> ui.editor.editor.renderer.updatePoint(t.x, t.y));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,7 +87,7 @@ public class EditorTile extends Tile{
|
||||
return;
|
||||
}
|
||||
|
||||
if(floor.isLiquid) return;
|
||||
if(!floor.hasSurface() && overlay.asFloor().needsSurface) return;
|
||||
if(overlay() == overlay) return;
|
||||
op(OpType.overlay, this.overlay.id);
|
||||
super.setOverlay(overlay);
|
||||
@@ -97,11 +108,18 @@ public class EditorTile extends Tile{
|
||||
super.recache();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
||||
protected void changed(){
|
||||
if(state.isGame()){
|
||||
super.changed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void changeBuild(Team team, Prov<Building> entityprov, int rotation){
|
||||
if(skip()){
|
||||
super.changeEntity(team, entityprov, rotation);
|
||||
super.changeBuild(team, entityprov, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +130,7 @@ public class EditorTile extends Tile{
|
||||
|
||||
Block block = block();
|
||||
|
||||
if(block.hasEntity()){
|
||||
if(block.hasBuilding()){
|
||||
build = entityprov.get().init(this, team, false, rotation);
|
||||
build.cons = new ConsumeModule(build);
|
||||
if(block.hasItems) build.items = new ItemModule();
|
||||
|
||||
@@ -118,7 +118,7 @@ public enum EditorTool{
|
||||
if(editor.drawBlock.isOverlay()){
|
||||
Block dest = tile.overlay();
|
||||
if(dest == editor.drawBlock) return;
|
||||
tester = t -> t.overlay() == dest && !t.floor().isLiquid;
|
||||
tester = t -> t.overlay() == dest && (t.floor().hasSurface() || !t.floor().needsSurface);
|
||||
setter = t -> t.setOverlay(editor.drawBlock);
|
||||
}else if(editor.drawBlock.isFloor()){
|
||||
Block dest = tile.floor();
|
||||
|
||||
@@ -4,6 +4,7 @@ import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.editor.DrawOperation.*;
|
||||
@@ -136,7 +137,7 @@ public class MapEditor{
|
||||
|
||||
if(isFloor){
|
||||
tile.setFloor(drawBlock.asFloor());
|
||||
}else{
|
||||
}else if(!(tile.block().isMultiblock() && !drawBlock.isMultiblock())){
|
||||
if(drawBlock.rotate && tile.build != null && tile.build.rotation != rotation){
|
||||
addTileOp(TileOp.get(tile.x, tile.y, (byte)OpType.rotation.ordinal(), (byte)rotation));
|
||||
}
|
||||
@@ -156,7 +157,7 @@ public class MapEditor{
|
||||
boolean hasOverlap(int x, int y){
|
||||
Tile tile = world.tile(x, y);
|
||||
//allow direct replacement of blocks of the same size
|
||||
if(tile != null && tile.isCenter() && tile.block() != drawBlock && tile.block().size == drawBlock.size){
|
||||
if(tile != null && tile.isCenter() && tile.block() != drawBlock && tile.block().size == drawBlock.size && tile.x == x && tile.y == y){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -167,12 +168,10 @@ public class MapEditor{
|
||||
for(int dy = 0; dy < drawBlock.size; dy++){
|
||||
int worldx = dx + offsetx + x;
|
||||
int worldy = dy + offsety + y;
|
||||
if(!(worldx == x && worldy == y)){
|
||||
Tile other = world.tile(worldx, worldy);
|
||||
Tile other = world.tile(worldx, worldy);
|
||||
|
||||
if(other != null && other.block().isMultiblock()){
|
||||
return true;
|
||||
}
|
||||
if(other != null && other.block().isMultiblock()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,6 +179,52 @@ public class MapEditor{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addCliffs(){
|
||||
for(Tile tile : world.tiles){
|
||||
if(!tile.block().isStatic() || tile.block() == Blocks.cliff) continue;
|
||||
|
||||
int rotation = 0;
|
||||
for(int i = 0; i < 8; i++){
|
||||
Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
|
||||
if(other != null && !other.block().isStatic()){
|
||||
rotation |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
if(rotation != 0){
|
||||
tile.setBlock(Blocks.cliff);
|
||||
}
|
||||
|
||||
tile.data = (byte)rotation;
|
||||
}
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
if(tile.block() != Blocks.cliff && tile.block().isStatic()){
|
||||
tile.setBlock(Blocks.air);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addFloorCliffs(){
|
||||
for(Tile tile : world.tiles){
|
||||
if(!tile.floor().hasSurface() || tile.block() == Blocks.cliff) continue;
|
||||
|
||||
int rotation = 0;
|
||||
for(int i = 0; i < 8; i++){
|
||||
Tile other = world.tiles.get(tile.x + Geometry.d8[i].x, tile.y + Geometry.d8[i].y);
|
||||
if(other != null && !other.floor().hasSurface()){
|
||||
rotation |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
if(rotation != 0){
|
||||
tile.setBlock(Blocks.cliff);
|
||||
}
|
||||
|
||||
tile.data = (byte)rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawCircle(int x, int y, Cons<Tile> drawer){
|
||||
for(int rx = -brushSize; rx <= brushSize; rx++){
|
||||
for(int ry = -brushSize; ry <= brushSize; ry++){
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
@@ -14,8 +13,8 @@ import arc.scene.event.*;
|
||||
import arc.scene.style.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.GameState.*;
|
||||
@@ -43,6 +42,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
private MapGenerateDialog generateDialog;
|
||||
private ScrollPane pane;
|
||||
private BaseDialog menu;
|
||||
private Table blockSelection;
|
||||
private Rules lastSavedRules;
|
||||
private boolean saved = false;
|
||||
private boolean shownWithMap = false;
|
||||
@@ -257,7 +257,14 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
player.set(world.width() * tilesize/2f, world.height() * tilesize/2f);
|
||||
player.clearUnit();
|
||||
Groups.unit.clear();
|
||||
Groups.build.clear();
|
||||
Groups.weather.clear();
|
||||
logic.play();
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,7 +392,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
}
|
||||
|
||||
public void build(){
|
||||
float size = 60f;
|
||||
float size = 58f;
|
||||
|
||||
clearChildren();
|
||||
table(cont -> {
|
||||
@@ -557,6 +564,21 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
t.add(slider).width(size * 3f - 20).padTop(4f);
|
||||
}).padTop(5).growX().top();
|
||||
|
||||
mid.row();
|
||||
|
||||
if(!mobile){
|
||||
mid.table(t -> {
|
||||
t.button("@editor.center", Icon.move, Styles.cleart, view::center).growX().margin(9f);
|
||||
}).growX().top();
|
||||
}
|
||||
|
||||
if(experimental){
|
||||
mid.row();
|
||||
|
||||
mid.table(t -> {
|
||||
t.button("Cliffs", Icon.terrain, Styles.cleart, editor::addCliffs).growX().margin(9f);
|
||||
}).growX().top();
|
||||
}
|
||||
}).margin(0).left().growY();
|
||||
|
||||
|
||||
@@ -645,9 +667,9 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
ui.showConfirm("@confirm", "@editor.unsaved", this::hide);
|
||||
}
|
||||
|
||||
private void addBlockSelection(Table table){
|
||||
Table content = new Table();
|
||||
pane = new ScrollPane(content);
|
||||
private void addBlockSelection(Table cont){
|
||||
blockSelection = new Table();
|
||||
pane = new ScrollPane(blockSelection);
|
||||
pane.setFadeScrollBars(false);
|
||||
pane.setOverscroll(true, false);
|
||||
pane.exited(() -> {
|
||||
@@ -655,9 +677,22 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
Core.scene.setScrollFocus(view);
|
||||
}
|
||||
});
|
||||
ButtonGroup<ImageButton> group = new ButtonGroup<>();
|
||||
|
||||
int i = 0;
|
||||
cont.table(search -> {
|
||||
search.image(Icon.zoom).padRight(8);
|
||||
search.field("", this::rebuildBlockSelection)
|
||||
.name("editor/search").maxTextLength(maxNameLength).get().setMessageText("@players.search");
|
||||
}).pad(-2);
|
||||
cont.row();
|
||||
cont.table(Tex.underline, extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
|
||||
cont.row();
|
||||
cont.add(pane).expandY().top().left();
|
||||
|
||||
rebuildBlockSelection("");
|
||||
}
|
||||
|
||||
private void rebuildBlockSelection(String searchText){
|
||||
blockSelection.clear();
|
||||
|
||||
blocksOut.clear();
|
||||
blocksOut.addAll(Vars.content.blocks());
|
||||
@@ -671,28 +706,32 @@ public class MapEditorDialog extends Dialog implements Disposable{
|
||||
return Integer.compare(b1.id, b2.id);
|
||||
});
|
||||
|
||||
int i = 0;
|
||||
|
||||
for(Block block : blocksOut){
|
||||
TextureRegion region = block.icon(Cicon.medium);
|
||||
|
||||
if(!Core.atlas.isFound(region) || !block.inEditor || (block.buildVisibility == BuildVisibility.debugOnly)) continue;
|
||||
if(!Core.atlas.isFound(region) || !block.inEditor
|
||||
|| block.buildVisibility == BuildVisibility.debugOnly
|
||||
|| (!searchText.isEmpty() && !block.localizedName.toLowerCase().contains(searchText.toLowerCase()))
|
||||
) continue;
|
||||
|
||||
ImageButton button = new ImageButton(Tex.whiteui, Styles.clearTogglei);
|
||||
button.getStyle().imageUp = new TextureRegionDrawable(region);
|
||||
button.clicked(() -> editor.drawBlock = block);
|
||||
button.resizeImage(8 * 4f);
|
||||
button.update(() -> button.setChecked(editor.drawBlock == block));
|
||||
group.add(button);
|
||||
content.add(button).size(50f);
|
||||
blockSelection.add(button).size(50f).tooltip(block.localizedName);
|
||||
|
||||
if(i == 0) editor.drawBlock = block;
|
||||
|
||||
if(++i % 4 == 0){
|
||||
content.row();
|
||||
blockSelection.row();
|
||||
}
|
||||
}
|
||||
|
||||
group.getButtons().get(2).setChecked(true);
|
||||
|
||||
table.table(Tex.underline, extra -> extra.labelWrap(() -> editor.drawBlock.localizedName).width(200f).center()).growX();
|
||||
table.row();
|
||||
table.add(pane).growY().fillX();
|
||||
if(i == 0){
|
||||
blockSelection.add("@none").color(Color.lightGray).padLeft(80f).padTop(10f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
private final Prov<GenerateFilter>[] filterTypes = new Prov[]{
|
||||
NoiseFilter::new, ScatterFilter::new, TerrainFilter::new, DistortFilter::new,
|
||||
RiverNoiseFilter::new, OreFilter::new, OreMedianFilter::new, MedianFilter::new,
|
||||
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new, EnemySpawnFilter::new
|
||||
BlendFilter::new, MirrorFilter::new, ClearFilter::new, CoreSpawnFilter::new,
|
||||
EnemySpawnFilter::new, SpawnPathFilter::new
|
||||
};
|
||||
private final MapEditor editor;
|
||||
private final boolean applied;
|
||||
@@ -44,16 +45,20 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
private AsyncExecutor executor = new AsyncExecutor(1);
|
||||
private AsyncResult<Void> result;
|
||||
boolean generating;
|
||||
private GenTile returnTile = new GenTile();
|
||||
|
||||
private GenTile[][] buffer1, buffer2;
|
||||
private long[] buffer1, buffer2;
|
||||
private Cons<Seq<GenerateFilter>> applier;
|
||||
CachedTile ctile = new CachedTile(){
|
||||
//nothing.
|
||||
@Override
|
||||
protected void changeEntity(Team team, Prov<Building> entityprov, int rotation){
|
||||
protected void changeBuild(Team team, Prov<Building> entityprov, int rotation){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(Block type, Team team, int rotation, Prov<Building> entityprov){
|
||||
this.block = type;
|
||||
}
|
||||
};
|
||||
|
||||
/** @param applied whether or not to use the applied in-game mode. */
|
||||
@@ -65,7 +70,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
shown(this::setup);
|
||||
addCloseButton();
|
||||
if(applied){
|
||||
buttons.button("@editor.apply", () -> {
|
||||
buttons.button("@editor.apply", Icon.ok, () -> {
|
||||
ui.loadAnd(() -> {
|
||||
apply();
|
||||
hide();
|
||||
@@ -78,14 +83,14 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
}
|
||||
buttons.button("@editor.randomize", () -> {
|
||||
buttons.button("@editor.randomize", Icon.refresh, () -> {
|
||||
for(GenerateFilter filter : filters){
|
||||
filter.randomize();
|
||||
}
|
||||
update();
|
||||
}).size(160f, 64f);
|
||||
|
||||
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(140f);
|
||||
buttons.button("@add", Icon.add, this::showAdd).height(64f).width(150f);
|
||||
|
||||
if(!applied){
|
||||
hidden(this::apply);
|
||||
@@ -107,38 +112,36 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
/** Applies the specified filters to the editor. */
|
||||
public void applyToEditor(Seq<GenerateFilter> filters){
|
||||
//writeback buffer
|
||||
GenTile[][] writeTiles = new GenTile[editor.width()][editor.height()];
|
||||
|
||||
for(int x = 0; x < editor.width(); x++){
|
||||
for(int y = 0; y < editor.height(); y++){
|
||||
writeTiles[x][y] = new GenTile();
|
||||
}
|
||||
}
|
||||
long[] writeTiles = new long[editor.width() * editor.height()];
|
||||
|
||||
for(GenerateFilter filter : filters){
|
||||
input.begin(filter, editor.width(), editor.height(), editor::tile);
|
||||
|
||||
//write to buffer
|
||||
for(int x = 0; x < editor.width(); x++){
|
||||
for(int y = 0; y < editor.height(); y++){
|
||||
Tile tile = editor.tile(x, y);
|
||||
input.apply(x, y, tile.floor(), tile.block(), tile.overlay());
|
||||
input.apply(x, y, tile.block(), tile.floor(), tile.overlay());
|
||||
filter.apply(input);
|
||||
writeTiles[x][y].set(input.floor, input.block, input.ore, tile.team());
|
||||
writeTiles[x + y*world.width()] = PackTile.get(input.block.id, input.floor.id, input.overlay.id);
|
||||
}
|
||||
}
|
||||
|
||||
editor.load(() -> {
|
||||
//read from buffer back into tiles
|
||||
for(int x = 0; x < editor.width(); x++){
|
||||
for(int y = 0; y < editor.height(); y++){
|
||||
Tile tile = editor.tile(x, y);
|
||||
GenTile write = writeTiles[x][y];
|
||||
for(int i = 0; i < editor.width() * editor.height(); i++){
|
||||
Tile tile = world.tiles.geti(i);
|
||||
long write = writeTiles[i];
|
||||
|
||||
tile.setFloor((Floor)content.block(write.floor));
|
||||
tile.setBlock(content.block(write.block));
|
||||
tile.setTeam(Team.get(write.team));
|
||||
tile.setOverlay(content.block(write.ore));
|
||||
Block block = content.block(PackTile.block(write)), floor = content.block(PackTile.floor(write)), overlay = content.block(PackTile.overlay(write));
|
||||
|
||||
//don't mess up synthetic stuff.
|
||||
if(!tile.synthetic() && !block.synthetic()){
|
||||
tile.setBlock(block);
|
||||
}
|
||||
|
||||
tile.setFloor((Floor)floor);
|
||||
tile.setOverlay(overlay);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,15 +204,8 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
rebuildFilters();
|
||||
}
|
||||
|
||||
GenTile[][] create(){
|
||||
GenTile[][] out = new GenTile[editor.width() / scaling][editor.height() / scaling];
|
||||
|
||||
for(int x = 0; x < out.length; x++){
|
||||
for(int y = 0; y < out[0].length; y++){
|
||||
out[x][y] = new GenTile();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
long[] create(){
|
||||
return new long[(editor.width() / scaling) * (editor.height() / scaling)];
|
||||
}
|
||||
|
||||
void rebuildFilters(){
|
||||
@@ -295,7 +291,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
for(Prov<GenerateFilter> gen : filterTypes){
|
||||
GenerateFilter filter = gen.get();
|
||||
|
||||
if((!applied && filter.isBuffered()) || (filter.isPost() && applied)) continue;
|
||||
if((filter.isPost() && applied)) continue;
|
||||
|
||||
selection.cont.button(filter.name(), () -> {
|
||||
filters.add(filter);
|
||||
@@ -317,9 +313,15 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
selection.show();
|
||||
}
|
||||
|
||||
GenTile dset(Tile tile){
|
||||
returnTile.set(tile);
|
||||
return returnTile;
|
||||
long pack(Tile tile){
|
||||
return PackTile.get(tile.blockID(), tile.floorID(), tile.overlayID());
|
||||
}
|
||||
|
||||
Tile unpack(long tile){
|
||||
ctile.setFloor((Floor)content.block(PackTile.floor(tile)));
|
||||
ctile.setBlock(content.block(PackTile.block(tile)));
|
||||
ctile.setOverlay(content.block(PackTile.overlay(tile)));
|
||||
return ctile;
|
||||
}
|
||||
|
||||
void apply(){
|
||||
@@ -350,6 +352,7 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
|
||||
result = executor.submit(() -> {
|
||||
try{
|
||||
int w = pixmap.getWidth();
|
||||
world.setGenerating(true);
|
||||
generating = true;
|
||||
|
||||
@@ -357,24 +360,24 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
//write to buffer1 for reading
|
||||
for(int px = 0; px < pixmap.getWidth(); px++){
|
||||
for(int py = 0; py < pixmap.getHeight(); py++){
|
||||
buffer1[px][py].set(editor.tile(px * scaling, py * scaling));
|
||||
buffer1[px + py*w] = pack(editor.tile(px * scaling, py * scaling));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(GenerateFilter filter : copy){
|
||||
input.begin(filter, editor.width(), editor.height(), (x, y) -> buffer1[Mathf.clamp(x / scaling, 0, pixmap.getWidth()-1)][Mathf.clamp(y / scaling, 0, pixmap.getHeight()-1)].tile());
|
||||
input.begin(filter, editor.width(), editor.height(), (x, y) -> unpack(buffer1[Mathf.clamp(x / scaling, 0, pixmap.getWidth()-1) + w* Mathf.clamp(y / scaling, 0, pixmap.getHeight()-1)]));
|
||||
|
||||
//read from buffer1 and write to buffer2
|
||||
pixmap.each((px, py) -> {
|
||||
int x = px * scaling, y = py * scaling;
|
||||
GenTile tile = buffer1[px][py];
|
||||
input.apply(x, y, content.block(tile.floor), content.block(tile.block), content.block(tile.ore));
|
||||
long tile = buffer1[px + py * w];
|
||||
input.apply(x, y, content.block(PackTile.block(tile)), content.block(PackTile.floor(tile)), content.block(PackTile.overlay(tile)));
|
||||
filter.apply(input);
|
||||
buffer2[px][py].set(input.floor, input.block, input.ore, Team.get(tile.team));
|
||||
buffer2[px + py * w] = PackTile.get(input.block.id, input.floor.id, input.overlay.id);
|
||||
});
|
||||
|
||||
pixmap.each((px, py) -> buffer1[px][py].set(buffer2[px][py]));
|
||||
pixmap.each((px, py) -> buffer1[px + py*w] = buffer2[px + py*w]);
|
||||
}
|
||||
|
||||
for(int px = 0; px < pixmap.getWidth(); px++){
|
||||
@@ -383,10 +386,10 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
//get result from buffer1 if there's filters left, otherwise get from editor directly
|
||||
if(filters.isEmpty()){
|
||||
Tile tile = editor.tile(px * scaling, py * scaling);
|
||||
color = MapIO.colorFor(tile.floor(), tile.block(), tile.overlay(), Team.derelict);
|
||||
color = MapIO.colorFor(tile.block(), tile.floor(), tile.overlay(), Team.derelict);
|
||||
}else{
|
||||
GenTile tile = buffer1[px][py];
|
||||
color = MapIO.colorFor(content.block(tile.floor), content.block(tile.block), content.block(tile.ore), Team.derelict);
|
||||
long tile = buffer1[px + py*w];
|
||||
color = MapIO.colorFor(content.block(PackTile.block(tile)), content.block(PackTile.floor(tile)), content.block(PackTile.overlay(tile)), Team.derelict);
|
||||
}
|
||||
pixmap.draw(px, pixmap.getHeight() - 1 - py, color);
|
||||
}
|
||||
@@ -401,44 +404,9 @@ public class MapGenerateDialog extends BaseDialog{
|
||||
});
|
||||
}catch(Exception e){
|
||||
generating = false;
|
||||
e.printStackTrace();
|
||||
Log.err(e);
|
||||
}
|
||||
world.setGenerating(false);
|
||||
});
|
||||
}
|
||||
|
||||
private class GenTile{
|
||||
public byte team;
|
||||
public short block, floor, ore;
|
||||
|
||||
GenTile(){
|
||||
}
|
||||
|
||||
public void set(Block floor, Block wall, Block ore, Team team){
|
||||
this.floor = floor.id;
|
||||
this.block = wall.id;
|
||||
this.ore = ore.id;
|
||||
this.team = (byte)team.id;
|
||||
}
|
||||
|
||||
public void set(GenTile other){
|
||||
this.floor = other.floor;
|
||||
this.block = other.block;
|
||||
this.ore = other.ore;
|
||||
this.team = other.team;
|
||||
}
|
||||
|
||||
public GenTile set(Tile other){
|
||||
set(other.floor(), other.block(), other.overlay(), other.team());
|
||||
return this;
|
||||
}
|
||||
|
||||
Tile tile(){
|
||||
ctile.setFloor((Floor)content.block(floor));
|
||||
ctile.setBlock(content.block(block));
|
||||
ctile.setOverlay(content.block(ore));
|
||||
ctile.setTeam(Team.get(team));
|
||||
return ctile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.struct.*;
|
||||
import arc.scene.ui.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.io.*;
|
||||
|
||||
@@ -8,7 +8,7 @@ import mindustry.maps.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
import static mindustry.Vars.maps;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapLoadDialog extends BaseDialog{
|
||||
private Map selected = null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import arc.util.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
public class MapResizeDialog extends BaseDialog{
|
||||
private static final int minSize = 50, maxSize = 500, increment = 50;
|
||||
public static int minSize = 50, maxSize = 500, increment = 50;
|
||||
int width, height;
|
||||
|
||||
public MapResizeDialog(MapEditor editor, Intc2 cons){
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.Core;
|
||||
import arc.graphics.Color;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.input.GestureDetector;
|
||||
import arc.input.GestureDetector.GestureListener;
|
||||
import arc.input.KeyCode;
|
||||
import arc.math.Mathf;
|
||||
import arc.input.*;
|
||||
import arc.input.GestureDetector.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.Element;
|
||||
import arc.scene.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.TextField;
|
||||
import arc.scene.ui.layout.Scl;
|
||||
import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.graphics.Pal;
|
||||
import mindustry.input.Binding;
|
||||
import mindustry.ui.GridImage;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.mobile;
|
||||
import static mindustry.Vars.ui;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MapView extends Element implements GestureListener{
|
||||
private MapEditor editor;
|
||||
@@ -173,6 +171,10 @@ public class MapView extends Element implements GestureListener{
|
||||
this.grid = grid;
|
||||
}
|
||||
|
||||
public void center(){
|
||||
offsetx = offsety = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(float delta){
|
||||
super.act(delta);
|
||||
@@ -241,14 +243,14 @@ public class MapView extends Element implements GestureListener{
|
||||
|
||||
image.setImageSize(editor.width(), editor.height());
|
||||
|
||||
if(!ScissorStack.push(rect.set(x, y, width, height))){
|
||||
if(!ScissorStack.push(rect.set(x, y + Core.scene.marginBottom, width, height))){
|
||||
return;
|
||||
}
|
||||
|
||||
Draw.color(Pal.remove);
|
||||
Lines.stroke(2f);
|
||||
Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2);
|
||||
editor.renderer.draw(centerx - sclwidth / 2, centery - sclheight / 2, sclwidth, sclheight);
|
||||
editor.renderer.draw(centerx - sclwidth / 2, centery - sclheight / 2 + Core.scene.marginBottom, sclwidth, sclheight);
|
||||
Draw.reset();
|
||||
|
||||
if(grid){
|
||||
@@ -319,7 +321,7 @@ public class MapView extends Element implements GestureListener{
|
||||
}
|
||||
|
||||
private boolean active(){
|
||||
return Core.scene.getKeyboardFocus() != null
|
||||
return Core.scene != null && Core.scene.getKeyboardFocus() != null
|
||||
&& Core.scene.getKeyboardFocus().isDescendantOf(ui.editor)
|
||||
&& ui.editor.isShown() && tool == EditorTool.zoom &&
|
||||
Core.scene.hit(Core.input.mouse().x, Core.input.mouse().y, true) == this;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.struct.Seq;
|
||||
import arc.struct.*;
|
||||
|
||||
public class OperationStack{
|
||||
private static final int maxSize = 10;
|
||||
|
||||
@@ -17,7 +17,7 @@ import mindustry.ui.*;
|
||||
|
||||
public class WaveGraph extends Table{
|
||||
public Seq<SpawnGroup> groups = new Seq<>();
|
||||
public int from, to = 20;
|
||||
public int from = 0, to = 20;
|
||||
|
||||
private Mode mode = Mode.counts;
|
||||
private int[][] values;
|
||||
@@ -81,7 +81,7 @@ public class WaveGraph extends Table{
|
||||
for(int i = 0; i < values.length; i++){
|
||||
float sum = 0;
|
||||
for(UnitType type : used.orderedItems()){
|
||||
sum += type.health * values[i][type.id];
|
||||
sum += (type.health) * values[i][type.id];
|
||||
}
|
||||
|
||||
float cx = graphX + i*spacing, cy = 2f + graphY + sum * (graphH - 4f) / maxHealth;
|
||||
@@ -114,7 +114,7 @@ public class WaveGraph extends Table{
|
||||
|
||||
Lines.line(cx, cy, cx, cy + len);
|
||||
if(i == values.length/2){
|
||||
font.draw("" + (i + from), cx, cy - 2f, Align.center);
|
||||
font.draw("" + (i + from + 1), cx, cy - 2f, Align.center);
|
||||
}
|
||||
}
|
||||
font.setColor(Color.white);
|
||||
@@ -154,13 +154,13 @@ public class WaveGraph extends Table{
|
||||
int sum = 0;
|
||||
|
||||
for(SpawnGroup spawn : groups){
|
||||
int spawned = spawn.getUnitsSpawned(i);
|
||||
int spawned = spawn.getSpawned(i);
|
||||
values[index][spawn.type.id] += spawned;
|
||||
if(spawned > 0){
|
||||
used.add(spawn.type);
|
||||
}
|
||||
max = Math.max(max, values[index][spawn.type.id]);
|
||||
healthsum += spawned * spawn.type.health;
|
||||
healthsum += spawned * (spawn.type.health);
|
||||
sum += spawned;
|
||||
}
|
||||
maxTotal = Math.max(maxTotal, sum);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package mindustry.editor;
|
||||
|
||||
import arc.*;
|
||||
import arc.input.*;
|
||||
import arc.math.*;
|
||||
import arc.scene.event.*;
|
||||
import arc.scene.ui.*;
|
||||
@@ -35,15 +34,9 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
super("@waves.title");
|
||||
|
||||
shown(this::setup);
|
||||
hidden(() -> {
|
||||
state.rules.spawns = groups;
|
||||
});
|
||||
hidden(() -> state.rules.spawns = groups);
|
||||
|
||||
keyDown(key -> {
|
||||
if(key == KeyCode.escape || key == KeyCode.back){
|
||||
Core.app.post(this::hide);
|
||||
}
|
||||
});
|
||||
addCloseListener();
|
||||
|
||||
onResize(this::setup);
|
||||
addCloseButton();
|
||||
@@ -71,7 +64,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
}).disabled(b -> Core.app.getClipboardText() == null || Core.app.getClipboardText().isEmpty());
|
||||
dialog.cont.row();
|
||||
dialog.cont.button("@settings.reset", () -> ui.showConfirm("@confirm", "@settings.clear.confirm", () -> {
|
||||
groups = JsonIO.copy(defaultWaves.get());
|
||||
groups = JsonIO.copy(waves.get());
|
||||
buildGroups();
|
||||
dialog.hide();
|
||||
}));
|
||||
@@ -101,6 +94,14 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
view(1);
|
||||
}
|
||||
});
|
||||
|
||||
if(experimental){
|
||||
buttons.button("Random", Icon.refresh, () -> {
|
||||
groups.clear();
|
||||
groups = Waves.generate(1f / 10f);
|
||||
updateWaves();
|
||||
}).width(200f);
|
||||
}
|
||||
}
|
||||
|
||||
void view(int amount){
|
||||
@@ -124,7 +125,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
}
|
||||
|
||||
void setup(){
|
||||
groups = JsonIO.copy(state.rules.spawns.isEmpty() ? defaultWaves.get() : state.rules.spawns);
|
||||
groups = JsonIO.copy(state.rules.spawns.isEmpty() ? waves.get() : state.rules.spawns);
|
||||
|
||||
cont.clear();
|
||||
cont.stack(new Table(Tex.clear, main -> {
|
||||
@@ -159,7 +160,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
t.margin(0).defaults().pad(3).padLeft(5f).growX().left();
|
||||
t.button(b -> {
|
||||
b.left();
|
||||
b.image(group.type.icon(mindustry.ui.Cicon.medium)).size(32f).padRight(3);
|
||||
b.image(group.type.icon(Cicon.medium)).size(32f).padRight(3).scaling(Scaling.fit);
|
||||
b.add(group.type.localizedName).color(Pal.accent);
|
||||
|
||||
b.add().growX();
|
||||
@@ -262,7 +263,7 @@ public class WaveInfoDialog extends BaseDialog{
|
||||
if(type.isHidden()) continue;
|
||||
p.button(t -> {
|
||||
t.left();
|
||||
t.image(type.icon(Cicon.medium)).size(40f).padRight(2f);
|
||||
t.image(type.icon(Cicon.medium)).size(8 * 4).scaling(Scaling.fit).padRight(2f);
|
||||
t.add(type.localizedName);
|
||||
}, () -> {
|
||||
lastType = type;
|
||||
|
||||
@@ -2,13 +2,13 @@ package mindustry.entities;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.*;
|
||||
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.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -23,7 +23,8 @@ public class Damage{
|
||||
private static Tile furthest;
|
||||
private static Rect rect = new Rect();
|
||||
private static Rect hitrect = new Rect();
|
||||
private static Vec2 tr = new Vec2();
|
||||
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();
|
||||
@@ -31,15 +32,22 @@ public class Damage{
|
||||
private static Unit tmpUnit;
|
||||
|
||||
/** Creates a dynamic explosion based on specified parameters. */
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, Color color, boolean damage){
|
||||
public static void dynamicExplosion(float x, float y, float flammability, float explosiveness, float power, float radius, boolean damage){
|
||||
dynamicExplosion(x, y, flammability, explosiveness, power, radius, damage, true, null);
|
||||
}
|
||||
|
||||
/** 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(flammability / 4, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
|
||||
if(fire){
|
||||
for(int i = 0; i < Mathf.clamp(flammability / 4, 0, 30); i++){
|
||||
Time.run(i / 2f, () -> Call.createBullet(Bullets.fireball, Team.derelict, x, y, Mathf.random(360f), Bullets.fireball.damage, 1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
int waves = Mathf.clamp((int)(explosiveness / 4), 0, 30);
|
||||
@@ -47,7 +55,7 @@ public class Damage{
|
||||
for(int i = 0; i < waves; i++){
|
||||
int f = i;
|
||||
Time.run(i * 2f, () -> {
|
||||
Damage.damage(x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f);
|
||||
Damage.damage(ignoreTeam, x, y, Mathf.clamp(radius + explosiveness, 0, 50f) * ((f + 1f) / waves), explosiveness / 2f, false);
|
||||
Fx.blockExplosionSmoke.at(x + Mathf.range(radius), y + Mathf.range(radius));
|
||||
});
|
||||
}
|
||||
@@ -82,7 +90,7 @@ public class Damage{
|
||||
|
||||
furthest = null;
|
||||
|
||||
boolean found = world.raycast(b.tileX(), b.tileY(), world.toTile(b.x + Tmp.v1.x), world.toTile(b.y + Tmp.v1.y),
|
||||
boolean found = world.raycast(b.tileX(), b.tileY(), World.toTile(b.x + Tmp.v1.x), World.toTile(b.y + Tmp.v1.y),
|
||||
(x, y) -> (furthest = world.tile(x, y)) != null && furthest.team() != b.team && furthest.block().absorbLasers);
|
||||
|
||||
return found && furthest != null ? Math.max(6f, b.dst(furthest.worldx(), furthest.worldy())) : length;
|
||||
@@ -112,20 +120,35 @@ public class Damage{
|
||||
|
||||
collidedBlocks.clear();
|
||||
tr.trns(angle, length);
|
||||
|
||||
Intc2 collider = (cx, cy) -> {
|
||||
Building tile = world.build(cx, cy);
|
||||
if(tile != null && !collidedBlocks.contains(tile.pos()) && tile.team != team && tile.collide(hitter)){
|
||||
tile.collision(hitter);
|
||||
collidedBlocks.add(tile.pos());
|
||||
hitter.type.hit(hitter, tile.x, tile.y);
|
||||
boolean collide = tile != null && collidedBlocks.add(tile.pos());
|
||||
|
||||
if(hitter.damage > 0){
|
||||
float health = !collide ? 0 : tile.health;
|
||||
|
||||
if(collide && tile.team != team && tile.collide(hitter)){
|
||||
tile.collision(hitter);
|
||||
hitter.type.hit(hitter, tile.x, tile.y);
|
||||
}
|
||||
|
||||
//try to heal the tile
|
||||
if(collide && hitter.type.testCollision(hitter, tile)){
|
||||
hitter.type.hitTile(hitter, tile, health, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(hitter.type.collidesGround){
|
||||
world.raycastEachWorld(x, y, x + tr.x, y + tr.y, (cx, cy) -> {
|
||||
seg1.set(x, y);
|
||||
seg2.set(seg1).add(tr);
|
||||
world.raycastEachWorld(x, y, seg2.x, seg2.y, (cx, cy) -> {
|
||||
collider.get(cx, cy);
|
||||
if(large){
|
||||
for(Point2 p : Geometry.d4){
|
||||
|
||||
for(Point2 p : Geometry.d4){
|
||||
Tile other = world.tile(p.x + cx, p.y + cy);
|
||||
if(other != null && (large || Intersector.intersectSegmentRectangle(seg1, seg2, other.getBounds(Tmp.r1)))){
|
||||
collider.get(cx + p.x, cy + p.y);
|
||||
}
|
||||
}
|
||||
@@ -154,20 +177,27 @@ public class Damage{
|
||||
rect.height += expand * 2;
|
||||
|
||||
Cons<Unit> cons = e -> {
|
||||
if(!e.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround)) return;
|
||||
|
||||
e.hitbox(hitrect);
|
||||
|
||||
Vec2 vec = Geometry.raycastRect(x, y, x2, y2, hitrect.grow(expand * 2));
|
||||
|
||||
if(vec != null){
|
||||
if(vec != null && hitter.damage > 0){
|
||||
effect.at(vec.x, vec.y);
|
||||
e.collision(hitter, vec.x, vec.y);
|
||||
hitter.collision(e, vec.x, vec.y);
|
||||
}
|
||||
};
|
||||
|
||||
Units.nearbyEnemies(team, rect, cons);
|
||||
units.clear();
|
||||
|
||||
Units.nearbyEnemies(team, rect, u -> {
|
||||
if(u.checkTarget(hitter.type.collidesAir, hitter.type.collidesGround)){
|
||||
units.add(u);
|
||||
}
|
||||
});
|
||||
|
||||
units.sort(u -> u.dst2(hitter));
|
||||
units.each(cons);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +214,6 @@ public class Damage{
|
||||
Building tile = world.build(cx, cy);
|
||||
if(tile != null && tile.team != hitter.team){
|
||||
tmpBuilding = tile;
|
||||
//TODO return tile
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -362,9 +391,9 @@ public class Damage{
|
||||
if(scaledDamage <= 0 || tile == null) continue;
|
||||
|
||||
//apply damage to entity if needed
|
||||
if(tile.build != null && tile.team() != team){
|
||||
int health = (int)tile.build.health();
|
||||
if(tile.build.health() > 0){
|
||||
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;
|
||||
|
||||
@@ -402,8 +431,7 @@ public class Damage{
|
||||
}
|
||||
|
||||
@Struct
|
||||
static
|
||||
class PropCellStruct{
|
||||
static class PropCellStruct{
|
||||
byte x;
|
||||
byte y;
|
||||
short damage;
|
||||
|
||||
@@ -8,7 +8,6 @@ import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -22,35 +21,46 @@ public class Effect{
|
||||
private static final EffectContainer container = new EffectContainer();
|
||||
private static final Seq<Effect> all = new Seq<>();
|
||||
|
||||
public final int id;
|
||||
public final Cons<EffectContainer> renderer;
|
||||
public final float lifetime;
|
||||
/** Clip size. */
|
||||
public float size;
|
||||
private boolean initialized;
|
||||
|
||||
public boolean ground;
|
||||
public float groundDuration;
|
||||
public final int id;
|
||||
|
||||
public Cons<EffectContainer> renderer = e -> {};
|
||||
public float lifetime = 50f;
|
||||
/** Clip size. */
|
||||
public float clip;
|
||||
|
||||
public float layer = Layer.effect;
|
||||
public float layerDuration;
|
||||
|
||||
public Effect(float life, float clipsize, Cons<EffectContainer> renderer){
|
||||
this.id = all.size;
|
||||
this.lifetime = life;
|
||||
this.renderer = renderer;
|
||||
this.size = clipsize;
|
||||
this.clip = clipsize;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public Effect(float life, Cons<EffectContainer> renderer){
|
||||
this(life,50f, renderer);
|
||||
this(life, 50f, renderer);
|
||||
}
|
||||
|
||||
public Effect ground(){
|
||||
ground = true;
|
||||
//for custom implementations
|
||||
public Effect(){
|
||||
this.id = all.size;
|
||||
all.add(this);
|
||||
}
|
||||
|
||||
public void init(){}
|
||||
|
||||
public Effect layer(float l){
|
||||
layer = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Effect ground(float duration){
|
||||
ground = true;
|
||||
this.groundDuration = duration;
|
||||
public Effect layer(float l, float duration){
|
||||
layer = l;
|
||||
this.layerDuration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -88,14 +98,18 @@ public class Effect{
|
||||
|
||||
public float render(int id, Color color, float life, float lifetime, float rotation, float x, float y, Object data){
|
||||
container.set(id, color, life, lifetime, rotation, x, y, data);
|
||||
Draw.z(ground ? Layer.debris : Layer.effect);
|
||||
Draw.z(layer);
|
||||
Draw.reset();
|
||||
renderer.get(container);
|
||||
render(container);
|
||||
Draw.reset();
|
||||
|
||||
return container.lifetime;
|
||||
}
|
||||
|
||||
public void render(EffectContainer e){
|
||||
renderer.get(e);
|
||||
}
|
||||
|
||||
public static @Nullable Effect get(int id){
|
||||
return id >= all.size || id < 0 ? null : all.get(id);
|
||||
}
|
||||
@@ -123,9 +137,14 @@ public class Effect{
|
||||
if(headless || effect == Fx.none) return;
|
||||
if(Core.settings.getBool("effects")){
|
||||
Rect view = Core.camera.bounds(Tmp.r1);
|
||||
Rect pos = Tmp.r2.setSize(effect.size).setCenter(x, y);
|
||||
Rect pos = Tmp.r2.setSize(effect.clip).setCenter(x, y);
|
||||
|
||||
if(view.overlaps(pos)){
|
||||
if(!effect.initialized){
|
||||
effect.initialized = true;
|
||||
effect.init();
|
||||
}
|
||||
|
||||
EffectState entity = EffectState.create();
|
||||
entity.effect = effect;
|
||||
entity.rotation = rotation;
|
||||
@@ -147,7 +166,7 @@ public class Effect{
|
||||
if(headless || region == null || !Core.atlas.isFound(region)) return;
|
||||
|
||||
Tile tile = world.tileWorld(x, y);
|
||||
if(tile == null || tile.floor().isLiquid) return;
|
||||
if(tile == null || !tile.floor().hasSurface()) return;
|
||||
|
||||
Decal decal = Decal.create();
|
||||
decal.set(x, y);
|
||||
@@ -210,4 +229,4 @@ public class Effect{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ public class EntityCollisions{
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Hitboxc> void updatePhysics(EntityGroup<T> group){
|
||||
|
||||
QuadTree tree = group.tree();
|
||||
tree.clear();
|
||||
|
||||
@@ -127,7 +126,7 @@ public class EntityCollisions{
|
||||
|
||||
public static boolean legsSolid(int x, int y){
|
||||
Tile tile = world.tile(x, y);
|
||||
return tile == null || tile.staticDarkness() >= 2;
|
||||
return tile == null || tile.staticDarkness() >= 2 || tile.floor().solid;
|
||||
}
|
||||
|
||||
public static boolean waterSolid(int x, int y){
|
||||
@@ -141,7 +140,6 @@ public class EntityCollisions{
|
||||
}
|
||||
|
||||
private void checkCollide(Hitboxc a, Hitboxc b){
|
||||
|
||||
a.hitbox(this.r1);
|
||||
b.hitbox(this.r2);
|
||||
|
||||
@@ -218,7 +216,6 @@ public class EntityCollisions{
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Hitboxc> void collide(EntityGroup<T> groupa){
|
||||
|
||||
groupa.each(solid -> {
|
||||
solid.hitbox(r1);
|
||||
r1.x += (solid.lastX() - solid.getX());
|
||||
|
||||
@@ -4,11 +4,12 @@ import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.collisions;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
/** Represents a group of a certain type of entity.*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -57,8 +58,9 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
|
||||
each(Entityc::update);
|
||||
}
|
||||
|
||||
public void copy(Seq<T> arr){
|
||||
public Seq<T> copy(Seq<T> arr){
|
||||
arr.addAll(array);
|
||||
return arr;
|
||||
}
|
||||
|
||||
public void each(Cons<T> cons){
|
||||
@@ -92,6 +94,7 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
|
||||
return map != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T getByID(int id){
|
||||
if(map == null) throw new RuntimeException("Mapping is not enabled for group " + id + "!");
|
||||
return map.get(id);
|
||||
@@ -182,16 +185,21 @@ public class EntityGroup<T extends Entityc> implements Iterable<T>{
|
||||
|
||||
array.each(Entityc::remove);
|
||||
array.clear();
|
||||
if(map != null)
|
||||
map.clear();
|
||||
if(map != null) map.clear();
|
||||
|
||||
clearing = false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T find(Boolf<T> pred){
|
||||
return array.find(pred);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T first(){
|
||||
return array.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator(){
|
||||
return array.iterator();
|
||||
|
||||
@@ -15,7 +15,7 @@ public class Fires{
|
||||
private static final float baseLifetime = 1000f;
|
||||
private static final IntMap<Fire> map = new IntMap<>();
|
||||
|
||||
/** Start a fire on the tile. If there already is a file there, refreshes its lifetime. */
|
||||
/** Start a fire on the tile. If there already is a fire there, refreshes its lifetime. */
|
||||
public static void create(Tile tile){
|
||||
if(net.client() || tile == null || !state.rules.fire) return; //not clientside.
|
||||
|
||||
@@ -23,14 +23,14 @@ public class Fires{
|
||||
|
||||
if(fire == null){
|
||||
fire = Fire.create();
|
||||
fire.tile(tile);
|
||||
fire.lifetime(baseLifetime);
|
||||
fire.tile = tile;
|
||||
fire.lifetime = baseLifetime;
|
||||
fire.set(tile.worldx(), tile.worldy());
|
||||
fire.add();
|
||||
map.put(tile.pos(), fire);
|
||||
}else{
|
||||
fire.lifetime(baseLifetime);
|
||||
fire.time(0f);
|
||||
fire.lifetime = baseLifetime;
|
||||
fire.time = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ class GroupDefs<G>{
|
||||
@GroupDef(value = Buildingc.class) G build;
|
||||
@GroupDef(value = Syncc.class, mapping = true) G sync;
|
||||
@GroupDef(value = Drawc.class) G draw;
|
||||
@GroupDef(value = Firec.class) G fire;
|
||||
@GroupDef(value = Puddlec.class) G puddle;
|
||||
@GroupDef(value = WeatherStatec.class) G weather;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -24,36 +26,34 @@ public class Lightning{
|
||||
|
||||
/** Create a lighting branch at a location. Use Team.derelict to damage everyone. */
|
||||
public static void create(Team team, Color color, float damage, float x, float y, float targetAngle, int length){
|
||||
createLightingInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
|
||||
createLightningInternal(null, lastSeed++, team, color, damage, x, y, targetAngle, length);
|
||||
}
|
||||
|
||||
/** Create a lighting branch at a location. Uses bullet parameters. */
|
||||
public static void create(Bullet bullet, Color color, float damage, float x, float y, float targetAngle, int length){
|
||||
createLightingInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
|
||||
createLightningInternal(bullet, lastSeed++, bullet.team, color, damage, x, y, targetAngle, length);
|
||||
}
|
||||
|
||||
//TODO remote method
|
||||
//@Remote(called = Loc.server, unreliable = true)
|
||||
private static void createLightingInternal(Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
|
||||
private static void createLightningInternal(@Nullable Bullet hitter, int seed, Team team, Color color, float damage, float x, float y, float rotation, int length){
|
||||
random.setSeed(seed);
|
||||
hit.clear();
|
||||
|
||||
BulletType bulletType = hitter != null && !hitter.type.collidesAir ? Bullets.damageLightningGround : Bullets.damageLightning;
|
||||
BulletType hitCreate = hitter == null || hitter.type.lightningType == null ? Bullets.damageLightning : hitter.type.lightningType;
|
||||
Seq<Vec2> lines = new Seq<>();
|
||||
bhit = false;
|
||||
|
||||
for(int i = 0; i < length / 2; i++){
|
||||
bulletType.create(null, team, x, y, 0f, damage, 1f, 1f, hitter);
|
||||
hitCreate.create(null, team, x, y, 0f, damage, 1f, 1f, hitter);
|
||||
lines.add(new Vec2(x + Mathf.range(3f), y + Mathf.range(3f)));
|
||||
|
||||
if(lines.size > 1){
|
||||
bhit = false;
|
||||
Vec2 from = lines.get(lines.size - 2);
|
||||
Vec2 to = lines.get(lines.size - 1);
|
||||
world.raycastEach(world.toTile(from.getX()), world.toTile(from.getY()), world.toTile(to.getX()), world.toTile(to.getY()), (wx, wy) -> {
|
||||
world.raycastEach(World.toTile(from.getX()), World.toTile(from.getY()), World.toTile(to.getX()), World.toTile(to.getY()), (wx, wy) -> {
|
||||
|
||||
Tile tile = world.tile(wx, wy);
|
||||
if(tile != null && tile.block().insulated){
|
||||
if(tile != null && tile.block().insulated && tile.team() != team){
|
||||
bhit = true;
|
||||
//snap it instead of removing
|
||||
lines.get(lines.size -1).set(wx * tilesize, wy * tilesize);
|
||||
|
||||
@@ -53,13 +53,13 @@ public class Predict{
|
||||
|
||||
public static Vec2 intercept(Position src, Position dst, float v){
|
||||
float ddx = 0, ddy = 0;
|
||||
if(dst instanceof Hitboxc){
|
||||
ddx += ((Hitboxc)dst).deltaX();
|
||||
ddy += ((Hitboxc)dst).deltaY();
|
||||
if(dst instanceof Hitboxc h){
|
||||
ddx += h.deltaX();
|
||||
ddy += h.deltaY();
|
||||
}
|
||||
if(src instanceof Hitboxc){
|
||||
ddx -= ((Hitboxc)src).deltaX()/(Time.delta);
|
||||
ddy -= ((Hitboxc)src).deltaY()/(Time.delta);
|
||||
if(src instanceof Hitboxc h){
|
||||
ddx -= h.deltaX();
|
||||
ddy -= h.deltaY();
|
||||
}
|
||||
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), ddx, ddy, v);
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ public class Puddles{
|
||||
|
||||
public static final float maxLiquid = 70f;
|
||||
|
||||
/** Deposists a Puddle between tile and source. */
|
||||
/** Deposits a Puddle between tile and source. */
|
||||
public static void deposit(Tile tile, Tile source, Liquid liquid, float amount){
|
||||
deposit(tile, source, liquid, amount, 0);
|
||||
}
|
||||
|
||||
/** Deposists a Puddle at a tile. */
|
||||
/** Deposits a Puddle at a tile. */
|
||||
public static void deposit(Tile tile, Liquid liquid, float amount){
|
||||
deposit(tile, tile, liquid, amount, 0);
|
||||
}
|
||||
@@ -38,13 +38,17 @@ public class Puddles{
|
||||
|
||||
Puddle p = map.get(tile.pos());
|
||||
|
||||
if(generation == 0 && p != null && p.lastRipple() <= Time.time() - 40f){
|
||||
if(generation == 0 && p != null && p.lastRipple <= Time.time - 40f){
|
||||
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, 1f, tile.floor().liquidDrop.color);
|
||||
p.lastRipple(Time.time());
|
||||
p.lastRipple = Time.time;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(tile.floor().solid){
|
||||
return;
|
||||
}
|
||||
|
||||
Puddle p = map.get(tile.pos());
|
||||
if(p == null){
|
||||
Puddle puddle = Puddle.create();
|
||||
@@ -58,9 +62,9 @@ public class Puddles{
|
||||
}else if(p.liquid() == liquid){
|
||||
p.accepting(Math.max(amount, p.accepting()));
|
||||
|
||||
if(generation == 0 && p.lastRipple() <= Time.time() - 40f && p.amount() >= maxLiquid / 2f){
|
||||
if(generation == 0 && p.lastRipple <= Time.time - 40f && p.amount() >= maxLiquid / 2f){
|
||||
Fx.ripple.at((tile.worldx() + source.worldx()) / 2f, (tile.worldy() + source.worldy()) / 2f, 1f, p.liquid().color);
|
||||
p.lastRipple(Time.time());
|
||||
p.lastRipple = Time.time;
|
||||
}
|
||||
}else{
|
||||
p.amount(p.amount() + reactPuddle(p.liquid(), liquid, amount, p.tile(), (p.x() + source.worldx())/2f, (p.y() + source.worldy())/2f));
|
||||
|
||||
@@ -3,9 +3,11 @@ package mindustry.entities;
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
@@ -65,7 +67,7 @@ public class Units{
|
||||
|
||||
/** @return whether a new instance of a unit of this team can be created. */
|
||||
public static boolean canCreate(Team team, UnitType type){
|
||||
return teamIndex.countType(team, type) < getCap(team);
|
||||
return team.data().countType(type) < getCap(team);
|
||||
}
|
||||
|
||||
public static int getCap(Team team){
|
||||
@@ -124,7 +126,7 @@ public class Units{
|
||||
|
||||
nearby(x, y, width, height, unit -> {
|
||||
if(boolResult) return;
|
||||
if((unit.isGrounded() && !unit.type().hovering) == ground){
|
||||
if((unit.isGrounded() && !unit.type.hovering) == ground){
|
||||
unit.hitbox(hitrect);
|
||||
|
||||
if(hitrect.overlaps(x, y, width, height)){
|
||||
@@ -175,6 +177,18 @@ public class Units{
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the closest target enemy. First, units are checked, then tile entities. */
|
||||
public static Teamc bestTarget(Team team, float x, float y, float range, Boolf<Unit> unitPred, Boolf<Building> tilePred, Sortf sort){
|
||||
if(team == Team.derelict) return null;
|
||||
|
||||
Unit unit = bestEnemy(team, x, y, range, unitPred, sort);
|
||||
if(unit != null){
|
||||
return unit;
|
||||
}else{
|
||||
return findEnemyTile(team, x, y, range, tilePred);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the closest enemy of this team. Filter by predicate. */
|
||||
public static Unit closestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate){
|
||||
if(team == Team.derelict) return null;
|
||||
@@ -183,7 +197,7 @@ public class Units{
|
||||
cdist = 0f;
|
||||
|
||||
nearbyEnemies(team, x - range, y - range, range*2f, range*2f, e -> {
|
||||
if(e.dead() || !predicate.get(e)) return;
|
||||
if(e.dead() || !predicate.get(e) || e.team == Team.derelict) return;
|
||||
|
||||
float dst2 = e.dst2(x, y);
|
||||
if(dst2 < range*range && (result == null || dst2 < cdist)){
|
||||
@@ -195,6 +209,26 @@ public class Units{
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the closest enemy of this team using a custom comparison function. Filter by predicate. */
|
||||
public static Unit bestEnemy(Team team, float x, float y, float range, Boolf<Unit> predicate, Sortf sort){
|
||||
if(team == Team.derelict) return null;
|
||||
|
||||
result = null;
|
||||
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;
|
||||
|
||||
float cost = sort.cost(e, x, y);
|
||||
if(result == null || cost < cdist){
|
||||
result = e;
|
||||
cdist = cost;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the closest ally of this team. Filter by predicate. No range. */
|
||||
public static Unit closest(Team team, float x, float y, Boolf<Unit> predicate){
|
||||
result = null;
|
||||
@@ -252,7 +286,7 @@ public class Units{
|
||||
|
||||
/** Iterates over all units in a rectangle. */
|
||||
public static void nearby(Team team, float x, float y, float width, float height, Cons<Unit> cons){
|
||||
teamIndex.tree(team).intersect(x, y, width, height, cons);
|
||||
team.data().tree().intersect(x, y, width, height, cons);
|
||||
}
|
||||
|
||||
/** Iterates over all units in a circle around this position. */
|
||||
@@ -276,20 +310,12 @@ public class Units{
|
||||
|
||||
/** Iterates over all units that are enemies of this team. */
|
||||
public static void nearbyEnemies(Team team, float x, float y, float width, float height, Cons<Unit> cons){
|
||||
if(team.active()){
|
||||
for(Team enemy : state.teams.enemiesOf(team)){
|
||||
nearby(enemy, x, y, width, height, cons);
|
||||
}
|
||||
}else{
|
||||
//inactive teams have no cache, check everything
|
||||
//TODO cache all teams with units OR blocks
|
||||
for(Team other : Team.all){
|
||||
if(other != team && teamIndex.count(other) > 0){
|
||||
nearby(other, x, y, width, height, cons);
|
||||
}
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
for(int i = 0; i < data.size; i++){
|
||||
if(data.items[i].team != team){
|
||||
nearby(data.items[i].team, x, y, width, height, cons);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Iterates over all units that are enemies of this team. */
|
||||
@@ -297,4 +323,7 @@ public class Units{
|
||||
nearbyEnemies(team, rect.x, rect.y, rect.width, rect.height, cons);
|
||||
}
|
||||
|
||||
public interface Sortf{
|
||||
float cost(Unit unit, float x, float y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public abstract class Ability implements Cloneable{
|
||||
@@ -14,4 +16,13 @@ public abstract class Ability implements Cloneable{
|
||||
throw new RuntimeException("java sucks", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void displayBars(Unit unit, Table bars){
|
||||
|
||||
}
|
||||
|
||||
/** @return localized ability name; mods should override this. */
|
||||
public String localized(){
|
||||
return Core.bundle.get("ability." + getClass().getSimpleName().replace("Ability", "").toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
public class ForceFieldAbility extends Ability{
|
||||
/** Shield radius. */
|
||||
@@ -27,8 +29,8 @@ public class ForceFieldAbility extends Ability{
|
||||
private static float realRad;
|
||||
private static Unit paramUnit;
|
||||
private static ForceFieldAbility paramField;
|
||||
private static final Cons<Shielderc> shieldConsumer = trait -> {
|
||||
if(trait.team() != paramUnit.team && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
|
||||
private static final Cons<Bullet> shieldConsumer = trait -> {
|
||||
if(trait.team != paramUnit.team && trait.type.absorbable && Intersector.isInsideHexagon(paramUnit.x, paramUnit.y, realRad * 2f, trait.x(), trait.y()) && paramUnit.shield > 0){
|
||||
trait.absorb();
|
||||
Fx.absorb.at(trait);
|
||||
|
||||
@@ -94,7 +96,12 @@ public class ForceFieldAbility extends Ability{
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRadius(Unit unit){
|
||||
@Override
|
||||
public void displayBars(Unit unit, Table bars){
|
||||
bars.add(new Bar("stat.shieldhealth", Pal.accent, () -> unit.shield / max)).row();
|
||||
}
|
||||
|
||||
public void checkRadius(Unit unit){
|
||||
//timer2 is used to store radius scale as an effect
|
||||
realRad = radiusScale * radius;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.util.*;
|
||||
import arc.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class MoveLightningAbility extends Ability{
|
||||
//Lightning damage
|
||||
public float damage = 35f;
|
||||
//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
|
||||
public int length = 12;
|
||||
//Speeds for when to start lightninging and when to stop getting faster
|
||||
public float minSpeed = 0.8f, maxSpeed = 1.2f;
|
||||
//Lightning color
|
||||
public Color color = Color.valueOf("a9d8ff");
|
||||
|
||||
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){
|
||||
this.damage = damage;
|
||||
this.length = length;
|
||||
this.chance = chance;
|
||||
this.minSpeed = minSpeed;
|
||||
this.maxSpeed = maxSpeed;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
shootSound.at(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class HealFieldAbility extends Ability{
|
||||
public class RepairFieldAbility extends Ability{
|
||||
public float amount = 1, reload = 100, range = 60;
|
||||
public Effect healEffect = Fx.heal;
|
||||
public Effect activeEffect = Fx.healWaveDynamic;
|
||||
@@ -13,9 +13,9 @@ public class HealFieldAbility extends Ability{
|
||||
protected float timer;
|
||||
protected boolean wasHealed = false;
|
||||
|
||||
HealFieldAbility(){}
|
||||
RepairFieldAbility(){}
|
||||
|
||||
public HealFieldAbility(float amount, float reload, float range){
|
||||
public RepairFieldAbility(float amount, float reload, float range){
|
||||
this.amount = amount;
|
||||
this.reload = reload;
|
||||
this.range = range;
|
||||
@@ -30,7 +30,7 @@ public class HealFieldAbility extends Ability{
|
||||
|
||||
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
|
||||
if(other.damaged()){
|
||||
healEffect.at(unit);
|
||||
healEffect.at(other);
|
||||
wasHealed = true;
|
||||
}
|
||||
other.heal(amount);
|
||||
@@ -5,7 +5,7 @@ import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class ShieldFieldAbility extends Ability{
|
||||
public class ShieldRegenFieldAbility extends Ability{
|
||||
public float amount = 1, max = 100f, reload = 100, range = 60;
|
||||
public Effect applyEffect = Fx.shieldApply;
|
||||
public Effect activeEffect = Fx.shieldWave;
|
||||
@@ -13,9 +13,9 @@ public class ShieldFieldAbility extends Ability{
|
||||
protected float timer;
|
||||
protected boolean applied = false;
|
||||
|
||||
ShieldFieldAbility(){}
|
||||
ShieldRegenFieldAbility(){}
|
||||
|
||||
public ShieldFieldAbility(float amount, float max, float reload, float range){
|
||||
public ShieldRegenFieldAbility(float amount, float max, float reload, float range){
|
||||
this.amount = amount;
|
||||
this.max = max;
|
||||
this.reload = reload;
|
||||
@@ -1,6 +1,5 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
@@ -8,7 +7,7 @@ import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
|
||||
public class StatusFieldAbility extends Ability{
|
||||
public @NonNull StatusEffect effect;
|
||||
public StatusEffect effect;
|
||||
public float duration = 60, reload = 100, range = 20;
|
||||
public Effect applyEffect = Fx.heal;
|
||||
public Effect activeEffect = Fx.overdriveWave;
|
||||
@@ -17,7 +16,7 @@ public class StatusFieldAbility extends Ability{
|
||||
|
||||
StatusFieldAbility(){}
|
||||
|
||||
public StatusFieldAbility(@NonNull StatusEffect effect, float duration, float reload, float range){
|
||||
public StatusFieldAbility(StatusEffect effect, float duration, float reload, float range){
|
||||
this.duration = duration;
|
||||
this.reload = reload;
|
||||
this.range = range;
|
||||
@@ -29,7 +28,6 @@ public class StatusFieldAbility extends Ability{
|
||||
timer += Time.delta;
|
||||
|
||||
if(timer >= reload){
|
||||
|
||||
Units.nearby(unit.team, unit.x, unit.y, range, other -> {
|
||||
other.apply(effect, duration);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mindustry.entities.abilities;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.content.*;
|
||||
@@ -12,14 +12,16 @@ import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class UnitSpawnAbility extends Ability{
|
||||
public @NonNull UnitType type;
|
||||
public UnitType type;
|
||||
public float spawnTime = 60f, spawnX, spawnY;
|
||||
public Effect spawnEffect = Fx.spawn;
|
||||
|
||||
protected float timer;
|
||||
|
||||
public UnitSpawnAbility(@NonNull UnitType type, float spawnTime, float spawnX, float spawnY){
|
||||
public UnitSpawnAbility(UnitType type, float spawnTime, float spawnX, float spawnY){
|
||||
this.type = type;
|
||||
this.spawnTime = spawnTime;
|
||||
this.spawnX = spawnX;
|
||||
@@ -31,10 +33,9 @@ public class UnitSpawnAbility extends Ability{
|
||||
|
||||
@Override
|
||||
public void update(Unit unit){
|
||||
timer += Time.delta;
|
||||
timer += Time.delta * state.rules.unitBuildSpeedMultiplier;
|
||||
|
||||
if(timer >= spawnTime && Units.canCreate(unit.team, type)){
|
||||
|
||||
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);
|
||||
@@ -57,4 +58,9 @@ public class UnitSpawnAbility extends Ability{
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String localized(){
|
||||
return Core.bundle.format("ability.unitspawn", type.localizedName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package mindustry.entities.bullet;
|
||||
import arc.audio.*;
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
@@ -14,6 +13,7 @@ import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -25,6 +25,7 @@ public abstract class BulletType extends Content{
|
||||
public float drawSize = 40f;
|
||||
public float drag = 0f;
|
||||
public boolean pierce, pierceBuilding;
|
||||
public int pierceCap = -1;
|
||||
public Effect hitEffect, despawnEffect;
|
||||
|
||||
/** Effect created when shooting. */
|
||||
@@ -40,7 +41,7 @@ public abstract class BulletType extends Content{
|
||||
/** Multiplied by turret reload speed to get final shoot speed. */
|
||||
public float reloadMultiplier = 1f;
|
||||
/** Multiplier of how much base damage is done to tiles. */
|
||||
public float tileDamageMultiplier = 1f;
|
||||
public float buildingDamageMultiplier = 1f;
|
||||
/** Recoil from shooter entities. */
|
||||
public float recoil;
|
||||
/** Whether to kill the shooter when this is shot. For suicide bombers. */
|
||||
@@ -71,15 +72,25 @@ public abstract class BulletType extends Content{
|
||||
public boolean hittable = true;
|
||||
/** Whether this bullet can be reflected. */
|
||||
public boolean reflectable = true;
|
||||
/** Whether this projectile can be absorbed by shields. */
|
||||
public boolean absorbable = true;
|
||||
/** Whether to move the bullet back depending on delta to fix some delta-time realted issues.
|
||||
* Do not change unless you know what you're doing. */
|
||||
public boolean backMove = true;
|
||||
/** Bullet range override. */
|
||||
public float range = -1f;
|
||||
public float maxRange = -1f;
|
||||
/** % of block health healed **/
|
||||
public float healPercent = 0f;
|
||||
/** whether to make fire on impact */
|
||||
public boolean makeFire = false;
|
||||
|
||||
//additional effects
|
||||
|
||||
public float fragCone = 360f;
|
||||
public float fragAngle = 0f;
|
||||
public int fragBullets = 9;
|
||||
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f, fragLifeMin = 1f, fragLifeMax = 1f;
|
||||
public BulletType fragBullet = null;
|
||||
public @Nullable BulletType fragBullet = null;
|
||||
public Color hitColor = Color.white;
|
||||
|
||||
public Color trailColor = Pal.missileYellowBack;
|
||||
@@ -95,12 +106,18 @@ public abstract class BulletType extends Content{
|
||||
public float incendChance = 1f;
|
||||
public float homingPower = 0f;
|
||||
public float homingRange = 50f;
|
||||
/** Use a negative value to disable homing delay. */
|
||||
public float homingDelay = -1f;
|
||||
|
||||
public Color lightningColor = Pal.surge;
|
||||
public int lightning;
|
||||
public int lightningLength = 5, lightningLengthRand = 0;
|
||||
/** Use a negative value to use default bullet damage. */
|
||||
public float lightningDamage = -1;
|
||||
public float lightningCone = 360f;
|
||||
public float lightningAngle = 0f;
|
||||
/** The bullet created at lightning points. */
|
||||
public @Nullable BulletType lightningType = null;
|
||||
|
||||
public float weaveScale = 1f;
|
||||
public float weaveMag = -1f;
|
||||
@@ -126,17 +143,37 @@ public abstract class BulletType extends Content{
|
||||
this(1f, 1f);
|
||||
}
|
||||
|
||||
/** @return estimated damage per shot. this can be very inaccurate. */
|
||||
public float estimateDPS(){
|
||||
float sum = damage + splashDamage*0.75f;
|
||||
if(fragBullet != null && fragBullet != this){
|
||||
sum += fragBullet.estimateDPS() * fragBullets / 2f;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/** Returns maximum distance the bullet this bullet type has can travel. */
|
||||
public float range(){
|
||||
return Math.max(speed * lifetime * (1f - drag), range);
|
||||
return Math.max(speed * lifetime * (1f - drag), maxRange);
|
||||
}
|
||||
|
||||
public boolean collides(Bullet bullet, Building tile){
|
||||
return true;
|
||||
public boolean testCollision(Bullet bullet, Building tile){
|
||||
return healPercent <= 0.001f || tile.team != bullet.team || tile.healthf() < 1f;
|
||||
}
|
||||
|
||||
public void hitTile(Bullet b, Building tile, float initialHealth){
|
||||
hit(b);
|
||||
/** If direct is false, this is an indirect hit and the tile was already damaged.
|
||||
* TODO this is a mess. */
|
||||
public void hitTile(Bullet b, Building build, float initialHealth, boolean direct){
|
||||
if(makeFire && build.team != b.team){
|
||||
Fires.create(build.tile);
|
||||
}
|
||||
|
||||
if(healPercent > 0f && build.team == b.team && !(build.block instanceof ConstructBlock)){
|
||||
Fx.healBlockFull.at(build.x, build.y, build.block.size, Pal.heal);
|
||||
build.heal(healPercent / 100f * build.maxHealth());
|
||||
}else if(build.team != b.team && direct){
|
||||
hit(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void hitEntity(Bullet b, Hitboxc other, float initialHealth){
|
||||
@@ -156,7 +193,7 @@ public abstract class BulletType extends Content{
|
||||
if(fragBullet != null){
|
||||
for(int i = 0; i < fragBullets; i++){
|
||||
float len = Mathf.random(1f, 7f);
|
||||
float a = b.rotation() + Mathf.range(fragCone/2);
|
||||
float a = b.rotation() + Mathf.range(fragCone/2) + fragAngle;
|
||||
fragBullet.create(b, x + Angles.trnsx(a, len), y + Angles.trnsy(a, len), a, Mathf.random(fragVelocityMin, fragVelocityMax), Mathf.random(fragLifeMin, fragLifeMax));
|
||||
}
|
||||
}
|
||||
@@ -172,16 +209,29 @@ public abstract class BulletType extends Content{
|
||||
Damage.createIncend(x, y, incendSpread, incendAmount);
|
||||
}
|
||||
|
||||
if(splashDamageRadius > 0){
|
||||
if(splashDamageRadius > 0 && !b.absorbed){
|
||||
Damage.damage(b.team, x, y, splashDamageRadius, splashDamage * b.damageMultiplier(), collidesAir, collidesGround);
|
||||
|
||||
if(status != StatusEffects.none){
|
||||
Damage.status(b.team, x, y, splashDamageRadius, status, statusDuration, collidesAir, collidesGround);
|
||||
}
|
||||
|
||||
if(healPercent > 0f){
|
||||
indexer.eachBlock(b.team, x, y, splashDamageRadius, Building::damaged, other -> {
|
||||
Fx.healBlockFull.at(other.x, other.y, other.block.size, Pal.heal);
|
||||
other.heal(healPercent / 100f * other.maxHealth());
|
||||
});
|
||||
}
|
||||
|
||||
if(makeFire){
|
||||
indexer.eachBlock(null, x, y, splashDamageRadius, other -> other.team != b.team, other -> {
|
||||
Fires.create(other.tile);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < lightning; i++){
|
||||
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, Mathf.random(360f), lightningLength + Mathf.random(lightningLengthRand));
|
||||
Lightning.create(b, lightningColor, lightningDamage < 0 ? damage : lightningDamage, b.x, b.y, b.rotation() + Mathf.range(lightningCone/2) + lightningAngle, lightningLength + Mathf.random(lightningLengthRand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,17 +254,18 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
public void init(Bullet b){
|
||||
if(killShooter && b.owner() instanceof Healthc){
|
||||
((Healthc)b.owner()).kill();
|
||||
|
||||
if(killShooter && b.owner() instanceof Healthc h){
|
||||
h.kill();
|
||||
}
|
||||
|
||||
if(instantDisappear){
|
||||
b.time(lifetime);
|
||||
b.time = lifetime;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(Bullet b){
|
||||
if(homingPower > 0.0001f){
|
||||
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));
|
||||
@@ -222,7 +273,8 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
if(weaveMag > 0){
|
||||
b.vel.rotate(Mathf.sin(Mathf.randomSeed(b.id, 10f) + b.time, weaveScale, weaveMag) * Time.delta);
|
||||
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);
|
||||
}
|
||||
|
||||
if(trailChance > 0){
|
||||
@@ -232,6 +284,18 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
if(pierceCap >= 1){
|
||||
pierce = true;
|
||||
//pierceBuilding is not enabled by default, because a bullet may want to *not* pierce buildings
|
||||
}
|
||||
|
||||
if(lightningType == null){
|
||||
lightningType = !collidesAir ? Bullets.damageLightningGround : Bullets.damageLightning;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentType getContentType(){
|
||||
return ContentType.bullet;
|
||||
@@ -254,11 +318,11 @@ public abstract class BulletType extends Content{
|
||||
}
|
||||
|
||||
public Bullet create(Bullet parent, float x, float y, float angle){
|
||||
return create(parent.owner(), parent.team, x, y, angle);
|
||||
return create(parent.owner, parent.team, x, y, angle);
|
||||
}
|
||||
|
||||
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl, float lifeScale){
|
||||
return create(parent.owner(), parent.team, x, y, angle, velocityScl, lifeScale);
|
||||
return create(parent.owner, parent.team, x, y, angle, velocityScl, lifeScale);
|
||||
}
|
||||
|
||||
public Bullet create(Bullet parent, float x, float y, float angle, float velocityScl){
|
||||
@@ -271,15 +335,19 @@ public abstract class BulletType extends Content{
|
||||
bullet.owner = owner;
|
||||
bullet.team = team;
|
||||
bullet.vel.trns(angle, speed * velocityScl);
|
||||
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
|
||||
if(backMove){
|
||||
bullet.set(x - bullet.vel.x * Time.delta, y - bullet.vel.y * Time.delta);
|
||||
}else{
|
||||
bullet.set(x, y);
|
||||
}
|
||||
bullet.lifetime = lifetime * lifetimeScl;
|
||||
bullet.data = data;
|
||||
bullet.drag = drag;
|
||||
bullet.hitSize = hitSize;
|
||||
bullet.damage = damage < 0 ? this.damage : damage;
|
||||
bullet.damage = (damage < 0 ? this.damage : damage) * bullet.damageMultiplier();
|
||||
bullet.add();
|
||||
|
||||
if(keepVelocity && owner instanceof Velc) bullet.vel.add(((Velc)owner).vel().x, ((Velc)owner).vel().y);
|
||||
if(keepVelocity && owner instanceof Velc v) bullet.vel.add(v.vel().x, v.vel().y);
|
||||
return bullet;
|
||||
}
|
||||
|
||||
@@ -289,6 +357,7 @@ public abstract class BulletType extends Content{
|
||||
|
||||
@Remote(called = Loc.server, unreliable = true)
|
||||
public static void createBullet(BulletType type, Team team, float x, float y, float angle, float damage, float velocityScl, float lifetimeScl){
|
||||
if(type == null) return;
|
||||
type.create(null, team, x, y, angle, damage, velocityScl, lifetimeScl, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
public float length = 220f;
|
||||
public float shake = 1f;
|
||||
public float fadeTime = 16f;
|
||||
public float lightStroke = 40f;
|
||||
public float spaceMag = 35f;
|
||||
public Color[] colors = {Color.valueOf("ec745855"), Color.valueOf("ec7458aa"), Color.valueOf("ff9c5a"), Color.white};
|
||||
public float[] tscales = {1f, 0.7f, 0.5f, 0.2f};
|
||||
public float[] strokes = {2f, 1.5f, 1f, 0.3f};
|
||||
@@ -28,21 +30,29 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
hitSize = 4;
|
||||
drawSize = 420f;
|
||||
lifetime = 16f;
|
||||
keepVelocity = false;
|
||||
pierce = true;
|
||||
hittable = false;
|
||||
hitColor = colors[2];
|
||||
collidesTiles = false;
|
||||
incendAmount = 1;
|
||||
incendSpread = 5;
|
||||
incendChance = 0.4f;
|
||||
lightColor = Color.orange;
|
||||
keepVelocity = false;
|
||||
collides = false;
|
||||
pierce = true;
|
||||
hittable = false;
|
||||
absorbable = false;
|
||||
}
|
||||
|
||||
protected ContinuousLaserBulletType(){
|
||||
this(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float estimateDPS(){
|
||||
//assume firing duration is about 100 by default, may not be accurate there's no way of knowing in this method
|
||||
//assume it pierces 3 blocks/units
|
||||
return damage * 100f / 5f * 3f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return length;
|
||||
@@ -57,7 +67,6 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
|
||||
@Override
|
||||
public void update(Bullet b){
|
||||
//TODO possible laser absorption from blocks
|
||||
|
||||
//damage every 5 ticks
|
||||
if(b.timer(1, 5f)){
|
||||
@@ -77,17 +86,17 @@ public class ContinuousLaserBulletType extends BulletType{
|
||||
|
||||
Lines.lineAngle(b.x, b.y, b.rotation(), baseLen);
|
||||
for(int s = 0; s < colors.length; s++){
|
||||
Draw.color(Tmp.c1.set(colors[s]).mul(1f + Mathf.absin(Time.time(), 1f, 0.1f)));
|
||||
Draw.color(Tmp.c1.set(colors[s]).mul(1f + Mathf.absin(Time.time, 1f, 0.1f)));
|
||||
for(int i = 0; i < tscales.length; i++){
|
||||
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * 35f);
|
||||
Lines.stroke((width + Mathf.absin(Time.time(), oscScl, oscMag)) * fout * strokes[s] * tscales[i]);
|
||||
Tmp.v1.trns(b.rotation() + 180f, (lenscales[i] - 1f) * spaceMag);
|
||||
Lines.stroke((width + Mathf.absin(Time.time, oscScl, oscMag)) * fout * strokes[s] * tscales[i]);
|
||||
Lines.lineAngle(b.x + Tmp.v1.x, b.y + Tmp.v1.y, b.rotation(), baseLen * lenscales[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
Tmp.v1.trns(b.rotation(), baseLen * 1.1f);
|
||||
|
||||
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, 40, lightColor, 0.7f);
|
||||
Drawf.light(b.team, b.x, b.y, b.x + Tmp.v1.x, b.y + Tmp.v1.y, lightStroke, lightColor, 0.7f);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.blocks.*;
|
||||
|
||||
public class HealBulletType extends BulletType{
|
||||
protected float healPercent = 3f;
|
||||
protected float height = 7f, width = 2f;
|
||||
protected Color backColor = Pal.heal, frontColor = Color.white;
|
||||
|
||||
public HealBulletType(float speed, float damage){
|
||||
super(speed, damage);
|
||||
|
||||
shootEffect = Fx.shootHeal;
|
||||
smokeEffect = Fx.hitLaser;
|
||||
hitEffect = Fx.hitLaser;
|
||||
despawnEffect = Fx.hitLaser;
|
||||
collidesTeam = true;
|
||||
hittable = false;
|
||||
}
|
||||
|
||||
public HealBulletType(){
|
||||
this(1f, 1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collides(Bullet b, Building tile){
|
||||
return tile.team != b.team || tile.healthf() < 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
Draw.color(backColor);
|
||||
Lines.stroke(width);
|
||||
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height);
|
||||
Draw.color(frontColor);
|
||||
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height / 2f);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTile(Bullet b, Building tile, float initialHealth){
|
||||
super.hit(b);
|
||||
|
||||
if(tile.team == b.team && !(tile.block instanceof ConstructBlock)){
|
||||
Fx.healBlockFull.at(tile.x, tile.y, tile.block.size, Pal.heal);
|
||||
tile.heal(healPercent / 100f * tile.maxHealth());
|
||||
}
|
||||
}
|
||||
}
|
||||
33
core/src/mindustry/entities/bullet/LaserBoltBulletType.java
Normal file
33
core/src/mindustry/entities/bullet/LaserBoltBulletType.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.graphics.g2d.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.content.*;
|
||||
|
||||
public class LaserBoltBulletType extends BasicBulletType{
|
||||
public float width = 2f, height = 7f;
|
||||
|
||||
public LaserBoltBulletType(float speed, float damage){
|
||||
super(speed, damage);
|
||||
|
||||
smokeEffect = Fx.hitLaser;
|
||||
hitEffect = Fx.hitLaser;
|
||||
despawnEffect = Fx.hitLaser;
|
||||
hittable = false;
|
||||
reflectable = false;
|
||||
}
|
||||
|
||||
public LaserBoltBulletType(){
|
||||
this(1f, 1f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
Draw.color(backColor);
|
||||
Lines.stroke(width);
|
||||
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height);
|
||||
Draw.color(frontColor);
|
||||
Lines.lineAngleCenter(b.x, b.y, b.rotation(), height / 2f);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
@@ -23,22 +23,29 @@ public class LaserBulletType extends BulletType{
|
||||
public LaserBulletType(float damage){
|
||||
super(0.01f, damage);
|
||||
|
||||
keepVelocity = false;
|
||||
hitEffect = Fx.hitLancer;
|
||||
despawnEffect = Fx.none;
|
||||
shootEffect = Fx.hitLancer;
|
||||
smokeEffect = Fx.none;
|
||||
collides = false;
|
||||
hitSize = 4;
|
||||
lifetime = 16f;
|
||||
keepVelocity = false;
|
||||
collides = false;
|
||||
pierce = true;
|
||||
hittable = false;
|
||||
absorbable = false;
|
||||
}
|
||||
|
||||
public LaserBulletType(){
|
||||
this(1f);
|
||||
}
|
||||
|
||||
//assume it pierces at least 3 blocks
|
||||
@Override
|
||||
public float estimateDPS(){
|
||||
return super.estimateDPS() * 3f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
super.init();
|
||||
|
||||
@@ -26,6 +26,11 @@ public class LightningBulletType extends BulletType{
|
||||
return (lightningLength + lightningLengthRand/2f) * 6f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float estimateDPS(){
|
||||
return super.estimateDPS() * Math.max(lightningLength / 10f, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package mindustry.entities.bullet;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
@@ -13,8 +13,9 @@ import mindustry.world.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class LiquidBulletType extends BulletType{
|
||||
public @NonNull Liquid liquid;
|
||||
public Liquid liquid;
|
||||
public float puddleSize = 6f;
|
||||
public float orbSize = 3f;
|
||||
|
||||
public LiquidBulletType(@Nullable Liquid liquid){
|
||||
super(3.5f, 0);
|
||||
@@ -22,10 +23,12 @@ public class LiquidBulletType extends BulletType{
|
||||
if(liquid != null){
|
||||
this.liquid = liquid;
|
||||
this.status = liquid.effect;
|
||||
lightColor = liquid.lightColor;
|
||||
lightOpacity = liquid.lightColor.a;
|
||||
}
|
||||
|
||||
ammoMultiplier = 1f;
|
||||
lifetime = 74f;
|
||||
lifetime = 34f;
|
||||
statusDuration = 60f * 2f;
|
||||
despawnEffect = Fx.none;
|
||||
hitEffect = Fx.hitLiquid;
|
||||
@@ -62,7 +65,7 @@ public class LiquidBulletType extends BulletType{
|
||||
public void draw(Bullet b){
|
||||
Draw.color(liquid.color, Color.white, b.fout() / 100f);
|
||||
|
||||
Fill.circle(b.x, b.y, 3f);
|
||||
Fill.circle(b.x, b.y, orbSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +82,7 @@ public class LiquidBulletType extends BulletType{
|
||||
Puddles.deposit(world.tileWorld(hitx, hity), liquid, puddleSize);
|
||||
|
||||
if(liquid.temperature <= 0.5f && liquid.flammability < 0.3f){
|
||||
float intensity = 400f;
|
||||
float intensity = 400f * puddleSize/6f;
|
||||
Fires.extinguish(world.tileWorld(hitx, hity), intensity);
|
||||
for(Point2 p : Geometry.d4){
|
||||
Fires.extinguish(world.tileWorld(hitx + p.x * tilesize, hity + p.y * tilesize), intensity);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.graphics.Color;
|
||||
import arc.graphics.g2d.Draw;
|
||||
import arc.math.Angles;
|
||||
import arc.math.Mathf;
|
||||
import mindustry.content.Fx;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.Pal;
|
||||
import mindustry.world.blocks.distribution.MassDriver.DriverBulletData;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.blocks.distribution.MassDriver.*;
|
||||
|
||||
import static mindustry.Vars.content;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class MassDriverBolt extends BulletType{
|
||||
|
||||
@@ -37,15 +36,13 @@ public class MassDriverBolt extends BulletType{
|
||||
@Override
|
||||
public void update(Bullet b){
|
||||
//data MUST be an instance of DriverBulletData
|
||||
if(!(b.data() instanceof DriverBulletData)){
|
||||
if(!(b.data() instanceof DriverBulletData data)){
|
||||
hit(b);
|
||||
return;
|
||||
}
|
||||
|
||||
float hitDst = 7f;
|
||||
|
||||
DriverBulletData data = (DriverBulletData)b.data();
|
||||
|
||||
//if the target is dead, just keep flying until the bullet explodes
|
||||
if(data.to.dead()){
|
||||
return;
|
||||
@@ -84,9 +81,7 @@ public class MassDriverBolt extends BulletType{
|
||||
public void despawned(Bullet b){
|
||||
super.despawned(b);
|
||||
|
||||
if(!(b.data() instanceof DriverBulletData)) return;
|
||||
|
||||
DriverBulletData data = (DriverBulletData)b.data();
|
||||
if(!(b.data() instanceof DriverBulletData data)) return;
|
||||
|
||||
for(int i = 0; i < data.items.length; i++){
|
||||
int amountDropped = Mathf.random(0, data.items[i]);
|
||||
|
||||
70
core/src/mindustry/entities/bullet/PointBulletType.java
Normal file
70
core/src/mindustry/entities/bullet/PointBulletType.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class PointBulletType extends BulletType{
|
||||
private static float cdist = 0f;
|
||||
private static Unit result;
|
||||
|
||||
public float trailSpacing = 10f;
|
||||
|
||||
public PointBulletType(){
|
||||
scaleVelocity = true;
|
||||
lifetime = 100f;
|
||||
collides = false;
|
||||
keepVelocity = false;
|
||||
backMove = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
float px = b.x + b.lifetime * b.vel.x,
|
||||
py = b.y + b.lifetime * b.vel.y,
|
||||
rot = b.rotation();
|
||||
|
||||
Geometry.iterateLine(0f, b.x, b.y, px, py, trailSpacing, (x, y) -> {
|
||||
trailEffect.at(x, y, rot);
|
||||
});
|
||||
|
||||
b.time = b.lifetime;
|
||||
b.set(px, py);
|
||||
|
||||
//calculate hit entity
|
||||
|
||||
cdist = 0f;
|
||||
result = null;
|
||||
float range = 1f;
|
||||
|
||||
Units.nearbyEnemies(b.team, px - range, py - range, range*2f, range*2f, e -> {
|
||||
if(e.dead()) return;
|
||||
|
||||
e.hitbox(Tmp.r1);
|
||||
if(!Tmp.r1.contains(px, py)) return;
|
||||
|
||||
float dst = e.dst(px, py) - e.hitSize;
|
||||
if((result == null || dst < cdist)){
|
||||
result = e;
|
||||
cdist = dst;
|
||||
}
|
||||
});
|
||||
|
||||
if(result != null){
|
||||
b.collision(result, px, py);
|
||||
}else{
|
||||
Building build = Vars.world.buildWorld(px, py);
|
||||
if(build != null && build.team != b.team){
|
||||
build.collision(b);
|
||||
}
|
||||
}
|
||||
|
||||
b.remove();
|
||||
|
||||
b.vel.setZero();
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,80 @@
|
||||
package mindustry.entities.bullet;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
//TODO this class is bad for multiple reasons, remove/replace it.
|
||||
//- effects unreliable
|
||||
//- not really hitscan but works like it
|
||||
//- buggy trails
|
||||
//- looks bad
|
||||
//- generally unreliable
|
||||
public class RailBulletType extends BulletType{
|
||||
public Effect pierceEffect = Fx.hitBulletSmall, updateEffect = Fx.none;
|
||||
/** Multiplier of damage decreased per health pierced. */
|
||||
public float pierceDamageFactor = 1f;
|
||||
|
||||
public float length = 100f;
|
||||
|
||||
public float updateEffectSeg = 20f;
|
||||
|
||||
public RailBulletType(){
|
||||
pierceBuilding = true;
|
||||
pierce = true;
|
||||
reflectable = false;
|
||||
hitEffect = Fx.none;
|
||||
despawnEffect = Fx.none;
|
||||
collides = false;
|
||||
lifetime = 1f;
|
||||
speed = 0.01f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float range(){
|
||||
return length;
|
||||
}
|
||||
|
||||
void handle(Bullet b, Posc pos, float initialHealth){
|
||||
float sub = initialHealth*pierceDamageFactor;
|
||||
float sub = Math.max(initialHealth*pierceDamageFactor, 0);
|
||||
|
||||
if(sub >= b.damage){
|
||||
//cause a despawn
|
||||
b.remove();
|
||||
if(b.damage <= 0){
|
||||
b.fdata = Math.min(b.fdata, b.dst(pos));
|
||||
return;
|
||||
}
|
||||
|
||||
if(b.damage > 0){
|
||||
pierceEffect.at(pos.getX(), pos.getY(), b.rotation());
|
||||
|
||||
hitEffect.at(pos.getX(), pos.getY());
|
||||
}
|
||||
|
||||
//subtract health from each consecutive pierce
|
||||
b.damage -= Math.min(b.damage, sub);
|
||||
|
||||
if(b.damage > 0){
|
||||
pierceEffect.at(pos.getX(), pos.getY(), b.rotation());
|
||||
}
|
||||
|
||||
hitEffect.at(pos.getX(), pos.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Bullet b){
|
||||
if(b.timer(1, 0.9f)){
|
||||
updateEffect.at(b.x, b.y, b.rotation());
|
||||
public void init(Bullet b){
|
||||
super.init(b);
|
||||
|
||||
b.fdata = length;
|
||||
Damage.collideLine(b, b.team, b.type.hitEffect, b.x, b.y, b.rotation(), length, false);
|
||||
float resultLen = b.fdata;
|
||||
|
||||
Vec2 nor = Tmp.v1.set(b.vel).nor();
|
||||
for(float i = 0; i <= resultLen; i += updateEffectSeg){
|
||||
updateEffect.at(b.x + nor.x * i, b.y + nor.y * i, b.rotation());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testCollision(Bullet bullet, Building tile){
|
||||
return bullet.team != tile.team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitEntity(Bullet b, Hitboxc entity, float initialHealth){
|
||||
handle(b, entity, initialHealth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitTile(Bullet b, Building tile, float initialHealth){
|
||||
handle(b, tile, initialHealth);
|
||||
public void hitTile(Bullet b, Building build, float initialHealth, boolean direct){
|
||||
handle(b, build, initialHealth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ public class SapBulletType extends BulletType{
|
||||
public SapBulletType(){
|
||||
speed = 0.0001f;
|
||||
despawnEffect = Fx.none;
|
||||
pierce = true;
|
||||
pierce = false;
|
||||
collides = false;
|
||||
hitSize = 0f;
|
||||
hittable = false;
|
||||
hitEffect = Fx.hitLiquid;
|
||||
@@ -29,8 +30,7 @@ public class SapBulletType extends BulletType{
|
||||
|
||||
@Override
|
||||
public void draw(Bullet b){
|
||||
if(b.data instanceof Position){
|
||||
Position data = (Position)b.data;
|
||||
if(b.data instanceof Position data){
|
||||
Tmp.v1.set(data).lerp(b, b.fin());
|
||||
|
||||
Draw.color(color);
|
||||
@@ -61,21 +61,17 @@ public class SapBulletType extends BulletType{
|
||||
b.data = target;
|
||||
|
||||
if(target != null){
|
||||
float result = Math.min(target.health(), damage);
|
||||
float result = Math.max(Math.min(target.health(), damage), 0);
|
||||
|
||||
if(b.owner instanceof Healthc){
|
||||
((Healthc)b.owner).heal(result * sapStrength);
|
||||
if(b.owner instanceof Healthc h){
|
||||
h.heal(result * sapStrength);
|
||||
}
|
||||
}
|
||||
|
||||
if(target instanceof Hitboxc){
|
||||
Hitboxc hit = (Hitboxc)target;
|
||||
|
||||
if(target instanceof Hitboxc hit){
|
||||
hit.collision(b, hit.x(), hit.y());
|
||||
b.collision(hit, hit.x(), hit.y());
|
||||
}else if(target instanceof Building){
|
||||
Building tile = (Building)target;
|
||||
|
||||
}else if(target instanceof Building tile){
|
||||
if(tile.collide(b)){
|
||||
tile.collision(b);
|
||||
hit(b, tile.x, tile.y);
|
||||
|
||||
@@ -24,9 +24,11 @@ public class ShrapnelBulletType extends BulletType{
|
||||
shootEffect = smokeEffect = Fx.lightningShoot;
|
||||
lifetime = 10f;
|
||||
despawnEffect = Fx.none;
|
||||
pierce = true;
|
||||
keepVelocity = false;
|
||||
collides = false;
|
||||
pierce = true;
|
||||
hittable = false;
|
||||
absorbable = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
28
core/src/mindustry/entities/comp/AmmoDistributeComp.java
Normal file
28
core/src/mindustry/entities/comp/AmmoDistributeComp.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.blocks.units.*;
|
||||
|
||||
@Component
|
||||
abstract class AmmoDistributeComp implements Unitc{
|
||||
@Import float x, y;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
@Import float ammo;
|
||||
|
||||
private transient float ammoCooldown;
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(ammoCooldown > 0f) ammoCooldown -= Time.delta;
|
||||
|
||||
if(ammo > 0 && ammoCooldown <= 0f && ResupplyPoint.resupply(team, x, y, type.ammoResupplyRange, Math.min(type.ammoResupplyAmount, ammo), type.ammoType.color, u -> u != self())){
|
||||
ammo -= Math.min(type.ammoResupplyAmount, ammo);
|
||||
ammoCooldown = 5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,13 @@ abstract class BoundedComp implements Velc, Posc, Healthc, Flyingc{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
//repel unit out of bounds
|
||||
if(x < 0) vel.x += (-x/warpDst);
|
||||
if(y < 0) vel.y += (-y/warpDst);
|
||||
if(x > world.unitWidth()) vel.x -= (x - world.unitWidth())/warpDst;
|
||||
if(y > world.unitHeight()) vel.y -= (y - world.unitHeight())/warpDst;
|
||||
if(!net.client() || isLocal()){
|
||||
//repel unit out of bounds
|
||||
if(x < 0) vel.x += (-x/warpDst);
|
||||
if(y < 0) vel.y += (-y/warpDst);
|
||||
if(x > world.unitWidth()) vel.x -= (x - world.unitWidth())/warpDst;
|
||||
if(y > world.unitHeight()) vel.y -= (y - world.unitHeight())/warpDst;
|
||||
}
|
||||
|
||||
//clamp position if not flying
|
||||
if(isGrounded()){
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.Queue;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
@@ -23,20 +25,38 @@ import java.util.*;
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class BuilderComp implements Unitc{
|
||||
abstract class BuilderComp implements Posc, Teamc, Rotc{
|
||||
static final Vec2[] vecs = new Vec2[]{new Vec2(), new Vec2(), new Vec2(), new Vec2()};
|
||||
|
||||
@Import float x, y, rotation;
|
||||
@Import UnitType type;
|
||||
@Import Team team;
|
||||
|
||||
@SyncLocal Queue<BuildPlan> plans = new Queue<>();
|
||||
@SyncLocal transient boolean updateBuilding = true;
|
||||
@SyncLocal Queue<BuildPlan> plans = new Queue<>(1);
|
||||
@SyncLocal boolean updateBuilding = true;
|
||||
|
||||
private transient BuildPlan lastActive;
|
||||
private transient int lastSize;
|
||||
private transient float buildAlpha = 0f;
|
||||
|
||||
public boolean canBuild(){
|
||||
return type.buildSpeed > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(!updateBuilding) return;
|
||||
if(!headless){
|
||||
//visual activity update
|
||||
if(lastActive != null && buildAlpha <= 0.01f){
|
||||
lastActive = null;
|
||||
}
|
||||
|
||||
buildAlpha = Mathf.lerpDelta(buildAlpha, activelyBuilding() ? 1f : 0f, 0.15f);
|
||||
}
|
||||
|
||||
if(!updateBuilding || !canBuild()) return;
|
||||
|
||||
float finalPlaceDst = state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange;
|
||||
|
||||
boolean infinite = state.rules.infiniteResources || team().rules().infiniteResources;
|
||||
|
||||
Iterator<BuildPlan> it = plans.iterator();
|
||||
@@ -57,7 +77,7 @@ abstract class BuilderComp implements Unitc{
|
||||
if(plans.size > 1){
|
||||
int total = 0;
|
||||
BuildPlan req;
|
||||
while((dst((req = buildPlan()).tile()) > finalPlaceDst || shouldSkip(req, core)) && total < plans.size){
|
||||
while((!within((req = buildPlan()).tile(), finalPlaceDst) || shouldSkip(req, core)) && total < plans.size){
|
||||
plans.removeFirst();
|
||||
plans.addLast(req);
|
||||
total++;
|
||||
@@ -65,82 +85,88 @@ abstract class BuilderComp implements Unitc{
|
||||
}
|
||||
|
||||
BuildPlan current = buildPlan();
|
||||
Tile tile = current.tile();
|
||||
|
||||
if(!within(current.tile(), finalPlaceDst)) return;
|
||||
lastActive = current;
|
||||
buildAlpha = 1f;
|
||||
if(current.breaking) lastSize = tile.block().size;
|
||||
|
||||
Tile tile = world.tile(current.x, current.y);
|
||||
if(!within(tile, finalPlaceDst)) return;
|
||||
|
||||
if(within(tile, finalPlaceDst)){
|
||||
lookAt(angleTo(tile));
|
||||
}
|
||||
|
||||
if(!(tile.block() instanceof ConstructBlock)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team(), current.x, current.y, current.rotation)){
|
||||
boolean hasAll = infinite || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
|
||||
if(!(tile.build instanceof ConstructBuild cb)){
|
||||
if(!current.initialized && !current.breaking && Build.validPlace(current.block, team, current.x, current.y, current.rotation)){
|
||||
boolean hasAll = infinite || current.isRotation(team) || !Structs.contains(current.block.requirements, i -> core != null && !core.items.has(i.item));
|
||||
|
||||
if(hasAll){
|
||||
Call.beginPlace(current.block, team(), current.x, current.y, current.rotation);
|
||||
Call.beginPlace(self(), current.block, team, current.x, current.y, current.rotation);
|
||||
}else{
|
||||
current.stuck = true;
|
||||
}
|
||||
}else if(!current.initialized && current.breaking && Build.validBreak(team(), current.x, current.y)){
|
||||
Call.beginBreak(team(), current.x, current.y);
|
||||
}else if(!current.initialized && current.breaking && Build.validBreak(team, current.x, current.y)){
|
||||
Call.beginBreak(self(), team, current.x, current.y);
|
||||
}else{
|
||||
plans.removeFirst();
|
||||
return;
|
||||
}
|
||||
}else if(tile.team() != team()){
|
||||
}else if((tile.team() != team && tile.team() != Team.derelict) || (!current.breaking && cb.cblock != current.block)){
|
||||
plans.removeFirst();
|
||||
return;
|
||||
}
|
||||
|
||||
if(tile.build instanceof ConstructBuild && !current.initialized){
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team(), (Builderc)this, current.breaking)));
|
||||
Core.app.post(() -> Events.fire(new BuildSelectEvent(tile, team, self(), current.breaking)));
|
||||
current.initialized = true;
|
||||
}
|
||||
|
||||
//if there is no core to build with or no build entity, stop building!
|
||||
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild)){
|
||||
if((core == null && !infinite) || !(tile.build instanceof ConstructBuild entity)){
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise, update it.
|
||||
ConstructBuild entity = tile.bc();
|
||||
|
||||
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 * 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 * state.rules.buildSpeedMultiplier, current.config);
|
||||
}
|
||||
|
||||
current.stuck = Mathf.equal(current.progress, entity.progress);
|
||||
current.progress = entity.progress;
|
||||
}
|
||||
|
||||
/** Draw all current build plans. Does not draw the beam effect, only the positions. */
|
||||
void drawBuildPlans(){
|
||||
|
||||
/** Draw all current build requests. Does not draw the beam effect, only the positions. */
|
||||
void drawBuildRequests(){
|
||||
|
||||
for(BuildPlan request : plans){
|
||||
if(request.progress > 0.01f || (buildPlan() == request && request.initialized && (dst(request.x * tilesize, request.y * tilesize) <= buildingRange || state.isEditor()))) continue;
|
||||
|
||||
request.animScale = 1f;
|
||||
if(request.breaking){
|
||||
control.input.drawBreaking(request);
|
||||
}else{
|
||||
request.block.drawRequest(request, control.input.allRequests(),
|
||||
Build.validPlace(request.block, team(), request.x, request.y, request.rotation) || control.input.requestMatches(request));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
void drawPlan(BuildPlan request, float alpha){
|
||||
request.animScale = 1f;
|
||||
if(request.breaking){
|
||||
control.input.drawBreaking(request);
|
||||
}else{
|
||||
request.block.drawPlan(request, control.input.allRequests(),
|
||||
Build.validPlace(request.block, team, request.x, request.y, request.rotation) || control.input.requestMatches(request),
|
||||
alpha);
|
||||
|
||||
Draw.reset();
|
||||
Draw.mixcol(Color.white, 0.24f + Mathf.absin(Time.globalTime, 6f, 0.28f));
|
||||
Draw.alpha(alpha);
|
||||
request.block.drawRequestConfigTop(request, plans);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return whether this request should be skipped, in favor of the next one. */
|
||||
boolean shouldSkip(BuildPlan request, @Nullable Building core){
|
||||
//requests that you have at least *started* are considered
|
||||
if(state.rules.infiniteResources || team().rules().infiniteResources || request.breaking || core == null) return false;
|
||||
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item)) && !request.initialized);
|
||||
if(state.rules.infiniteResources || team.rules().infiniteResources || request.breaking || core == null || request.isRotation(team)) return false;
|
||||
|
||||
return (request.stuck && !core.items.has(request.block.requirements)) || (Structs.contains(request.block.requirements, i -> !core.items.has(i.item) && Mathf.round(i.amount * state.rules.buildCostMultiplier) > 0) && !request.initialized);
|
||||
}
|
||||
|
||||
void removeBuild(int x, int y, boolean breaking){
|
||||
@@ -168,6 +194,8 @@ abstract class BuilderComp implements Unitc{
|
||||
|
||||
/** Add another build requests to the queue, if it doesn't exist there yet. */
|
||||
void addBuild(BuildPlan place, boolean tail){
|
||||
if(!canBuild()) return;
|
||||
|
||||
BuildPlan replace = null;
|
||||
for(BuildPlan request : plans){
|
||||
if(request.x == place.x && request.y == place.y){
|
||||
@@ -179,8 +207,8 @@ abstract class BuilderComp implements Unitc{
|
||||
plans.remove(replace);
|
||||
}
|
||||
Tile tile = world.tile(place.x, place.y);
|
||||
if(tile != null && tile.build instanceof ConstructBuild){
|
||||
place.progress = tile.<ConstructBuild>bc().progress;
|
||||
if(tile != null && tile.build instanceof ConstructBuild cons){
|
||||
place.progress = cons.progress;
|
||||
}
|
||||
if(tail){
|
||||
plans.addLast(place);
|
||||
@@ -190,6 +218,12 @@ abstract class BuilderComp implements Unitc{
|
||||
}
|
||||
|
||||
boolean activelyBuilding(){
|
||||
//not actively building when not near the build plan
|
||||
if(isBuilding()){
|
||||
if(!state.isEditor() && !within(buildPlan(), state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isBuilding() && updateBuilding;
|
||||
}
|
||||
|
||||
@@ -199,25 +233,32 @@ abstract class BuilderComp implements Unitc{
|
||||
return plans.size == 0 ? null : plans.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(){
|
||||
if(!isBuilding() || !updateBuilding) return;
|
||||
boolean active = activelyBuilding();
|
||||
if(!active && lastActive == null) return;
|
||||
|
||||
//TODO check correctness
|
||||
Draw.z(Layer.flyingUnit);
|
||||
|
||||
BuildPlan plan = buildPlan();
|
||||
BuildPlan plan = active ? buildPlan() : lastActive;
|
||||
Tile tile = world.tile(plan.x, plan.y);
|
||||
var core = team.core();
|
||||
|
||||
if(dst(tile) > buildingRange && !state.isEditor()){
|
||||
if(tile == null || !within(plan, state.rules.infiniteResources ? Float.MAX_VALUE : buildingRange)){
|
||||
return;
|
||||
}
|
||||
|
||||
int size = plan.breaking ? tile.block().size : plan.block.size;
|
||||
//draw remote plans.
|
||||
if(core != null && active && !isLocal() && !(tile.block() instanceof ConstructBlock)){
|
||||
Draw.z(Layer.plans - 1f);
|
||||
drawPlan(plan, 0.5f);
|
||||
Draw.z(Layer.flyingUnit);
|
||||
}
|
||||
|
||||
int size = plan.breaking ? active ? tile.block().size : lastSize : plan.block.size;
|
||||
float tx = plan.drawx(), ty = plan.drawy();
|
||||
|
||||
Lines.stroke(1f, Pal.accent);
|
||||
float focusLen = 3.8f + Mathf.absin(Time.time(), 1.1f, 0.6f);
|
||||
Lines.stroke(1f, plan.breaking ? Pal.remove : Pal.accent);
|
||||
float focusLen = type.buildBeamOffset + Mathf.absin(Time.time, 3f, 0.6f);
|
||||
float px = x + Angles.trnsx(rotation, focusLen);
|
||||
float py = y + Angles.trnsy(rotation, focusLen);
|
||||
|
||||
@@ -231,16 +272,35 @@ abstract class BuilderComp implements Unitc{
|
||||
|
||||
Arrays.sort(vecs, Structs.comparingFloat(vec -> -Angles.angleDist(angleTo(vec), ang)));
|
||||
|
||||
Vec2 close = Geometry.findClosest(x, y, vecs);
|
||||
|
||||
float x1 = vecs[0].x, y1 = vecs[0].y,
|
||||
x2 = close.x, y2 = close.y,
|
||||
x3 = vecs[1].x, y3 = vecs[1].y;
|
||||
|
||||
Draw.alpha(1f);
|
||||
Draw.z(Layer.buildBeam);
|
||||
|
||||
Lines.line(px, py, x1, y1);
|
||||
Lines.line(px, py, x3, y3);
|
||||
Draw.alpha(buildAlpha);
|
||||
|
||||
Fill.circle(px, py, 1.6f + Mathf.absin(Time.time(), 0.8f, 1.5f));
|
||||
if(!active && !(tile.build instanceof ConstructBuild)){
|
||||
Fill.square(plan.drawx(), plan.drawy(), size * tilesize/2f);
|
||||
}
|
||||
|
||||
Draw.color();
|
||||
if(renderer.animateShields){
|
||||
if(close != vecs[0] && close != vecs[1]){
|
||||
Fill.tri(px, py, x1, y1, x2, y2);
|
||||
Fill.tri(px, py, x3, y3, x2, y2);
|
||||
}else{
|
||||
Fill.tri(px, py, x1, y1, x3, y3);
|
||||
}
|
||||
}else{
|
||||
Lines.line(px, py, x1, y1);
|
||||
Lines.line(px, py, x3, y3);
|
||||
}
|
||||
|
||||
Fill.square(px, py, 1.8f + Mathf.absin(Time.time, 2.2f, 1.1f), rotation + 45);
|
||||
|
||||
Draw.reset();
|
||||
Draw.z(Layer.flyingUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,25 @@ import arc.scene.ui.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.io.*;
|
||||
import mindustry.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.audio.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.logic.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.ConstructBlock.*;
|
||||
import mindustry.world.blocks.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
import mindustry.world.blocks.payloads.*;
|
||||
import mindustry.world.blocks.power.*;
|
||||
@@ -47,17 +51,18 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
static final Seq<Tile> tempTiles = new Seq<>();
|
||||
static int sleepingEntities = 0;
|
||||
|
||||
@Import float x, y, health;
|
||||
@Import float x, y, health, maxHealth;
|
||||
@Import Team team;
|
||||
|
||||
transient Tile tile;
|
||||
transient Block block;
|
||||
transient Seq<Building> proximity = new Seq<>(8);
|
||||
transient boolean updateFlow;
|
||||
transient byte dump;
|
||||
transient byte cdump;
|
||||
transient int rotation;
|
||||
transient boolean enabled = true;
|
||||
transient float enabledControlTime;
|
||||
transient String lastAccessed;
|
||||
|
||||
PowerModule power;
|
||||
ItemModule items;
|
||||
@@ -79,8 +84,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}else{
|
||||
if(block.hasPower){
|
||||
//reinit power graph
|
||||
power.graph = new PowerGraph();
|
||||
power.graph.add(self());
|
||||
new PowerGraph().add(self());
|
||||
}
|
||||
}
|
||||
this.rotation = rotation;
|
||||
@@ -103,8 +107,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
this.block = block;
|
||||
this.team = team;
|
||||
|
||||
if(block.activeSound != Sounds.none){
|
||||
sound = new SoundLoop(block.activeSound, block.activeSoundVolume);
|
||||
if(block.loopSound != Sounds.none){
|
||||
sound = new SoundLoop(block.loopSound, block.loopSoundVolume);
|
||||
}
|
||||
|
||||
health = block.health;
|
||||
@@ -191,6 +195,39 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//endregion
|
||||
//region utility methods
|
||||
|
||||
public void addPlan(boolean checkPrevious){
|
||||
if(!block.rebuildable || (team == state.rules.defaultTeam && state.isCampaign() && !block.isVisible())) return;
|
||||
|
||||
Object overrideConfig = null;
|
||||
|
||||
if(self() instanceof ConstructBuild entity){
|
||||
//update block to reflect the fact that something was being constructed
|
||||
if(entity.cblock != null && entity.cblock.synthetic() && entity.wasConstructing){
|
||||
block = entity.cblock;
|
||||
overrideConfig = entity.lastConfig;
|
||||
}else{
|
||||
//otherwise this was a deconstruction that was interrupted, don't want to rebuild that
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TeamData data = state.teams.get(team);
|
||||
|
||||
if(checkPrevious){
|
||||
//remove existing blocks that have been placed here.
|
||||
//painful O(n) iteration + copy
|
||||
for(int i = 0; i < data.blocks.size; i++){
|
||||
BlockPlan b = data.blocks.get(i);
|
||||
if(b.x == tile.x && b.y == tile.y){
|
||||
data.blocks.removeIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.blocks.addFirst(new BlockPlan(tile.x, tile.y, (short)rotation, block.id, overrideConfig == null ? config() : overrideConfig));
|
||||
}
|
||||
|
||||
/** Configure with the current, local player. */
|
||||
public void configure(Object value){
|
||||
//save last used config
|
||||
@@ -216,9 +253,12 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applyBoost(float intensity, float duration){
|
||||
public void applyBoost(float intensity, float duration){
|
||||
//do not refresh time scale when getting a weaker intensity
|
||||
if(intensity >= this.timeScale){
|
||||
timeScaleDuration = Math.max(timeScaleDuration, duration);
|
||||
}
|
||||
timeScale = Math.max(timeScale, intensity);
|
||||
timeScaleDuration = Math.max(timeScaleDuration, duration);
|
||||
}
|
||||
|
||||
public Building nearby(int dx, int dy){
|
||||
@@ -318,6 +358,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return power != null && (block.consumes.has(ConsumeType.power) && !block.consumes.getPower().buffered) ? power.status : 1f;
|
||||
}
|
||||
|
||||
public BlockStatus status(){
|
||||
return cons.status();
|
||||
}
|
||||
|
||||
/** Call when nothing is happening to the entity. This increments the internal sleep timer. */
|
||||
public void sleep(){
|
||||
sleepTime += Time.delta;
|
||||
@@ -346,6 +390,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
//endregion
|
||||
//region handler methods
|
||||
|
||||
public boolean canUnload(){
|
||||
return block.unloadable;
|
||||
}
|
||||
|
||||
/** Called when an unloader takes an item. */
|
||||
public void itemTaken(Item item){
|
||||
|
||||
}
|
||||
|
||||
/** Called when this block is dropped as a payload. */
|
||||
public void dropped(){
|
||||
|
||||
@@ -393,7 +446,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** Handle a stack input. */
|
||||
public void handleStack(Item item, int amount, Teamc source){
|
||||
public void handleStack(Item item, int amount, @Nullable Teamc source){
|
||||
noSleep();
|
||||
items.add(item, amount);
|
||||
}
|
||||
@@ -421,9 +474,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* @param todump payload to dump.
|
||||
* @return whether the payload was moved successfully
|
||||
*/
|
||||
public boolean movePayload(@NonNull Payload todump){
|
||||
public boolean movePayload(Payload todump){
|
||||
int trns = block.size/2 + 1;
|
||||
Tile next = tile.getNearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
|
||||
Tile next = tile.nearby(Geometry.d4(rotation).x * trns, Geometry.d4(rotation).y * trns);
|
||||
|
||||
if(next != null && next.build != null && next.build.team == team && next.build.acceptPayload(self(), todump)){
|
||||
next.build.handlePayload(self(), todump);
|
||||
@@ -438,10 +491,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* @param todump payload to dump.
|
||||
* @return whether the payload was moved successfully
|
||||
*/
|
||||
public boolean dumpPayload(@NonNull Payload todump){
|
||||
public boolean dumpPayload(Payload todump){
|
||||
if(proximity.size == 0) return false;
|
||||
|
||||
int dump = this.dump;
|
||||
int dump = this.cdump;
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
Building other = proximity.get((i + dump) % proximity.size);
|
||||
@@ -466,8 +519,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return block.consumes.itemFilters.get(item.id) && items.get(item) < getMaximumAccepted(item);
|
||||
}
|
||||
|
||||
public boolean acceptLiquid(Building source, Liquid liquid, float amount){
|
||||
return block.hasLiquids && liquids.get(liquid) + amount < block.liquidCapacity && block.consumes.liquidfilters.get(liquid.id);
|
||||
public boolean acceptLiquid(Building source, Liquid liquid){
|
||||
return block.hasLiquids && block.consumes.liquidfilters.get(liquid.id);
|
||||
}
|
||||
|
||||
public void handleLiquid(Building source, Liquid liquid, float amount){
|
||||
@@ -475,7 +528,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void dumpLiquid(Liquid liquid){
|
||||
int dump = this.dump;
|
||||
int dump = this.cdump;
|
||||
|
||||
if(liquids.get(liquid) <= 0.0001f) return;
|
||||
|
||||
if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) liquid.unlock();
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
@@ -497,23 +554,23 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void transferLiquid(Building next, float amount, Liquid liquid){
|
||||
float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f, amount);
|
||||
float flow = Math.min(next.block.liquidCapacity - next.liquids.get(liquid), amount);
|
||||
|
||||
if(next.acceptLiquid(self(), liquid, flow)){
|
||||
if(next.acceptLiquid(self(), liquid)){
|
||||
next.handleLiquid(self(), liquid, flow);
|
||||
liquids.remove(liquid, flow);
|
||||
}
|
||||
}
|
||||
|
||||
public float moveLiquidForward(float leakResistance, Liquid liquid){
|
||||
Tile next = tile.getNearby(rotation);
|
||||
public float moveLiquidForward(boolean leaks, Liquid liquid){
|
||||
Tile next = tile.nearby(rotation);
|
||||
|
||||
if(next == null) return 0;
|
||||
|
||||
if(next.build != null){
|
||||
return moveLiquid(next.build, liquid);
|
||||
}else if(leakResistance != 100f && !next.block().solid && !next.block().hasLiquids){
|
||||
float leakAmount = liquids.get(liquid) / leakResistance;
|
||||
}else if(leaks && !next.block().solid && !next.block().hasLiquids){
|
||||
float leakAmount = liquids.get(liquid) / 1.5f;
|
||||
Puddles.deposit(next, tile, liquid, leakAmount);
|
||||
liquids.remove(liquid, leakAmount);
|
||||
}
|
||||
@@ -528,10 +585,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
if(next.team == team && next.block.hasLiquids && liquids.get(liquid) > 0f){
|
||||
float ofract = next.liquids.get(liquid) / next.block.liquidCapacity;
|
||||
float fract = liquids.get(liquid) / block.liquidCapacity * block.liquidPressure;
|
||||
float flow = Math.min(Mathf.clamp((fract - ofract) * (1f)) * (block.liquidCapacity), liquids.get(liquid));
|
||||
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid) - 0.001f);
|
||||
float flow = Math.min(Mathf.clamp((fract - ofract)) * (block.liquidCapacity), liquids.get(liquid));
|
||||
flow = Math.min(flow, next.block.liquidCapacity - next.liquids.get(liquid));
|
||||
|
||||
if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid, flow)){
|
||||
if(flow > 0f && ofract <= fract && next.acceptLiquid(self(), liquid)){
|
||||
next.handleLiquid(self(), liquid, flow);
|
||||
liquids.remove(liquid, flow);
|
||||
return flow;
|
||||
@@ -575,7 +632,9 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* containers, it gets added to the block's inventory.
|
||||
*/
|
||||
public void offload(Item item){
|
||||
int dump = this.dump;
|
||||
produced(item, 1);
|
||||
int dump = this.cdump;
|
||||
if(!net.client() && state.isCampaign() && team == state.rules.defaultTeam) item.unlock();
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
@@ -593,7 +652,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
* Tries to put this item into a nearby container. Returns success. Unlike #offload(), this method does not change the block inventory.
|
||||
*/
|
||||
public boolean put(Item item){
|
||||
int dump = this.dump;
|
||||
int dump = this.cdump;
|
||||
|
||||
for(int i = 0; i < proximity.size; i++){
|
||||
incrementDump(proximity.size);
|
||||
@@ -607,6 +666,14 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return false;
|
||||
}
|
||||
|
||||
public void produced(Item item){
|
||||
produced(item, 1);
|
||||
}
|
||||
|
||||
public void produced(Item item, int amount){
|
||||
if(Vars.state.rules.sector != null && team == state.rules.defaultTeam) Vars.state.rules.sector.info.handleProduction(item, amount);
|
||||
}
|
||||
|
||||
/** Try dumping any item near the */
|
||||
public boolean dump(){
|
||||
return dump(null);
|
||||
@@ -619,7 +686,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public boolean dump(Item todump){
|
||||
if(!block.hasItems || items.total() == 0 || (todump != null && !items.has(todump))) return false;
|
||||
|
||||
int dump = this.dump;
|
||||
int dump = this.cdump;
|
||||
|
||||
if(proximity.size == 0) return false;
|
||||
|
||||
@@ -654,7 +721,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void incrementDump(int prox){
|
||||
dump = (byte)((dump + 1) % prox);
|
||||
cdump = (byte)((cdump + 1) % prox);
|
||||
}
|
||||
|
||||
/** Used for dumping items. */
|
||||
@@ -692,9 +759,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
public void powerGraphRemoved(){
|
||||
if(power == null){
|
||||
return;
|
||||
}
|
||||
if(power == null) return;
|
||||
|
||||
power.graph.remove(self());
|
||||
for(int i = 0; i < power.links.size; i++){
|
||||
@@ -739,7 +804,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
|
||||
/** @return whether this block should play its idle sound.*/
|
||||
public boolean shouldIdleSound(){
|
||||
public boolean shouldAmbientSound(){
|
||||
return shouldConsume();
|
||||
}
|
||||
|
||||
@@ -751,16 +816,16 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
Draw.z(Layer.power + 1);
|
||||
Draw.color(Pal.gray);
|
||||
Fill.square(brcx, brcy, 2.5f, 45);
|
||||
Draw.color(cons.status().color);
|
||||
Draw.color(status().color);
|
||||
Fill.square(brcx, brcy, 1.5f, 45);
|
||||
Draw.color();
|
||||
}
|
||||
}
|
||||
|
||||
public void drawCracks(){
|
||||
if(!damaged() || block.size > Block.maxCrackSize) return;
|
||||
if(!damaged() || block.size > BlockRenderer.maxCrackSize) return;
|
||||
int id = pos();
|
||||
TextureRegion region = Block.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * Block.crackRegions), 0, Block.crackRegions-1)];
|
||||
TextureRegion region = renderer.blocks.cracks[block.size - 1][Mathf.clamp((int)((1f - healthf()) * BlockRenderer.crackRegions), 0, BlockRenderer.crackRegions-1)];
|
||||
Draw.colorl(0.2f, 0.1f + (1f - healthf())* 0.6f);
|
||||
Draw.rect(region, x, y, (id%4)*90);
|
||||
Draw.color();
|
||||
@@ -828,7 +893,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
public void placed(){
|
||||
if(net.client()) return;
|
||||
|
||||
if((block.consumesPower && !block.outputsPower) || (!block.consumesPower && block.outputsPower)){
|
||||
if(block.consumesPower || block.outputsPower){
|
||||
int range = 10;
|
||||
tempTiles.clear();
|
||||
Geometry.circle(tileX(), tileY(), range, (x, y) -> {
|
||||
@@ -849,6 +914,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a block is placed over some other blocks. This seq will always have at least one item.
|
||||
* Should load some previous state, if necessary. */
|
||||
public void overwrote(Seq<Building> previous){
|
||||
|
||||
}
|
||||
|
||||
public void onRemoved(){
|
||||
}
|
||||
|
||||
@@ -863,7 +935,15 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
/** Called when arbitrary configuration is applied to a tile. */
|
||||
public void configured(@Nullable Unit builder, @Nullable Object value){
|
||||
//null is of type void.class; anonymous classes use their superclass.
|
||||
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() ? value.getClass().getSuperclass() : value.getClass();
|
||||
Class<?> type = value == null ? void.class : value.getClass().isAnonymousClass() || value.getClass().getSimpleName().startsWith("adapter") ? value.getClass().getSuperclass() : value.getClass();
|
||||
|
||||
if(value instanceof Item) type = Item.class;
|
||||
if(value instanceof Block) type = Block.class;
|
||||
if(value instanceof Liquid) type = Liquid.class;
|
||||
|
||||
if(builder != null && builder.isPlayer()){
|
||||
lastAccessed = builder.getPlayer().name;
|
||||
}
|
||||
|
||||
if(block.configurations.containsKey(type)){
|
||||
block.configurations.get(type).get(this, value);
|
||||
@@ -914,34 +994,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
});
|
||||
}
|
||||
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, Pal.darkFlame, state.rules.damageExplosions);
|
||||
Damage.dynamicExplosion(x, y, flammability, explosiveness * 3.5f, power, tilesize * block.size / 2f, state.rules.damageExplosions);
|
||||
|
||||
if(!floor().solid && !floor().isLiquid){
|
||||
Effect.rubble(x, y, block.size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the flammability of the Used for fire calculations.
|
||||
* Takes flammability of floor liquid into account.
|
||||
*/
|
||||
public float getFlammability(){
|
||||
if(!block.hasItems){
|
||||
if(floor().isLiquid && !block.solid){
|
||||
return floor().liquidDrop.flammability;
|
||||
}
|
||||
return 0;
|
||||
}else{
|
||||
float result = items.sum((item, amount) -> item.flammability * amount);
|
||||
|
||||
if(block.hasLiquids){
|
||||
result += liquids.sum((liquid, amount) -> liquid.flammability * amount / 3f);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplayName(){
|
||||
return block.localizedName;
|
||||
}
|
||||
@@ -1029,6 +1088,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
if(net.active() && lastAccessed != null){
|
||||
table.row();
|
||||
table.add(Core.bundle.format("lastaccessed", lastAccessed)).growX().wrap().left();
|
||||
}
|
||||
|
||||
table.marginBottom(-5);
|
||||
}
|
||||
}
|
||||
@@ -1043,8 +1107,13 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
public void displayBars(Table table){
|
||||
for(Func<Building, Bar> bar : block.bars.list()){
|
||||
table.add(bar.get(self())).growX();
|
||||
table.row();
|
||||
//TODO fix conclusively
|
||||
try{
|
||||
table.add(bar.get(self())).growX();
|
||||
table.row();
|
||||
}catch(ClassCastException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,7 +1130,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 ? SystemCursor.hand : SystemCursor.arrow;
|
||||
return block.configurable && team == player.team() ? SystemCursor.hand : SystemCursor.arrow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1093,7 +1162,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public float handleDamage(float amount){
|
||||
return amount;
|
||||
}
|
||||
@@ -1105,7 +1173,7 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
/** Handle a bullet collision.
|
||||
* @return whether the bullet should be removed. */
|
||||
public boolean collision(Bullet other){
|
||||
damage(other.damage() * other.type().tileDamageMultiplier);
|
||||
damage(other.damage() * other.type().buildingDamageMultiplier);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1114,6 +1182,10 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
return true;
|
||||
}
|
||||
|
||||
public void pickedUp(){
|
||||
|
||||
}
|
||||
|
||||
public void removeFromProximity(){
|
||||
onProximityRemoved();
|
||||
tmpTiles.clear();
|
||||
@@ -1156,10 +1228,6 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
proximity.add(tile);
|
||||
}
|
||||
|
||||
for(Building other : tmpTiles){
|
||||
other.onProximityUpdate();
|
||||
}
|
||||
|
||||
onProximityAdded();
|
||||
onProximityUpdate();
|
||||
|
||||
@@ -1172,6 +1240,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
}
|
||||
|
||||
/** @return ambient sound volume scale. */
|
||||
public float ambientVolume(){
|
||||
return efficiency();
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region overrides
|
||||
|
||||
@@ -1213,31 +1286,41 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
|
||||
@Override
|
||||
public double sense(LAccess sensor){
|
||||
if(sensor == LAccess.x) return x;
|
||||
if(sensor == LAccess.y) return y;
|
||||
if(sensor == LAccess.team) return team.id;
|
||||
if(sensor == LAccess.health) return health;
|
||||
if(sensor == LAccess.maxHealth) return maxHealth();
|
||||
if(sensor == LAccess.efficiency) return efficiency();
|
||||
if(sensor == LAccess.rotation) return rotation;
|
||||
if(sensor == LAccess.totalItems && items != null) return items.total();
|
||||
if(sensor == LAccess.totalLiquids && liquids != null) return liquids.total();
|
||||
if(sensor == LAccess.totalPower && power != null && block.consumes.hasPower()) return power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
|
||||
if(sensor == LAccess.itemCapacity) return block.itemCapacity;
|
||||
if(sensor == LAccess.liquidCapacity) return block.liquidCapacity;
|
||||
if(sensor == LAccess.powerCapacity) return block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
|
||||
if(sensor == LAccess.powerNetIn && power != null) return power.graph.getLastScaledPowerIn() * 60;
|
||||
if(sensor == LAccess.powerNetOut && power != null) return power.graph.getLastScaledPowerOut() * 60;
|
||||
if(sensor == LAccess.powerNetStored && power != null) return power.graph.getLastPowerStored();
|
||||
if(sensor == LAccess.powerNetCapacity && power != null) return power.graph.getLastCapacity();
|
||||
return 0;
|
||||
return switch(sensor){
|
||||
case x -> World.conv(x);
|
||||
case y -> World.conv(y);
|
||||
case team -> team.id;
|
||||
case health -> health;
|
||||
case maxHealth -> maxHealth;
|
||||
case efficiency -> efficiency();
|
||||
case rotation -> rotation;
|
||||
case totalItems -> items == null ? 0 : items.total();
|
||||
case totalLiquids -> liquids == null ? 0 : liquids.total();
|
||||
case totalPower -> power == null || !block.consumes.hasPower() ? 0 : power.status * (block.consumes.getPower().buffered ? block.consumes.getPower().capacity : 1f);
|
||||
case itemCapacity -> block.hasItems ? block.itemCapacity : 0;
|
||||
case liquidCapacity -> block.hasLiquids ? block.liquidCapacity : 0;
|
||||
case powerCapacity -> block.consumes.hasPower() ? block.consumes.getPower().capacity : 0f;
|
||||
case powerNetIn -> power == null ? 0 : power.graph.getLastScaledPowerIn() * 60;
|
||||
case powerNetOut -> power == null ? 0 : power.graph.getLastScaledPowerOut() * 60;
|
||||
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 payloadCount -> getPayload() != null ? 1 : 0;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object senseObject(LAccess sensor){
|
||||
if(sensor == LAccess.type) return block;
|
||||
return switch(sensor){
|
||||
case type -> block;
|
||||
case firstItem -> items == null ? null : items.first();
|
||||
case config -> block.configurations.containsKey(Item.class) || block.configurations.containsKey(Liquid.class) ? config() : null;
|
||||
case payloadType -> getPayload() instanceof UnitPayload p1 ? p1.unit.type : getPayload() instanceof BuildPayload p2 ? p2.block() : null;
|
||||
default -> noSensed;
|
||||
};
|
||||
|
||||
return noSensed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1255,6 +1338,18 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void control(LAccess type, Object p1, double p2, double p3, double p4){
|
||||
//don't execute configure instructions as the client
|
||||
if(type == LAccess.configure && block.logicConfigurable && !net.client()){
|
||||
//change config only if it's new
|
||||
Object prev = senseObject(LAccess.config);
|
||||
if(prev != p1){
|
||||
configureAny(p1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
if(sound != null){
|
||||
@@ -1274,6 +1369,8 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
@Final
|
||||
@Override
|
||||
public void update(){
|
||||
if(state.isEditor()) return;
|
||||
|
||||
timeScaleDuration -= Time.delta;
|
||||
if(timeScaleDuration <= 0f || !block.canOverdrive){
|
||||
timeScale = 1f;
|
||||
@@ -1287,12 +1384,18 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
|
||||
}
|
||||
}
|
||||
|
||||
if(sound != null){
|
||||
sound.update(x, y, shouldActiveSound());
|
||||
if(team == Team.derelict){
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
if(block.idleSound != Sounds.none && shouldIdleSound()){
|
||||
loops.play(block.idleSound, self(), block.idleSoundVolume);
|
||||
if(!headless){
|
||||
if(sound != null){
|
||||
sound.update(x, y, shouldActiveSound());
|
||||
}
|
||||
|
||||
if(block.ambientSound != Sounds.none && shouldAmbientSound()){
|
||||
control.sound.loop(block.ambientSound, self(), block.ambientSoundVolume * ambientVolume());
|
||||
}
|
||||
}
|
||||
|
||||
if(enabled || !block.noUpdateDisabled){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.*;
|
||||
import arc.func.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
@@ -7,10 +8,14 @@ import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.core.*;
|
||||
import mindustry.entities.bullet.*;
|
||||
import mindustry.game.EventType.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.game.Teams.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.graphics.*;
|
||||
import mindustry.world.blocks.defense.Wall.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
@@ -19,25 +24,20 @@ import static mindustry.Vars.*;
|
||||
abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Drawc, Shielderc, Ownerc, Velc, Bulletc, Timerc{
|
||||
@Import Team team;
|
||||
@Import Entityc owner;
|
||||
@Import float x,y;
|
||||
@Import float x, y, damage;
|
||||
|
||||
IntSeq collided = new IntSeq(6);
|
||||
Object data;
|
||||
BulletType type;
|
||||
float damage;
|
||||
float fdata;
|
||||
transient boolean absorbed;
|
||||
|
||||
@Override
|
||||
public void getCollisions(Cons<QuadTree> consumer){
|
||||
if(team.active()){
|
||||
for(Team team : team.enemies()){
|
||||
consumer.get(teamIndex.tree(team));
|
||||
}
|
||||
}else{
|
||||
for(Team other : Team.all){
|
||||
if(other != team && teamIndex.count(other) > 0){
|
||||
consumer.get(teamIndex.tree(other));
|
||||
}
|
||||
Seq<TeamData> data = state.teams.present;
|
||||
for(int i = 0; i < data.size; i++){
|
||||
if(data.items[i].team != team){
|
||||
consumer.get(data.items[i].tree());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
|
||||
@Override
|
||||
public void absorb(){
|
||||
absorbed = true;
|
||||
remove();
|
||||
}
|
||||
|
||||
@@ -76,11 +77,6 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
return type.drawSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float damage(){
|
||||
return damage * damageMultiplier();
|
||||
}
|
||||
|
||||
@Replace
|
||||
@Override
|
||||
public boolean collides(Hitboxc other){
|
||||
@@ -95,14 +91,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
type.hit(self(), x, y);
|
||||
float health = 0f;
|
||||
|
||||
if(other instanceof Healthc){
|
||||
Healthc h = (Healthc)other;
|
||||
if(other instanceof Healthc h){
|
||||
health = h.health();
|
||||
h.damage(damage);
|
||||
}
|
||||
|
||||
if(other instanceof Unit){
|
||||
Unit unit = (Unit)other;
|
||||
if(other instanceof Unit unit){
|
||||
unit.impulse(Tmp.v3.set(unit).sub(this.x, this.y).nor().scl(type.knockback * 80f));
|
||||
unit.apply(type.status, type.statusDuration);
|
||||
}
|
||||
@@ -115,6 +109,10 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
}
|
||||
|
||||
type.hitEntity(self(), other, health);
|
||||
|
||||
if(owner instanceof WallBuild && player != null && team == player.team() && other instanceof Unit unit && unit.dead){
|
||||
Events.fire(Trigger.phaseDeflectHit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,12 +120,12 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
type.update(self());
|
||||
|
||||
if(type.collidesTiles && type.collides && type.collidesGround){
|
||||
world.raycastEach(world.toTile(lastX()), world.toTile(lastY()), tileX(), tileY(), (x, y) -> {
|
||||
world.raycastEach(World.toTile(lastX()), World.toTile(lastY()), tileX(), tileY(), (x, y) -> {
|
||||
|
||||
Building tile = world.build(x, y);
|
||||
if(tile == null || !isAdded()) return false;
|
||||
|
||||
if(tile.collide(self()) && type.collides(self(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team) && !(type.pierceBuilding && collided.contains(tile.id))){
|
||||
if(tile.collide(self()) && type.testCollision(self(), tile) && !tile.dead() && (type.collidesTeam || tile.team != team) && !(type.pierceBuilding && collided.contains(tile.id))){
|
||||
boolean remove = false;
|
||||
|
||||
float health = tile.health;
|
||||
@@ -144,7 +142,7 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
}
|
||||
}
|
||||
|
||||
type.hitTile(self(), tile, health);
|
||||
type.hitTile(self(), tile, health, true);
|
||||
|
||||
return !type.pierceBuilding;
|
||||
}
|
||||
@@ -152,6 +150,10 @@ abstract class BulletComp implements Timedc, Damagec, Hitboxc, Teamc, Posc, Draw
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if(type.pierceCap != -1 && collided.size >= type.pierceCap){
|
||||
remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package mindustry.entities.comp;
|
||||
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user