diff --git a/assets/joystick.png b/assets/joystick.png new file mode 100644 index 0000000000..338f959836 Binary files /dev/null and b/assets/joystick.png differ diff --git a/assets/toolbar.png b/assets/toolbar.png index 89fe80800d..07131536db 100644 Binary files a/assets/toolbar.png and b/assets/toolbar.png differ diff --git a/src/com/watabou/pixeldungeon/Assets.java b/src/com/watabou/pixeldungeon/Assets.java index a940c77265..79c4720508 100644 --- a/src/com/watabou/pixeldungeon/Assets.java +++ b/src/com/watabou/pixeldungeon/Assets.java @@ -33,6 +33,7 @@ public class Assets { public static final String HP_BAR = "hp_bar.png"; public static final String XP_BAR = "exp_bar.png"; public static final String TOOLBAR = "toolbar.png"; + public static final String JOYSTICK = "joystick.png"; public static final String WARRIOR = "warrior.png"; public static final String MAGE = "mage.png"; diff --git a/src/com/watabou/pixeldungeon/PixelDungeon.java b/src/com/watabou/pixeldungeon/PixelDungeon.java index 6f5af9c539..c7f61db7e4 100644 --- a/src/com/watabou/pixeldungeon/PixelDungeon.java +++ b/src/com/watabou/pixeldungeon/PixelDungeon.java @@ -195,6 +195,25 @@ public static void intro( boolean value ) { public static boolean intro() { return Preferences.INSTANCE.getBoolean( Preferences.KEY_INTRO, true ); } + + public static void joystick( boolean value ) { + Preferences.INSTANCE.put( Preferences.KEY_JOYSTICK, value ); + if (scene() instanceof GameScene) { + ((GameScene)scene()).joystick( value ); + } + } + + public static boolean joystick () { + return Preferences.INSTANCE.getBoolean( Preferences.KEY_JOYSTICK, true ); + } + + public static void continuous( boolean value ) { + Preferences.INSTANCE.put( Preferences.KEY_CONTINUOUS, value ); + } + + public static boolean continuous () { + return Preferences.INSTANCE.getBoolean( Preferences.KEY_CONTINUOUS, true ); + } /* * <--- Preferences diff --git a/src/com/watabou/pixeldungeon/Preferences.java b/src/com/watabou/pixeldungeon/Preferences.java index 7fbfdbb798..74b959a2af 100644 --- a/src/com/watabou/pixeldungeon/Preferences.java +++ b/src/com/watabou/pixeldungeon/Preferences.java @@ -34,6 +34,8 @@ enum Preferences { public static final String KEY_DONATED = "donated"; public static final String KEY_INTRO = "intro"; public static final String KEY_BRIGHTNESS = "brightness"; + public static final String KEY_JOYSTICK = "joystick"; + public static final String KEY_CONTINUOUS = "continuous"; private SharedPreferences prefs; diff --git a/src/com/watabou/pixeldungeon/scenes/CellSelector.java b/src/com/watabou/pixeldungeon/scenes/CellSelector.java index dbfd1d6d27..1d3398238d 100644 --- a/src/com/watabou/pixeldungeon/scenes/CellSelector.java +++ b/src/com/watabou/pixeldungeon/scenes/CellSelector.java @@ -19,10 +19,14 @@ import com.watabou.input.Touchscreen.Touch; import com.watabou.noosa.TouchArea; +import com.watabou.noosa.Game; +import com.watabou.pixeldungeon.Dungeon; import com.watabou.pixeldungeon.DungeonTilemap; import com.watabou.pixeldungeon.PixelDungeon; +import com.watabou.pixeldungeon.levels.Level; import com.watabou.utils.GameMath; import com.watabou.utils.PointF; +import com.watabou.utils.Point; public class CellSelector extends TouchArea { @@ -31,6 +35,11 @@ public class CellSelector extends TouchArea { public boolean enabled; private float dragThreshold; + + private boolean pressed; + private float pressTime; + private boolean continuous; + private float continuousThreshold = 0.2f; public CellSelector( DungeonTilemap map ) { super( map ); @@ -73,6 +82,7 @@ public void select( int cell ) { @Override protected void onTouchDown( Touch t ) { + continuous = false; if (t != touch && another == null) { @@ -89,11 +99,80 @@ protected void onTouchDown( Touch t ) { startZoom = camera.zoom; dragging = false; + } else { + pressed = true; + } + } + + @Override + public void update () { + if (!pressed || dragging || pinching) + return; + + if (!(pressTime >= continuousThreshold)) { + pressTime += Game.elapsed; + return; + } + + continuous = PixelDungeon.continuous(); + if (!continuous) + return; + + float size = DungeonTilemap.SIZE*camera.zoom; + PointF p = Dungeon.hero.sprite.worldToCamera( Dungeon.hero.pos ); + Point hero = camera.cameraToScreen (p.x, p.y); + hero.x += size/2; + hero.y += size/2; + float x = Math.abs( touch.current.x - hero.x ); + float y = Math.abs( touch.current.y - hero.y ); + float x2 = 0, y2 = 0; + if (Math.abs(x) > Math.abs(y)) { + x2 += size; + y2 += (y*(x2/x)); + } else { + y2 += size; + x2 += (x*(y2/y)); } + + if (touch.current.x < hero.x) + x2 *= -1; + + if (touch.current.y < hero.y) + y2 *= -1; + + int cell = ((DungeonTilemap)target).screenToTile( hero.x + (int)x2, hero.y + (int)y2 ); + if (!Dungeon.level.passable[cell]) { + int[] neighbours = {Dungeon.hero.pos+1, + Dungeon.hero.pos+1+Level.WIDTH, + Dungeon.hero.pos+Level.WIDTH, + Dungeon.hero.pos-1+Level.WIDTH, + Dungeon.hero.pos-1, + Dungeon.hero.pos-1-Level.WIDTH, + Dungeon.hero.pos-Level.WIDTH, + Dungeon.hero.pos+1-Level.WIDTH}; + + for (int i = 0; i < 8; i++) { + if (neighbours[i] != cell) + continue; + + int next = (i+1 >= 8) ? 0 : i+1; + int prv = (i-1 < 0) ? 7 : i-1; + + if (Dungeon.level.passable[neighbours[next]]) + cell = neighbours[next]; + + if (Dungeon.level.passable[neighbours[prv]]) + cell = neighbours[prv]; + } + } + + select( cell ); } @Override protected void onTouchUp( Touch t ) { + pressed = false; + pressTime = 0; if (pinching && (t == touch || t == another)) { pinching = false; @@ -117,6 +196,9 @@ protected void onTouchUp( Touch t ) { @Override protected void onDrag( Touch t ) { + if (continuous) + return; + camera.target = null; if (pinching) { diff --git a/src/com/watabou/pixeldungeon/scenes/GameScene.java b/src/com/watabou/pixeldungeon/scenes/GameScene.java index d6ff401530..42929e072f 100644 --- a/src/com/watabou/pixeldungeon/scenes/GameScene.java +++ b/src/com/watabou/pixeldungeon/scenes/GameScene.java @@ -66,6 +66,7 @@ import com.watabou.pixeldungeon.ui.Toast; import com.watabou.pixeldungeon.ui.Toolbar; import com.watabou.pixeldungeon.ui.Window; +import com.watabou.pixeldungeon.ui.Joystick; import com.watabou.pixeldungeon.utils.GLog; import com.watabou.pixeldungeon.windows.WndBag.Mode; import com.watabou.pixeldungeon.windows.WndGame; @@ -109,6 +110,7 @@ public class GameScene extends PixelScene { private Group statuses; private Group emoicons; + private Joystick joystick; private Toolbar toolbar; private Toast prompt; @@ -136,7 +138,7 @@ public void create() { ripples = new Group(); terrain.add( ripples ); - + tiles = new DungeonTilemap(); terrain.add( tiles ); @@ -228,6 +230,12 @@ public void create() { log.camera = uiCamera; log.setRect( 0, toolbar.top(), attack.left(), 0 ); add( log ); + + joystick = new Joystick (); + joystick.camera = uiCamera; + joystick.setPos(5, toolbar.top()-60); + joystick (PixelDungeon.joystick ()); + add (joystick); if (Dungeon.depth < Statistics.deepestFloor) { GLog.i( TXT_WELCOME_BACK, Dungeon.depth ); @@ -364,6 +372,10 @@ public void brightness( boolean value ) { fog.aa = 0f; } } + + public void joystick( boolean value ) { + joystick.visible = value; + } private void addHeapSprite( Heap heap ) { ItemSprite sprite = heap.sprite = (ItemSprite)heaps.recycle( ItemSprite.class ); diff --git a/src/com/watabou/pixeldungeon/ui/GameLog.java b/src/com/watabou/pixeldungeon/ui/GameLog.java index 188a84b146..002b7420ee 100644 --- a/src/com/watabou/pixeldungeon/ui/GameLog.java +++ b/src/com/watabou/pixeldungeon/ui/GameLog.java @@ -18,13 +18,21 @@ package com.watabou.pixeldungeon.ui; import java.util.regex.Pattern; +import java.util.List; +import java.util.ArrayList; import com.watabou.noosa.BitmapTextMultiline; import com.watabou.noosa.ui.Component; +import com.watabou.noosa.Gizmo; +import com.watabou.input.Touchscreen.Touch; +import com.watabou.noosa.TouchArea; +import com.watabou.pixeldungeon.windows.WndLog; import com.watabou.pixeldungeon.scenes.PixelScene; import com.watabou.pixeldungeon.sprites.CharSprite; import com.watabou.pixeldungeon.utils.GLog; import com.watabou.pixeldungeon.utils.Utils; +import com.watabou.pixeldungeon.scenes.GameScene; + import com.watabou.utils.Signal; public class GameLog extends Component implements Signal.Listener { @@ -34,7 +42,10 @@ public class GameLog extends Component implements Signal.Listener { private static final Pattern PUNCTUATION = Pattern.compile( ".*[.,;?! ]$" ); private BitmapTextMultiline lastEntry; + private List entries = new ArrayList (); private int lastColor; + private WndLog window; + private TouchArea hotArea; public GameLog() { super(); @@ -47,6 +58,16 @@ public void newLine() { lastEntry = null; } + @Override + protected void createChildren() { + hotArea = new TouchArea( 0, 0, 0, 0 ) { + protected void onTouchUp( Touch touch ) { + GameScene.show( new WndLog (entries) ); + } + }; + add( hotArea ); + } + @Override public void onSignal( String text ) { @@ -84,12 +105,12 @@ public void onSignal( String text ) { lastEntry.measure(); lastEntry.hardlight( color ); lastColor = color; + entries.add ( lastEntry ); add( lastEntry ); - } if (length > MAX_MESSAGES) { - remove( members.get( 0 ) ); + remove( entries.get(entries.size()-MAX_MESSAGES) ); } layout(); @@ -97,13 +118,26 @@ public void onSignal( String text ) { @Override protected void layout() { - float pos = y; + float pos = bottom(); for (int i=length-1; i >= 0; i--) { - BitmapTextMultiline entry = (BitmapTextMultiline)members.get( i ); + Gizmo item = members.get( i ); + if (item == hotArea) + continue; + + BitmapTextMultiline entry = (BitmapTextMultiline)item; entry.x = x; entry.y = pos - entry.height(); pos -= entry.height(); } + + height = bottom ()-pos; + y = pos; + + hotArea.active = visible; + hotArea.x = x; + hotArea.y = y; + hotArea.width = width; + hotArea.height = height; } @Override diff --git a/src/com/watabou/pixeldungeon/ui/Joystick.java b/src/com/watabou/pixeldungeon/ui/Joystick.java new file mode 100644 index 0000000000..3bbb7189bd --- /dev/null +++ b/src/com/watabou/pixeldungeon/ui/Joystick.java @@ -0,0 +1,125 @@ +package com.watabou.pixeldungeon.ui; + +import com.watabou.utils.PointF; +import com.watabou.input.Touchscreen.Touch; +import com.watabou.noosa.TouchArea; +import com.watabou.noosa.Game; +import com.watabou.noosa.Image; +import com.watabou.noosa.ui.Component; +import com.watabou.pixeldungeon.Dungeon; +import com.watabou.pixeldungeon.Assets; +import com.watabou.pixeldungeon.levels.Level; +import com.watabou.pixeldungeon.scenes.GameScene; + +public class Joystick extends Component +{ + public static float longClick = 1f; + + protected float pressTime; + protected boolean pressed; + protected boolean draging; + + protected TouchArea hotArea; + + protected Image bg; + + protected PointF current; + + public Joystick () { + bg = new Image (Assets.JOYSTICK); + add( bg ); + setSize (30, 30); + } + + @Override + protected void createChildren() { + hotArea = new TouchArea( 0, 0, 0, 0 ) { + protected void onDrag( Touch touch ) { + pressTime = 0; + PointF p = camera().screenToCamera( (int)touch.current.x, (int)touch.current.y ); + if (draging) + setPos (p.x-15, p.y-15); + else { + if (p.x > left()+(width()/3) && p.x < right()-(width()/3) + && p.y < bottom()-(height()/3) && p.y > top()+(height()/3)) + pressed = true; + else { + pressed = false; + current = p; + } + } + } + + protected void onTouchDown( Touch touch ) { + onDrag (touch); + } + + protected void onTouchUp( Touch touch ) { + pressed = false; + draging = false; + current = null; + bg.x = x; + bg.y = y; + } + + }; + add( hotArea ); + } + + @Override + public void update() { + super.update(); + + hotArea.active = visible; + + if (current != null) + step (current); + + if (pressed) { + if ((pressTime += Game.elapsed) >= longClick) { + pressed = false; + draging = true; + Game.vibrate( 50 ); + } + } + } + + protected boolean onLongClick() { + return false; + }; + + @Override + protected void layout() { + hotArea.x = x; + hotArea.y = y; + hotArea.width = width; + hotArea.height = height; + + bg.x = x; + bg.y = y; + } + + private void step (PointF p) { + if (p.x > left()+(width()/3) && p.x < right()-(width()/3)) { + if (p.y < top()+(height()/3)) { + bg.x = x; + bg.y = y-1; + GameScene.handleCell (Dungeon.hero.pos-Level.WIDTH); + } else if (p.y > bottom()-(height()/3)) { + bg.x = x; + bg.y = y+1; + GameScene.handleCell (Dungeon.hero.pos+Level.WIDTH); + } + } else if (p.y < bottom()-(height()/3) && p.y > top()+(height()/3)) { + if (p.x < left()+(width()/3)) { + bg.y = y; + bg.x = x-1; + GameScene.handleCell (Dungeon.hero.pos-1); + } else if (p.x > right()-(width()/3)) { + bg.y = y; + bg.x = x+1; + GameScene.handleCell (Dungeon.hero.pos+1); + } + } + } +} \ No newline at end of file diff --git a/src/com/watabou/pixeldungeon/ui/Toolbar.java b/src/com/watabou/pixeldungeon/ui/Toolbar.java index 0f317cef7b..1a003a1592 100644 --- a/src/com/watabou/pixeldungeon/ui/Toolbar.java +++ b/src/com/watabou/pixeldungeon/ui/Toolbar.java @@ -17,6 +17,7 @@ */ package com.watabou.pixeldungeon.ui; +import com.watabou.utils.PathFinder; import com.watabou.noosa.Game; import com.watabou.noosa.Gizmo; import com.watabou.noosa.Image; @@ -30,6 +31,7 @@ import com.watabou.pixeldungeon.items.Heap; import com.watabou.pixeldungeon.items.Item; import com.watabou.pixeldungeon.levels.Level; +import com.watabou.pixeldungeon.levels.Terrain; import com.watabou.pixeldungeon.plants.Plant; import com.watabou.pixeldungeon.scenes.CellSelector; import com.watabou.pixeldungeon.scenes.GameScene; @@ -49,6 +51,7 @@ public class Toolbar extends Component { private Tool btnWait; private Tool btnSearch; private Tool btnInfo; + private Tool btnExplore; private Tool btnResume; private Tool btnInventory; private Tool btnQuick; @@ -91,6 +94,76 @@ protected void onClick() { } } ); + add( btnExplore = new Tool( 127, 7, 21, 24 ) { + @Override + protected void onClick() { + Level level = Dungeon.level; + int[] cells = {-1, -1, -1}; + int[] dst = {-1, -1, -1}; + for (int i = 0; i < Level.LENGTH; i++) { + if (level.visited[i]) + continue; + + int type = Terrain.flags[level.map[i]]; + if (type == Terrain.LOCKED_DOOR || (type & Terrain.PASSABLE) != 0) { + for (int n : Level.NEIGHBOURS8) { + if (i+n < 0 || i+n >= Level.LENGTH || !level.visited[i+n]) + continue; + + if ((Terrain.flags[level.map[i+n]] & Terrain.PASSABLE) != 0) { + PathFinder.Path p = PathFinder.find( Dungeon.hero.pos, i+n, level.passable ); + if (p == null) + continue; + + int size = p.size(); + + if (size < dst[0] || dst[0] == -1) { + cells[0] = i+n; + dst[0] = size; + } + } else if (level.map[i+n] == Terrain.LOCKED_DOOR) { + PathFinder.Path p = PathFinder.find( Dungeon.hero.pos, i+n, level.passable ); + if (p == null) + continue; + + int size = p.size(); + + if (size < dst[1] || dst[1] == -1) { + cells[1] = i+n; + dst[1] = size; + } + } + } + } else if ((type & Terrain.AVOID) != 0) { + for (int n : Level.NEIGHBOURS8) { + if (i+n < 0 || i+n >= Level.LENGTH + || !(level.visited[i+n] && (Terrain.flags[level.map[i+n]] & Terrain.PASSABLE) != 0)) + continue; + + PathFinder.Path p = PathFinder.find( Dungeon.hero.pos, i+n, level.passable ); + if (p == null) + continue; + + int size = p.size(); + + if (size < dst[2] || dst[2] == -1) { + cells[2] = i+n; + dst[2] = size; + } + } + } + } + + for (int cell : cells) { + if (cell == -1) + continue; + + GameScene.handleCell( cell ); + break; + } + } + } ); + add( btnResume = new Tool( 61, 7, 21, 24 ) { @Override protected void onClick() { @@ -131,6 +204,7 @@ protected void layout() { btnWait.setPos( x, y ); btnSearch.setPos( btnWait.right(), y ); btnInfo.setPos( btnSearch.right(), y ); + btnExplore.setPos( btnInfo.right(), y ); btnResume.setPos( btnInfo.right(), y ); btnQuick.setPos( width - btnQuick.width(), y ); btnInventory.setPos( btnQuick.left() - btnInventory.width(), y ); @@ -151,6 +225,7 @@ public void update() { } btnResume.visible = Dungeon.hero.lastAction != null; + btnExplore.visible = !btnResume.visible; if (!Dungeon.hero.isAlive()) { btnInventory.enable( true ); diff --git a/src/com/watabou/pixeldungeon/windows/WndLog.java b/src/com/watabou/pixeldungeon/windows/WndLog.java new file mode 100644 index 0000000000..9814be78b7 --- /dev/null +++ b/src/com/watabou/pixeldungeon/windows/WndLog.java @@ -0,0 +1,53 @@ +package com.watabou.pixeldungeon.windows; + +import java.util.List; + +import com.watabou.noosa.BitmapTextMultiline; +import com.watabou.noosa.BitmapText; +import com.watabou.noosa.ui.Component; +import com.watabou.pixeldungeon.scenes.PixelScene; +import com.watabou.pixeldungeon.ui.ScrollPane; +import com.watabou.pixeldungeon.ui.Window; + +public class WndLog extends Window { + + private static final int WIDTH = 112; + private static final int HEIGHT = 160; + + private static final String TXT_TITLE = "Log"; + + private BitmapText txtTitle; + + public WndLog(List entries) { + + super(); + resize( WIDTH, HEIGHT ); + + txtTitle = PixelScene.createText( TXT_TITLE, 9 ); + txtTitle.hardlight( Window.TITLE_COLOR ); + txtTitle.measure(); + txtTitle.x = PixelScene.align( PixelScene.uiCamera, (WIDTH - txtTitle.width()) / 2 ); + add( txtTitle ); + + float pos = txtTitle.height(); + Component content = new Component(); + for (int i = entries.size()-1; i >= 0; i--) { + BitmapTextMultiline entry = entries.get( i ); + BitmapTextMultiline text = PixelScene.createMultiline( entry.text(), 6 ); + text.maxWidth = entry.maxWidth; + text.measure(); + text.hardlight( entry.rm, entry.gm, entry.bm ); + content.add (text); + text.x = 0; + text.y = pos; + pos += entry.height(); + } + + content.setSize ( WIDTH, pos ); + + ScrollPane list = new ScrollPane( content ); + add( list ); + + list.setRect( 0, txtTitle.height(), WIDTH, HEIGHT - txtTitle.height() ); + } +} diff --git a/src/com/watabou/pixeldungeon/windows/WndSettings.java b/src/com/watabou/pixeldungeon/windows/WndSettings.java index f1a3b0b24f..fa9f32085b 100644 --- a/src/com/watabou/pixeldungeon/windows/WndSettings.java +++ b/src/com/watabou/pixeldungeon/windows/WndSettings.java @@ -40,6 +40,9 @@ public class WndSettings extends Window { private static final String TXT_BRIGHTNESS = "Brightness"; + private static final String TXT_JOYSTICK = "Joystick"; + private static final String TXT_CONTINUOUS = "Continuous goto"; + private static final String TXT_SWITCH_PORT = "Switch to portrait"; private static final String TXT_SWITCH_LAND = "Switch to landscape"; @@ -119,6 +122,30 @@ protected void onClick() { btnSound.setRect( 0, btnMusic.bottom() + GAP, WIDTH, BTN_HEIGHT ); btnSound.checked( PixelDungeon.soundFx() ); add( btnSound ); + + CheckBox btnJoystick = new CheckBox( TXT_JOYSTICK ) { + @Override + protected void onClick() { + super.onClick(); + PixelDungeon.joystick( checked() ); + Sample.INSTANCE.play( Assets.SND_CLICK ); + } + }; + btnJoystick.setRect( 0, btnSound.bottom() + GAP, WIDTH, BTN_HEIGHT ); + btnJoystick.checked( PixelDungeon.joystick() ); + add( btnJoystick ); + + CheckBox btnContinuous = new CheckBox( TXT_CONTINUOUS ) { + @Override + protected void onClick() { + super.onClick(); + PixelDungeon.continuous( checked() ); + Sample.INSTANCE.play( Assets.SND_CLICK ); + } + }; + btnContinuous.setRect( 0, btnJoystick.bottom() + GAP, WIDTH, BTN_HEIGHT ); + btnContinuous.checked( PixelDungeon.continuous() ); + add( btnContinuous ); if (!inGame) { @@ -128,7 +155,7 @@ protected void onClick() { PixelDungeon.landscape( !PixelDungeon.landscape() ); } }; - btnOrientation.setRect( 0, btnSound.bottom() + GAP, WIDTH, BTN_HEIGHT ); + btnOrientation.setRect( 0, btnContinuous.bottom() + GAP, WIDTH, BTN_HEIGHT ); add( btnOrientation ); resize( WIDTH, (int)btnOrientation.bottom() ); @@ -142,7 +169,7 @@ protected void onClick() { PixelDungeon.brightness( checked() ); } }; - btnBrightness.setRect( 0, btnSound.bottom() + GAP, WIDTH, BTN_HEIGHT ); + btnBrightness.setRect( 0, btnContinuous.bottom() + GAP, WIDTH, BTN_HEIGHT ); btnBrightness.checked( PixelDungeon.brightness() ); add( btnBrightness );