Skip to content

Commit

Permalink
Fix Instruct bypassing AtkCanceler, Entry Hazards targeting wrong sid…
Browse files Browse the repository at this point in the history
…e of the field if opponent fainted (#5262)

* Fix Instruct bypassing AtkCanceler + interaction with firstTurnOnly moves

* Fix Entry Hazards targeting the wrong side of the field if opponent fainted

* Remove unused BattleScript_AuroraVeilEnds and STRINGID_AURORAVEILENDS

* Alternative fix + Fix gBattlerTarget initially set to 2

* Fix Instruct animation

* Alternative anim fix
  • Loading branch information
PhallenTree authored Aug 27, 2024
1 parent c406c4c commit 930cac6
Show file tree
Hide file tree
Showing 8 changed files with 532 additions and 281 deletions.
2 changes: 2 additions & 0 deletions data/battle_scripts_1.s
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,8 @@ BattleScript_EffectInstruct::
tryinstruct BattleScript_ButItFailed
attackanimation
waitanimation
copybyte gBattlerAttacker, gBattlerTarget
copybyte gBattlerTarget, gEffectBattler
printstring STRINGID_USEDINSTRUCTEDMOVE
waitmessage B_WAIT_TIME_LONG
setbyte sB_ANIM_TURN, 0
Expand Down
1 change: 0 additions & 1 deletion include/battle_scripts.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ extern const u8 BattleScript_EmbargoEndTurn[];
extern const u8 BattleScript_TelekinesisEndTurn[];
extern const u8 BattleScript_BufferEndTurn[];
extern const u8 BattleScript_AquaRingHeal[];
extern const u8 BattleScript_AuroraVeilEnds[];
extern const u8 BattleScript_LuckyChantEnds[];
extern const u8 BattleScript_TailwindEnds[];
extern const u8 BattleScript_TrickRoomEnds[];
Expand Down
539 changes: 269 additions & 270 deletions include/constants/battle_string_ids.h

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions src/battle_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,6 @@ static const u8 sText_TargetAbilityRaisedStat[] = _("{B_DEF_NAME_WITH_PREFIX}'s
static const u8 sText_TargetAbilityLoweredStat[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY}\nlowered its {B_BUFF1}!");
static const u8 sText_AttackerAbilityRaisedStat[] = _("{B_ATK_NAME_WITH_PREFIX}'s {B_ATK_ABILITY}\nraised its {B_BUFF1}!");
static const u8 sText_ScriptingAbilityRaisedStat[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX}'s {B_SCR_ACTIVE_ABILITY}\nraised its {B_BUFF1}!");
static const u8 sText_AuroraVeilEnds[] = _("{B_DEF_NAME_WITH_PREFIX}'s {B_DEF_ABILITY}\nwore off!");
static const u8 sText_ElectricTerrainEnds[] = _("The electricity disappeared\nfrom the battlefield.");
static const u8 sText_MistyTerrainEnds[] = _("The mist disappeared\nfrom the battlefield.");
static const u8 sText_PsychicTerrainEnds[] = _("The weirdness disappeared\nfrom the battlefield.");
Expand Down Expand Up @@ -1442,7 +1441,6 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_WATERSPORTENDS - BATTLESTRINGS_TABLE_START] = sText_WaterSportEnds,
[STRINGID_GRAVITYENDS - BATTLESTRINGS_TABLE_START] = sText_GravityEnds,
[STRINGID_AQUARINGHEAL - BATTLESTRINGS_TABLE_START] = sText_AquaRingHeal,
[STRINGID_AURORAVEILENDS - BATTLESTRINGS_TABLE_START] = sText_AuroraVeilEnds,
[STRINGID_ELECTRICTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_ElectricTerrainEnds,
[STRINGID_MISTYTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_MistyTerrainEnds,
[STRINGID_PSYCHICTERRAINENDS - BATTLESTRINGS_TABLE_START] = sText_PsychicTerrainEnds,
Expand Down
10 changes: 5 additions & 5 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -9957,23 +9957,23 @@ static void Cmd_various(void)
else
{
gSpecialStatuses[gBattlerTarget].instructedChosenTarget = *(gBattleStruct->moveTarget + gBattlerTarget) | 0x4;
gBattlerAttacker = gBattlerTarget;
gCalledMove = move;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBattleMons[gBattlerAttacker].moves[i] == gCalledMove)
if (gBattleMons[gBattlerTarget].moves[i] == gCalledMove)
{
gCurrMovePos = i;
i = 4;
break;
}
}
if (i != 4 || gBattleMons[gBattlerAttacker].pp[gCurrMovePos] == 0)
if (i != 4 || gBattleMons[gBattlerTarget].pp[gCurrMovePos] == 0)
gBattlescriptCurrInstr = cmd->failInstr;
else
{
gBattlerTarget = gBattleStruct->lastMoveTarget[gBattlerAttacker];
gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget];
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED;
gBattleStruct->atkCancellerTracker = 0;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
Expand Down Expand Up @@ -11408,7 +11408,7 @@ static void Cmd_jumpifnotfirstturn(void)

const u8 *jumpInstr = cmd->jumpInstr;

if (gDisableStructs[gBattlerAttacker].isFirstTurn)
if (gDisableStructs[gBattlerAttacker].isFirstTurn && !(gSpecialStatuses[gBattlerAttacker].instructedChosenTarget))
gBattlescriptCurrInstr = cmd->nextInstr;
else
gBattlescriptCurrInstr = jumpInstr;
Expand Down
8 changes: 5 additions & 3 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ void HandleAction_UseMove(void)
else
{
gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker);
if (!IsBattlerAlive(gBattlerTarget))
if (!IsBattlerAlive(gBattlerTarget) && moveTarget != MOVE_TARGET_OPPONENTS_FIELD)
{
if (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))
{
Expand Down Expand Up @@ -3233,7 +3233,7 @@ u8 AtkCanceller_UnableToUseMove(u32 moveType)
case CANCELLER_FLAGS: // flags clear
gBattleMons[gBattlerAttacker].status2 &= ~STATUS2_DESTINY_BOND;
gStatuses3[gBattlerAttacker] &= ~STATUS3_GRUDGE;
gStatuses4[gBattlerAttacker] &= ~ STATUS4_GLAIVE_RUSH;
gStatuses4[gBattlerAttacker] &= ~STATUS4_GLAIVE_RUSH;
gBattleStruct->atkCancellerTracker++;
break;
case CANCELLER_SKY_DROP:
Expand Down Expand Up @@ -8302,11 +8302,13 @@ u32 GetMoveTarget(u16 move, u8 setTarget)
case MOVE_TARGET_DEPENDS:
case MOVE_TARGET_BOTH:
case MOVE_TARGET_FOES_AND_ALLY:
case MOVE_TARGET_OPPONENTS_FIELD:
targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)));
if (!IsBattlerAlive(targetBattler))
targetBattler ^= BIT_FLANK;
break;
case MOVE_TARGET_OPPONENTS_FIELD:
targetBattler = GetBattlerAtPosition(BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker)));
break;
case MOVE_TARGET_RANDOM:
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, move))
Expand Down
218 changes: 218 additions & 0 deletions test/battle/move_effect/instruct.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#include "global.h"
#include "test/battle.h"

ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_INSTRUCT].effect == EFFECT_INSTRUCT);
}

DOUBLE_BATTLE_TEST("Instruct fails if target hasn't made a move")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct fails if move is banned by Instruct")
{
ASSUME(gMovesInfo[MOVE_BIDE].instructBanned == TRUE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_BIDE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_BIDE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BIDE, playerRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
}
}

DOUBLE_BATTLE_TEST("Instruct-called move targets the target of the move picked on its last use")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
HP_BAR(opponentLeft);
NOT HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
HP_BAR(opponentLeft);
NOT HP_BAR(opponentRight);
}
}

DOUBLE_BATTLE_TEST("Instruct doesn't bypass sleep")
{
ASSUME(gMovesInfo[MOVE_SPORE].effect == EFFECT_SLEEP);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(opponentLeft, MOVE_SPORE, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
MESSAGE("Wobbuffet is fast asleep.");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct fails if target doesn't know the last move it used")
{
ASSUME(gMovesInfo[MOVE_DRAGON_DANCE].danceMove == TRUE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_ORICORIO) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_DRAGON_DANCE); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, opponentLeft);
ABILITY_POPUP(playerRight, ABILITY_DANCER);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DRAGON_DANCE, playerRight);
}
}
}

DOUBLE_BATTLE_TEST("Instruct-called move fails if it can only be used on the first turn but consumes PP")
{
ASSUME(gMovesInfo[MOVE_FAKE_OUT].effect == EFFECT_FIRST_TURN_ONLY);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, playerRight);
} THEN {
EXPECT_EQ(playerRight->pp[3], gMovesInfo[MOVE_FAKE_OUT].pp - 2);
}
}

DOUBLE_BATTLE_TEST("Instruct-called move doesn't fail if tormented")
{
ASSUME(gMovesInfo[MOVE_TORMENT].effect == EFFECT_TORMENT);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_FAKE_OUT); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_TORMENT, target: playerRight); MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TORMENT, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct-called status moves don't fail if holding Assault Vest")
{
ASSUME(gItemsInfo[ITEM_ASSAULT_VEST].holdEffect == HOLD_EFFECT_ASSAULT_VEST);
ASSUME(gMovesInfo[MOVE_TRICK].effect == EFFECT_TRICK);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_TRICK); }
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_ASSAULT_VEST); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TRICK, target: opponentLeft); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK, playerRight);
}
}

DOUBLE_BATTLE_TEST("Instruct-called status move fails if taunted")
{
ASSUME(gMovesInfo[MOVE_TAUNT].effect == EFFECT_TAUNT);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_GROWL); MOVE(opponentLeft, MOVE_TAUNT, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
}
} THEN {
EXPECT_EQ(playerRight->pp[3], gMovesInfo[MOVE_GROWL].pp - 1);
}
}

DOUBLE_BATTLE_TEST("Instruct-called moves fail if disabled")
{
ASSUME(gMovesInfo[MOVE_DISABLE].effect == EFFECT_DISABLE);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_GROWL); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_TACKLE, target: opponentLeft); MOVE(opponentLeft, MOVE_DISABLE, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DISABLE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
} THEN {
EXPECT_EQ(playerRight->pp[0], gMovesInfo[MOVE_TACKLE].pp - 1);
}
}

DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority")
{
ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1);
ASSUME(gMovesInfo[MOVE_PSYCHIC_TERRAIN].effect == EFFECT_PSYCHIC_TERRAIN);
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET) { Moves(MOVE_TACKLE, MOVE_POUND, MOVE_SCRATCH, MOVE_QUICK_ATTACK); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_QUICK_ATTACK, target: opponentLeft); MOVE(opponentLeft, MOVE_PSYCHIC_TERRAIN, target: playerRight); MOVE(playerLeft, MOVE_INSTRUCT, target: playerRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PSYCHIC_TERRAIN, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, playerRight);
}
}
33 changes: 33 additions & 0 deletions test/battle/move_effect/sticky_web.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,36 @@ DOUBLE_BATTLE_TEST("Sticky Web has correct interactions with Mirror Armor - no o
EXPECT_EQ(opponentRight->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Sticky Web is placed on the correct side after Explosion")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_EXPLOSION); MOVE(opponent, MOVE_STICKY_WEB); SEND_OUT(player, 1);}
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_EXPLOSION, player);
MESSAGE("Wobbuffet fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
MESSAGE("A sticky web spreads out on the ground around your team!");
}
}

SINGLE_BATTLE_TEST("Sticky Web is placed on the correct side after Memento")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_MEMENTO); MOVE(opponent, MOVE_STICKY_WEB); SEND_OUT(player, 1); }
} SCENE {
HP_BAR(player, hp: 0);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, player);
MESSAGE("Wobbuffet fainted!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, opponent);
MESSAGE("A sticky web spreads out on the ground around your team!");
}
}

0 comments on commit 930cac6

Please sign in to comment.