The Physics of Cave Story

Jan 31, 2011 at 7:43 PM
Junior Member
"It's dangerous to go alone!"
Join Date: May 22, 2008
Location:
Posts: 35
The Physics of Cave Story - V.6 Released

[Updates] Using the knowledge gathered by the helpful members of the forum, a java program was created to show how far the understandings of technological aspects of Cave Story's physics have come, the bottom of this post contains the latest instructions and file.

[op]
While considering Cave Story, as many of us once have, I stumbled upon a thought; never again will I play another platformer and love it as much as I do Cave Story because it will never feel the same. This led to the question of "What is the feel of Cave Story?". The answer appears to be non-trivial, Pixel obviously spent his time fine tuning the game to a high degree of precision. I plan to spend some spare time in the coming months discovering the exact pseudo-code style way Cave Story handles the physics of player simulation and camera-movement. I'm placing a list out in this post so that if anyone wants to help me, they can! Please feel free to help expand the list or suggest values.

[EDIT] The forums are a terrible place to store information and collaborate on it. To that end, the list has been transformed into a design document. This document comes in two variations:
Publicly Editable and Viewable *Updated Infrequently*: https://docs.google.com/document/d/1jmHGorrZlM8-X4QkDetLOXskcAmhMa6lwNLt22SIvho/edit?hl=en
Protected Static and Viewable *Updated First Always*: https://docs.google.com/document/d/1wr7RKzmb0UC2abh_2HfyIMN8N-JDGwBFaNHUJmpKxRs/edit?hl=en

If people use the public document responsibly, I will transfer their information to the protected document, leave your name if you wish for credit!
This setup will prevent vandalism... I hope ;)

Just to be absolutely clear on this, what I really want is numeric values and formulas that define how quote interacts with collision and input. So, is a jump modeled as an impulse? a change in velocity in the y? Is gravity kinematically calculated by an artificial acceleration? pseudo-acceleration by a frame by frame decrease of velocity? When quote bumps his head is this an elastic/inelastic collision, does gravity effect him differently in this case? What is terminal velocity? For the camera, is the center of the camera attracted to the center of quote? if so by what amount of force/acceleration/constant velocity?

My best bet is to dissect the source code from the NXEngine code: http://nxengine.sourceforge.net/
It uses some advanced coding practices so I am trying to rip out the pure physics into an easy to understand beginner level.
More Sources:
http://www.cavestory.org/forums/threads/792/
And thanks to Noxid for some code
[/op]
_________________________________________
Release 6: Refactory!
p120352-0-arkiv1.png


Releases For Testing: dist* = application only, arkiv* = application and src files
Feb. 18/11dist
Feb. 19/11dist2
Feb. 21/11dist3
\____>dist4
The above versions work for mac computers only, the below should be multiplatform
Feb. 25/11Arkiv
Mar. 29/11Arkiv1

Instructions:
1. Unpack the distribution
2. Open terminal and type:

java -Djava.library.path="*" -jar "**"

where * is the path to the /lib directory inside /dist
and ** is the path to the cavestoryphysics.jar
3. Laugh at my terrible programming skills :awesomeface:
 
Jan 31, 2011 at 7:47 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
0x200 in-game units is one pixel
Code:
void __cdecl PlayerAgil(int CanControlPC)
{
  int v1; // ecx@162
  int v2; // eax@166
  int v3; // edx@170
  int v4; // eax@171
  int WaterX2; // ST48_4@201
  int WaterVelY2; // ST10_4@201
  int WaterVelX2; // eax@201
  int WaterX; // ST48_4@207
  int WaterVelY; // ST10_4@207
  int WaterVelX; // eax@207
  signed int WaterDir; // [sp+0h] [bp-2Ch]@195
  unsigned int JumpSpeed; // [sp+4h] [bp-28h]@4
  unsigned int WalkSpeed; // [sp+8h] [bp-24h]@4
  unsigned int Friction; // [sp+Ch] [bp-20h]@4
  unsigned int Grav; // [sp+10h] [bp-1Ch]@4
  unsigned int AirControl; // [sp+14h] [bp-18h]@4
  unsigned int MaxRunSpeed; // [sp+18h] [bp-14h]@4
  unsigned int GravLow; // [sp+1Ch] [bp-10h]@4
  signed int j; // [sp+24h] [bp-8h]@199
  signed int i; // [sp+24h] [bp-8h]@205

  if ( !(PlayerFlags & 2) )
  {
    if ( PlayerTileCollis & 0x100 )
    {
      MaxRunSpeed = 0x196u;
      Grav = 0x28u;
      GravLow = 0x10u;
      JumpSpeed = 0x280u;
      WalkSpeed = 0x2Au;
      AirControl = 0x10u;
      Friction = 0x19u;
    }
    else
    {
      MaxRunSpeed = 0x32Cu;
      Grav = 0x50u;
      GravLow = 0x20u;
      JumpSpeed = 0x500u;
      WalkSpeed = 0x55u;
      AirControl = 0x20u;
      Friction = 0x33u;
    }
    GenerateQmark = 0;
    if ( !CanControlPC )
      BoostState = 0;
    if ( PlayerTileCollis & 8 || PlayerTileCollis & 0x10 || PlayerTileCollis & 0x20 )
    {
      BoostState = 0;
      if ( EquippedItems & 1 )
      {
        JetPackEnergy = 0x32u;
      }
      else
      {
        if ( EquippedItems & 0x20 )
          JetPackEnergy = 0x32u;
        else
          JetPackEnergy = 0;
      }
      if ( CanControlPC )
      {
        if ( Key_Pressed != Key_Down || Key_Held != Key_Down || PlayerFlags & 1 || GameState & 4 )
        {
          if ( Key_Held != Key_Down )
          {
            if ( KeyLeft & Key_Held )
            {
              if ( PlayerXVel > -MaxRunSpeed )
                PlayerXVel -= WalkSpeed;
            }
            if ( KeyRight & Key_Held )
            {
              if ( PlayerXVel < (signed int)MaxRunSpeed )
                PlayerXVel += WalkSpeed;
            }
            if ( KeyLeft & Key_Held )
              DirectionFaced = 0;
            if ( KeyRight & Key_Held )
              DirectionFaced = 2;
          }
        }
        else
        {
          PlayerFlags |= 1u;
          GenerateQmark = 1;
        }
      }
      if ( !(PlayerFlags & 0x20) )
      {
        if ( PlayerXVel < 0 )
        {
          if ( PlayerXVel <= -Friction )
            PlayerXVel += Friction;
          else
            PlayerXVel = 0;
        }
        if ( PlayerXVel > 0 )
        {
          if ( PlayerXVel >= (signed int)Friction )
            PlayerXVel -= Friction;
          else
            PlayerXVel = 0;
        }
      }
    }
    else
    {
      if ( CanControlPC )
      {
        if ( EquippedItems & 0x21 )
        {
          if ( Key_Jump & Key_Pressed )
          {
            if ( JetPackEnergy )
            {
              if ( EquippedItems & 1 )
              {
                BoostState = 1;
                if ( PlayerYVel > 256 )
                  PlayerYVel /= 2;
              }
              if ( EquippedItems & 0x20 )
              {
                if ( Key_Up & Key_Held )
                {
                  BoostState = 2;
                  PlayerXVel = 0;
                  PlayerYVel = -1535;
                }
                else
                {
                  if ( KeyLeft & Key_Held )
                  {
                    BoostState = 1;
                    PlayerYVel = 0;
                    PlayerXVel = -1535;
                  }
                  else
                  {
                    if ( KeyRight & Key_Held )
                    {
                      BoostState = 1;
                      PlayerYVel = 0;
                      PlayerXVel = 1535;
                    }
                    else
                    {
                      if ( Key_Down & Key_Held )
                      {
                        BoostState = 3;
                        PlayerXVel = 0;
                        PlayerYVel = 1535;
                      }
                      else
                      {
                        BoostState = 2;
                        PlayerXVel = 0;
                        PlayerYVel = -1535;
                      }
                    }
                  }
                }
              }
            }
          }
        }
        if ( KeyLeft & Key_Held )
        {
          if ( PlayerXVel > -MaxRunSpeed )
            PlayerXVel -= AirControl;
        }
        if ( KeyRight & Key_Held )
        {
          if ( PlayerXVel < (signed int)MaxRunSpeed )
            PlayerXVel += AirControl;
        }
        if ( KeyLeft & Key_Held )
          DirectionFaced = 0;
        if ( KeyRight & Key_Held )
          DirectionFaced = 2;
      }
      if ( EquippedItems & 0x20 )
      {
        if ( BoostState )
        {
          if ( !(Key_Jump & Key_Held) || !JetPackEnergy )
          {
            if ( BoostState == 1 )
            {
              PlayerXVel /= 2;
            }
            else
            {
              if ( BoostState == 2 )
                PlayerYVel /= 2;
            }
          }
        }
      }
      if ( !JetPackEnergy || !(Key_Jump & Key_Held) )
        BoostState = 0;
    }
    if ( CanControlPC )
    {
      if ( Key_Up & Key_Held )
        IsFacingUp = 1;
      else
        IsFacingUp = 0;
      IsFacingDown = Key_Down & Key_Held && !(PlayerTileCollis & 8);
      if ( Key_Jump & Key_Pressed )
      {
        if ( PlayerTileCollis & 8 || PlayerTileCollis & 0x10 || PlayerTileCollis & 0x20 )
        {
          if ( !(PlayerTileCollis & 0x2000) )
          {
            PlayerYVel = -JumpSpeed;
            PlaySound(15, 1);
          }
        }
      }
    }
    if ( CanControlPC )
    {
      if ( Key_Held & (Key_Shoot | Key_Jump | Key_Up | KeyRight | KeyLeft) )
        PlayerFlags &= 0xFEu;
    }
    if ( BoostState )
    {
      if ( JetPackEnergy )
        --JetPackEnergy;
    }
    if ( PlayerTileCollis & 0x1000 )
      PlayerXVel -= 136;
    if ( PlayerTileCollis & 0x2000 )
      PlayerYVel -= 128;
    if ( PlayerTileCollis & 0x4000 )
      PlayerXVel += 136;
    if ( PlayerTileCollis & 0x8000 )
      PlayerYVel += 85;
    if ( EquippedItems & 0x20 && BoostState )
    {
      switch ( BoostState )
      {
        case 1:
          if ( PlayerTileCollis & 5 )
            PlayerYVel = -256;
          if ( !DirectionFaced )
            PlayerXVel -= 32;
          if ( DirectionFaced == 2 )
            PlayerXVel += 32;
          if ( Key_Jump & Key_Pressed || JetPackEnergy % 3 == 1 )
          {
            if ( !DirectionFaced )
              CreateEffect(PlayerX + 0x400, PlayerY + 0x400, 7, 2);
            if ( DirectionFaced == 2 )
              CreateEffect(PlayerX - 0x400, PlayerY + 0x400, 7, 0);
            PlaySound(113, 1);
          }
          break;
        case 2:
          PlayerYVel -= 32;
          if ( Key_Jump & Key_Pressed || JetPackEnergy % 3 == 1 )
          {
            CreateEffect(PlayerX, PlayerY + 0xC00, 7, 3);
            PlaySound(113, 1);
          }
          break;
        case 3:
          if ( Key_Jump & Key_Pressed || JetPackEnergy % 3 == 1 )
          {
            CreateEffect(PlayerX, PlayerY - 0xC00, 7, 1);
            PlaySound(113, 1);
          }
          break;
      }
    }
    else
    {
      if ( PlayerTileCollis & 0x2000 )
      {
        PlayerYVel += Grav;
      }
      else
      {
        if ( EquippedItems & 1 && BoostState && PlayerYVel > -1024 )
        {
          PlayerYVel -= 32;
          if ( !(JetPackEnergy % 3) )
          {
            CreateEffect(PlayerX, PlayerY + PCHitrectDown / 2, 7, 3);
            PlaySound(113, 1);
          }
          if ( PlayerTileCollis & 2 )
            PlayerYVel = 512;
        }
        else
        {
          if ( PlayerYVel < 0 && CanControlPC && Key_Jump & Key_Held )
            PlayerYVel += GravLow;
          else
            PlayerYVel += Grav;
        }
      }
    }
    if ( !CanControlPC || !(Key_Jump & Key_Pressed) )
    {
      if ( PlayerTileCollis & 0x10 )
      {
        if ( PlayerXVel < 0 )
          PlayerYVel = -PlayerXVel;
      }
      if ( PlayerTileCollis & 0x20 )
      {
        if ( PlayerXVel > 0 )
          PlayerYVel = PlayerXVel;
      }
      if ( PlayerTileCollis & 8 )
      {
        v1 = PlayerTileCollis;
        if ( v1 & 0x80000 )
        {
          if ( PlayerXVel < 0 )
            PlayerYVel = 1024;
        }
      }
      if ( PlayerTileCollis & 8 )
      {
        v2 = PlayerTileCollis;
        if ( v2 & 0x10000 )
        {
          if ( PlayerXVel > 0 )
            PlayerYVel = 1024;
        }
      }
      if ( PlayerTileCollis & 8 )
      {
        v3 = PlayerTileCollis;
        if ( v3 & 0x20000 )
        {
          v4 = PlayerTileCollis;
          if ( v4 & 0x40000 )
            PlayerYVel = 1024;
        }
      }
    }
    if ( !(PlayerTileCollis & 0x100) || PlayerTileCollis & 0xF000 )
    {
      if ( PlayerXVel < -1535 )
        PlayerXVel = -1535;
      if ( PlayerXVel > 1535 )
        PlayerXVel = 1535;
      if ( PlayerYVel < -1535 )
        PlayerYVel = -1535;
      if ( PlayerYVel > 1535 )
        PlayerYVel = 1535;
    }
    else
    {
      if ( PlayerXVel < -767 )
        PlayerXVel = -767;
      if ( PlayerXVel > 767 )
        PlayerXVel = 767;
      if ( PlayerYVel < -767 )
        PlayerYVel = -767;
      if ( PlayerYVel > 767 )
        PlayerYVel = 767;
    }
    if ( !IsInWater )
    {
      if ( PlayerTileCollis & 0x100 )
      {
        if ( PlayerTileCollis & 0x800 )
          WaterDir = 2;
        else
          WaterDir = 0;
        if ( PlayerTileCollis & 8 || PlayerYVel <= 512 )
        {
          if ( PlayerXVel > 512 || PlayerXVel < -512 )
          {
            for ( i = 0; i < 8; ++i )
            {
              WaterX = PlayerX + (Random(-8, 8) << 9);
              WaterVelY = Random(-512, 128);
              WaterVelX = Random(-512, 512);
              CreateNPC(73, WaterX, PlayerY, PlayerXVel + WaterVelX, WaterVelY, WaterDir, 0, 0);
            }
            PlaySound(0x38u, 1);
          }
        }
        else
        {
          for ( j = 0; j < 8; ++j )
          {
            WaterX2 = PlayerX + (Random(-8, 8) << 9);
            WaterVelY2 = Random(-512, 128) - PlayerYVel / 2;
            WaterVelX2 = Random(-512, 512);
            CreateNPC(73, WaterX2, PlayerY, PlayerXVel + WaterVelX2, WaterVelY2, WaterDir, 0, 0);
          }
          PlaySound(0x38u, 1);
        }
        IsInWater = 1;
      }
    }
    if ( !(PlayerTileCollis & 0x100) )
      IsInWater = 0;
    if ( PlayerTileCollis & 0x400 )
      TakeDamage(10);
    if ( DirectionFaced )
    {
      QuoteCameraOffsetX += 512;
      if ( QuoteCameraOffsetX > 32768 )
        QuoteCameraOffsetX = 32768;
    }
    else
    {
      QuoteCameraOffsetX -= 512;
      if ( QuoteCameraOffsetX < -32768 )
        QuoteCameraOffsetX = -32768;
    }
    if ( Key_Up & Key_Held && CanControlPC )
    {
      QuoteCameraOffsetY -= 512;
      if ( QuoteCameraOffsetY < -32768 )
        QuoteCameraOffsetY = -32768;
    }
    else
    {
      if ( Key_Down & Key_Held && CanControlPC )
      {
        QuoteCameraOffsetY += 512;
        if ( QuoteCameraOffsetY > 32768 )
          QuoteCameraOffsetY = 32768;
      }
      else
      {
        if ( QuoteCameraOffsetY > 512 )
          QuoteCameraOffsetY -= 512;
        if ( QuoteCameraOffsetY < -512 )
          QuoteCameraOffsetY += 512;
      }
    }
    QuoteCamPosX = QuoteCameraOffsetX + PlayerX;
    QuoteCamPosY = QuoteCameraOffsetY + PlayerY;
    if ( PlayerXVel > (signed int)Friction || PlayerXVel < -Friction )
      PlayerX += PlayerXVel;
    PlayerY += PlayerYVel;
  }

Code:
int __cdecl AgilUni1(int GameStateAnd2True)
{
  int result; // eax@73

  IsFacingUp = 0;
  IsFacingDown = 0;
  if ( GameStateAnd2True )
  {
    if ( Key_Held & (KeyRight | KeyLeft) )
    {
      if ( KeyLeft & Key_Held )
        PlayerXVel -= 256;
      if ( KeyRight & Key_Held )
        PlayerXVel += 256;
    }
    else
    {
      if ( PlayerXVel >= 128 || PlayerXVel <= -128 )
      {
        if ( PlayerXVel <= 0 )
          PlayerXVel += PlayerXVel < 0 ? 0x80 : 0;
        else
          PlayerXVel -= 128;
      }
      else
      {
        PlayerXVel = 0;
      }
    }
    if ( Key_Held & (Key_Down | Key_Up) )
    {
      if ( Key_Up & Key_Held )
        PlayerYVel -= 256;
      if ( Key_Down & Key_Held )
        PlayerYVel += 256;
    }
    else
    {
      if ( PlayerYVel >= 128 || PlayerYVel <= -128 )
      {
        if ( PlayerYVel <= 0 )
          PlayerYVel += PlayerYVel < 0 ? 0x80 : 0;
        else
          PlayerYVel -= 128;
      }
      else
      {
        PlayerYVel = 0;
      }
    }
  }
  else
  {
    if ( PlayerXVel >= 128 || PlayerXVel <= -64 )
    {
      if ( PlayerXVel <= 0 )
        PlayerXVel += PlayerXVel < 0 ? 0x80 : 0;
      else
        PlayerXVel -= 128;
    }
    else
    {
      PlayerXVel = 0;
    }
    if ( PlayerYVel >= 128 || PlayerYVel <= -64 )
    {
      if ( PlayerYVel <= 0 )
        PlayerYVel += PlayerYVel < 0 ? 0x80 : 0;
      else
        PlayerYVel -= 128;
    }
    else
    {
      PlayerYVel = 0;
    }
  }
  if ( PlayerYVel < -512 )
  {
    if ( PlayerTileCollis & 2 )
      CreateEffect(PlayerX, PlayerY - PCHitRectUp, 13, 5);
  }
  if ( PlayerYVel > 512 )
  {
    if ( PlayerTileCollis & 8 )
      CreateEffect(PlayerX, PCHitrectDown + PlayerY, 13, 5);
  }
  if ( PlayerXVel > 1024 )
    PlayerXVel = 1024;
  if ( PlayerXVel < -1024 )
    PlayerXVel = -1024;
  if ( PlayerYVel > 1024 )
    PlayerYVel = 1024;
  if ( PlayerYVel < -1024 )
    PlayerYVel = -1024;
  if ( (Key_Held & (Key_Up | KeyLeft)) == (Key_Up | KeyLeft) )
  {
    if ( PlayerXVel < -780 )
      PlayerXVel = -780;
    if ( PlayerYVel < -780 )
      PlayerYVel = -780;
  }
  if ( (Key_Held & (Key_Up | KeyRight)) == (Key_Up | KeyRight) )
  {
    if ( PlayerXVel > 780 )
      PlayerXVel = 780;
    if ( PlayerYVel < -780 )
      PlayerYVel = -780;
  }
  if ( (Key_Held & (Key_Down | KeyLeft)) == (Key_Down | KeyLeft) )
  {
    if ( PlayerXVel < -780 )
      PlayerXVel = -780;
    if ( PlayerYVel > 780 )
      PlayerYVel = 780;
  }
  if ( (Key_Held & (Key_Down | KeyRight)) == (Key_Down | KeyRight) )
  {
    if ( PlayerXVel > 780 )
      PlayerXVel = 780;
    if ( PlayerYVel > 780 )
      PlayerYVel = 780;
  }
  PlayerX += PlayerXVel;
  result = PlayerYVel + PlayerY;
  PlayerY += PlayerYVel;
  return result;
}
Code:
//CS Tile Collision

#define LEFT 1
#define UP 2
#define RIGHT 4
#define DOWN 8

int Player::TileSolid(int X, int Y)
{
	int LocalCollisionFlags = 0;

	if (((PosY - HitRect.Up) <= ((Y * 0x10 + 4) * 0x200)) 
		&& ((PosY + HitRect.Down) >= ((Y * 0x10 - 4) * 0x200)) 
		&& ((PosX - HitRect.Right) <= ((X * 0x10 + 8) * 0x200)) 
		&& ((PosX - HitRect.Right) >= ((X * 0x10) * 0x200)))
	{
		PosX = ((X * 0x10 + 8) *0x200) + HitRect.Right;
		if (VelX <= -0x180)
		{
			VelX = -0x180;
		}
		if (Key_Held & Key_Left)
		{
			if (VelX <= 0)
			{
				VelX = 0;
			}
		}
		LocalCollisionFlags = (LocalCollisionFlags|LEFT)
	}

	if (((PosY - HitRect.Up) <= ((Y * 0x10 + 4) * 0x200))
		&& ((PosY + HitRect.Down) >= ((Y * 0x10 - 4) * 0x200))
		&& ((PosX + HitRect.Right) >= ((X * 0x10 - 8) * 0x200))
		&& ((PosX + HitRect.Right) <= ((X * 0x10) * 0x200)))
	{
		PosX = ((TileX * 0x20 - 8) * 0x200) - HitRect.Right;
		if (VelX >= 180)
		{
			VelX = 0x180;
		}
		if (Key_Held & Key_Right)
		{
			if (VelX >= 0)
			{
				VelX = 0;
			}
		}
		LocalCollisionFlags = (LocalCollisionFlags|RIGHT);
	}

	if (((PosX - HitRect.Right) <= ((X * 0x10 + 5) * 0x200))
		&& ((PosX + HitRect.Right) >= ((X * 0x10 - 5) * 0x200))
		&& ((PosY - HitRect.Up) <= ((Y * 0x10 + 8) * 0x200))
		&& ((PosY - HitRect.Up) >= ((Y * 0x10) * 0x200)))
	{
		PosY = ((Y * 0x10 + 8) * 0x200) + HitRect.Up;
		if ((PlayerFlags & 2) == 0)
		{
			if (VelY <= -0x200)
			{
				HeadBump();
			}
		}
		if (VelY <= 0)
		{
			VelY = 0;
		}
		LocalCollisionFlags = (LocalCollisionFlags|UP);
	}

	if (((PosX - HitRect.Right) <= ((X * 0x10 + 5) * 0x200))
		&& ((PosX + HitRect.Right) >= ((X * 0x10 - 5) * 0x200))
		&& ((PosY + HitRect.Down) >= ((Y * 0x10 - 8) * 0x200))
		&& ((PosY + HitRect.Down) <= ((Y * 0x10) * 0x200))
	{
		PosY = ((Y * 0x10 - 8) * 0x200) - HitRect.Down;
		if (VelY >= 0x400)
		{
			PlaySound(17, 1);
		}
		if (VelY >= 0)
		{
			VelY = 0;
		}
		LocalCollisionFlags = (LocalCollisionFlags|DOWN);
	}

	return LocalCollisionFlags;
}	


Order of Testing: 
LEFT
RIGHT
UP
DOWN

PosX = [49E654]
PosY = [49E658]
VelX = [49E66C]
VelY = [49E670]
HitRect.Left = [49E67C]
HitRect.Up = [49E680]
HitRect.Right = [49E684]
HitRect.Down = [49E688]
HeadBump() = CALL 00417160
PlaySound(int SoundNum, int Channel) = CALL 00420640
I can also do the camera if you'd like.
 
Jan 31, 2011 at 7:48 PM
Lvl 1
Forum Moderator
"Life begins and ends with Nu."
Join Date: May 28, 2008
Location: PMMM MMO
Posts: 3713
Age: 31
The best bet is to look inside the executable itself, where you can examine all of the physics in their native x86 form.

Also you're missing some important things, mainly how friction works, and underwater values for physics.

[edit]: Noxid ninja'd me. Noxid, why aren't you on msn?

Why aren'y you on the msn side?
 
Jan 31, 2011 at 7:54 PM
Junior Member
"It's dangerous to go alone!"
Join Date: May 22, 2008
Location:
Posts: 35
Wow, you guys are quick. That code is great Noxid, the first order of buisness is to change those values into decimal for readability (by n00bs like me). I assume it is C++ just more advanced than anything I have learned yet, Wikipedia is my friend :awesomeface: Will update that list soon then.
 
Jan 31, 2011 at 7:58 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Jan 31, 2011 at 9:40 PM
Junior Member
"It's dangerous to go alone!"
Join Date: May 22, 2008
Location:
Posts: 35
Alright... So when I'm converting these values, 0x196 turns into 0.79 (rounded) or 406/512 pixels?

Also, PlayerTileCollis & 0x100 simply means boolean (Player is colliding with something) BitWiseAnd boolean (That collision is in the downward direction)?

[edit]Wikipedia tells me such a technique is called 'bit-masking' which makes sense, so the above are not booleans, but numbers with that 0x100 being the mask and the result of non-zero reading as true. Tell me if I'm wrong!

And what purpose does the GravLow variable serve?
 
Jan 31, 2011 at 9:45 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
The 0x100 I think relates to the tile type; 0x1, 0x2, 0x4 and 0x8 are the directions. But yes, & is bitwise AND n' | is bitwise OR. This is setting flags that tell other parts of the game what we're colliding with and how. So, if we have PlayerTileCollis&1 == True, then we know the player collided on the left with a tile. Or right. I forget.

I guess you could round if you wanted but I prefer to use integers since the game only deals with those, for the most part. You've got the conversion from hex to decimal correct though.
 
Jan 31, 2011 at 10:18 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
GravLow happens when you are holding z. that's why you fly high in the fans and also how the variable jumping works.
 
Feb 1, 2011 at 5:25 AM
Junior Member
"It's dangerous to go alone!"
Join Date: May 22, 2008
Location:
Posts: 35
Thanks Lace, I added that information to the document, which I also added. Now if anyone feels like unloading the torrent of information I know they have in the spare moments of boredom... they can! I will continue to work on this until I have something that remotely resembles a design document. I quite like the way it looks right now.

[Edit] looked at the links Noxid gave, which provided a better naming scheme and also another variable which wasn't in his original posted code. The basic physics of Cave Story could now be implemented with those values easily.
 
Feb 3, 2011 at 5:08 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Feb 3, 2011
Location: Russia
Posts: 4
Age: 35
Noxid said:
0x200 in-game units is one pixel
Code:
...
I can also do the camera if you'd like.
Where did you get this code? If you can, then for the camera too.

By the way, anyone can suggest how to compile code for the link from the first post?

P.S. Sorry for my bad English, translated by Google Translate :toroko:
 
Feb 3, 2011 at 5:22 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
The tile collision code was hand-decompiled by myself, and the earlier two examples were done using a decompilation program using my knowledge of what the different variables should be called.

@person what do you mean "Another variable"? There are no real variables other than the ones in the code, anything else is effectively calculated on the fly or implied.
 
Feb 3, 2011 at 5:37 PM
Neophyte Member
"Fresh from the Bakery"
Join Date: Feb 3, 2011
Location: Russia
Posts: 4
Age: 35
2 Noxid
If you're not hard, you can give the code for the camera?
 
Feb 3, 2011 at 6:01 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
I can, yes.

Code:
void __cdecl Camera()
{
  __int16 MapWidth; // [sp+0h] [bp-8h]@1
  __int16 MapHeight; // [sp+4h] [bp-4h]@1

  GetMapParams(0, &MapWidth, &MapHeight);
  CamPosX += (*FocusX - 0x14000 - CamPosX) / FocusSpeed;
  CamPosY += (*FocusY - 0xF000 - CamPosY) / FocusSpeed;// Focus speed from <FOM, <FON, <FOB
  if ( CamPosX / 0x200u < 0 )
    CamPosX = 0;
  if ( CamPosY / 0x200u < 0 )
    CamPosY = 0;
  if ( CamPosX > (0x10 * (MapWidth - 1) - 0x140) << 9 )
    CamPosX = (0x10 * (MapWidth - 1) - 0x140) << 9;
  if ( CamPosY > (0x10 * (MapHeight - 1) - 0xF0) << 9 )
    CamPosY = (0x10 * (MapHeight - 1) - 0xF0) << 9;
  if ( HardQuakeDuration )
  {
    CamPosX += Random(-5, 5) << 9;
    CamPosY += Random(-3, 3) << 9;
    --HardQuakeDuration;
  }
  else
  {
    if ( SoftQuakeDuration )
    {
      CamPosX += Random(-1, 1) << 9;
      CamPosY += Random(-1, 1) << 9;
      --SoftQuakeDuration;
    }
  }
}
Code:
void __cdecl SimpleCameraFunc()
{
  int localPlayerY; // [sp+0h] [bp-10h]@1
  int localPlayerX; // [sp+4h] [bp-Ch]@1
  int MapWidth; // [sp+8h] [bp-8h]@1
  int MapHeight; // [sp+Ch] [bp-4h]@1

  GetPlayerXY(&localPlayerX, &localPlayerY);
  GetMapParams(0, &MapWidth, &MapHeight);
  CamPosX = localPlayerX - 0x14000;
  CamPosY = localPlayerY - 0xF000;
  if ( CamPosX / 0x200u < 0 )
    CamPosX = 0;
  if ( CamPosY / 0x200u < 0 )
    CamPosY = 0;
  if ( CamPosX > (0x10 * (MapWidth - 1) - 0x140) << 9 )
    CamPosX = (0x10 * (MapWidth - 1) - 0x140) << 9;
  if ( CamPosY > (0x10 * (MapHeight - 1) - 0xF0) << 9 )
    CamPosY = (0x10 * (MapHeight - 1) - 0xF0) << 9;
}
Code:
void __cdecl DiscardedCamFunction(int inCamX, int inCamY)
{
  int MapWidth; // [sp+0h] [bp-8h]@1
  int MapHeight; // [sp+4h] [bp-4h]@1

  SoftQuakeDuration = 0;
  HardQuakeDuration = 0;
  GetMapParams(0, &MapWidth, &MapHeight);
  CamPosX = inCamX;
  CamPosY = inCamY;
  if ( CamPosX / 0x200u < 0 )
    CamPosX = 0;
  if ( CamPosY / 0x200u < 0 )
    CamPosY = 0;
  if ( CamPosX > (16 * (MapWidth - 1) - 0x140) << 9 )
    CamPosX = (16 * (MapWidth - 1) - 0x140) << 9;
  if ( CamPosY > (16 * (MapHeight - 1) - 0xF0) << 9 )
    CamPosY = (16 * (MapHeight - 1) - 0xF0) << 9;
}
Code:
void __cdecl GetMapParams(int a1, int a2, int a3)
{
  if ( a1 )
    *a1 = dword_49E480;
  if ( a2 )
    *a2 = MapWidth;
  if ( a3 )
    *a3 = MapHeight;
}
Code:
void __cdecl TSC_FOB(int TSC_Arg1, int TSC_Arg2)
{
  FocusX = &BossX[43 * TSC_Arg1];
  FocusY = &BossY[43 * TSC_Arg1];
  FocusSpeed = TSC_Arg2;
}
Code:
void __cdecl TSC_FOM(int TSC_Arg1)
{
  FocusX = &QuoteCamPosX;
  FocusY = &QuoteCamPosY;
  FocusSpeed = TSC_Arg1;
}
Code:
void __cdecl TSC_FON(int TSC_Arg1, int TSC_Arg2)
{
  signed int i; // [sp+0h] [bp-4h]@1

  for ( i = 0; i < 0x200u && EntityEventNum[43 * i] != TSC_Arg1; ++i )
    ;
  if ( i != 0x200 )
  {
    FocusX = &EntityX[43 * i];
    FocusY = &EntityY[43 * i];
    FocusSpeed = TSC_Arg2;
  }
}

The ones involving the entity pointers are probably messed up because they're most likely a C++ class and the decompiler doesn't like those. The important thing to note is that we're getting the address of a specific entity's x and y position.
I am fairly confident that DiscardedCameraFunc is never used by the game, but I'm not sure if SimpleCameraFunc is. CameraPosX and CameraPosY are in game-units where 0x200 units = 1 pixel. Anything not explicitly listed as a local variable or argument is probably a global variable.
 
Feb 4, 2011 at 4:19 AM
Junior Member
"It's dangerous to go alone!"
Join Date: May 22, 2008
Location:
Posts: 35
Noxid said:
@person what do you mean "Another variable"? There are no real variables other than the ones in the code, anything else is effectively calculated on the fly or implied.

I must have not looked far enough yet into your code but at the top it gives 7 variables and the link you posted gives 8. I'm pretty sure that it was the falling speed variable.
 
Feb 4, 2011 at 4:23 AM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
Oh. That's this bit later on:

Code:
    if ( !(PlayerTileCollis & 0x100) || PlayerTileCollis & 0xF000 )
    {
      if ( PlayerXVel < -1535 )
        PlayerXVel = -1535;
      if ( PlayerXVel > 1535 )
        PlayerXVel = 1535;
      if ( PlayerYVel < -1535 )
        PlayerYVel = -1535;
      if ( PlayerYVel > 1535 )
        PlayerYVel = 1535;
    }
    else
    {
      if ( PlayerXVel < -767 )
        PlayerXVel = -767;
      if ( PlayerXVel > 767 )
        PlayerXVel = 767;
      if ( PlayerYVel < -767 )
        PlayerYVel = -767;
      if ( PlayerYVel > 767 )
        PlayerYVel = 767;

I was lazy and didn't convert every value to hex I guess but 1535 is 5FF if I'm correct.
 
Feb 4, 2011 at 4:31 AM
Pirate Member
"Big Joe Tire and Battery Restaurant! Opening Soon! Eat at Big Joes!"
Join Date: Jun 29, 2010
Location: Hills of Amber, Amh Araeng, Norvrandt, The First
Posts: 512
1535=0x5FF
767=0x2FF
protip: windows calculator makes a great conversion tool.
 
Feb 4, 2011 at 4:33 AM
Lvl 1
Forum Moderator
"Life begins and ends with Nu."
Join Date: May 28, 2008
Location: PMMM MMO
Posts: 3713
Age: 31
Noxid said:
I was lazy and didn't convert every value to hex I guess but 1535 is 5FF if I'm correct.

When I'm hacking I never really bother to convert the values since I just kind of think in hex when I'm looking at ASM.
 
Feb 4, 2011 at 8:06 AM
Neophyte Member
"Fresh from the Bakery"
Join Date: Feb 3, 2011
Location: Russia
Posts: 4
Age: 35
Noxid said:
I can, yes.

Code:
...

The ones involving the entity pointers are probably messed up because they're most likely a C++ class and the decompiler doesn't like those. The important thing to note is that we're getting the address of a specific entity's x and y position.
I am fairly confident that DiscardedCameraFunc is never used by the game, but I'm not sure if SimpleCameraFunc is. CameraPosX and CameraPosY are in game-units where 0x200 units = 1 pixel. Anything not explicitly listed as a local variable or argument is probably a global variable.

Thank you very much, I will learn it :)
 
Feb 9, 2011 at 7:20 PM
Senior Member
"Ha! Ha! Ha! Mega Man is no match for my Mimiga Man!"
Join Date: Jan 21, 2011
Location:
Posts: 249
Would be interesting to see where and how the physics glitch up when Quote interacts with sloped tiles that aren't adjacent to other solid tiles. He'll tend to teleport around the sloped tile, either moving up or down.
 
Feb 9, 2011 at 7:25 PM
In my body, in my head
Forum Moderator
"Life begins and ends with Nu."
Join Date: Aug 28, 2009
Location: The Purple Zone
Posts: 5998
If quote's hitbox touches the slope part of a slope tile (top half of a large slope or just a small slope by itself) he is moved upwards based on a calculation of something like
PlayerY = ((PlayerX - TileLeftSide)/2)+TileBottom

This is why he teleports, because this executes even if the *top* of his hitBox collides.
 
Top