Merge pull request #91 from rh-hideout/master

sync
This commit is contained in:
RoamerX 2026-02-09 15:39:34 +08:00 committed by GitHub
commit 49e2d8fba5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 138 additions and 11 deletions

View File

@ -67,3 +67,46 @@ A: Shadows can be disabled for certain locations by modifying the `CurrentMapHas
### Q: How do I change the default light-blend color?
A: The default color is handled by the `#define DEFAULT_LIGHT_COLOR` in `src/palette.c`.
### Q. How do I use alternate nighttime palettes?
In addition to palette tinting, the DNS allows tilesets to define alternate nighttime palettes.
These palettes are automatically blended with their corresponding daytime palettes with the passage of time.
Each tileset has `16` total palette slots. `3` of the BG palettes (`13 - 15`) are reserved for the UI leaving `13` (`0-12`) usable for tilesets. Because primary tilesets load `6` (`NUM_PALS_IN_PRIMARY`) palettes (`0-5`) and secondary Tilesets load `7` palettes (6-12), some slots are unused for each. DNS repurposes these unused slots to store alternate nighttime palettes.
To avoid overlap with active palettes, each nighttime palette is stored in a different slot determined by the formula: `night_pal = (day_pal + 9) % 16`
**Day palette index vs. Night palette index**
| Day | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|-----|---|---|---|---|---|---|---|---|---|----|----|----|----|----|----|----|
| Night | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
For instance, in a secondary Tileset (slots `6-12`), the nighttime palettes for index `8` would be stored at index `1` (which is unused in a secondary tileset). Slots `0` and `13-15` are not used for blending so the corresponding slots `9` and `6-8` are never used for nighttime palettes.
_Note that palette `0` is not used for blending._
Once the appropriate nighttime `.pal` file has been added, add a `swapPalettes` field to the tileset struct definition in `src/data/tilesets/headers.h`. The macro `SWAP_PAL(x)` is provided for this purpose.
`swapPalettes` is a bitmask so to use nighttime palettes for indices 7 and 9 in the Petalburg Tileset we add a `swapPalettes` with `SWAP_PAL(7) | SWAP_PAL(9)`.
Note that the palette index to specify here is the palette index that you want to swap at night (**NOT** the corresponding nighttime palette).
```diff
const struct Tileset gTileset_Petalburg =
{
.isCompressed = TRUE,
.isSecondary = TRUE,
.tiles = gTilesetTiles_Petalburg,
+ .swapPalettes = SWAP_PAL(7) | SWAP_PAL(9), // Enable nighttime variants for slots 7 and 9
.palettes = gTilesetPalettes_Petalburg,
.metatiles = gMetatiles_Petalburg,
.metatileAttributes = gMetatileAttributes_Petalburg,
.callback = InitTilesetAnim_Petalburg,
};
```

View File

@ -3162,23 +3162,31 @@ static u32 GetPoisonDamage(u32 battlerId)
return damage;
}
static bool32 BattlerAffectedBySandstorm(u32 battlerId, enum Ability ability)
static bool32 DoesBattlerTakeSandstormDamage(u32 battlerId, enum Ability ability)
{
if (!(AI_GetWeather() & B_WEATHER_SANDSTORM))
return FALSE;
if (!IS_BATTLER_ANY_TYPE(battlerId, TYPE_ROCK, TYPE_GROUND, TYPE_STEEL)
&& ability != ABILITY_SAND_VEIL
&& ability != ABILITY_SAND_FORCE
&& ability != ABILITY_SAND_RUSH
&& ability != ABILITY_MAGIC_GUARD
&& ability != ABILITY_OVERCOAT)
return TRUE;
return FALSE;
}
static bool32 BattlerAffectedByHail(u32 battlerId, enum Ability ability)
static bool32 DoesBattlerTakeHailDamage(u32 battlerId, enum Ability ability)
{
if (!(AI_GetWeather() & B_WEATHER_HAIL))
return FALSE;
if (!IS_BATTLER_OF_TYPE(battlerId, TYPE_ICE)
&& ability != ABILITY_SNOW_CLOAK
&& ability != ABILITY_OVERCOAT
&& ability != ABILITY_ICE_BODY)
&& ability != ABILITY_ICE_BODY
&& ability != ABILITY_MAGIC_GUARD
&& ability != ABILITY_OVERCOAT)
return TRUE;
return FALSE;
}
@ -3194,7 +3202,7 @@ static u32 GetWeatherDamage(u32 battlerId)
if (weather & B_WEATHER_SANDSTORM)
{
if (BattlerAffectedBySandstorm(battlerId, ability)
if (DoesBattlerTakeSandstormDamage(battlerId, ability)
&& gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERGROUND
&& gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERWATER
&& holdEffect != HOLD_EFFECT_SAFETY_GOGGLES)
@ -3206,7 +3214,7 @@ static u32 GetWeatherDamage(u32 battlerId)
}
if ((weather & B_WEATHER_HAIL) && ability != ABILITY_ICE_BODY)
{
if (BattlerAffectedByHail(battlerId, ability)
if (DoesBattlerTakeHailDamage(battlerId, ability)
&& gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERGROUND
&& gBattleMons[battlerId].volatiles.semiInvulnerable != STATE_UNDERWATER
&& holdEffect != HOLD_EFFECT_SAFETY_GOGGLES)
@ -3238,8 +3246,11 @@ u32 GetBattlerSecondaryDamage(u32 battlerId)
bool32 BattlerWillFaintFromWeather(u32 battler, enum Ability ability)
{
if ((BattlerAffectedBySandstorm(battler, ability) || BattlerAffectedByHail(battler, ability))
&& gBattleMons[battler].hp <= max(1, gBattleMons[battler].maxHP / 16))
if (gAiLogicData->holdEffects[battler] == HOLD_EFFECT_SAFETY_GOGGLES)
return FALSE;
if ((DoesBattlerTakeSandstormDamage(battler, ability) || DoesBattlerTakeHailDamage(battler, ability))
&& gBattleMons[battler].hp <= max(1, GetNonDynamaxMaxHP(battler) / 16))
return TRUE;
return FALSE;

View File

@ -4442,7 +4442,7 @@ static void Cmd_jumpifability(void)
}
break;
case BS_TARGET_SIDE:
battler = IsAbilityOnOpposingSide(gBattlerAttacker, ability);
battler = IsAbilityOnSide(gBattlerTarget, ability);
if (battler)
{
battler--;

View File

@ -9703,8 +9703,11 @@ u32 TryImmunityAbilityHealStatus(u32 battler, enum AbilityEffect caseID)
}
gBattleScripting.battler = gBattlerAbility = battler;
if (effect == 1) // Only primary status changes should sync party status.
{
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_STATUS_BATTLE, 0, 4, &gBattleMons[battler].status1);
MarkBattlerForControllerExec(battler);
}
return effect;
}

View File

@ -22,6 +22,24 @@ DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Taunt")
}
}
DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from ally Taunt")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_TAUNT) == EFFECT_TAUNT);
ASSUME(GetMoveCategory(MOVE_HARDEN) == DAMAGE_CATEGORY_STATUS);
PLAYER(SPECIES_AROMATISSE) { Ability(ABILITY_AROMA_VEIL); Speed(1); }
PLAYER(SPECIES_WOBBUFFET) { Speed(2); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(3); }
OPPONENT(SPECIES_WYNAUT) { Speed(4); }
} WHEN {
TURN { MOVE(playerRight, MOVE_TAUNT, target: playerLeft); MOVE(playerLeft, MOVE_HARDEN); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, playerRight);
ABILITY_POPUP(playerLeft, ABILITY_AROMA_VEIL);
ANIMATION(ANIM_TYPE_MOVE, MOVE_HARDEN, playerLeft);
}
}
DOUBLE_BATTLE_TEST("Aroma Veil protects the Pokémon's side from Torment")
{
struct BattlePokemon *moveTarget = NULL;

View File

@ -99,3 +99,29 @@ SINGLE_BATTLE_TEST("Oblivious prevents Intimidate (Gen8+)")
MESSAGE("Slowpoke's Oblivious prevents stat loss!");
}
}
SINGLE_BATTLE_TEST("Oblivious cured infatuation should not persist toxic counter after switching")
{
s16 firstTick, secondTick, postSwitchTick;
GIVEN {
ASSUME(GetMoveEffect(MOVE_ATTRACT) == EFFECT_ATTRACT);
ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP);
PLAYER(SPECIES_WOBBUFFET) { Gender(MON_MALE); Status1(STATUS1_TOXIC_POISON); MaxHP(160); HP(160); Speed(100); }
PLAYER(SPECIES_WYNAUT) { Speed(90); }
OPPONENT(SPECIES_SLOWPOKE) { Gender(MON_FEMALE); Ability(ABILITY_OBLIVIOUS); Speed(80); }
} WHEN {
TURN { MOVE(opponent, MOVE_ATTRACT); }
TURN { MOVE(opponent, MOVE_SKILL_SWAP); }
TURN { SWITCH(player, 1); }
TURN { SWITCH(player, 0); }
} SCENE {
HP_BAR(player, captureDamage: &firstTick);
HP_BAR(player, captureDamage: &secondTick);
HP_BAR(player, captureDamage: &postSwitchTick);
} THEN {
EXPECT_EQ(firstTick, 10);
EXPECT_EQ(secondTick, 20);
EXPECT_EQ(postSwitchTick, 10);
}
}

View File

@ -143,3 +143,29 @@ SINGLE_BATTLE_TEST("Own Tempo prevents confusion from items")
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
}
}
SINGLE_BATTLE_TEST("Own Tempo cured confusion should not persist toxic counter after switching")
{
s16 firstTick, secondTick, postSwitchTick;
GIVEN {
ASSUME(GetMoveEffect(MOVE_CONFUSE_RAY) == EFFECT_CONFUSE);
ASSUME(GetMoveEffect(MOVE_SKILL_SWAP) == EFFECT_SKILL_SWAP);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_TOXIC_POISON); MaxHP(160); HP(160); Speed(100); }
PLAYER(SPECIES_WYNAUT) { Speed(90); }
OPPONENT(SPECIES_SLOWPOKE) { Ability(ABILITY_OWN_TEMPO); Speed(80); }
} WHEN {
TURN { MOVE(opponent, MOVE_CONFUSE_RAY); }
TURN { MOVE(opponent, MOVE_SKILL_SWAP); MOVE(player, MOVE_CELEBRATE, WITH_RNG(RNG_CONFUSION, FALSE)); }
TURN { SWITCH(player, 1); }
TURN { SWITCH(player, 0); }
} SCENE {
HP_BAR(player, captureDamage: &firstTick);
HP_BAR(player, captureDamage: &secondTick);
HP_BAR(player, captureDamage: &postSwitchTick);
} THEN {
EXPECT_EQ(firstTick, 10);
EXPECT_EQ(secondTick, 20);
EXPECT_EQ(postSwitchTick, 10);
}
}