Skip to content
265 changes: 226 additions & 39 deletions RA Scripts/Legend of Dragoon, The.rascript
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function EnteredBattleOnce() => once(Delta(battleMarker()) != battleMarker() &&
// 0x8005 = world map(?)
function battleMarker() => word(0xccdec)
function IsInBattle() => battleMarker() == 0x1021
function JustLeftBattle() => !IsInBattle() && Delta(battleMarker()) == 0x1021

// $0badae: Total items in inventory
function itemsInInventory() => byte(0x0badae)
Expand Down Expand Up @@ -300,6 +301,56 @@ achievement(
trigger = word(0x052C30) == 0x0032 && bit5(0x0BAD90) == 0x1 && prev(bit5(0x0BAD90)) == 0x0
)

function urobolusHp() => word(0x1adf44)
function itemsInInventory() => byte(0x0BADAE)
function ItemCountDecreased() => Delta(itemsInInventory()) > itemsInInventory()
function characterObjectWidth() => 0x2c
function characterSlot1Id() => byte(0x0bac50)
function characterSlot2Id() => byte(0x0bac54)
function characterSlot3Id() => byte(0x0bac58)

function Dart() => 0
function Lavitz() => 1
function Shana() => 2
function Haschel() => 4
function Albert() => 5
function Kongol() => 7
function Miranda() => 8

function CharacterIsInParty(id)
{
return characterSlot1Id() == id || characterSlot2Id() == id || characterSlot3Id() == id
}

function GetCharacterById(id)
{
characterArrayBaseAddr = 0x0baef4
addr = characterArrayBaseAddr + id * characterObjectWidth()
return {
"exp": dword(addr),
"statusFlags": byte(addr + 4),
"hp": word(addr + 8),
"level": byte(addr + 18),
"accessoryId": byte(addr + 24)
}
}

function CharacterWithIdHasItemWithIdEquipped(charId, accessoryId)
{
character = GetCharacterById(charId)
return character["accessoryId"] == accessoryId
}

function poisonGuardId() => 0x67
achievement(title = "Pharmacophobia", points = 5, id = 189207, badge = "209992",
description = "Defeat the Urobolus without using items and without equipping any Poison Guards.",
trigger = mapId() == 0x35 && enemyId() == 0x19e && EnteredBattleOnce() && never(!IsInBattle())
&& trigger_when(once(Delta(urobolusHp()) > 0 && urobolusHp() == 0) && repeated(290, urobolusHp() == 0))
&& never(ItemCountDecreased()) && never(CharacterWithIdHasItemWithIdEquipped(Dart(), poisonGuardId()))
&& never(CharacterWithIdHasItemWithIdEquipped(Lavitz(), poisonGuardId()))
&& never(CharacterWithIdHasItemWithIdEquipped(Shana(), poisonGuardId()))
)

achievement(
title = "No Ordinary Keepsake", description = "Awaken the Dragoon Spirit of the Red-Eyed Dragon.", points = 5,
id = 94790, badge = "106752", published = "1/15/2020 11:23:24 PM", modified = "2/3/2020 10:16:19 PM",
Expand All @@ -321,6 +372,140 @@ achievement(
unless(byte(0x0BAC58) == 0x03) && never(word(0x1AC50C) == 0x0000) && never(word(0x1AC184) == 0x0000)))
)

function playerSlot1Addr() => 0x0bc1d8
function playerSlot2Addr() => 0x0bc1dc
function playerSlot3Addr() => 0x0bc1e0
function enemySlot1Addr() => 0x0bc1ec
function enemySlot2Addr() => 0x0bc1f0
function enemySlot3Addr() => 0x0bc1f4
function garbagePointer() => 0xbc0c0

function PointerIsValid(addr) => Delta(tbyte(addr)) != garbagePointer()
function GetCombatantInGivenSlotAddr(addr)
{
return {
"hp": word(tbyte(addr) + 0x108),
"mp": word(tbyte(addr) + 0x10c)
}
}

function CombatantInSlotAddrWasJustDefeated(addr)
{
combatant = GetCombatantInGivenSlotAddr(addr)
return PointerIsValid(addr) && Delta(combatant["hp"]) > 0 && combatant["hp"] == 0
}

function CombatantInSlotAddrWasAlreadyDefeated(addr)
{
combatant = GetCombatantInGivenSlotAddr(addr)
return PointerIsValid(addr) && Delta(combatant["hp"]) == 0 && combatant["hp"] == 0
}

function AllCombatantsInGivenSlotAddrsWereJustDefeated(slots, useTrigger = true, deferTime = 0)
{
allGivenCombatantsDefeated = always_true()
anyGivenCombatantWasJustDefeated = always_false()
ret = always_false()
for slot in slots
{
combatant = GetCombatantInGivenSlotAddr(slot)
allGivenCombatantsDefeated = allGivenCombatantsDefeated && combatant["hp"] == 0 && PointerIsValid(slot)
wasJustDefeated = CombatantInSlotAddrWasJustDefeated(slot)

anyGivenCombatantWasJustDefeated = anyGivenCombatantWasJustDefeated || wasJustDefeated
}

if (deferTime > 0)
{
ret = repeated(deferTime, allGivenCombatantsDefeated) && anyGivenCombatantWasJustDefeated
}
else
{
ret = allGivenCombatantsDefeated && anyGivenCombatantWasJustDefeated
}

if (useTrigger == true)
{
ret = trigger_when(ret)
}

return ret
}

function slot1DragoonTurnsRemaining() => byte(0x06e62c)
function slot2DragoonTurnsRemaining() => byte(0x06e630)
function slot3DragoonTurnsRemaining() => byte(0x06e634)

function DragoonTurnConsumedBySlot(slotId)
{
if (slotId == 1)
{
return Delta(slot1DragoonTurnsRemaining()) > slot1DragoonTurnsRemaining()
}
else if (slotId == 2)
{
return Delta(slot2DragoonTurnsRemaining()) > slot2DragoonTurnsRemaining()
}
else
{
return Delta(slot3DragoonTurnsRemaining()) > slot3DragoonTurnsRemaining()
}
}

function turnCounter() => dword(0x0bac80)
function TurnConsumed() => Delta(turnCounter()) < turnCounter()

function GetPlayerAddrFromSlot(slotId)
{
if (slotId == 1)
{
return playerSlot1Addr()
}
else if (slotId == 2)
{
return playerSlot2Addr()
}
else
{
return playerSlot3Addr()
}
}

function PlayerUsedMagicThisTurn()
{
trigger = always_false()
for slot in range(1, 3)
{
slotAddr = GetPlayerAddrFromSlot(slot)
combatant = GetCombatantInGivenSlotAddr(slotAddr)
trigger = trigger || unless(!PointerIsValid(slotAddr)) && trigger_when(once(Delta(combatant["mp"]) > combatant["mp"] && never(TurnConsumed())))
}

return trigger
}

function PlayerJustUsedMagic()
{
trigger = always_false()
for slot in range(1, 3)
{
slotAddr = GetPlayerAddrFromSlot(slot)
combatant = GetCombatantInGivenSlotAddr(slotAddr)
// todo: use `trigger_when()` around the `once()` statement when https://github.com/Jamiras/RATools/issues/272 is addressed.
trigger = trigger || PointerIsValid(slotAddr) && once(Delta(combatant["mp"]) > combatant["mp"])
}

return trigger
}

function Feyrbrand() => enemySlot1Addr()
function Greham() => enemySlot2Addr()
achievement(title = "No Revenge Like Overkill", description = "Finish the battle against Greham and Feyrbrand with a Dragoon spell.", points = 10,
id = 189209, badge = "209994",
trigger = mapId() == 0x290 && enemyId() == 0x189 && EnteredBattleOnce() && never(!IsInBattle())
&& AllCombatantsInGivenSlotAddrsWereJustDefeated([ Feyrbrand(), Greham() ], true) && PlayerUsedMagicThisTurn()
)

achievement(
title = "You Killed My Father. Prepare to Die.", description = "Acquire the Dragoon Spirit of the Jade Dragon.", points = 5,
id = 94793, badge = "106749", published = "1/15/2020 11:26:00 PM", modified = "2/3/2020 2:19:05 AM",
Expand Down Expand Up @@ -368,14 +553,14 @@ achievement(
word(0x052C30) == 0x027E
)

function monsterConquestAddr() => tbyte(0x0bc1e8)
function monsterConquestScore() => dword(monsterConquestAddr() + 0x7c)
achievement(
title = "Monkeyball Master", description = "Get a score of 100 or more in the Monster Conquest Game Stand minigame.", points = 5,
id = 94798, badge = "106744", published = "1/15/2020 11:26:27 PM", modified = "2/4/2020 10:54:22 PM",
trigger = word(0x052C30) == 0x0098 && never(word(0x052C30) != 0x0098) &&
((dword(0x195DD4) >= 0x00000064 && dword(0x195DD4) != 0xFFFFFFFF && once(word(0x195DD4) == 0xFFFF) &&
word(0x0BAC60) == 0x0000) ||
(word(0x0BAC60) > 0x0000 && dword(0x195054) >= 0x00000064 && dword(0x195054) != 0xFFFFFFFF &&
once(word(0x195054) == 0xFFFF)))
trigger = mapId() == 0x98 && never(mapId() != 0x98) &&
never(!PointerIsValid(monsterConquestAddr())) && monsterConquestScore() >= 100 && monsterConquestScore() != 0xffffffff
&& once(monsterConquestScore() == 0xffffffff)
)

achievement(
Expand Down Expand Up @@ -435,6 +620,15 @@ achievement(
trigger = word(0x0BB0F8) == 0x0183 && word(0x0BC95C) == 0x07D0
)

function Doel() => enemySlot1Addr()
function DragoonDoel() => enemySlot2Addr()
achievement(title = "Electrical Paralysis", points = 5, id = 189208, badge = "209993",
description = "Defeat Emperor Doel without using Dragoon magic.",
trigger = mapId() == 0x294 && enemyId() == 0x186 && EnteredBattleOnce() && never(!IsInBattle())
&& AllCombatantsInGivenSlotAddrsWereJustDefeated([ Doel(), DragoonDoel() ], true)
&& never(PlayerJustUsedMagic())
)

// To make sure the achievement doesn't trigger during a game over while still letting it trigger during the
// transition from the cutscene to the results screen, we're checking to make sure that the map ID changes
// for about a half second before triggering--because, OF COURSE, the map ID changes to the next one for
Expand Down Expand Up @@ -557,9 +751,10 @@ achievement(
achievement(
title = "Magician's Hat-Trick", description = "With Albert and Kongol in your party, defeat the Magician Bogy Trio simultaneously.", points = 5,
id = 97752, badge = "107878", published = "2/7/2020 5:10:49 AM", modified = "2/13/2020 10:38:15 PM",
trigger = word(0x0BB0F8) == 0x01E9 && word(0x052C30) == 0x0125 && word(0x0CCDEC) == 0x1021 &&
prev(word(0x1AD694)) > 0x0000 && word(0x1AD694) == 0x0000 && prev(word(0x1AD30C)) > 0x0000 && word(0x1AD30C) == 0x0000 &&
prev(word(0x1ACF84)) > 0x0000 && word(0x1ACF84) == 0x0000
trigger = enemyId() == 0x01E9 && mapId() == 0x0125
&& EnteredBattleOnce() && never(!IsInBattle())
&& CombatantInSlotAddrWasJustDefeated(enemySlot1Addr()) && CombatantInSlotAddrWasJustDefeated(enemySlot2Addr()) && CombatantInSlotAddrWasJustDefeated(enemySlot3Addr())
&& never(!CharacterIsInParty(Albert())) && never(!CharacterIsInParty(Kongol()))
)

achievement(
Expand All @@ -579,11 +774,12 @@ achievement(

achievement(
title = "Muggles Do It Better", description = "Defeat Lenus and Regole without using Dragoon transformations.", points = 5,
id = 98008, badge = "108035", published = "2/8/2020 11:03:41 PM", modified = "2/13/2020 10:38:26 PM",
trigger = once(prev(byte(0x06E648)) != 0xFF && byte(0x06E648) == 0xFF) && word(0x0CCDEC) == 0x1021 &&
id = 98008, badge = "108035", published = "2/8/2020 11:03:41 PM", modified = "2/13/2020 10:38:26 PM", type = "missable",
trigger = EnteredBattleOnce() && word(0x0CCDEC) == 0x1021 &&
((never(byte(0x06E62C) > 0x00) && never(byte(0x06E630) > 0x00) && never(byte(0x06E634) > 0x00) &&
unless(byte(0x06E62C) == 0x28) && unless(byte(0x06E630) == 0x2C) && unless(byte(0x06E634) == 0x30) && always_false()) ||
(prev(word(0x0BC95C)) == 0x1B58 && word(0x0BC95C) == 0x0000))
&& never(!IsInBattle())
)

achievement(
Expand Down Expand Up @@ -712,12 +908,23 @@ achievement(
(word(0x1AD824) == 0x0000 && prev(word(0x1AD824)) == 0x0003))
)

function slot1DragoonTurnsRemaining() => byte(0x06E62C)
function slot2DragoonTurnsRemaining() => byte(0x06E630)
function slot3DragoonTurnsRemaining() => byte(0x06E634)
function slotIdOfDragoonSpecial() => byte(0x06E648)
function DragoonLloyd() => enemySlot1Addr()

// This achievement needs a rework. We need to ensure the player actually spends at least six turns in a Dragoon Special. The player can simply wait to pop the special until the end, which
// was not intended.
// Additionally, we need to specify 0 HP instead of defeating him because Lloyd will always do an AoE attack when he's brought to 0 HP. This will have to be specified until I can revise this
// further and trigger the achievement after the cutscene like before.
achievement(
title = "Confidence, or Recklessness?", description = "Defeat Lloyd after triggering at least a second-level Dragoon Special at least once.", points = 10,
title = "Confidence, or Recklessness?", description = "Bring Lloyd down to 0 HP after triggering at least a second-level Dragoon Special at least once.", points = 10,
id = 98127, badge = "108055", published = "2/9/2020 9:25:08 PM", modified = "2/13/2020 10:38:38 PM",
trigger = word(0x0BC95C) == 0x2EE0 && once(byte(0x06E62C) > 0x01 && byte(0x06E630) > 0x01 && byte(0x06E634) > 0x01
&& prev(byte(0x06E648)) == 0xFF && byte(0x06E648) != 0xFF) &&
never(prev(word(0x0CCDEC)) != 0x1021 && word(0x0CCDEC) == 0x1021) && word(0x0BB0F8) == 0x0188
trigger = trigger_when(once(slot1DragoonTurnsRemaining() > 1 && slot2DragoonTurnsRemaining() > 1 && slot3DragoonTurnsRemaining() > 1
&& Delta(slotIdOfDragoonSpecial()) == 0xFF && slotIdOfDragoonSpecial() != 0xFF))
&& never(JustLeftBattle()) && enemyId() == 0x0188
&& AllCombatantsInGivenSlotAddrsWereJustDefeated([ DragoonLloyd() ], true)
)

achievement(
Expand Down Expand Up @@ -765,27 +972,6 @@ achievement(
trigger = prev(word(0x052C30)) == 0x023C && word(0x052C30) == 0x0210
)

achievement(
title = "Protectionist Parliamentarian", description = "Repeal the law permitting use of the shop next to the Law Factory in Zenebatos.", points = 1,
id = 96106, badge = "106738", published = "1/26/2020 7:52:18 PM", modified = "2/3/2020 2:50:34 AM",
trigger = word(0x052C30) == 0x0218 && prev(bit0(0x0BAD68)) == 0x1 && bit0(0x0BAD68) == 0x0 && bit0(0x0BACDA) == 0x1 &&
prev(bit0(0x0BACDA)) == 0x0
)

achievement(
title = "Trespassers Will Be Ignored", description = "Prohibit the Zenebatos guards from arresting you by changing the law.", points = 1,
id = 96108, badge = "106738", published = "1/26/2020 8:05:07 PM", modified = "2/3/2020 2:50:37 AM",
trigger = word(0x052C30) == 0x0218 && prev(bit0(0x0BAD68)) == 0x1 && bit0(0x0BAD68) == 0x0 && bit5(0x0BACD9) == 0x1 &&
prev(bit5(0x0BACD9)) == 0x0
)

achievement(
title = "No Lines, No Waiting", description = "Repeal the law in Zenebatos requiring the need to queue at the Legislation Center.", points = 1,
id = 96111, badge = "106738", published = "1/26/2020 8:13:46 PM", modified = "2/3/2020 2:50:30 AM",
trigger = word(0x052C30) == 0x0218 && prev(bit0(0x0BAD68)) == 0x1 && bit0(0x0BAD68) == 0x0 && bit4(0x0BACD9) == 0x1 &&
prev(bit4(0x0BACD9)) == 0x0
)

achievement(
title = "Jury Nullification", description = "With Meru and Miranda in your party, defeat Kubila, Selebus, and Vector simultaneously.", points = 10,
id = 98143, badge = "108080", published = "2/11/2020 1:16:21 AM", modified = "2/25/2020 10:08:04 PM",
Expand Down Expand Up @@ -839,12 +1025,12 @@ achievement(
(word(0x0CCDEC) == 0x0000 && word(0x0BC95C) == 0x1770 && word(0x0BC920) == 0x012C))
)

function DivineDragonSpirit() => enemySlot2Addr()
achievement(
title = "Ful, Losei Dovahkiin?", description = "Defeat the spirit of the Divine Dragon in Mayfil.", points = 25,
id = 96124, badge = "106736", published = "1/26/2020 9:17:07 PM", modified = "2/3/2020 2:53:03 AM",
trigger = word(0x0BB0F8) == 0x01BF && word(0x052C30) == 0x0222 && word(0x0BB0F4) == 0x0044 &&
((prev(word(0x0BC95C)) == 0x1F40 && word(0x0BC95C) == 0x0000) ||
(word(0x0CCDEC) == 0x0000 && word(0x0BC95C) == 0x1F40 && word(0x0BC920) == 0x0190))
trigger = mapId() == 0x0222 && enemyId() == 0x01BF && EnteredBattleOnce() && never(!IsInBattle())
&& AllCombatantsInGivenSlotAddrsWereJustDefeated([ DivineDragonSpirit() ], false)
)

achievement(
Expand Down Expand Up @@ -893,9 +1079,10 @@ achievement(

achievement(
title = "Leave No Trace", description = "With Kongol and Haschel in your party, destroy the arm of the Super Virage on the Moon before either its head or its body.", points = 5,
id = 98230, badge = "108084", published = "2/12/2020 12:39:52 AM", modified = "2/13/2020 10:39:00 PM",
id = 98230, badge = "108084", published = "2/12/2020 12:39:52 AM", modified = "2/13/2020 10:39:00 PM", type = "missable",
trigger = word(0x0BC95C) == 0x3A98 && word(0x0BB0F8) == 0x019B && never(word(0x052C30) != 0x02DD) &&
(always_false() || (once(prev(word(0x0BAC80)) != word(0x0BAC80)) && unless(word(0x1AB01C) > 0x0000)))
&& never(!CharacterIsInParty(Haschel())) && never(!CharacterIsInParty(Kongol()))
)

achievement(
Expand Down