Formatting

This commit is contained in:
Anuken
2018-07-12 20:37:15 -04:00
parent a3bda3a941
commit baaeb229cf
379 changed files with 17470 additions and 16215 deletions
@@ -59,7 +59,8 @@ public class AndroidTextFieldDialog{
while(!isBuild){
try{
Thread.sleep(10);
} catch (InterruptedException e) { }
}catch(InterruptedException e){
}
}
return this;
@@ -8,12 +8,9 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.Button;
import org.sufficientlysecure.donations.DonationsFragment;
public class DonationsActivity extends FragmentActivity{
DonationsFragment donationsFragment;
/**
* Google
*/
@@ -22,6 +19,7 @@ public class DonationsActivity extends FragmentActivity {
"mindustry.donation.1", "mindustry.donation.2", "mindustry.donation.5",
"mindustry.donation.10", "mindustry.donation.15",
"mindustry.donation.25", "mindustry.donation.50"};
DonationsFragment donationsFragment;
/**
* Called when the activity is first created.
@@ -50,7 +48,8 @@ public class DonationsActivity extends FragmentActivity {
super.onStart();
Button b = ((Button) findViewById(org.sufficientlysecure.donations.R.id.donations__google_android_market_donate_button));
b.setOnClickListener(new View.OnClickListener(){
@Override public void onClick(View view) {
@Override
public void onClick(View view){
donationsFragment.donateGoogleOnClick(donationsFragment.getView());
b.setEnabled(false);
}
@@ -58,7 +57,6 @@ public class DonationsActivity extends FragmentActivity {
}
/**
* Needed for Google Play In-app Billing. It uses startIntentSenderForResult(). The result is not propagated to
* the Fragment like in startActivityForResult(). Thus we need to propagate manually to our Fragment.
@@ -15,6 +15,13 @@ public class TextFieldDialogListener extends ClickListener{
private int type;
private int max;
//type - 0 is text, 1 is numbers, 2 is decimals
public TextFieldDialogListener(TextField field, int type, int max){
this.field = field;
this.type = type;
this.max = max;
}
public static void add(TextField field, int type, int max){
field.addListener(new TextFieldDialogListener(field, type, max));
field.addListener(new InputListener(){
@@ -29,13 +36,6 @@ public class TextFieldDialogListener extends ClickListener{
add(field, 0, 16);
}
//type - 0 is text, 1 is numbers, 2 is decimals
public TextFieldDialogListener(TextField field, int type, int max){
this.field = field;
this.type = type;
this.max = max;
}
public void clicked(final InputEvent event, float x, float y){
if(Gdx.app.getType() == ApplicationType.Desktop) return;
@@ -11,45 +11,6 @@ import java.lang.annotation.Target;
*/
public class Annotations{
/**Marks a method as invokable remotely across a server/client connection.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Remote {
/**Specifies the locations from which this method can be invoked.*/
Loc targets() default Loc.server;
/**Specifies which methods are generated. Only affects server-to-client methods.*/
Variant variants() default Variant.all;
/**The local locations where this method is called locally, when invoked.*/
Loc called() default Loc.none;
/**Whether to forward this packet to all other clients upon recieval. Client only.*/
boolean forward() default false;
/**Whether the packet for this method is sent with UDP instead of TCP.
* UDP is faster, but is prone to packet loss and duplication.*/
boolean unreliable() default false;
/**The simple class name where this method is placed.*/
String in() default "Call";
/**Priority of this event.*/
PacketPriority priority() default PacketPriority.normal;
}
/**Specifies that this method will be used to write classes of the type returned by {@link #value()}.<br>
* This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second
* being the type returned by {@link #value()}.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface WriteClass {
Class<?> value();
}
/**Specifies that this method will be used to read classes of the type returned by {@link #value()}. <br>
* This method must return the type returned by {@link #value()},
* and have one parameter, being of type {@link java.nio.ByteBuffer}.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ReadClass {
Class<?> value();
}
public enum PacketPriority{
/** Gets put in a queue and processed if not connected. */
normal,
@@ -96,4 +57,55 @@ public class Annotations {
this.isAll = isAll;
}
}
/** Marks a method as invokable remotely across a server/client connection. */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Remote{
/** Specifies the locations from which this method can be invoked. */
Loc targets() default Loc.server;
/** Specifies which methods are generated. Only affects server-to-client methods. */
Variant variants() default Variant.all;
/** The local locations where this method is called locally, when invoked. */
Loc called() default Loc.none;
/** Whether to forward this packet to all other clients upon recieval. Client only. */
boolean forward() default false;
/**
* Whether the packet for this method is sent with UDP instead of TCP.
* UDP is faster, but is prone to packet loss and duplication.
*/
boolean unreliable() default false;
/** The simple class name where this method is placed. */
String in() default "Call";
/** Priority of this event. */
PacketPriority priority() default PacketPriority.normal;
}
/**
* Specifies that this method will be used to write classes of the type returned by {@link #value()}.<br>
* This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second
* being the type returned by {@link #value()}.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface WriteClass{
Class<?> value();
}
/**
* Specifies that this method will be used to read classes of the type returned by {@link #value()}. <br>
* This method must return the type returned by {@link #value()},
* and have one parameter, being of type {@link java.nio.ByteBuffer}.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ReadClass{
Class<?> value();
}
}
@@ -10,12 +10,16 @@ import javax.tools.Diagnostic.Kind;
import java.util.HashMap;
import java.util.Set;
/**This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass}
* and {@link io.anuke.annotations.Annotations.ReadClass} annotations.*/
/**
* This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass}
* and {@link io.anuke.annotations.Annotations.ReadClass} annotations.
*/
public class IOFinder{
/**Finds all class serializers for all types and returns them. Logs errors when necessary.
* Maps fully qualified class names to their serializers.*/
/**
* Finds all class serializers for all types and returns them. Logs errors when necessary.
* Maps fully qualified class names to their serializers.
*/
public HashMap<String, ClassSerializer> findSerializers(RoundEnvironment env){
HashMap<String, ClassSerializer> result = new HashMap<>();
@@ -14,8 +14,10 @@ public class MethodEntry {
public final String targetMethod;
/** Whether this method can be called on a client/server. */
public final Loc where;
/**Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true.
* Only applicable to client (server-invoked) methods.*/
/**
* Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true.
* Only applicable to client (server-invoked) methods.
*/
public final Variant target;
/** Whether this method is called locally as well as remotely. */
public final Loc local;
@@ -23,11 +23,14 @@ public class RemoteReadGenerator {
this.serializers = serializers;
}
/**Generates a class for reading remote invoke packets.
/**
* Generates a class for reading remote invoke packets.
*
* @param entries List of methods to use/
* @param className Simple target class name.
* @param packageName Full target package name.
* @param needsPlayer Whether this read method requires a reference to the player sender.*/
* @param needsPlayer Whether this read method requires a reference to the player sender.
*/
public void generateFor(List<MethodEntry> entries, String className, String packageName, boolean needsPlayer)
throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException{
+41 -58
View File
@@ -19,14 +19,54 @@ import io.anuke.mindustry.io.Version;
import io.anuke.mindustry.net.Net;
import io.anuke.ucore.entities.Entities;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.trait.DrawTrait;
import io.anuke.ucore.entities.impl.EffectEntity;
import io.anuke.ucore.entities.trait.DrawTrait;
import io.anuke.ucore.scene.ui.layout.Unit;
import io.anuke.ucore.util.OS;
import java.util.Locale;
public class Vars{
//respawn time in frames
public static final float respawnduration = 60 * 4;
//time between waves in frames (on normal mode)
public static final float wavespace = 60 * 60 * 2f;
//waves can last no longer than 3 minutes, otherwise the next one spawns
public static final float maxwavespace = 60 * 60 * 4f;
//set ridiculously high for now
public static final float coreBuildRange = 800999f;
//discord group URL
public static final String discordURL = "https://discord.gg/BKADYds";
public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases";
public static final int maxTextLength = 150;
public static final int maxNameLength = 40;
public static final int maxCharNameLength = 20;
public static final int saveSlots = 64;
public static final float itemSize = 5f;
public static final int tilesize = 8;
public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"),
new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), new Locale("ita"), new Locale("es")};
public static final Color[] playerColors = {
Color.valueOf("82759a"),
Color.valueOf("c0c1c5"),
Color.valueOf("fff0e7"),
Color.valueOf("7d2953"),
Color.valueOf("ff074e"),
Color.valueOf("ff072a"),
Color.valueOf("ff76a6"),
Color.valueOf("a95238"),
Color.valueOf("ffa108"),
Color.valueOf("feeb2c"),
Color.valueOf("ffcaa8"),
Color.valueOf("008551"),
Color.valueOf("00e339"),
Color.valueOf("423c7b"),
Color.valueOf("4b5ef1"),
Color.valueOf("2cabfe"),
};
//server port
public static final int port = 6567;
public static final int webPort = 6568;
public static boolean testMobile;
//shorthand for whether or not this is running on android or ios
public static boolean mobile;
@@ -34,21 +74,6 @@ public class Vars{
public static boolean android;
//shorthand for whether or not this is running on GWT
public static boolean gwt;
//respawn time in frames
public static final float respawnduration = 60*4;
//time between waves in frames (on normal mode)
public static final float wavespace = 60*60*2f;
//waves can last no longer than 3 minutes, otherwise the next one spawns
public static final float maxwavespace = 60*60*4f;
//set ridiculously high for now
public static final float coreBuildRange = 800999f;
//discord group URL
public static final String discordURL = "https://discord.gg/BKADYds";
public static final String releasesURL = "https://api.github.com/repos/Anuken/Mindustry/releases";
//directory for user-created map data
public static FileHandle customMapDirectory;
//save file directory
@@ -74,54 +99,12 @@ public class Vars{
public static boolean showUI = true;
//whether to show block debug
public static boolean showBlockDebug = false;
public static boolean showFog = true;
public static final int maxTextLength = 150;
public static final int maxNameLength = 40;
public static final int maxCharNameLength = 20;
public static boolean headless = false;
public static float controllerMin = 0.25f;
public static float baseControllerSpeed = 11f;
public static final int saveSlots = 64;
public static final float itemSize = 5f;
//only if smoothCamera
public static boolean snapCamera = true;
public static final int tilesize = 8;
public static final Locale[] locales = {new Locale("en"), new Locale("fr"), new Locale("ru"), new Locale("uk", "UA"), new Locale("pl"),
new Locale("de"), new Locale("pt", "BR"), new Locale("ko"), new Locale("in", "ID"), new Locale("ita"), new Locale("es")};
public static final Color[] playerColors = {
Color.valueOf("82759a"),
Color.valueOf("c0c1c5"),
Color.valueOf("fff0e7"),
Color.valueOf("7d2953"),
Color.valueOf("ff074e"),
Color.valueOf("ff072a"),
Color.valueOf("ff76a6"),
Color.valueOf("a95238"),
Color.valueOf("ffa108"),
Color.valueOf("feeb2c"),
Color.valueOf("ffcaa8"),
Color.valueOf("008551"),
Color.valueOf("00e339"),
Color.valueOf("423c7b"),
Color.valueOf("4b5ef1"),
Color.valueOf("2cabfe"),
};
//server port
public static final int port = 6567;
public static final int webPort = 6568;
public static GameState state;
public static ThreadHandler threads;
@@ -26,32 +26,53 @@ import static io.anuke.mindustry.Vars.*;
//TODO consider using quadtrees for finding specific types of blocks within an area
//TODO maybe use Arrays instead of ObjectSets?
/**Class used for indexing special target blocks for AI.*/
/**
* Class used for indexing special target blocks for AI.
*/
public class BlockIndexer{
/**Size of one ore quadrant.*/
/**
* Size of one ore quadrant.
*/
private final static int oreQuadrantSize = 20;
/**Size of one structure quadrant.*/
/**
* Size of one structure quadrant.
*/
private final static int structQuadrantSize = 12;
/**Set of all ores that are being scanned.*/
/**
* Set of all ores that are being scanned.
*/
private final ObjectSet<Item> scanOres = ObjectSet.with(Items.tungsten, Items.coal, Items.lead, Items.thorium, Items.titanium);
/**Stores all ore quadtrants on the map.*/
private ObjectMap<Item, ObjectSet<Tile>> ores;
private final ObjectSet<Item> itemSet = new ObjectSet<>();
/**Tags all quadrants.*/
/**
* Stores all ore quadtrants on the map.
*/
private ObjectMap<Item, ObjectSet<Tile>> ores;
/**
* Tags all quadrants.
*/
private Bits[] structQuadrants;
/**Maps teams to a map of flagged tiles by type.*/
/**
* Maps teams to a map of flagged tiles by type.
*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> enemyMap = new ObjectMap<>();
/**Maps teams to a map of flagged tiles by type.*/
/**
* Maps teams to a map of flagged tiles by type.
*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> allyMap = new ObjectMap<>();
/**Empty map for invalid teams.*/
/**
* Empty map for invalid teams.
*/
private ObjectMap<BlockFlag, ObjectSet<Tile>> emptyMap = new ObjectMap<>();
/**Maps tile positions to their last known tile index data.*/
/**
* Maps tile positions to their last known tile index data.
*/
private IntMap<TileIndex> typeMap = new IntMap<>();
/**Empty array used for returning.*/
/**
* Empty array used for returning.
*/
private ObjectSet<Tile> emptyArray = new ObjectSet<>();
public BlockIndexer(){
@@ -94,12 +115,16 @@ public class BlockIndexer {
});
}
/**Get all allied blocks with a flag.*/
/**
* Get all allied blocks with a flag.
*/
public ObjectSet<Tile> getAllied(Team team, BlockFlag type){
return (state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
}
/**Get all enemy blocks with a flag.*/
/**
* Get all enemy blocks with a flag.
*/
public ObjectSet<Tile> getEnemy(Team team, BlockFlag type){
return (!state.teams.get(team).ally ? allyMap : enemyMap).get(type, emptyArray);
}
@@ -134,15 +159,19 @@ public class BlockIndexer {
return (TileEntity) closest;
}
/**Returns a set of tiles that have ores of the specified type nearby.
/**
* Returns a set of tiles that have ores of the specified type nearby.
* While each tile in the set is not guaranteed to have an ore directly on it,
* each tile will at least have an ore within {@link #oreQuadrantSize} / 2 blocks of it.
* Only specific ore types are scanned. See {@link #scanOres}.*/
* Only specific ore types are scanned. See {@link #scanOres}.
*/
public ObjectSet<Tile> getOrePositions(Item item){
return ores.get(item, emptyArray);
}
/**Find the closest ore block relative to a position.*/
/**
* Find the closest ore block relative to a position.
*/
public Tile findClosestOre(float xp, float yp, Item item){
Tile tile = Geometry.findClosest(xp, yp, world.indexer().getOrePositions(item));
@@ -11,7 +11,9 @@ import io.anuke.mindustry.type.Upgrade;
public class Mechs implements ContentList{
public static Mech alpha, delta, tau, omega, dart, javelin, trident, halberd;
/**These are not new mechs, just re-assignments for convenience.*/
/**
* These are not new mechs, just re-assignments for convenience.
*/
public static Mech starterDesktop, starterMobile;
@Override
@@ -171,7 +171,6 @@ public class Recipes implements ContentList{
new Recipe(production, ProductionBlocks.oilextractor, new ItemStack(Items.titanium, 40), new ItemStack(Items.surgealloy, 40));*/
//new Recipe(distribution, DistributionBlocks.massDriver, new ItemStack(Items.carbide, 1));
@@ -3,10 +3,10 @@ package io.anuke.mindustry.content;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.content.fx.EnvironmentFx;
import io.anuke.mindustry.entities.StatusController.StatusEntry;
import io.anuke.mindustry.game.Content;
import io.anuke.mindustry.type.StatusEffect;
import io.anuke.mindustry.entities.Unit;
import io.anuke.mindustry.game.Content;
import io.anuke.mindustry.type.ContentList;
import io.anuke.mindustry.type.StatusEffect;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.util.Mathf;
@@ -15,17 +15,22 @@ public class Blocks extends BlockList implements ContentList{
public static Block air, spawn, blockpart, space, metalfloor, deepwater, water, lava, oil, stone, blackstone, dirt, sand, ice, snow, grass, shrub, rock, icerock, blackrock;
@Override
public void load(){
air = new Floor("air"){
{
blend = false;
}
//don't draw
public void draw(Tile tile) {}
public void load() {}
public void init() {}
public void draw(Tile tile){
}
public void load(){
}
public void init(){
}
};
blockpart = new BlockPart();
@@ -28,6 +28,12 @@ import java.io.IOException;
public class DebugBlocks extends BlockList implements ContentList{
public static Block powerVoid, powerInfinite, itemSource, liquidSource, itemVoid;
@Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true)
public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){
LiquidSourceEntity entity = tile.entity();
entity.source = liquid;
}
@Override
public void load(){
powerVoid = new PowerBlock("powervoid"){
@@ -53,6 +59,7 @@ public class DebugBlocks extends BlockList implements ContentList{
{
hasItems = true;
}
@Override
public void update(Tile tile){
SorterEntity entity = tile.entity();
@@ -142,12 +149,6 @@ public class DebugBlocks extends BlockList implements ContentList{
};
}
@Remote(targets = Loc.both, called = Loc.both, in = In.blocks, forward = true)
public static void setLiquidSourceLiquid(Player player, Tile tile, Liquid liquid){
LiquidSourceEntity entity = tile.entity();
entity.source = liquid;
}
class LiquidSourceEntity extends TileEntity{
public Liquid source = Liquids.water;
@@ -10,6 +10,13 @@ import io.anuke.mindustry.world.blocks.OreBlock;
public class OreBlocks extends BlockList{
private static final ObjectMap<Item, ObjectMap<Block, Block>> oreBlockMap = new ObjectMap<>();
public static Block get(Block floor, Item item){
if(!oreBlockMap.containsKey(item)) throw new IllegalArgumentException("Item '" + item + "' is not an ore!");
if(!oreBlockMap.get(item).containsKey(floor))
throw new IllegalArgumentException("Block '" + floor.name + "' does not support ores!");
return oreBlockMap.get(item).get(floor);
}
@Override
public void load(){
Item[] ores = {Items.tungsten, Items.lead, Items.coal, Items.titanium, Items.thorium};
@@ -25,10 +32,4 @@ public class OreBlocks extends BlockList {
}
}
}
public static Block get(Block floor, Item item){
if(!oreBlockMap.containsKey(item)) throw new IllegalArgumentException("Item '" + item + "' is not an ore!");
if(!oreBlockMap.get(item).containsKey(floor)) throw new IllegalArgumentException("Block '" + floor.name + "' does not support ores!");
return oreBlockMap.get(item).get(floor);
}
}
@@ -13,7 +13,8 @@ import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Strings;
public class TurretBlocks extends BlockList implements ContentList{
public static Block duo, /*scatter,*/ scorch, hail, wave, lancer, arc, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown;
public static Block duo, /*scatter,*/
scorch, hail, wave, lancer, arc, swarmer, salvo, fuse, ripple, cyclone, spectre, meltdown;
@Override
public void load(){
@@ -25,8 +25,10 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.util.Log;
/**Loads all game content.
* Call load() before doing anything with content.*/
/**
* Loads all game content.
* Call load() before doing anything with content.
*/
public class ContentLoader{
private static boolean loaded = false;
private static ObjectSet<Array<? extends Content>> contentSet = new OrderedSet<>();
@@ -94,7 +96,9 @@ public class ContentLoader {
new Recipes(),
};
/**Creates all content types.*/
/**
* Creates all content types.
*/
public static void load(){
if(loaded){
Log.info("Content already loaded, skipping.");
@@ -134,7 +138,9 @@ public class ContentLoader {
loaded = true;
}
/**Initializes all content with the specified function.*/
/**
* Initializes all content with the specified function.
*/
public static void initialize(Consumer<Content> callable){
if(initialization.contains(callable)) return;
@@ -155,8 +161,10 @@ public class ContentLoader {
return contentMap;
}
/**Registers sync IDs for all types of sync entities.
* Do not register units here!*/
/**
* Registers sync IDs for all types of sync entities.
* Do not register units here!
*/
private static void registerTypes(){
TypeTrait.registerType(Player.class, Player::new);
TypeTrait.registerType(ItemDrop.class, ItemDrop::new);
@@ -30,12 +30,16 @@ import io.anuke.ucore.util.Atlas;
import static io.anuke.mindustry.Vars.*;
/**Control module.
/**
* Control module.
* Handles all input, saving, keybinds and keybinds.
* Should <i>not</i> handle any logic-critical state.
* This class is not created in the headless server.*/
* This class is not created in the headless server.
*/
public class Control extends Module{
/**Minimum period of time between the same sound being played.*/
/**
* Minimum period of time between the same sound being played.
*/
private static final long minSoundPeriod = 100;
private boolean hiscore = false;
@@ -8,8 +8,6 @@ import io.anuke.mindustry.game.TeamInfo;
import io.anuke.ucore.core.Events;
public class GameState{
private State state = State.menu;
public int wave = 1;
public float wavetime;
public boolean gameOver = false;
@@ -18,6 +16,7 @@ public class GameState{
public boolean friendlyFire;
public WaveSpawner spawner = new WaveSpawner();
public TeamInfo teams = new TeamInfo();
private State state = State.menu;
public void set(State astate){
Events.fire(StateChangeEvent.class, state, astate);
+7 -4
View File
@@ -24,12 +24,14 @@ import io.anuke.ucore.modules.Module;
import static io.anuke.mindustry.Vars.*;
/**Logic module.
/**
* Logic module.
* Handles all logic for entities and waves.
* Handles game state events.
* Does not store any game state itself.
*
* This class should <i>not</i> call any outside methods to change state of modules, but instead fire events.*/
* <p>
* This class should <i>not</i> call any outside methods to change state of modules, but instead fire events.
*/
public class Logic extends Module{
public boolean doUpdate = true;
@@ -137,7 +139,8 @@ public class Logic extends Module {
runWave();
}
if(!Entities.defaultGroup().isEmpty()) throw new RuntimeException("Do not add anything to the default group!");
if(!Entities.defaultGroup().isEmpty())
throw new RuntimeException("Do not add anything to the default group!");
Entities.update(bulletGroup);
for(EntityGroup group : unitGroups){
+199 -170
View File
@@ -41,33 +41,59 @@ public class NetClient extends Module {
private final static float playerSyncTime = 2;
private Timer timer = new Timer(5);
/**Whether the client is currently connecting.*/
/**
* Whether the client is currently connecting.
*/
private boolean connecting = false;
/**If true, no message will be shown on disconnect.*/
/**
* If true, no message will be shown on disconnect.
*/
private boolean quiet = false;
/**Counter for data timeout.*/
/**
* Counter for data timeout.
*/
private float timeoutTime = 0f;
/**Last sent client snapshot ID.*/
/**
* Last sent client snapshot ID.
*/
private int lastSent;
/**Last snapshot ID recieved.*/
/**
* Last snapshot ID recieved.
*/
private int lastSnapshotBaseID = -1;
/**Last snapshot recieved.*/
/**
* Last snapshot recieved.
*/
private byte[] lastSnapshotBase;
/**Current snapshot that is being built from chinks.*/
/**
* Current snapshot that is being built from chinks.
*/
private byte[] currentSnapshot;
/**Array of recieved chunk statuses.*/
/**
* Array of recieved chunk statuses.
*/
private boolean[] recievedChunks;
/**Counter of how many chunks have been recieved.*/
/**
* Counter of how many chunks have been recieved.
*/
private int recievedChunkCounter;
/**ID of snapshot that is currently being constructed.*/
/**
* ID of snapshot that is currently being constructed.
*/
private int currentSnapshotID = -1;
/**Decoder for uncompressing snapshots.*/
/**
* Decoder for uncompressing snapshots.
*/
private DEZDecoder decoder = new DEZDecoder();
/**List of entities that were removed, and need not be added while syncing.*/
/**
* List of entities that were removed, and need not be added while syncing.
*/
private IntSet removed = new IntSet();
/**Byte stream for reading in snapshots.*/
/**
* Byte stream for reading in snapshots.
*/
private ReusableByteArrayInputStream byteStream = new ReusableByteArrayInputStream();
private DataInputStream dataStream = new DataInputStream(byteStream);
@@ -145,6 +171,166 @@ public class NetClient extends Module {
});
}
@Remote(variants = Variant.one, priority = PacketPriority.high)
public static void onKick(KickReason reason){
netClient.disconnectQuietly();
state.set(State.menu);
if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name());
ui.loadfrag.hide();
}
@Remote(variants = Variant.one)
public static void onPositionSet(float x, float y){
players[0].x = x;
players[0].y = y;
}
@Remote(variants = Variant.one)
public static void onTraceInfo(TraceInfo info){
Player player = playerGroup.getByID(info.playerid);
ui.traces.show(player, info);
}
@Remote
public static void onPlayerDisconnect(int playerid){
playerGroup.removeByID(playerid);
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){
if(NetServer.showSnapshotSize)
Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID);
//skip snapshot IDs that have already been recieved OR snapshots that are too far in front
if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){
if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT");
return;
}
try{
byte[] snapshot;
//total length exceeds that needed to hold one snapshot, therefore, it is split into chunks
if(totalLength > NetServer.maxSnapshotSize){
//total amount of chunks to recieve
int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize);
//reset status when a new snapshot sending begins
if(netClient.currentSnapshotID != snapshotID){
netClient.currentSnapshotID = snapshotID;
netClient.currentSnapshot = new byte[totalLength];
netClient.recievedChunkCounter = 0;
netClient.recievedChunks = new boolean[totalChunks];
}
//if this chunk hasn't been recieved yet...
if(!netClient.recievedChunks[chunkID]){
netClient.recievedChunks[chunkID] = true;
netClient.recievedChunkCounter++; //update recieved status
//copy the recieved bytes into the holding array
System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize,
Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize));
}
//when all chunks have been recieved, begin
if(netClient.recievedChunkCounter >= totalChunks){
snapshot = netClient.currentSnapshot;
}else{
return;
}
}else{
snapshot = chunk;
}
if(NetServer.showSnapshotSize)
Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
byte[] result;
int length;
if(base == -1){ //fresh snapshot
result = snapshot;
length = snapshot.length;
netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length);
}else{ //otherwise, last snapshot must not be null, decode it
if(NetServer.showSnapshotSize)
Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length);
netClient.decoder.init(netClient.lastSnapshotBase, snapshot);
result = netClient.decoder.decode();
length = netClient.decoder.getDecodedLength();
//set last snapshot to a copy to prevent issues
netClient.lastSnapshotBase = Arrays.copyOf(result, length);
}
netClient.lastSnapshotBaseID = snapshotID;
//set stream bytes to begin snapshot reaeding
netClient.byteStream.setBytes(result, 0, length);
//get data input for reading from the stream
DataInputStream input = netClient.dataStream;
//read wave info
state.wavetime = input.readFloat();
state.wave = input.readInt();
byte cores = input.readByte();
for(int i = 0; i < cores; i++){
int pos = input.readInt();
world.tile(pos).entity.items.read(input);
}
long timestamp = input.readLong();
byte totalGroups = input.readByte();
//for each group...
for(int i = 0; i < totalGroups; i++){
//read group info
byte groupID = input.readByte();
short amount = input.readShort();
EntityGroup group = Entities.getGroup(groupID);
//go through each entity
for(int j = 0; j < amount; j++){
int position = netClient.byteStream.position(); //save position to check read/write correctness
int id = input.readInt();
byte typeID = input.readByte();
SyncTrait entity = (SyncTrait) group.getByID(id);
boolean add = false;
//entity must not be added yet, so create it
if(entity == null){
entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier
entity.resetID(id);
if(!netClient.isEntityUsed(entity.getID())){
add = true;
}
}
//read the entity
entity.read(input, timestamp);
byte readLength = input.readByte();
if(netClient.byteStream.position() - position - 1 != readLength){
throw new RuntimeException("Error reading entity of type '" + group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1) + "]");
}
if(add){
entity.add();
netClient.addRemovedEntity(entity.getID());
}
}
}
//confirm that snapshot has been recieved
netClient.lastSnapshotBaseID = snapshotID;
}catch(Exception e){
throw new RuntimeException(e);
}
}
@Override
public void update(){
if(!Net.client()) return;
@@ -224,161 +410,4 @@ public class NetClient extends Module {
return result;
}
}
@Remote(variants = Variant.one, priority = PacketPriority.high)
public static void onKick(KickReason reason){
netClient.disconnectQuietly();
state.set(State.menu);
if(!reason.quiet) ui.showError("$text.server.kicked." + reason.name());
ui.loadfrag.hide();
}
@Remote(variants = Variant.one)
public static void onPositionSet(float x, float y){
players[0].x = x;
players[0].y = y;
}
@Remote(variants = Variant.one)
public static void onTraceInfo(TraceInfo info){
Player player = playerGroup.getByID(info.playerid);
ui.traces.show(player, info);
}
@Remote
public static void onPlayerDisconnect(int playerid){
playerGroup.removeByID(playerid);
}
@Remote(variants = Variant.one, priority = PacketPriority.low, unreliable = true)
public static void onSnapshot(byte[] chunk, int snapshotID, short chunkID, int totalLength, int base){
if(NetServer.showSnapshotSize) Log.info("Recieved snapshot: len {0} ID {1} chunkID {2} totalLength {3} base {4} client-base {5}", chunk.length, snapshotID, chunkID, totalLength, base, netClient.lastSnapshotBaseID);
//skip snapshot IDs that have already been recieved OR snapshots that are too far in front
if(snapshotID < netClient.lastSnapshotBaseID || base != netClient.lastSnapshotBaseID){
if(NetServer.showSnapshotSize) Log.info("//SKIP SNAPSHOT");
return;
}
try {
byte[] snapshot;
//total length exceeds that needed to hold one snapshot, therefore, it is split into chunks
if(totalLength > NetServer.maxSnapshotSize) {
//total amount of chunks to recieve
int totalChunks = Mathf.ceil((float) totalLength / NetServer.maxSnapshotSize);
//reset status when a new snapshot sending begins
if (netClient.currentSnapshotID != snapshotID) {
netClient.currentSnapshotID = snapshotID;
netClient.currentSnapshot = new byte[totalLength];
netClient.recievedChunkCounter = 0;
netClient.recievedChunks = new boolean[totalChunks];
}
//if this chunk hasn't been recieved yet...
if (!netClient.recievedChunks[chunkID]) {
netClient.recievedChunks[chunkID] = true;
netClient.recievedChunkCounter ++; //update recieved status
//copy the recieved bytes into the holding array
System.arraycopy(chunk, 0, netClient.currentSnapshot, chunkID * NetServer.maxSnapshotSize,
Math.min(NetServer.maxSnapshotSize, totalLength - chunkID * NetServer.maxSnapshotSize));
}
//when all chunks have been recieved, begin
if(netClient.recievedChunkCounter >= totalChunks){
snapshot = netClient.currentSnapshot;
}else{
return;
}
}else{
snapshot = chunk;
}
if(NetServer.showSnapshotSize) Log.info("Finished recieving snapshot ID {0} length {1}", snapshotID, chunk.length);
byte[] result;
int length;
if (base == -1) { //fresh snapshot
result = snapshot;
length = snapshot.length;
netClient.lastSnapshotBase = Arrays.copyOf(snapshot, snapshot.length);
} else { //otherwise, last snapshot must not be null, decode it
if(NetServer.showSnapshotSize) Log.info("Base size: {0} Patch size: {1}", netClient.lastSnapshotBase.length, snapshot.length);
netClient.decoder.init(netClient.lastSnapshotBase, snapshot);
result = netClient.decoder.decode();
length = netClient.decoder.getDecodedLength();
//set last snapshot to a copy to prevent issues
netClient.lastSnapshotBase = Arrays.copyOf(result, length);
}
netClient.lastSnapshotBaseID = snapshotID;
//set stream bytes to begin snapshot reaeding
netClient.byteStream.setBytes(result, 0, length);
//get data input for reading from the stream
DataInputStream input = netClient.dataStream;
//read wave info
state.wavetime = input.readFloat();
state.wave = input.readInt();
byte cores = input.readByte();
for (int i = 0; i < cores; i++) {
int pos = input.readInt();
world.tile(pos).entity.items.read(input);
}
long timestamp = input.readLong();
byte totalGroups = input.readByte();
//for each group...
for (int i = 0; i < totalGroups; i++) {
//read group info
byte groupID = input.readByte();
short amount = input.readShort();
EntityGroup group = Entities.getGroup(groupID);
//go through each entity
for (int j = 0; j < amount; j++) {
int position = netClient.byteStream.position(); //save position to check read/write correctness
int id = input.readInt();
byte typeID = input.readByte();
SyncTrait entity = (SyncTrait) group.getByID(id);
boolean add = false;
//entity must not be added yet, so create it
if(entity == null){
entity = (SyncTrait) TypeTrait.getTypeByID(typeID).get(); //create entity from supplier
entity.resetID(id);
if(!netClient.isEntityUsed(entity.getID())){
add = true;
}
}
//read the entity
entity.read(input, timestamp);
byte readLength = input.readByte();
if(netClient.byteStream.position() - position - 1 != readLength){
throw new RuntimeException("Error reading entity of type '"+ group.getType() + "': Read length mismatch [write=" + readLength + ", read=" + (netClient.byteStream.position() - position - 1)+ "]");
}
if(add){
entity.add();
netClient.addRemovedEntity(entity.getID());
}
}
}
//confirm that snapshot has been recieved
netClient.lastSnapshotBaseID = snapshotID;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
+101 -86
View File
@@ -45,20 +45,30 @@ public class NetServer extends Module{
private final static byte[] reusableSnapArray = new byte[maxSnapshotSize];
private final static float serverSyncTime = 4, kickDuration = 30 * 1000;
private final static Vector2 vector = new Vector2();
/**If a play goes away of their server-side coordinates by this distance, they get teleported back.*/
/**
* If a play goes away of their server-side coordinates by this distance, they get teleported back.
*/
private final static float correctDist = 16f;
public final Administration admins = new Administration();
/**Maps connection IDs to players.*/
/**
* Maps connection IDs to players.
*/
private IntMap<Player> connections = new IntMap<>();
private boolean closing = false;
/**Stream for writing player sync data to.*/
/**
* Stream for writing player sync data to.
*/
private CountableByteArrayOutputStream syncStream = new CountableByteArrayOutputStream();
/**Data stream for writing player sync data to.*/
/**
* Data stream for writing player sync data to.
*/
private DataOutputStream dataStream = new DataOutputStream(syncStream);
/**Encoder for computing snapshot deltas.*/
/**
* Encoder for computing snapshot deltas.
*/
private DEZEncoder encoder = new DEZEncoder();
public NetServer(){
@@ -238,6 +248,86 @@ public class NetServer extends Module{
});
}
/**
* Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.
*/
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){
if(bytes.length < maxSnapshotSize){
Call.onSnapshot(userid, bytes, snapshotID, (short) 0, bytes.length, base);
}else{
int remaining = bytes.length;
int offset = 0;
int chunkid = 0;
while(remaining > 0){
int used = Math.min(remaining, maxSnapshotSize);
byte[] toSend;
//re-use sent byte arrays when possible
if(used == maxSnapshotSize){
toSend = reusableSnapArray;
System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset);
}else{
toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
}
Call.onSnapshot(userid, toSend, snapshotID, (short) chunkid, bytes.length, base);
remaining -= used;
offset += used;
chunkid++;
}
}
}
public static void onDisconnect(Player player){
Call.sendMessage("[accent]" + player.name + " has disconnected.");
Call.onPlayerDisconnect(player.id);
player.remove();
netServer.connections.remove(player.con.id);
}
@Remote(targets = Loc.client, called = Loc.server)
public static void onAdminRequest(Player player, Player other, AdminAction action){
if(!player.isAdmin){
Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.",
player.name, player.con.address);
return;
}
if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself
Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name);
return;
}
if(action == AdminAction.wave){
//no verification is done, so admins can hypothetically spam waves
//not a real issue, because server owners may want to do just that
state.wavetime = 0f;
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
netServer.kick(other.con.id, KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(action == AdminAction.kick){
netServer.kick(other.con.id, KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(action == AdminAction.trace){
//TODO
if(player.con != null){
Call.onTraceInfo(player.con.id, netServer.admins.getTraceByID(other.uuid));
}else{
NetClient.onTraceInfo(netServer.admins.getTraceByID(other.uuid));
}
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
}
}
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
player.add();
player.con.hasConnected = true;
Call.sendMessage("[accent]" + player.name + " has connected.");
Log.info("&y{0} has connected.", player.name);
}
public void update(){
if(!headless && !closing && Net.server() && state.is(State.menu)){
closing = true;
@@ -344,7 +434,8 @@ public class NetServer extends Module{
//if the player hasn't acknowledged that it has recieved the packet, send the same thing again
if(connection.currentBaseID < connection.lastSentSnapshotID){
if(showSnapshotSize) Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length);
if(showSnapshotSize)
Log.info("Re-sending snapshot: {0} bytes, ID {1} base {2} baselength {3}", connection.lastSentSnapshot.length, connection.lastSentSnapshotID, connection.lastSentBase, connection.currentBaseSnapshot.length);
sendSplitSnapshot(connection.id, connection.lastSentSnapshot, connection.lastSentSnapshotID, connection.lastSentBase);
return;
}
@@ -409,7 +500,8 @@ public class NetServer extends Module{
dataStream.writeByte(((SyncTrait) entity).getTypeID()); //write type ID
((SyncTrait) entity).write(dataStream); //write entity
int length = syncStream.position() - position; //length must always be less than 127 bytes
if(length > 127) throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!");
if(length > 127)
throw new RuntimeException("Write size for entity of type " + group.getType() + " must not exceed 127!");
dataStream.writeByte(length);
}
}
@@ -433,7 +525,8 @@ public class NetServer extends Module{
//send diff, otherwise
byte[] diff = ByteDeltaEncoder.toDiff(new ByteMatcherHash(connection.currentBaseSnapshot, bytes), encoder);
if(showSnapshotSize) Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length);
if(showSnapshotSize)
Log.info("Shrank snapshot: {0} -> {1}, Base {2} ID {3} base length = {4}", bytes.length, diff.length, connection.currentBaseID, connection.currentBaseID + 1, connection.currentBaseSnapshot.length);
sendSplitSnapshot(connection.id, diff, connection.currentBaseID + 1, connection.currentBaseID);
connection.lastSentSnapshot = diff;
connection.lastSentSnapshotID = connection.currentBaseID + 1;
@@ -445,82 +538,4 @@ public class NetServer extends Module{
e.printStackTrace();
}
}
/**Sends a raw byte[] snapshot to a client, splitting up into chunks when needed.*/
private static void sendSplitSnapshot(int userid, byte[] bytes, int snapshotID, int base){
if(bytes.length < maxSnapshotSize){
Call.onSnapshot(userid, bytes, snapshotID, (short)0, bytes.length, base);
}else{
int remaining = bytes.length;
int offset = 0;
int chunkid = 0;
while(remaining > 0){
int used = Math.min(remaining, maxSnapshotSize);
byte[] toSend;
//re-use sent byte arrays when possible
if(used == maxSnapshotSize){
toSend = reusableSnapArray;
System.arraycopy(bytes, offset, toSend, 0, Math.min(offset + maxSnapshotSize, bytes.length) - offset);
}else {
toSend = Arrays.copyOfRange(bytes, offset, Math.min(offset + maxSnapshotSize, bytes.length));
}
Call.onSnapshot(userid, toSend, snapshotID, (short)chunkid, bytes.length, base);
remaining -= used;
offset += used;
chunkid ++;
}
}
}
public static void onDisconnect(Player player){
Call.sendMessage("[accent]" + player.name + " has disconnected.");
Call.onPlayerDisconnect(player.id);
player.remove();
netServer.connections.remove(player.con.id);
}
@Remote(targets = Loc.client, called = Loc.server)
public static void onAdminRequest(Player player, Player other, AdminAction action){
if(!player.isAdmin){
Log.err("ACCESS DENIED: Player {0} / {1} attempted to perform admin action without proper security access.",
player.name, player.con.address);
return;
}
if(other == null || (other.isAdmin && other != player)){ //fun fact: this means you can ban yourself
Log.err("{0} attempted to perform admin action on nonexistant or admin player.", player.name);
return;
}
if(action == AdminAction.wave) {
//no verification is done, so admins can hypothetically spam waves
//not a real issue, because server owners may want to do just that
state.wavetime = 0f;
}else if(action == AdminAction.ban){
netServer.admins.banPlayerIP(other.con.address);
netServer.kick(other.con.id, KickReason.banned);
Log.info("&lc{0} has banned {1}.", player.name, other.name);
}else if(action == AdminAction.kick){
netServer.kick(other.con.id, KickReason.kick);
Log.info("&lc{0} has kicked {1}.", player.name, other.name);
}else if(action == AdminAction.trace){
//TODO
if(player.con != null) {
Call.onTraceInfo(player.con.id, netServer.admins.getTraceByID(other.uuid));
}else{
NetClient.onTraceInfo(netServer.admins.getTraceByID(other.uuid));
}
Log.info("&lc{0} has requested trace info of {1}.", player.name, other.name);
}
}
@Remote(targets = Loc.client)
public static void connectConfirm(Player player){
player.add();
player.con.hasConnected = true;
Call.sendMessage("[accent]" + player.name + " has connected.");
Log.info("&y{0} has connected.", player.name);
}
}
+138 -42
View File
@@ -12,44 +12,101 @@ import java.util.Locale;
import java.util.Random;
public abstract class Platform{
/**Each separate game platform should set this instance to their own implementation.*/
public static Platform instance = new Platform() {};
/**
* Each separate game platform should set this instance to their own implementation.
*/
public static Platform instance = new Platform(){
};
/**Format the date using the default date formatter.*/
public String format(Date date){return "invalid";}
/**Format a number by adding in commas or periods where needed.*/
public String format(int number){return "invalid";}
/**Show a native error dialog.*/
public void showError(String text){}
/**Add a text input dialog that should show up after the field is tapped.*/
/**
* Format the date using the default date formatter.
*/
public String format(Date date){
return "invalid";
}
/**
* Format a number by adding in commas or periods where needed.
*/
public String format(int number){
return "invalid";
}
/**
* Show a native error dialog.
*/
public void showError(String text){
}
/**
* Add a text input dialog that should show up after the field is tapped.
*/
public void addDialog(TextField field){
addDialog(field, 16);
}
/**See addDialog().*/
public void addDialog(TextField field, int maxLength){}
/**Update discord RPC.*/
public void updateRPC(){}
/**Called when the game is exited.*/
public void onGameExit(){}
/**Open donation dialog. Currently android only.*/
public void openDonations(){}
/**Whether donating is supported.*/
/**
* See addDialog().
*/
public void addDialog(TextField field, int maxLength){
}
/**
* Update discord RPC.
*/
public void updateRPC(){
}
/**
* Called when the game is exited.
*/
public void onGameExit(){
}
/**
* Open donation dialog. Currently android only.
*/
public void openDonations(){
}
/**
* Whether donating is supported.
*/
public boolean canDonate(){
return false;
}
/**Whether discord RPC is supported.*/
public boolean hasDiscord(){return true;}
/**Return the localized name for the locale. This is basically a workaround for GWT not supporting getName().*/
/**
* Whether discord RPC is supported.
*/
public boolean hasDiscord(){
return true;
}
/**
* Return the localized name for the locale. This is basically a workaround for GWT not supporting getName().
*/
public String getLocaleName(Locale locale){
return locale.toString();
}
/**Whether joining games is supported.*/
/**
* Whether joining games is supported.
*/
public boolean canJoinGame(){
return true;
}
/**Whether debug mode is enabled.*/
public boolean isDebug(){return false;}
/**Must be a base64 string 8 bytes in length.*/
/**
* Whether debug mode is enabled.
*/
public boolean isDebug(){
return false;
}
/**
* Must be a base64 string 8 bytes in length.
*/
public String getUUID(){
String uuid = Settings.getString("uuid", "");
if(uuid.isEmpty()){
@@ -62,12 +119,21 @@ public abstract class Platform {
}
return uuid;
}
/**Only used for iOS or android: open the share menu for a map or save.*/
public void shareFile(FileHandle file){}
/**Download a file. Only used on GWT backend.*/
public void downloadFile(String name, byte[] bytes){}
/**Show a file chooser. Desktop only.
/**
* Only used for iOS or android: open the share menu for a map or save.
*/
public void shareFile(FileHandle file){
}
/**
* Download a file. Only used on GWT backend.
*/
public void downloadFile(String name, byte[] bytes){
}
/**
* Show a file chooser. Desktop only.
*
* @param text File chooser title text
* @param content Description of the type of files to be loaded
@@ -75,24 +141,54 @@ public abstract class Platform {
* @param open Whether to open or save files
* @param filetype File extension to filter
*/
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, String filetype){}
/**Use the default thread provider from the kryonet module for this.*/
public void showFileChooser(String text, String content, Consumer<FileHandle> cons, boolean open, String filetype){
}
/**
* Use the default thread provider from the kryonet module for this.
*/
public ThreadProvider getThreadProvider(){
return new ThreadProvider(){
@Override public boolean isOnThread() {return true;}
@Override public void sleep(long ms) {}
@Override public void start(Runnable run) {}
@Override public void stop() {}
@Override public void notify(Object object) {}
@Override public void wait(Object object) {}
@Override
public boolean isOnThread(){
return true;
}
@Override
public void sleep(long ms){
}
@Override
public void start(Runnable run){
}
@Override
public void stop(){
}
@Override
public void notify(Object object){
}
@Override
public void wait(Object object){
}
};
}
//TODO iOS implementation
/**Forces the app into landscape mode. Currently Android only.*/
public void beginForceLandscape(){}
/**
* Forces the app into landscape mode. Currently Android only.
*/
public void beginForceLandscape(){
}
//TODO iOS implementation
/**Stops forcing the app into landscape orientation. Currently Android only.*/
public void endForceLandscape(){}
/**
* Stops forcing the app into landscape orientation. Currently Android only.
*/
public void endForceLandscape(){
}
}
@@ -12,13 +12,12 @@ import static io.anuke.mindustry.Vars.logic;
public class ThreadHandler{
private final Queue<Runnable> toRun = new Queue<>();
private final ThreadProvider impl;
private final Object updateLock = new Object();
private float delta = 1f;
private float smoothDelta = 1f;
private long frame = 0, lastDeltaUpdate;
private float framesSinceUpdate;
private boolean enabled;
private final Object updateLock = new Object();
private boolean rendered = true;
public ThreadHandler(ThreadProvider impl){
@@ -82,6 +81,10 @@ public class ThreadHandler {
}
}
public boolean isEnabled(){
return enabled;
}
public void setEnabled(boolean enabled){
if(enabled){
logic.doUpdate = false;
@@ -98,10 +101,6 @@ public class ThreadHandler {
}
}
public boolean isEnabled(){
return enabled;
}
public boolean doInterpolate(){
return enabled && Gdx.graphics.getFramesPerSecond() - getTPS() > 20 && getTPS() < 30;
}
@@ -166,10 +165,15 @@ public class ThreadHandler {
public interface ThreadProvider{
boolean isOnThread();
void sleep(long ms) throws InterruptedException;
void start(Runnable run);
void stop();
void wait(Object object) throws InterruptedException;
void notify(Object object);
}
}
+10 -25
View File
@@ -37,6 +37,14 @@ import static io.anuke.mindustry.Vars.players;
import static io.anuke.ucore.scene.actions.Actions.*;
public class UI extends SceneModule{
public final MenuFragment menufrag = new MenuFragment();
public final HudFragment hudfrag = new HudFragment();
public final ChatFragment chatfrag = new ChatFragment();
public final PlayerListFragment listfrag = new PlayerListFragment();
public final BackgroundFragment backfrag = new BackgroundFragment();
public final LoadingFragment loadfrag = new LoadingFragment();
public final DebugFragment debugfrag = new DebugFragment();
public AboutDialog about;
public RestartDialog restart;
public LevelDialog levels;
@@ -59,14 +67,6 @@ public class UI extends SceneModule{
public UnlocksDialog unlocks;
public ContentInfoDialog content;
public final MenuFragment menufrag = new MenuFragment();
public final HudFragment hudfrag = new HudFragment();
public final ChatFragment chatfrag = new ChatFragment();
public final PlayerListFragment listfrag = new PlayerListFragment();
public final BackgroundFragment backfrag = new BackgroundFragment();
public final LoadingFragment loadfrag = new LoadingFragment();
public final DebugFragment debugfrag = new DebugFragment();
private Locale lastLocale;
public UI(){
@@ -234,7 +234,8 @@ public class UI extends SceneModule{
public void showTextInput(String title, String text, String def, TextFieldFilter filter, Consumer<String> confirmed){
new Dialog(title, "dialog"){{
content().margin(30).add(text).padRight(6f);
TextField field = content().addField(def, t->{}).size(170f, 50f).get();
TextField field = content().addField(def, t -> {
}).size(170f, 50f).get();
field.setTextFieldFilter((f, c) -> field.getText().length() < 12 && filter.acceptChar(f, c));
Platform.instance.addDialog(field);
buttons().defaults().size(120, 54).pad(4);
@@ -286,20 +287,4 @@ public class UI extends SceneModule{
dialog.keyDown(Keys.BACK, dialog::hide);
dialog.show();
}
public void showConfirmListen(String title, String text, Consumer<Boolean> listener){
FloatingDialog dialog = new FloatingDialog(title);
dialog.content().add(text).pad(4f);
dialog.buttons().defaults().size(200f, 54f).pad(2f);
dialog.buttons().addButton("$text.cancel", () -> {
dialog.hide();
listener.accept(true);
});
dialog.buttons().addButton("$text.ok", () -> {
dialog.hide();
listener.accept(true);
});
dialog.show();
}
}
+31 -15
View File
@@ -11,7 +11,10 @@ import io.anuke.mindustry.core.GameState.State;
import io.anuke.mindustry.game.EventType.TileChangeEvent;
import io.anuke.mindustry.game.EventType.WorldLoadEvent;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.io.Map;
import io.anuke.mindustry.io.MapIO;
import io.anuke.mindustry.io.MapMeta;
import io.anuke.mindustry.io.Maps;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.mapgen.WorldGenerator;
@@ -93,6 +96,10 @@ public class World extends Module{
return currentMap;
}
public void setMap(Map map){
this.currentMap = map;
}
public int width(){
return tiles == null ? 0 : tiles.length;
}
@@ -143,8 +150,10 @@ public class World extends Module{
}
}
/**Resizes the tile array to the specified size and returns the resulting tile array.
* Only use for loading saves!*/
/**
* Resizes the tile array to the specified size and returns the resulting tile array.
* Only use for loading saves!
*/
public Tile[][] createTiles(int width, int height){
if(tiles != null){
clearTileEntities();
@@ -159,14 +168,18 @@ public class World extends Module{
return tiles;
}
/**Call to signify the beginning of map loading.
* TileChangeEvents will not be fired until endMapLoad().*/
/**
* Call to signify the beginning of map loading.
* TileChangeEvents 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.
* A WorldLoadEvent will be fire.*/
/**
* Call to signify the end of map loading. Updates tile occlusions and sets up physics for the world.
* A WorldLoadEvent will be fire.
*/
public void endMapLoad(){
for(int x = 0; x < tiles.length; x++){
for(int y = 0; y < tiles[0].length; y++){
@@ -184,7 +197,9 @@ public class World extends Module{
Events.fire(WorldLoadEvent.class);
}
/**Loads up a procedural map. This does not call play(), but calls reset().*/
/**
* Loads up a procedural map. This does not call play(), but calls reset().
*/
public void loadProceduralMap(){
Timers.mark();
Timers.mark();
@@ -213,10 +228,6 @@ public class World extends Module{
Log.info("Full time to generate: {0}", Timers.elapsed());
}
public void setMap(Map map){
this.currentMap = map;
}
public void loadMap(Map map){
loadMap(map, MathUtils.random(0, 999999));
}
@@ -290,14 +301,19 @@ public class World extends Module{
}
}
/**Raycast, but with world coordinates.*/
/**
* Raycast, but with world coordinates.
*/
public GridPoint2 raycastWorld(float x, float y, float x2, float y2){
return raycast(Mathf.scl2(x, tilesize), Mathf.scl2(y, tilesize),
Mathf.scl2(x2, tilesize), Mathf.scl2(y2, tilesize));
}
/**Input is in block coordinates, not world coordinates.
* @return null if no collisions found, block position otherwise.*/
/**
* Input is in block coordinates, not world coordinates.
*
* @return null if no collisions found, block position otherwise.
*/
public GridPoint2 raycast(int x0f, int y0f, int x1, int y1){
int x0 = x0f;
int y0 = y0f;
@@ -7,11 +7,17 @@ import io.anuke.mindustry.io.MapTileData.TileDataMarker;
import io.anuke.ucore.util.Bits;
public class DrawOperation{
/**Data to apply operation to.*/
/**
* Data to apply operation to.
*/
private MapTileData data;
/**List of per-tile operations that occurred.*/
/**
* List of per-tile operations that occurred.
*/
private Array<TileOperation> operations = new Array<>();
/**Checks for duplicate operations, useful for brushes.*/
/**
* Checks for duplicate operations, useful for brushes.
*/
private IntSet checks = new IntSet();
public DrawOperation(MapTileData data){
@@ -56,14 +56,14 @@ public class MapEditor{
renderer.resize(map.width(), map.height());
}
public void setDrawElevation(int elevation){
this.elevation = (byte)elevation;
}
public byte getDrawElevation(){
return elevation;
}
public void setDrawElevation(int elevation){
this.elevation = (byte) elevation;
}
public int getDrawRotation(){
return rotation;
}
@@ -72,14 +72,14 @@ public class MapEditor{
this.rotation = rotation;
}
public void setDrawTeam(Team team){
this.drawTeam = team;
}
public Team getDrawTeam(){
return drawTeam;
}
public void setDrawTeam(Team team){
this.drawTeam = team;
}
public Block getDrawBlock(){
return drawBlock;
}
@@ -88,14 +88,14 @@ public class MapEditor{
this.drawBlock = block;
}
public void setBrushSize(int size){
this.brushSize = size;
}
public int getBrushSize(){
return brushSize;
}
public void setBrushSize(int size){
this.brushSize = size;
}
public void draw(int x, int y){
draw(x, y, drawBlock);
}
@@ -284,11 +284,13 @@ public class MapEditorDialog extends Dialog implements Disposable{
saved = true;
}
/**Argument format:
/**
* Argument format:
* 0) button name
* 1) description
* 2) icon name
* 3) listener */
* 3) listener
*/
private FloatingDialog createDialog(String title, Object... arguments){
FloatingDialog dialog = new FloatingDialog(title);
@@ -584,7 +586,8 @@ public class MapEditorDialog extends Dialog implements Disposable{
for(Block block : Block.all()){
TextureRegion[] regions = block.getCompactIcon();
if((block.synthetic() && (Recipe.getByResult(block) == null || !control.database().isUnlocked(Recipe.getByResult(block)))) && !debug && block != StorageBlocks.core) continue;
if((block.synthetic() && (Recipe.getByResult(block) == null || !control.database().isUnlocked(Recipe.getByResult(block)))) && !debug && block != StorageBlocks.core)
continue;
if(regions.length == 0 || regions[0] == Draw.region("jjfgj")) continue;
@@ -1,7 +1,7 @@
package io.anuke.mindustry.editor;
import io.anuke.mindustry.io.Map;
import io.anuke.mindustry.core.Platform;
import io.anuke.mindustry.io.Map;
import io.anuke.mindustry.ui.dialogs.FloatingDialog;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.scene.ui.TextButton;
+45 -45
View File
@@ -53,51 +53,6 @@ public class MapView extends Element implements GestureListener{
private float mousex, mousey;
private EditorTool lastTool;
public void setTool(EditorTool tool){
this.tool = tool;
}
public EditorTool getTool() {
return tool;
}
public void clearStack(){
stack.clear();
//TODO clear und obuffer
}
public OperationStack getStack() {
return stack;
}
public void setGrid(boolean grid) {
this.grid = grid;
}
public boolean isGrid() {
return grid;
}
public void undo(){
if(stack.canUndo()){
stack.undo(editor);
}
}
public void redo(){
if(stack.canRedo()){
stack.redo(editor);
}
}
public void addTileOp(TileOperation t){
op.addOperation(t);
}
public boolean checkForDuplicates(short x, short y){
return op.checkDuplicate(x, y);
}
public MapView(MapEditor editor){
this.editor = editor;
@@ -211,6 +166,51 @@ public class MapView extends Element implements GestureListener{
});
}
public EditorTool getTool(){
return tool;
}
public void setTool(EditorTool tool){
this.tool = tool;
}
public void clearStack(){
stack.clear();
//TODO clear und obuffer
}
public OperationStack getStack(){
return stack;
}
public boolean isGrid(){
return grid;
}
public void setGrid(boolean grid){
this.grid = grid;
}
public void undo(){
if(stack.canUndo()){
stack.undo(editor);
}
}
public void redo(){
if(stack.canRedo()){
stack.redo(editor);
}
}
public void addTileOp(TileOperation t){
op.addOperation(t);
}
public boolean checkForDuplicates(short x, short y){
return op.checkDuplicate(x, y);
}
@Override
public void act(float delta){
super.act(delta);
@@ -24,13 +24,17 @@ import io.anuke.ucore.util.Translator;
import static io.anuke.mindustry.Vars.*;
/**Utility class for damaging in an area.*/
/**
* Utility class for damaging in an area.
*/
public class Damage{
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
private static Translator tr = new Translator();
/**Creates a dynamic explosion based on specified parameters.*/
/**
* 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){
for(int i = 0; i < Mathf.clamp(power / 20, 0, 6); i++){
int branches = 5 + Mathf.clamp((int) (power / 30), 1, 20);
@@ -76,8 +80,10 @@ public class Damage {
}
}
/**Damages entities in a line.
* Only enemies of the specified team are damaged.*/
/**
* Damages entities in a line.
* Only enemies of the specified team are damaged.
*/
public static void collideLine(SolidEntity hitter, Team team, Effect effect, float x, float y, float angle, float length){
tr.trns(angle, length);
rect.setPosition(x, y).setSize(tr.x, tr.y);
@@ -120,7 +126,9 @@ public class Damage {
Units.getNearbyEnemies(team, rect, cons);
}
/**Damages all entities and blocks in a radius that are enemies of the team.*/
/**
* Damages all entities and blocks in a radius that are enemies of the team.
*/
public static void damageUnits(Team team, float x, float y, float size, float damage, Predicate<Unit> predicate, Consumer<Unit> acceptor){
Consumer<Unit> cons = entity -> {
if(!predicate.test(entity)) return;
@@ -141,12 +149,16 @@ public class Damage {
}
}
/**Damages everything in a radius.*/
/**
* Damages everything in a radius.
*/
public static void damage(float x, float y, float radius, float damage){
damage(null, x, y, radius, damage);
}
/**Damages all entities and blocks in a radius that are enemies of the team.*/
/**
* Damages all entities and blocks in a radius that are enemies of the team.
*/
public static void damage(Team team, float x, float y, float radius, float damage){
Consumer<Unit> cons = entity -> {
if(entity.team == team || entity.distanceTo(x, y) > radius){
@@ -26,7 +26,10 @@ import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.blocks.Floor;
import io.anuke.mindustry.world.blocks.storage.CoreBlock.CoreEntity;
import io.anuke.mindustry.world.blocks.units.MechFactory;
import io.anuke.ucore.core.*;
import io.anuke.ucore.core.Core;
import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Inputs;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.entities.trait.SolidTrait;
import io.anuke.ucore.graphics.Draw;
@@ -40,13 +43,11 @@ import java.io.IOException;
import static io.anuke.mindustry.Vars.*;
public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTrait{
public static final int timerSync = 2;
private static final int timerShootLeft = 0;
private static final int timerShootRight = 1;
public static final int timerSync = 2;
//region instance variables, constructor
public float baseRotation;
public float pointerX, pointerY;
@@ -82,6 +83,30 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
//region unit and event overrides, utility methods
@Remote(in = In.entities, targets = Loc.server, called = Loc.server)
public static void onPlayerDamage(Player player, float amount){
if(player == null) return;
player.hitTime = hitDuration;
player.health -= amount;
}
@Remote(in = In.entities, targets = Loc.server, called = Loc.server)
public static void onPlayerDeath(Player player){
if(player == null) return;
player.dead = true;
player.placeQueue.clear();
player.dropCarry();
float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f);
float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize() / 2f, Palette.darkFlame);
ScorchDecal.create(player.x, player.y);
player.onDeath();
}
@Override
public Timer getTimer(){
@@ -215,31 +240,6 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
return super.collides(other) || other instanceof ItemDrop;
}
@Remote(in = In.entities, targets = Loc.server, called = Loc.server)
public static void onPlayerDamage(Player player, float amount){
if(player == null) return;
player.hitTime = hitDuration;
player.health -= amount;
}
@Remote(in = In.entities, targets = Loc.server, called = Loc.server)
public static void onPlayerDeath(Player player){
if(player == null) return;
player.dead = true;
player.placeQueue.clear();
player.dropCarry();
float explosiveness = 2f + (player.inventory.hasItem() ? player.inventory.getItem().item.explosiveness * player.inventory.getItem().amount : 0f);
float flammability = (player.inventory.hasItem() ? player.inventory.getItem().item.flammability * player.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(player.x, player.y, flammability, explosiveness, 0f, player.getSize()/2f, Palette.darkFlame);
ScorchDecal.create(player.x, player.y);
player.onDeath();
}
@Override
public void set(float x, float y){
this.x = x;
@@ -402,7 +402,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
Draw.tscl(fontScale);
}
/**Draw all current build requests. Does not draw the beam effect, only the positions.*/
/**
* Draw all current build requests. Does not draw the beam effect, only the positions.
*/
public void drawBuildRequests(){
synchronized(getPlaceQueue()){
for(BuildRequest request : getPlaceQueue()){
@@ -661,7 +663,9 @@ public class Player extends Unit implements BuilderTrait, CarryTrait, ShooterTra
team = (team == Team.blue ? Team.red : Team.blue);
}
/**Resets all values of the player.*/
/**
* Resets all values of the player.
*/
public void reset(){
status.clear();
team = Team.blue;
@@ -4,12 +4,16 @@ import com.badlogic.gdx.math.Vector2;
import io.anuke.mindustry.entities.traits.TargetTrait;
import io.anuke.ucore.util.Mathf;
/**Class for predicting shoot angles based on velocities of targets.*/
/**
* Class for predicting shoot angles based on velocities of targets.
*/
public class Predict{
private static Vector2 vec = new Vector2();
private static Vector2 vresult = new Vector2();
/**Calculates of intercept of a stationary and moving target. Do not call from multiple threads!
/**
* Calculates of intercept of a stationary and moving target. Do not call from multiple threads!
*
* @param srcx X of shooter
* @param srcy Y of shooter
* @param dstx X of target
@@ -17,7 +21,8 @@ public class Predict {
* @param dstvx X velocity of target (subtract shooter X velocity if needed)
* @param dstvy Y velocity of target (subtract shooter Y velocity if needed)
* @param v speed of bullet
* @return the intercept location*/
* @return the intercept location
*/
public static Vector2 intercept(float srcx, float srcy, float dstx, float dsty, float dstvx, float dstvy, float v){
float tx = dstx - srcx,
ty = dsty - srcy;
@@ -44,7 +49,9 @@ public class Predict {
return sol;
}
/**See {@link #intercept(float, float, float, float, float, float, float)}.*/
/**
* See {@link #intercept(float, float, float, float, float, float, float)}.
*/
public static Vector2 intercept(TargetTrait src, TargetTrait dst, float v){
return intercept(src.getX(), src.getY(), dst.getX(), dst.getY(), dst.getVelocity().x - src.getVelocity().x, dst.getVelocity().x - src.getVelocity().y, v);
}
@@ -12,7 +12,9 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**Class for controlling status effects on an entity.*/
/**
* Class for controlling status effects on an entity.
*/
public class StatusController implements Saveable{
private static final StatusEntry globalResult = new StatusEntry();
private static final Array<StatusEntry> removals = new ThreadArray<>();
@@ -37,11 +37,11 @@ import static io.anuke.mindustry.Vars.world;
public class TileEntity extends BaseEntity implements TargetTrait{
public static final float timeToSleep = 60f * 4; //4 seconds to fall asleep
/**This value is only used for debugging.*/
public static int sleepingEntities = 0;
private static final ObjectSet<Tile> tmpTiles = new ObjectSet<>();
/**
* This value is only used for debugging.
*/
public static int sleepingEntities = 0;
public Tile tile;
public Timer timer;
public float health;
@@ -51,13 +51,25 @@ public class TileEntity extends BaseEntity implements TargetTrait {
public LiquidModule liquids;
public ConsumeModule cons;
//list of (cached) tiles with entities in proximity, used for outputting to
//TODO implement
/**List of (cached) tiles with entities in proximity, used for outputting to*/
private Array<Tile> proximity = new Array<>(8);
private boolean dead = false;
private boolean sleeping;
private float sleepTime;
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDamage(Tile tile, float health){
if(tile.entity != null){
tile.entity.health = health;
}
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDestroyed(Tile tile){
if(tile.entity == null) return;
tile.entity.onDeath();
}
/**Sets this tile entity data to this tile, and adds it if necessary.*/
public TileEntity init(Tile tile, boolean added){
this.tile = tile;
@@ -75,8 +87,10 @@ public class TileEntity extends BaseEntity implements TargetTrait {
return this;
}
/**Call when nothing is happening to the entity.
* This increments the internal sleep timer.*/
/**
* Call when nothing is happening to the entity.
* This increments the internal sleep timer.
*/
public void sleep(){
sleepTime += Timers.delta();
if(!sleeping && sleepTime >= timeToSleep){
@@ -86,8 +100,10 @@ public class TileEntity extends BaseEntity implements TargetTrait {
}
}
/**Call when something just happened to the entity.
* If the entity was sleeping, this enables it. This also resets the sleep timer.*/
/**
* Call when something just happened to the entity.
* If the entity was sleeping, this enables it. This also resets the sleep timer.
*/
public void wakeUp(){
sleepTime = 0f;
if(sleeping){
@@ -105,8 +121,11 @@ public class TileEntity extends BaseEntity implements TargetTrait {
return dead;
}
public void write(DataOutputStream stream) throws IOException{}
public void read(DataInputStream stream) throws IOException{}
public void write(DataOutputStream stream) throws IOException{
}
public void read(DataInputStream stream) throws IOException{
}
private void onDeath(){
if(!dead){
@@ -231,17 +250,4 @@ public class TileEntity extends BaseEntity implements TargetTrait {
public EntityGroup targetGroup(){
return tileGroup;
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDamage(Tile tile, float health){
if(tile.entity != null){
tile.entity.health = health;
}
}
@Remote(called = Loc.server, in = In.blocks)
public static void onTileDestroyed(Tile tile){
if(tile.entity == null) return;
tile.entity.onDeath();
}
}
+34 -12
View File
@@ -32,11 +32,17 @@ import static io.anuke.mindustry.Vars.state;
import static io.anuke.mindustry.Vars.world;
public abstract class Unit extends DestructibleEntity implements SaveTrait, TargetTrait, SyncTrait, DrawTrait, TeamTrait, CarriableTrait, InventoryTrait{
/**total duration of hit flash effect*/
/**
* total duration of hit flash effect
*/
public static final float hitDuration = 9f;
/**Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks.*/
/**
* Percision divisor of velocity, used when writing. For example a value of '2' would mean the percision is 1/2 = 0.5-size chunks.
*/
public static final float velocityPercision = 8f;
/**Maximum absolute value of a velocity vector component.*/
/**
* Maximum absolute value of a velocity vector component.
*/
public static final float maxAbsVelocity = 127f / velocityPercision;
public static final float elevationScale = 4f;
@@ -71,13 +77,13 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
@Override
public void setCarrier(CarryTrait carrier) {
this.carrier = carrier;
public CarryTrait getCarrier(){
return carrier;
}
@Override
public CarryTrait getCarrier() {
return carrier;
public void setCarrier(CarryTrait carrier){
this.carrier = carrier;
}
@Override
@@ -201,14 +207,17 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
public void avoidOthers(float avoidRange){
EntityPhysics.getNearby(getGroup(), x, y, avoidRange * 2f, t -> {
if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t)) return;
if(t == this || (t instanceof Unit && (((Unit) t).isDead() || (((Unit) t).isFlying() != isFlying()) || ((Unit) t).getCarrier() == this) || getCarrier() == t))
return;
float dst = distanceTo(t);
if(dst > avoidRange) return;
velocity.add(moveVector.set(x, y).sub(t.getX(), t.getY()).setLength(1f * (1f - (dst / avoidRange))));
});
}
/**Updates velocity and status effects.*/
/**
* Updates velocity and status effects.
*/
public void updateVelocityStatus(float drag, float maxVelocity){
if(isCarried()){ //carried units do not take into account velocity normally
set(carrier.getX(), carrier.getY());
@@ -302,9 +311,14 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
return inventory.totalAmmo() / (float) inventory.ammoCapacity();
}
public void drawUnder(){}
public void drawOver(){}
public void drawShadow(){}
public void drawUnder(){
}
public void drawOver(){
}
public void drawShadow(){
}
public void drawView(){
Fill.circle(x, y, getViewDistance());
@@ -319,12 +333,20 @@ public abstract class Unit extends DestructibleEntity implements SaveTrait, Targ
}
public abstract TextureRegion getIconRegion();
public abstract int getItemCapacity();
public abstract int getAmmoCapacity();
public abstract float getArmor();
public abstract boolean acceptsAmmo(Item item);
public abstract void addAmmo(Item item);
public abstract float getMass();
public abstract boolean isFlying();
public abstract float getSize();
}
@@ -8,15 +8,16 @@ import io.anuke.mindustry.type.AmmoType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.ItemStack;
import java.io.*;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class UnitInventory implements Saveable{
private final Unit unit;
private Array<AmmoEntry> ammos = new Array<>();
private int totalAmmo;
private ItemStack item = new ItemStack(Items.stone, 0);
private final Unit unit;
public UnitInventory(Unit unit){
this.unit = unit;
}
@@ -53,7 +54,9 @@ public class UnitInventory implements Saveable{
item.amount = iamount;
}
/**Returns ammo range, or MAX_VALUE if this inventory has no ammo.*/
/**
* Returns ammo range, or MAX_VALUE if this inventory has no ammo.
*/
public float getAmmoRange(){
return hasAmmo() ? getAmmo().getRange() : Float.MAX_VALUE;
}
+51 -17
View File
@@ -15,7 +15,9 @@ import io.anuke.ucore.function.Predicate;
import static io.anuke.mindustry.Vars.*;
/**Utility class for unit and team interactions.*/
/**
* Utility class for unit and team interactions.
*/
public class Units{
private static Rectangle rect = new Rectangle();
private static Rectangle hitrect = new Rectangle();
@@ -23,7 +25,9 @@ public class Units {
private static float cdist;
private static boolean boolResult;
/**Validates a target.
/**
* Validates a target.
*
* @param target The target to validate
* @param team The team of the thing doing tha targeting
* @param x The X position of the thing doign the targeting
@@ -36,17 +40,23 @@ public class Units {
}
/**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/
/**
* See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}
*/
public static boolean invalidateTarget(TargetTrait target, Team team, float x, float y){
return invalidateTarget(target, team, x, y, Float.MAX_VALUE);
}
/**See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}*/
/**
* See {@link #invalidateTarget(TargetTrait, Team, float, float, float)}
*/
public static boolean invalidateTarget(TargetTrait target, Unit targeter){
return invalidateTarget(target, targeter.team, targeter.x, targeter.y, targeter.inventory.getAmmoRange());
}
/**Returns whether there are any entities on this tile.*/
/**
* Returns whether there are any entities on this tile.
*/
public static boolean anyEntities(Tile tile){
Block type = tile.block();
rect.setSize(type.size * tilesize, type.size * tilesize);
@@ -68,7 +78,9 @@ public class Units {
return boolResult;
}
/**Returns whether there are any entities on this tile, with the hitbox expanded.*/
/**
* Returns whether there are any entities on this tile, with the hitbox expanded.
*/
public static boolean anyEntities(Tile tile, float expansion, Predicate<Unit> pred){
Block type = tile.block();
rect.setSize(type.size * tilesize + expansion, type.size * tilesize + expansion);
@@ -90,7 +102,9 @@ public class Units {
return value[0];
}
/**Returns the neareset ally tile in a range.*/
/**
* Returns the neareset ally tile in a range.
*/
public static TileEntity findAllyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
for(Team enemy : state.teams.alliesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
@@ -101,7 +115,9 @@ public class Units {
return null;
}
/**Returns the neareset enemy tile in a range.*/
/**
* Returns the neareset enemy tile in a range.
*/
public static TileEntity findEnemyTile(Team team, float x, float y, float range, Predicate<Tile> pred){
for(Team enemy : state.teams.enemiesOf(team)){
TileEntity entity = world.indexer().findTile(enemy, x, y, range, pred);
@@ -112,7 +128,9 @@ public class Units {
return null;
}
/**Iterates over all units on all teams, including players.*/
/**
* Iterates over all units on all teams, including players.
*/
public static void allUnits(Consumer<Unit> cons){
//check all unit groups first
for(EntityGroup<BaseUnit> group : unitGroups){
@@ -129,7 +147,9 @@ public class Units {
}
}
/**Returns the closest target enemy. First, units are checked, then tile entities.*/
/**
* Returns the closest target enemy. First, units are checked, then tile entities.
*/
public static TargetTrait getClosestTarget(Team team, float x, float y, float range){
Unit unit = getClosestEnemy(team, x, y, range, u -> true);
if(unit != null){
@@ -139,7 +159,9 @@ public class Units {
}
}
/**Returns the closest enemy of this team. Filter by predicate.*/
/**
* Returns the closest enemy of this team. Filter by predicate.
*/
public static Unit getClosestEnemy(Team team, float x, float y, float range, Predicate<Unit> predicate){
result = null;
cdist = 0f;
@@ -162,7 +184,9 @@ public class Units {
return result;
}
/**Returns the closest ally of this team. Filter by predicate.*/
/**
* Returns the closest ally of this team. Filter by predicate.
*/
public static Unit getClosest(Team team, float x, float y, float range, Predicate<Unit> predicate){
result = null;
cdist = 0f;
@@ -185,7 +209,9 @@ public class Units {
return result;
}
/**Iterates over all units in a rectangle.*/
/**
* Iterates over all units in a rectangle.
*/
public static void getNearby(Team team, Rectangle rect, Consumer<Unit> cons){
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
@@ -199,7 +225,9 @@ public class Units {
});
}
/**Iterates over all units in a circle around this position.*/
/**
* Iterates over all units in a circle around this position.
*/
public static void getNearby(Team team, float x, float y, float radius, Consumer<Unit> cons){
rect.setSize(radius * 2).setCenter(x, y);
@@ -220,7 +248,9 @@ public class Units {
});
}
/**Iterates over all units in a rectangle.*/
/**
* Iterates over all units in a rectangle.
*/
public static void getNearby(Rectangle rect, Consumer<Unit> cons){
for(Team team : Team.all){
@@ -234,7 +264,9 @@ public class Units {
EntityPhysics.getNearby(playerGroup, rect, player -> cons.accept((Unit) player));
}
/**Iterates over all units that are enemies of this team.*/
/**
* Iterates over all units that are enemies of this team.
*/
public static void getNearbyEnemies(Team team, Rectangle rect, Consumer<Unit> cons){
ObjectSet<Team> targets = state.teams.enemiesOf(team);
@@ -253,7 +285,9 @@ public class Units {
});
}
/**Iterates over all units.*/
/**
* Iterates over all units.
*/
public static void getAllUnits(Consumer<Unit> cons){
for(Team team : Team.all){
@@ -12,7 +12,9 @@ import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Mathf;
/**A BulletType for most ammo-based bullets shot from turrets and units.*/
/**
* A BulletType for most ammo-based bullets shot from turrets and units.
*/
public class BasicBulletType extends BulletType{
public Color backColor = Palette.bulletYellowBack, frontColor = Palette.bulletYellow;
public float bulletWidth = 5f, bulletHeight = 7f;
@@ -23,7 +25,9 @@ public class BasicBulletType extends BulletType {
public float fragVelocityMin = 0.2f, fragVelocityMax = 1f;
public BulletType fragBullet = null;
/**Use a negative value to disable splash damage.*/
/**
* Use a negative value to disable splash damage.
*/
public float splashDamageRadius = -1f;
public float splashDamage = 6f;
@@ -28,12 +28,16 @@ import static io.anuke.mindustry.Vars.world;
public class Bullet extends BulletEntity<BulletType> implements TeamTrait, SyncTrait{
private static Vector2 vector = new Vector2();
public Timer timer = new Timer(3);
private Team team;
private Object data;
private boolean supressCollision;
public Timer timer = new Timer(3);
/**
* Internal use only!
*/
public Bullet(){
}
public static void create(BulletType type, TeamTrait owner, float x, float y, float angle){
create(type, owner, owner.getTeam(), x, y, angle);
@@ -86,9 +90,6 @@ public class Bullet extends BulletEntity<BulletType> implements TeamTrait, SyncT
create(type, null, Team.none, x, y, angle);
}
/**Internal use only!*/
public Bullet(){}
public boolean collidesTiles(){
return type.collidesTiles;
}
@@ -13,23 +13,41 @@ public abstract class BulletType extends BaseBulletType<Bullet> implements Conte
private static Array<BulletType> types = new Array<>();
public final int id;
/**Knockback in velocity.*/
/**
* Knockback in velocity.
*/
public float knockback;
/**Whether this bullet hits tiles.*/
/**
* Whether this bullet hits tiles.
*/
public boolean hitTiles = true;
/**Status effect applied on hit.*/
/**
* Status effect applied on hit.
*/
public StatusEffect status = StatusEffects.none;
/**Intensity of applied status effect in terms of duration.*/
/**
* Intensity of applied status effect in terms of duration.
*/
public float statusIntensity = 0.5f;
/**What fraction of armor is pierced, 0-1*/
/**
* What fraction of armor is pierced, 0-1
*/
public float armorPierce = 0f;
/**Whether to sync this bullet to clients.*/
/**
* Whether to sync this bullet to clients.
*/
public boolean syncable;
/**Whether this bullet type collides with tiles.*/
/**
* Whether this bullet type collides with tiles.
*/
public boolean collidesTiles = true;
/**Whether this bullet types collides with anything at all.*/
/**
* Whether this bullet types collides with anything at all.
*/
public boolean collides = true;
/**Whether velocity is inherited from the shooter.*/
/**
* Whether velocity is inherited from the shooter.
*/
public boolean keepVelocity = true;
public BulletType(float speed, float damage){
@@ -43,6 +61,14 @@ public abstract class BulletType extends BaseBulletType<Bullet> implements Conte
types.add(this);
}
public static BulletType getByID(int id){
return types.get(id);
}
public static Array<BulletType> all(){
return types;
}
@Override
public void hit(Bullet b, float hitx, float hity){
Effects.effect(hiteffect, hitx, hity, b.angle());
@@ -62,12 +88,4 @@ public abstract class BulletType extends BaseBulletType<Bullet> implements Conte
public Array<? extends Content> getAll(){
return types;
}
public static BulletType getByID(int id){
return types.get(id);
}
public static Array<BulletType> all(){
return types;
}
}
@@ -10,7 +10,9 @@ import io.anuke.ucore.util.Mathf;
import static io.anuke.mindustry.Vars.groundEffectGroup;
/**Class for creating block rubble on the ground.*/
/**
* Class for creating block rubble on the ground.
*/
public abstract class Decal extends TimedEntity implements BelowLiquidTrait, DrawTrait{
private static final Color color = Color.valueOf("52504e");
@@ -41,7 +41,15 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
private float baseFlammability = -1, puddleFlammability;
private float lifetime;
/**Start a fire on the tile. If there already is a file there, refreshes its lifetime.*/
/**
* Deserialization use only!
*/
public Fire(){
}
/**
* Start a fire on the tile. If there already is a file there, refreshes its lifetime.
*/
public static void create(Tile tile){
if(Net.client() || tile == null) return; //not clientside.
@@ -60,15 +68,19 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
}
}
/**Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.*/
/**
* Attempts to extinguish a fire by shortening its life. If there is no fire here, does nothing.
*/
public static void extinguish(Tile tile, float intensity){
if(tile != null && map.containsKey(tile.packedPosition())){
map.get(tile.packedPosition()).time += intensity * Timers.delta();
}
}
/**Deserialization use only!*/
public Fire(){}
@Remote(called = Loc.server, in = In.entities)
public static void onFireRemoved(int fireid){
fireGroup.removeByID(fireid);
}
@Override
public float lifetime(){
@@ -196,9 +208,4 @@ public class Fire extends TimedEntity implements SaveTrait, SyncTrait, Poolable
public EntityGroup targetGroup(){
return fireGroup;
}
@Remote(called = Loc.server, in = In.entities)
public static void onFireRemoved(int fireid){
fireGroup.removeByID(fireid);
}
}
@@ -9,7 +9,9 @@ import io.anuke.ucore.entities.impl.EffectEntity;
import io.anuke.ucore.function.EffectRenderer;
import io.anuke.ucore.util.Mathf;
/**A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.*/
/**
* A ground effect contains an effect that is rendered on the ground layer as opposed to the top layer.
*/
public class GroundEffectEntity extends EffectEntity{
private boolean once;
@@ -53,12 +55,18 @@ public class GroundEffectEntity extends EffectEntity {
once = false;
}
/**An effect that is rendered on the ground layer as opposed to the top layer.*/
/**
* An effect that is rendered on the ground layer as opposed to the top layer.
*/
public static class GroundEffect extends Effect{
/**How long this effect stays on the ground when static.*/
/**
* How long this effect stays on the ground when static.
*/
public final float staticLife;
/**If true, this effect will stop and lie on the ground for a specific duration,
* after its initial lifetime is over.*/
/**
* If true, this effect will stop and lie on the ground for a specific duration,
* after its initial lifetime is over.
*/
public final boolean isStatic;
public GroundEffect(float life, float staticLife, EffectRenderer draw){
@@ -46,6 +46,14 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
private float time;
private float sinktime;
/**
* Internal use only!
*/
public ItemDrop(){
hitbox.setSize(5f);
hitboxTile.setSize(5f);
}
public static ItemDrop create(Item item, int amount, float x, float y, float angle){
ItemDrop drop = new ItemDrop();
drop.item = item;
@@ -73,12 +81,6 @@ public class ItemDrop extends SolidEntity implements SaveTrait, SyncTrait, DrawT
}
}
/**Internal use only!*/
public ItemDrop(){
hitbox.setSize(5f);
hitboxTile.setSize(5f);
}
public Item getItem(){
return item;
}
@@ -32,17 +32,22 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
private PosTrait to;
private Runnable done;
public ItemTransfer(){
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
public static void transferAmmo(Item item, float x, float y, Unit to){
if(to == null) return;
to.addAmmo(item);
create(item, x, y, to, () -> {});
create(item, x, y, to, () -> {
});
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
public static void transferItemEffect(Item item, float x, float y, Unit to){
if(to == null) return;
create(item, x, y, to, () -> {});
create(item, x, y, to, () -> {
});
}
@Remote(in = In.entities, called = Loc.server, unreliable = true)
@@ -55,7 +60,8 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
public static void transferItemTo(Item item, int amount, float x, float y, Tile tile){
if(tile == null) return;
for(int i = 0; i < Mathf.clamp(amount / 3, 1, 8); i++){
Timers.run(i*3, () -> create(item, x, y, tile, () -> {}));
Timers.run(i * 3, () -> create(item, x, y, tile, () -> {
}));
}
tile.entity.items.add(item, amount);
}
@@ -70,8 +76,6 @@ public class ItemTransfer extends TimedEntity implements DrawTrait{
tr.add();
}
public ItemTransfer(){}
@Override
public float lifetime(){
return 60;
@@ -46,7 +46,15 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
private Color color = Palette.lancerLaser;
private SeedRandom random = new SeedRandom();
/**Create a lighting branch at a location. Use Team.none to damage everyone.*/
/**
* For pooling use only. Do not call directly!
*/
public Lightning(){
}
/**
* Create a lighting branch at a location. Use Team.none to damage everyone.
*/
public static void create(Team team, Effect effect, Color color, float damage, float x, float y, float targetAngle, int length){
CallEntity.createLighting(lastSeed++, team, effect, color, damage, x, y, targetAngle, length);
}
@@ -115,9 +123,6 @@ public class Lightning extends TimedEntity implements Poolable, DrawTrait, SyncT
l.add();
}
/**For pooling use only. Do not call directly!*/
public Lightning(){}
@Override
public boolean isSyncing(){
return false;
@@ -58,17 +58,29 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
private float accepting;
private byte generation;
/**Deposists a puddle between tile and source.*/
/**
* Deserialization use only!
*/
public Puddle(){
}
/**
* Deposists 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.*/
/**
* Deposists a puddle at a tile.
*/
public static void deposit(Tile tile, Liquid liquid, float amount){
deposit(tile, tile, liquid, amount, 0);
}
/**Returns the puddle on the specified tile. May return null.*/
/**
* Returns the puddle on the specified tile. May return null.
*/
public static Puddle getPuddle(Tile tile){
return map.get(tile.packedPosition());
}
@@ -108,13 +120,17 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
}
}
/**Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.*/
/**
* Returns whether the first liquid can 'stay' on the second one.
* Currently, the only place where this can happen is oil on water.
*/
private static boolean canStayOn(Liquid liquid, Liquid other){
return liquid == Liquids.oil && other == Liquids.water;
}
/**Reacts two liquids together at a location.*/
/**
* Reacts two liquids together at a location.
*/
private static float reactPuddle(Liquid dest, Liquid liquid, float amount, Tile tile, float x, float y){
if((dest.flammability > 0.3f && liquid.temperature > 0.7f) ||
(liquid.flammability > 0.3f && dest.temperature > 0.7f)){ //flammable liquid + hot liquid
@@ -136,8 +152,10 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
return 0f;
}
/**Deserialization use only!*/
public Puddle(){}
@Remote(called = Loc.server, in = In.entities)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
public float getFlammability(){
return liquid.flammability * amount;
@@ -293,9 +311,4 @@ public class Puddle extends BaseEntity implements SaveTrait, Poolable, DrawTrait
public EntityGroup targetGroup(){
return puddleGroup;
}
@Remote(called = Loc.server, in = In.entities)
public static void onPuddleRemoved(int puddleid){
puddleGroup.removeByID(puddleid);
}
}
@@ -6,7 +6,9 @@ import io.anuke.ucore.util.Mathf;
public class RubbleDecal extends Decal{
private int size;
/**Creates a rubble effect at a position. Provide a block size to use.*/
/**
* Creates a rubble effect at a position. Provide a block size to use.
*/
public static void create(float x, float y, int size){
RubbleDecal decal = new RubbleDecal();
decal.size = size;
@@ -14,12 +14,11 @@ import static io.anuke.mindustry.Vars.shieldGroup;
//todo re-implement
public class Shield extends BaseEntity implements DrawTrait{
private final Tile tile;
public boolean active;
public boolean hitPlayers = false;
public float radius = 0f;
private float uptime = 0f;
private final Tile tile;
public Shield(Tile tile){
this.tile = tile;
@@ -1,5 +1,7 @@
package io.anuke.mindustry.entities.traits;
/**A flag interface for marking an effect as appearing below liquids.*/
/**
* A flag interface for marking an effect as appearing below liquids.
*/
public interface BelowLiquidTrait{
}
@@ -36,29 +36,43 @@ import java.util.Arrays;
import static io.anuke.mindustry.Vars.tilesize;
import static io.anuke.mindustry.Vars.world;
/**Interface for units that build, break or mine things.*/
/**
* Interface for units that build, break or mine things.
*/
public interface BuilderTrait extends Entity{
//these are not instance variables!
Translator[] tmptr = {new Translator(), new Translator(), new Translator(), new Translator()};
float placeDistance = 140f;
float mineDistance = 70f;
/**Returns the queue for storing build requests.*/
/**
* Returns the queue for storing build requests.
*/
Queue<BuildRequest> getPlaceQueue();
/**Returns the tile this builder is currently mining.*/
/**
* Returns the tile this builder is currently mining.
*/
Tile getMineTile();
/**Sets the tile this builder is currently mining.*/
/**
* Sets the tile this builder is currently mining.
*/
void setMineTile(Tile tile);
/**Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc.*/
/**
* Returns the minining speed of this miner. 1 = standard, 0.5 = half speed, 2 = double speed, etc.
*/
float getMinePower();
/**Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.*/
/**
* Build power, can be any float. 1 = builds recipes in normal time, 0 = doesn't build at all.
*/
float getBuildPower(Tile tile);
/**Whether this type of builder can begin creating new blocks.*/
/**
* Whether this type of builder can begin creating new blocks.
*/
default boolean canCreateBlocks(){
return true;
}
@@ -106,13 +120,17 @@ public interface BuilderTrait extends Entity{
}
}
/**Return whether this builder's place queue contains items.*/
/**
* Return whether this builder's place queue contains items.
*/
default boolean isBuilding(){
return getPlaceQueue().size != 0;
}
/**If a place request matching this signature is present, it is removed.
* Otherwise, a new place request is added to the queue.*/
/**
* If a place request matching this signature is present, it is removed.
* Otherwise, a new place request is added to the queue.
*/
default void replaceBuilding(int x, int y, int rotation, Recipe recipe){
synchronized(getPlaceQueue()){
for(BuildRequest request : getPlaceQueue()){
@@ -127,12 +145,16 @@ public interface BuilderTrait extends Entity{
addBuildRequest(new BuildRequest(x, y, rotation, recipe));
}
/**Clears the placement queue.*/
/**
* Clears the placement queue.
*/
default void clearBuilding(){
getPlaceQueue().clear();
}
/**Add another build requests to the tail of the queue, if it doesn't exist there yet.*/
/**
* Add another build requests to the tail of the queue, if it doesn't exist there yet.
*/
default void addBuildRequest(BuildRequest place){
synchronized(getPlaceQueue()){
for(BuildRequest request : getPlaceQueue()){
@@ -144,16 +166,20 @@ public interface BuilderTrait extends Entity{
}
}
/**Return the build requests currently active, or the one at the top of the queue.
* May return null.*/
/**
* Return the build requests currently active, or the one at the top of the queue.
* May return null.
*/
default BuildRequest getCurrentRequest(){
synchronized(getPlaceQueue()){
return getPlaceQueue().size == 0 ? null : getPlaceQueue().first();
}
}
/**Update building mechanism for this unit.
* This includes mining.*/
/**
* Update building mechanism for this unit.
* This includes mining.
*/
default void updateBuilding(Unit unit){
BuildRequest current = getCurrentRequest();
@@ -203,7 +229,9 @@ public interface BuilderTrait extends Entity{
current.progress = entity.progress();
}
/**Do not call directly.*/
/**
* Do not call directly.
*/
default void updateMining(Unit unit){
Tile tile = getMineTile();
@@ -229,7 +257,9 @@ public interface BuilderTrait extends Entity{
}
}
/**Draw placement effects for an entity. This includes mining*/
/**
* Draw placement effects for an entity. This includes mining
*/
default void drawBuilding(Unit unit){
BuildRequest request;
@@ -286,7 +316,9 @@ public interface BuilderTrait extends Entity{
Draw.color();
}
/**Internal use only.*/
/**
* Internal use only.
*/
default void drawMining(Unit unit){
Tile tile = getMineTile();
@@ -313,7 +345,9 @@ public interface BuilderTrait extends Entity{
Draw.color();
}
/**Class for storing build requests. Can be either a place or remove request.*/
/**
* Class for storing build requests. Can be either a place or remove request.
*/
class BuildRequest{
public final int x, y, rotation;
public final Recipe recipe;
@@ -321,7 +355,9 @@ public interface BuilderTrait extends Entity{
public float progress;
/**This creates a build request.*/
/**
* This creates a build request.
*/
public BuildRequest(int x, int y, int rotation, Recipe recipe){
this.x = x;
this.y = y;
@@ -330,7 +366,9 @@ public interface BuilderTrait extends Entity{
this.remove = false;
}
/**This creates a remove request.*/
/**
* This creates a remove request.
*/
public BuildRequest(int x, int y){
this.x = x;
this.y = y;
@@ -8,6 +8,7 @@ public interface CarriableTrait extends TeamTrait, TargetTrait, SolidTrait{
return getCarrier() != null;
}
void setCarrier(CarryTrait carrier);
CarryTrait getCarrier();
void setCarrier(CarryTrait carrier);
}
@@ -10,28 +10,6 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.entities.trait.SolidTrait;
public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
/**Returns the thing this carrier is carrying.*/
CarriableTrait getCarry();
/**Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state.*/
void setCarry(CarriableTrait unit);
/**Returns maximum mass this carrier can carry.*/
float getCarryWeight();
/**Drops the unit that is being carried, if applicable.*/
default void dropCarry(){
carry(null);
}
default void dropCarryLocal(){
setCarryOf(null, this, null);
}
/**Do not override unless absolutely necessary.
* Carries a unit. To drop a unit, call with {@code null}.*/
default void carry(CarriableTrait unit){
CallEntity.setCarryOf(this instanceof Player ? (Player)this : null, this, unit);
}
@Remote(called = Loc.both, targets = Loc.both, forward = true, in = In.entities)
static void dropSelf(Player player){
if(player.getCarrier() != null){
@@ -62,4 +40,38 @@ public interface CarryTrait extends TeamTrait, SolidTrait, TargetTrait{
Effects.effect(UnitFx.unitPickup, trait);
}
}
/**
* Returns the thing this carrier is carrying.
*/
CarriableTrait getCarry();
/**
* Sets the carrying unit. Internal use only! Use {@link #carry(CarriableTrait)} to set state.
*/
void setCarry(CarriableTrait unit);
/**
* Returns maximum mass this carrier can carry.
*/
float getCarryWeight();
/**
* Drops the unit that is being carried, if applicable.
*/
default void dropCarry(){
carry(null);
}
default void dropCarryLocal(){
setCarryOf(null, this, null);
}
/**
* Do not override unless absolutely necessary.
* Carries a unit. To drop a unit, call with {@code null}.
*/
default void carry(CarriableTrait unit){
CallEntity.setCarryOf(this instanceof Player ? (Player) this : null, this, unit);
}
}
@@ -2,6 +2,8 @@ package io.anuke.mindustry.entities.traits;
import io.anuke.ucore.entities.trait.Entity;
/**Marks an entity as serializable.*/
/**
* Marks an entity as serializable.
*/
public interface SaveTrait extends Entity, TypeTrait, Saveable{
}
@@ -6,5 +6,6 @@ import java.io.IOException;
public interface Saveable{
void writeSave(DataOutput stream) throws IOException;
void readSave(DataInput stream) throws IOException;
}
@@ -7,6 +7,8 @@ import io.anuke.ucore.util.Timer;
public interface ShooterTrait extends VelocityTrait, TeamTrait, InventoryTrait{
Timer getTimer();
int getShootTimer(boolean left);
Weapon getWeapon();
}
@@ -5,6 +5,8 @@ import io.anuke.mindustry.world.Tile;
public interface SpawnerTrait{
Tile getTile();
void updateSpawning(Unit unit);
float getSpawnProgress();
}
@@ -12,12 +12,16 @@ import static io.anuke.mindustry.Vars.threads;
public interface SyncTrait extends Entity, TypeTrait{
/**Whether smoothing of entities is enabled when using multithreading; not yet implemented.*/
/**
* Whether smoothing of entities is enabled when using multithreading; not yet implemented.
*/
static boolean isSmoothing(){
return threads.isEnabled() && threads.getTPS() <= Gdx.graphics.getFramesPerSecond() / 2f;
}
/**Sets the position of this entity and updated the interpolator.*/
/**
* Sets the position of this entity and updated the interpolator.
*/
default void setNet(float x, float y){
set(x, y);
@@ -30,9 +34,12 @@ public interface SyncTrait extends Entity, TypeTrait {
}
}
/**Interpolate entity position only. Override if you need to interpolate rotations or other values.*/
/**
* Interpolate entity position only. Override if you need to interpolate rotations or other values.
*/
default void interpolate(){
if(getInterpolator() == null) throw new RuntimeException("This entity must have an interpolator to interpolate()!");
if(getInterpolator() == null)
throw new RuntimeException("This entity must have an interpolator to interpolate()!");
getInterpolator().update();
@@ -40,17 +47,22 @@ public interface SyncTrait extends Entity, TypeTrait {
setY(getInterpolator().pos.y);
}
/**Return the interpolator used for smoothing the position. Optional.*/
/**
* Return the interpolator used for smoothing the position. Optional.
*/
default Interpolator getInterpolator(){
return null;
}
/**Whether syncing is enabled for this entity; true by default.*/
/**
* Whether syncing is enabled for this entity; true by default.
*/
default boolean isSyncing(){
return true;
}
//Read and write sync data, usually position
void write(DataOutput data) throws IOException;
void read(DataInput data, long time) throws IOException;
}
@@ -1,16 +1,21 @@
package io.anuke.mindustry.entities.traits;
import io.anuke.mindustry.game.Team;
import io.anuke.ucore.entities.trait.VelocityTrait;
import io.anuke.ucore.entities.trait.PosTrait;
import io.anuke.ucore.entities.trait.VelocityTrait;
/**Base interface for targetable entities.*/
/**
* Base interface for targetable entities.
*/
public interface TargetTrait extends PosTrait, VelocityTrait{
boolean isDead();
Team getTeam();
/**Whether this entity is a valid target.*/
/**
* Whether this entity is a valid target.
*/
default boolean isValid(){
return !isDead();
}
@@ -9,7 +9,9 @@ public interface TypeTrait {
Array<Supplier<? extends TypeTrait>> registeredTypes = new Array<>();
ObjectIntMap<Class<? extends TypeTrait>> typeToID = new ObjectIntMap<>();
/**Register and return a type ID. The supplier should return a fresh instace of that type.*/
/**
* Register and return a type ID. The supplier should return a fresh instace of that type.
*/
static <T extends TypeTrait> void registerType(Class<T> type, Supplier<T> supplier){
if(typeToID.get(type, -1) != -1){
throw new RuntimeException("Type is already registered: '" + type + "'!");
@@ -21,7 +23,9 @@ public interface TypeTrait {
lastRegisteredID[0]++;
}
/**Registers a syncable type by ID.*/
/**
* Registers a syncable type by ID.
*/
static Supplier<? extends TypeTrait> getTypeByID(int id){
if(id == -1){
throw new IllegalArgumentException("Attempt to retrieve invalid entity type ID! Did you forget to set it in ContentLoader.registerTypes()?");
@@ -29,11 +33,14 @@ public interface TypeTrait {
return registeredTypes.get(id);
}
/**Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX.
* Do not override!*/
/**
* Returns the type ID of this entity used for intstantiation. Should be < BYTE_MAX.
* Do not override!
*/
default int getTypeID(){
int id = typeToID.get(getClass(), -1);
if(id == -1) throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?");
if(id == -1)
throw new RuntimeException("Class of type '" + getClass() + "' is not registered! Did you forget to register it in ContentLoader#registerTypes()?");
return id;
}
}
@@ -30,7 +30,10 @@ import io.anuke.ucore.core.Effects;
import io.anuke.ucore.core.Timers;
import io.anuke.ucore.entities.EntityGroup;
import io.anuke.ucore.graphics.Draw;
import io.anuke.ucore.util.*;
import io.anuke.ucore.util.Angles;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
import io.anuke.ucore.util.Timer;
import java.io.DataInput;
import java.io.DataOutput;
@@ -55,7 +58,37 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
protected Squad squad;
protected int spawner;
/**Initialize the type and team of this unit. Only call once!*/
/**
* internal constructor used for deserialization, DO NOT USE
*/
public BaseUnit(){
}
@Remote(called = Loc.server, in = In.entities)
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
if(Net.server() || !Net.active()){
UnitDrops.dropItems(unit);
}
float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f);
float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize() / 2f, Palette.darkFlame);
unit.onSuperDeath();
ScorchDecal.create(unit.x, unit.y);
Effects.effect(ExplosionFx.explosion, unit);
Effects.shake(2f, 2f, unit);
//must run afterwards so the unit's group is not null
threads.runDelay(unit::remove);
}
/**
* Initialize the type and team of this unit. Only call once!
*/
public void init(UnitType type, Team team){
if(this.type != null) throw new RuntimeException("This unit is already initialized!");
@@ -63,10 +96,6 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
this.team = team;
}
public void setSpawner(Tile tile) {
this.spawner = tile.packedPosition();
}
public UnitType getType(){
return type;
}
@@ -75,10 +104,13 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
return world.tile(spawner);
}
/**internal constructor used for deserialization, DO NOT USE*/
public BaseUnit(){}
public void setSpawner(Tile tile){
this.spawner = tile.packedPosition();
}
/**Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo.*/
/**
* Sets this to a 'wave' unit, which means it has slightly different AI and will not run out of ammo.
*/
public void setWave(){
isWave = true;
}
@@ -120,7 +152,9 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
}
}
/**Only runs when the unit has a target.*/
/**
* Only runs when the unit has a target.
*/
public void behavior(){
}
@@ -131,6 +165,7 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
target = null;
}
}
public void targetClosestAllyFlag(BlockFlag flag){
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, flag));
if(target != null) this.target = target.entity;
@@ -395,26 +430,4 @@ public abstract class BaseUnit extends Unit implements ShooterTrait{
public void onSuperDeath(){
super.onDeath();
}
@Remote(called = Loc.server, in = In.entities)
public static void onUnitDeath(BaseUnit unit){
if(unit == null) return;
if(Net.server() || !Net.active()){
UnitDrops.dropItems(unit);
}
float explosiveness = 2f + (unit.inventory.hasItem() ? unit.inventory.getItem().item.explosiveness * unit.inventory.getItem().amount : 0f);
float flammability = (unit.inventory.hasItem() ? unit.inventory.getItem().item.flammability * unit.inventory.getItem().amount : 0f);
Damage.dynamicExplosion(unit.x, unit.y, flammability, explosiveness, 0f, unit.getSize()/2f, Palette.darkFlame);
unit.onSuperDeath();
ScorchDecal.create(unit.x, unit.y);
Effects.effect(ExplosionFx.explosion, unit);
Effects.shake(2f, 2f, unit);
//must run afterwards so the unit's group is not null
threads.runDelay(unit::remove);
}
}
@@ -23,7 +23,96 @@ import static io.anuke.mindustry.Vars.world;
public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
protected static Translator vec = new Translator();
protected static float wobblyness = 0.6f;
public final UnitState
resupply = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.resupplyPoint)){
retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint));
}else{
circle(20f);
}
}
},
idle = new UnitState(){
public void update(){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
if(target != null){
setState(attack);
}
});
target = getClosestCore();
if(target != null){
circle(50f);
}
velocity.scl(0.8f);
}
},
attack = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(Units.invalidateTarget(target, team, x, y)){
target = null;
}
if(!inventory.hasAmmo()){
state.set(resupply);
}else if(target == null){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
targetClosestEnemyFlag(BlockFlag.producer);
if(target == null){
setState(idle);
}
});
}else{
attack(150f);
if((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation
&& distanceTo(target) < inventory.getAmmo().getRange()){
AmmoType ammo = inventory.getAmmo();
inventory.useAmmo();
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= maxHealth()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.repair)){
retarget(() -> {
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair));
if(target != null) FlyingUnit.this.target = target.entity;
});
}else{
circle(20f);
}
}
};
protected Trail trail = new Trail(8);
protected CarriableTrait carrying;
@@ -164,95 +253,4 @@ public abstract class FlyingUnit extends BaseUnit implements CarryTrait{
velocity.add(vec);
}
public final UnitState
resupply = new UnitState(){
public void entered() {
target = null;
}
public void update() {
if(inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.resupplyPoint)){
retarget(() -> targetClosestAllyFlag(BlockFlag.resupplyPoint));
}else{
circle(20f);
}
}
},
idle = new UnitState() {
public void update() {
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
if(target != null){
setState(attack);
}
});
target = getClosestCore();
if(target != null){
circle(50f);
}
velocity.scl(0.8f);
}
},
attack = new UnitState(){
public void entered() {
target = null;
}
public void update() {
if(Units.invalidateTarget(target, team, x, y)){
target = null;
}
if(!inventory.hasAmmo()) {
state.set(resupply);
}else if (target == null){
retarget(() -> {
targetClosest();
targetClosestEnemyFlag(BlockFlag.target);
targetClosestEnemyFlag(BlockFlag.producer);
if(target == null){
setState(idle);
}
});
}else{
attack(150f);
if ((Mathf.angNear(angleTo(target), rotation, 15f) || !inventory.getAmmo().bullet.keepVelocity) //bombers don't care about rotation
&& distanceTo(target) < inventory.getAmmo().getRange()) {
AmmoType ammo = inventory.getAmmo();
inventory.useAmmo();
Vector2 to = Predict.intercept(FlyingUnit.this, target, ammo.bullet.speed);
getWeapon().update(FlyingUnit.this, to.x, to.y);
}
}
}
},
retreat = new UnitState() {
public void entered() {
target = null;
}
public void update() {
if(health >= maxHealth()){
state.set(attack);
}else if(!targetHasFlag(BlockFlag.repair)){
retarget(() -> {
Tile target = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair));
if (target != null) FlyingUnit.this.target = target.entity;
});
}else{
circle(20f);
}
}
};
}
@@ -31,6 +31,83 @@ public abstract class GroundUnit extends BaseUnit {
protected float walkTime;
protected float baseRotation;
public final UnitState
resupply = new UnitState(){
public void entered(){
target = null;
}
public void update(){
Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint));
if(tile != null && distanceTo(tile) > 40){
moveAwayFromCore();
}
//TODO move toward resupply point
if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}
}
},
attack = new UnitState(){
public void entered(){
target = null;
}
public void update(){
TileEntity core = getClosestEnemyCore();
float dst = core == null ? 0 : distanceTo(core);
if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange() / 1.1f){
target = core;
}else{
retarget(() -> targetClosest());
}
if(!inventory.hasAmmo()){
state.set(resupply);
}else if(target != null){
if(core != null){
if(dst > inventory.getAmmo().getRange() * 0.5f){
moveToCore();
}
}else{
moveToCore();
}
if(distanceTo(target) < inventory.getAmmo().getRange()){
rotate(angleTo(target));
if(Mathf.angNear(angleTo(target), rotation, 13f)){
AmmoType ammo = inventory.getAmmo();
Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed);
getWeapon().update(GroundUnit.this, to.x, to.y);
}
}
}else{
moveToCore();
}
}
},
retreat = new UnitState(){
public void entered(){
target = null;
}
public void update(){
if(health >= health){
state.set(attack);
}
moveAwayFromCore();
}
};
protected Weapon weapon;
@Override
@@ -75,6 +152,10 @@ public abstract class GroundUnit extends BaseUnit {
return weapon;
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
@Override
public void draw(){
Draw.alpha(hitTime / hitDuration);
@@ -159,10 +240,6 @@ public abstract class GroundUnit extends BaseUnit {
super.readSave(stream);
}
public void setWeapon(Weapon weapon){
this.weapon = weapon;
}
protected void moveToCore(){
Tile tile = world.tileWorld(x, y);
if(tile == null) return;
@@ -189,82 +266,4 @@ public abstract class GroundUnit extends BaseUnit {
walkTime += Timers.delta();
velocity.add(vec);
}
public final UnitState
resupply = new UnitState(){
public void entered() {
target = null;
}
public void update() {
Tile tile = Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.resupplyPoint));
if (tile != null && distanceTo(tile) > 40) {
moveAwayFromCore();
}
//TODO move toward resupply point
if(isWave || inventory.totalAmmo() + 10 >= inventory.ammoCapacity()){
state.set(attack);
}
}
},
attack = new UnitState(){
public void entered() {
target = null;
}
public void update() {
TileEntity core = getClosestEnemyCore();
float dst = core == null ? 0 :distanceTo(core);
if(core != null && inventory.hasAmmo() && dst < inventory.getAmmo().getRange()/1.1f){
target = core;
}else {
retarget(() -> targetClosest());
}
if(!inventory.hasAmmo()) {
state.set(resupply);
}else if(target != null){
if(core != null){
if(dst > inventory.getAmmo().getRange() * 0.5f){
moveToCore();
}
}else{
moveToCore();
}
if(distanceTo(target) < inventory.getAmmo().getRange()){
rotate(angleTo(target));
if (Mathf.angNear(angleTo(target), rotation, 13f)) {
AmmoType ammo = inventory.getAmmo();
Vector2 to = Predict.intercept(GroundUnit.this, target, ammo.bullet.speed);
getWeapon().update(GroundUnit.this, to.x, to.y);
}
}
}else{
moveToCore();
}
}
},
retreat = new UnitState() {
public void entered() {
target = null;
}
public void update() {
if(health >= health){
state.set(attack);
}
moveAwayFromCore();
}
};
}
@@ -5,8 +5,10 @@ import io.anuke.ucore.util.Translator;
import static io.anuke.mindustry.Vars.threads;
/**Used to group entities together, for formations and such.
* Usually, squads are used by units spawned in the same wave.*/
/**
* Used to group entities together, for formations and such.
* Usually, squads are used by units spawned in the same wave.
*/
public class Squad{
public Vector2 direction = new Translator();
public int units;
@@ -1,7 +1,12 @@
package io.anuke.mindustry.entities.units;
public interface UnitState{
default void entered(){}
default void exited(){}
default void update(){}
default void entered(){
}
default void exited(){
}
default void update(){
}
}
@@ -18,12 +18,9 @@ import io.anuke.ucore.util.Bundles;
public class UnitType implements UnlockableContent{
private static byte lastid = 0;
private static Array<UnitType> types = new Array<>();
protected final Supplier<? extends BaseUnit> constructor;
public final String name;
public final byte id;
protected final Supplier<? extends BaseUnit> constructor;
public float health = 60;
public float hitsize = 5f;
public float hitsizeTile = 4f;
@@ -57,6 +54,14 @@ public class UnitType implements UnlockableContent{
TypeTrait.registerType(type, mainConstructor);
}
public static UnitType getByID(byte id){
return types.get(id);
}
public static Array<UnitType> all(){
return types;
}
@Override
public void displayInfo(Table table){
ContentDisplay.displayUnit(table, this);
@@ -103,12 +108,4 @@ public class UnitType implements UnlockableContent{
unit.init(this, team);
return unit;
}
public static UnitType getByID(byte id){
return types.get(id);
}
public static Array<UnitType> all(){
return types;
}
}
@@ -47,184 +47,6 @@ public class Drone extends FlyingUnit implements BuilderTrait {
protected Item targetItem;
protected Tile mineTile;
protected Queue<BuildRequest> placeQueue = new ThreadQueue<>();
/**Initialize placement event notifier system.
* Static initialization is to be avoided, thus, this is done lazily.*/
private static void initEvents(){
if(initialized) return;
toMine = ObjectSet.with(Items.lead, Items.tungsten);
Events.on(BlockBuildEvent.class, (team, tile) -> {
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!(tile.entity instanceof BuildEntity)) return;
BuildEntity entity = tile.entity();
for(BaseUnit unit : group.all()){
if(unit instanceof Drone){
((Drone) unit).notifyPlaced(entity);
}
}
});
initialized = true;
}
{
initEvents();
}
private void notifyPlaced(BuildEntity entity){
float timeToBuild = entity.recipe.cost;
float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0);
if(dist / type.maxVelocity < timeToBuild * 0.9f){
//CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe);
target = entity;
setState(build);
}
}
@Override
public float getBuildPower(Tile tile) {
return type.buildPower;
}
@Override
public float getMinePower() {
return type.minePower;
}
@Override
public Queue<BuildRequest> getPlaceQueue() {
return placeQueue;
}
@Override
public Tile getMineTile() {
return mineTile;
}
@Override
public void setMineTile(Tile tile) {
mineTile = tile;
}
@Override
public void update() {
super.update();
x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f);
y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f);
updateBuilding(this);
}
@Override
protected void updateRotation() {
if(target != null && (state.is(repair) || state.is(mine))){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f);
}
if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f);
}
}
@Override
public void behavior() {
if(health <= health * type.retreatPercent &&
Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}
@Override
public UnitState getStartState() {
return repair;
}
@Override
public void drawOver() {
trail.draw(Palette.lightTrail, 3f);
TargetTrait entity = target;
if(entity instanceof TileEntity && state.is(repair)){
float len = 5f;
Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f));
Shapes.laser("beam", "beam-end",
x + Angles.trnsx(rotation, len),
y + Angles.trnsy(rotation, len),
entity.getX(), entity.getY());
Draw.color();
}
drawBuilding(this);
}
@Override
public float drawSize() {
return isBuilding() ? placeDistance*2f : 30f;
}
@Override
public float getAmmoFraction() {
return inventory.getItem().amount / (float)type.itemCapacity;
}
protected void findItem(){
TileEntity entity = getClosestCore();
if(entity == null){
return;
}
targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b)));
}
protected boolean findItemDrop(){
TileEntity core = getClosestCore();
if(core == null) return false;
//find nearby dropped items to pick up if applicable
ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f,
item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() &&
inventory.canAcceptItem(item.getItem(), 1));
if(drop != null){
setState(pickup);
target = drop;
return true;
}
return false;
}
@Override
public boolean canCreateBlocks() {
return false;
}
@Override
public void write(DataOutput data) throws IOException {
super.write(data);
data.writeInt(mineTile == null ? -1 : mineTile.packedPosition());
writeBuilding(data);
}
@Override
public void read(DataInput data, long time) throws IOException {
super.read(data, time);
int mined = data.readInt();
readBuilding(data);
if(mined != -1){
mineTile = world.tile(mined);
}
}
public final UnitState
build = new UnitState(){
@@ -432,4 +254,183 @@ public class Drone extends FlyingUnit implements BuilderTrait {
}
};
{
initEvents();
}
/**
* Initialize placement event notifier system.
* Static initialization is to be avoided, thus, this is done lazily.
*/
private static void initEvents(){
if(initialized) return;
toMine = ObjectSet.with(Items.lead, Items.tungsten);
Events.on(BlockBuildEvent.class, (team, tile) -> {
EntityGroup<BaseUnit> group = unitGroups[team.ordinal()];
if(!(tile.entity instanceof BuildEntity)) return;
BuildEntity entity = tile.entity();
for(BaseUnit unit : group.all()){
if(unit instanceof Drone){
((Drone) unit).notifyPlaced(entity);
}
}
});
initialized = true;
}
private void notifyPlaced(BuildEntity entity){
float timeToBuild = entity.recipe.cost;
float dist = Math.min(entity.distanceTo(x, y) - placeDistance, 0);
if(dist / type.maxVelocity < timeToBuild * 0.9f){
//CallEntity.onDroneBeginBuild(this, entity.tile, entity.recipe);
target = entity;
setState(build);
}
}
@Override
public float getBuildPower(Tile tile){
return type.buildPower;
}
@Override
public float getMinePower(){
return type.minePower;
}
@Override
public Queue<BuildRequest> getPlaceQueue(){
return placeQueue;
}
@Override
public Tile getMineTile(){
return mineTile;
}
@Override
public void setMineTile(Tile tile){
mineTile = tile;
}
@Override
public void update(){
super.update();
x += Mathf.sin(Timers.time() + id * 999, 25f, 0.07f);
y += Mathf.cos(Timers.time() + id * 999, 25f, 0.07f);
updateBuilding(this);
}
@Override
protected void updateRotation(){
if(target != null && (state.is(repair) || state.is(mine))){
rotation = Mathf.slerpDelta(rotation, angleTo(target), 0.3f);
}else{
rotation = Mathf.slerpDelta(rotation, velocity.angle(), 0.3f);
}
if(velocity.len() <= 0.2f && !(state.is(repair) && target != null)){
rotation += Mathf.sin(Timers.time() + id * 99, 10f, 5f);
}
}
@Override
public void behavior(){
if(health <= health * type.retreatPercent &&
Geometry.findClosest(x, y, world.indexer().getAllied(team, BlockFlag.repair)) != null){
setState(retreat);
}
}
@Override
public UnitState getStartState(){
return repair;
}
@Override
public void drawOver(){
trail.draw(Palette.lightTrail, 3f);
TargetTrait entity = target;
if(entity instanceof TileEntity && state.is(repair)){
float len = 5f;
Draw.color(Color.BLACK, Color.WHITE, 0.95f + Mathf.absin(Timers.time(), 0.8f, 0.05f));
Shapes.laser("beam", "beam-end",
x + Angles.trnsx(rotation, len),
y + Angles.trnsy(rotation, len),
entity.getX(), entity.getY());
Draw.color();
}
drawBuilding(this);
}
@Override
public float drawSize(){
return isBuilding() ? placeDistance * 2f : 30f;
}
@Override
public float getAmmoFraction(){
return inventory.getItem().amount / (float) type.itemCapacity;
}
protected void findItem(){
TileEntity entity = getClosestCore();
if(entity == null){
return;
}
targetItem = Mathf.findMin(toMine, (a, b) -> -Integer.compare(entity.items.get(a), entity.items.get(b)));
}
protected boolean findItemDrop(){
TileEntity core = getClosestCore();
if(core == null) return false;
//find nearby dropped items to pick up if applicable
ItemDrop drop = EntityPhysics.getClosest(itemGroup, x, y, 60f,
item -> core.tile.block().acceptStack(item.getItem(), item.getAmount(), core.tile, Drone.this) == item.getAmount() &&
inventory.canAcceptItem(item.getItem(), 1));
if(drop != null){
setState(pickup);
target = drop;
return true;
}
return false;
}
@Override
public boolean canCreateBlocks(){
return false;
}
@Override
public void write(DataOutput data) throws IOException{
super.write(data);
data.writeInt(mineTile == null ? -1 : mineTile.packedPosition());
writeBuilding(data);
}
@Override
public void read(DataInput data, long time) throws IOException{
super.read(data, time);
int mined = data.readInt();
readBuilding(data);
if(mined != -1){
mineTile = world.tile(mined);
}
}
}
+21 -9
View File
@@ -2,20 +2,32 @@ package io.anuke.mindustry.game;
import com.badlogic.gdx.utils.Array;
/**Base interface for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}.*/
/**
* Base interface for a content type that is loaded in {@link io.anuke.mindustry.core.ContentLoader}.
*/
public interface Content{
/**Returns the type name of this piece of content.
* This should return the same value for all instances of this content type.*/
/**
* Returns the type name of this piece of content.
* This should return the same value for all instances of this content type.
*/
String getContentTypeName();
/**Returns a list of all instances of this content.*/
/**
* Returns a list of all instances of this content.
*/
Array<? extends Content> getAll();
/**Called after all content is created. Do not use to load regions or texture data!*/
default void init(){}
/**
* Called after all content is created. Do not use to load regions or texture data!
*/
default void init(){
}
/**Called after all content is created, only on non-headless versions.
* Use for loading regions or other image data.*/
default void load(){}
/**
* Called after all content is created, only on non-headless versions.
* Use for loading regions or other image data.
*/
default void load(){
}
}
@@ -9,12 +9,18 @@ import io.anuke.ucore.core.Events;
import io.anuke.ucore.core.Settings;
public class ContentDatabase{
/**Maps unlockable type names to a set of unlocked content.*/
/**
* Maps unlockable type names to a set of unlocked content.
*/
private ObjectMap<String, ObjectSet<String>> unlocked = new ObjectMap<>();
/**Whether unlockables have changed since the last save.*/
/**
* Whether unlockables have changed since the last save.
*/
private boolean dirty;
/**Returns whether or not this piece of content is unlocked yet.*/
/**
* Returns whether or not this piece of content is unlocked yet.
*/
public boolean isUnlocked(UnlockableContent content){
if(!unlocked.containsKey(content.getContentTypeName())){
unlocked.put(content.getContentTypeName(), new ObjectSet<>());
@@ -25,10 +31,13 @@ public class ContentDatabase {
return set.contains(content.getContentName());
}
/**Makes this piece of content 'unlocked', if possible.
/**
* Makes this piece of content 'unlocked', if possible.
* If this piece of content is already unlocked or cannot be unlocked due to dependencies, nothing changes.
* Results are not saved until you call {@link #save()}.
* @return whether or not this content was newly unlocked.*/
*
* @return whether or not this content was newly unlocked.
*/
public boolean unlockContent(UnlockableContent content){
if(!content.canBeUnlocked()) return false;
@@ -48,12 +57,16 @@ public class ContentDatabase {
return ret;
}
/**Returns whether unlockables have changed since the last save.*/
/**
* Returns whether unlockables have changed since the last save.
*/
public boolean isDirty(){
return dirty;
}
/**Clears all unlocked content.*/
/**
* Clears all unlocked content.
*/
public void reset(){
unlocked.clear();
dirty = true;
@@ -10,13 +10,19 @@ public enum Difficulty {
//purge removed due to new wave system
/*purge(0.25f, 0.01f, 0.25f)*/;
/**The scaling of how many waves it takes for one more enemy of a type to appear.
/**
* The scaling of how many waves it takes for one more enemy of a type to appear.
* For example: with enemeyScaling = 2 and the default scaling being 2, it would take 4 waves for
* an enemy spawn to go from 1->2 enemies.*/
* an enemy spawn to go from 1->2 enemies.
*/
public final float enemyScaling;
/**Multiplier of the time between waves.*/
/**
* Multiplier of the time between waves.
*/
public final float timeScaling;
/**Scaling of max time between waves. Default time is 4 minutes.*/
/**
* Scaling of max time between waves. Default time is 4 minutes.
*/
public final float maxTimeScaling;
private String value;
@@ -22,19 +22,25 @@ public class EventType {
void handle();
}
/**This event is called from the logic thread.
* DO NOT INITIALIZE GRAPHICS HERE.*/
/**
* This event is called from the logic thread.
* DO NOT INITIALIZE GRAPHICS HERE.
*/
public interface WorldLoadEvent extends Event{
void handle();
}
/**Called after the WorldLoadEvent is, and all logic has been loaded.
* It is safe to intialize graphics here.*/
/**
* Called after the WorldLoadEvent is, and all logic has been loaded.
* It is safe to intialize graphics here.
*/
public interface WorldLoadGraphicsEvent extends Event{
void handle();
}
/**Called from the logic thread. Do not access graphics here!*/
/**
* Called from the logic thread. Do not access graphics here!
*/
public interface TileChangeEvent extends Event{
void handle(Tile tile);
}
@@ -8,42 +8,72 @@ import io.anuke.mindustry.type.ItemStack;
import io.anuke.mindustry.type.StatusEffect;
import io.anuke.mindustry.type.Weapon;
/**A spawn group defines spawn information for a specific type of unit, with optional extra information like
/**
* A spawn group defines spawn information for a specific type of unit, with optional extra information like
* weapon equipped, ammo used, and status effects.
* Each spawn group can have multiple sub-groups spawned in different areas of the map.*/
* Each spawn group can have multiple sub-groups spawned in different areas of the map.
*/
public class SpawnGroup{
/**The unit type spawned*/
/**
* The unit type spawned
*/
public final UnitType type;
/**When this spawn should end*/
/**
* When this spawn should end
*/
protected int end = Integer.MAX_VALUE;
/**When this spawn should start*/
/**
* When this spawn should start
*/
protected int begin;
/**The spacing, in waves, of spawns. For example, 2 = spawns every other wave*/
/**
* The spacing, in waves, of spawns. For example, 2 = spawns every other wave
*/
protected int spacing = 1;
/**Maximum amount of units that spawn*/
/**
* Maximum amount of units that spawn
*/
protected int max = 60;
/**How many waves need to pass before the amount of units spawned increases by 1*/
/**
* How many waves need to pass before the amount of units spawned increases by 1
*/
protected float unitScaling = 9999f;
/**How many waves need to pass before the amount of instances of this group increases by 1*/
/**
* How many waves need to pass before the amount of instances of this group increases by 1
*/
protected float groupScaling = 9999f;
/**Amount of enemies spawned initially, with no scaling*/
/**
* Amount of enemies spawned initially, with no scaling
*/
protected int unitAmount = 1;
/**Amount of enemies spawned initially, with no scaling*/
/**
* Amount of enemies spawned initially, with no scaling
*/
protected int groupAmount = 1;
/**Weapon used by the spawned unit. Null to disable. Only applicable to ground units.*/
/**
* Weapon used by the spawned unit. Null to disable. Only applicable to ground units.
*/
protected Weapon weapon;
/**Status effect applied to the spawned unit. Null to disable.*/
/**
* Status effect applied to the spawned unit. Null to disable.
*/
protected StatusEffect effect;
/**Items this unit spawns with. Null to disable.*/
/**
* Items this unit spawns with. Null to disable.
*/
protected ItemStack items;
/**Ammo type this unit spawns with. Null to use the first available ammo.*/
/**
* Ammo type this unit spawns with. Null to use the first available ammo.
*/
protected Item ammoItem;
public SpawnGroup(UnitType type){
this.type = type;
}
/**Returns the amount of units spawned on a specific wave.*/
/**
* Returns the amount of units spawned on a specific wave.
*/
public int getUnitsSpawned(int wave){
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
return 0;
@@ -53,7 +83,9 @@ public class SpawnGroup {
return Math.min(unitAmount - 1 + Math.max((int) ((wave / spacing) / scaling), 1), max);
}
/**Returns the amount of different unit groups at a specific wave.*/
/**
* Returns the amount of different unit groups at a specific wave.
*/
public int getGroupsSpawned(int wave){
if(wave < begin || wave > end || (wave - begin) % spacing != 0){
return 0;
@@ -63,8 +95,10 @@ public class SpawnGroup {
return Math.min(groupAmount - 1 + Math.max((int) ((wave / spacing) / groupScaling), 1), max);
}
/**Creates a unit, and assigns correct values based on this group's data.
* This method does not add() the unit.*/
/**
* Creates a unit, and assigns correct values based on this group's data.
* This method does not add() the unit.
*/
public BaseUnit createUnit(Team team){
BaseUnit unit = type.create(team);
+1 -2
View File
@@ -10,11 +10,10 @@ public enum Team {
purple(Color.valueOf("ba5bd9")),
orange(Color.valueOf("e8c66a"));
public final static Team[] all = values();
public final Color color;
public final int intColor;
public final static Team[] all = values();
Team(Color color){
this.color = color;
intColor = Color.rgba8888(color);
+35 -14
View File
@@ -6,7 +6,9 @@ import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.util.ThreadArray;
import io.anuke.ucore.util.ThreadSet;
/**Class for various team-based utilities.*/
/**
* Class for various team-based utilities.
*/
public class TeamInfo{
private ObjectMap<Team, TeamData> map = new ObjectMap<>();
private ThreadSet<Team> allies = new ThreadSet<>(),
@@ -18,20 +20,27 @@ public class TeamInfo {
private int allyBits = 0;
private int enemyBits = 0;
/**Returns all teams on a side.*/
/**
* Returns all teams on a side.
*/
public ObjectSet<TeamData> getTeams(boolean ally){
return ally ? allyData : enemyData;
}
/**Returns all team data.*/
/**
* Returns all team data.
*/
public ObjectSet<TeamData> getTeams(){
return allTeamData;
}
/**Register a team.
/**
* Register a team.
*
* @param team The team type enum.
* @param ally Whether this team is an ally with the player or an enemy with the player.
* In PvP situations with dedicated servers, the sides can be arbitrary.*/
* In PvP situations with dedicated servers, the sides can be arbitrary.
*/
public void add(Team team, boolean ally){
if(has(team)) throw new RuntimeException("Can't define team information twice!");
@@ -53,19 +62,25 @@ public class TeamInfo {
map.put(team, data);
}
/**Returns team data by type. Call {@link #has(Team)} first to make sure it's active!*/
/**
* Returns team data by type. Call {@link #has(Team)} first to make sure it's active!
*/
public TeamData get(Team team){
if(!has(team)) throw new RuntimeException("This team is not active! Check has() before calling get().");
return map.get(team);
}
/**Returns whether the specified team is active, e.g. whether it is participating in the game.*/
/**
* Returns whether the specified team is active, e.g. whether it is participating in the game.
*/
public boolean has(Team team){
return map.containsKey(team);
}
/**Returns a set of all teams that are enemies of this team.
* For teams not active, an empty set is returned.*/
/**
* Returns a set of all teams that are enemies of this team.
* For teams not active, an empty set is returned.
*/
public ObjectSet<Team> enemiesOf(Team team){
boolean ally = allies.contains(team);
boolean enemy = enemies.contains(team);
@@ -76,8 +91,10 @@ public class TeamInfo {
return ally ? enemies : allies;
}
/**Returns a set of all teams that are allies of this team.
* For teams not active, an empty set is returned.*/
/**
* Returns a set of all teams that are allies of this team.
* For teams not active, an empty set is returned.
*/
public ObjectSet<Team> alliesOf(Team team){
boolean ally = allies.contains(team);
boolean enemy = enemies.contains(team);
@@ -88,8 +105,10 @@ public class TeamInfo {
return !ally ? enemies : allies;
}
/**Returns a set of all teams that are enemies of this team.
* For teams not active, an empty set is returned.*/
/**
* Returns a set of all teams that are enemies of this team.
* For teams not active, an empty set is returned.
*/
public ObjectSet<TeamData> enemyDataOf(Team team){
boolean ally = allies.contains(team);
boolean enemy = enemies.contains(team);
@@ -100,7 +119,9 @@ public class TeamInfo {
return ally ? enemyData : allyData;
}
/**Returns whether or not these two teams are enemies.*/
/**
* Returns whether or not these two teams are enemies.
*/
public boolean areEnemies(Team team, Team other){
if(team == other) return false; //fast fail to be more efficient
boolean ally = (allyBits & (1 << team.ordinal())) != 0;
@@ -5,37 +5,54 @@ import io.anuke.ucore.scene.ui.layout.Table;
import static io.anuke.mindustry.Vars.control;
/**Base interface for an unlockable content type.*/
/**
* Base interface for an unlockable content type.
*/
public interface UnlockableContent extends Content{
/**Returns the unqiue name of this piece of content.
/**
* Returns the unqiue name of this piece of content.
* The name only needs to be unique for all content of this type.
* Do not use IDs for names! Make sure this string stays constant with each update unless removed.
* (e.g. having a recipe and a block, both with name "wall" is fine, as they are different types).*/
* (e.g. having a recipe and a block, both with name "wall" is fine, as they are different types).
*/
String getContentName();
/**Returns the localized name of this content.*/
/**
* Returns the localized name of this content.
*/
String localizedName();
TextureRegion getContentIcon();
/**This should show all necessary info about this content in the specified table.*/
/**
* This should show all necessary info about this content in the specified table.
*/
void displayInfo(Table table);
/**Called when this content is unlocked. Use this to unlock other related content.*/
default void onUnlock(){}
/**
* Called when this content is unlocked. Use this to unlock other related content.
*/
default void onUnlock(){
}
/**Whether this content is always hidden in the content info dialog.*/
/**
* Whether this content is always hidden in the content info dialog.
*/
default boolean isHidden(){
return false;
}
/**Lists the content that must be unlocked in order for this specific content to become unlocked. May return null.*/
/**
* Lists the content that must be unlocked in order for this specific content to become unlocked. May return null.
*/
default UnlockableContent[] getDependencies(){
return null;
}
/**Returns whether dependencies are satisfied for unlocking this content.*/
/**
* Returns whether dependencies are satisfied for unlocking this content.
*/
default boolean canBeUnlocked(){
UnlockableContent[] depend = getDependencies();
if(depend == null){
@@ -32,22 +32,9 @@ public class BlockRenderer{
}
}
private class BlockRequest implements Comparable<BlockRequest>{
Tile tile;
Layer layer;
@Override
public int compareTo(BlockRequest other){
return layer.compareTo(other.layer);
}
@Override
public String toString(){
return tile.block().name + ":" + layer.toString();
}
}
/**Process all blocks to draw, simultaneously drawing block shadows and static blocks.*/
/**
* Process all blocks to draw, simultaneously drawing block shadows and static blocks.
*/
public void processBlocks(){
requestidx = 0;
lastLayer = null;
@@ -198,9 +185,11 @@ public class BlockRenderer{
floorRenderer.drawFloor();
}
private void layerBegins(Layer layer){}
private void layerBegins(Layer layer){
}
private void layerEnds(Layer layer){}
private void layerEnds(Layer layer){
}
private void addRequest(Tile tile, Layer layer){
if(requestidx >= requests.size){
@@ -214,4 +203,19 @@ public class BlockRenderer{
r.layer = layer;
requestidx++;
}
private class BlockRequest implements Comparable<BlockRequest>{
Tile tile;
Layer layer;
@Override
public int compareTo(BlockRequest other){
return layer.compareTo(other.layer);
}
@Override
public String toString(){
return tile.block().name + ":" + layer.toString();
}
}
}
@@ -38,6 +38,35 @@ public class FloorRenderer {
Events.on(WorldLoadGraphicsEvent.class, this::clearTiles);
}
static ShaderProgram createDefaultShader(){
String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ "uniform mat4 u_projTrans;\n" //
+ "varying vec2 v_texCoords;\n" //
+ "\n" //
+ "void main()\n" //
+ "{\n" //
+ " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "}\n";
String fragmentShader = "#ifdef GL_ES\n" //
+ "#define LOWP lowp\n" //
+ "precision mediump float;\n" //
+ "#else\n" //
+ "#define LOWP \n" //
+ "#endif\n" //
+ "varying vec2 v_texCoords;\n" //
+ "uniform sampler2D u_texture;\n" //
+ "void main()\n"//
+ "{\n" //
+ " gl_FragColor = texture2D(u_texture, v_texCoords);\n" //
+ "}";
ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
if(!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());
return shader;
}
public void drawFloor(){
if(cache == null){
return;
@@ -211,10 +240,6 @@ public class FloorRenderer {
chunk.caches[layer.ordinal()] = cbatch.getLastCache();
}
private class Chunk{
int[] caches = new int[CacheLayer.values().length];
}
public void clearTiles(){
if(cbatch != null) cbatch.dispose();
@@ -240,32 +265,7 @@ public class FloorRenderer {
Log.info("Time to cache: {0}", Timers.elapsed());
}
static ShaderProgram createDefaultShader () {
String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ "uniform mat4 u_projTrans;\n" //
+ "varying vec2 v_texCoords;\n" //
+ "\n" //
+ "void main()\n" //
+ "{\n" //
+ " v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
+ " gl_Position = u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
+ "}\n";
String fragmentShader = "#ifdef GL_ES\n" //
+ "#define LOWP lowp\n" //
+ "precision mediump float;\n" //
+ "#else\n" //
+ "#define LOWP \n" //
+ "#endif\n" //
+ "varying vec2 v_texCoords;\n" //
+ "uniform sampler2D u_texture;\n" //
+ "void main()\n"//
+ "{\n" //
+ " gl_FragColor = texture2D(u_texture, v_texCoords);\n" //
+ "}";
ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
if (!shader.isCompiled()) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());
return shader;
private class Chunk{
int[] caches = new int[CacheLayer.values().length];
}
}
@@ -24,7 +24,9 @@ import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.*;
/**Used for rendering fog of war. A framebuffer is used for this.*/
/**
* Used for rendering fog of war. A framebuffer is used for this.
*/
public class FogRenderer implements Disposable{
private TextureRegion region = new TextureRegion();
private FrameBuffer buffer;
@@ -1,16 +1,28 @@
package io.anuke.mindustry.graphics;
public enum Layer{
/**Base block layer.*/
/**
* Base block layer.
*/
block,
/**for placement*/
/**
* for placement
*/
placement,
/**First overlay. Stuff like conveyor items.*/
/**
* First overlay. Stuff like conveyor items.
*/
overlay,
/**"High" blocks, like turrets.*/
/**
* "High" blocks, like turrets.
*/
turret,
/**Power lasers.*/
/**
* Power lasers.
*/
power,
/**Extra lasers, like healing turrets.*/
/**
* Extra lasers, like healing turrets.
*/
laser
}
@@ -12,7 +12,6 @@ import io.anuke.mindustry.game.TeamInfo.TeamData;
import io.anuke.mindustry.input.InputHandler;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Tile;
import io.anuke.mindustry.world.consumers.Consume;
import io.anuke.mindustry.world.meta.BlockBar;
import io.anuke.ucore.core.Graphics;
import io.anuke.ucore.core.Settings;
@@ -8,7 +8,9 @@ import io.anuke.ucore.graphics.Fill;
import io.anuke.ucore.graphics.Lines;
import io.anuke.ucore.util.Mathf;
/**Class that renders a trail.*/
/**
* Class that renders a trail.
*/
public class Trail{
private final static float maxJump = 15f;
private final int length;
@@ -3,7 +3,9 @@ package io.anuke.mindustry.input;
import io.anuke.ucore.function.Callable;
import io.anuke.ucore.scene.utils.Cursors;
/**Type of cursor for displaying on desktop.*/
/**
* Type of cursor for displaying on desktop.
*/
public enum CursorType{
normal(Cursors::restoreCursor),
hand(Cursors::setHand),
@@ -16,7 +18,9 @@ public enum CursorType {
this.call = call;
}
/**Sets the current system cursor to this.*/
/**
* Sets the current system cursor to this.
*/
void set(){
call.run();
}
@@ -27,19 +27,26 @@ import static io.anuke.mindustry.input.CursorType.*;
import static io.anuke.mindustry.input.PlaceMode.*;
public class DesktopInput extends InputHandler{
private final String section;
//controller info
private float controlx, controly;
private boolean controlling;
private final String section;
/**Current cursor type.*/
/**
* Current cursor type.
*/
private CursorType cursorType = normal;
/**Position where the player started dragging a line.*/
/**
* Position where the player started dragging a line.
*/
private int selectX, selectY;
/**Whether selecting mode is active.*/
/**
* Whether selecting mode is active.
*/
private PlaceMode mode;
/**Animation scale for line.*/
/**
* Animation scale for line.
*/
private float selectScale;
public DesktopInput(Player player){
@@ -47,7 +54,9 @@ public class DesktopInput extends InputHandler{
this.section = "player_" + (player.playerIndex + 1);
}
/**Draws a placement icon for a specific block.*/
/**
* Draws a placement icon for a specific block.
*/
void drawPlace(int x, int y, Block block, int rotation){
if(validPlace(x, y, block, rotation)){
Draw.color();
@@ -33,12 +33,18 @@ import io.anuke.ucore.util.Translator;
import static io.anuke.mindustry.Vars.*;
public abstract class InputHandler extends InputAdapter{
/**Used for dropping items.*/
/**
* Used for dropping items.
*/
final static float playerSelectRange = mobile ? 17f : 11f;
/**Maximum line length.*/
/**
* Maximum line length.
*/
final static int maxLength = 100;
final static Translator stackTrns = new Translator();
/**Distance on the back from where items originate.*/
/**
* Distance on the back from where items originate.
*/
final static float backTrns = 3f;
public final Player player;
@@ -57,6 +63,73 @@ public abstract class InputHandler extends InputAdapter{
//methods to override
@Remote(targets = Loc.client, called = Loc.server, in = In.entities)
public static void dropItem(Player player, float angle){
if(Net.server() && !player.inventory.hasItem()){
throw new ValidateException(player, "Player cannot drop an item.");
}
ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle);
player.inventory.clearItem();
}
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
public static void transferInventory(Player player, Tile tile){
if(Net.server() && (!player.inventory.hasItem() || player.isTransferring)){
throw new ValidateException(player, "Player cannot transfer an item.");
}
threads.run(() -> {
if(player == null || tile.entity == null) return;
player.isTransferring = true;
ItemStack stack = player.inventory.getItem();
int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player);
boolean clear = stack.amount == accepted;
int sent = Mathf.clamp(accepted / 4, 1, 8);
int removed = accepted / sent;
int[] remaining = {accepted, accepted};
for(int i = 0; i < sent; i++){
boolean end = i == sent - 1;
Timers.run(i * 3, () -> {
tile.block().getStackOffset(stack.item, tile, stackTrns);
ItemTransfer.create(stack.item,
player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns),
new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> {
tile.block().handleStack(stack.item, removed, tile, player);
remaining[1] -= removed;
if(end && remaining[1] > 0){
tile.block().handleStack(stack.item, remaining[1], tile, player);
}
});
stack.amount -= removed;
remaining[0] -= removed;
if(end){
stack.amount -= remaining[0];
if(clear){
player.inventory.clearItem();
}
player.isTransferring = false;
}
});
}
});
}
@Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks)
public static void onTileTapped(Player player, Tile tile){
if(tile == null || player == null) return;
tile.block().tapped(tile, player);
}
public void update(){
}
@@ -97,7 +170,9 @@ public abstract class InputHandler extends InputAdapter{
return false;
}
/**Handles tile tap events that are not platform specific.*/
/**
* Handles tile tap events that are not platform specific.
*/
boolean tileTapped(Tile tile){
tile = tile.target();
@@ -153,7 +228,9 @@ public abstract class InputHandler extends InputAdapter{
return consumed;
}
/**Tries to select the player to drop off items, returns true if successful.*/
/**
* Tries to select the player to drop off items, returns true if successful.
*/
boolean tryTapPlayer(float x, float y){
if(canTapPlayer(x, y)){
droppingItem = true;
@@ -166,7 +243,9 @@ public abstract class InputHandler extends InputAdapter{
return Vector2.dst(x, y, player.x, player.y) <= playerSelectRange && player.inventory.hasItem();
}
/**Tries to begin mining a tile, returns true if successful.*/
/**
* Tries to begin mining a tile, returns true if successful.
*/
boolean tryBeginMine(Tile tile){
if(canMine(tile)){
//if a block is clicked twice, reset it
@@ -185,7 +264,9 @@ public abstract class InputHandler extends InputAdapter{
&& tile.block() == Blocks.air && player.distanceTo(tile.worldx(), tile.worldy()) <= Player.mineDistance;
}
/**Returns the tile at the specified MOUSE coordinates.*/
/**
* Returns the tile at the specified MOUSE coordinates.
*/
Tile tileAt(float x, float y){
Vector2 vec = Graphics.world(x, y);
if(isPlacing()){
@@ -279,71 +360,4 @@ public abstract class InputHandler extends InputAdapter{
player.addBuildRequest(new BuildRequest(tile.x, tile.y));
}
@Remote(targets = Loc.client, called = Loc.server, in = In.entities)
public static void dropItem(Player player, float angle){
if(Net.server() && !player.inventory.hasItem()){
throw new ValidateException(player, "Player cannot drop an item.");
}
ItemDrop.create(player.inventory.getItem().item, player.inventory.getItem().amount, player.x, player.y, angle);
player.inventory.clearItem();
}
@Remote(targets = Loc.both, forward = true, called = Loc.server, in = In.blocks)
public static void transferInventory(Player player, Tile tile){
if(Net.server() && (!player.inventory.hasItem() || player.isTransferring)) {
throw new ValidateException(player, "Player cannot transfer an item.");
}
threads.run(() -> {
if (player == null || tile.entity == null) return;
player.isTransferring = true;
ItemStack stack = player.inventory.getItem();
int accepted = tile.block().acceptStack(stack.item, stack.amount, tile, player);
boolean clear = stack.amount == accepted;
int sent = Mathf.clamp(accepted / 4, 1, 8);
int removed = accepted / sent;
int[] remaining = {accepted, accepted};
for (int i = 0; i < sent; i++) {
boolean end = i == sent - 1;
Timers.run(i * 3, () -> {
tile.block().getStackOffset(stack.item, tile, stackTrns);
ItemTransfer.create(stack.item,
player.x + Angles.trnsx(player.rotation + 180f, backTrns), player.y + Angles.trnsy(player.rotation + 180f, backTrns),
new Translator(tile.drawx() + stackTrns.x, tile.drawy() + stackTrns.y), () -> {
tile.block().handleStack(stack.item, removed, tile, player);
remaining[1] -= removed;
if (end && remaining[1] > 0) {
tile.block().handleStack(stack.item, remaining[1], tile, player);
}
});
stack.amount -= removed;
remaining[0] -= removed;
if (end) {
stack.amount -= remaining[0];
if (clear) {
player.inventory.clearItem();
}
player.isTransferring = false;
}
});
}
});
}
@Remote(targets = Loc.both, called = Loc.server, forward = true, in = In.blocks)
public static void onTileTapped(Player player, Tile tile){
if(tile == null || player == null) return;
tile.block().tapped(tile, player);
}
}
@@ -39,11 +39,14 @@ import static io.anuke.mindustry.Vars.*;
import static io.anuke.mindustry.input.PlaceMode.*;
public class MobileInput extends InputHandler implements GestureListener{
private static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
/**Maximum speed the player can pan.*/
/**
* Maximum speed the player can pan.
*/
private static final float maxPanSpeed = 1.3f;
/**Distance to edge of screen to start panning.*/
private static Rectangle r1 = new Rectangle(), r2 = new Rectangle();
/**
* Distance to edge of screen to start panning.
*/
private final float edgePan = io.anuke.ucore.scene.ui.layout.Unit.dp.scl(60f);
//gesture data
@@ -51,31 +54,53 @@ public class MobileInput extends InputHandler implements GestureListener{
private Vector2 vector = new Vector2();
private float initzoom = -1;
private boolean zoomed = false;
/**Set of completed guides.*/
/**
* Set of completed guides.
*/
private ObjectSet<String> guides = new ObjectSet<>();
/**Position where the player started dragging a line.*/
/**
* Position where the player started dragging a line.
*/
private int lineStartX, lineStartY;
/**Animation scale for line.*/
/**
* Animation scale for line.
*/
private float lineScale;
/**Animation data for crosshair.*/
/**
* Animation data for crosshair.
*/
private float crosshairScale;
private TargetTrait lastTarget;
/**List of currently selected tiles to place.*/
/**
* List of currently selected tiles to place.
*/
private Array<PlaceRequest> selection = new Array<>();
/**Place requests to be removed.*/
/**
* Place requests to be removed.
*/
private Array<PlaceRequest> removals = new Array<>();
/**Whether or not the player is currently shifting all placed tiles.*/
/**
* Whether or not the player is currently shifting all placed tiles.
*/
private boolean selecting;
/**Whether the player is currently in line-place mode.*/
/**
* Whether the player is currently in line-place mode.
*/
private boolean lineMode;
/**Current place mode.*/
/**
* Current place mode.
*/
private PlaceMode mode = none;
/**Whether no recipe was available when switching to break mode.*/
/**
* Whether no recipe was available when switching to break mode.
*/
private Recipe lastRecipe;
/**Last placed request. Used for drawing block overlay.*/
/**
* Last placed request. Used for drawing block overlay.
*/
private PlaceRequest lastPlaced;
public MobileInput(Player player){
@@ -85,7 +110,9 @@ public class MobileInput extends InputHandler implements GestureListener{
//region utility methods
/**Check and assign targets for a specific position.*/
/**
* Check and assign targets for a specific position.
*/
void checkTargets(float x, float y){
synchronized(Entities.entityLock){
Unit unit = Units.getClosestEnemy(player.getTeam(), x, y, 20f, u -> true);
@@ -103,12 +130,16 @@ public class MobileInput extends InputHandler implements GestureListener{
}
}
/**Returns whether this tile is in the list of requests, or at least colliding with one.*/
/**
* Returns whether this tile is in the list of requests, or at least colliding with one.
*/
boolean hasRequest(Tile tile){
return getRequest(tile) != null;
}
/**Returns whether this block overlaps any selection requests.*/
/**
* Returns whether this block overlaps any selection requests.
*/
boolean checkOverlapPlacement(int x, int y, Block block){
r2.setSize(block.size * tilesize);
r2.setCenter(x * tilesize + block.offset(), y * tilesize + block.offset());
@@ -128,7 +159,9 @@ public class MobileInput extends InputHandler implements GestureListener{
return false;
}
/**Returns the selection request that overlaps this tile, or null.*/
/**
* Returns the selection request that overlaps this tile, or null.
*/
PlaceRequest getRequest(Tile tile){
r2.setSize(tilesize);
r2.setCenter(tile.worldx(), tile.worldy());
@@ -733,8 +766,15 @@ public class MobileInput extends InputHandler implements GestureListener{
zoomed = false;
}
@Override public boolean touchDown(float x, float y, int pointer, int button) { return false; }
@Override public boolean fling(float velocityX, float velocityY, int button) { return false; }
@Override
public boolean touchDown(float x, float y, int pointer, int button){
return false;
}
@Override
public boolean fling(float velocityX, float velocityY, int button){
return false;
}
//endregion
@@ -9,7 +9,8 @@ public class PlaceUtils {
private static final NormalizeResult result = new NormalizeResult();
private static final NormalizeDrawResult drawResult = new NormalizeDrawResult();
/**Normalizes a placement area and returns the result, ready to be used for drawing a rectangle.
/**
* Normalizes a placement area and returns the result, ready to be used for drawing a rectangle.
* Returned x2 and y2 will <i>always</i> be greater than x and y.
*
* @param block block that will be drawn
@@ -45,7 +46,8 @@ public class PlaceUtils {
return drawResult;
}
/**Normalizes a placement area and returns the result.
/**
* Normalizes a placement area and returns the result.
* Returned x2 and y2 will <i>always</i> be greater than x and y.
*
* @param tilex starting X coordinate
@@ -121,17 +123,23 @@ public class PlaceUtils {
return Math.abs(x2 - x) > Math.abs(y2 - y);
}
/**Returns length of greater edge of the selection.*/
/**
* Returns length of greater edge of the selection.
*/
int getLength(){
return Math.max(x2 - x, y2 - y);
}
/**Returns the X position of a specific index along this area as a line.*/
/**
* Returns the X position of a specific index along this area as a line.
*/
int getScaledX(int i){
return x + (x2 - x > y2 - y ? i : 0);
}
/**Returns the Y position of a specific index along this area as a line.*/
/**
* Returns the Y position of a specific index along this area as a line.
*/
int getScaledY(int i){
return y + (x2 - x > y2 - y ? 0 : i);
}
+15 -5
View File
@@ -7,15 +7,25 @@ import io.anuke.ucore.function.Supplier;
import java.io.InputStream;
public class Map{
/**Internal map name. This is the filename, without any extensions.*/
/**
* Internal map name. This is the filename, without any extensions.
*/
public final String name;
/**Whether this is a custom map.*/
/**
* Whether this is a custom map.
*/
public final boolean custom;
/**Metadata. Author description, display name, etc.*/
/**
* Metadata. Author description, display name, etc.
*/
public final MapMeta meta;
/**Supplies a new input stream with the data of this map.*/
/**
* Supplies a new input stream with the data of this map.
*/
public final Supplier<InputStream> stream;
/**Preview texture.*/
/**
* Preview texture.
*/
public Texture texture;
public Map(String name, MapMeta meta, boolean custom, Supplier<InputStream> streamSupplier){
+12 -4
View File
@@ -18,7 +18,9 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**Reads and writes map files.*/
/**
* Reads and writes map files.
*/
public class MapIO{
private static final int version = 0;
private static IntIntMap defaultBlockMap = new IntIntMap();
@@ -94,21 +96,27 @@ public class MapIO {
ds.close();
}
/**Reads tile data, skipping meta.*/
/**
* Reads tile data, skipping meta.
*/
public static MapTileData readTileData(DataInputStream stream, boolean readOnly) throws IOException{
MapMeta meta = readMapMeta(stream);
return readTileData(stream, meta, readOnly);
}
/**Does not skip meta. Call after reading meta.*/
/**
* Does not skip meta. Call after reading meta.
*/
public static MapTileData readTileData(DataInputStream stream, MapMeta meta, boolean readOnly) throws IOException{
byte[] bytes = new byte[stream.available()];
stream.readFully(bytes);
return new MapTileData(bytes, meta.width, meta.height, meta.blockMap, readOnly);
}
/**Reads tile data, skipping meta tags.*/
/**
* Reads tile data, skipping meta tags.
*/
public static MapTileData readTileData(Map map, boolean readOnly){
try(DataInputStream ds = new DataInputStream(map.stream.get())){
return MapIO.readTileData(ds, readOnly);
@@ -6,12 +6,14 @@ import io.anuke.ucore.util.Bits;
import java.nio.ByteBuffer;
public class MapTileData{
/**Tile size: 4 bytes. <br>
/**
* Tile size: 4 bytes. <br>
* 0: ground tile <br>
* 1: wall tile <br>
* 2: rotation + team <br>
* 3: link (x/y) <br>
* 4: elevation <br>*/
* 4: elevation <br>
*/
private final static int TILE_SIZE = 5;
private final ByteBuffer buffer;
@@ -59,28 +61,38 @@ public class MapTileData {
return height;
}
/**Write a byte to a specific position.*/
/**
* Write a byte to a specific position.
*/
public void write(int x, int y, DataPosition position, byte data){
buffer.put((x + width * y) * TILE_SIZE + position.ordinal(), data);
}
/**Gets a byte at a specific position.*/
/**
* Gets a byte at a specific position.
*/
public byte read(int x, int y, DataPosition position){
return buffer.get((x + width * y) * TILE_SIZE + position.ordinal());
}
/**Reads and returns the next tile data.*/
/**
* Reads and returns the next tile data.
*/
public TileDataMarker read(TileDataMarker marker){
marker.read(buffer);
return marker;
}
/**Writes this tile data marker.*/
/**
* Writes this tile data marker.
*/
public void write(TileDataMarker marker){
marker.write(buffer);
}
/**Sets read position to the specified coordinates*/
/**
* Sets read position to the specified coordinates
*/
public void position(int x, int y){
buffer.position((x + width * y) * TILE_SIZE);
}
+42 -14
View File
@@ -17,30 +17,46 @@ import java.io.*;
import static io.anuke.mindustry.Vars.*;
public class Maps implements Disposable{
/**List of all built-in maps.*/
/**
* List of all built-in maps.
*/
private static final String[] defaultMapNames = {};
/**Tile format version.*/
/**
* Tile format version.
*/
private static final int version = 0;
/**Maps map names to the real maps.*/
/**
* Maps map names to the real maps.
*/
private ObjectMap<String, Map> maps = new ObjectMap<>();
/**All maps stored in an ordered array.*/
/**
* All maps stored in an ordered array.
*/
private Array<Map> allMaps = new ThreadArray<>();
/**Temporary array used for returning things.*/
/**
* Temporary array used for returning things.
*/
private Array<Map> returnArray = new ThreadArray<>();
/**Used for storing a list of custom map names for GWT.*/
/**
* Used for storing a list of custom map names for GWT.
*/
private Array<String> customMapNames;
public Maps(){
}
/**Returns a list of all maps, including custom ones.*/
/**
* Returns a list of all maps, including custom ones.
*/
public Array<Map> all(){
return allMaps;
}
/**Returns a list of only custom maps.*/
/**
* Returns a list of only custom maps.
*/
public Array<Map> customMaps(){
returnArray.clear();
for(Map map : allMaps){
@@ -49,7 +65,9 @@ public class Maps implements Disposable{
return returnArray;
}
/**Returns a list of only default maps.*/
/**
* Returns a list of only default maps.
*/
public Array<Map> defaultMaps(){
returnArray.clear();
for(Map map : allMaps){
@@ -58,12 +76,16 @@ public class Maps implements Disposable{
return returnArray;
}
/**Returns map by internal name.*/
/**
* Returns map by internal name.
*/
public Map getByName(String name){
return maps.get(name);
}
/**Load all maps. Should be called at application start.*/
/**
* Load all maps. Should be called at application start.
*/
public void load(){
try{
for(String name : defaultMapNames){
@@ -77,7 +99,9 @@ public class Maps implements Disposable{
loadCustomMaps();
}
/**Save a map. This updates all values and stored data necessary.*/
/**
* Save a map. This updates all values and stored data necessary.
*/
public void saveMap(String name, MapTileData data, ObjectMap<String, String> tags){
try{
if(!gwt){
@@ -114,7 +138,9 @@ public class Maps implements Disposable{
}
}
/**Removes a map completely.*/
/**
* Removes a map completely.
*/
public void removeMap(Map map){
if(map.texture != null){
map.texture.dispose();
@@ -177,7 +203,9 @@ public class Maps implements Disposable{
}
}
/**Returns an input stream supplier for a given map name.*/
/**
* Returns an input stream supplier for a given map name.
*/
private Supplier<InputStream> getStreamFor(String name){
if(!gwt){
return customMapDirectory.child(name + "." + mapExtension)::read;
@@ -23,5 +23,6 @@ public abstract class SaveFileVersion {
}
public abstract void read(DataInputStream stream) throws IOException;
public abstract void write(DataOutputStream stream) throws IOException;
}
+3 -1
View File
@@ -27,7 +27,9 @@ import java.nio.ByteBuffer;
import static io.anuke.mindustry.Vars.*;
/**Class for specifying read/write methods for code generation.*/
/**
* Class for specifying read/write methods for code generation.
*/
public class TypeIO{
@WriteClass(Player.class)
@@ -19,11 +19,17 @@ public class Administration {
public static final int defaultMaxBrokenBlocks = 15;
public static final int defaultBreakCooldown = 1000 * 15;
/**All player info. Maps UUIDs to info. This persists throughout restarts.*/
/**
* All player info. Maps UUIDs to info. This persists throughout restarts.
*/
private ObjectMap<String, PlayerInfo> playerInfo = new ObjectMap<>();
/**Maps UUIDs to trace infos. This is wiped when a player logs off.*/
/**
* Maps UUIDs to trace infos. This is wiped when a player logs off.
*/
private ObjectMap<String, TraceInfo> traceInfo = new ObjectMap<>();
/**Maps packed coordinates to logs for that coordinate */
/**
* Maps packed coordinates to logs for that coordinate
*/
private IntMap<Array<EditLog>> editLogs = new IntMap<>();
private Array<String> bannedIPs = new Array<>();
@@ -42,6 +48,11 @@ public class Administration {
return Settings.getBool("antigrief");
}
public void setAntiGrief(boolean antiGrief){
Settings.putBool("antigrief", antiGrief);
Settings.save();
}
public boolean allowsCustomClients(){
return Settings.getBool("allow-custom", !headless);
}
@@ -55,11 +66,6 @@ public class Administration {
return false;
}
public void setAntiGrief(boolean antiGrief){
Settings.putBool("antigrief", antiGrief);
Settings.save();
}
public void setAntiGriefParams(int maxBreak, int cooldown){
Settings.putInt("antigrief-max", maxBreak);
Settings.putInt("antigrief-cooldown", cooldown);
@@ -71,11 +77,11 @@ public class Administration {
}
public void logEdit(int x, int y, Player player, Block block, int rotation, EditLog.EditAction action){
if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock) return;
if(block instanceof BlockPart || block instanceof Rock || block instanceof Floor || block instanceof StaticBlock)
return;
if(editLogs.containsKey(x + y * world.width())){
editLogs.get(x + y * world.width()).add(new EditLog(player.name, block, rotation, action));
}
else {
}else{
Array<EditLog> logs = new Array<>();
logs.add(new EditLog(player.name, block, rotation, action));
editLogs.put(x + y * world.width(), logs);
@@ -167,7 +173,9 @@ public class Administration {
return true;
}
/**Call when a player joins to update their information here.*/
/**
* Call when a player joins to update their information here.
*/
public void updatePlayerJoined(String id, String ip, String name){
PlayerInfo info = getCreateInfo(id);
info.lastName = name;
@@ -177,7 +185,9 @@ public class Administration {
if(!info.ips.contains(ip, false)) info.ips.add(ip);
}
/**Returns trace info by IP.*/
/**
* Returns trace info by IP.
*/
public TraceInfo getTraceByID(String uuid){
if(!traceInfo.containsKey(uuid)) traceInfo.put(uuid, new TraceInfo(uuid));
@@ -188,8 +198,10 @@ public class Administration {
traceInfo.clear();
}
/**Bans a player by IP; returns whether this player was already banned.
* If there are players who at any point had this IP, they will be UUID banned as well.*/
/**
* Bans a player by IP; returns whether this player was already banned.
* If there are players who at any point had this IP, they will be UUID banned as well.
*/
public boolean banPlayerIP(String ip){
if(bannedIPs.contains(ip, false))
return false;
@@ -206,7 +218,9 @@ public class Administration {
return true;
}
/**Bans a player by UUID; returns whether this player was already banned.*/
/**
* Bans a player by UUID; returns whether this player was already banned.
*/
public boolean banPlayerID(String id){
if(playerInfo.containsKey(id) && playerInfo.get(id).banned)
return false;
@@ -218,8 +232,10 @@ public class Administration {
return true;
}
/**Unbans a player by IP; returns whether this player was banned in the first place.
* This method also unbans any player that was banned and had this IP.*/
/**
* Unbans a player by IP; returns whether this player was banned in the first place.
* This method also unbans any player that was banned and had this IP.
*/
public boolean unbanPlayerIP(String ip){
boolean found = bannedIPs.contains(ip, false);
@@ -237,8 +253,10 @@ public class Administration {
return found;
}
/**Unbans a player by ID; returns whether this player was banned in the first place.
* This also unbans all IPs the player used.*/
/**
* Unbans a player by ID; returns whether this player was banned in the first place.
* This also unbans all IPs the player used.
*/
public boolean unbanPlayerID(String id){
PlayerInfo info = getCreateInfo(id);
@@ -252,7 +270,9 @@ public class Administration {
return true;
}
/**Returns list of all players with admin status*/
/**
* Returns list of all players with admin status
*/
public Array<PlayerInfo> getAdmins(){
Array<PlayerInfo> result = new Array<>();
for(PlayerInfo info : playerInfo.values()){
@@ -263,7 +283,9 @@ public class Administration {
return result;
}
/**Returns list of all players with admin status*/
/**
* Returns list of all players with admin status
*/
public Array<PlayerInfo> getBanned(){
Array<PlayerInfo> result = new Array<>();
for(PlayerInfo info : playerInfo.values()){
@@ -274,12 +296,16 @@ public class Administration {
return result;
}
/**Returns all banned IPs. This does not include the IPs of ID-banned players.*/
/**
* Returns all banned IPs. This does not include the IPs of ID-banned players.
*/
public Array<String> getBannedIPs(){
return bannedIPs;
}
/**Makes a player an admin. Returns whether this player was already an admin.*/
/**
* Makes a player an admin. Returns whether this player was already an admin.
*/
public boolean adminPlayer(String id, String usid){
PlayerInfo info = getCreateInfo(id);
@@ -293,7 +319,9 @@ public class Administration {
return true;
}
/**Makes a player no longer an admin. Returns whether this player was an admin in the first place.*/
/**
* Makes a player no longer an admin. Returns whether this player was an admin in the first place.
*/
public boolean unAdminPlayer(String id){
PlayerInfo info = getCreateInfo(id);
@@ -401,7 +429,8 @@ public class Administration {
this.id = id;
}
private PlayerInfo(){}
private PlayerInfo(){
}
}
}
+3 -1
View File
@@ -1,6 +1,8 @@
package io.anuke.mindustry.net;
/**Stores class nameas for remote method invocation for consistency's sake.*/
/**
* Stores class nameas for remote method invocation for consistency's sake.
*/
public class In{
public static final String normal = "Call";
public static final String entities = "CallEntity";
+175 -71
View File
@@ -38,7 +38,9 @@ public class Net{
private static IntMap<StreamBuilder> streams = new IntMap<>();
/**Display a network error.*/
/**
* Display a network error.
*/
public static void showError(String text){
if(!headless){
ui.showError(text);
@@ -47,7 +49,9 @@ public class Net{
}
}
/**Sets the client loaded status, or whether it will recieve normal packets from the server.*/
/**
* Sets the client loaded status, or whether it will recieve normal packets from the server.
*/
public static void setClientLoaded(boolean loaded){
clientLoaded = loaded;
@@ -62,7 +66,9 @@ public class Net{
packetQueue.clear();
}
/**Connect to an address.*/
/**
* Connect to an address.
*/
public static void connect(String ip, int port) throws IOException{
if(!active){
clientProvider.connect(ip, port);
@@ -73,7 +79,9 @@ public class Net{
}
}
/**Host a server at an address*/
/**
* Host a server at an address
*/
public static void host(int port) throws IOException{
serverProvider.host(port);
active = true;
@@ -82,7 +90,9 @@ public class Net{
Timers.runTask(60f, Platform.instance::updateRPC);
}
/**Closes the server.*/
/**
* Closes the server.
*/
public static void closeServer(){
serverProvider.close();
server = false;
@@ -95,23 +105,31 @@ public class Net{
active = false;
}
/**Starts discovering servers on a different thread. Does not work with GWT.
* Callback is run on the main libGDX thread.*/
/**
* Starts discovering servers on a different thread. Does not work with GWT.
* Callback is run on the main libGDX thread.
*/
public static void discoverServers(Consumer<Array<Host>> cons){
clientProvider.discover(cons);
}
/**Returns a list of all connections IDs.*/
/**
* Returns a list of all connections IDs.
*/
public static Array<NetConnection> getConnections(){
return (Array<NetConnection>) serverProvider.getConnections();
}
/**Returns a connection by ID*/
/**
* Returns a connection by ID
*/
public static NetConnection getConnection(int id){
return serverProvider.getByID(id);
}
/**Send an object to all connected clients, or to the server if this is a client.*/
/**
* Send an object to all connected clients, or to the server if this is a client.
*/
public static void send(Object object, SendMode mode){
if(server){
if(serverProvider != null) serverProvider.send(object, mode);
@@ -120,42 +138,58 @@ public class Net{
}
}
/**Send an object to a certain client. Server-side only*/
/**
* Send an object to a certain client. Server-side only
*/
public static void sendTo(int id, Object object, SendMode mode){
serverProvider.sendTo(id, object, mode);
}
/**Send an object to everyone EXCEPT certain client. Server-side only*/
/**
* Send an object to everyone EXCEPT certain client. Server-side only
*/
public static void sendExcept(int id, Object object, SendMode mode){
serverProvider.sendExcept(id, object, mode);
}
/**Send a stream to a specific client. Server-side only.*/
/**
* Send a stream to a specific client. Server-side only.
*/
public static void sendStream(int id, Streamable stream){
serverProvider.sendStream(id, stream);
}
/**Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.*/
/**
* Sets the net clientProvider, e.g. what handles sending, recieving and connecting to a server.
*/
public static void setClientProvider(ClientProvider provider){
Net.clientProvider = provider;
}
/**Sets the net serverProvider, e.g. what handles hosting a server.*/
/**
* Sets the net serverProvider, e.g. what handles hosting a server.
*/
public static void setServerProvider(ServerProvider provider){
Net.serverProvider = provider;
}
/**Registers a client listener for when an object is recieved.*/
/**
* Registers a client listener for when an object is recieved.
*/
public static <T> void handleClient(Class<T> type, Consumer<T> listener){
clientListeners.put(type, listener);
}
/**Registers a server listener for when an object is recieved.*/
/**
* Registers a server listener for when an object is recieved.
*/
public static <T> void handleServer(Class<T> type, BiConsumer<Integer, T> listener){
serverListeners.put(type, (BiConsumer<Integer, Object>) listener);
}
/**Call to handle a packet being recieved for the client.*/
/**
* Call to handle a packet being recieved for the client.
*/
public static void handleClientReceived(Object object){
if(object instanceof StreamBegin){
@@ -175,7 +209,8 @@ public class Net{
}else if(clientListeners.get(object.getClass()) != null){
if(clientLoaded || ((object instanceof Packet) && ((Packet) object).isImportant())){
if(clientListeners.get(object.getClass()) != null) clientListeners.get(object.getClass()).accept(object);
if(clientListeners.get(object.getClass()) != null)
clientListeners.get(object.getClass()).accept(object);
synchronized(packetPoolLock){
Pooling.free(object);
}
@@ -192,11 +227,14 @@ public class Net{
}
}
/**Call to handle a packet being recieved for the server.*/
/**
* Call to handle a packet being recieved for the server.
*/
public static void handleServerReceived(int connection, Object object){
if(serverListeners.get(object.getClass()) != null){
if(serverListeners.get(object.getClass()) != null) serverListeners.get(object.getClass()).accept(connection, object);
if(serverListeners.get(object.getClass()) != null)
serverListeners.get(object.getClass()).accept(connection, object);
synchronized(packetPoolLock){
Pooling.free(object);
}
@@ -205,32 +243,44 @@ public class Net{
}
}
/**Pings a host in an new thread. If an error occured, failed() should be called with the exception. */
/**
* Pings a host in an new thread. If an error occured, failed() should be called with the exception.
*/
public static void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed){
clientProvider.pingHost(address, port, valid, failed);
}
/**Update client ping.*/
/**
* Update client ping.
*/
public static void updatePing(){
clientProvider.updatePing();
}
/**Get the client ping. Only valid after updatePing().*/
/**
* Get the client ping. Only valid after updatePing().
*/
public static int getPing(){
return server() ? 0 : clientProvider.getPing();
}
/**Whether the net is active, e.g. whether this is a multiplayer game.*/
/**
* Whether the net is active, e.g. whether this is a multiplayer game.
*/
public static boolean active(){
return active;
}
/**Whether this is a server or not.*/
/**
* Whether this is a server or not.
*/
public static boolean server(){
return server && active;
}
/**Whether this is a client or not.*/
/**
* Whether this is a client or not.
*/
public static boolean client(){
return !server && active;
}
@@ -260,54 +310,108 @@ public class Net{
}
@Override
public void cancelled() {}
public void cancelled(){
}
});
}
/**Client implementation.*/
public interface ClientProvider {
/**Connect to a server.*/
void connect(String ip, int port) throws IOException;
/**Send an object to the server.*/
void send(Object object, SendMode mode);
/**Update the ping. Should be done every second or so.*/
void updatePing();
/**Get ping in milliseconds. Will only be valid after a call to updatePing.*/
int getPing();
/**Disconnect from the server.*/
void disconnect();
/**Discover servers. This should run the callback regardless of whether any servers are found. Should not block.
* Callback should be run on libGDX main thread.*/
void discover(Consumer<Array<Host>> callback);
/**Ping a host. If an error occured, failed() should be called with the exception. */
void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed);
/**Close all connections.*/
void dispose();
}
/**Server implementation.*/
public interface ServerProvider {
/**Host a server at specified port.*/
void host(int port) throws IOException;
/**Sends a large stream of data to a specific client.*/
void sendStream(int id, Streamable stream);
/**Send an object to everyone connected.*/
void send(Object object, SendMode mode);
/**Send an object to a specific client ID.*/
void sendTo(int id, Object object, SendMode mode);
/**Send an object to everyone <i>except</i> a client ID.*/
void sendExcept(int id, Object object, SendMode mode);
/**Close the server connection.*/
void close();
/**Return all connected users.*/
Array<? extends NetConnection> getConnections();
/**Returns a connection by ID.*/
NetConnection getByID(int id);
/**Close all connections.*/
void dispose();
}
public enum SendMode{
tcp, udp
}
/**
* Client implementation.
*/
public interface ClientProvider{
/**
* Connect to a server.
*/
void connect(String ip, int port) throws IOException;
/**
* Send an object to the server.
*/
void send(Object object, SendMode mode);
/**
* Update the ping. Should be done every second or so.
*/
void updatePing();
/**
* Get ping in milliseconds. Will only be valid after a call to updatePing.
*/
int getPing();
/**
* Disconnect from the server.
*/
void disconnect();
/**
* Discover servers. This should run the callback regardless of whether any servers are found. Should not block.
* Callback should be run on libGDX main thread.
*/
void discover(Consumer<Array<Host>> callback);
/**
* Ping a host. If an error occured, failed() should be called with the exception.
*/
void pingHost(String address, int port, Consumer<Host> valid, Consumer<Exception> failed);
/**
* Close all connections.
*/
void dispose();
}
/**
* Server implementation.
*/
public interface ServerProvider{
/**
* Host a server at specified port.
*/
void host(int port) throws IOException;
/**
* Sends a large stream of data to a specific client.
*/
void sendStream(int id, Streamable stream);
/**
* Send an object to everyone connected.
*/
void send(Object object, SendMode mode);
/**
* Send an object to a specific client ID.
*/
void sendTo(int id, Object object, SendMode mode);
/**
* Send an object to everyone <i>except</i> a client ID.
*/
void sendExcept(int id, Object object, SendMode mode);
/**
* Close the server connection.
*/
void close();
/**
* Return all connected users.
*/
Array<? extends NetConnection> getConnections();
/**
* Returns a connection by ID.
*/
NetConnection getByID(int id);
/**
* Close all connections.
*/
void dispose();
}
}
@@ -6,10 +6,14 @@ public abstract class NetConnection {
public final int id;
public final String address;
/**The current base snapshot that the client is absolutely confirmed to have recieved.
* All sent snapshots should be taking the diff from this base snapshot, if it isn't null.*/
/**
* The current base snapshot that the client is absolutely confirmed to have recieved.
* All sent snapshots should be taking the diff from this base snapshot, if it isn't null.
*/
public byte[] currentBaseSnapshot;
/**ID of the current base snapshot.*/
/**
* ID of the current base snapshot.
*/
public int currentBaseID = -1;
public int lastSentBase = -1;
@@ -17,9 +21,13 @@ public abstract class NetConnection {
public byte[] lastSentRawSnapshot;
public int lastSentSnapshotID = -1;
/**ID of last recieved client snapshot.*/
/**
* ID of last recieved client snapshot.
*/
public int lastRecievedClientSnapshot = -1;
/**Timestamp of last recieved snapshot.*/
/**
* Timestamp of last recieved snapshot.
*/
public long lastRecievedClientTime;
public boolean hasConnected = false;
@@ -34,5 +42,6 @@ public abstract class NetConnection {
}
public abstract void send(Object object, SendMode mode);
public abstract void close();
}
@@ -112,7 +112,9 @@ public class NetworkIO {
}
}
/**Return whether a custom map is expected, and thus whether the client should wait for additional data.*/
/**
* Return whether a custom map is expected, and thus whether the client should wait for additional data.
*/
public static void loadWorld(InputStream is){
Player player = players[0];
+7 -3
View File
@@ -5,10 +5,14 @@ import com.badlogic.gdx.utils.Pool.Poolable;
import java.nio.ByteBuffer;
public interface Packet extends Poolable{
default void read(ByteBuffer buffer){}
default void write(ByteBuffer buffer){}
default void read(ByteBuffer buffer){
}
default void reset() {}
default void write(ByteBuffer buffer){
}
default void reset(){
}
default boolean isImportant(){
return false;

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