Skip to content
Jamiras edited this page Jul 29, 2020 · 22 revisions
NOTE: This page has been split into several subpages. Please use the sidebar to navigate to the appropriate subpage.

RAScript is a domain-specific language (DSL) that allows achievement developers for retroachievements.org the ability to design achievements using many of the modern conveniences provided by programming languages - such as variables, loops, and comments.

You can browse several examples in Jamiras's repository.

Example

Here's an excerpt from Magic Darts illustrating several of the features:

First, we define a function for determining the character in each player slot and some constants for each of the available values.

function player_character(index) => byte(0x0060 + index * 16)
character_none = 0
character_tom = 1
character_bob = 2
character_ann = 3
character_sue = 4
character_joe = 8 // expert male
character_max = 9 // macho man
character_lee = 10 // kung fu
character_rom = 11 // robot
character_liz = 12 // expert female
character_ken = 13 // ninja
character_rio = 14 // monkey
character_ebe = 15 // alien
character_ebe2 = 5 // alien alternate face

Then, we create a helper function to ensure a player exists for the slot and is not CPU controlled.

function player_is_player(index) => player_character(index) != character_none && byte(0x006E + index * 16) == 1

And some more helper functions that map other data in memory to other constants.

function player_dart_weight(index) => byte(0x0067 + index * 16)
weight_light = 1
weight_medium = 2
weight_heavy = 3

function game_variant() => byte(0x04B0)
variant_301 = 1
variant_501 = 2
variant_701 = 3
variant_countup = 4
variant_roundtheclock = 5
variant_halfit = 6

function current_player() => byte(0x04CF) // 1-based

function throwing_phase() => byte(0x04CA)
phase_player_intro = 2
phase_left_right = 3
phase_angle = 4
phase_power = 5
phase_throw = 6
phase_in_flight = 7
phase_hit_board = 8
phase_scoring = 9

And a function for cheat protection. If you're not familiar with the functionality for creating achievements provided by the base editor, the explanation may not make sense.

function trigger_game_start() => word(0x04D6) == 0x0100 // round 1, 0 darts thrown

function using_alien_aim() => byte(0x04DC) == 2

function never_using_alien_aim() => 
    once(trigger_game_start()) &&
    (always_true() || 
     (always_false() && never(using_alien_aim()) && unless(current_player() != 1))
    )
  • The once(trigger_game_start()) sets a flag when the game starts that must be true for the achievement to trigger.
  • The never(using_alien_aim()) will reset the flag if the player activates the cheat.
  • The unless(current_player() != 1) clause prevents the never(using_alien_aim()) clause from resetting the flag if one of the opponents activates the cheat.
  • The always_true() clause pushes the rest of conditions into an alt group so the pause doesn't affect the logic in the core group.
  • The always_false() clause prevents the alt group that is being used to reset the game start flag from being true, so another alt group (usually the always_true() above) must be true for the achievement to trigger

There's a lot of complex logic expressed in mostly-readable English. Additionally, it's packaged up in a function, so it can be reused on every achievement where the cheat protection is needed.

And finally, the actual achievement definition.

achievement(
    title = "Light to 500",
    description = "Get at least 500 points in Count Up using light darts",
    points = 5,
    trigger = game_variant() == variant_countup &&
              player_is_player(0) && 
              current_player() == 1 && 
              byte(0x04F2) >= 5 &&                       // score (hundreds)
              throwing_phase() == phase_hit_board && 
              player_dart_weight(0) == weight_light &&
              never_using_alien_aim()
)

Other than the byte(0x04F2) >= 5 (which is documented with a comment), it's very easy to read what the achievement is expecting.

  • The player must be playing "Count Up"
  • The first player (0) must be human controlled
  • It must be the player's turn
  • The player must have at least 500 points
  • The player's dart must have just hit the board
  • The player must be using light darts
  • The player must not have used the cheat

The resulting achievement is:

Core Group

  	 Mem 	 8-bit 	 0x0004b0 	 = 	 Value 	  	 0x000004 	 (0) 
  	 Mem 	 8-bit 	 0x000060 	 != 	 Value 	  	 0x000000 	 (0) 
  	 Mem 	 8-bit 	 0x00006e 	 = 	 Value 	  	 0x000001 	 (0) 
  	 Mem 	 8-bit 	 0x0004cf 	 = 	 Value 	  	 0x000001 	 (0) 
  	 Mem 	 8-bit 	 0x0004f2 	 >= 	 Value 	  	 0x000005 	 (0) 
  	 Mem 	 8-bit 	 0x0004ca 	 = 	 Value 	  	 0x000008 	 (0) 
  	 Mem 	 8-bit 	 0x000067 	 = 	 Value 	  	 0x000001 	 (0) 
  	 Mem 	 16-bit	 0x0004d6 	 = 	 Value 	  	 0x000100 	 (1) 

Alt 1

  	 Value 	  	 0x000001 	 = 	 Value 	  	 0x000001 	 (0)

Alt 2

Reset If Mem 	 8-bit 	 0x0004dc 	 = 	 Value 	  	 0x000002 	 (0) 
Pause If Mem 	 8-bit 	 0x0004cf 	 != 	 Value 	  	 0x000001 	 (0) 
	 Value 	  	 0x000000 	 = 	 Value 	  	 0x000001 	 (0) 

And since everything is neatly packaged up into helper functions and constants, if we want a second achievement for doing the same things with heavy darts, we only have to change the comparison related to dart weight and the title/description text:

achievement(
    title = "Heavy to 500",
    description = "Get at least 500 points in Count Up using heavy darts",
    points = 5,
    trigger = game_variant() == variant_countup &&
              player_is_player(0) && 
              current_player() == 1 && 
              byte(0x04F2) >= 5 &&                       // score (hundreds)
              throwing_phase() == phase_hit_board && 
              player_dart_weight(0) == weight_heavy &&   // <-- this was changed
              never_using_alien_aim()
)

Much of the same logic can also be carried through to other achievements. I've left the description off this one, see if you can guess it:

achievement(
    title = "??",
    description = "??",
    points = 10,
    trigger = trigger_win() && 
              game_variant() == variant_301 && 
              current_round() <= 4 &&
              never_using_alien_aim()
)
Show answer The correct answer is "Win a game of 301 in four rounds or less". Did you guess it?