v1.3.0: implemented new radial menus and made a bunch of keybind changes

This commit is contained in:
Evan Debenham
2022-07-01 13:37:51 -04:00
parent 2e595ed392
commit e3687bb2ca
21 changed files with 620 additions and 98 deletions

View File

@@ -27,6 +27,8 @@ import com.badlogic.gdx.controllers.Controller;
import com.badlogic.gdx.controllers.ControllerListener;
import com.badlogic.gdx.controllers.ControllerMapping;
import com.badlogic.gdx.controllers.Controllers;
import com.watabou.noosa.Game;
import com.watabou.noosa.ui.Cursor;
import com.watabou.utils.DeviceCompat;
import com.watabou.utils.PointF;
@@ -138,14 +140,32 @@ public class ControllerHandler implements ControllerListener {
//we use a separate variable as Gdx.input.isCursorCatched only works on desktop
private static boolean controllerPointerActive = false;
private static PointF controllerPointerPos;
public static void setControllerPointer( boolean active ){
Gdx.input.setCursorCatched(active);
if (controllerPointerActive == active) return;
controllerPointerActive = active;
if (active){
Gdx.input.setCursorCatched(true);
controllerPointerPos = new PointF(Game.width/2, Game.height/2);
} else if (!Cursor.isCursorCaptured()) {
Gdx.input.setCursorCatched(false);
}
}
public static boolean controllerPointerActive(){
return controllerPointerActive;
return controllerPointerActive && !Cursor.isCursorCaptured();
}
public static PointF getControllerPointerPos(){
return controllerPointerPos.clone();
}
public static void updateControllerPointer(PointF pos, boolean sendEvent){
controllerPointerPos.set(pos);
if (sendEvent) {
PointerEvent.addPointerEvent(new PointerEvent((int) controllerPointerPos.x, (int) controllerPointerPos.y, 10_000, PointerEvent.Type.HOVER, PointerEvent.NONE));
}
}
//converts controller button codes to keyEvent codes
@@ -199,6 +219,16 @@ public class ControllerHandler implements ControllerListener {
} else if (keyCode == Input.Keys.BUTTON_Y){
return "Triangle Button";
}
} else if (lastUsedType == ControllerType.XBOX){
if (keyCode == Input.Keys.BUTTON_L1){
return "Left Bumper";
} else if (keyCode == Input.Keys.BUTTON_L2){
return "Left Trigger";
} else if (keyCode == Input.Keys.BUTTON_R1){
return "Right Bumper";
} else if (keyCode == Input.Keys.BUTTON_R2){
return "Right Trigger";
}
}
if (keyCode == Input.Keys.DPAD_UP + 1000){

View File

@@ -27,6 +27,7 @@ import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.InputProcessor;
import com.watabou.noosa.Game;
import com.watabou.noosa.ui.Cursor;
public class InputHandler extends InputAdapter {

View File

@@ -82,19 +82,17 @@ public class KeyBindings {
return GameAction.NONE;
}
public static ArrayList<Integer> getBoundKeysForAction(GameAction action){
ArrayList<Integer> result = new ArrayList<>();
for( int i : bindings.keySet() ){
if (bindings.get(i) == action){
result.add(i);
}
public static int getFirstKeyForAction(GameAction action, boolean preferController){
ArrayList<Integer> keys = getKeyboardKeysForAction(action);
ArrayList<Integer> buttons = getControllerKeysForAction(action);
if (preferController){
if (!buttons.isEmpty()) return buttons.get(0);
else if (!keys.isEmpty()) return keys.get(0);
} else {
if (!keys.isEmpty()) return keys.get(0);
else if (!buttons.isEmpty()) return buttons.get(0);
}
for( int i : controllerBindings.keySet() ){
if (controllerBindings.get(i) == action){
result.add(i);
}
}
return result;
return 0;
}
public static ArrayList<Integer> getKeyboardKeysForAction(GameAction action){

View File

@@ -23,10 +23,13 @@ package com.watabou.input;
import com.badlogic.gdx.Input;
import com.watabou.noosa.Game;
import com.watabou.noosa.ui.Cursor;
import com.watabou.utils.GameMath;
import com.watabou.utils.PointF;
import com.watabou.utils.Signal;
import java.util.ArrayList;
import java.util.Currency;
import java.util.HashMap;
public class PointerEvent {
@@ -57,6 +60,10 @@ public class PointerEvent {
}
public PointerEvent( int x, int y, int id, Type type, int button){
if (Cursor.isCursorCaptured()){
x = Game.width/2;
y = Game.width/2;
}
start = current = new PointF(x, y);
this.id = id;
this.type = type;

View File

@@ -24,7 +24,10 @@ package com.watabou.noosa.ui;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Pixmap;
import com.watabou.input.ControllerHandler;
import com.watabou.noosa.Game;
import com.watabou.utils.FileUtils;
import com.watabou.utils.PointF;
public class Cursor {
@@ -86,4 +89,30 @@ public class Cursor {
}
private static boolean cursorCaptured = false;
public static void captureCursor(boolean captured){
cursorCaptured = captured;
if (captured) {
Gdx.input.setCursorCatched(true);
} else {
if (ControllerHandler.controllerPointerActive()) {
ControllerHandler.setControllerPointer(true);
ControllerHandler.updateControllerPointer(new PointF(Game.width/2, Game.height/2), false);
} else {
Gdx.input.setCursorCatched(false);
Gdx.input.setCursorPosition(Game.width/2, Game.height/2);
}
}
}
public static PointF getCursorDelta(){
return new PointF(Gdx.input.getDeltaX(), Gdx.input.getDeltaY());
}
public static boolean isCursorCaptured(){
return cursorCaptured;
}
}

View File

@@ -138,7 +138,11 @@ public class PointF {
float dy = a.y - b.y;
return (float)Math.sqrt( dx * dx + dy * dy );
}
public static float angle( float x, float y ) {
return (float)Math.atan2( y, x );
}
public static float angle( PointF start, PointF end ) {
return (float)Math.atan2( end.y - start.y, end.x - start.x );
}

View File

@@ -14,8 +14,8 @@ allprojects {
appName = 'Shattered Pixel Dungeon'
appPackageName = 'com.shatteredpixel.shatteredpixeldungeon'
appVersionCode = 636
appVersionName = '1.3.0-BETA-2'
appVersionCode = 637
appVersionName = '1.3.0-BETA-3'
appJavaCompatibility = JavaVersion.VERSION_1_8

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -5,6 +5,18 @@ ui.talentspane.unlock_tier2=Reach level 6 to unlock more talents.
ui.talentspane.unlock_tier3=Reach level 12 and defeat the second boss to unlock more talents.
ui.talentspane.unlock_tier4=Reach level 20 and defeat the fourth boss to unlock more talents.
ui.toolbar.quickslot_prompt=Select a Quickslot
ui.toolbar.quickslot_select=Select Quickslot
ui.toolbar.quickslot_assign=Assign Quickslot
ui.toolbar.quickslot_cancel=Cancel
ui.toolbar.container_prompt=Select a Container
ui.toolbar.container_select=Select Container
ui.toolbar.container_cancel=Cancel
ui.toolbar.container_empty=That container is empty!
ui.toolbar.item_prompt=Select an Item
ui.toolbar.item_select=Select Item
ui.toolbar.item_use=Quick-use Item
ui.toolbar.item_cancel=Cancel
ui.toolbar.examine_prompt=Press again to search\nPress a tile to examine
ui.updatenotification.title=Update

View File

@@ -117,8 +117,8 @@ windows.wndkeybindings.journal=Journal
windows.wndkeybindings.wait=Wait
windows.wndkeybindings.examine=Examine
windows.wndkeybindings.rest=Rest
windows.wndkeybindings.inventory_selector=Inventory Selector
windows.wndkeybindings.inventory=Inventory
windows.wndkeybindings.inventory_selector=Inventory Selector
windows.wndkeybindings.quickslot_selector=Quickslot Selector
windows.wndkeybindings.quickslot_1=Quickslot 1
windows.wndkeybindings.quickslot_2=Quickslot 2
@@ -136,8 +136,8 @@ windows.wndkeybindings.tag_danger=Switch Enemy
windows.wndkeybindings.tag_action=Special Action
windows.wndkeybindings.tag_loot=Pickup Item
windows.wndkeybindings.tag_resume=Resume Motion
windows.wndkeybindings.zoom_in=Zoom In / Scroll Up
windows.wndkeybindings.zoom_out=Zoom Out / Scroll Down
windows.wndkeybindings.zoom_in=Zoom In / Scroll Down
windows.wndkeybindings.zoom_out=Zoom Out / Scroll Up
windows.wndkeybindings.n=Go North
windows.wndkeybindings.e=Go East
windows.wndkeybindings.s=Go South

View File

@@ -87,13 +87,15 @@ public class Assets {
public static final String LOADING_CITY = "interfaces/loading_city.png";
public static final String LOADING_HALLS = "interfaces/loading_halls.png";
public static final String BUFFS_SMALL = "interfaces/buffs.png";
public static final String BUFFS_LARGE = "interfaces/large_buffs.png";
public static final String BUFFS_SMALL = "interfaces/buffs.png";
public static final String BUFFS_LARGE = "interfaces/large_buffs.png";
public static final String TALENT_ICONS = "interfaces/talent_icons.png";
public static final String TALENT_BUTTON = "interfaces/talent_button.png";
public static final String TALENT_ICONS = "interfaces/talent_icons.png";
public static final String TALENT_BUTTON = "interfaces/talent_button.png";
public static final String HERO_ICONS = "interfaces/hero_icons.png";
public static final String HERO_ICONS = "interfaces/hero_icons.png";
public static final String RADIAL_MENU = "interfaces/radial_menu.png";
}
//these points to resource bundles, not raw asset files

View File

@@ -57,6 +57,8 @@ public class SPDAction extends GameAction {
public static final GameAction WAIT_OR_PICKUP = new SPDAction("wait_or_pickup");
public static final GameAction INVENTORY = new SPDAction("inventory");
public static final GameAction INVENTORY_SELECTOR = new SPDAction("inventory_selector");
public static final GameAction QUICKSLOT_SELECTOR = new SPDAction("quickslot_selector");
public static final GameAction QUICKSLOT_1 = new SPDAction("quickslot_1");
public static final GameAction QUICKSLOT_2 = new SPDAction("quickslot_2");
public static final GameAction QUICKSLOT_3 = new SPDAction("quickslot_3");
@@ -158,23 +160,20 @@ public class SPDAction extends GameAction {
defaultControllerBindings.put( Input.Keys.BUTTON_L2, SPDAction.RIGHT_CLICK );
defaultControllerBindings.put( Input.Keys.BUTTON_SELECT, SPDAction.MIDDLE_CLICK );
defaultControllerBindings.put( Input.Keys.DPAD_UP+1000, SPDAction.N );
defaultControllerBindings.put( Input.Keys.DPAD_LEFT+1000, SPDAction.W );
defaultControllerBindings.put( Input.Keys.DPAD_DOWN+1000, SPDAction.S );
defaultControllerBindings.put( Input.Keys.DPAD_RIGHT+1000, SPDAction.E );
defaultControllerBindings.put( Input.Keys.DPAD_UP+1000, SPDAction.TAG_ACTION );
defaultControllerBindings.put( Input.Keys.DPAD_LEFT+1000, SPDAction.TAG_DANGER );
defaultControllerBindings.put( Input.Keys.DPAD_DOWN+1000, SPDAction.TAG_RESUME );
defaultControllerBindings.put( Input.Keys.DPAD_RIGHT+1000, SPDAction.TAG_LOOT );
defaultControllerBindings.put( Input.Keys.BUTTON_THUMBL, SPDAction.WAIT_OR_PICKUP );
defaultControllerBindings.put( Input.Keys.BUTTON_R1, SPDAction.INVENTORY );
defaultControllerBindings.put( Input.Keys.BUTTON_R1, SPDAction.ZOOM_IN );
defaultControllerBindings.put( Input.Keys.BUTTON_L1, SPDAction.ZOOM_OUT );
defaultControllerBindings.put( Input.Keys.BUTTON_L1, SPDAction.EXAMINE );
//plan for new buttons: quickslots, quick inventory,
//Probably want to repurpose dpad too: attack, loot(or combine this?), special action, resume
defaultControllerBindings.put( Input.Keys.BUTTON_Y, SPDAction.QUICKSLOT_1 );
defaultControllerBindings.put( Input.Keys.BUTTON_B, SPDAction.QUICKSLOT_2 );
defaultControllerBindings.put( Input.Keys.BUTTON_X, SPDAction.QUICKSLOT_3 );
defaultControllerBindings.put( Input.Keys.BUTTON_A, SPDAction.QUICKSLOT_4 );
defaultControllerBindings.put( Input.Keys.BUTTON_A, SPDAction.TAG_ATTACK );
defaultControllerBindings.put( Input.Keys.BUTTON_B, SPDAction.EXAMINE );
defaultControllerBindings.put( Input.Keys.BUTTON_X, SPDAction.QUICKSLOT_SELECTOR );
defaultControllerBindings.put( Input.Keys.BUTTON_Y, SPDAction.INVENTORY_SELECTOR );
}
public static LinkedHashMap<Integer, GameAction> getControllerDefaults() {

View File

@@ -35,6 +35,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.Ring;
import com.shatteredpixel.shatteredpixeldungeon.items.scrolls.ScrollOfRemoveCurse;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.watabou.utils.Bundle;
import com.watabou.utils.Random;
@@ -46,6 +47,9 @@ public class Belongings implements Iterable<Item> {
private Hero owner;
public static class Backpack extends Bag {
{
image = ItemSpriteSheet.BACKPACK;
}
public int capacity(){
int cap = super.capacity();
for (Item item : items){

View File

@@ -108,6 +108,10 @@ public class CircleArc extends Visual {
this.sweep = sweep;
dirty = true;
}
public float getSweep(){
return sweep;
}
private void updateTriangles(){

View File

@@ -160,34 +160,34 @@ public class PixelScene extends Scene {
}
private static PointF virtualCursorPos;
@Override
public void update() {
super.update();
//20% deadzone
if (Math.abs(ControllerHandler.rightStickPosition.x) >= 0.2f
|| Math.abs(ControllerHandler.rightStickPosition.y) >= 0.2f) {
if (!ControllerHandler.controllerPointerActive()) {
ControllerHandler.setControllerPointer(true);
virtualCursorPos = PointerEvent.currentHoverPos();
if (!Cursor.isCursorCaptured()) {
if (Math.abs(ControllerHandler.rightStickPosition.x) >= 0.2f
|| Math.abs(ControllerHandler.rightStickPosition.y) >= 0.2f) {
if (!ControllerHandler.controllerPointerActive()) {
ControllerHandler.setControllerPointer(true);
}
int sensitivity = SPDSettings.controllerPointerSensitivity() * 100;
//cursor moves 100xsens scaled pixels per second at full speed
//35x at 50% movement, ~9x at 20% deadzone threshold
float xMove = (float) Math.pow(Math.abs(ControllerHandler.rightStickPosition.x), 1.5);
if (ControllerHandler.rightStickPosition.x < 0) xMove = -xMove;
float yMove = (float) Math.pow(Math.abs(ControllerHandler.rightStickPosition.y), 1.5);
if (ControllerHandler.rightStickPosition.y < 0) yMove = -yMove;
PointF virtualCursorPos = ControllerHandler.getControllerPointerPos();
virtualCursorPos.x += defaultZoom * sensitivity * Game.elapsed * xMove;
virtualCursorPos.y += defaultZoom * sensitivity * Game.elapsed * yMove;
virtualCursorPos.x = GameMath.gate(0, virtualCursorPos.x, Game.width);
virtualCursorPos.y = GameMath.gate(0, virtualCursorPos.y, Game.height);
ControllerHandler.updateControllerPointer(virtualCursorPos, true);
}
int sensitivity = SPDSettings.controllerPointerSensitivity() * 100;
//cursor moves 100xsens scaled pixels per second at full speed
//35x at 50% movement, ~9x at 20% deadzone threshold
float xMove = (float)Math.pow(Math.abs(ControllerHandler.rightStickPosition.x), 1.5);
if (ControllerHandler.rightStickPosition.x < 0) xMove = -xMove;
float yMove = (float)Math.pow(Math.abs(ControllerHandler.rightStickPosition.y), 1.5);
if (ControllerHandler.rightStickPosition.y < 0) yMove = -yMove;
virtualCursorPos.x += defaultZoom * sensitivity * Game.elapsed * xMove;
virtualCursorPos.y += defaultZoom * sensitivity * Game.elapsed * yMove;
virtualCursorPos.x = GameMath.gate(0, virtualCursorPos.x, Game.width);
virtualCursorPos.y = GameMath.gate(0, virtualCursorPos.y, Game.height);
PointerEvent.addPointerEvent(new PointerEvent((int) virtualCursorPos.x, (int) virtualCursorPos.y, 10_000, PointerEvent.Type.HOVER, PointerEvent.NONE));
}
}
@@ -203,6 +203,7 @@ public class PixelScene extends Scene {
cursor = new Image(Cursor.Type.CONTROLLER.file);
}
PointF virtualCursorPos = ControllerHandler.getControllerPointerPos();
cursor.x = (virtualCursorPos.x / defaultZoom) - cursor.width()/2f;
cursor.y = (virtualCursorPos.y / defaultZoom) - cursor.height()/2f;
cursor.camera = uiCamera;

View File

@@ -84,18 +84,8 @@ public class Button extends Component {
String text = hoverText();
if (text != null){
if (keyAction() != null){
ArrayList<Integer> bindings = KeyBindings.getBoundKeysForAction(keyAction());
if (!bindings.isEmpty()){
int key = bindings.get(0);
//prefer controller buttons if we are using a controller
if (ControllerHandler.controllerPointerActive()){
for (int code : bindings){
if (ControllerHandler.icControllerKey(code)){
key = code;
break;
}
}
}
int key = KeyBindings.getFirstKeyForAction(keyAction(), ControllerHandler.controllerPointerActive());
if (key != 0){
text += " _(" + KeyBindings.getKeyName(key) + ")_";
}
}

View File

@@ -49,7 +49,7 @@ public class QuickSlotButton extends Button {
private Image crossB;
private Image crossM;
private static boolean targeting = false;
public static int targetingSlot = -1;
public static Char lastTarget = null;
public QuickSlotButton( int slotNum ) {
@@ -83,7 +83,7 @@ public class QuickSlotButton extends Button {
if (!Dungeon.hero.isAlive() || !Dungeon.hero.ready){
return;
}
if (targeting) {
if (targetingSlot == slotNum) {
int cell = autoAim(lastTarget, select(slotNum));
if (cell != -1){
@@ -160,7 +160,7 @@ public class QuickSlotButton extends Button {
@Override
public void update() {
super.update();
if (targeting && lastTarget != null && lastTarget.sprite != null){
if (targetingSlot != -1 && lastTarget != null && lastTarget.sprite != null){
crossM.point(lastTarget.sprite.center(crossM));
}
}
@@ -270,7 +270,7 @@ public class QuickSlotButton extends Button {
lastTarget.alignment != Char.Alignment.ALLY &&
Dungeon.level.heroFOV[lastTarget.pos]) {
targeting = true;
targetingSlot = slotNum;
CharSprite sprite = lastTarget.sprite;
if (sprite.parent != null) {
@@ -284,7 +284,7 @@ public class QuickSlotButton extends Button {
} else {
lastTarget = null;
targeting = false;
targetingSlot = -1;
}
@@ -337,11 +337,11 @@ public class QuickSlotButton extends Button {
}
public static void cancel() {
if (targeting) {
if (targetingSlot != -1) {
for (QuickSlotButton btn : instance) {
btn.crossB.visible = false;
btn.crossM.remove();
targeting = false;
targetingSlot = -1;
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2022 Evan Debenham
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package com.shatteredpixel.shatteredpixeldungeon.ui;
import com.shatteredpixel.shatteredpixeldungeon.Assets;
import com.shatteredpixel.shatteredpixeldungeon.Chrome;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.effects.CircleArc;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.watabou.input.ControllerHandler;
import com.watabou.noosa.ColorBlock;
import com.watabou.noosa.Image;
import com.watabou.noosa.ui.Cursor;
import com.watabou.utils.PointF;
public class RadialMenu extends Window {
int slots;
float targetAngle;
PointF center;
CircleArc selectionArc;
RenderedTextBlock titleTxt;
RenderedTextBlock descTxt;
String[] texts;
Image[] icons;
int selectedIdx = -1;
public RadialMenu(String title, String desc, String[] optionTexts, Image[] optionIcons){
super(0, 0, Chrome.get(Chrome.Type.BLANK));
remove(shadow);
int size = SPDSettings.interfaceSize() == 0 ? 140 : 200;
resize(size, size);
slots = optionTexts.length;
center = new PointF(size/2, size/2);
int length = SPDSettings.interfaceSize() == 0 ? 57 : 80;
selectionArc = new CircleArc(120/slots, size/2 - 1);
selectionArc.color(0xFFFFFF, false);
selectionArc.alpha(0.6f);
selectionArc.setSweep(1f/slots);
selectionArc.point(center);
selectionArc.visible = false;
add(selectionArc);
Image outerBG = getBGTexture(size, false);
add(outerBG);
texts = optionTexts;
icons = optionIcons;
for (int i = 0; i < slots; i++){
PointF iconCenter = new PointF().polar((PointF.PI2/slots * i)-PointF.PI/2, length);
iconCenter.offset(center);
optionIcons[i].x = iconCenter.x - optionIcons[i].width()/2f;
optionIcons[i].y = iconCenter.y - optionIcons[i].height()/2f;
PixelScene.align(optionIcons[i]);
optionIcons[i].alpha(0.4f);
add(optionIcons[i]);
ColorBlock sep = new ColorBlock(size/2 - 2, 1, 0xFF000000);
sep.x = center.x;
sep.y = center.y;
sep.angle = (360f/slots * i) + selectionArc.getSweep()*180 - 90;
addToFront(sep);
}
Image innerBG = getBGTexture(size, true);
innerBG.x = (width - innerBG.width) / 2;
innerBG.y = (height - innerBG.height) / 2;
add(innerBG);
descTxt = PixelScene.renderTextBlock(desc, 6);
descTxt.align(RenderedTextBlock.CENTER_ALIGN);
descTxt.maxWidth(SPDSettings.interfaceSize() == 0 ? 80 : 100);
descTxt.setPos(center.x - descTxt.width()/2, center.y - descTxt.height()/4);
add(descTxt);
titleTxt = PixelScene.renderTextBlock(title, 9);
titleTxt.setPos(center.x - titleTxt.width()/2f, descTxt.top() - titleTxt.height() - 6);
PixelScene.align(titleTxt);
titleTxt.hardlight(TITLE_COLOR);
add(titleTxt);
Cursor.captureCursor(true);
Button selector = new Button(){
@Override
protected void onClick() {
super.onClick();
if (selectedIdx != -1){
hide();
onSelect(selectedIdx, false);
}
}
@Override
protected boolean onLongClick() {
if (selectedIdx != -1){
hide();
onSelect(selectedIdx, true);
}
return true;
}
@Override
protected void onRightClick() {
super.onRightClick();
if (selectedIdx != -1){
hide();
onSelect(selectedIdx, true);
}
}
};
selector.setRect(0, 0, size, size);
add(selector);
}
public void onSelect(int idx, boolean alt){}
@Override
public void destroy() {
super.destroy();
Cursor.captureCursor(false);
}
//the mouse has a hidden position in a 20-pixel radius circle, helps make selection feel more natural
private PointF mousePos = new PointF();
private boolean first = true; //ignore the first mouse input as it's caused by hiding mouse
@Override
public synchronized void update() {
super.update();
PointF movement = Cursor.getCursorDelta();
//40% deadzone for controller input here
if (Math.abs(ControllerHandler.rightStickPosition.x) >= 0.4f
|| Math.abs(ControllerHandler.rightStickPosition.y) >= 0.4f){
movement.x = ControllerHandler.rightStickPosition.x;
movement.y = ControllerHandler.rightStickPosition.y;
first = false;
} else if (movement.length() != 0 && !first) {
mousePos.offset(movement);
if (mousePos.length() > PixelScene.defaultZoom*20) {
mousePos.invScale(mousePos.length() / (PixelScene.defaultZoom*20));
}
movement = mousePos;
}
if (movement.length() != 0) {
if (first){
first = false;
return;
}
targetAngle = PointF.angle(movement.x, movement.y) / PointF.G2R + 90;
if (targetAngle < 0) targetAngle += 360f;
selectionArc.visible = true;
selectionArc.angle = targetAngle + selectionArc.getSweep()*180;
int newSelect = (int) Math.round((targetAngle) / (360f/slots));
if (newSelect >= slots) newSelect = 0;
if (newSelect != selectedIdx) {
selectedIdx = newSelect;
for (int i = 0; i < slots; i++) {
if (i == selectedIdx) {
icons[i].alpha(1f);
titleTxt.text(texts[i]);
titleTxt.setPos(center.x - titleTxt.width()/2f, descTxt.top() - titleTxt.height() - 6);
PixelScene.align(titleTxt);
} else {
icons[i].alpha(0.4f);
}
}
}
}
}
private static Image getBGTexture(int size, boolean inner){
if (size >= 200){
if (!inner) return new Image(Assets.Interfaces.RADIAL_MENU, 0, 0, 200, 200);
else return new Image(Assets.Interfaces.RADIAL_MENU, 340, 0, 120, 120);
} else {
if (!inner) return new Image(Assets.Interfaces.RADIAL_MENU, 200, 0, 140, 140);
else return new Image(Assets.Interfaces.RADIAL_MENU, 340, 120, 90, 90);
}
}
}

View File

@@ -26,17 +26,25 @@ import com.shatteredpixel.shatteredpixeldungeon.Dungeon;
import com.shatteredpixel.shatteredpixeldungeon.QuickSlot;
import com.shatteredpixel.shatteredpixeldungeon.SPDAction;
import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
import com.shatteredpixel.shatteredpixeldungeon.actors.buffs.LostInventory;
import com.shatteredpixel.shatteredpixeldungeon.actors.hero.Belongings;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.bags.Bag;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.CellSelector;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.tiles.DungeonTerrainTilemap;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndBag;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndKeyBindings;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndMessage;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndQuickBag;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndUseItem;
import com.watabou.input.ControllerHandler;
import com.watabou.input.GameAction;
import com.watabou.input.KeyBindings;
import com.watabou.noosa.Camera;
import com.watabou.noosa.Game;
import com.watabou.noosa.Gizmo;
@@ -46,6 +54,8 @@ import com.watabou.noosa.ui.Component;
import com.watabou.utils.Point;
import com.watabou.utils.PointF;
import java.util.ArrayList;
public class Toolbar extends Component {
private Tool btnWait;
@@ -90,6 +100,99 @@ public class Toolbar extends Component {
for (int i = 0; i < btnQuick.length; i++){
add( btnQuick[i] = new QuickslotTool(64, 0, 22, 24, i) );
}
//hidden button for quickslot selector keybind
add(new Button(){
@Override
protected void onClick() {
if (QuickSlotButton.targetingSlot != -1){
int cell = QuickSlotButton.autoAim(QuickSlotButton.lastTarget, Dungeon.quickslot.getItem(QuickSlotButton.targetingSlot));
if (cell != -1){
GameScene.handleCell(cell);
} else {
//couldn't auto-aim, just target the position and hope for the best.
GameScene.handleCell( QuickSlotButton.lastTarget.pos );
}
return;
}
if (Dungeon.hero.ready && !GameScene.cancel()) {
String[] slotNames = new String[6];
Image[] slotIcons = new Image[6];
for (int i = 0; i < 6; i++){
Item item = Dungeon.quickslot.getItem(i);
if (item != null && !Dungeon.quickslot.isPlaceholder(i) &&
(Dungeon.hero.buff(LostInventory.class) == null || item.keptThoughLostInvent)){
slotNames[i] = Messages.titleCase(item.name());
slotIcons[i] = new ItemSprite(item);
} else {
slotNames[i] = Messages.get(Toolbar.class, "quickslot_assign");
slotIcons[i] = new ItemSprite(ItemSpriteSheet.SOMETHING);
}
}
String info = "";
if (ControllerHandler.controllerPointerActive()){
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.LEFT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "quickslot_select") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.RIGHT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "quickslot_assign") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, true)) + ": " + Messages.get(Toolbar.class, "quickslot_cancel");
} else {
info += Messages.get(WndKeyBindings.class, SPDAction.LEFT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "quickslot_select") + "\n";
info += Messages.get(WndKeyBindings.class, SPDAction.RIGHT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "quickslot_assign") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "quickslot_cancel");
}
GameScene.show(new RadialMenu(Messages.get(Toolbar.class, "quickslot_prompt"), info, slotNames, slotIcons) {
@Override
public void onSelect(int idx, boolean alt) {
Item item = Dungeon.quickslot.getItem(idx);
if (item == null || Dungeon.quickslot.isPlaceholder(idx)
|| (Dungeon.hero.buff(LostInventory.class) != null && !item.keptThoughLostInvent)
|| alt){
//TODO would be nice to use a radial menu for this too
// Also a bunch of code could be moved out of here into subclasses of RadialMenu
GameScene.selectItem(new WndBag.ItemSelector() {
@Override
public String textPrompt() {
return Messages.get(QuickSlotButton.class, "select_item");
}
@Override
public boolean itemSelectable(Item item) {
return item.defaultAction != null;
}
@Override
public void onSelect(Item item) {
if (item != null) {
Dungeon.quickslot.setSlot( idx , item );
QuickSlotButton.refresh();
}
}
});
} else {
item.execute(Dungeon.hero);
if (item.usesTargeting) {
QuickSlotButton.useTargeting(idx);
}
}
super.onSelect(idx, alt);
}
});
}
}
@Override
public GameAction keyAction() {
if (btnWait.active) return SPDAction.QUICKSLOT_SELECTOR;
else return null;
}
});
add(btnWait = new Tool(24, 0, 20, 26) {
@Override
@@ -119,6 +222,7 @@ public class Toolbar extends Component {
}
});
//hidden button for rest keybind
add(new Button(){
@Override
protected void onClick() {
@@ -135,6 +239,7 @@ public class Toolbar extends Component {
}
});
//hidden button for wait / pickup keybind
add(new Button(){
@Override
protected void onClick() {
@@ -253,6 +358,98 @@ public class Toolbar extends Component {
}
});
//hidden button for inventory selector keybind
add(new Button(){
@Override
protected void onClick() {
if (Dungeon.hero.ready && !GameScene.cancel()) {
ArrayList<Bag> bags = Dungeon.hero.belongings.getBags();
String[] names = new String[bags.size()];
Image[] images = new Image[bags.size()];
for (int i = 0; i < bags.size(); i++){
names[i] = Messages.titleCase(bags.get(i).name());
images[i] = new ItemSprite(bags.get(i));
}
String info = "";
if (ControllerHandler.controllerPointerActive()){
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.LEFT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "container_select") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, true)) + ": " + Messages.get(Toolbar.class, "container_cancel");
} else {
info += Messages.get(WndKeyBindings.class, SPDAction.LEFT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "container_select") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "container_cancel");
}
GameScene.show(new RadialMenu(Messages.get(Toolbar.class, "container_prompt"), info, names, images){
@Override
public void onSelect(int idx, boolean alt) {
super.onSelect(idx, alt);
Bag bag = bags.get(idx);
ArrayList<Item> items = (ArrayList<Item>) bag.items.clone();
for(Item i : bag.items){
if (i instanceof Bag) items.remove(i);
if (Dungeon.hero.buff(LostInventory.class) != null && !i.keptThoughLostInvent) items.remove(i);
}
if (idx == 0){
Belongings b = Dungeon.hero.belongings;
if (b.ring() != null) items.add(0, b.ring());
if (b.misc() != null) items.add(0, b.misc());
if (b.artifact() != null) items.add(0, b.artifact());
if (b.armor() != null) items.add(0, b.armor());
if (b.weapon() != null) items.add(0, b.weapon());
}
if (items.size() == 0){
GameScene.show(new WndMessage(Messages.get(Toolbar.class, "container_empty")));
return;
}
String[] itemNames = new String[items.size()];
Image[] itemIcons = new Image[items.size()];
for (int i = 0; i < items.size(); i++){
itemNames[i] = Messages.titleCase(items.get(i).name());
itemIcons[i] = new ItemSprite(items.get(i));
}
String info = "";
if (ControllerHandler.controllerPointerActive()){
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.LEFT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "item_select") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.RIGHT_CLICK, true)) + ": " + Messages.get(Toolbar.class, "item_use") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "item_cancel");
} else {
info += Messages.get(WndKeyBindings.class, SPDAction.LEFT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "item_select") + "\n";
info += Messages.get(WndKeyBindings.class, SPDAction.RIGHT_CLICK.name()) + ": " + Messages.get(Toolbar.class, "item_use") + "\n";
info += KeyBindings.getKeyName(KeyBindings.getFirstKeyForAction(GameAction.BACK, false)) + ": " + Messages.get(Toolbar.class, "item_cancel");
}
GameScene.show(new RadialMenu(Messages.get(Toolbar.class, "item_prompt"), info, itemNames, itemIcons){
@Override
public void onSelect(int idx, boolean alt) {
super.onSelect(idx, alt);
Item item = items.get(idx);
if (alt && item.defaultAction != null) {
item.execute(Dungeon.hero);
if (item.usesTargeting) {
QuickSlotButton.useTargeting(idx);
}
} else {
Game.scene().addToFront(new WndUseItem(null, item));
}
}
});
}
});
}
}
@Override
public GameAction keyAction() {
if (btnWait.active) return SPDAction.INVENTORY_SELECTOR;
else return null;
}
});
add(pickedUp = new PickedUpItem());
}

View File

@@ -84,16 +84,43 @@ public class v1_X_Changes {
changes.hardlight(Window.TITLE_COLOR);
changeInfos.add(changes);
changes = new ChangeInfo("", false, null);
changes = new ChangeInfo("BETA-3", false, null);
changes.hardlight(Window.TITLE_COLOR);
changeInfos.add(changes);
changes.addButton( new ChangeButton(Icons.get(Icons.SHPX), "To-Do",
"I'm going to round the beta out and try to get it released early/mid next week. Because of that some of these things may be handled in patches:\n\n" +
"_-_ Additional enemy info on the top-left of the screen on full UI mode\n" +
"_-_ Improvements to some of the game's interfaces on full UI mode\n" +
"_-_ Translation work, and updated translators credits.\n" +
"_-_ Fixes for any bugs that get reported"));
changes.addButton( new ChangeButton(Icons.get(Icons.CONTROLLER), "UI/UX Improvements",
"A new radial menu system has been added, primarily for controller users! This should make using quickslots and the inventory much faster.\n\n" +
"These new menus also free up a bunch of buttons, so default controller button mappings have been reworked.\n\n" +
"A few other key binding actions have been adjusted as well, including a combo 'wait/loot item' action, and scrolling up/down using the zoom actions."));
changes.addButton(new ChangeButton(Icons.get(Icons.PREFS), Messages.get(ChangesScene.class, "misc"),
"_-_ Levels are now cleared during ascension just before the hero returns to them. This prevents difficulty spikes if floors were left full of enemies. Ascension enemy spawn rates slightly increased to compensate\n\n" +
"_-_ Nearby enemies are no longer constantly drawn to the hero's position during the ascension challenge"));
changes.addButton(new ChangeButton(new Image(Assets.Sprites.SPINNER, 144, 0, 16, 16), Messages.get(ChangesScene.class, "bugfixes"),
"Fixed (Caused by BETA)\n" +
"_-_ Various rare crash bugs\n" +
"_-_ Various minor visual and textual bugs\n" +
"_-_ Daily run history being lost in some cases\n" +
"_-_ Higher tier 'score chaser' badges not unlocking lower tier ones\n" +
"_-_ 'Friendly Fire' badge not being awarded by wand of corrosion\n\n" +
"Fixed (Existed Prior to BETA)\n" +
"_-_ Beacon of returning not working at all in boss arenas\n" +
"_-_ Various 'cause of death' badges not being awarded if death occurred with an ankh."));
changes = new ChangeInfo("BETA-2", false, null);
changes.hardlight(Window.TITLE_COLOR);
changeInfos.add(changes);
changes.addButton(new ChangeButton(new Image(Assets.Sprites.SPINNER, 144, 0, 16, 16), Messages.get(ChangesScene.class, "bugfixes"),
"Fixed (Caused by Beta)\n" +
"Fixed (Caused by BETA)\n" +
"_-_ Various rare crash issues\n" +
"_-_ Daily runs preventing regular runs from being started\n" +
"_-_ Duplicate crystal keys from pit rooms\n" +
@@ -108,15 +135,6 @@ public class v1_X_Changes {
"_-_ XX days after Shattered v1.2.0\n" +
"Expect dev commentary here in the future."));*/
changes.addButton( new ChangeButton(Icons.get(Icons.SHPX), "To-Do",
"There are still a few bits and pieces left to finish up before v1.3 is released, mainly focused on interface work:\n\n" +
"_-_ A new radial selection menu to make using quickslots and the inventory easier on controller\n" +
"_-_ Improvements to default controller bindings, and some improved button functionality\n" +
"_-_ Additional enemy info on the top-left of the screen on full UI mode\n" +
"_-_ Improvements to some of the game's interfaces on full UI mode\n" +
"_-_ Translation work, from volunteers on the game's Transifex project.\n" +
"_-_ Fixes for any bugs that get reported"));
Image ic = Icons.get(Icons.SEED);
ic.hardlight(1f, 1.5f, 0.67f);
changes.addButton( new ChangeButton(ic, "Seeded Runs!",

View File

@@ -134,19 +134,28 @@ public class WndKeyBindings extends Window {
bindingsList.add(sep);
}
for (GameAction action : GameAction.allActions()){
LinkedHashMap<Integer, GameAction> defaults = controller ? SPDAction.getControllerDefaults() : SPDAction.getDefaults();
ArrayList<GameAction> actionList = GameAction.allActions();
for (GameAction action : actionList.toArray(new GameAction[0])) {
//start at 1. No bindings for NONE
if (action.code() < 1) continue;
if (action.code() < 1) {
actionList.remove(action);
//mouse bindings are only available to controllers
if ((action == GameAction.LEFT_CLICK
} else if ((action == GameAction.LEFT_CLICK
|| action == GameAction.RIGHT_CLICK
|| action == GameAction.MIDDLE_CLICK) && !controller){
continue;
|| action == GameAction.MIDDLE_CLICK) && !controller) {
actionList.remove(action);
//actions with no default binding are moved to the end of the list
} else if (!defaults.containsValue(action)){
actionList.remove(action);
actionList.add(action);
}
}
//TODO probably exclude some binding for controllers, and adjust default mappings
for (GameAction action : actionList){
BindingItem item = new BindingItem(action);
item.setRect(0, y, WIDTH, BindingItem.HEIGHT);
bindingsList.addToBack(item);