Merge branch 'master' of https://github.com/Anuken/Mindustry into mech-rework

# Conflicts:
#	core/assets/sprites/sprites.atlas
#	core/assets/sprites/sprites.png
This commit is contained in:
Anuken
2019-10-20 10:21:35 -04:00
54 changed files with 1643 additions and 336 deletions

View File

@@ -9,6 +9,7 @@ import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.async.*;
import io.anuke.mindustry.core.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.EventType.*;
@@ -81,6 +82,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
add(netClient = new NetClient());
assets.load(mods);
assets.load(schematics);
assets.loadRun("contentinit", ContentLoader.class, () -> {
content.init();
@@ -118,10 +120,11 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
for(ApplicationListener listener : modules){
listener.init();
}
super.resize(graphics.getWidth(), graphics.getHeight());
mods.each(Mod::init);
finished = true;
Events.fire(new ClientLoadEvent());
super.resize(graphics.getWidth(), graphics.getHeight());
app.post(() -> app.post(() -> app.post(() -> app.post(() -> super.resize(graphics.getWidth(), graphics.getHeight())))));
}
}else{
super.update();
@@ -133,11 +136,7 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform
long target = (1000 * 1000000) / targetfps; //target in nanos
long elapsed = Time.timeSinceNanos(lastTime);
if(elapsed < target){
try{
Thread.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}catch(InterruptedException ignored){
//ignore
}
Threads.sleep((target - elapsed) / 1000000, (int)((target - elapsed) % 1000000));
}
}

View File

@@ -33,6 +33,10 @@ public class Vars implements Loadable{
public static boolean loadLocales = true;
/** Maximum number of broken blocks. TODO implement or remove.*/
public static final int maxBrokenBlocks = 256;
/** Maximum schematic size.*/
public static final int maxSchematicSize = 32;
/** All schematic base64 starts with this string.*/
public static final String schematicBaseStart ="bXNjaAB";
/** IO buffer size. */
public static final int bufferSize = 8192;
/** global charset, since Android doesn't support the Charsets class */
@@ -128,10 +132,14 @@ public class Vars implements Loadable{
public static FileHandle saveDirectory;
/** data subdirectory used for mods */
public static FileHandle modDirectory;
/** data subdirectory used for schematics */
public static FileHandle schematicDirectory;
/** map file extension */
public static final String mapExtension = "msav";
/** save file extension */
public static final String saveExtension = "msav";
/** schematic file extension */
public static final String schematicExtension = "msch";
/** list of all locales that can be switched to */
public static Locale[] locales;
@@ -146,6 +154,7 @@ public class Vars implements Loadable{
public static LoopControl loops;
public static Platform platform = new Platform(){};
public static Mods mods;
public static Schematics schematics = new Schematics();
public static World world;
public static Maps maps;
@@ -251,6 +260,7 @@ public class Vars implements Loadable{
saveDirectory = dataDirectory.child("saves/");
tmpDirectory = dataDirectory.child("tmp/");
modDirectory = dataDirectory.child("mods/");
schematicDirectory = dataDirectory.child("schematics/");
modDirectory.mkdirs();

View File

@@ -1344,7 +1344,8 @@ public class Blocks implements ContentList{
Items.pyratite, Bullets.pyraFlame
);
recoil = 0f;
reload = 4f;
reload = 5f;
coolantMultiplier = 2f;
range = 60f;
shootCone = 50f;
targetAir = false;

View File

@@ -99,8 +99,7 @@ public class Bullets implements ContentList{
collidesTiles = false;
splashDamageRadius = 25f;
splashDamage = 30f;
incendAmount = 4;
incendSpread = 11f;
status = StatusEffects.burning;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
trailEffect = Fx.incendTrail;
@@ -228,8 +227,7 @@ public class Bullets implements ContentList{
splashDamage = 10f;
lifetime = 160f;
hitEffect = Fx.blastExplosion;
incendSpread = 10f;
incendAmount = 3;
status = StatusEffects.burning;
}};
missileSurge = new MissileBulletType(4.4f, 15, "bullet"){{
@@ -342,9 +340,7 @@ public class Bullets implements ContentList{
bulletHeight = 12f;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
incendSpread = 3f;
incendAmount = 1;
incendChance = 0.3f;
status = StatusEffects.burning;
inaccuracy = 3f;
lifetime = 60f;
}};
@@ -354,9 +350,7 @@ public class Bullets implements ContentList{
bulletHeight = 12f;
frontColor = Color.valueOf("feb380");
backColor = Color.valueOf("ea8878");
incendSpread = 3f;
incendAmount = 1;
incendChance = 0.3f;
status = StatusEffects.burning;
lifetime = 60f;
}};
@@ -385,9 +379,7 @@ public class Bullets implements ContentList{
bulletHeight = 21f;
frontColor = Pal.lightishOrange;
backColor = Pal.lightOrange;
incendSpread = 3f;
incendAmount = 2;
incendChance = 0.3f;
status = StatusEffects.burning;
shootEffect = Fx.shootBig;
}};

View File

@@ -18,7 +18,7 @@ public class StatusEffects implements ContentList{
none = new StatusEffect();
burning = new StatusEffect(){{
damage = 0.04f;
damage = 0.06f;
effect = Fx.burning;
opposite(() -> wet, () -> freezing);

View File

@@ -317,9 +317,9 @@ public class TechTree implements ContentList{
return node(block, () -> {});
}
public static void create(Block parent, Block block){
public static TechNode create(Block parent, Block block){
TechNode.context = all.find(t -> t.block == parent);
node(block, () -> {});
return node(block, () -> {});
}
public static class TechNode{

View File

@@ -8,9 +8,9 @@ import io.anuke.arc.function.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.maps.*;
import io.anuke.mindustry.net.*;
import io.anuke.mindustry.net.Net.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.dialogs.*;
import static io.anuke.mindustry.Vars.mobile;
@@ -24,27 +24,18 @@ public interface Platform{
default void inviteFriends(){}
/** Steam: Share a map on the workshop.*/
default void publishMap(Map map){}
/** Steam: Return external workshop maps to be loaded.*/
default Array<FileHandle> getExternalMaps(){
return Array.with();
}
/** Steam: Return external workshop mods to be loaded.*/
default Array<FileHandle> getExternalMods(){
return Array.with();
}
/** Steam: View a map listing on the workshop.*/
default void viewMapListing(Map map){}
default void publish(Publishable pub){}
/** Steam: View a listing on the workshop.*/
default void viewListing(String mapid){}
default void viewListing(Publishable pub){}
/** Steam: View map workshop info, removing the map ID tag if its listing is deleted.
* Also presents the option to update the map. */
default void viewMapListingInfo(Map map){}
/** Steam: View a listing on the workshop by an ID.*/
default void viewListingID(String mapid){}
/** Steam: Return external workshop maps to be loaded.*/
default Array<FileHandle> getWorkshopContent(Class<? extends Publishable> type){
return new Array<>(0);
}
/** Steam: Open workshop for maps.*/
default void openWorkshop(){}

View File

@@ -68,6 +68,7 @@ public class UI implements ApplicationListener, Loadable{
public DeployDialog deploy;
public TechTreeDialog tech;
public MinimapDialog minimap;
public SchematicsDialog schematics;
public ModsDialog mods;
public Cursor drillCursor, unloadCursor;
@@ -185,6 +186,13 @@ public class UI implements ApplicationListener, Loadable{
Core.scene.act();
Core.scene.draw();
if(Core.input.keyTap(KeyCode.MOUSE_LEFT) && Core.scene.getKeyboardFocus() instanceof TextField){
Element e = Core.scene.hit(Core.input.mouseX(), Core.input.mouseY(), true);
if(!(e instanceof TextField)){
Core.scene.setKeyboardFocus(null);
}
}
//draw overlay for buttons
if(state.rules.tutorial){
control.tutorial.draw();
@@ -225,6 +233,7 @@ public class UI implements ApplicationListener, Loadable{
tech = new TechTreeDialog();
minimap = new MinimapDialog();
mods = new ModsDialog();
schematics = new SchematicsDialog();
Group group = Core.scene.root;
@@ -296,7 +305,7 @@ public class UI implements ApplicationListener, Loadable{
}
public void showTextInput(String title, String text, String def, Consumer<String> confirmed){
showTextInput(title, text, 24, def, confirmed);
showTextInput(title, text, 32, def, confirmed);
}
public void showTextInput(String titleText, String text, int textLength, String def, Consumer<String> confirmed){
@@ -307,7 +316,7 @@ public class UI implements ApplicationListener, Loadable{
Table table = new Table();
table.setFillParent(true);
table.actions(Actions.fadeOut(7f, Interpolation.fade), Actions.remove());
table.top().add(info).padTop(10);
table.top().add(info).style(Styles.outlineLabel).padTop(10);
Core.scene.add(table);
}

View File

@@ -149,15 +149,16 @@ public class MapEditorDialog extends Dialog implements Disposable{
if(steam){
menu.cont.addImageTextButton("$editor.publish.workshop", Icon.linkSmall, () -> {
Map builtin = maps.all().find(m -> m.name().equals(editor.getTags().get("name", "").trim()));
if(editor.getTags().containsKey("steamid") && builtin != null && !builtin.custom){
platform.viewListing(editor.getTags().get("steamid"));
platform.viewListingID(editor.getTags().get("steamid"));
return;
}
Map map = save();
if(editor.getTags().containsKey("steamid") && map != null){
platform.viewMapListingInfo(map);
platform.viewListing(map);
return;
}
@@ -173,7 +174,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
return;
}
platform.publishMap(map);
platform.publish(map);
}).padTop(-3).size(swidth * 2f + 10, 60f).update(b -> b.setText(editor.getTags().containsKey("steamid") ? editor.getTags().get("author").equals(player.name) ? "$workshop.listing" : "$view.workshop" : "$editor.publish.workshop"));
menu.cont.row();

View File

@@ -47,7 +47,7 @@ public abstract class BulletType extends Content{
/** Status effect applied on hit. */
public StatusEffect status = StatusEffects.none;
/** Intensity of applied status effect in terms of duration. */
public float statusDuration = 60 * 1f;
public float statusDuration = 60 * 10f;
/** Whether this bullet type collides with tiles. */
public boolean collidesTiles = true;
/** Whether this bullet type collides with tiles that are of the same team. */

View File

@@ -104,7 +104,7 @@ public interface BuilderTrait extends Entity, TeamTrait{
if(current.breaking){
entity.deconstruct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier);
}else{
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier)){
if(entity.construct(unit, core, 1f / entity.buildCost * Time.delta() * getBuildPower(tile) * state.rules.buildSpeedMultiplier, current.hasConfig)){
if(current.hasConfig){
Call.onTileConfig(null, tile, current.config);
}
@@ -267,18 +267,26 @@ public interface BuilderTrait extends Entity, TeamTrait{
/** Class for storing build requests. Can be either a place or remove request. */
class BuildRequest{
/** Position and rotation of this request. */
public int x, y, rotation;
/** Block being placed. If null, this is a breaking request.*/
public @Nullable Block block;
/** Whether this is a break request.*/
public boolean breaking;
/** Whether this request comes with a config int. If yes, any blocks placed with this request will not call playerPlaced.*/
public boolean hasConfig;
/** Config int. Not used unless hasConfig is true.*/
public int config;
/** Original position, only used in schematics.*/
public int originalX, originalY, originalWidth, originalHeight;
/** Last progress.*/
public float progress;
/** Whether construction has started for this request.*/
public boolean initialized;
//animation variables
/** Visual scale. Used only for rendering.*/
public float animScale = 0f;
public float animInvalid;
/** This creates a build request. */
public BuildRequest(int x, int y, int rotation, Block block){
@@ -302,6 +310,31 @@ public interface BuilderTrait extends Entity, TeamTrait{
}
public BuildRequest copy(){
BuildRequest copy = new BuildRequest();
copy.x = x;
copy.y = y;
copy.rotation = rotation;
copy.block = block;
copy.breaking = breaking;
copy.hasConfig = hasConfig;
copy.config = config;
copy.originalX = originalX;
copy.originalY = originalY;
copy.progress = progress;
copy.initialized = initialized;
copy.animScale = animScale;
return copy;
}
public BuildRequest original(int x, int y, int originalWidth, int originalHeight){
originalX = x;
originalY = y;
this.originalWidth = originalWidth;
this.originalHeight = originalHeight;
return this;
}
public Rectangle bounds(Rectangle rect){
if(breaking){
return rect.set(-100f, -100f, 0f, 0f);

View File

@@ -1,5 +1,6 @@
package io.anuke.mindustry.entities.type;
import io.anuke.mindustry.*;
import io.anuke.mindustry.entities.EntityGroup;
import io.anuke.mindustry.entities.traits.Entity;
@@ -14,6 +15,14 @@ public abstract class BaseEntity implements Entity{
id = lastid++;
}
public int tileX(){
return Vars.world.toTile(x);
}
public int tileY(){
return Vars.world.toTile(y);
}
@Override
public int getID(){
return id;

View File

@@ -801,6 +801,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{
textFadeTime = 0f;
target = null;
moveTarget = null;
isShooting = isBoosting = isTransferring = isTyping = false;
spawner = lastSpawner = null;
health = maxHealth();
mining = null;

View File

@@ -39,6 +39,9 @@ public class GlobalData{
files.add(Core.settings.getSettingsFile());
files.addAll(customMapDirectory.list());
files.addAll(saveDirectory.list());
files.addAll(screenshotDirectory.list());
files.addAll(modDirectory.list());
files.addAll(schematicDirectory.list());
String base = Core.settings.getDataDirectory().path();
try(OutputStream fos = file.write(false, 2048); ZipOutputStream zos = new ZipOutputStream(fos)){

View File

@@ -0,0 +1,111 @@
package io.anuke.mindustry.game;
import io.anuke.arc.collection.*;
import io.anuke.arc.collection.IntIntMap.*;
import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.Schematics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import static io.anuke.mindustry.Vars.*;
public class Schematic implements Publishable{
public final Array<Stile> tiles;
public StringMap tags;
public int width, height;
public @Nullable FileHandle file;
public Schematic(Array<Stile> tiles, StringMap tags, int width, int height){
this.tiles = tiles;
this.tags = tags;
this.width = width;
this.height = height;
}
public Array<ItemStack> requirements(){
IntIntMap amounts = new IntIntMap();
tiles.each(t -> {
for(ItemStack stack : t.block.requirements){
amounts.getAndIncrement(stack.item.id, 0, stack.amount);
}
});
Array<ItemStack> stacks = new Array<>();
for(Entry ent : amounts.entries()){
stacks.add(new ItemStack(Vars.content.item(ent.key), ent.value));
}
stacks.sort();
return stacks;
}
public String name(){
return tags.get("name", "unknown");
}
public void save(){
schematics.saveChanges(this);
}
@Override
public String getSteamID(){
return tags.get("steamid");
}
@Override
public void addSteamID(String id){
tags.put("steamid", id);
save();
}
@Override
public void removeSteamID(){
tags.remove("steamid");
save();
}
@Override
public String steamTitle(){
return name();
}
@Override
public String steamDescription(){
return null;
}
@Override
public String steamTag(){
return "schematic";
}
@Override
public FileHandle createSteamFolder(String id){
FileHandle directory = tmpDirectory.child("schematic_" + id).child("schematic." + schematicExtension);
file.copyTo(directory);
return directory;
}
@Override
public FileHandle createSteamPreview(String id){
FileHandle preview = tmpDirectory.child("schematic_preview_" + id + ".png");
schematics.savePreview(this, PreviewRes.high, preview);
return preview;
}
public static class Stile{
public @NonNull Block block;
public short x, y;
public int config;
public byte rotation;
public Stile(Block block, int x, int y, int config, byte rotation){
this.block = block;
this.x = (short)x;
this.y = (short)y;
this.config = config;
this.rotation = rotation;
}
}
}

View File

@@ -0,0 +1,380 @@
package io.anuke.mindustry.game;
import io.anuke.arc.*;
import io.anuke.arc.assets.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.graphics.glutils.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.io.Streams.*;
import io.anuke.arc.util.serialization.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.Schematic.*;
import io.anuke.mindustry.input.*;
import io.anuke.mindustry.input.PlaceUtils.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
import java.io.*;
import java.util.zip.*;
import static io.anuke.mindustry.Vars.*;
/** Handles schematics.*/
public class Schematics implements Loadable{
private static final byte[] header = {'m', 's', 'c', 'h'};
private static final byte version = 0;
private static final int padding = 2;
private OptimizedByteArrayOutputStream out = new OptimizedByteArrayOutputStream(1024);
private Array<Schematic> all = new Array<>();
private OrderedMap<Schematic, ObjectMap<PreviewRes, FrameBuffer>> previews = new OrderedMap<>();
private FrameBuffer shadowBuffer;
public Schematics(){
Events.on(DisposeEvent.class, e -> {
previews.each((schem, m) -> m.each((res, buffer) -> buffer.dispose()));
previews.clear();
shadowBuffer.dispose();
});
}
@Override
public void loadSync(){
load();
}
/** Load all schematics in the folder immediately.*/
public void load(){
all.clear();
for(FileHandle file : schematicDirectory.list()){
loadFile(file);
}
platform.getWorkshopContent(Schematic.class).each(this::loadFile);
Core.app.post(() -> {
shadowBuffer = new FrameBuffer(maxSchematicSize + padding + 2, maxSchematicSize + padding + 2);
});
}
private void loadFile(FileHandle file){
if(!file.extension().equals(schematicExtension)) return;
try{
Schematic s = read(file);
all.add(s);
//external file from workshop
if(!s.file.parent().equals(schematicDirectory)){
s.tags.put("steamid", s.file.parent().name());
}
}catch(IOException e){
Log.err(e);
}
}
public Array<Schematic> all(){
return all;
}
public void saveChanges(Schematic s){
if(s.file != null){
try{
write(s, s.file);
}catch(Exception e){
ui.showException(e);
}
}
}
public void savePreview(Schematic schematic, PreviewRes res, FileHandle file){
FrameBuffer buffer = getBuffer(schematic, res);
Draw.flush();
buffer.begin();
Pixmap pixmap = ScreenUtils.getFrameBufferPixmap(0, 0, buffer.getWidth(), buffer.getHeight());
file.writePNG(pixmap);
buffer.end();
}
public Texture getPreview(Schematic schematic, PreviewRes res){
return getBuffer(schematic, res).getTexture();
}
public FrameBuffer getBuffer(Schematic schematic, PreviewRes res){
if(!previews.getOr(schematic, ObjectMap::new).containsKey(res)){
int resolution = res.resolution;
Draw.blend();
Draw.reset();
Time.mark();
Tmp.m1.set(Draw.proj());
Tmp.m2.set(Draw.trans());
FrameBuffer buffer = new FrameBuffer((schematic.width + padding) * resolution, (schematic.height + padding) * resolution);
shadowBuffer.beginDraw(Color.clear);
Draw.trans().idt();
Draw.proj().setOrtho(0, 0, shadowBuffer.getWidth(), shadowBuffer.getHeight());
Draw.color();
schematic.tiles.each(t -> {
int size = t.block.size;
int offsetx = -(size - 1) / 2;
int offsety = -(size - 1) / 2;
for(int dx = 0; dx < size; dx++){
for(int dy = 0; dy < size; dy++){
int wx = t.x + dx + offsetx;
int wy = t.y + dy + offsety;
Fill.square(padding/2f + wx + 0.5f, padding/2f + wy + 0.5f, 0.5f);
}
}
});
shadowBuffer.endDraw();
buffer.beginDraw(Color.clear);
Draw.proj().setOrtho(0, buffer.getHeight(), buffer.getWidth(), -buffer.getHeight());
Tmp.tr1.set(shadowBuffer.getTexture(), 0, 0, schematic.width + padding, schematic.height + padding);
Draw.color(0f, 0f, 0f, 1f);
Draw.rect(Tmp.tr1, buffer.getWidth()/2f, buffer.getHeight()/2f, buffer.getWidth(), -buffer.getHeight());
Draw.color();
Array<BuildRequest> requests = schematic.tiles.map(t -> new BuildRequest(t.x, t.y, t.rotation, t.block).configure(t.config));
Draw.flush();
//scale each request to fit schematic
Draw.trans().scale(resolution / tilesize, resolution / tilesize).translate(tilesize*1.5f, tilesize*1.5f);
//draw requests
requests.each(req -> {
req.animScale = 1f;
req.block.drawRequestRegion(req, requests::each);
});
requests.each(req -> req.block.drawRequestConfigTop(req, requests::each));
Draw.flush();
Draw.trans().idt();
buffer.endDraw();
Draw.proj(Tmp.m1);
Draw.trans(Tmp.m2);
previews.getOr(schematic, ObjectMap::new).put(res, buffer);
Log.info("Time taken: {0}", Time.elapsed());
}
return previews.get(schematic).get(res);
}
/** Creates an array of build requests from a schematic's data, centered on the provided x+y coordinates. */
public Array<BuildRequest> toRequests(Schematic schem, int x, int y){
return schem.tiles.map(t -> new BuildRequest(t.x + x - schem.width/2, t.y + y - schem.height/2, t.rotation, t.block).original(t.x, t.y, schem.width, schem.height).configure(t.config)).removeAll(s -> !s.block.isVisible());
}
/** Adds a schematic to the list, also copying it into the files.*/
public void add(Schematic schematic){
all.add(schematic);
try{
write(schematic, schematicDirectory.child(Time.millis() + "." + schematicExtension));
}catch(IOException e){
Log.err(e);
}
}
public void remove(Schematic s){
all.remove(s);
if(s.file != null){
s.file.delete();
}
}
/** Creates a schematic from a world selection. */
public Schematic create(int x, int y, int x2, int y2){
NormalizeResult result = PlaceUtils.normalizeArea(x, y, x2, y2, 0, false, maxSchematicSize);
x = result.x;
y = result.y;
x2 = result.x2;
y2 = result.y2;
Array<Stile> tiles = new Array<>();
int minx = x2, miny = y2, maxx = x, maxy = y;
boolean found = false;
for(int cx = x; cx <= x2; cx++){
for(int cy = y; cy <= y2; cy++){
Tile linked = world.ltile(cx, cy);
if(linked != null && linked.entity != null && linked.entity.block.isVisible()){
int top = linked.block().size/2;
int bot = linked.block().size % 2 == 1 ? -linked.block().size/2 : -(linked.block().size - 1)/2;
minx = Math.min(linked.x + bot, minx);
miny = Math.min(linked.y + bot, miny);
maxx = Math.max(linked.x + top, maxx);
maxy = Math.max(linked.y + top, maxy);
found = true;
}
}
}
if(found){
x = minx;
y = miny;
x2 = maxx;
y2 = maxy;
}else{
return new Schematic(new Array<>(), new StringMap(), 1, 1);
}
int width = x2 - x + 1, height = y2 - y + 1;
int offsetX = -x, offsetY = -y;
for(int cx = x; cx <= x2; cx++){
for(int cy = y; cy <= y2; cy++){
Tile tile = world.tile(cx, cy);
if(tile != null && tile.entity != null){
int config = tile.entity.config();
if(tile.block().posConfig){
config = Pos.get(Pos.x(config) + offsetX, Pos.y(config) + offsetY);
}
tiles.add(new Stile(tile.block(), cx + offsetX, cy + offsetY, config, tile.rotation()));
}
}
}
return new Schematic(tiles, new StringMap(), width, height);
}
/** Converts a schematic to base64. Note that the result of this will always start with 'bXNjaAB'.*/
public String writeBase64(Schematic schematic){
try{
out.reset();
write(schematic, out);
return new String(Base64Coder.encode(out.getBuffer(), out.size()));
}catch(IOException e){
throw new RuntimeException(e);
}
}
/** Loads a schematic from base64. May throw an exception. */
public Schematic readBase64(String schematic) throws IOException{
return read(new ByteArrayInputStream(Base64Coder.decode(schematic)));
}
//region IO methods
public static Schematic read(FileHandle file) throws IOException{
Schematic s = read(new DataInputStream(file.read(1024)));
if(!s.tags.containsKey("name")){
s.tags.put("name", file.nameWithoutExtension());
}
s.file = file;
return s;
}
public static Schematic read(InputStream input) throws IOException{
for(byte b : header){
if(input.read() != b){
throw new IOException("Not a schematic file (missing header).");
}
}
int ver;
if((ver = input.read()) != version){
throw new IOException("Unknown version: " + ver);
}
try(DataInputStream stream = new DataInputStream(new InflaterInputStream(input))){
short width = stream.readShort(), height = stream.readShort();
StringMap map = new StringMap();
byte tags = stream.readByte();
for(int i = 0; i < tags; i++){
map.put(stream.readUTF(), stream.readUTF());
}
IntMap<Block> blocks = new IntMap<>();
byte length = stream.readByte();
for(int i = 0; i < length; i++){
Block block = Vars.content.getByName(ContentType.block, stream.readUTF());
blocks.put(i, block == null ? Blocks.air : block);
}
int total = stream.readInt();
Array<Stile> tiles = new Array<>(total);
for(int i = 0; i < total; i++){
Block block = blocks.get(stream.readByte());
int position = stream.readInt();
int config = stream.readInt();
byte rotation = stream.readByte();
if(block != Blocks.air){
tiles.add(new Stile(block, Pos.x(position), Pos.y(position), config, rotation));
}
}
return new Schematic(tiles, map, width, height);
}
}
public static void write(Schematic schematic, FileHandle file) throws IOException{
write(schematic, file.write(false, 1024));
}
public static void write(Schematic schematic, OutputStream output) throws IOException{
output.write(header);
output.write(version);
try(DataOutputStream stream = new DataOutputStream(new DeflaterOutputStream(output))){
stream.writeShort(schematic.width);
stream.writeShort(schematic.height);
stream.writeByte(schematic.tags.size);
for(ObjectMap.Entry<String, String> e : schematic.tags.entries()){
stream.writeUTF(e.key);
stream.writeUTF(e.value);
}
OrderedSet<Block> blocks = new OrderedSet<>();
schematic.tiles.each(t -> blocks.add(t.block));
//create dictionary
stream.writeByte(blocks.size);
for(int i = 0; i < blocks.size; i++){
stream.writeUTF(blocks.orderedItems().get(i).name);
}
stream.writeInt(schematic.tiles.size);
//write each tile
for(Stile tile : schematic.tiles){
stream.writeByte(blocks.orderedItems().indexOf(tile.block));
stream.writeInt(Pos.get(tile.x, tile.y));
stream.writeInt(tile.config);
stream.writeByte(tile.rotation);
}
}
}
//endregion
public enum PreviewRes{
low(8), med(8), high(32);
public final int resolution;
PreviewRes(int resolution){
this.resolution = resolution;
}
}
}

View File

@@ -40,6 +40,10 @@ public class Tutorial{
Events.on(BlockInfoEvent.class, event -> events.add("blockinfo"));
Events.on(DepositEvent.class, event -> events.add("deposit"));
Events.on(WithdrawEvent.class, event -> events.add("withdraw"));
for(TutorialStage stage : TutorialStage.values()){
stage.load();
}
}
/** update tutorial state, transition if needed */
@@ -204,13 +208,17 @@ public class Tutorial{
/** displayed tutorial stage text.*/
public String text(){
if(sentences == null){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
load();
}
String line = sentences.get(control.tutorial.sentence);
return line.contains("{") ? text.get(line) : line;
}
void load(){
this.line = Core.bundle.has("tutorial." + name() + ".mobile") && mobile ? "tutorial." + name() + ".mobile" : "tutorial." + name();
this.sentences = Array.select(Core.bundle.get(line).split("\n"), s -> !s.isEmpty());
}
/** called every frame when this stage is active.*/
void update(){

View File

@@ -18,6 +18,10 @@ public enum Binding implements KeyBind{
rotateplaced(KeyCode.R),
diagonal_placement(KeyCode.CONTROL_LEFT),
pick(KeyCode.MOUSE_MIDDLE),
schematic_select(KeyCode.F),
schematic_flip_x(KeyCode.Z),
schematic_flip_y(KeyCode.X),
schematic_menu(KeyCode.T),
dash(KeyCode.SHIFT_LEFT),
gridMode(KeyCode.BACKTICK),
gridModeShift(KeyCode.ALT_LEFT),

View File

@@ -6,11 +6,15 @@ import io.anuke.arc.Graphics.Cursor.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.core.GameState.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.ui.*;
@@ -24,9 +28,9 @@ public class DesktopInput extends InputHandler{
/** Current cursor type. */
private Cursor cursorType = SystemCursor.arrow;
/** Position where the player started dragging a line. */
private int selectX, selectY;
private int selectX, selectY, schemX, schemY;
/** Last known line positions.*/
private int lastLineX, lastLineY;
private int lastLineX, lastLineY, schematicX, schematicY;
/** Whether selecting mode is active. */
private PlaceMode mode;
/** Animation scale for line. */
@@ -40,14 +44,39 @@ public class DesktopInput extends InputHandler{
public void buildUI(Group group){
group.fill(t -> {
t.bottom().update(() -> t.getColor().a = Mathf.lerpDelta(t.getColor().a, player.isBuilding() ? 1f : 0f, 0.15f));
t.visible(() -> Core.settings.getBool("hints"));
t.visible(() -> Core.settings.getBool("hints") && selectRequests.isEmpty());
t.table(Styles.black6, b -> {
b.defaults().left();
b.label(() -> Core.bundle.format(!player.isBuilding ? "resumebuilding" : "pausebuilding", Core.keybinds.get(Binding.pause_building).key.name())).style(Styles.outlineLabel);
b.row();
b.add(Core.bundle.format("cancelbuilding", Core.keybinds.get(Binding.clear_building).key.name())).style(Styles.outlineLabel);
b.row();
b.add(Core.bundle.format("selectschematic", Core.keybinds.get(Binding.schematic_select).key.name())).style(Styles.outlineLabel);
}).margin(10f);
});
group.fill(t -> {
t.visible(() -> lastSchematic != null && !selectRequests.isEmpty());
t.bottom();
t.table(Styles.black6, b -> {
b.touchable(Touchable.enabled);
b.defaults().left();
b.add(Core.bundle.format("schematic.flip",
Core.keybinds.get(Binding.schematic_flip_x).key.name(),
Core.keybinds.get(Binding.schematic_flip_y).key.name())).style(Styles.outlineLabel);
b.row();
b.table(a -> {
a.addImageTextButton("$schematic.add", Icon.saveSmall, () -> {
ui.showTextInput("$schematic.add", "$name", "", text -> {
lastSchematic.tags.put("name", text);
schematics.add(lastSchematic);
ui.showInfoFade("$schematic.saved");
ui.schematics.showInfo(lastSchematic);
});
}).colspan(2).size(250f, 50f);
});
}).margin(6f);
});
}
@Override
@@ -66,7 +95,7 @@ public class DesktopInput extends InputHandler{
drawRequest(lineRequests.get(i));
}
}else if(mode == breaking){
drawSelection(selectX, selectY, cursorX, cursorY);
drawBreakSelection(selectX, selectY, cursorX, cursorY);
}else if(isPlacing()){
if(block.rotate){
drawArrow(block, cursorX, cursorY, rotation);
@@ -83,6 +112,12 @@ public class DesktopInput extends InputHandler{
}
}
//draw schematic requests
for(BuildRequest request : selectRequests){
request.animScale = 1f;
drawRequest(request);
}
if(sreq != null){
boolean valid = validPlace(sreq.x, sreq.y, sreq.block, sreq.rotation, sreq);
if(sreq.block.rotate){
@@ -94,6 +129,10 @@ public class DesktopInput extends InputHandler{
drawSelected(sreq.x, sreq.y, sreq.block, getRequest(sreq.x, sreq.y, sreq.block.size, sreq) != null ? Pal.remove : Pal.accent);
}
if(Core.input.keyDown(Binding.schematic_select)){
drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize);
}
Draw.reset();
}
@@ -118,7 +157,7 @@ public class DesktopInput extends InputHandler{
if(state.is(State.menu) || Core.scene.hasDialog()) return;
//zoom things
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && (Core.input.keyDown(Binding.zoom_hold))){
if(Math.abs(Core.input.axisTap(Binding.zoom)) > 0 && Core.input.keyDown(Binding.zoom_hold)){
renderer.scaleCamera(Core.input.axisTap(Binding.zoom));
}
@@ -134,6 +173,10 @@ public class DesktopInput extends InputHandler{
mode = none;
}
if(mode != none){
selectRequests.clear();
}
if(player.isShooting && !canShoot()){
player.isShooting = false;
}
@@ -151,8 +194,12 @@ public class DesktopInput extends InputHandler{
sreq.rotation = Mathf.mod(sreq.rotation + (int)Core.input.axisTap(Binding.rotate), 4);
}
if(Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0 && isPlacing() && mode == placing){
updateLine(selectX, selectY);
if(!Core.input.keyDown(Binding.zoom_hold) && Math.abs((int)Core.input.axisTap(Binding.rotate)) > 0){
if(isPlacing() && mode == placing){
updateLine(selectX, selectY);
}else if(!selectRequests.isEmpty()){
rotateRequests(selectRequests, (int)Core.input.axisTap(Binding.rotate));
}
}
Tile cursor = tileAt(Core.input.mouseX(), Core.input.mouseY());
@@ -162,7 +209,7 @@ public class DesktopInput extends InputHandler{
cursorType = cursor.block().getCursor(cursor);
if(isPlacing()){
if(isPlacing() || !selectRequests.isEmpty()){
cursorType = SystemCursor.hand;
}
@@ -190,15 +237,49 @@ public class DesktopInput extends InputHandler{
cursorType = SystemCursor.arrow;
}
@Override
public void useSchematic(Schematic schem){
block = null;
schematicX = tileX(getMouseX());
schematicY = tileY(getMouseY());
selectRequests.addAll(schematics.toRequests(schem, schematicX, schematicY));
mode = none;
}
@Override
public boolean isBreaking(){
return mode == breaking;
}
@Override
public void buildPlacementUI(Table table){
table.addImage().color(Pal.gray).height(4f).colspan(4).growX();
table.row();
table.left().margin(0f).defaults().size(48f).left();
table.addImageButton(Icon.wikiSmall, Styles.clearPartiali, () -> {
ui.schematics.show();
});
}
void pollInput(){
Tile selected = tileAt(Core.input.mouseX(), Core.input.mouseY());
int cursorX = tileX(Core.input.mouseX());
int cursorY = tileY(Core.input.mouseY());
int rawCursorX = world.toTile(Core.input.mouseWorld().x), rawCursorY = world.toTile(Core.input.mouseWorld().y);
if(!selectRequests.isEmpty()){
int shiftX = rawCursorX - schematicX, shiftY = rawCursorY - schematicY;
selectRequests.each(s -> {
s.x += shiftX;
s.y += shiftY;
});
schematicX += shiftX;
schematicY += shiftY;
}
if(Core.input.keyTap(Binding.deselect)){
player.setMineTile(null);
@@ -208,6 +289,38 @@ public class DesktopInput extends InputHandler{
player.clearBuilding();
}
if(Core.input.keyTap(Binding.schematic_select)){
schemX = rawCursorX;
schemY = rawCursorY;
}
if(Core.input.keyTap(Binding.schematic_menu)){
ui.schematics.show();
}
if(Core.input.keyTap(Binding.clear_building)){
lastSchematic = null;
selectRequests.clear();
}
if(Core.input.keyRelease(Binding.schematic_select)){
lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY);
useSchematic(lastSchematic);
if(selectRequests.isEmpty()){
lastSchematic = null;
}
}
if(!selectRequests.isEmpty()){
if(Core.input.keyTap(Binding.schematic_flip_x)){
flipRequests(selectRequests, true);
}
if(Core.input.keyTap(Binding.schematic_flip_y)){
flipRequests(selectRequests, false);
}
}
if(sreq != null){
float offset = ((sreq.block.size + 2) % 2) * tilesize / 2f;
float x = Core.input.mouseWorld().x + offset;
@@ -233,7 +346,10 @@ public class DesktopInput extends InputHandler{
if(Core.input.keyTap(Binding.select) && !Core.scene.hasMouse()){
BuildRequest req = getRequest(cursorX, cursorY);
if(isPlacing()){
if(!selectRequests.isEmpty()){
flushRequests(selectRequests);
//selectRequests.clear();
}else if(isPlacing()){
selectX = cursorX;
selectY = cursorY;
lastLineX = cursorX;
@@ -329,6 +445,7 @@ public class DesktopInput extends InputHandler{
mode = none;
block = null;
sreq = null;
selectRequests.clear();
}
}
}

View File

@@ -14,12 +14,14 @@ import io.anuke.arc.scene.*;
import io.anuke.arc.scene.event.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.entities.*;
import io.anuke.mindustry.entities.effect.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Teams.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
@@ -51,6 +53,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
public boolean droppingItem;
public Group uiGroup;
protected @Nullable Schematic lastSchematic;
protected GestureDetector detector;
protected PlaceLine line = new PlaceLine();
protected BuildRequest resultreq;
@@ -207,7 +210,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
if(request.breaking){
drawBreaking(request.x, request.y);
}else{
drawSelected(request.x, request.y, request.tile().block(), Pal.remove);
drawSelected(request.x, request.y, request.block, Pal.remove);
}
}
@@ -219,6 +222,76 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
drawSelected(x, y, block, Pal.remove);
}
public void useSchematic(Schematic schem){
selectRequests.addAll(schematics.toRequests(schem, world.toTile(player.x), world.toTile(player.y)));
}
public void rotateRequests(Array<BuildRequest> requests, int direction){
int ox = rawTileX(), oy = rawTileY();
requests.each(req -> {
//rotate config position
if(req.block.posConfig){
int cx = Pos.x(req.config) - req.originalX, cy = Pos.y(req.config) - req.originalY;
int lx = cx;
if(direction >= 0){
cx = -cy;
cy = lx;
}else{
cx = cy;
cy = -lx;
}
req.config = Pos.get(cx + req.originalX, cy + req.originalY);
}
//rotate actual request, centered on its multiblock position
float wx = (req.x - ox) * tilesize + req.block.offset(), wy = (req.y - oy) * tilesize + req.block.offset();
float x = wx;
if(direction >= 0){
wx = -wy;
wy = x;
}else{
wx = wy;
wy = -x;
}
req.x = world.toTile(wx - req.block.offset()) + ox;
req.y = world.toTile(wy - req.block.offset()) + oy;
req.rotation = Mathf.mod(req.rotation + direction, 4);
});
}
public void flipRequests(Array<BuildRequest> requests, boolean x){
int origin = x ? rawTileX() : rawTileY();
requests.each(req -> {
int value = -((x ? req.x : req.y) - origin) + origin;
if(x){
req.x = value;
}else{
req.y = value;
}
if(req.block.posConfig){
int corigin = x ? req.originalWidth/2 : req.originalHeight/2;
int nvalue = -((x ? Pos.x(req.config) : Pos.y(req.config)) - corigin) + corigin;
if(x){
req.originalX = -(req.originalX - corigin) + corigin;
req.config = Pos.get(nvalue, Pos.y(req.config));
}else{
req.originalY = -(req.originalY - corigin) + corigin;
req.config = Pos.get(Pos.x(req.config), nvalue);
}
}
//flip rotation
if(x == (req.rotation % 2 == 0)){
req.rotation = Mathf.mod(req.rotation + 2, 4);
}
});
}
/** Returns the selection request that overlaps this position, or null. */
protected BuildRequest getRequest(int x, int y){
return getRequest(x, y, 1, null);
@@ -259,7 +332,7 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return null;
}
protected void drawSelection(int x1, int y1, int x2, int y2){
protected void drawBreakSelection(int x1, int y1, int x2, int y2){
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
NormalizeResult dresult = PlaceUtils.normalizeArea(x1, y1, x2, y2, rotation, false, maxLength);
@@ -307,10 +380,21 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}
protected void drawSelection(int x1, int y1, int x2, int y2, int maxLength){
NormalizeDrawResult result = PlaceUtils.normalizeDrawArea(Blocks.air, x1, y1, x2, y2, false, maxLength, 1f);
Lines.stroke(2f);
Draw.color(Pal.accentBack);
Lines.rect(result.x, result.y - 1, result.x2 - result.x, result.y2 - result.y);
Draw.color(Pal.accent);
Lines.rect(result.x, result.y, result.x2 - result.x, result.y2 - result.y);
}
protected void flushSelectRequests(Array<BuildRequest> requests){
for(BuildRequest req : requests){
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
selectRequests.add(req);
selectRequests.add(req.copy());
}
}
}
@@ -318,7 +402,11 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
protected void flushRequests(Array<BuildRequest> requests){
for(BuildRequest req : requests){
if(req.block != null && validPlace(req.x, req.y, req.block, req.rotation)){
player.addBuildRequest(req);
BuildRequest copy = req.copy();
if(copy.hasConfig && copy.block.posConfig){
copy.config = Pos.get(Pos.x(copy.config) + copy.x - copy.originalX, Pos.y(copy.config) + copy.y - copy.originalY);
}
player.addBuildRequest(copy);
}
}
}
@@ -448,14 +536,6 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
}
}
/*
//clear when the player taps on something else
if(!consumed && !mobile && player.isBuilding() && block == null){
//player.clearBuilding();
block = null;
return true;
}*/
if(!showedInventory){
frag.inv.hide();
}
@@ -499,6 +579,14 @@ public abstract class InputHandler implements InputProcessor, GestureListener{
return world.tile(tileX(x), tileY(y));
}
int rawTileX(){
return world.toTile(Core.input.mouseWorld().x);
}
int rawTileY(){
return world.toTile(Core.input.mouseWorld().y);
}
int tileX(float cursorX){
Vector2 vec = Core.input.mouseWorld(cursorX, 0);
if(selectedBlock()){

View File

@@ -208,7 +208,7 @@ public class MobileInput extends InputHandler implements GestureListener{
}
//move all current requests to removal array so they fade out
removals.addAll(selectRequests.find(r -> !r.breaking));
removals.addAll(selectRequests.select(r -> !r.breaking));
selectRequests.clear();
selecting = false;
}).visible(() -> !selectRequests.isEmpty()).name("confirmplace");
@@ -243,7 +243,6 @@ public class MobileInput extends InputHandler implements GestureListener{
if(tile == null) continue;
request.animScale = Mathf.lerpDelta(request.animScale, 0f, 0.2f);
request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0f, 0.2f);
if(request.breaking){
drawSelected(request.x, request.y, tile.block(), Pal.remove);
@@ -263,10 +262,8 @@ public class MobileInput extends InputHandler implements GestureListener{
if((!request.breaking && validPlace(tile.x, tile.y, request.block, request.rotation))
|| (request.breaking && validBreak(tile.x, tile.y))){
request.animScale = Mathf.lerpDelta(request.animScale, 1f, 0.2f);
request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0f, 0.2f);
}else{
request.animScale = Mathf.lerpDelta(request.animScale, 0.6f, 0.1f);
request.animInvalid = Mathf.lerpDelta(request.animInvalid, 0.9f, 0.2f);
}
Tmp.c1.set(Draw.getMixColor());
@@ -305,7 +302,7 @@ public class MobileInput extends InputHandler implements GestureListener{
drawRequest(lineRequests.get(i));
}
}else if(mode == breaking){
drawSelection(lineStartX, lineStartY, tileX, tileY);
drawBreakSelection(lineStartX, lineStartY, tileX, tileY);
}
}
@@ -340,7 +337,7 @@ public class MobileInput extends InputHandler implements GestureListener{
if(request.breaking){
drawSelected(request.x, request.y, request.tile().block(), Pal.remove);
}else{
drawRequest(request.x, request.y, request.block, request.rotation);
request.block.drawRequest(request, allRequests(), validPlace(request.x, request.y, request.block, request.rotation));
drawSelected(request.x, request.y, request.block, Pal.accent);
}
}

View File

@@ -95,14 +95,14 @@ public class PlaceUtils{
}else{
endx = tilex;
}
}
if(Math.abs(endx - tilex) > maxLength){
endx = Mathf.sign(endx - tilex) * maxLength + tilex;
}
if(Math.abs(endx - tilex) > maxLength){
endx = Mathf.sign(endx - tilex) * maxLength + tilex;
}
if(Math.abs(endy - tiley) > maxLength){
endy = Mathf.sign(endy - tiley) * maxLength + tiley;
}
if(Math.abs(endy - tiley) > maxLength){
endy = Mathf.sign(endy - tiley) * maxLength + tiley;
}
int dx = endx - tilex, dy = endy - tiley;
@@ -141,12 +141,12 @@ public class PlaceUtils{
return result;
}
static class NormalizeDrawResult{
public static class NormalizeDrawResult{
float x, y, x2, y2;
}
static class NormalizeResult{
int x, y, x2, y2, rotation;
public static class NormalizeResult{
public int x, y, x2, y2, rotation;
boolean isX(){
return Math.abs(x2 - x) > Math.abs(y2 - y);

View File

@@ -4,14 +4,17 @@ import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.game.EventType.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.io.*;
import io.anuke.mindustry.maps.filters.*;
import io.anuke.mindustry.type.*;
import static io.anuke.mindustry.Vars.maps;
import static io.anuke.mindustry.Vars.*;
public class Map implements Comparable<Map>{
public class Map implements Comparable<Map>, Publishable{
/** Whether this is a custom map. */
public final boolean custom;
/** Metadata. Author description, display name, etc. */
@@ -131,6 +134,76 @@ public class Map implements Comparable<Map>{
return tags.containsKey(name);
}
@Override
public String getSteamID(){
return tags.get("steamid");
}
@Override
public void addSteamID(String id){
tags.put("steamid", id);
ui.editor.editor.getTags().put("steamid", id);
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
Events.fire(new MapPublishEvent());
}
@Override
public void removeSteamID(){
tags.remove("steamid");
ui.editor.editor.getTags().remove("steamid");
try{
ui.editor.save();
}catch(Exception e){
Log.err(e);
}
}
@Override
public String steamTitle(){
return name();
}
@Override
public String steamDescription(){
return description();
}
@Override
public String steamTag(){
return "map";
}
@Override
public FileHandle createSteamFolder(String id){
return null;
}
@Override
public FileHandle createSteamPreview(String id){
return null;
}
@Override
public Array<String> extraTags(){
Gamemode mode = Gamemode.attack.valid(this) ? Gamemode.attack : Gamemode.survival;
return Array.with(mode.name());
}
@Override
public boolean prePublish(){
tags.put("author", player.name);
ui.editor.editor.getTags().put("author", tags.get("author"));
ui.editor.save();
return true;
}
@Override
public int compareTo(Map map){
int work = -Boolean.compare(workshop, map.workshop);

View File

@@ -104,7 +104,7 @@ public class Maps{
}
//workshop
for(FileHandle file : platform.getExternalMaps()){
for(FileHandle file : platform.getWorkshopContent(Map.class)){
try{
Map map = loadMap(file, false);
map.workshop = true;

View File

@@ -15,6 +15,7 @@ import io.anuke.arc.util.serialization.*;
import io.anuke.arc.util.serialization.Json.*;
import io.anuke.mindustry.*;
import io.anuke.mindustry.content.*;
import io.anuke.mindustry.content.TechTree.*;
import io.anuke.mindustry.entities.Effects.*;
import io.anuke.mindustry.entities.bullet.*;
import io.anuke.mindustry.entities.type.*;
@@ -42,21 +43,12 @@ public class ContentParser{
if(data.isString()){
return field(Bullets.class, data);
}
Class<? extends BulletType> bc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.entities.bullets") : BasicBulletType.class;
Class<? extends BulletType> bc = data.has("type") ? resolve(data.getString("type"), "io.anuke.mindustry.entities.bullet") : BasicBulletType.class;
data.remove("type");
BulletType result = make(bc);
readFields(result, data);
return result;
});
/*
put(Music.class, (type, data) -> {
if(fieldOpt(Musics.class, data) != null) return fieldOpt(Musics.class, data);
String path = "music/" + data.asString() + (Vars.ios ? ".mp3" : ".ogg");
Core.assets.load(path, Music.class);
Core.assets.finishLoadingAsset(path);
return Core.assets.get(path);
});*/
put(Sound.class, (type, data) -> {
if(fieldOpt(Sounds.class, data) != null) return fieldOpt(Sounds.class, data);
@@ -78,6 +70,7 @@ public class ContentParser{
/** Stores things that need to be parsed fully, e.g. reading fields of content.
* This is done to accomodate binding of content names first.*/
private Array<Runnable> reads = new Array<>();
private Array<Runnable> postreads = new Array<>();
private LoadedMod currentMod;
private Content currentContent;
@@ -147,6 +140,15 @@ public class ContentParser{
}
currentContent = block;
String[] research = {null};
//add research tech node
if(value.has("research")){
research[0] = value.get("research").asString();
value.remove("research");
}
read(() -> {
if(value.has("consumes")){
for(JsonValue child : value.get("consumes")){
@@ -174,8 +176,16 @@ public class ContentParser{
readFields(block, value, true);
//add research tech node
if(value.has("research")){
TechTree.create(find(ContentType.block, value.get("research").asString()), block);
if(research[0] != null){
Block parent = find(ContentType.block, research[0]);
TechNode baseNode = TechTree.create(parent, block);
postreads.add(() -> {
TechNode parnode = TechTree.all.find(t -> t.block == parent);
if(!parnode.children.contains(baseNode)){
parnode.children.add(baseNode);
}
});
}
//make block visible by default if there are requirements and no visibility set
@@ -275,10 +285,12 @@ public class ContentParser{
public void finishParsing(){
try{
reads.each(Runnable::run);
postreads.each(Runnable::run);
}catch(Exception e){
Vars.mods.handleError(new ModLoadException("Error occurred parsing content: " + currentContent, currentContent, e), currentMod);
}
reads.clear();
postreads.clear();
}
/**
@@ -395,9 +407,7 @@ public class ContentParser{
FieldMetadata metadata = fields.get(child.name().replace(" ", "_"));
if(metadata == null){
if(ignoreUnknownFields){
if(!child.name.equals("research")){
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
}
Log.err("{0}: Ignoring unknown field: " + child.name + " (" + type.getName() + ")", object);
continue;
}else{
SerializationException ex = new SerializationException("Field not found: " + child.name + " (" + type.getName() + ")");

View File

@@ -60,7 +60,7 @@ public class Mods implements Loadable{
file.copyTo(dest);
try{
loaded.add(loadMod(file, false));
loaded.add(loadMod(dest, false));
requiresReload = true;
}catch(IOException e){
dest.delete();
@@ -180,7 +180,7 @@ public class Mods implements Loadable{
}
//load workshop mods now
for(FileHandle file : platform.getExternalMods()){
for(FileHandle file : platform.getWorkshopContent(LoadedMod.class)){
try{
LoadedMod mod = loadMod(file, true);
if(mod.enabled()){
@@ -442,7 +442,7 @@ public class Mods implements Loadable{
}
/** Represents a plugin that has been loaded from a jar file.*/
public static class LoadedMod{
public static class LoadedMod implements Publishable{
/** The location of this mod's zip file/folder on the disk. */
public final FileHandle file;
/** The root zip file; points to the contents of this mod. In the case of folders, this is the same as the mod's file. */
@@ -453,8 +453,6 @@ public class Mods implements Loadable{
public final String name;
/** This mod's metadata. */
public final ModMeta meta;
/** The ID of this mod in the workshop.*/
public @Nullable String workshopID;
public LoadedMod(FileHandle file, FileHandle root, Mod mod, ModMeta meta){
this.root = root;
@@ -468,6 +466,63 @@ public class Mods implements Loadable{
return Core.settings.getBool(name + "-enabled", true);
}
@Override
public String getSteamID(){
return Core.settings.getString(name + "-steamid", null);
}
@Override
public void addSteamID(String id){
Core.settings.put(name + "-steamid", id);
Core.settings.save();
}
@Override
public void removeSteamID(){
Core.settings.remove(name + "-steamid");
Core.settings.save();
}
@Override
public String steamTitle(){
return meta.name;
}
@Override
public String steamDescription(){
return meta.description;
}
@Override
public String steamTag(){
return "mod";
}
@Override
public FileHandle createSteamFolder(String id){
return file;
}
@Override
public FileHandle createSteamPreview(String id){
return file.child("preview.png");
}
@Override
public boolean prePublish(){
if(!file.isDirectory()){
ui.showErrorMessage("$mod.folder.missing");
return false;
}
if(!file.child("preview.png").exists()){
ui.showErrorMessage("$mod.preview.missing");
return false;
}
return true;
}
@Override
public String toString(){
return "LoadedMod{" +

View File

@@ -0,0 +1,40 @@
package io.anuke.mindustry.type;
import io.anuke.arc.collection.*;
import io.anuke.arc.files.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.*;
/** Defines a piece of content that can be published on the Workshop. */
public interface Publishable{
/** @return workshop item ID, or null if this isn't on the workshop. */
@Nullable String getSteamID();
/** adds a steam ID to this item once it's published. should save the item to make sure this change is persisted. */
void addSteamID(String id);
/** removes the item ID; called when the item isn't found. */
void removeSteamID();
/** @return default title of the listing. */
String steamTitle();
/** @return standard steam listing description, may be null. this is editable by users after release.*/
@Nullable String steamDescription();
/** @return the tag that this content has. e.g. 'schematic' or 'map'. */
String steamTag();
/** @return a folder with everything needed for this piece of content in it; does not need to be a copy. */
FileHandle createSteamFolder(String id);
/** @return a preview file PNG. */
FileHandle createSteamPreview(String id);
/** @return any extra tags to add to this item.*/
default Array<String> extraTags(){
return new Array<>(0);
}
/** @return whether this item is or was once on the workshop.*/
default boolean hasSteamID(){
return getSteamID() != null && Vars.steam;
}
/** called before this item is published.
* @return true to signify that everything is cool and good, or false to significy that the user has done something wrong.
* if false is returned, make sure to show a dialog explaining the error. */
default boolean prePublish(){
return true;
}
}

View File

@@ -55,6 +55,7 @@ public class FloatingDialog extends Dialog{
@Override
public void addCloseButton(){
buttons.defaults().size(210f, 64f);
buttons.addImageTextButton("$back", Icon.arrowLeft, this::hide).size(210f, 64f);
keyDown(key -> {

View File

@@ -205,7 +205,7 @@ public class MapsDialog extends FloatingDialog{
table.addImageTextButton(map.workshop && steam ? "$view.workshop" : "$delete", map.workshop && steam ? Icon.linkSmall : Icon.trash16Small, () -> {
if(map.workshop && steam){
platform.viewMapListing(map);
platform.viewListing(map);
}else{
ui.showConfirm("$confirm", Core.bundle.format("map.delete", map.name()), () -> {
maps.removeMap(map);

View File

@@ -41,11 +41,11 @@ public class ModsDialog extends FloatingDialog{
mods.reloadContent();
setup();
ui.loadfrag.hide();
}catch(Exception e){
}catch(Throwable e){
ui.showException(e);
}
});
}catch(Exception e){
}catch(Throwable e){
ui.showException(e);
}
}, t -> Core.app.post(() -> ui.showException(t)));
@@ -101,14 +101,20 @@ public class ModsDialog extends FloatingDialog{
setup();
}).height(50f).margin(8f).width(130f);
title.addImageButton(mod.workshopID != null ? Icon.linkSmall : Icon.trash16Small, Styles.cleari, () -> {
if(mod.workshopID == null){
if(steam && !mod.hasSteamID()){
title.addImageButton(Icon.loadMapSmall, Styles.cleari, () -> {
platform.publish(mod);
}).size(50f);
}
title.addImageButton(mod.hasSteamID() ? Icon.linkSmall : Icon.trash16Small, Styles.cleari, () -> {
if(!mod.hasSteamID()){
ui.showConfirm("$confirm", "$mod.remove.confirm", () -> {
mods.removeMod(mod);
setup();
});
}else{
platform.viewListing(mod.workshopID);
platform.viewListing(mod);
}
}).size(50f);
}).growX().left().padTop(-14f).padRight(-14f);

View File

@@ -0,0 +1,287 @@
package io.anuke.mindustry.ui.dialogs;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.Texture.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.ImageButton.*;
import io.anuke.arc.scene.ui.TextButton.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.game.Schematics.*;
import io.anuke.mindustry.gen.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.ui.*;
import static io.anuke.mindustry.Vars.*;
public class SchematicsDialog extends FloatingDialog{
private SchematicInfoDialog info = new SchematicInfoDialog();
private String search = "";
public SchematicsDialog(){
super("$schematics");
Core.assets.load("sprites/schematic-background.png", Texture.class).loaded = t -> {
((Texture)t).setWrap(TextureWrap.Repeat);
};
shouldPause = true;
addCloseButton();
buttons.addImageTextButton("$schematic.import", Icon.loadMapSmall, this::showImport);
shown(this::setup);
}
void setup(){
search = "";
Runnable[] rebuildPane = {null};
cont.top();
cont.clear();
cont.table(s -> {
s.left();
s.addImage(Icon.zoom);
s.addField(search, res -> {
search = res;
rebuildPane[0].run();
}).growX();
}).fillX().padBottom(4);
cont.row();
cont.pane(t -> {
t.top();
t.margin(20f);
rebuildPane[0] = () -> {
t.clear();
int i = 0;
if(!schematics.all().contains(s -> search.isEmpty() || s.name().contains(search))){
t.add("$none");
}
for(Schematic s : schematics.all()){
if(!search.isEmpty() && !s.name().contains(search)) continue;
Button[] sel = {null};
sel[0] = t.addButton(b -> {
b.top();
b.margin(0f);
b.table(buttons -> {
buttons.left();
buttons.defaults().size(50f);
ImageButtonStyle style = Styles.clearPartiali;
buttons.addImageButton(Icon.infoSmall, style, () -> {
showInfo(s);
});
buttons.addImageButton(Icon.loadMapSmall, style, () -> {
showExport(s);
});
buttons.addImageButton(Icon.pencilSmall, style, () -> {
ui.showTextInput("$schematic.rename", "$name", s.name(), res -> {
s.tags.put("name", res);
s.save();
rebuildPane[0].run();
});
});
if(s.hasSteamID()){
buttons.addImageButton(Icon.linkSmall, style, () -> platform.viewListing(s));
}else{
buttons.addImageButton(Icon.trash16Small, style, () -> {
ui.showConfirm("$confirm", "$schematic.delete.confirm", () -> {
schematics.remove(s);
rebuildPane[0].run();
});
});
}
}).growX().height(50f);
b.row();
b.stack(new SchematicImage(s).setScaling(Scaling.fit), new Table(n -> {
n.top();
n.table(Styles.black3, c -> {
Label label = c.add(s.name()).style(Styles.outlineLabel).color(Color.white).top().growX().get();
label.setEllipsis(true);
label.setAlignment(Align.center);
}).growX().margin(1).pad(4).padBottom(0);
})).size(200f);
}, () -> {
if(sel[0].childrenPressed()) return;
control.input.useSchematic(s);
hide();
}).pad(4).style(Styles.cleari).get();
sel[0].getStyle().up = Tex.pane;
if(++i % 4 == 0){
t.row();
}
}
};
rebuildPane[0].run();
}).get().setScrollingDisabled(true, false);
}
public void showInfo(Schematic schematic){
info.show(schematic);
}
public void showImport(){
FloatingDialog dialog = new FloatingDialog("$editor.export");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
t.row();
t.addImageTextButton("$schematic.copy.import", Icon.copySmall, style, () -> {
dialog.hide();
try{
Schematic s = schematics.readBase64(Core.app.getClipboardText());
schematics.add(s);
setup();
ui.showInfoFade("$schematic.saved");
showInfo(s);
}catch(Exception e){
ui.showException(e);
}
}).marginLeft(12f).disabled(b -> Core.app.getClipboardText() == null || !Core.app.getClipboardText().startsWith(schematicBaseStart));
t.row();
t.addImageTextButton("$schematic.importfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(true, schematicExtension, file -> {
dialog.hide();
try{
Schematic s = Schematics.read(file);
schematics.add(s);
setup();
showInfo(s);
}catch(Exception e){
ui.showException(e);
}
})).marginLeft(12f);
t.row();
if(steam){
t.addImageTextButton("$schematic.browseworkshop", Icon.wikiSmall, style, () -> {
dialog.hide();
platform.openWorkshop();
}).marginLeft(12f);
}
});
});
dialog.addCloseButton();
dialog.show();
}
public void showExport(Schematic s){
FloatingDialog dialog = new FloatingDialog("$editor.export");
dialog.cont.pane(p -> {
p.margin(10f);
p.table(Tex.button, t -> {
TextButtonStyle style = Styles.cleart;
t.defaults().size(280f, 60f).left();
if(steam && !s.hasSteamID()){
t.addImageTextButton("$schematic.shareworkshop", Icon.wikiSmall, style,
() -> platform.publish(s)).marginLeft(12f);
t.row();
}
t.addImageTextButton("$schematic.copy", Icon.copySmall, style, () -> {
dialog.hide();
ui.showInfoFade("$copied");
Core.app.setClipboardText(schematics.writeBase64(s));
}).marginLeft(12f);
t.row();
t.addImageTextButton("$schematic.exportfile", Icon.saveMapSmall, style, () -> platform.showFileChooser(false, schematicExtension, file -> {
dialog.hide();
try{
Schematics.write(s, file);
}catch(Exception e){
ui.showException(e);
}
})).marginLeft(12f);
});
});
dialog.addCloseButton();
dialog.show();
}
public static class SchematicImage extends Image{
public float scaling = 16f;
public float thickness = 4f;
public Color borderColor = Pal.gray;
public SchematicImage(Schematic s){
super(schematics.getPreview(s, PreviewRes.high));
setScaling(Scaling.fit);
}
@Override
public void draw(){
boolean checked = getParent().getParent() instanceof Button
&& ((Button)getParent().getParent()).isOver();
Texture background = Core.assets.get("sprites/schematic-background.png", Texture.class);
TextureRegion region = Draw.wrap(background);
float xr = width / scaling;
float yr = height / scaling;
region.setU2(xr);
region.setV2(yr);
Draw.color();
Draw.alpha(parentAlpha);
Draw.rect(region, x + width/2f, y + height/2f, width, height);
super.draw();
Draw.color(checked ? Pal.accent : borderColor);
Draw.alpha(parentAlpha);
Lines.stroke(Scl.scl(thickness));
Lines.rect(x, y, width, height);
Draw.reset();
}
}
public static class SchematicInfoDialog extends FloatingDialog{
SchematicInfoDialog(){
super("");
setFillParent(true);
addCloseButton();
}
public void show(Schematic schem){
cont.clear();
title.setText("[[" + Core.bundle.get("schematic") + "] " +schem.name());
cont.add(Core.bundle.format("schematic.info", schem.width, schem.height, schem.tiles.size)).color(Color.lightGray);
cont.row();
cont.add(new SchematicImage(schem)).maxSize(800f);
cont.row();
Array<ItemStack> arr = schem.requirements();
cont.table(r -> {
int i = 0;
for(ItemStack s : arr){
r.addImage(s.item.icon(Cicon.small)).left();
r.add(s.amount + "").padLeft(2).left().color(Color.lightGray).padRight(4);
if(++i % 4 == 0){
r.row();
}
}
});
show();
}
}
}

View File

@@ -42,6 +42,10 @@ public class TechTreeDialog extends FloatingDialog{
margin(0f).marginBottom(8);
cont.stack(view = new View(), items = new ItemsDisplay()).grow();
Events.on(ContentReloadEvent.class, e -> {
root = new TechTreeNode(TechTree.root, null);
});
shown(() -> {
checkNodes(root);
treeLayout();
@@ -105,8 +109,18 @@ public class TechTreeDialog extends FloatingDialog{
RadialTreeLayout layout = new RadialTreeLayout();
LayoutNode node = new LayoutNode(root, null);
layout.layout(node);
//bounds.y += nodeSize*1.5f;
float minx = 0f, miny = 0f, maxx = 0f, maxy = 0f;
copyInfo(node);
for(TechTreeNode n : nodes){
if(!n.visible) continue;
minx = Math.min(n.x - n.width/2f, minx);
maxx = Math.max(n.x + n.width/2f, maxx);
miny = Math.min(n.y - n.height/2f, miny);
maxy = Math.max(n.y + n.height/2f, maxy);
}
bounds = new Rectangle(minx, miny, maxx - minx, maxy - miny);
bounds.y += nodeSize*1.5f;
}
void copyInfo(LayoutNode node){
@@ -262,7 +276,7 @@ public class TechTreeDialog extends FloatingDialog{
float rx = bounds.x + panX + ox, ry = panY + oy + bounds.y;
float rw = bounds.width, rh = bounds.height;
rx = Mathf.clamp(rx, -rw + pad, Core.graphics.getWidth() - pad);
ry = Mathf.clamp(ry, pad, Core.graphics.getHeight() - rh - pad);
ry = Mathf.clamp(ry, -rh + pad, Core.graphics.getHeight() - pad);
panX = rx - bounds.x - ox;
panY = ry - bounds.y - oy;
}

View File

@@ -250,8 +250,9 @@ public class HudFragment extends Fragment{
});
parent.fill(t -> {
t.visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial);
//minimap
t.add(new Minimap().visible(() -> Core.settings.getBool("minimap") && !state.rules.tutorial));
t.add(new Minimap());
t.row();
//position
t.label(() -> world.toTile(player.x) + "," + world.toTile(player.y)).style(Styles.outlineLabel)

View File

@@ -22,7 +22,6 @@ public class LoadingFragment extends Fragment{
t.visible(false);
t.touchable(Touchable.enabled);
t.add().height(133f).row();
t.addImage().growX().height(3f).pad(4f).growX().get().setColor(Pal.accent);
t.row();
t.add("$loading").name("namelabel").pad(10f);

View File

@@ -87,6 +87,8 @@ public class Block extends BlockStorage{
public boolean configurable;
/** Whether this block consumes touchDown events when tapped. */
public boolean consumesTap;
/** Whether the config is positional and needs to be shifted. */
public boolean posConfig;
/**
* The color of this block when displayed on the minimap or map preview.
* Do not set manually! This is overriden when loading for most blocks.
@@ -116,7 +118,7 @@ public class Block extends BlockStorage{
public float idleSoundVolume = 0.5f;
/** Cost of constructing this block. */
public ItemStack[] requirements = new ItemStack[]{};
public ItemStack[] requirements = {};
/** Category in place menu. */
public Category category = Category.distribution;
/** Cost of building this block; do not modify directly! */
@@ -676,8 +678,32 @@ public class Block extends BlockStorage{
public void drawRequestRegion(BuildRequest req, Eachable<BuildRequest> list){
TextureRegion reg = icon(Cicon.full);
Draw.rect(icon(Cicon.full), req.drawx(), req.drawy(),
reg.getWidth() * req.animScale * Draw.scl, reg.getHeight() * req.animScale * Draw.scl,
!rotate ? 0 : req.rotation * 90);
reg.getWidth() * req.animScale * Draw.scl,
reg.getHeight() * req.animScale * Draw.scl,
!rotate ? 0 : req.rotation * 90);
if(req.hasConfig){
drawRequestConfig(req, list);
}
}
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
}
public void drawRequestConfigCenter(BuildRequest req, Content content, String region){
Color color = content instanceof Item ? ((Item)content).color : content instanceof Liquid ? ((Liquid)content).color : null;
if(color == null) return;
Draw.color(color);
Draw.scl *= req.animScale;
Draw.rect(region, req.drawx(), req.drawy());
Draw.scl /= req.animScale;
Draw.color();
}
public void drawRequestConfigTop(BuildRequest req, Eachable<BuildRequest> list){
}
@Override

View File

@@ -56,7 +56,7 @@ public class BuildBlock extends Block{
}
@Remote(called = Loc.server)
public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team){
public static void onConstructFinish(Tile tile, Block block, int builderID, byte rotation, Team team, boolean skipConfig){
if(tile == null) return;
float healthf = tile.entity == null ? 1f : tile.entity.healthf();
world.setBlock(tile, block, team, rotation);
@@ -70,7 +70,9 @@ public class BuildBlock extends Block{
if(!headless && builderID == player.id){
//this is run delayed, since if this is called on the server, all clients need to recieve the onBuildFinish()
//event first before they can recieve the placed() event modification results
Core.app.post(() -> tile.block().playerPlaced(tile));
if(!skipConfig){
Core.app.post(() -> tile.block().playerPlaced(tile));
}
}
Core.app.post(() -> Events.fire(new BlockBuildEndEvent(tile, playerGroup.getByID(builderID), team, false)));
Sounds.place.at(tile, Mathf.random(0.7f, 1.4f));
@@ -185,7 +187,7 @@ public class BuildBlock extends Block{
private float[] accumulator;
private float[] totalAccumulator;
public boolean construct(Unit builder, @Nullable TileEntity core, float amount){
public boolean construct(Unit builder, @Nullable TileEntity core, float amount, boolean configured){
if(cblock == null){
kill();
return false;
@@ -208,7 +210,7 @@ public class BuildBlock extends Block{
}
if(progress >= 1f || state.rules.infiniteResources){
Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam());
Call.onConstructFinish(tile, cblock, builderID, tile.rotation(), builder.getTeam(), configured);
return true;
}
return false;

View File

@@ -52,11 +52,13 @@ public class Conduit extends LiquidBlock implements Autotiler{
Draw.colorl(0.34f);
Draw.alpha(0.5f);
Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90);
Draw.rect(botRegions[bits[0]], req.drawx(), req.drawy(),
botRegions[bits[0]].getWidth() * Draw.scl * req.animScale, botRegions[bits[0]].getHeight() * Draw.scl * req.animScale,
req.rotation * 90);
Draw.color();
Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), req.rotation * 90);
Draw.rect(topRegions[bits[0]], req.drawx(), req.drawy(), topRegions[bits[0]].getWidth() * Draw.scl * req.animScale, topRegions[bits[0]].getHeight() * Draw.scl * req.animScale, req.rotation * 90);
}
@Override

View File

@@ -107,7 +107,7 @@ public class Conveyor extends Block implements Autotiler{
if(bits == null) return;
TextureRegion region = regions[bits[0]][0];
Draw.rect(region, req.drawx(), req.drawy(), region.getWidth() * bits[1] * Draw.scl, region.getHeight() * bits[2] * Draw.scl, req.rotation * 90);
Draw.rect(region, req.drawx(), req.drawy(), region.getWidth() * bits[1] * Draw.scl * req.animScale, region.getHeight() * bits[2] * Draw.scl * req.animScale, req.rotation * 90);
}
@Override

View File

@@ -3,11 +3,13 @@ package io.anuke.mindustry.world.blocks.distribution;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.collection.IntSet.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.math.geom.*;
import io.anuke.arc.util.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.graphics.*;
import io.anuke.mindustry.type.*;
@@ -23,6 +25,7 @@ public class ItemBridge extends Block{
protected int range;
protected float transportTime = 2f;
protected TextureRegion endRegion, bridgeRegion, arrowRegion;
protected BuildRequest otherReq;
private static int lastPlaced = Pos.invalid;
@@ -34,6 +37,7 @@ public class ItemBridge extends Block{
layer = Layer.power;
expanded = true;
itemCapacity = 10;
posConfig = true;
configurable = true;
hasItems = true;
unloadable = false;
@@ -65,6 +69,27 @@ public class ItemBridge extends Block{
arrowRegion = Core.atlas.find(name + "-arrow");
}
@Override
public void drawRequestConfigTop(BuildRequest req, Eachable<BuildRequest> list){
otherReq = null;
list.each(other -> {
if(other.block == this && req.config == Pos.get(other.x, other.y)){
otherReq = other;
}
});
if(otherReq == null) return;
Lines.stroke(8f);
Lines.line(bridgeRegion,
req.drawx(),
req.drawy(),
otherReq.drawx(),
otherReq.drawy(), CapStyle.none, -tilesize / 2f);
Draw.rect(arrowRegion, (req.drawx() + otherReq.drawx()) / 2f, (req.drawy() + otherReq.drawy()) / 2f,
Angles.angle(req.drawx(), req.drawy(), otherReq.drawx(), otherReq.drawy()));
}
@Override
public void playerPlaced(Tile tile){
Tile link = findLink(tile.x, tile.y);

View File

@@ -36,6 +36,7 @@ public class MassDriver extends Block{
super(name);
update = true;
solid = true;
posConfig = true;
configurable = true;
hasItems = true;
layer = Layer.turret;

View File

@@ -1,10 +1,12 @@
package io.anuke.mindustry.world.blocks.distribution;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.math.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -46,6 +48,11 @@ public class Sorter extends Block{
tile.<SorterEntity>entity().sortItem = content.item(value);
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "center");
}
@Override
public void draw(Tile tile){
super.draw(tile);

View File

@@ -33,53 +33,6 @@ public class PowerNode extends PowerBlock{
consumesPower = false;
outputsPower = false;
}
/*
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void linkPowerNodes(Player player, Tile tile, Tile other){
if(tile.entity == null || other == null || tile.entity.power == null || !((PowerNode)tile.block()).linkValid(tile, other)
|| tile.entity.power.links.size >= ((PowerNode)tile.block()).maxNodes) return;
if(!Units.canInteract(player, tile)) return;
TileEntity entity = tile.entity();
if(!entity.power.links.contains(other.pos())){
entity.power.links.add(other.pos());
}
if(other.getTeamID() == tile.getTeamID()){
if(!other.entity.power.links.contains(tile.pos())){
other.entity.power.links.add(tile.pos());
}
}
entity.power.graph.add(other.entity.power.graph);
}
@Remote(targets = Loc.both, called = Loc.server, forward = true)
public static void unlinkPowerNodes(Player player, Tile tile, Tile other){
if(tile.entity.power == null || other.entity == null || other.entity.power == null) return;
if(!Units.canInteract(player, tile)) return;
TileEntity entity = tile.entity();
entity.power.links.removeValue(other.pos());
other.entity.power.links.removeValue(tile.pos());
PowerGraph newgraph = new PowerGraph();
//reflow from this point, covering all tiles on this side
newgraph.reflow(tile);
if(other.entity.power.graph != newgraph){
//create new graph for other end
PowerGraph og = new PowerGraph();
//reflow from other end
og.reflow(other);
}
}
*/
@Override
public void configured(Tile tile, Player player, int value){

View File

@@ -34,6 +34,11 @@ public class Fracker extends SolidPump{
topRegion = Core.atlas.find(name + "-top");
}
@Override
public boolean outputsItems(){
return false;
}
@Override
public void drawCracks(Tile tile){}

View File

@@ -1,8 +1,10 @@
package io.anuke.mindustry.world.blocks.sandbox;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -43,6 +45,11 @@ public class ItemSource extends Block{
bars.remove("items");
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "center");
}
@Override
public boolean outputsItems(){
return true;

View File

@@ -2,11 +2,13 @@ package io.anuke.mindustry.world.blocks.sandbox;
import io.anuke.arc.*;
import io.anuke.arc.collection.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.style.*;
import io.anuke.arc.scene.ui.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.arc.util.ArcAnnotate.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.game.*;
import io.anuke.mindustry.gen.*;
@@ -57,6 +59,11 @@ public class LiquidSource extends Block{
}
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.liquid(req.config), "center");
}
@Override
public void draw(Tile tile){
super.draw(tile);

View File

@@ -1,9 +1,11 @@
package io.anuke.mindustry.world.blocks.storage;
import io.anuke.arc.*;
import io.anuke.arc.function.*;
import io.anuke.arc.graphics.*;
import io.anuke.arc.graphics.g2d.*;
import io.anuke.arc.scene.ui.layout.*;
import io.anuke.mindustry.entities.traits.BuilderTrait.*;
import io.anuke.mindustry.entities.type.*;
import io.anuke.mindustry.type.*;
import io.anuke.mindustry.world.*;
@@ -28,6 +30,11 @@ public class Unloader extends Block{
configurable = true;
}
@Override
public void drawRequestConfig(BuildRequest req, Eachable<BuildRequest> list){
drawRequestConfigCenter(req, content.item(req.config), "unloader-center");
}
@Override
public boolean canDump(Tile tile, Tile to, Item item){
return !(to.block() instanceof StorageBlock);
@@ -109,7 +116,7 @@ public class Unloader extends Block{
UnloaderEntity entity = tile.entity();
Draw.color(entity.sortItem == null ? Color.clear : entity.sortItem.color);
Fill.square(tile.worldx(), tile.worldy(), 1f);
Draw.rect("unloader-center", tile.worldx(), tile.worldy());
Draw.color();
}