Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ All major changes brought to **Cobblemon Cards** in this update.
* Fixed `ItemStack` instances being shared between a cabinet's container and the `BINDER_CONTENTS` component when broken or placed.


### Master Album Config for Disabling Stats
* **New Config Option**: `doesMasterBinderProvideStats` — Set to `false` to disable stats from master tier binders. Due to their large card capacity.

---

## 🚀 Version 1.0.1 (Multiloader & Easter Eggs Update)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ public class CobblemonCardsConfig extends MidnightConfig {
@Entry
public static boolean enableSpawnBoostStats = true;

/** Master toggle for the Exp / Catch / Shiny "trainer" stats. */
@Entry
public static boolean enableTrainerStats = true;

/** Single toggle for all 15 egg-group spawn influences. Also requires {@link #enableSpawnBoostStats}. */
@Entry
public static boolean enableEggGroupStats = false;

/** Single toggle for all 6 EV-yield spawn influences. Also requires {@link #enableSpawnBoostStats}. */
@Entry
public static boolean enableEvYieldStats = false;

@Entry(min = 0.0f, max = 100.0f)
public static float globalStatMultiplier = 10.0f;

Expand All @@ -22,9 +34,47 @@ public class CobblemonCardsConfig extends MidnightConfig {
@Entry(min = 0.0f, max = 100.0f)
public static float spawnBoostStatMultiplier = 1.0f;

@Entry(min = 0.0f, max = 100.0f)
public static float trainerStatMultiplier = 1.0f;

@Entry(min = 0.0f, max = 10000.0f)
public static float maxSpawnBoostMultiplier = 100.0f;

// --- Trainer stat safety caps (mirrors the maxSpawnBoostMultiplier idiom) ---

@Entry(min = 1.0f, max = 100.0f)
public static float maxExpBoostMultiplier = 5.0f;

@Entry(min = 1.0f, max = 100.0f)
public static float maxCatchBoostMultiplier = 5.0f;

/** Shiny rate is "1-in-N", so a boost divides it. This caps the divisor. */
@Entry(min = 1.0f, max = 100.0f)
public static float maxShinyBoostDivisor = 10.0f;

// --- How trainer stats are earned ---
// Trainer stats never roll on a normal card. They are granted either by grading a card to
// trainerStatMinGrade or higher, or by the small lucky chance on Legendary/Mythic/Shiny cards.

@Entry
public static boolean enableTrainerStatGrading = true;

/** Minimum grade that can earn a trainer stat. */
@Entry(min = 1, max = 10)
public static int trainerStatMinGrade = 9;

/** Chance (%) that a card graded at or above {@link #trainerStatMinGrade} converts to a trainer stat. */
@Entry(min = 0.0f, max = 100.0f)
public static float trainerStatGradeChance = 50.0f;

/** Chance (%) that a Legendary / Mythic / Shiny card rolls a trainer stat on drop. */
@Entry(min = 0.0f, max = 100.0f)
public static float trainerStatLuckyChance = 5.0f;

/** Lucky trainer stats roll at this fraction of the normal rarity-based value. */
@Entry(min = 0.0f, max = 1.0f)
public static float trainerStatLuckyValueMultiplier = 0.25f;

@Entry(min = 1, max = 1200)
public static int recyclerProcessTime = 40;

Expand Down Expand Up @@ -107,6 +157,18 @@ public class CobblemonCardsConfig extends MidnightConfig {
@Entry(min = 0.0f, max = 100.0f)
public static float cardDropChanceStatMultiplier = 1.0f;

// --- Per-stat multipliers for the trainer stats ---
/** Trainer stats are incredibly powerful, so they are damped by default. */

@Entry(min = 0.0f, max = 100.0f)
public static float expBoostStatMultiplier = 0.8f;

@Entry(min = 0.0f, max = 100.0f)
public static float catchBoostStatMultiplier = 0.25f;

@Entry(min = 0.0f, max = 100.0f)
public static float shinyChanceStatMultiplier = 0.1f;

/**
* When false (default), species whose National Pokédex number is outside [1, 1025]
* (i.e. Fakemon added by addon mods) are excluded from card drops and booster packs.
Expand All @@ -117,14 +179,23 @@ public class CobblemonCardsConfig extends MidnightConfig {


public static float getStatMultiplier(CardStat stat) {
if (stat == null || !enableCardStats || (!enablePlayerStats && isPlayerStat(stat)) || (!enableSpawnBoostStats && isSpawnStat(stat))) {
if (stat == null || !enableCardStats
|| (!enablePlayerStats && isPlayerStat(stat))
|| (!enableSpawnBoostStats && isSpawnBoostStat(stat))
|| (!enableTrainerStats && isTrainerStat(stat))
|| (!enableEggGroupStats && isEggGroupStat(stat))
|| (!enableEvYieldStats && isEvYieldStat(stat))) {
return 0.0f;
}

if (isPlayerStat(stat)) {
return globalStatMultiplier * playerStatMultiplier * getPerStatMultiplier(stat);
}
if (isSpawnStat(stat)) {
if (isTrainerStat(stat)) {
return globalStatMultiplier * trainerStatMultiplier * getPerStatMultiplier(stat);
}
// Elemental-type, egg-group and EV-yield boosts all feed the same spawn weighting pipeline.
if (isSpawnBoostStat(stat)) {
return globalStatMultiplier * spawnBoostStatMultiplier;
}

Expand All @@ -147,6 +218,9 @@ public static float getPerStatMultiplier(CardStat stat) {
case ARMOR -> armorStatMultiplier;
case MAX_HEALTH -> maxHealthStatMultiplier;
case CARD_DROP_CHANCE -> cardDropChanceStatMultiplier;
case EXP_BOOST -> expBoostStatMultiplier;
case CATCH_BOOST -> catchBoostStatMultiplier;
case SHINY_CHANCE -> shinyChanceStatMultiplier;
default -> 1.0f;
};
}
Expand All @@ -158,10 +232,34 @@ public static boolean isPlayerStat(CardStat stat) {
|| stat == CardStat.MAX_HEALTH || stat == CardStat.CARD_DROP_CHANCE;
}

/** Elemental-type spawn boosts only (the original {@code *_spawn} stats). */
public static boolean isSpawnStat(CardStat stat) {
return stat != null && stat.getSerializedName().endsWith("_spawn");
}

/** The Exp / Catch / Shiny global player boosts. */
public static boolean isTrainerStat(CardStat stat) {
return stat == CardStat.EXP_BOOST || stat == CardStat.CATCH_BOOST || stat == CardStat.SHINY_CHANCE;
}

/** Egg-group spawn influences ({@code *_egg}). */
public static boolean isEggGroupStat(CardStat stat) {
return stat != null && stat.getSerializedName().endsWith("_egg");
}

/** EV-yield spawn influences ({@code *_yield}). */
public static boolean isEvYieldStat(CardStat stat) {
return stat != null && stat.getSerializedName().endsWith("_yield");
}

/**
* Any stat that feeds Cobblemon's spawn weighting: elemental type, egg group or EV yield.
* All three are gated by {@link #enableSpawnBoostStats} so the master toggle stays meaningful.
*/
public static boolean isSpawnBoostStat(CardStat stat) {
return isSpawnStat(stat) || isEggGroupStat(stat) || isEvYieldStat(stat);
}

public static int getBinderPages(com.howlite.cobblemoncards.item.custom.BinderTier tier, int defaultPages) {
if (tier == null) return defaultPages;
return switch (tier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.howlite.cobblemoncards.CobblemonCardsConfig;
import com.howlite.cobblemoncards.component.CardData;
import com.howlite.cobblemoncards.component.CardStat;
import com.howlite.cobblemoncards.component.ModDataComponents;
import com.howlite.cobblemoncards.item.ModItems;
import com.howlite.cobblemoncards.block.ModBlocks;
Expand Down Expand Up @@ -29,6 +30,8 @@

public class CardRestorerBlockEntity extends BlockEntity implements ImplementedInventory, MenuProvider {

private static final java.util.Random RANDOM = new java.util.Random();

// Slots: 0 = card input, 1-4 = card dust input
private final NonNullList<ItemStack> inventory = NonNullList.withSize(5, ItemStack.EMPTY);

Expand Down Expand Up @@ -217,11 +220,26 @@ public boolean performRestore(Player player) {
: data.statValue();
float newStatValue = baseStatValue * (1f + targetBonus);

// Restoring can lift a card to grade 9+, which is the main way to earn a "trainer" stat.
// Without this, a card restored to grade 10 could never obtain one (the Grading Station only
// ever runs on grade-0 cards). Gated on newly CROSSING the threshold, so repeatedly restoring
// an already-9 card to 10 isn't a cheap re-roll.
CardStat finalStat = data.stat();
if (currentGrade < CobblemonCardsConfig.trainerStatMinGrade
&& target >= CobblemonCardsConfig.trainerStatMinGrade
&& !com.howlite.cobblemoncards.util.CardUtil.isCosmeticCard(data.pokemonId())) {
CardStat trainerStat = com.howlite.cobblemoncards.util.CardStatUtil
.rollTrainerStatForGrade(target, RANDOM);
if (trainerStat != null) {
finalStat = trainerStat;
}
}

CardData newData = new CardData(
data.pokemonId(),
data.isShiny(),
data.rarity(),
data.stat(),
finalStat,
newStatValue,
target,
data.background(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.howlite.cobblemoncards.CobblemonCardsConfig;
import com.howlite.cobblemoncards.component.CardData;
import com.howlite.cobblemoncards.component.CardStat;
import com.howlite.cobblemoncards.component.ModDataComponents;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
Expand Down Expand Up @@ -172,11 +173,22 @@ private void finalizeGrading() {
float bonusMultiplier = newGrade * 0.03f;
float newStatValue = oldData.statValue() + (oldData.statValue() * bonusMultiplier);

// A high grade is the main way to earn a "trainer" stat (Exp / Catch / Shiny).
// Cosmetic cards are excluded: they are meant to carry no stats at all.
CardStat finalStat = oldData.stat();
if (!com.howlite.cobblemoncards.util.CardUtil.isCosmeticCard(oldData.pokemonId())) {
CardStat trainerStat = com.howlite.cobblemoncards.util.CardStatUtil
.rollTrainerStatForGrade(newGrade, RANDOM);
if (trainerStat != null) {
finalStat = trainerStat;
}
}

CardData newData = new CardData(
oldData.pokemonId(),
oldData.isShiny(),
oldData.rarity(),
oldData.stat(),
finalStat,
newStatValue,
newGrade,
oldData.background(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.howlite.cobblemoncards.component;

import com.cobblemon.mod.common.api.pokemon.egg.EggGroup;
import com.cobblemon.mod.common.api.pokemon.stats.Stat;
import com.cobblemon.mod.common.api.pokemon.stats.Stats;
import com.mojang.serialization.Codec;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
Expand Down Expand Up @@ -34,7 +37,39 @@ public enum CardStat implements StringRepresentable {
DRAGON_SPAWN("dragon_spawn"),
STEEL_SPAWN("steel_spawn"),
FAIRY_SPAWN("fairy_spawn"),
DARK_SPAWN("dark_spawn");
DARK_SPAWN("dark_spawn"),

// Stats Dresseur (boosts globaux du joueur)
// Ces stats sont volontairement absentes du tirage normal : elles s'obtiennent uniquement
// via un grade >= 9 ou la petite chance sur les cartes Legendary / Mythic / Shiny.
EXP_BOOST("exp_boost"),
CATCH_BOOST("catch_boost"),
SHINY_CHANCE("shiny_chance"),

// Influences de spawn par groupe d'oeuf (suffixe _egg, 16 au total)
MONSTER_EGG("monster_egg"),
WATER_1_EGG("water_1_egg"),
BUG_EGG("bug_egg"),
FLYING_EGG("flying_egg"),
FIELD_EGG("field_egg"),
FAIRY_EGG("fairy_egg"),
GRASS_EGG("grass_egg"),
HUMAN_LIKE_EGG("human_like_egg"),
WATER_3_EGG("water_3_egg"),
MINERAL_EGG("mineral_egg"),
AMORPHOUS_EGG("amorphous_egg"),
WATER_2_EGG("water_2_egg"),
DITTO_EGG("ditto_egg"),
DRAGON_EGG("dragon_egg"),
UNDISCOVERED_EGG("undiscovered_egg"),

// Influences de spawn par rendement d'EV (suffixe _yield, 6 au total)
HP_YIELD("hp_yield"),
ATTACK_YIELD("attack_yield"),
DEFENCE_YIELD("defence_yield"),
SPECIAL_ATTACK_YIELD("special_attack_yield"),
SPECIAL_DEFENCE_YIELD("special_defence_yield"),
SPEED_YIELD("speed_yield");

public static final Codec<CardStat> CODEC = StringRepresentable.fromEnum(CardStat::values);

Expand All @@ -52,4 +87,49 @@ public MutableComponent getTranslatedName() {
public String getSerializedName() {
return name;
}

/**
* Resolves the Cobblemon {@link EggGroup} boosted by an {@code *_egg} stat.
* Kept next to the constants so the stat -> Cobblemon mapping lives in one place, mirroring
* how {@code BinderSpawnModifier} derives {@code ElementalTypes.get(...)} from the serialized name.
*
* @return the matching egg group, or {@code null} if this stat is not an egg-group stat.
*/
public EggGroup getEggGroup() {
return switch (this) {
case MONSTER_EGG -> EggGroup.MONSTER;
case WATER_1_EGG -> EggGroup.WATER_1;
case BUG_EGG -> EggGroup.BUG;
case FLYING_EGG -> EggGroup.FLYING;
case FIELD_EGG -> EggGroup.FIELD;
case FAIRY_EGG -> EggGroup.FAIRY;
case GRASS_EGG -> EggGroup.GRASS;
case HUMAN_LIKE_EGG -> EggGroup.HUMAN_LIKE;
case WATER_3_EGG -> EggGroup.WATER_3;
case MINERAL_EGG -> EggGroup.MINERAL;
case AMORPHOUS_EGG -> EggGroup.AMORPHOUS;
case WATER_2_EGG -> EggGroup.WATER_2;
case DITTO_EGG -> EggGroup.DITTO;
case DRAGON_EGG -> EggGroup.DRAGON;
case UNDISCOVERED_EGG -> EggGroup.UNDISCOVERED;
default -> null;
};
}

/**
* Resolves the Cobblemon {@link Stat} whose EV yield is boosted by a {@code *_yield} stat.
*
* @return the matching stat, or {@code null} if this stat is not an EV-yield stat.
*/
public Stat getEvYieldStat() {
return switch (this) {
case HP_YIELD -> Stats.HP;
case ATTACK_YIELD -> Stats.ATTACK;
case DEFENCE_YIELD -> Stats.DEFENCE;
case SPECIAL_ATTACK_YIELD -> Stats.SPECIAL_ATTACK;
case SPECIAL_DEFENCE_YIELD -> Stats.SPECIAL_DEFENCE;
case SPEED_YIELD -> Stats.SPEED;
default -> null;
};
}
}
Loading
Loading