v2.5.0: added functionality for custom note! Only plain text notes atm

This commit is contained in:
Evan Debenham
2024-07-02 13:05:38 -04:00
parent e8ea9da26e
commit 8e99b51cf9
5 changed files with 366 additions and 23 deletions

View File

@@ -1,3 +1,13 @@
ui.customnotebutton.hover_text=Add a Custom Note
ui.customnotebutton.default_title_text=Custom Text Note
ui.customnotebutton$customnotewindow.edit_title=Edit Title
ui.customnotebutton$customnotewindow.add_text=Add Text
ui.customnotebutton$customnotewindow.edit_text=Edit Text
ui.customnotebutton$customnotewindow.delete=Delete
ui.customnotebutton$customnotewindow.delete_warn=Are you sure you want to delete this custom note?
ui.customnotebutton$customnotewindow.confirm=Confirm
ui.customnotebutton$customnotewindow.cancel=Cancel
ui.quickslotbutton.select_item=Quickslot an item
ui.talentspane.tier=tier %d

View File

@@ -117,6 +117,7 @@ windows.wndjournal$alchemytab.title=Alchemy Guide
windows.wndjournal$guidetab.missing=page missing
windows.wndjournal$notestab.title=Adventuring Notes
windows.wndjournal$notestab.desc=As you journey through the dungeon, you will automatically record noteworthy things here.
windows.wndjournal$notestab.custom_notes=Custom Notes
windows.wndjournal$notestab.floor_header=Floor %d
windows.wndjournal$catalogtab.title=Catalogs
windows.wndjournal$catalogtab.title_equipment=Equipment

View File

@@ -36,6 +36,7 @@ import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.RatKing;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Shopkeeper;
import com.shatteredpixel.shatteredpixeldungeon.actors.mobs.npcs.Wandmaker;
import com.shatteredpixel.shatteredpixeldungeon.items.Generator;
import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.keys.Key;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.special.WeakFloorRoom;
@@ -77,6 +78,8 @@ public class Notes {
public Visual secondIcon() { return null; }
public int quantity() { return 1; }
protected abstract int order();
public abstract String title();
@@ -243,6 +246,11 @@ public class Notes {
}
}
@Override
protected int order(){
return landmark.ordinal();
}
@Override
public boolean equals(Object obj) {
return (obj instanceof LandmarkRecord)
@@ -310,6 +318,11 @@ public class Notes {
return key.getClass();
}
@Override
protected int order() {
return 1000 + Generator.Category.order(key);
}
public int quantity(){
return key.quantity();
}
@@ -338,6 +351,128 @@ public class Notes {
bundle.put( KEY, key );
}
}
public enum CustomType {
TEXT,
DEPTH, //TODO
ITEM, //TODO
ITEM_TYPE //TODO
}
public static class CustomRecord extends Record {
protected CustomType type;
protected int ID;
protected Class itemClass;
protected String title;
protected String body;
public CustomRecord() {}
public CustomRecord(String title, String desc) {
type = CustomType.TEXT;
this.title = title;
body = desc;
}
public CustomRecord(int depth, String title, String desc) {
type = CustomType.DEPTH;
this.depth = depth;
this.title = title;
body = desc;
}
public CustomRecord(Item item, String title, String desc) {
type = CustomType.ITEM;
itemClass = item.getClass();
this.title = title;
body = desc;
}
@Override
public int depth() {
if (type == CustomType.DEPTH){
return depth;
} else {
return 0;
}
}
@Override
public Image icon() {
switch (type){
case TEXT: default:
return Icons.SCROLL_COLOR.get();
case DEPTH:
return Icons.STAIRS.get();
}
}
@Override
public Visual secondIcon() {
switch (type){
case TEXT: default:
//TODO perhaps use first few chars from title?
return null;
case DEPTH:
BitmapText text = new BitmapText(Integer.toString(depth()), PixelScene.pixelFont);
text.measure();
return text;
}
}
@Override
protected int order() {
return 2000 + ID;
}
public void editText(String title, String desc){
this.title = title;
this.body = desc;
}
@Override
public String title() {
return title;
}
@Override
public String desc() {
return body;
}
@Override
public boolean equals(Object obj) {
return obj instanceof CustomRecord && ((CustomRecord) obj).ID == ID;
}
private static final String TYPE = "type";
private static final String ID_NUMBER = "id_number";
private static final String TITLE = "title";
private static final String BODY = "body";
@Override
public void storeInBundle(Bundle bundle) {
super.storeInBundle(bundle);
bundle.put(TYPE, type);
bundle.put(ID_NUMBER, ID);
bundle.put(TITLE, title);
bundle.put(BODY, body);
}
@Override
public void restoreFromBundle(Bundle bundle) {
super.restoreFromBundle(bundle);
type = bundle.getEnum(TYPE, CustomType.class);
ID = bundle.getInt(ID_NUMBER);
title = bundle.getString(TITLE);
body = bundle.getString(BODY);
}
}
private static ArrayList<Record> records;
@@ -345,14 +480,19 @@ public class Notes {
records = new ArrayList<>();
}
private static final String RECORDS = "records";
private static final String RECORDS = "records";
private static final String NEXT_CUSTOM_ID = "next_custom_id";
protected static int nextCustomID = 0;
public static void storeInBundle( Bundle bundle ) {
bundle.put( RECORDS, records );
bundle.put( NEXT_CUSTOM_ID, nextCustomID );
}
public static void restoreFromBundle( Bundle bundle ) {
records = new ArrayList<>();
nextCustomID = bundle.getInt( NEXT_CUSTOM_ID );
for (Bundlable rec : bundle.getCollection( RECORDS ) ) {
records.add( (Record) rec );
}
@@ -362,7 +502,7 @@ public class Notes {
LandmarkRecord l = new LandmarkRecord( landmark, Dungeon.depth );
if (!records.contains(l)) {
boolean result = records.add(new LandmarkRecord(landmark, Dungeon.depth));
Collections.sort(records);
Collections.sort(records, comparator);
return result;
}
return false;
@@ -380,7 +520,7 @@ public class Notes {
KeyRecord k = new KeyRecord(key);
if (!records.contains(k)){
boolean result = records.add(k);
Collections.sort(records);
Collections.sort(records, comparator);
return result;
} else {
k = (KeyRecord) records.get(records.indexOf(k));
@@ -412,9 +552,23 @@ public class Notes {
return 0;
}
}
public static ArrayList<Record> getRecords(){
return getRecords(Record.class);
public static boolean add( CustomRecord rec ){
rec.ID = nextCustomID++;
if (!records.contains(rec)){
boolean result = records.add(rec);
Collections.sort(records, comparator);
return result;
}
return false;
}
public static boolean remove( CustomRecord rec ){
if (records.contains(rec)){
records.remove(rec);
return true;
}
return false;
}
public static <T extends Record> ArrayList<T> getRecords( Class<T> recordType ){
@@ -443,23 +597,8 @@ public class Notes {
private static final Comparator<Record> comparator = new Comparator<Record>() {
@Override
public int compare(Record r1, Record r2) {
if (r1 instanceof LandmarkRecord){
if (r2 instanceof LandmarkRecord){
return ((LandmarkRecord) r1).landmark.ordinal() - ((LandmarkRecord) r2).landmark.ordinal();
} else {
return -1;
}
} else if (r2 instanceof LandmarkRecord){
return 1;
} else {
//matches order in key display
return Generator.Category.order(((KeyRecord)r2).key) - Generator.Category.order(((KeyRecord)r1).key);
}
return r1.order() - r2.order();
}
};
public static void remove( Record rec ){
records.remove(rec);
}
}

View File

@@ -0,0 +1,160 @@
/*
* Pixel Dungeon
* Copyright (C) 2012-2015 Oleg Dolya
*
* Shattered Pixel Dungeon
* Copyright (C) 2014-2024 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.ShatteredPixelDungeon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Notes;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.scenes.GameScene;
import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndJournalItem;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndOptions;
import com.shatteredpixel.shatteredpixeldungeon.windows.WndTextInput;
import com.watabou.noosa.Game;
import com.watabou.utils.Callback;
//this is contained in its own class as custom notes have a lot of messy window UI logic
public class CustomNoteButton extends IconButton {
public CustomNoteButton () {
super(Icons.PLUS.get());
width = 11;
height = 11;
}
@Override
protected void onClick() {
super.onClick();
//TODO we want a selection window for note type here, atm it's only plaintext notes
Notes.CustomRecord custom = new Notes.CustomRecord(Messages.get(this, "default_title_text"), "");
Notes.add(custom);
refreshScene(custom);
}
@Override
protected String hoverText() {
return Messages.get(this, "hover_text");
}
public static class CustomNoteWindow extends WndJournalItem {
public CustomNoteWindow(Notes.CustomRecord rec) {
super(rec.icon(), rec.title(), rec.desc());
RedButton title = new RedButton( Messages.get(CustomNoteWindow.class, "edit_title") ){
@Override
protected void onClick() {
GameScene.show(new WndTextInput(Messages.get(CustomNoteWindow.class, "edit_title"),
"",
rec.title(),
50,
false,
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
public void onSelect(boolean positive, String text) {
if (positive){
rec.editText(text, rec.desc());
refreshScene(rec);
}
}
});
}
};
add(title);
title.setRect(0, Math.min(height+2, PixelScene.uiCamera.height-50), width/2-1, 16);
String editBodyText = rec.desc().isEmpty() ? Messages.get(CustomNoteWindow.class, "add_text") : Messages.get(CustomNoteWindow.class, "edit_text");
RedButton body = new RedButton(editBodyText){
@Override
protected void onClick() {
GameScene.show(new WndTextInput(editBodyText,
"",
rec.desc(),
500,
true,
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
public void onSelect(boolean positive, String text) {
if (positive){
rec.editText(rec.title(), text);
refreshScene(rec);
}
}
});
}
};
add(body);
body.setRect(title.right()+2, title.top(), width/2-1, 16);
RedButton delete = new RedButton( Messages.get(CustomNoteWindow.class, "delete") ){
@Override
protected void onClick() {
GameScene.show(new WndOptions(Icons.WARNING.get(),
Messages.get(CustomNoteWindow.class, "delete"),
Messages.get(CustomNoteWindow.class, "delete_warn"),
Messages.get(CustomNoteWindow.class, "confirm"),
Messages.get(CustomNoteWindow.class, "cancel")){
@Override
protected void onSelect(int index) {
if (index == 0){
Notes.remove(rec);
refreshScene(null);
}
}
});
}
};
add(delete);
delete.setRect(0, title.bottom()+1, width, 16);
resize(width, (int)delete.bottom());
}
}
private static void refreshScene(Notes.CustomRecord recToShow){
if (recToShow == null){
ShatteredPixelDungeon.seamlessResetScene();
} else {
ShatteredPixelDungeon.seamlessResetScene(new Game.SceneChangeCallback() {
@Override
public void beforeCreate() {
}
@Override
public void afterCreate() {
Game.runOnRenderThread(new Callback() {
@Override
public void call() {
ShatteredPixelDungeon.scene().addToFront(new CustomNoteWindow(recToShow));
}
});
}
});
}
}
}

View File

@@ -53,6 +53,7 @@ import com.shatteredpixel.shatteredpixeldungeon.sprites.CharSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSprite;
import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
import com.shatteredpixel.shatteredpixeldungeon.tiles.TerrainFeaturesTilemap;
import com.shatteredpixel.shatteredpixeldungeon.ui.CustomNoteButton;
import com.shatteredpixel.shatteredpixeldungeon.ui.Icons;
import com.shatteredpixel.shatteredpixeldungeon.ui.QuickRecipe;
import com.shatteredpixel.shatteredpixeldungeon.ui.RedButton;
@@ -424,6 +425,7 @@ public class WndJournal extends WndTabbed {
private static class NotesTab extends Component {
private ScrollingGridPane grid;
private CustomNoteButton custom;
@Override
protected void createChildren() {
@@ -443,6 +445,33 @@ public class WndJournal extends WndTabbed {
grid.addHeader(Messages.get(this, "desc"), 6, true);
ArrayList<Notes.CustomRecord> customRecs = Notes.getRecords(Notes.CustomRecord.class);
if (!customRecs.isEmpty()){
grid.addHeader("_" + Messages.get(this, "custom_notes") + "_");
for (Notes.CustomRecord rec : customRecs){
ScrollingGridPane.GridItem gridItem = new ScrollingGridPane.GridItem(rec.icon()){
@Override
public boolean onClick(float x, float y) {
if (inside(x, y)) {
GameScene.show(new CustomNoteButton.CustomNoteWindow(rec));
return true;
} else {
return false;
}
}
};
Visual secondIcon = rec.secondIcon();
if (secondIcon != null){
gridItem.addSecondIcon( secondIcon );
}
grid.addItem(gridItem);
}
}
for (int i = Statistics.deepestFloor; i > 0; i--){
ArrayList<Notes.Record> recs = Notes.getRecords(i);
@@ -473,6 +502,10 @@ public class WndJournal extends WndTabbed {
}
}
custom = new CustomNoteButton();
grid.content().add(custom);
custom.setPos(width-custom.width()-1, 0);
grid.setRect(x, y, width, height);
}