diff --git a/core/assets/scripts/base.js b/core/assets/scripts/base.js index d689453537..124acce5b7 100755 --- a/core/assets/scripts/base.js +++ b/core/assets/scripts/base.js @@ -1,5 +1,5 @@ -const print = function(obj){ - java.lang.System.out.println(obj ? String(obj) : "null") +const log = function(context, obj){ + Vars.mods.getScripts().log(context, obj ? String(obj) : "null") } const extendContent = function(classType, name, params){ diff --git a/core/assets/scripts/global.js b/core/assets/scripts/global.js index 8cc613f050..d3c53473ee 100755 --- a/core/assets/scripts/global.js +++ b/core/assets/scripts/global.js @@ -1,7 +1,7 @@ //Generated class. Do not modify. -const print = function(obj){ - java.lang.System.out.println(obj ? String(obj) : "null") +const log = function(context, obj){ + Vars.mods.getScripts().log(context, obj ? String(obj) : "null") } const extendContent = function(classType, name, params){ diff --git a/core/assets/scripts/wrapper.js b/core/assets/scripts/wrapper.js index 121d306d71..0c7a8aba4d 100755 --- a/core/assets/scripts/wrapper.js +++ b/core/assets/scripts/wrapper.js @@ -1,2 +1,10 @@ modName = "$MOD_NAME$" + +!function(){ + +const scriptName = "$SCRIPT_NAME$" +const print = text => log(scriptName, text); $CODE$ + +}(); + diff --git a/core/src/io/anuke/mindustry/ClientLauncher.java b/core/src/io/anuke/mindustry/ClientLauncher.java index 96190b6701..1c8fd4048f 100644 --- a/core/src/io/anuke/mindustry/ClientLauncher.java +++ b/core/src/io/anuke/mindustry/ClientLauncher.java @@ -69,14 +69,12 @@ public abstract class ClientLauncher extends ApplicationCore implements Platform Musics.load(); Sounds.load(); - assets.loadRun("scriptinit", Scripts.class, () -> {}, () -> { - content.createContent(false); - mods.loadScripts(); - }); - assets.loadRun("contentcreate", Content.class, () -> { - content.createContent(); + content.createBaseContent(); content.loadColors(); + }, () -> { + mods.loadScripts(); + content.createModContent(); }); add(logic = new Logic()); diff --git a/core/src/io/anuke/mindustry/core/ContentLoader.java b/core/src/io/anuke/mindustry/core/ContentLoader.java index cb5ef8d49c..b3672d5c00 100644 --- a/core/src/io/anuke/mindustry/core/ContentLoader.java +++ b/core/src/io/anuke/mindustry/core/ContentLoader.java @@ -20,7 +20,6 @@ import static io.anuke.mindustry.Vars.mods; */ @SuppressWarnings("unchecked") public class ContentLoader{ - private boolean loaded = false; private ObjectMap[] contentNameMap = new ObjectMap[ContentType.values().length]; private Array[] contentMap = new Array[ContentType.values().length]; private MappableContent[][] temporaryMapper; @@ -43,59 +42,47 @@ public class ContentLoader{ new LegacyColorMapper(), }; + public ContentLoader(){ + for(ContentType type : ContentType.values()){ + contentMap[type.ordinal()] = new Array<>(); + contentNameMap[type.ordinal()] = new ObjectMap<>(); + } + } + /** Clears all initialized content.*/ public void clear(){ contentNameMap = new ObjectMap[ContentType.values().length]; contentMap = new Array[ContentType.values().length]; initialization = new ObjectSet<>(); - loaded = false; } - /** Creates all content types. */ - public void createContent(){ - createContent(true); + + /** Creates all base types. */ + public void createBaseContent(){ + for(ContentList list : content){ + list.load(); + } } - /** Creates all content types. */ - public void createContent(boolean load){ - if(loaded){ - Log.info("Content already loaded, skipping."); - return; - } - - if(contentMap[0] == null){ - for(ContentType type : ContentType.values()){ - contentMap[type.ordinal()] = new Array<>(); - contentNameMap[type.ordinal()] = new ObjectMap<>(); - } - } - - if(load){ - - for(ContentList list : content){ - list.load(); - } - - if(mods != null){ - mods.loadContent(); - } - - //check up ID mapping, make sure it's linear - for(Array arr : contentMap){ - for(int i = 0; i < arr.size; i++){ - int id = arr.get(i).id; - if(id != i){ - throw new IllegalArgumentException("Out-of-order IDs for content '" + arr.get(i) + "' (expected " + i + " but got " + id + ")"); - } - } - } - - loaded = true; + /** Creates mod content, if applicable. */ + public void createModContent(){ + if(mods != null){ + mods.loadContent(); } } /** Logs content statistics.*/ public void logContent(){ + //check up ID mapping, make sure it's linear (debug only) + for(Array arr : contentMap){ + for(int i = 0; i < arr.size; i++){ + int id = arr.get(i).id; + if(id != i){ + throw new IllegalArgumentException("Out-of-order IDs for content '" + arr.get(i) + "' (expected " + i + " but got " + id + ")"); + } + } + } + Log.info("--- CONTENT INFO ---"); for(int k = 0; k < contentMap.length; k++){ Log.info("[{0}]: loaded {1}", ContentType.values()[k].name(), contentMap[k].size); diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index b38cc63297..c7a04955ca 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -451,12 +451,12 @@ public class Control implements ApplicationListener, Loadable{ platform.updateRPC(); } - if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ + if(Core.input.keyTap(Binding.pause) && !scene.hasDialog() && !scene.hasKeyboard() && !ui.restart.isShown() && (state.is(State.paused) || state.is(State.playing))){ state.set(state.is(State.playing) ? State.paused : State.playing); } if(Core.input.keyTap(Binding.menu) && !ui.restart.isShown()){ - if(ui.chatfrag.chatOpen()){ + if(ui.chatfrag.shown()){ ui.chatfrag.hide(); }else if(!ui.paused.isShown() && !scene.hasDialog()){ ui.paused.show(); @@ -464,7 +464,7 @@ public class Control implements ApplicationListener, Loadable{ } } - if(!mobile && Core.input.keyTap(Binding.screenshot) && !(scene.getKeyboardFocus() instanceof TextField) && !ui.chatfrag.chatOpen()){ + if(!mobile && Core.input.keyTap(Binding.screenshot) && !(scene.getKeyboardFocus() instanceof TextField) && !scene.hasKeyboard()){ renderer.takeMapScreenshot(); } diff --git a/core/src/io/anuke/mindustry/core/NetClient.java b/core/src/io/anuke/mindustry/core/NetClient.java index a3b85facb0..9b89a25e5c 100644 --- a/core/src/io/anuke/mindustry/core/NetClient.java +++ b/core/src/io/anuke/mindustry/core/NetClient.java @@ -471,7 +471,7 @@ public class NetClient implements ApplicationListener{ player.pointerX, player.pointerY, player.rotation, player.baseRotation, player.velocity().x, player.velocity().y, player.getMineTile(), - player.isBoosting, player.isShooting, ui.chatfrag.chatOpen(), player.isBuilding, + player.isBoosting, player.isShooting, ui.chatfrag.shown(), player.isBuilding, requests, Core.camera.position.x, Core.camera.position.y, Core.camera.width * viewScale, Core.camera.height * viewScale); diff --git a/core/src/io/anuke/mindustry/core/UI.java b/core/src/io/anuke/mindustry/core/UI.java index 142481b96d..3eb530ac7f 100644 --- a/core/src/io/anuke/mindustry/core/UI.java +++ b/core/src/io/anuke/mindustry/core/UI.java @@ -42,6 +42,7 @@ public class UI implements ApplicationListener, Loadable{ public MenuFragment menufrag; public HudFragment hudfrag; public ChatFragment chatfrag; + public ScriptConsoleFragment scriptfrag; public PlayerListFragment listfrag; public LoadingFragment loadfrag; @@ -211,6 +212,7 @@ public class UI implements ApplicationListener, Loadable{ chatfrag = new ChatFragment(); listfrag = new PlayerListFragment(); loadfrag = new LoadingFragment(); + scriptfrag = new ScriptConsoleFragment(); picker = new ColorPicker(); editor = new MapEditorDialog(); @@ -253,6 +255,7 @@ public class UI implements ApplicationListener, Loadable{ menufrag.build(menuGroup); chatfrag.container().build(hudGroup); listfrag.build(hudGroup); + scriptfrag.container().build(hudGroup); loadfrag.build(group); new FadeInFragment().build(group); } diff --git a/core/src/io/anuke/mindustry/entities/type/Player.java b/core/src/io/anuke/mindustry/entities/type/Player.java index 4e83cd1091..73c0fc4dc0 100644 --- a/core/src/io/anuke/mindustry/entities/type/Player.java +++ b/core/src/io/anuke/mindustry/entities/type/Player.java @@ -556,7 +556,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ updateKeyboard(); } - isTyping = ui.chatfrag.chatOpen(); + isTyping = ui.chatfrag.shown(); updateMechanics(); @@ -604,7 +604,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ movement.limit(speed).scl(Time.delta()); - if(!ui.chatfrag.chatOpen()){ + if(!Core.scene.hasKeyboard()){ velocity.add(movement.x, movement.y); }else{ isShooting = false; @@ -613,7 +613,7 @@ public class Player extends Unit implements BuilderMinerTrait, ShooterTrait{ updateVelocityStatus(); moved = dst(prex, prey) > 0.001f; - if(!ui.chatfrag.chatOpen()){ + if(!Core.scene.hasKeyboard()){ float baseLerp = mech.getRotationAlpha(this); if(!isShooting() || !mech.turnCursor){ if(!movement.isZero()){ diff --git a/core/src/io/anuke/mindustry/input/Binding.java b/core/src/io/anuke/mindustry/input/Binding.java index 45383cb6ef..25d41289e3 100644 --- a/core/src/io/anuke/mindustry/input/Binding.java +++ b/core/src/io/anuke/mindustry/input/Binding.java @@ -54,6 +54,7 @@ public enum Binding implements KeyBind{ chat_history_prev(KeyCode.UP), chat_history_next(KeyCode.DOWN), chat_scroll(new Axis(KeyCode.SCROLL)), + console(KeyCode.BACKTICK), ; private final KeybindValue defaultValue; diff --git a/core/src/io/anuke/mindustry/input/DesktopInput.java b/core/src/io/anuke/mindustry/input/DesktopInput.java index 7b015600ce..e8cc6793d8 100644 --- a/core/src/io/anuke/mindustry/input/DesktopInput.java +++ b/core/src/io/anuke/mindustry/input/DesktopInput.java @@ -122,7 +122,7 @@ 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) && !ui.chatfrag.chatOpen()){ + if(Core.input.keyDown(Binding.schematic_select) && !Core.scene.hasKeyboard()){ drawSelection(schemX, schemY, cursorX, cursorY, Vars.maxSchematicSize); } @@ -139,7 +139,7 @@ public class DesktopInput extends InputHandler{ player.isShooting = false; } - if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && (scene.getKeyboardFocus() == ui.minimap || !scene.hasDialog()) && !ui.chatfrag.chatOpen() && !(scene.getKeyboardFocus() instanceof TextField)){ + if(!state.is(State.menu) && Core.input.keyTap(Binding.minimap) && (scene.getKeyboardFocus() == ui.minimap || !scene.hasDialog()) && !Core.scene.hasKeyboard() && !(scene.getKeyboardFocus() instanceof TextField)){ if(!ui.minimap.isShown()){ ui.minimap.show(); }else{ @@ -293,12 +293,12 @@ public class DesktopInput extends InputHandler{ player.clearBuilding(); } - if(Core.input.keyTap(Binding.schematic_select) && !ui.chatfrag.chatOpen()){ + if(Core.input.keyTap(Binding.schematic_select) && !Core.scene.hasKeyboard()){ schemX = rawCursorX; schemY = rawCursorY; } - if(Core.input.keyTap(Binding.schematic_menu) && !ui.chatfrag.chatOpen()){ + if(Core.input.keyTap(Binding.schematic_menu) && !Core.scene.hasKeyboard()){ if(ui.schematics.isShown()){ ui.schematics.hide(); }else{ @@ -311,7 +311,7 @@ public class DesktopInput extends InputHandler{ selectRequests.clear(); } - if(Core.input.keyRelease(Binding.schematic_select) && !ui.chatfrag.chatOpen()){ + if(Core.input.keyRelease(Binding.schematic_select) && !Core.scene.hasKeyboard()){ lastSchematic = schematics.create(schemX, schemY, rawCursorX, rawCursorY); useSchematic(lastSchematic); if(selectRequests.isEmpty()){ @@ -371,10 +371,10 @@ public class DesktopInput extends InputHandler{ }else if(selected != null){ //only begin shooting if there's no cursor event if(!tileTapped(selected) && !tryTapPlayer(Core.input.mouseWorld().x, Core.input.mouseWorld().y) && (player.buildQueue().size == 0 || !player.isBuilding) && !droppingItem && - !tryBeginMine(selected) && player.getMineTile() == null && !ui.chatfrag.chatOpen()){ + !tryBeginMine(selected) && player.getMineTile() == null && !Core.scene.hasKeyboard()){ player.isShooting = true; } - }else if(!ui.chatfrag.chatOpen()){ //if it's out of bounds, shooting is just fine + }else if(!Core.scene.hasKeyboard()){ //if it's out of bounds, shooting is just fine player.isShooting = true; } }else if(Core.input.keyTap(Binding.deselect) && block != null){ diff --git a/core/src/io/anuke/mindustry/mod/Mods.java b/core/src/io/anuke/mindustry/mod/Mods.java index 23599e0732..1f7120ce06 100644 --- a/core/src/io/anuke/mindustry/mod/Mods.java +++ b/core/src/io/anuke/mindustry/mod/Mods.java @@ -198,6 +198,16 @@ public class Mods implements Loadable{ requiresReload = true; } + public Scripts getScripts(){ + if(scripts == null) scripts = platform.createScripts(); + return scripts; + } + + /** @return whether the scripting engine has been initialized. */ + public boolean hasScripts(){ + return scripts != null; + } + public boolean requiresReload(){ return requiresReload; } @@ -353,9 +363,10 @@ public class Mods implements Loadable{ scripts = null; } content.clear(); - content.createContent(false); + content.createBaseContent(); + content.loadColors(); loadScripts(); - content.createContent(); + content.createModContent(); loadAsync(); loadSync(); content.init(); diff --git a/core/src/io/anuke/mindustry/mod/Scripts.java b/core/src/io/anuke/mindustry/mod/Scripts.java index 6bde96c2a5..e1edbfa078 100644 --- a/core/src/io/anuke/mindustry/mod/Scripts.java +++ b/core/src/io/anuke/mindustry/mod/Scripts.java @@ -1,24 +1,28 @@ package io.anuke.mindustry.mod; import io.anuke.arc.*; +import io.anuke.arc.collection.*; import io.anuke.arc.files.*; import io.anuke.arc.util.*; import io.anuke.mindustry.*; import io.anuke.mindustry.mod.Mods.*; import org.mozilla.javascript.*; +import static io.anuke.mindustry.Vars.*; + public class Scripts implements Disposable{ private final Context context; private final String wrapper; private Scriptable scope; + private Array logBuffer = new Array<>(); public Scripts(){ Time.mark(); context = Vars.platform.getScriptContext(); - context.setClassShutter(type -> ClassAccess.allowedClassNames.contains(type) || type.startsWith("adapter") || type.contains("PrintStream")); + context.setClassShutter(type -> (ClassAccess.allowedClassNames.contains(type) || type.startsWith("adapter") || type.contains("PrintStream") || type.startsWith("io.anuke.mindustry")) && !type.equals("io.anuke.mindustry.mod.ClassAccess")); - scope = new ImporterTopLevel(context);//context.initStandardObjects(); + scope = new ImporterTopLevel(context); wrapper = Core.files.internal("scripts/wrapper.js").readString(); run(Core.files.internal("scripts/global.js").readString(), "global.js"); @@ -27,18 +31,51 @@ public class Scripts implements Disposable{ public String runConsole(String text){ try{ - return String.valueOf(context.evaluateString(scope, text, "console.js", 1, null)); + Object o = context.evaluateString(scope, text, "console.js", 1, null); + if(o instanceof NativeJavaObject){ + o = ((NativeJavaObject)o).unwrap(); + } + if(o instanceof Undefined){ + o = "undefined"; + } + return String.valueOf(o); }catch(Throwable t){ - return t.getClass().getSimpleName() + (t.getMessage() == null ? "" : ": " + t.getMessage()); + return getError(t); } } + private String getError(Throwable t){ + if(t instanceof EcmaError && t.getCause() != null){ + t = t.getCause(); + } + return t.getClass().getSimpleName() + (t.getMessage() == null ? "" : ": " + t.getMessage()); + } + + public void log(String source, String message){ + Log.info("[{0}]: {1}", source, message); + logBuffer.add("[accent][" + source + "]:[] " + message); + if(!headless & ui.scriptfrag != null){ + onLoad(); + } + } + + public void onLoad(){ + if(!headless){ + logBuffer.each(ui.scriptfrag::addMessage); + } + logBuffer.clear(); + } + public void run(LoadedMod mod, FileHandle file){ - run(wrapper.replace("$SCRIPT_NAME$", mod.name + "_" + file.nameWithoutExtension().replace("-", "_").replace(" ", "_")).replace("$CODE$", file.readString()).replace("$MOD_NAME$", mod.name), file.name()); + run(wrapper.replace("$SCRIPT_NAME$", mod.name + "/" + file.nameWithoutExtension()).replace("$CODE$", file.readString()).replace("$MOD_NAME$", mod.name), file.name()); } private void run(String script, String file){ - context.evaluateString(scope, script, file, 1, null); + try{ + context.evaluateString(scope, script, file, 1, null); + }catch(Throwable t){ + log(file, getError(t)); + } } @Override diff --git a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java index 2801d10149..c7c168bc84 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/ChatFragment.java @@ -24,7 +24,7 @@ public class ChatFragment extends Table{ private final static int messagesShown = 10; private Array messages = new Array<>(); private float fadetime; - private boolean chatOpen = false; + private boolean shown = false; private TextField chatfield; private Label fieldlabel = new Label(">"); private BitmapFont font; @@ -52,7 +52,7 @@ public class ChatFragment extends Table{ if(!net.active() && messages.size > 0){ clearMessages(); - if(chatOpen){ + if(shown){ hide(); } } @@ -66,7 +66,7 @@ public class ChatFragment extends Table{ toggle(); } - if(chatOpen){ + if(shown){ if(input.keyTap(Binding.chat_history_prev) && historyPos < history.size - 1){ if(historyPos == 0) history.set(0, chatfield.getText()); historyPos++; @@ -123,7 +123,7 @@ public class ChatFragment extends Table{ Draw.color(shadowColor); - if(chatOpen){ + if(shown){ Fill.crect(offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight() - 1); } @@ -131,14 +131,14 @@ public class ChatFragment extends Table{ float spacing = chatspace; - chatfield.visible(chatOpen); - fieldlabel.visible(chatOpen); + chatfield.visible(shown); + fieldlabel.visible(shown); Draw.color(shadowColor); Draw.alpha(shadowColor.a * opacity); float theight = offsety + spacing + getMarginBottom(); - for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || chatOpen); i++){ + for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || shown); i++){ layout.setText(font, messages.get(i).formattedMessage, Color.white, textWidth, Align.bottomLeft, true); theight += layout.height + textspacing; @@ -147,7 +147,7 @@ public class ChatFragment extends Table{ font.getCache().clear(); font.getCache().addText(messages.get(i).formattedMessage, fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true); - if(!chatOpen && fadetime - i < 1f && fadetime - i >= 0f){ + if(!shown && fadetime - i < 1f && fadetime - i >= 0f){ font.getCache().setAlphas((fadetime - i) * opacity); Draw.color(0, 0, 0, shadowColor.a * (fadetime - i) * opacity); }else{ @@ -163,7 +163,7 @@ public class ChatFragment extends Table{ Draw.color(); - if(fadetime > 0 && !chatOpen) + if(fadetime > 0 && !shown) fadetime -= Time.delta() / 180f; } @@ -180,9 +180,9 @@ public class ChatFragment extends Table{ public void toggle(){ - if(!chatOpen){ + if(!shown){ scene.setKeyboardFocus(chatfield); - chatOpen = !chatOpen; + shown = !shown; if(mobile){ TextInput input = new TextInput(); input.maxLength = maxTextLength; @@ -199,7 +199,7 @@ public class ChatFragment extends Table{ } }else{ scene.setKeyboardFocus(null); - chatOpen = !chatOpen; + shown = !shown; scrollPos = 0; sendMessage(); } @@ -207,7 +207,7 @@ public class ChatFragment extends Table{ public void hide(){ scene.setKeyboardFocus(null); - chatOpen = false; + shown = false; clearChatInput(); } @@ -222,12 +222,8 @@ public class ChatFragment extends Table{ chatfield.setText(""); } - public boolean chatOpen(){ - return chatOpen; - } - - public int getMessagesSize(){ - return messages.size; + public boolean shown(){ + return shown; } public void addMessage(String message, String sender){ diff --git a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java index 3bbce3b1ec..b2c60deca9 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/HudFragment.java @@ -83,7 +83,7 @@ public class HudFragment extends Fragment{ select.addImageButton(Icon.chatSmall, style,() -> { if(net.active() && mobile){ - if(ui.chatfrag.chatOpen()){ + if(ui.chatfrag.shown()){ ui.chatfrag.hide(); }else{ ui.chatfrag.toggle(); @@ -131,7 +131,7 @@ public class HudFragment extends Fragment{ } cont.update(() -> { - if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.chatOpen() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){ + if(Core.input.keyTap(Binding.toggle_menus) && !ui.chatfrag.shown() && !Core.scene.hasDialog() && !(Core.scene.getKeyboardFocus() instanceof TextField)){ toggleMenus(); } }); diff --git a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java index 95bdd5affa..5d8e5ff0e5 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/PlacementFragment.java @@ -103,7 +103,8 @@ public class PlacementFragment extends Fragment{ } } - if(ui.chatfrag.chatOpen()) return false; + if(ui.chatfrag.shown() || Core.scene.hasKeyboard()) return false; + for(int i = 0; i < blockSelect.length; i++){ if(Core.input.keyTap(blockSelect[i])){ if(i > 9) { //select block directionally diff --git a/core/src/io/anuke/mindustry/ui/fragments/ScriptConsoleFragment.java b/core/src/io/anuke/mindustry/ui/fragments/ScriptConsoleFragment.java index f0e28611db..700c5beb7c 100644 --- a/core/src/io/anuke/mindustry/ui/fragments/ScriptConsoleFragment.java +++ b/core/src/io/anuke/mindustry/ui/fragments/ScriptConsoleFragment.java @@ -1,11 +1,231 @@ package io.anuke.mindustry.ui.fragments; +import io.anuke.arc.*; +import io.anuke.arc.Input.*; +import io.anuke.arc.collection.*; +import io.anuke.arc.graphics.*; +import io.anuke.arc.graphics.g2d.*; +import io.anuke.arc.math.*; import io.anuke.arc.scene.*; +import io.anuke.arc.scene.ui.*; +import io.anuke.arc.scene.ui.Label.*; +import io.anuke.arc.scene.ui.layout.*; +import io.anuke.arc.util.*; +import io.anuke.mindustry.*; +import io.anuke.mindustry.input.*; +import io.anuke.mindustry.ui.*; -public class ScriptConsoleFragment extends Fragment{ +import static io.anuke.arc.Core.*; +import static io.anuke.mindustry.Vars.*; + +public class ScriptConsoleFragment extends Table{ + private final static int messagesShown = 14; + private Array messages = new Array<>(); + private float fadetime; + private boolean open = false, shown; + private TextField chatfield; + private Label fieldlabel = new Label(">"); + private BitmapFont font; + private GlyphLayout layout = new GlyphLayout(); + private float offsetx = Scl.scl(4), offsety = Scl.scl(4), fontoffsetx = Scl.scl(2), chatspace = Scl.scl(50); + private Color shadowColor = new Color(0, 0, 0, 0.4f); + private float textspacing = Scl.scl(10); + private Array history = new Array<>(); + private int historyPos = 0; + private int scrollPos = 0; + private Fragment container = new Fragment(){ + @Override + public void build(Group parent){ + scene.add(ScriptConsoleFragment.this); + } + }; + + public ScriptConsoleFragment(){ + + setFillParent(true); + font = Fonts.def; + + visible(() -> { + if(input.keyTap(Binding.console) && !Vars.net.client() && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){ + shown = !shown; + if(shown && !open){ + toggle(); + } + clearChatInput(); + } + + return shown && !Vars.net.client(); + }); + + update(() -> { + if(input.keyTap(Binding.chat) && (scene.getKeyboardFocus() == chatfield || scene.getKeyboardFocus() == null)){ + toggle(); + } + + if(open){ + if(input.keyTap(Binding.chat_history_prev) && historyPos < history.size - 1){ + if(historyPos == 0) history.set(0, chatfield.getText()); + historyPos++; + updateChat(); + } + if(input.keyTap(Binding.chat_history_next) && historyPos > 0){ + historyPos--; + updateChat(); + } + scrollPos = (int)Mathf.clamp(scrollPos + input.axis(Binding.chat_scroll), 0, Math.max(0, messages.size - messagesShown)); + } + }); + + history.insert(0, ""); + setup(); + + if(mods.hasScripts()){ + app.post(() -> mods.getScripts().onLoad()); + } + } + + public Fragment container(){ + return container; + } + + public void clearMessages(){ + messages.clear(); + history.clear(); + history.insert(0, ""); + } + + private void setup(){ + fieldlabel.setStyle(new LabelStyle(fieldlabel.getStyle())); + fieldlabel.getStyle().font = font; + fieldlabel.setStyle(fieldlabel.getStyle()); + + chatfield = new TextField("", new TextField.TextFieldStyle(scene.getStyle(TextField.TextFieldStyle.class))); + chatfield.setMaxLength(Vars.maxTextLength); + chatfield.getStyle().background = null; + chatfield.getStyle().font = Fonts.chat; + chatfield.getStyle().fontColor = Color.white; + chatfield.setStyle(chatfield.getStyle()); + + bottom().left().marginBottom(offsety).marginLeft(offsetx * 2).add(fieldlabel).padBottom(6f); + + add(chatfield).padBottom(offsety).padLeft(offsetx).growX().padRight(offsetx).height(28); + } @Override - public void build(Group parent){ + public void draw(){ + float opacity = 1f; + float textWidth = graphics.getWidth() - offsetx*2f; + Draw.color(shadowColor); + + if(open){ + Fill.crect(offsetx, chatfield.getY(), chatfield.getWidth() + 15f, chatfield.getHeight() - 1); + } + + super.draw(); + + float spacing = chatspace; + + chatfield.visible(open); + fieldlabel.visible(open); + + Draw.color(shadowColor); + Draw.alpha(shadowColor.a * opacity); + + float theight = offsety + spacing + getMarginBottom(); + for(int i = scrollPos; i < messages.size && i < messagesShown + scrollPos && (i < fadetime || open); i++){ + + layout.setText(font, messages.get(i), Color.white, textWidth, Align.bottomLeft, true); + theight += layout.height + textspacing; + if(i - scrollPos == 0) theight -= textspacing + 1; + + font.getCache().clear(); + font.getCache().addText(messages.get(i), fontoffsetx + offsetx, offsety + theight, textWidth, Align.bottomLeft, true); + + if(!open && fadetime - i < 1f && fadetime - i >= 0f){ + font.getCache().setAlphas((fadetime - i) * opacity); + Draw.color(0, 0, 0, shadowColor.a * (fadetime - i) * opacity); + }else{ + font.getCache().setAlphas(opacity); + } + + Fill.crect(offsetx, theight - layout.height - 2, textWidth + Scl.scl(4f), layout.height + textspacing); + Draw.color(shadowColor); + Draw.alpha(opacity * shadowColor.a); + + font.getCache().draw(); + } + + Draw.color(); + + if(fadetime > 0 && !open) + fadetime -= Time.delta() / 180f; + } + + private void sendMessage(){ + String message = chatfield.getText(); + clearChatInput(); + + if(message.replaceAll(" ", "").isEmpty()) return; + + history.insert(1, message); + + addMessage("[lightgray]> " + message); + addMessage(mods.getScripts().runConsole(message)); + } + + public void toggle(){ + + if(!open){ + scene.setKeyboardFocus(chatfield); + open = !open; + if(mobile){ + TextInput input = new TextInput(); + input.maxLength = maxTextLength; + input.accepted = text -> { + chatfield.setText(text); + sendMessage(); + hide(); + Core.input.setOnscreenKeyboardVisible(false); + }; + input.canceled = this::hide; + Core.input.getTextInput(input); + }else{ + chatfield.fireClick(); + } + }else{ + scene.setKeyboardFocus(null); + open = !open; + scrollPos = 0; + sendMessage(); + } + } + + public void hide(){ + scene.setKeyboardFocus(null); + open = false; + clearChatInput(); + } + + public void updateChat(){ + chatfield.setText(history.get(historyPos)); + chatfield.setCursorPosition(chatfield.getText().length()); + } + + public void clearChatInput(){ + historyPos = 0; + history.set(0, ""); + chatfield.setText(""); + } + + public boolean open(){ + return open; + } + + public void addMessage(String message){ + messages.insert(0, message); + + fadetime += 1f; + fadetime = Math.min(fadetime, messagesShown) + 1f; } } diff --git a/gradle.properties b/gradle.properties index f61b492a7f..5157fccbe2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms256m -Xmx1024m -archash=ef6d1485c36346e008ac6818ec972e7781f9ba8d +archash=6d310772fec1f69efeae2e487cab3bd64728ae05 diff --git a/server/src/io/anuke/mindustry/server/MindustryServer.java b/server/src/io/anuke/mindustry/server/MindustryServer.java index ad83a69eb7..ca6077fd9e 100644 --- a/server/src/io/anuke/mindustry/server/MindustryServer.java +++ b/server/src/io/anuke/mindustry/server/MindustryServer.java @@ -33,7 +33,9 @@ public class MindustryServer implements ApplicationListener{ Vars.loadSettings(); Vars.init(); - content.createContent(); + content.createBaseContent(); + mods.loadScripts(); + content.createModContent(); content.init(); Core.app.addListener(logic = new Logic()); diff --git a/tests/src/test/java/ApplicationTests.java b/tests/src/test/java/ApplicationTests.java index a9ed831fbc..58f51ad473 100644 --- a/tests/src/test/java/ApplicationTests.java +++ b/tests/src/test/java/ApplicationTests.java @@ -47,7 +47,7 @@ public class ApplicationTests{ net = new Net(null); tree = new FileTree(); Vars.init(); - content.createContent(); + content.createBaseContent(); add(logic = new Logic()); add(netServer = new NetServer()); diff --git a/tests/src/test/java/power/PowerTestFixture.java b/tests/src/test/java/power/PowerTestFixture.java index 75f4a56854..e3a2fea469 100644 --- a/tests/src/test/java/power/PowerTestFixture.java +++ b/tests/src/test/java/power/PowerTestFixture.java @@ -33,7 +33,7 @@ public class PowerTestFixture{ } }; - content.createContent(); + content.createBaseContent(); Log.setUseColors(false); Time.setDeltaProvider(() -> 0.5f); } diff --git a/tools/src/io/anuke/mindustry/tools/ImagePacker.java b/tools/src/io/anuke/mindustry/tools/ImagePacker.java index 7e4ff467a4..76346817ab 100644 --- a/tools/src/io/anuke/mindustry/tools/ImagePacker.java +++ b/tools/src/io/anuke/mindustry/tools/ImagePacker.java @@ -24,7 +24,7 @@ public class ImagePacker{ Log.setLogger(new NoopLogHandler()); Vars.content = new ContentLoader(); - Vars.content.createContent(); + Vars.content.createBaseContent(); Log.setLogger(new LogHandler()); Files.walk(Paths.get("../../../assets-raw/sprites_out")).forEach(path -> {