From 806b762333863f8221ad3fe2e38e337b7fff50d5 Mon Sep 17 00:00:00 2001 From: Anuken Date: Mon, 20 Nov 2017 19:02:35 -0500 Subject: [PATCH] Added full block descriptions, improved shield shaders --- core/assets/shaders/shield.fragment | 3 +- core/src/io/anuke/mindustry/Renderer.java | 167 ++++++++++-------- core/src/io/anuke/mindustry/Shaders.java | 7 +- core/src/io/anuke/mindustry/UI.java | 28 ++- core/src/io/anuke/mindustry/Vars.java | 2 + .../mindustry/world/blocks/DefenseBlocks.java | 2 + desktop/mindustry-saves/3.mins | Bin 6776 -> 1539 bytes 7 files changed, 131 insertions(+), 78 deletions(-) diff --git a/core/assets/shaders/shield.fragment b/core/assets/shaders/shield.fragment index 98f0b5adba..e2cdb0dfc1 100644 --- a/core/assets/shaders/shield.fragment +++ b/core/assets/shaders/shield.fragment @@ -8,6 +8,7 @@ uniform sampler2D u_texture; uniform vec4 u_color; uniform vec2 u_texsize; uniform float u_time; +uniform float u_scaling; uniform vec2 u_offset; varying vec4 v_color; @@ -19,7 +20,7 @@ void main() { vec2 coords = (T * u_texsize) + u_offset; - T += vec2(sin(coords.y / 3.0 + u_time / 20.0) / 250.0, sin(coords.x / 3.0 + u_time / 20.0) / 250.0); + T += vec2(sin(coords.y / 3.0 + u_time / 20.0) / 250.0, sin(coords.x / 3.0 + u_time / 20.0) / 250.0) * u_scaling; float si = 1.0 + sin(u_time / 20.0 /*+ (coords.x + coords.y) / 30.0*/) / 8.0; diff --git a/core/src/io/anuke/mindustry/Renderer.java b/core/src/io/anuke/mindustry/Renderer.java index dc9db318ed..304e0e826e 100644 --- a/core/src/io/anuke/mindustry/Renderer.java +++ b/core/src/io/anuke/mindustry/Renderer.java @@ -45,17 +45,17 @@ public class Renderer extends RendererModule{ public Renderer() { Core.cameraScale = baseCameraScale; - + Graphics.addSurface("pixel", Core.cameraScale); } - + @Override public void init(){ pixelate = Settings.getBool("pixelate"); Graphics.addSurface("shadow", Settings.getBool("pixelate") ? Core.cameraScale : 1); Graphics.addSurface("shield", Settings.getBool("pixelate") ? Core.cameraScale : 1); } - + public void setPixelate(boolean pixelate){ this.pixelate = pixelate; } @@ -82,9 +82,9 @@ public class Renderer extends RendererModule{ clearScreen(); }else{ boolean smoothcam = Settings.getBool("smoothcam"); - + if(World.core.block() == ProductionBlocks.core){ - + if(!smoothcam){ setCamera(player.x, player.y); }else{ @@ -93,7 +93,7 @@ public class Renderer extends RendererModule{ }else{ smoothCamera(World.core.worldx(), World.core.worldy(), 0.4f); } - + if(Settings.getBool("pixelate")) limitCamera(4f, player.x, player.y); @@ -111,28 +111,26 @@ public class Renderer extends RendererModule{ } float lastx = camera.position.x, lasty = camera.position.y; - + if(Vars.snapCamera && smoothcam && Settings.getBool("pixelate")){ camera.position.set((int) camera.position.x, (int) camera.position.y, 0); } - + if(Gdx.graphics.getHeight() / Core.cameraScale % 2 == 1){ camera.position.add(0, -0.5f, 0); } - + if(Gdx.graphics.getWidth() / Core.cameraScale % 2 == 1){ camera.position.add(-0.5f, 0, 0); } - + long time = TimeUtils.nanoTime(); drawDefault(); - if(Timers.get("profiled", profileTime)) Profiler.draw = TimeUtils.timeSinceNanos(time); - + if(Timers.get("profiled", profileTime)) + Profiler.draw = TimeUtils.timeSinceNanos(time); + if(Vars.debug && Vars.debugGL && Timers.get("profile", 60)){ - UCore.log("shaders: " + GLProfiler.shaderSwitches, - "calls: " + GLProfiler.drawCalls, - "bindings: " + GLProfiler.textureBindings, - "vertices: " + GLProfiler.vertexCount.average); + UCore.log("shaders: " + GLProfiler.shaderSwitches, "calls: " + GLProfiler.drawCalls, "bindings: " + GLProfiler.textureBindings, "vertices: " + GLProfiler.vertexCount.average); } camera.position.set(lastx - deltax, lasty - deltay, 0); @@ -147,19 +145,21 @@ public class Renderer extends RendererModule{ public void draw(){ Graphics.surface("shield"); Graphics.surface(); - + long time = TimeUtils.nanoTime(); renderTiles(); - if(Timers.get("profilebd", profileTime)) Profiler.blockDraw = TimeUtils.timeSinceNanos(time); - + if(Timers.get("profilebd", profileTime)) + Profiler.blockDraw = TimeUtils.timeSinceNanos(time); + time = TimeUtils.nanoTime(); Entities.draw(); - if(Timers.get("profileed", profileTime)) Profiler.entityDraw = TimeUtils.timeSinceNanos(time); - + if(Timers.get("profileed", profileTime)) + Profiler.entityDraw = TimeUtils.timeSinceNanos(time); + drawShield(); - + renderPixelOverlay(); - + if(Settings.getBool("indicators")){ drawEnemyMarkers(); } @@ -173,20 +173,18 @@ public class Renderer extends RendererModule{ AndroidInput.mousey = Gdx.graphics.getHeight() / 2; camera.position.set(player.x, player.y, 0); } - + void drawEnemyMarkers(){ Draw.color(Color.RED); Draw.alpha(0.6f); for(Entity entity : Entities.all()){ if(entity instanceof Enemy){ - Enemy enemy = (Enemy)entity; - - if(Tmp.r1.setSize(camera.viewportWidth, camera.viewportHeight) - .setCenter(camera.position.x, camera.position.y) - .overlaps(enemy.hitbox.getRect(enemy.x, enemy.y))){ + Enemy enemy = (Enemy) entity; + + if(Tmp.r1.setSize(camera.viewportWidth, camera.viewportHeight).setCenter(camera.position.x, camera.position.y).overlaps(enemy.hitbox.getRect(enemy.x, enemy.y))){ continue; } - + float angle = Angles.angle(camera.position.x, camera.position.y, enemy.x, enemy.y); Angles.translation(angle, Unit.dp.inPixels(20f)); Draw.rect("enemyarrow", camera.position.x + Angles.x(), camera.position.y + Angles.y(), angle); @@ -194,20 +192,20 @@ public class Renderer extends RendererModule{ } Draw.color(); } - + void drawShield(){ Texture texture = Graphics.getSurface("shield").texture(); Shaders.shield.color.set(Color.SKY); - + Tmp.tr2.setRegion(texture); Shaders.shield.region = Tmp.tr2; - + Graphics.end(); Graphics.shader(Shaders.shield); Graphics.setScreen(); - + Core.batch.draw(texture, 0, Gdx.graphics.getHeight(), Gdx.graphics.getWidth(), -Gdx.graphics.getHeight()); - + Graphics.shader(); Graphics.end(); Graphics.beginCam(); @@ -219,9 +217,9 @@ public class Renderer extends RendererModule{ //render the entire map if(floorCache == null || floorCache.length != chunksx || floorCache[0].length != chunksy){ floorCache = new Cache[chunksx][chunksy]; - - for(int x = 0; x < chunksx; x ++){ - for(int y = 0; y < chunksy; y ++){ + + for(int x = 0; x < chunksx; x++){ + for(int y = 0; y < chunksy; y++){ renderCache(x, y); } } @@ -257,9 +255,9 @@ public class Renderer extends RendererModule{ int rangey = (int) (camera.viewportHeight * camera.zoom / tilesize / 2) + 2; boolean noshadows = Settings.getBool("noshadows"); - + boolean drawTiles = true; - + //0 = shadows //1 = normal blocks //2 = over blocks @@ -267,7 +265,7 @@ public class Renderer extends RendererModule{ if(l == 0){ Graphics.surface("shadow"); } - + for(int x = -rangex; x <= rangex; x++){ for(int y = -rangey; y <= rangey; y++){ int worldx = Mathf.scl(camera.position.x, tilesize) + x; @@ -294,6 +292,23 @@ public class Renderer extends RendererModule{ Draw.color(); } } + + if(Vars.debug && Vars.debugChunks){ + Draw.color(Color.YELLOW); + Draw.thick(1f); + for(int x = -crangex; x <= crangex; x++){ + for(int y = -crangey; y <= crangey; y++){ + int worldx = Mathf.scl(camera.position.x, chunksize * tilesize) + x; + int worldy = Mathf.scl(camera.position.y, chunksize * tilesize) + y; + + if(!Mathf.inBounds(worldx, worldy, floorCache)) + continue; + Draw.linerect(worldx * chunksize * tilesize, worldy * chunksize * tilesize, + chunksize * tilesize, chunksize * tilesize); + } + } + Draw.reset(); + } } void renderCache(int cx, int cy){ @@ -303,11 +318,11 @@ public class Renderer extends RendererModule{ for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){ Tile tile = World.tile(tilex, tiley); tile.floor().drawCache(tile); - + } } floorCache[cx][cy] = Caches.end(); - + } public void clearTiles(){ @@ -316,15 +331,16 @@ public class Renderer extends RendererModule{ void renderPixelOverlay(){ + //draw tutorial placement point if(Vars.control.tutorial.showBlock()){ int x = World.core.x + Vars.control.tutorial.getPlacePoint().x; int y = World.core.y + Vars.control.tutorial.getPlacePoint().y; int rot = Vars.control.tutorial.getPlaceRotation(); - + Draw.thick(1f); Draw.color(Color.YELLOW); - Draw.square(x * tilesize, y * tilesize, tilesize/2f + Mathf.sin(Timers.time(), 4f, 1f)); - + Draw.square(x * tilesize, y * tilesize, tilesize / 2f + Mathf.sin(Timers.time(), 4f, 1f)); + Draw.color(Color.ORANGE); Draw.thick(2f); if(rot != -1){ @@ -332,8 +348,10 @@ public class Renderer extends RendererModule{ } Draw.reset(); } - - if(player.recipe != null && Vars.control.hasItems(player.recipe.requirements) && (!ui.hasMouse() || android) && AndroidInput.mode == PlaceMode.cursor){ + + //draw placement box + if(player.recipe != null && Vars.control.hasItems(player.recipe.requirements) + && (!ui.hasMouse() || android) && AndroidInput.mode == PlaceMode.cursor){ float x = 0; float y = 0; @@ -348,22 +366,19 @@ public class Renderer extends RendererModule{ tilex = Input.tilex(); tiley = Input.tiley(); } - - x = tilex*tilesize; - y = tiley*tilesize; - boolean valid = World.validPlace(tilex, tiley, player.recipe.result) && (android || - Input.cursorNear()); - + x = tilex * tilesize; + y = tiley * tilesize; + + boolean valid = World.validPlace(tilex, tiley, player.recipe.result) && (android || Input.cursorNear()); + Vector2 offset = player.recipe.result.getPlaceOffset(); - + float si = MathUtils.sin(Timers.time() / 6f) + 1; - + Draw.color(valid ? Color.PURPLE : Color.SCARLET); Draw.thickness(2f); - Draw.linecrect(x + offset.x, y + offset.y, - tilesize * player.recipe.result.width + si, - tilesize * player.recipe.result.height + si); + Draw.linecrect(x + offset.x, y + offset.y, tilesize * player.recipe.result.width + si, tilesize * player.recipe.result.height + si); player.recipe.result.drawPlace(tilex, tiley, valid); @@ -390,25 +405,27 @@ public class Renderer extends RendererModule{ //block breaking if(Inputs.buttonDown(Buttons.RIGHT) && World.validBreak(Input.tilex(), Input.tiley())){ Tile tile = World.tile(Input.tilex(), Input.tiley()); - if(tile.isLinked()) tile = tile.getLinked(); + if(tile.isLinked()) + tile = tile.getLinked(); Vector2 offset = tile.block().getPlaceOffset(); - + Draw.color(Color.YELLOW, Color.SCARLET, player.breaktime / tile.getBreakTime()); Draw.linecrect(tile.worldx() + offset.x, tile.worldy() + offset.y, tile.block().width * Vars.tilesize, tile.block().height * Vars.tilesize); Draw.reset(); }else if(android && player.breaktime > 0){ //android block breaking Vector2 vec = Graphics.world(Gdx.input.getX(0), Gdx.input.getY(0)); - + if(World.validBreak(Mathf.scl2(vec.x, tilesize), Mathf.scl2(vec.y, tilesize))){ Tile tile = World.tile(Mathf.scl2(vec.x, tilesize), Mathf.scl2(vec.y, tilesize)); - + float fract = player.breaktime / tile.getBreakTime(); Draw.color(Color.YELLOW, Color.SCARLET, fract); Draw.circle(tile.worldx(), tile.worldy(), 4 + (1f - fract) * 26); Draw.reset(); } } - + + //draw selected block health if(player.recipe == null && !ui.hasMouse()){ Tile tile = World.tile(Input.tilex(), Input.tiley()); @@ -416,33 +433,33 @@ public class Renderer extends RendererModule{ Tile target = tile; if(tile.isLinked()) target = tile.getLinked(); - + Vector2 offset = target.block().getPlaceOffset(); - + if(target.entity != null) - drawHealth(target.entity.x + offset.x, target.entity.y - 3f - target.block().height/2f * Vars.tilesize + offset.y, - target.entity.health, target.entity.maxhealth); - + drawHealth(target.entity.x + offset.x, target.entity.y - 3f - target.block().height / 2f * Vars.tilesize + offset.y, target.entity.health, target.entity.maxhealth); + target.block().drawPixelOverlay(target); } } - - boolean smoothcam = Settings.getBool("smoothcam"); + boolean smoothcam = Settings.getBool("smoothcam"); + + //draw entity health bars for(Entity entity : Entities.all()){ if(entity instanceof DestructibleEntity && !(entity instanceof TileEntity)){ DestructibleEntity dest = ((DestructibleEntity) entity); - + if(dest instanceof Player && Vars.snapCamera && smoothcam && Settings.getBool("pixelate")){ - drawHealth((int)dest.x, (int)dest.y - 7f, dest.health, dest.maxhealth); + drawHealth((int) dest.x, (int) dest.y - 7f, dest.health, dest.maxhealth); }else{ drawHealth(dest.x, dest.y - 7f, dest.health, dest.maxhealth); } - + } } } - + void drawHealth(float x, float y, float health, float maxhealth){ drawBar(Color.RED, x, y, health / maxhealth); } diff --git a/core/src/io/anuke/mindustry/Shaders.java b/core/src/io/anuke/mindustry/Shaders.java index 5efc77295d..ec8a6b7da0 100644 --- a/core/src/io/anuke/mindustry/Shaders.java +++ b/core/src/io/anuke/mindustry/Shaders.java @@ -3,6 +3,7 @@ package io.anuke.mindustry; import com.badlogic.gdx.graphics.Color; import io.anuke.ucore.core.Core; +import io.anuke.ucore.core.Settings; import io.anuke.ucore.core.Timers; import io.anuke.ucore.graphics.Shader; import io.anuke.ucore.util.Tmp; @@ -35,10 +36,14 @@ public class Shaders{ @Override public void apply(){ + float scale = Settings.getBool("pixelate") ? 1 : Core.cameraScale / Core.camera.zoom; + float scaling = Core.cameraScale / 4f / Core.camera.zoom; shader.setUniformf("u_color", color); shader.setUniformf("u_time", Timers.time()); + shader.setUniformf("u_scaling", scaling); shader.setUniformf("u_offset", Tmp.v1.set(Core.camera.position.x, Core.camera.position.y)); - shader.setUniformf("u_texsize", Tmp.v1.set(region.getTexture().getWidth(), region.getTexture().getHeight())); + shader.setUniformf("u_texsize", Tmp.v1.set(region.getTexture().getWidth() / scale, + region.getTexture().getHeight() / scale)); } } diff --git a/core/src/io/anuke/mindustry/UI.java b/core/src/io/anuke/mindustry/UI.java index 224733fcbb..6de60ef5d2 100644 --- a/core/src/io/anuke/mindustry/UI.java +++ b/core/src/io/anuke/mindustry/UI.java @@ -665,7 +665,33 @@ public class UI extends SceneModule{ header.addImage(region).size(8*5).padTop(4).units(Unit.dp); Label nameLabel = new Label(recipe.result.formalName); nameLabel.setWrap(true); - header.add(nameLabel).padLeft(4).width(160f).units(Unit.dp); + header.add(nameLabel).padLeft(4).width(135f).units(Unit.dp); + + //extra info + if(recipe.result.fullDescription != null){ + header.addButton("?", ()->{ + Label desclabel = new Label(recipe.result.fullDescription); + desclabel.setWrap(true); + + boolean wasPaused = GameState.is(State.paused); + GameState.set(State.paused); + + FloatingDialog d = new FloatingDialog("Block Info"); + Table top = new Table(); + top.left(); + top.add(new Image(region)).size(8*5).units(Unit.dp); + top.add("[orange]"+recipe.result.formalName).padLeft(6f).units(Unit.dp); + d.content().add(top).fill().left(); + d.content().row(); + d.content().add(desclabel).width(600).units(Unit.dp); + d.buttons().addButton("OK", ()->{ + if(!wasPaused) GameState.set(State.playing); + d.hide(); + }).size(110, 50).pad(10f).units(Unit.dp); + d.show(); + }).fillX().top().right().size(36f, 40f).units(Unit.dp); + } + desctable.add().pad(2).units(Unit.dp); diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index 66e272d0ad..4810113676 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -31,6 +31,8 @@ public class Vars{ public static boolean debug = false; //whether to debug openGL info public static boolean debugGL = false; + //whether to draw chunk borders + public static boolean debugChunks = false; //whether turrets have infinite ammo (only with debug) public static boolean infiniteAmmo = true; //whether to show paths of enemies diff --git a/core/src/io/anuke/mindustry/world/blocks/DefenseBlocks.java b/core/src/io/anuke/mindustry/world/blocks/DefenseBlocks.java index e2d092e07a..59a835fb06 100644 --- a/core/src/io/anuke/mindustry/world/blocks/DefenseBlocks.java +++ b/core/src/io/anuke/mindustry/world/blocks/DefenseBlocks.java @@ -13,6 +13,8 @@ public class DefenseBlocks{ stonewall = new Wall("stonewall"){{ health = 50; formalName = "stone wall"; + fullDescription = + "A cheap defensive block. Useful for protecting the core and turrets in the first few waves."; }}, ironwall = new Wall("ironwall"){{ diff --git a/desktop/mindustry-saves/3.mins b/desktop/mindustry-saves/3.mins index 2554c77e80ac4eab10b843dcb443c87d6fe36195..3fe1e6e8ec005ea0d827c486a4820eebd48a10db 100644 GIT binary patch literal 1539 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