Slay the Spire

Slay the Spire

评价数不足
How to allow cast custom cards on your teammate
由 Gray_Nomad 制作
This guide tells you how to allow your custom character or any mod card to be casted on your teammate when you're playing with Together in Spire mod
   
奖励
收藏
已收藏
取消收藏
Prepare
At first: I'm bad in programming and don't even know the java so this guide won't be the best solution. Also english isn't my native language

At second: I won't tell here anything basic like how to install IDE and so. You can find it on BasicMod wiki[github.com]

In pom.xml file of your mod you need to find <dependencies> and add this
<dependency> <groupId>spiretogether</groupId> <artifactId>SpireTogether</artifactId> <version>2.0</version> <scope>system</scope> <systemPath>${steam.path}/workshop/content/646570/2384072973/TogetherInSpire.jar</systemPath> </dependency>

And if you're adding mod that will work only with TiS you need to find ModTheSpire.json and add "spireTogether" in dependencies.

Now you're ready to actually work with cards
Targeting cards on teammates
As I made my mod as relic i will tell how to make it same but if you're experienced with mods I'm sure you'll get your way to make it

After you create your relic you need to use next code and change every place where used SkillCastRelic to your relic class name:

Here you'll add exceptions (example: poison, pressure points and anything else that affects enemies). If skill is enemy targeted it must return true
public static boolean IsAllowedSkill(AbstractCard __inst){ if (__inst == null){ return false; } else{ return true } return false; } } private static boolean queueContins(AbstractCard card) { for(CardQueueItem i : AbstractDungeon.actionManager.cardQueue) { if (i.card == card) { return true; } } return false; } @SpirePatch2( clz = AbstractPlayer.class, method = "playCard" ) public static class DefendPatches { public DefendPatches() { } public static SpireReturn Prefix(AbstractPlayer __instance) { if (SpireTogetherMod.isConnected && SkillCastRelic.IsAllowedSkill(__instance.hoveredCard)) { InputHelper.justClickedLeft = false; __instance.hoverEnemyWaitTimer = 1.0F; __instance.hoveredCard.unhover(); if (!SkillCastRelic.queueContins(__instance.hoveredCard)) { AbstractMonster hoveredMonster = (AbstractMonster)Reflection.getFieldValue("hoveredMonster", __instance); if (hoveredMonster instanceof CharacterEntity) { AbstractDungeon.actionManager.cardQueue.add(new CardQueueItem(__instance.hoveredCard, hoveredMonster)); } else { AbstractDungeon.actionManager.cardQueue.add(new CardQueueItem(__instance.hoveredCard, (AbstractMonster)null)); } } Reflection.setFieldValue("isUsingClickDragControl", __instance, false); __instance.hoveredCard = null; __instance.isDraggingCard = false; return SpireReturn.Return(); } else { return SpireReturn.Continue(); } } } @SpirePatch( clz = AbstractCard.class, method = "update" ) public static class TargPLayer { public TargPLayer() { } public static void Prefix(AbstractCard __instance) { if (SpireTogetherMod.isConnected && SkillCastRelic.IsAllowedSkill(__instance) && AbstractDungeon.player.isDraggingCard && __instance == AbstractDungeon.player.hoveredCard) { boolean shouldNotReset = false; Iterator pI = P2PManager.GetAllPlayers(false); while(pI.hasNext()) { P2PPlayer next = (P2PPlayer)pI.next(); if (next.IsPlayerInSameRoomAndAction() && next.IsTechnicallyAlive()) { CharacterEntity entity = next.GetEntity(); if (entity != null && (entity.hb.hovered || next.GetInfobox().IsHovered())) { __instance.target = AbstractCard.CardTarget.ENEMY; shouldNotReset = true; } } } if (!shouldNotReset) { if (AbstractDungeon.player.inSingleTargetMode) { AbstractDungeon.player.inSingleTargetMode = false; } __instance.target = AbstractCard.CardTarget.SELF; } } } }
Rewritting card effects
Now is time for work that will take as much time as many cards you're need to implement.

Before adding code I reccomend you to decompile card you want to add and look it's use function.

If card is simple (as defend) it'll be easy to change. Example with Ironclad defend:

@SpirePatch( clz = Defend_Red.class, // You'll need to change Defend_Red to name of your card class everywhere in code method = "use" ) public static class RedDefendUsePatcher { public RedDefendUsePatcher() { } public static SpireReturn Prefix(Defend_Red __instance, AbstractPlayer p, AbstractMonster m) { if (SpireTogetherMod.isConnected && m instanceof CharacterEntity) { m.addBlock(__instance.block); // This method adds block to your teammate. To see other methods you can use easily you need to decompile Together in Spire mod and find file spireTogether/monsters/CharacterEntity AbstractDungeon.effectList.add(new FlashAtkImgEffect(m.hb.cX, m.hb.cY, AbstractGameAction.AttackEffect.SHIELD)); // It's the visual effect for card. I tired to find and add them so most of skills don't have animation return SpireReturn.Return(); } else { return SpireReturn.Continue(); } } }


If card applies power or has non-usual action you'll need to make som more work. Armaments as example:

@SpirePatch( clz = Armaments.class, // As previouse you need to change Armaments to you card class name method = "use" ) public static class ArmamentsUsePatcher { public ArmamentsUsePatcher() { } public static SpireReturn Prefix(Armaments __instance, AbstractPlayer p, AbstractMonster m) { if (SpireTogetherMod.isConnected && m instanceof CharacterEntity) { Object[] argums = new Object[] { // Here you'll need to add everything that important to custom action (magic number, block, is card upgraded and such) __instance.upgraded }; m.addBlock(__instance.block); // If you're card do anything simple reccomend to make it by Together in Spire methods AbstractDungeon.effectList.add(new FlashAtkImgEffect(m.hb.cX, m.hb.cY, AbstractGameAction.AttackEffect.SHIELD)); P2PPlayer target_sous = ((CharacterEntity) m).GetPlayer(); target_sous.specialAction("ArmamentsAction", argums); return SpireReturn.Return(); } else { return SpireReturn.Continue(); } } } @SpirePatch2( clz = P2PCallbacks.class, method = "OnFFSpecialAction" ) public static class ArmamentsEffectPatch { public ArmamentsEffectPatch() { } public static void Postfix(P2PPlayer source, String actionID, Object[] params) { if (actionID.equals("ArmamentsAction")) { AbstractPlayer p = AbstractDungeon.player; // If your action need the player as parametr you can get it using whis func boolean upgr = (boolean)params[0]; // For simple reading I use new variables AbstractDungeon.actionManager.addToBottom(new ArmamentsAction(upgr)); // in addToBottom you put an action your card does (applies effect or other) } } }
Conclusion
With this info all you need is to write script for every card that will be used on teammates. And yeah, it's pretty time wasting

Anyway, if you have any questions leave them at comments and I'll try to answer them but still remember that I don't know java neither StS modding