Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shrieking hat #586

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
* New art for Double (thanks Newigeg)
* New art for Exhale (thanks Zyalin)

#### CULL new cards
* Shrieking Hat (thanks wang429)

#### CULL balance/design changes

* Blasphemer: Smites added when drawn 5->4, Blasphemer+ now adds 4 upgraded Smites. (thanks LankSSBM)
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/stsjorbsmod/cards/DeathPreventionCard.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package stsjorbsmod.cards;

import org.apache.commons.lang3.mutable.MutableInt;

public interface DeathPreventionCard {
MutableInt currentPriority = new MutableInt(1);

int getPriority();
}
5 changes: 5 additions & 0 deletions src/main/java/stsjorbsmod/cards/WasHPLostCardSubscriber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package stsjorbsmod.cards;

public interface WasHPLostCardSubscriber {
void wasHPLost(int damageAmount);
}
49 changes: 45 additions & 4 deletions src/main/java/stsjorbsmod/cards/cull/OldBook.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
package stsjorbsmod.cards.cull;

import basemod.abstracts.CustomSavable;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.actions.common.ReducePowerAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import stsjorbsmod.JorbsMod;
import stsjorbsmod.cards.CustomJorbsModCard;
import stsjorbsmod.cards.DeathPreventionCard;
import stsjorbsmod.cards.OnCardExhumedSubscriber;
import stsjorbsmod.cards.OnEntombedSubscriber;
import stsjorbsmod.characters.Cull;
import stsjorbsmod.patches.EntombedField;
import stsjorbsmod.powers.OldBookPower;

import static com.megacrit.cardcrawl.cards.AbstractCard.CardTags.HEALING;
import static stsjorbsmod.JorbsMod.JorbsCardTags.LEGENDARY;

public class OldBook extends CustomJorbsModCard implements OnCardExhumedSubscriber, OnEntombedSubscriber {
public class OldBook extends CustomJorbsModCard implements OnCardExhumedSubscriber, OnEntombedSubscriber, DeathPreventionCard, CustomSavable<Integer> {
public static final String ID = JorbsMod.makeID(OldBook.class);

private static final CardRarity RARITY = CardRarity.RARE;
private static final CardTarget TARGET = CardTarget.SELF;
private static final CardType TYPE = CardType.SKILL;
public static final CardColor COLOR = Cull.Enums.CULL_CARD_COLOR;

private AbstractPlayer p = AbstractDungeon.player;
private final AbstractPlayer p = AbstractDungeon.player;
private int priority;

private static final int COST = COST_UNPLAYABLE;
private static final int HEAL_PERCENT = 0;
Expand All @@ -34,8 +39,19 @@ public OldBook() {
EntombedField.entombed.set(this, true);

magicNumber = baseMagicNumber = HEAL_PERCENT;
exhaust = true;

tags.add(LEGENDARY);
tags.add(HEALING);
}

/**
* used in ShowCardAndAddToHandEffect::new which gets used in Discovery, Card Potions, and MakeTempCardInHandAction (Dead Branch and console command HandAdd)
*/
@Override
public void triggerWhenCopied() {
super.triggerWhenCopied();
priority = currentPriority.incrementAndGet();
}

@Override
Expand All @@ -44,23 +60,48 @@ public void onCardEntombed() {
}

@Override
public boolean canUse(AbstractPlayer abstractPlayer, AbstractMonster abstractMonster) { return false; }
public boolean canUse(AbstractPlayer abstractPlayer, AbstractMonster abstractMonster) {
return false;
}

@Override
public void use(AbstractPlayer abstractPlayer, AbstractMonster abstractMonster) {
onCardEntombed();
}

@Override
public void onCardExhumed() {
addToBot(new ReducePowerAction(p, p, OldBookPower.POWER_ID, 1));
}

@Override
public int getPriority() {
return priority;
}

@Override
public void upgrade() {
if(!upgraded) {
if (!upgraded) {
upgradeName();
upgradeMagicNumber(UPGRADE_HEAL_PERCENT);
upgradeDescription();
}
}

@Override
public AbstractCard makeCopy() {
OldBook copy = (OldBook) super.makeCopy();
copy.priority = this.priority;
return copy;
}

@Override
public Integer onSave() {
return priority;
}

@Override
public void onLoad(Integer integer) {
this.priority = integer;
}
}
124 changes: 124 additions & 0 deletions src/main/java/stsjorbsmod/cards/cull/ShriekingHat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package stsjorbsmod.cards.cull;

import basemod.abstracts.CustomSavable;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.actions.animations.VFXAction;
import com.megacrit.cardcrawl.actions.common.DamageAllEnemiesAction;
import com.megacrit.cardcrawl.actions.utility.SFXAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.Settings;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import com.megacrit.cardcrawl.vfx.cardManip.ShowCardBrieflyEffect;
import com.megacrit.cardcrawl.vfx.combat.ShockWaveEffect;
import stsjorbsmod.JorbsMod;
import stsjorbsmod.actions.ConsumerGameAction;
import stsjorbsmod.actions.PermanentlyModifyDamageAction;
import stsjorbsmod.cards.CustomJorbsModCard;
import stsjorbsmod.cards.DeathPreventionCard;
import stsjorbsmod.cards.WasHPLostCardSubscriber;
import stsjorbsmod.characters.Cull;
import stsjorbsmod.util.CardMetaUtils;
import stsjorbsmod.util.EffectUtils;

import static stsjorbsmod.JorbsMod.JorbsCardTags.LEGENDARY;

/**
* Unplayable. NL Retain. NL When you die instead deal (3 +) all damage taken while this was in your hand to all enemies. NL stsjorbsmod:Destroy.
wang429 marked this conversation as resolved.
Show resolved Hide resolved
*/
public class ShriekingHat extends CustomJorbsModCard implements DeathPreventionCard, WasHPLostCardSubscriber, CustomSavable<Integer> {
public static final String ID = JorbsMod.makeID(ShriekingHat.class);

private static final CardRarity RARITY = CardRarity.RARE;
private static final CardTarget TARGET = CardTarget.SELF;
private static final CardType TYPE = CardType.SKILL;
public static final CardColor COLOR = Cull.Enums.CULL_CARD_COLOR;

private AbstractPlayer p = AbstractDungeon.player;
private int priority;

private static final int COST = COST_UNPLAYABLE;
private static final int UPGRADE_DAMAGE = 3;

public ShriekingHat() {
super(ID, COST, TYPE, COLOR, RARITY, TARGET);

damage = baseDamage = misc = magicNumber = 0;
selfRetain = true;

tags.add(LEGENDARY);
}

@Override
public int getPriority() {
return priority;
}

/**
* used in ShowCardAndAddToHandEffect::new which gets used in Discovery, Card Potions, and MakeTempCardInHandAction leading to Dead Branch
*/
@Override
public void triggerWhenCopied() {
super.triggerWhenCopied();
priority = currentPriority.incrementAndGet();
}

@Override
public void wasHPLost(int damageAmount) {
addToBot(new PermanentlyModifyDamageAction(uuid, damageAmount));
}

@Override
public boolean canUse(AbstractPlayer abstractPlayer, AbstractMonster abstractMonster) {
this.cantUseMessage = cardStrings.EXTENDED_DESCRIPTION[1];
return false;
}

@Override
public void use(AbstractPlayer abstractPlayer, AbstractMonster abstractMonster) {
addToBot(new VFXAction(new ShowCardBrieflyEffect(this)));
addToBot(new SFXAction("ATTACK_PIERCING_WAIL", -1, true));
if (Settings.FAST_MODE) {
addToBot(new VFXAction(p, new ShockWaveEffect(p.hb.cX, p.hb.cY, Settings.RED_TEXT_COLOR, ShockWaveEffect.ShockWaveType.CHAOTIC), 0.3F));
} else {
addToBot(new VFXAction(p, new ShockWaveEffect(p.hb.cX, p.hb.cY, Settings.RED_TEXT_COLOR, ShockWaveEffect.ShockWaveType.CHAOTIC), 1.5F));
}
addToBot(new DamageAllEnemiesAction(p, baseDamage, DamageInfo.DamageType.THORNS, AbstractGameAction.AttackEffect.SLASH_HORIZONTAL));
addToBot(new ConsumerGameAction<>(EffectUtils::showDestroyEffect, this));
CardMetaUtils.destroyCardPermanently(this);
}

@Override
public String getRawDynamicDescriptionSuffix() {
return EXTENDED_DESCRIPTION[0];
}

@Override
public void upgrade() {
if (!upgraded) {
upgradeName();
upgradeMagicNumber(UPGRADE_DAMAGE);
upgradeDamage(UPGRADE_DAMAGE);
upgradeDescription();
}
}

@Override
public AbstractCard makeCopy() {
ShriekingHat copy = (ShriekingHat) super.makeCopy();
copy.priority = this.priority;
return copy;
}

@Override
public Integer onSave() {
return priority;
}

@Override
public void onLoad(Integer integer) {
this.priority = integer;
}
}
130 changes: 130 additions & 0 deletions src/main/java/stsjorbsmod/patches/DeathPreventionPatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package stsjorbsmod.patches;

import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
import com.megacrit.cardcrawl.actions.common.RemoveSpecificPowerAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.potions.FairyPotion;
import com.megacrit.cardcrawl.relics.LizardTail;
import com.megacrit.cardcrawl.relics.MarkOfTheBloom;
import javassist.CannotCompileException;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import stsjorbsmod.cards.DeathPreventionCard;
import stsjorbsmod.cards.cull.OldBook;
import stsjorbsmod.cards.cull.ShriekingHat;
import stsjorbsmod.powers.OldBookPower;
import stsjorbsmod.util.CardMetaUtils;

import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;

@SpirePatch(clz = DamageInfo.class, method = SpirePatch.CLASS)
public class DeathPreventionPatch {
public static boolean triggerDeathPrevention(AbstractPlayer p, int damageAmount) {
if (damageAmount < p.currentHealth)
return false;
if (p.hasPotion(FairyPotion.POTION_ID))
return false;
if (p.hasRelic(LizardTail.ID) && p.getRelic(LizardTail.ID).counter == -1)
return false;
if (!p.hasPower(OldBookPower.POWER_ID + OldBookPower.UPGRADED) &&
!p.hasPower(OldBookPower.POWER_ID + OldBookPower.NORMAL) &&
p.hand.group.stream().noneMatch(c -> c instanceof ShriekingHat))
return false;

// requested that death preventing cards trigger in order of pickup.
SortedSet<DeathPreventionCard> deathPreventionCards = new TreeSet<>(Comparator.comparingInt(DeathPreventionCard::getPriority));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the change suggested above, we should be able to loop the masterDeck object first to determine "permanent" cards that were added to the deck that need to be consumed, and then pursue the "temporary" cards using your priority implemented.

Copy link
Collaborator Author

@wang429 wang429 Dec 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It added a little bit more complexity to the the logic, but I think it should work as expected.

for (AbstractCard c : p.masterDeck.group) {
if (c instanceof ShriekingHat && p.hand.group.stream().anyMatch(ch -> c.uuid.equals(ch.uuid))) {
deathPreventionCards.add((DeathPreventionCard) c);
}
else if (c instanceof OldBook) {
deathPreventionCards.add((DeathPreventionCard) c);
}
}

if (deathPreventionCards.isEmpty()) {
for (AbstractCard c : p.hand.group) {
if (c instanceof ShriekingHat) {
deathPreventionCards.add((DeathPreventionCard) c);
}
}
for (AbstractCard c : p.exhaustPile.group) {
if (c instanceof OldBook) {
deathPreventionCards.add((DeathPreventionCard) c);
}
}
}

if (deathPreventionCards.isEmpty()) {
return false;
}
// n.b. for debugging: TreeSet.add (and the underlying TreeMap.put) impl only adds if there isn't
// already an object matching the comparator. As such if there's both a ShriekingHat and OldBook in the
// deck, only the first card found will be in deathPreventionCards set since priority for these cards
// added to the master deck will be 0.
AbstractCard firstDeathPreventionCard = (AbstractCard) deathPreventionCards.first();

if (OldBook.ID.equals(firstDeathPreventionCard.cardID)) {
handleOldBook(p);
} else if (ShriekingHat.ID.equals(firstDeathPreventionCard.cardID)) {
handleShriekingHat(p, (ShriekingHat) firstDeathPreventionCard);
}

return true;
}

private static void handleOldBook(AbstractPlayer p) {
OldBookPower po;
if (p.hasPower(OldBookPower.POWER_ID + OldBookPower.NORMAL))
po = (OldBookPower) p.getPower(OldBookPower.POWER_ID + OldBookPower.NORMAL);
else
po = (OldBookPower) p.getPower(OldBookPower.POWER_ID + OldBookPower.UPGRADED);

AbstractCard c = po.getCard();
if (c.upgraded) {
if (!p.hasRelic(MarkOfTheBloom.ID)) {
float percent = (float) c.magicNumber / 100.0F;
int healAmt = (int) ((float) p.maxHealth * percent);
if (healAmt < 1) {
healAmt = 1;
}

p.heal(healAmt, true);
} else {
p.getRelic(MarkOfTheBloom.ID).flash();
}
}

po.reducePower(1);
if (po.amount <= 0)
AbstractDungeon.actionManager.addToBottom(new RemoveSpecificPowerAction(p, p, po));

CardMetaUtils.destroyCardPermanently(c);
}

private static void handleShriekingHat(AbstractPlayer p, ShriekingHat shriekingHatCard) {
shriekingHatCard.use(p, null);
}

@SpirePatch(clz = AbstractPlayer.class, method = "damage")
public static class AbstractPlayer_damage {
public static ExprEditor Instrument() {
return new ExprEditor() {
@Override
public void edit(FieldAccess fieldAccess) throws CannotCompileException {
if (fieldAccess.getClassName().equals(AbstractPlayer.class.getName()) &&
fieldAccess.getFieldName().equals("lastDamageTaken")) {
fieldAccess.replace(String.format("{ if (%1$s.triggerDeathPrevention($0, damageAmount)) { damageAmount = 0; lastDamageTaken = 0; }" +
"else { $proceed($$); }; } ",
DeathPreventionPatch.class.getName()));
}
}
};
}
}
}
Loading