Skip to content

Commit 34f525c

Browse files
feat: alchemy
1 parent b40a3a3 commit 34f525c

52 files changed

Lines changed: 3889 additions & 168 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

commons/src/main/java/net/swofty/commons/item/ItemType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ public enum ItemType {
905905
GOLDEN_CARROT(Material.GOLDEN_CARROT, Rarity.COMMON),
906906
GOLD_NUGGET(Material.GOLD_NUGGET, Rarity.COMMON),
907907
IRON_NUGGET(Material.IRON_NUGGET, Rarity.COMMON),
908+
GLASS_BOTTLE(Material.GLASS_BOTTLE, Rarity.COMMON),
908909
WATER_BOTTLE(Material.POTION, Rarity.COMMON),
909910
CACTUS(Material.CACTUS, Rarity.COMMON),
910911
CHEST(Material.CHEST, Rarity.COMMON),
@@ -1497,6 +1498,7 @@ public enum ItemType {
14971498
ENCHANTED_PUMPKIN(Material.PUMPKIN, Rarity.UNCOMMON),
14981499
ENCHANTED_REDSTONE(Material.REDSTONE, Rarity.UNCOMMON),
14991500
ENCHANTED_REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Rarity.RARE),
1501+
ENCHANTED_REDSTONE_LAMP(Material.REDSTONE_LAMP, Rarity.RARE),
15001502
ENCHANTED_ROTTEN_FLESH(Material.ROTTEN_FLESH, Rarity.UNCOMMON),
15011503
ENCHANTED_SPONGE(Material.SPONGE, Rarity.UNCOMMON),
15021504
ENCHANTED_SPRUCE_LOG(Material.SPRUCE_LOG, Rarity.UNCOMMON),
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package net.swofty.commons.item.attribute.attributes;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
import lombok.Setter;
7+
import net.swofty.commons.item.attribute.ItemAttribute;
8+
import net.swofty.commons.statistics.ItemStatistics;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
/**
12+
* Item attribute that stores potion effect data on potion items.
13+
* This allows potions to remember their effect type, level, duration, and modifiers.
14+
*/
15+
public class ItemAttributePotionData extends ItemAttribute<ItemAttributePotionData.PotionData> {
16+
17+
@Override
18+
public String getKey() {
19+
return "potion_data";
20+
}
21+
22+
@Override
23+
public PotionData getDefaultValue(@Nullable ItemStatistics defaultStatistics) {
24+
return null;
25+
}
26+
27+
@Override
28+
public PotionData loadFromString(String string) {
29+
if (string == null || string.isEmpty() || string.equals("null")) {
30+
return null;
31+
}
32+
33+
String[] parts = string.split(":");
34+
if (parts.length < 3) return null;
35+
36+
try {
37+
String effectType = parts[0];
38+
int level = Integer.parseInt(parts[1]);
39+
int baseDurationSeconds = Integer.parseInt(parts[2]);
40+
boolean isSplash = parts.length > 3 && Boolean.parseBoolean(parts[3]);
41+
boolean isExtended = parts.length > 4 && Boolean.parseBoolean(parts[4]);
42+
43+
return new PotionData(effectType, level, baseDurationSeconds, isSplash, isExtended);
44+
} catch (NumberFormatException e) {
45+
return null;
46+
}
47+
}
48+
49+
@Override
50+
public String saveIntoString() {
51+
if (getValue() == null) return "null";
52+
PotionData data = getValue();
53+
return data.effectType + ":" +
54+
data.level + ":" +
55+
data.baseDurationSeconds + ":" +
56+
data.isSplash + ":" +
57+
data.isExtended;
58+
}
59+
60+
@Getter
61+
@Setter
62+
@NoArgsConstructor
63+
@AllArgsConstructor
64+
public static class PotionData {
65+
private String effectType;
66+
private int level;
67+
private int baseDurationSeconds; // Base duration before Alchemy/splash modifiers
68+
private boolean isSplash;
69+
private boolean isExtended;
70+
71+
/**
72+
* Create a simple potion data with default flags
73+
*/
74+
public PotionData(String effectType, int level, int baseDurationSeconds) {
75+
this(effectType, level, baseDurationSeconds, false, false);
76+
}
77+
78+
/**
79+
* Calculate final duration in milliseconds with Alchemy skill bonus
80+
* @param alchemyLevel Player's Alchemy skill level (0-50)
81+
* @return Duration in milliseconds
82+
*/
83+
public long getFinalDurationMs(int alchemyLevel) {
84+
if (baseDurationSeconds <= 0) return 0; // Instant potions
85+
86+
// Alchemy gives +1% duration per level (max +50%)
87+
double alchemyBonus = 1.0 + (Math.min(alchemyLevel, 50) * 0.01);
88+
89+
// Splash potions have half duration (unless extended modifier was used)
90+
double splashMultiplier = (isSplash && !isExtended) ? 0.5 : 1.0;
91+
92+
return (long) (baseDurationSeconds * 1000 * alchemyBonus * splashMultiplier);
93+
}
94+
95+
/**
96+
* Get the final duration in seconds with Alchemy bonus
97+
*/
98+
public int getFinalDurationSeconds(int alchemyLevel) {
99+
return (int) (getFinalDurationMs(alchemyLevel) / 1000);
100+
}
101+
102+
/**
103+
* Get formatted duration string (e.g., "3:00" or "45s")
104+
*/
105+
public String getFormattedDuration(int alchemyLevel) {
106+
int totalSeconds = getFinalDurationSeconds(alchemyLevel);
107+
if (totalSeconds <= 0) return "Instant";
108+
109+
int minutes = totalSeconds / 60;
110+
int seconds = totalSeconds % 60;
111+
112+
if (minutes > 0) {
113+
return minutes + ":" + String.format("%02d", seconds);
114+
} else {
115+
return seconds + "s";
116+
}
117+
}
118+
119+
/**
120+
* Get the display name for this potion
121+
*/
122+
public String getDisplayName() {
123+
String prefix = isSplash ? "Splash " : "";
124+
String levelRoman = toRoman(level);
125+
return prefix + "Potion of " + formatEffectName(effectType) + " " + levelRoman;
126+
}
127+
128+
/**
129+
* Get just the effect display (e.g., "Speed V")
130+
*/
131+
public String getEffectDisplay() {
132+
return formatEffectName(effectType) + " " + toRoman(level);
133+
}
134+
135+
/**
136+
* Format effect type name for display (FIRE_RESISTANCE -> Fire Resistance)
137+
*/
138+
private static String formatEffectName(String effectType) {
139+
if (effectType == null) return "Unknown";
140+
String[] words = effectType.toLowerCase().split("_");
141+
StringBuilder result = new StringBuilder();
142+
for (String word : words) {
143+
if (!word.isEmpty()) {
144+
if (result.length() > 0) result.append(" ");
145+
result.append(Character.toUpperCase(word.charAt(0)));
146+
if (word.length() > 1) {
147+
result.append(word.substring(1));
148+
}
149+
}
150+
}
151+
return result.toString();
152+
}
153+
154+
/**
155+
* Convert integer to Roman numeral
156+
*/
157+
private static String toRoman(int level) {
158+
String[] romans = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"};
159+
if (level >= 1 && level <= 10) {
160+
return romans[level];
161+
}
162+
return String.valueOf(level);
163+
}
164+
165+
/**
166+
* Create a copy with updated level
167+
*/
168+
public PotionData withLevel(int newLevel) {
169+
return new PotionData(effectType, newLevel, baseDurationSeconds, isSplash, isExtended);
170+
}
171+
172+
/**
173+
* Create a copy with updated duration
174+
*/
175+
public PotionData withDuration(int newDurationSeconds) {
176+
return new PotionData(effectType, level, newDurationSeconds, isSplash, isExtended);
177+
}
178+
179+
/**
180+
* Create a copy as splash potion
181+
*/
182+
public PotionData asSplash(boolean preserveDuration) {
183+
return new PotionData(effectType, level, baseDurationSeconds, true, preserveDuration);
184+
}
185+
186+
/**
187+
* Check if this is an instant effect (healing, damage, stamina)
188+
*/
189+
public boolean isInstant() {
190+
return baseDurationSeconds <= 0;
191+
}
192+
}
193+
}

commons/src/main/java/net/swofty/commons/statistics/ItemStatistic.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public enum ItemStatistic {
8787
PULL("Pull","§a","§b",false,"ᛷ"),
8888
RESPIRATION("Respiration","§a","§3",false,"⚶"),
8989
PRESSURE_RESISTANCE("Pressure Resistance","§a","§9",false,"❍"),
90+
BREWER("Brewer", "§a", "§d", true, "☕"),
9091

9192
// Other Stats
9293
DAMAGE("Damage", "§c", "§c", false, "❁",

0 commit comments

Comments
 (0)