diff --git a/core/src/main/assets/messages/items/items.properties b/core/src/main/assets/messages/items/items.properties
index e8ae8f711..12f38a96b 100644
--- a/core/src/main/assets/messages/items/items.properties
+++ b/core/src/main/assets/messages/items/items.properties
@@ -1291,6 +1291,15 @@ items.stones.stoneofshock.desc=This runestone unleashes a blast of electrical en
###trinkets
+items.trinkets.exoticcrystal.name=exotic crystal
+items.trinkets.exoticcrystal.desc=...\n\nAt its current level it will replace %d%% of potion or scroll drops with their exotic equivalents. This trinket does not affect potions of strength or scrolls of upgrade.
+
+items.trinkets.parchmentscrap.name=parchment scrap
+items.trinkets.parchmentscrap.desc=This little scrap of parchment looks like it came from a scroll. It has regained some of its magic, and it seems to be influencing weapons and armor found in the dungeon.\n\nAt its current level it will make enchantments/glyphs _%dx_ as common, and curses on weapons and armor _%sx_ as common. Curses on wands, rings, or artifacts are not affected.
+
+items.trinkets.petrifiedseed.name=petrified seed
+items.trinkets.petrifiedseed.desc=This seed has been fossilised, either by slow geological processes or by magic. The seed seems to be magically influencing the flora of the dungeon, occasionally replacing plant seeds with runestones.\n\nAt its current level it will cause trampled grass to drop runestones instead of seeds _%1$d%%_ of the time, and will also cause high grass to drop items _%2$d%%_ more often.
+
items.trinkets.ratskull.name=rat skull
items.trinkets.ratskull.desc=This macabre trinket isn't much larger than the skull of a normal rat, which is somehow a rarity down in this dungeon. The skull's magical influence seems to attract the more rare denizens of the dungeon, making them appear far more often.\n\nAt its current level it will make rare exotic enemies _%dx_ as likely to appear. The skull is only half as effective at attracting crystal mimics and armored statues, however.
diff --git a/core/src/main/assets/sprites/items.png b/core/src/main/assets/sprites/items.png
index da78b405f..ecf540216 100644
Binary files a/core/src/main/assets/sprites/items.png and b/core/src/main/assets/sprites/items.png differ
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Blacksmith.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Blacksmith.java
index e4d98b1b6..bc91c2665 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Blacksmith.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Blacksmith.java
@@ -31,6 +31,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.Item;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.Armor;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.DarkGold;
import com.shatteredpixel.shatteredpixeldungeon.items.quest.Pickaxe;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Notes;
import com.shatteredpixel.shatteredpixeldungeon.levels.rooms.Room;
@@ -466,7 +467,7 @@ public class Blacksmith extends NPC {
// 30% base chance to be enchanted, stored separately so status isn't revealed early
float enchantRoll = Random.Float();
- if (enchantRoll <= 0.3f){
+ if (enchantRoll <= 0.3f * ParchmentScrap.enchantChanceMultiplier()){
smithEnchant = Weapon.Enchantment.random();
smithGlyph = Armor.Glyph.random();
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java
index 68d22f4c7..db70bdaa4 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/actors/mobs/npcs/Ghost.java
@@ -37,6 +37,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.LeatherArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.MailArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.PlateArmor;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.ScaleArmor;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.Weapon;
import com.shatteredpixel.shatteredpixeldungeon.journal.Notes;
import com.shatteredpixel.shatteredpixeldungeon.levels.SewerLevel;
@@ -349,7 +350,7 @@ public class Ghost extends NPC {
// 20% base chance to be enchanted, stored separately so status isn't revealed early
float enchantRoll = Random.Float();
- if (enchantRoll < 0.20f){
+ if (enchantRoll < 0.2f * ParchmentScrap.enchantChanceMultiplier()){
enchant = Weapon.Enchantment.random();
glyph = Armor.Glyph.random();
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Generator.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Generator.java
index b0e6df9ea..d3c75aaea 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Generator.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/Generator.java
@@ -100,6 +100,10 @@ import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfFear;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfFlock;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfIntuition;
import com.shatteredpixel.shatteredpixeldungeon.items.stones.StoneOfShock;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.PetrifiedSeed;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.RatSkull;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.Trinket;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.Wand;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfBlastWave;
import com.shatteredpixel.shatteredpixeldungeon.items.wands.WandOfCorrosion;
@@ -186,6 +190,8 @@ import java.util.LinkedHashMap;
public class Generator {
public enum Category {
+ TRINKET ( 0, 0, Trinket.class),
+
WEAPON ( 2, 2, MeleeWeapon.class),
WEP_T1 ( 0, 0, MeleeWeapon.class),
WEP_T2 ( 0, 0, MeleeWeapon.class),
@@ -509,6 +515,16 @@ public class Generator {
ARTIFACT.defaultProbs = new float[]{ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 };
ARTIFACT.probs = ARTIFACT.defaultProbs.clone();
+ //Trinkets are unique like artifacts, but unlike them you can only have one at once
+ //So we don't need the same enforcement of uniqueness
+ TRINKET.classes = new Class>[]{
+ RatSkull.class,
+ ParchmentScrap.class,
+ PetrifiedSeed.class
+ };
+ TRINKET.defaultProbs = new float[]{ 1, 1, 1 };
+ TRINKET.probs = TRINKET.defaultProbs.clone();
+
for (Category cat : Category.values()){
if (cat.defaultProbs2 != null){
cat.defaultProbsTotal = new float[cat.defaultProbs.length];
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/Armor.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/Armor.java
index 47825fc7a..c2406fe3f 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/Armor.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/armor/Armor.java
@@ -58,6 +58,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Swiftness;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Thorns;
import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Viscosity;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfArcana;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
import com.shatteredpixel.shatteredpixeldungeon.sprites.HeroSprite;
@@ -545,10 +546,10 @@ public class Armor extends EquipableItem {
//30% chance to be cursed
//15% chance to be inscribed
float effectRoll = Random.Float();
- if (effectRoll < 0.3f) {
+ if (effectRoll < 0.3f * ParchmentScrap.curseChanceMultiplier()) {
inscribe(Glyph.randomCurse());
cursed = true;
- } else if (effectRoll >= 0.85f){
+ } else if (effectRoll >= 1f - (0.15f * ParchmentScrap.enchantChanceMultiplier())){
inscribe();
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ParchmentScrap.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ParchmentScrap.java
new file mode 100644
index 000000000..68ad49200
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/ParchmentScrap.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ */
+
+package com.shatteredpixel.shatteredpixeldungeon.items.trinkets;
+
+import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
+import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
+
+public class ParchmentScrap extends Trinket {
+
+ {
+ image = ItemSpriteSheet.PARCHMENT_SCRAP;
+ }
+
+ @Override
+ protected int upgradeEnergyCost() {
+ return 1 + level(); //TODO
+ }
+
+ @Override
+ public String desc() {
+ return Messages.get(this, "desc", (int)enchantChanceMultiplier(buffedLvl()), Messages.decimalFormat("#.##", curseChanceMultiplier(buffedLvl())));
+ }
+
+ public static float enchantChanceMultiplier(){
+ return enchantChanceMultiplier(trinketLevel(ParchmentScrap.class));
+ }
+
+ public static float enchantChanceMultiplier( int level ){
+ switch (level){
+ default:
+ return 1;
+ case 0:
+ return 2;
+ case 1:
+ return 4;
+ case 2:
+ return 7;
+ case 3:
+ return 10;
+ }
+ }
+
+ public static float curseChanceMultiplier(){
+ return curseChanceMultiplier(trinketLevel(ParchmentScrap.class));
+ }
+
+ public static float curseChanceMultiplier( int level ){
+ switch (level){
+ default:
+ return 1;
+ case 0:
+ return 1.5f;
+ case 1:
+ return 2f;
+ case 2:
+ return 1f;
+ case 3:
+ return 0f;
+ }
+ }
+}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/PetrifiedSeed.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/PetrifiedSeed.java
new file mode 100644
index 000000000..122a04276
--- /dev/null
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/PetrifiedSeed.java
@@ -0,0 +1,62 @@
+/*
+ * 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
+ */
+
+package com.shatteredpixel.shatteredpixeldungeon.items.trinkets;
+
+import com.shatteredpixel.shatteredpixeldungeon.messages.Messages;
+import com.shatteredpixel.shatteredpixeldungeon.sprites.ItemSpriteSheet;
+
+public class PetrifiedSeed extends Trinket {
+
+ {
+ image = ItemSpriteSheet.PETRIFIED_SEED;
+ }
+
+ @Override
+ protected int upgradeEnergyCost() {
+ return 1 + level(); //TODO
+ }
+
+ @Override
+ public String desc() {
+ return Messages.get(this, "desc", (int)(100*stoneInsteadOfSeedChance(buffedLvl())), (int)Math.round(100*(grassLootMultiplier(buffedLvl())-1f)));
+ }
+
+ public static float grassLootMultiplier(){
+ return grassLootMultiplier(trinketLevel(PetrifiedSeed.class));
+ }
+
+ public static float grassLootMultiplier( int level ){
+ return 1f + .4f*level/3f;
+ }
+
+ public static float stoneInsteadOfSeedChance(){
+ return stoneInsteadOfSeedChance(trinketLevel(PetrifiedSeed.class));
+ }
+
+ public static float stoneInsteadOfSeedChance( int level ){
+ if (level == -1){
+ return 0f;
+ } else {
+ return 0.35f + .05f*level;
+ }
+ }
+}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/RatSkull.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/RatSkull.java
index af6fbde79..fea02b8e1 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/RatSkull.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/RatSkull.java
@@ -32,7 +32,7 @@ public class RatSkull extends Trinket {
@Override
protected int upgradeEnergyCost() {
- return 5 + 10*level();
+ return 1 + level(); //TODO
}
@Override
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/TrinketCatalyst.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/TrinketCatalyst.java
index d922b2ddb..97198df80 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/TrinketCatalyst.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/trinkets/TrinketCatalyst.java
@@ -119,7 +119,9 @@ public class TrinketCatalyst extends Item {
ShatteredPixelDungeon.scene().addToFront(new RewardWindow(item()));
}
};
- btnReward.item(Generator.randomUsingDefaults());
+ //TODO we need to persist these through save/load in case someone quits when the window is up
+ //alternatively we could just 'peek' at the items and then actually remove them when one is awarded.
+ btnReward.item(Generator.random(Generator.Category.TRINKET));
btnReward.setRect( i*(WIDTH - BTN_GAP) / NUM_TRINKETS - BTN_SIZE, message.top() + message.height() + BTN_GAP, BTN_SIZE, BTN_SIZE );
add( btnReward );
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java
index 163750628..a44eb442a 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/items/weapon/Weapon.java
@@ -34,6 +34,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.KindOfWeapon;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfArcana;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfForce;
import com.shatteredpixel.shatteredpixeldungeon.items.rings.RingOfFuror;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.ParchmentScrap;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Annoying;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Dazzling;
import com.shatteredpixel.shatteredpixeldungeon.items.weapon.curses.Displacing;
@@ -316,10 +317,10 @@ abstract public class Weapon extends KindOfWeapon {
//30% chance to be cursed
//10% chance to be enchanted
float effectRoll = Random.Float();
- if (effectRoll < 0.3f) {
+ if (effectRoll < 0.3f * ParchmentScrap.curseChanceMultiplier()) {
enchant(Enchantment.randomCurse());
cursed = true;
- } else if (effectRoll >= 0.9f){
+ } else if (effectRoll >= 1f - (0.1f * ParchmentScrap.enchantChanceMultiplier())){
enchant();
}
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java
index 836141962..61f3dd47b 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/features/HighGrass.java
@@ -39,6 +39,7 @@ import com.shatteredpixel.shatteredpixeldungeon.items.armor.glyphs.Camouflage;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.DriedRose;
import com.shatteredpixel.shatteredpixeldungeon.items.artifacts.SandalsOfNature;
import com.shatteredpixel.shatteredpixeldungeon.items.food.Berry;
+import com.shatteredpixel.shatteredpixeldungeon.items.trinkets.PetrifiedSeed;
import com.shatteredpixel.shatteredpixeldungeon.levels.Level;
import com.shatteredpixel.shatteredpixeldungeon.levels.MiningLevel;
import com.shatteredpixel.shatteredpixeldungeon.levels.Terrain;
@@ -122,8 +123,18 @@ public class HighGrass {
if (naturalismLevel >= 0) {
// Seed, scales from 1/25 to 1/9
- if (Random.Int(25 - (naturalismLevel * 4)) == 0) {
- level.drop(Generator.random(Generator.Category.SEED), pos).sprite.drop();
+ float lootChance = 1/(25f - naturalismLevel*4f);
+
+ //absolute max drop rate is ~1/6.5 with footwear of nature, ~1/18 without
+ lootChance *= PetrifiedSeed.grassLootMultiplier();
+
+ if (Random.Float() < lootChance) {
+ if (Random.Float() < PetrifiedSeed.stoneInsteadOfSeedChance()) {
+ //TODO do we want to use decks here in some way?
+ level.drop(Generator.randomUsingDefaults(Generator.Category.STONE), pos).sprite.drop();
+ } else {
+ level.drop(Generator.random(Generator.Category.SEED), pos).sprite.drop();
+ }
}
// Dew, scales from 1/6 to 1/4
diff --git a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
index 7a659c740..26bcd2330 100644
--- a/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
+++ b/core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/sprites/ItemSpriteSheet.java
@@ -470,8 +470,12 @@ public class ItemSpriteSheet {
private static final int TRINKETS = xy(9, 17); //24 slots
public static final int RAT_SKULL = TRINKETS+0;
+ public static final int PARCHMENT_SCRAP = TRINKETS+1;
+ public static final int PETRIFIED_SEED = TRINKETS+2;
static{
- assignItemRect(RAT_SKULL, 16, 11);
+ assignItemRect(RAT_SKULL, 16, 11);
+ assignItemRect(PARCHMENT_SCRAP, 10, 14);
+ assignItemRect(PETRIFIED_SEED, 10, 10);
}
private static final int SCROLLS = xy(1, 19); //16 slots