Castlevania Tricks

TODO: Expand this page to have a proper structure

Random item drops algorithm

var FrameCounter = byte at RAM[0x1A],
    CurrentStage = byte at RAM[0x28],
    SpecialItemCounter = byte at RAM[0x7B],
    CurrentSpecialWeapon = byte at RAM[0x15B],
    MultiplierSpawnCounter = byte at RAM[0x79],
    SpecialWeaponOnScreen = byte at RAM[0x72],
    WhipLength012 = RAM[0x70],
    NumberOfHearts = RAM[0x71],
    CurrentMultiplier = RAM[0x64],
    CurrentRandomBonusId = RAM[0x6F]
function SpawnItem_WhenEnemyDies: # Is at $1E09A:
  var ai_type = this->ObjectAItype
  
  if((ai_type < 32 AND ai_type ≠ 11) OR (ai_type ≥ 40))
  {
    if((FrameCounter MOD 16) = 2)
      return this->SpawnSpecialItem();
  }
  
  if(ai_type = 10 OR ai_type = 19)
  {
    return this->BecomeBonusItem(4);  # unconditionally a large heart
  }

  var default_item ≔ 0; # random bonus
  
  if(ai_type = 3 OR ai_type = 8 OR ai_type = 12 OR ai_type = 14 OR ai_type = 18)
  {
    default_item ≔ 4; # a large heart instead of random bonus
    # (but it can still become a multiplier instead of large heart)
  }
  
  if( (FrameCounter MOD 8) ≠ 0)
    ai_type ≔ 0x30 # just disappears after a while, no bonus!
  else
    ai_type ≔ 0x31 # will become a bonus item.
  
  this->BecomeBonusItem(default_item, ai_type);
function GetCurrentLevel:
  return floor((CurrentStage - 1) / 3)
function SpawnSpecialItem:
  var special_table ≔ Array(0..5, 0..3)
  {
    {10,15,11,0},  # level 0(boss:bat):          rosary, watch, firebomb, <none>
    {11,10,0,9},   # level 1(boss:medusa):       firebomb, rosary, <none>, boomerang
    {10,10,14,0},  # level 2(boss:mummies):      rosary, rosary, amphora, <none>
    {10,14,13,0},  # level 3(boss:frankenstein): rosary, amphora, axe, <none>
    {0,15,14,8},   # level 4(boss:death):        <none>, watch, amphora, dagger
    {9,15,10,0}    # level 5(boss:dracula):      boomerang, watch, rosary, <none>
  }

  # Note: SpecialItemCounter is a global variable that is never ever referenced
  # anywhere else but in this function. It is a 2-bit counter.

  do {
    var special_item ≔ special_table[ GetCurrentLevel ] [ (++SpecialItemCounter) MOD 4 ];
  } while(special_item = CurrentWeapon);

  this->BecomeBonusItem (special_item)
function BecomeBonusItem(bonustype, ai_type = 0x31):
  # This function does not really change the item yet.
  # It only assigns the AI to be used by this object.
  this->AItype     ≔ ai_type
  this->BonusType  ≔ bonustype
function BecomeActor(actorid):
  # This function creates the actual object from the given id.
  # implementation is omitted here, it is not relevant.
var TranslateBonusIdToActorId = Array(0..15)
  {0x0F,0x0E,0x05,0x0C,0x42,0x10,0x23,0x00,0x17,0x43,0x0A,0x44,0x2F,0x18,0x45,0x46};

function RunObjectAI_30:
  if(--this->ObjectMultiPurposeCounter) return;
  this->AItype ≔ 0x32;
function RunObjectAI_32:
  this->StopExisting();
function RunObjectAI_2F:
  # Common AI code for bonus actors. Omitted here.
function RunObjectAI_31:
  if(--this->ObjectMultiPurposeCounter) return;
  this->AItype ≔ 0x2F;

  switch(this->BonusType MOD 16)
  {
    default:
      if(SpecialWeaponOnScreen OR (CurrentWeapon = this->BonusType))
        return this->BecomeTinyRandomBonusA();
      SpecialWeaponOnScreen ≔ 0xFF;
      # fallthrough:
    case 5: # beef
    case 6: # red sphere
    case 7: # unknown (ghost?)
      # Keep this decision of "bonus type", and become the actual object.
      return this->BecomeActor(TranslateBonusIdToActorId[this->BonusType]);

    case 1: # bonus sac
      this->BecomeActor(0x0E); # become a sac
      this->PaletteIndex ≔ this->BonusType SHR 4;
      return;
    
    case 12: # multiplier
      # Try becoming a multiplier
      
      MultiplierSpawnCounter ≔ 0;
      var tmp ≔ CurrentMultiplier;
      if(tmp < 2)
      {
        this->BonusType ≔ 12;
        tmp ≔ tmp + BonusTypeToActorTypeTransTable[this->BonusType];
        return this->BecomeActor(tmp);
      }
      # passthru

    case 0: # small heart or a sac of some value
      return this->BecomeCompletelyRandomBonus();
  }
function BecomeCompletelyRandomBonus:
  # It is unclear where MultiplierSpawnCounter is incremented.
  # It happens at $E65D, but the circumstances in which that
  # code is executed are not clear. It seems to occur whenever
  # a special weapon delivers damage, though. In the case of
  # the fire bomb, for enemies that are not immediately killed
  # from damage, it can mean that the counter is incremented
  # by ~50 times at once.
  if(MultiplierSpawnCounter ≥ 10)
  {
    # Try becoming a multiplier
    
    MultiplierSpawnCounter = 0;
    var tmp ≔ CurrentMultiplier;
    if(tmp < 2)
    {
      this->BonusType ≔ 12;
      tmp ≔ tmp + BonusTypeToActorTypeTransTable[this->BonusType];
      return this->BecomeActor(tmp);
    }
  }
  if(NOT SpecialWeaponOnScreen)
  {
    if(NumberOfHearts < 8)
    {
      if(NumberOfHearts ≥ 4 AND WhipLength012 < 1)
        return this->BecomeWhipUpgrade();
    }
    else if(WhipLength012 < 2)
      return this->BecomeWhipUpgrade();
  }
  return this->BecomeTinyRandomBonusA();
function BecomeTinyRandomBonusA:
  this->BonusType ≔ CurrentRandomBonusId MOD 2; # 0 = heart, 1 = sac
  return this->BecomeActor(TranslateBonusIdToActorId[this->BonusType]);
function BecomeWhipUpgrade:
  SpecialWeaponOnScreen ≔ 0xFF;
  this->BonusType ≔ 3; # whip upgrade
  return this->BecomeActor(TranslateBonusIdToActorId[this->BonusType]);
# This code is executed non-stop in the main-loop of the program.
# Each time NMI resumes, it returns to this loop.
# It is responsible for permutating the CurrentRandomBonusId variable.
# If you want to influence the outcome of this loop before the next NMI,
# you need to influence the number of CPU cycles spent in the previous NMI.
function MainLoop:
  var RandomTable ≔ Array(0..15)
  {
    0x33,0xBB,0x3F,0x80,
    0x2E,0xA9,0x61,0x87,
    0xAD,0xC3,0xB2,0xC8,
    0x7C,0x25,0x48,0x7A
  };
  loop_forever:
  {
    var tmp ≔ (CurrentRandomBonusId OR FrameCounter) AND 15;
    CurrentRandomBonusId ≔ RandomTable[tmp];
  }

Damage dealing tricks

Adding own damage into enemy damage

Stub explanation: In some circumstances, taking damage at the same time an enemy takes damage may cause the own damage to be added to the enemy's damage, greatly increasing the rate at which the enemy approaches death.

TODO: Explain this trick.

Shortcut tricks

Bumping from enemies to reach upper platforms

A brief description of what happens when Simon takes damage:

When Simon takes damage, he is knocked up into the air and pushed either left or right of his previous position (assuming there is no ceiling immediately above him). For example, if Simon suffers a blow from his left side, he will be knocked to the right of his former position, first rising up in the air and later falling back down towards the ground. The jump peaks at 14 Y coordinates higher than his original position.

Abusing this feature:

The above holds true when Simon is already in the air. Experienced players can use this to perform a double jump. Here is an example: Simon jumps right. While in the air, Simon is hit by a monster on his left side. Simon now launches to the right, first rising in the air followed by falling towards the ground. This can be abused to reach platforms that are out of Simon's normal horizontal jumping range. The first step is jumping towards a desired platform, hitting an enemy during the jump that causes Simon to get bumped further up and to the left or right. In many of the Castlevania movies on this site, this trick is used with the bats in stage 2 in order to avoid the scene where Simon must fight or dodge the fishmen.

This trick can also be used to ascend to platforms that are exactly 3 "blocks" above a lower platform (example: Stage 4 after climbing stairs, Stage 7 before climbing stairs, stage 12 before Frankenstein's monster, etc). First, wait or manipulate an enemy to approach a desired platform 3 blocks above the current platform. Second, time a jump so that Simon is partially inside the platform above him when damage is taken. Simon's reaction to damage causes him to be launched high enough to pass through the horizontal threshold of the higher platform. Once a certain height is gained, the game is programmed to push Simon up and through the higher platform instead of falling back down to the first platform. With practice, this shortcut can be performed quite easily in real time.

TODO: Revise the current explanation of this trick (perhaps copy explanation from another tricks page) and add .gif images.


Get Firefox!CastlevaniaTricks last edited by Morrison on 2007-06-29 06:14:29
Page info and history | Latest diff | List referrers