Need help with keyboard movement code [yet again]

Started by Kinoko, Mon 04/07/2005 15:29:55

Previous topic - Next topic

Kinoko

This old code is back for yet another appearance.

Code: ags

#define DIR_DISTANCE 10000
#define DIR_DOWN_LEFT 1
#define DIR_DOWN 2
#define DIR_DOWN_RIGHT 3
#define DIR_LEFT 4
#define DIR_STOP 5
#define DIR_RIGHT 6
#define DIR_UP_LEFT 7
#define DIR_UP 8
#define DIR_UP_RIGHT 9


In repeatedly execute:

Code: ags

if (IsGamePaused()==0) {
Ã,  
	if ((IsKeyPressed (371) > 0) || (IsKeyPressed (55) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_UP_LEFT;
	else if ((IsKeyPressed (373) > 0) || (IsKeyPressed (57) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (377) > 0))) Direct = DIR_UP_RIGHT; 
	else if ((IsKeyPressed (379) > 0) || (IsKeyPressed (49) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_DOWN_LEFT;
	else if ((IsKeyPressed (381) > 0) || (IsKeyPressed (51) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (377) > 0))) Direct =DIR_DOWN_RIGHT;
	else if ((IsKeyPressed (372) > 0) || (IsKeyPressed (56) > 0)) Direct = DIR_UP; 
	else if ((IsKeyPressed (375) > 0) || (IsKeyPressed (52) > 0)) Direct = DIR_LEFT; 
	else if ((IsKeyPressed (376) > 0) || (IsKeyPressed (53) > 0)) Direct = DIR_STOP; 
	else if ((IsKeyPressed (377) > 0) || (IsKeyPressed (54) > 0)) Direct = DIR_RIGHT; 
	else if ((IsKeyPressed (380) > 0) || (IsKeyPressed (50) > 0)) Direct = DIR_DOWN; 
	else Direct = DIR_STOP;
	
	if (IsKeyPressed(keya)==1) { //RUNNING - 'A' button held down
	
	Ã,  Ã, if (isRunning==0) {
	Ã,  Ã,  Ã,  StopMoving(GetPlayerCharacter());
	Ã,  Ã,  Ã,  SetCharacterSpeed(GetPlayerCharacter(), 12);
	Ã,  Ã,  Ã,  isRunning = 1;
	Ã,  Ã,  Ã,  ReleaseCharacterView(EGO);
	Ã,  Ã,  Ã,  ChangeCharacterView(EGO, 33);
	Ã,  Ã,  Ã,  Direct = DIR_STOP;
	Ã,  Ã, }
	
	} 
Ã,  else {
	
	Ã,  Ã, if (isRunning==1) {
	Ã,  Ã,  Ã,  StopMoving(GetPlayerCharacter());
	Ã,  Ã,  Ã,  SetCharacterSpeed(GetPlayerCharacter(), 6);
	Ã,  Ã,  Ã,  isRunning = 0;
	Ã,  Ã,  Ã,  ReleaseCharacterView(EGO);
	Ã,  Ã,  Ã,  ChangeCharacterView(EGO, 1);
	Ã,  Ã,  Ã,  Direct = DIR_STOP;
	Ã,  Ã, }
	}
	
	if ((PrevDirection != Direct) || (character[EGO].walking == 0)) {
			PrevDirection = Direct;
			CharId = GetPlayerCharacter ();
			if (Direct == DIR_STOP) StopMoving (CharId); // 5 Stop (numeric pad)
			else {
				if (Direct == DIR_UP_LEFT) {
					dx = -DIR_DISTANCE; dy = -DIR_DISTANCE;
					StrCopy (direct, "ul"); 
					} // 7 Home (numeric pad)
				else if (Direct == DIR_UP) {
					dx = 0; dy = -DIR_DISTANCE;
					StrCopy (direct, "u");
					} // 8 Up arrow
				else if (Direct == DIR_UP_RIGHT) { 
					dx = DIR_DISTANCE; dy = -DIR_DISTANCE;
					StrCopy (direct, "ur");
					} // 9 PgUp (numeric pad)
				else if (Direct == DIR_LEFT) { 
					dx = -DIR_DISTANCE; dy = 0; 
					StrCopy (direct, "l");
					} // 4 Left arrow
				else if (Direct == DIR_RIGHT) { 
					dx = DIR_DISTANCE; dy = 0;
					StrCopy (direct, "r");
					} // 6 Right arrow
				else if (Direct == DIR_DOWN_LEFT) { 
					dx = -DIR_DISTANCE; dy = DIR_DISTANCE;
					StrCopy (direct, "dl");
					} // 1 End (numeric pad)
				else if (Direct == DIR_DOWN) { 
					dx = 0; dy = DIR_DISTANCE; 
					StrCopy (direct, "d");
					} // 2 Down arrow
				else if (Direct == DIR_DOWN_RIGHT) { 
					dx = DIR_DISTANCE; dy = DIR_DISTANCE; 
					StrCopy (direct, "dr");
					} // 3 PgDn (numeric pad)
				MoveCharacterStraight (CharId, character [CharId].x + dx, character [CharId].y + dy);
			}
	}
}


At the moment, the code works -great-. I love it. Responds perfectly to keyboard control. There's another feature I'd like to add though.

Basically, when a character is walking along, say, left... if he hits an edge, he stops. Fair enough, but if I then, while holding left still, hold down the 'up' key, he still won't move. In a typical RPG, what would happen is that the character would start moving up along the edge (while still facing left) until it either hit a spot where it could go left/up-left or in neither direction anymore.

Also, even if the character is against an edge, I'd like him to keep "walking" on the spot if I have the key pressed.

One other thing is that if the character is against an edge and I hit a direction key (not hold it down necessarilly) facing that edge, the character won't move because, well, it's an edge. I'd like him to face that direction still, even if there's no walkable area.

I've written down all sorts of basic code ideas but when I try to think practically about how to adjust the above code, I just end up with a headache. Can anyone at least point me in the right direction? I don't know how to ask the game if it's a possibility to walk when I hit a key. It's not as simple as just checking if a certain coordinate is a walkable area or not because the next pixel over isn't necessarilly where the character would move given his speed and that it changes between walking and running.

I've been thinking about this for days and I just can't get my head around it.

Kweepa

I think it's going to be difficult to tweak your "old code" to do what you want.
Here's how I'd do it instead.

You'll need to know how many pixels the character moves every frame (every time repeatedly execute is run, in other words).
Use that to predict where the character will be next frame. Then break down the movement into pixels and see if there is walkable ground at each pixel. If so, move; if not, and you are moving diagonally, try the corresponding straight moves.
Then, animate the character independent of how it actually moves.

For the movements, you'll need to use floats to store character's position and the per frame movement.

//PSEUDOCODE (i.e. I haven't tested it :):
Code: ags

float characterX;
float characterY;
float animFrame = 0.0;

// character walks 30 = 0.75*GetGameSpeed() pixels per second
// (obviously assumes GetGameSpeed() is the default 40)
#define DIR_DISTANCE 0.75
// character changes animation frame every other game frame
#define ANIM_SPEED 0.5

// in player enters room:
// start in the middle of the pixel
characterX = IntToFloat(player.x) + 0.5;
characterY = IntToFloat(player.y) + 0.5;
int oldDirect = DIR_STOP;

// free functions
function TryDiagonalMove(float dist, int dir, int dx, int dy, int slideDirX, int slideDirY)
{
Ã,  int newDir = dir;
Ã,  // test that direction
Ã,  float delta = 0.7071*dist;
Ã,  float dxf = IntToFloat(dx);
Ã,  float dyf = IntToFloat(dy);
Ã,  float newCharacterX = characterX + dxf*delta;
Ã,  int pixelMove = FloatToInt(newCharacterX) - FloatToInt(characterX);
Ã,  if (pixelMove != 0)
Ã,  {
Ã,  Ã,  // check the diagonal move
Ã,  Ã,  if (GetWalkableAreaAt(player.x + dx, player.y + dy) != 0)
Ã,  Ã,  {
Ã,  Ã,  Ã,  // can move that way
Ã,  Ã,  Ã,  float maxDelta = delta;
Ã,  Ã,  Ã,  if (maxDelta > 1.0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  maxDelta = 1.0;
Ã,  Ã,  Ã,  Ã,  dist -= 1.414;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  characterX += dxf*maxDelta;
Ã,  Ã,  Ã,  characterY += dyf*maxDelta;
Ã,  Ã,  }
Ã,  Ã,  else
Ã,  Ã,  {
Ã,  Ã,  Ã,  // round off the position
Ã,  Ã,  Ã,  characterX = IntToFloat(FloatToInt(characterX)) + 0.5;
Ã,  Ã,  Ã,  characterY = IntToFloat(FloatToInt(characterY)) + 0.5;
Ã,  Ã,  Ã,  if (GetWalkableAreaAt(player.x + dx, player.y) != 0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  newDir = slideDirX;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  newDir = slideDirY;
Ã,  Ã,  Ã,  }
Ã,  Ã,  }
Ã,  }
Ã,  else
Ã,  {
Ã,  Ã,  // pixelMove == 0
Ã,  Ã,  // didn't change pixel so just move within the pixel
Ã,  Ã,  characterX = characterX + dxf*delta;
Ã,  Ã,  characterY = characterY + dyf*delta;
Ã,  Ã,  // stop
Ã,  Ã,  dist = 0.0;
Ã,  }
Ã,  
Ã,  return newDir;
}

function TryStraightMove(float dist, int dx, int dy)
{
Ã,  float delta = dist;
Ã,  float dxf = IntToFloat(dx);
Ã,  float dyf = IntToFloat(dy);
Ã,  float newCharacterX = characterX + dxf*delta;
Ã,  float newCharacterY = characterY + dyf*delta;
Ã,  int pixelMoveX = FloatToInt(newCharacterX) - FloatToInt(characterX);
Ã,  int pixelMoveY = FloatToInt(newCharacterY) - FloatToInt(characterY);
Ã,  if (pixelMoveX != 0 || pixelMoveY != 0)
Ã,  {
Ã,  Ã,  // check the move
Ã,  Ã,  if (GetWalkableAreaAt(player.x + dx, player.y + dy) != 0)
Ã,  Ã,  {
Ã,  Ã,  Ã,  // can move that way
Ã,  Ã,  Ã,  float maxDelta = delta;
Ã,  Ã,  Ã,  if (maxDelta > 1.0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  maxDelta = 1.0;
Ã,  Ã,  Ã,  Ã,  dist -= 1.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  characterX += dxf*maxDelta;
Ã,  Ã,  Ã,  characterY += dyf*maxDelta;
Ã,  Ã,  }
Ã,  Ã,  else
Ã,  Ã,  {
Ã,  Ã,  Ã,  // round off the position
Ã,  Ã,  Ã,  characterX = IntToFloat(FloatToInt(characterX)) + 0.5;
Ã,  Ã,  Ã,  characterY = IntToFloat(FloatToInt(characterY)) + 0.5;
Ã,  Ã,  Ã,  // stop moving
Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  }
Ã,  }
Ã,  else
Ã,  {
Ã,  Ã,  // pixelMove == 0
Ã,  Ã,  // didn't change pixel so just move within the pixel
Ã,  Ã,  characterX = characterX + dxf*delta;
Ã,  Ã,  characterY = characterY + dyf*delta;
Ã,  Ã,  // stop
Ã,  Ã,  dist = 0.0;
Ã,  }Ã,  
}

// in repeatedly execute:

Direct = DirectionFromKeyPresses(); // as before

if (Direct != oldDirect)
{
Ã,  // changing direction, so reset these
Ã,  characterX = IntToFloat(player.x) + 0.5;
Ã,  characterY = IntToFloat(player.y) + 0.5;
}
if (Direct != DIR_STOP)
{
Ã,  float dist = DIR_DISTANCE;
Ã,  if (IsKeyPressed(keya))
Ã,  {
Ã,  Ã,  // running
Ã,  Ã,  dist = 2.0*DIR_DISTANCE;
Ã,  }
Ã,  int newDir = Direct;
Ã,  while (dist > 0.0)
Ã,  {
Ã,  Ã,  if (newDir == DIR_DOWN_LEFT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  newDir = TryDiagonalMove(dist, newDir, -1, 1, DIR_LEFT, DIR_DOWN);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_DOWN_RIGHT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  newDir = TryDiagonalMove(dist, newDir, 1, 1, DIR_RIGHT, DIR_DOWN);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_UP_LEFT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  newDir = TryDiagonalMove(dist, newDir, -1, -1, DIR_LEFT, DIR_UP);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_UP_RIGHT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  newDir = TryDiagonalMove(dist, newDir, 1, -1, DIR_RIGHT, DIR_UP);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_LEFT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  TryStraightMove(dist, -1, 0);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_RIGHT)
Ã,  Ã,  {
Ã,  Ã,  Ã,  TryStraightMove(dist, 1, 0);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_UP)
Ã,  Ã,  {
Ã,  Ã,  Ã,  TryStraightMove(dist, 0, -1);
Ã,  Ã,  }
Ã,  Ã,  else if (newDir == DIR_DOWN)
Ã,  Ã,  {
Ã,  Ã,  Ã,  TryStraightMove(dist, 0, 1);
Ã,  Ã,  }
Ã,  }
Ã,  
Ã,  // now animate the character
Ã,  animFrame += ANIM_SPEED;
Ã,  int animFrameInt = FloatToInt(animFrame);
Ã,  if (animFrameInt > NUM_FRAMES) animFrameInt = 0;
Ã,  // TODO: set the player loop and frame depending on Direct and animFrameInt
}
else
{
  // TODO: set the standing frame based on oldDirect
}
player.x = FloatToInt(characterX);
player.y = FloatToInt(characterY);
oldDirect = Direct;


Sorry it's a bit long.
It might have been better using some lookup tables, but AGS doesn't lend itself to setting those up.
Still waiting for Purity of the Surf II

Kinoko

Remind me if we ever meet that I'm buying you a drink Steve. I don't mind at all if it's long, as long as it works.

I haven't got time to test it right now 'cause I have to scoot out for a bit but my gamespeed is actually 80, and (I might be understanding you wrong here but) do I have to put some of the code in a room script? This is the way the character will be moving throughout the entire game (hundreds of room).

Kweepa

#3
No, it should all work from the global repeatedly_execute.
[EDIT - apart from that bit I said had to be in the room. Ugh. It should work pretty well without it though.]

Please send drinks to:
Creation of Adventures Studio Haven
Boat "The Onion Ring"
Ocho Rios Harbour
Jamaica
Still waiting for Purity of the Surf II

Kinoko

(Turns out I don't have to scoot out yet so I'm giving the code a go)

I'm getting an error with the "DirectionFromKeyPresses()" line. It's an undefinied symbol.

Kweepa

You were supposed to substitute your block of code that finds the direction from key presses there.

Code: ags


function DirectionFromKeyPresses()
{
  int Direct = DIR_NONE;
       if ((IsKeyPressed (371) > 0) || (IsKeyPressed (55) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_UP_LEFT;
  else if ((IsKeyPressed (373) > 0) || (IsKeyPressed (57) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (377) > 0))) Direct = DIR_UP_RIGHT; 
        else if ((IsKeyPressed (379) > 0) || (IsKeyPressed (49) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_DOWN_LEFT;
 else if ((IsKeyPressed (381) > 0) || (IsKeyPressed (51) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (377) > 0))) Direct =DIR_DOWN_RIGHT;
  else if ((IsKeyPressed (372) > 0) || (IsKeyPressed (56) > 0)) Direct = DIR_UP; 
  else if ((IsKeyPressed (375) > 0) || (IsKeyPressed (52) > 0)) Direct = DIR_LEFT; 
  else if ((IsKeyPressed (376) > 0) || (IsKeyPressed (53) > 0)) Direct = DIR_STOP; 
  else if ((IsKeyPressed (377) > 0) || (IsKeyPressed (54) > 0)) Direct = DIR_RIGHT; 
  else if ((IsKeyPressed (380) > 0) || (IsKeyPressed (50) > 0)) Direct = DIR_DOWN; 

  return Direct;
}


You'll probably want to wrap the whole thing in a "IsGamePaused()", and I didn't look up the proper expression for testing the walkable area so that might be wrong too.

Also, I didn't write the code to animate the character. You'll need to do something about that.

Good luck!
Still waiting for Purity of the Surf II

Kinoko

#6
No problem, thanks ^_^ Sorry I misunderstood before. The code has always been wrapped in an IsGamePaused() line so no problem there.

EDIT: "Cannot convert int to float" ->   float dist = DIR_DISTANCE;

I'm not even sure what a float is so...

Kweepa

#7
Quote from: Kinoko on Tue 05/07/2005 14:25:01
EDIT: "Cannot convert int to float" ->Ã,  Ã, float dist = DIR_DISTANCE;

Damn. I'm surprised that didn't work.
Try this instead:
Code: ags

float dist = 0.75;
if (IsKeyPressed(keya))
{
Ã,  // running, so go twice the distance
Ã,  dist = 1.5;
}


0.75 and 1.5 are how far in pixels the player will move per frame. You'll need to tweak these.

A float is just a "real" number, as opposed to an integer, if you remember that from maths class. (It's short for floating-point number.)
Still waiting for Purity of the Surf II

Kinoko

Now I'm getting undefined symbol with ANIM_SPEED and NUM_FRAMES

Kweepa

ANIM_SPEED should have given you the same problem as DIR_DISTANCE.
Just comment those lines out for now - you'll need to come up with good values for ANIM_SPEED (maybe 0.1, i.e. advance an animation frame every 10 game frames) and NUM_FRAMES (4 if the walk cycle is 4 frames) that correspond to the main character running/walking.

Phew, we're getting there...
Still waiting for Purity of the Surf II

Kinoko

Okay, for clarification, I have this:

Code: ags

#define DIR_DISTANCE 10000
#define DIR_DOWN_LEFT 1
#define DIR_DOWN 2
#define DIR_DOWN_RIGHT 3
#define DIR_LEFT 4
#define DIR_STOP 5
#define DIR_RIGHT 6
#define DIR_UP_LEFT 7
#define DIR_UP 8
#define DIR_UP_RIGHT 9
#define DIR_NONE 0
int PrevDirection;
int isRunning = 0;
int CharId;
int Direct;
int oldDirect;
int newdir;
float characterX;
float characterY;
float animFrame = 0.0;


and:
Code: ags

function TryDiagonalMove(float dist, int dir, int dx, int dy, int slideDirX, int slideDirY)
{
  int newDir = dir;
  // test that direction
  float delta = 0.7071*dist;
  float dxf = IntToFloat(dx);
  float dyf = IntToFloat(dy);
  float newCharacterX = characterX + dxf*delta;
  int pixelMove = FloatToInt(newCharacterX) - FloatToInt(characterX);
  if (pixelMove != 0)
  {
    // check the diagonal move
    if (GetWalkableAreaAt(player.x + dx, player.y + dy) != 0)
    {
      // can move that way
      float maxDelta = delta;
      if (maxDelta > 1.0)
      {
        maxDelta = 1.0;
        dist -= 1.414;
      }
      else
      {
        dist = 0.0;
      }
      characterX += dxf*maxDelta;
      characterY += dyf*maxDelta;
    }
    else
    {
      // round off the position
      characterX = IntToFloat(FloatToInt(characterX)) + 0.5;
      characterY = IntToFloat(FloatToInt(characterY)) + 0.5;
      if (GetWalkableAreaAt(player.x + dx, player.y) != 0)
      {
        newDir = slideDirX;
      }
      else
      {
        newDir = slideDirY;
      }
    }
  }
  else
  {
    // pixelMove == 0
    // didn't change pixel so just move within the pixel
    characterX = characterX + dxf*delta;
    characterY = characterY + dyf*delta;
    // stop
    dist = 0.0;
  }
 
  return newDir;
}

function TryStraightMove(float dist, int dx, int dy)
{
  float delta = dist;
  float dxf = IntToFloat(dx);
  float dyf = IntToFloat(dy);
  float newCharacterX = characterX + dxf*delta;
  float newCharacterY = characterY + dyf*delta;
  int pixelMoveX = FloatToInt(newCharacterX) - FloatToInt(characterX);
  int pixelMoveY = FloatToInt(newCharacterY) - FloatToInt(characterY);
  if (pixelMoveX != 0 || pixelMoveY != 0)
  {
    // check the move
    if (GetWalkableAreaAt(player.x + dx, player.y + dy) != 0)
    {
      // can move that way
      float maxDelta = delta;
      if (maxDelta > 1.0)
      {
        maxDelta = 1.0;
        dist -= 1.0;
      }
      else
      {
        dist = 0.0;
      }
      characterX += dxf*maxDelta;
      characterY += dyf*maxDelta;
    }
    else
    {
      // round off the position
      characterX = IntToFloat(FloatToInt(characterX)) + 0.5;
      characterY = IntToFloat(FloatToInt(characterY)) + 0.5;
      // stop moving
      dist = 0.0;
    }
  }
  else
  {
    // pixelMove == 0
    // didn't change pixel so just move within the pixel
    characterX = characterX + dxf*delta;
    characterY = characterY + dyf*delta;
    // stop
    dist = 0.0;
  } 
}

function DirectionFromKeyPresses()
{
  Direct = DIR_NONE;
       if ((IsKeyPressed (371) > 0) || (IsKeyPressed (55) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_UP_LEFT;
  else if ((IsKeyPressed (373) > 0) || (IsKeyPressed (57) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (377) > 0))) Direct = DIR_UP_RIGHT;
        else if ((IsKeyPressed (379) > 0) || (IsKeyPressed (49) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_DOWN_LEFT;
else if ((IsKeyPressed (381) > 0) || (IsKeyPressed (51) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (377) > 0))) Direct =DIR_DOWN_RIGHT;
  else if ((IsKeyPressed (372) > 0) || (IsKeyPressed (56) > 0)) Direct = DIR_UP;
  else if ((IsKeyPressed (375) > 0) || (IsKeyPressed (52) > 0)) Direct = DIR_LEFT;
  else if ((IsKeyPressed (376) > 0) || (IsKeyPressed (53) > 0)) Direct = DIR_STOP;
  else if ((IsKeyPressed (377) > 0) || (IsKeyPressed (54) > 0)) Direct = DIR_RIGHT;
  else if ((IsKeyPressed (380) > 0) || (IsKeyPressed (50) > 0)) Direct = DIR_DOWN;

  return Direct;
}


and then in repeatedly_execute:
Code: ags

if (IsGamePaused()==0) {
					
				Direct = DirectionFromKeyPresses(); // as before
				if (Direct != oldDirect) {
					// changing direction, so reset these
					characterX = IntToFloat(player.x) + 0.5;
					characterY = IntToFloat(player.y) + 0.5;
				}
				if (Direct != DIR_STOP) {
								float dist = 0.75;
								if (IsKeyPressed(keya)) {
									// running, so go twice the distance
									dist = 1.5;
								}
								int newDir = Direct;
								while (dist > 0.0) {
											if (newDir == DIR_DOWN_LEFT) {
												newDir = TryDiagonalMove(dist, newDir, -1, 1, DIR_LEFT, DIR_DOWN);
											}
											else if (newDir == DIR_DOWN_RIGHT) {
												newDir = TryDiagonalMove(dist, newDir, 1, 1, DIR_RIGHT, DIR_DOWN);
											}
											else if (newDir == DIR_UP_LEFT) {
												newDir = TryDiagonalMove(dist, newDir, -1, -1, DIR_LEFT, DIR_UP);
											}
											else if (newDir == DIR_UP_RIGHT) {
												newDir = TryDiagonalMove(dist, newDir, 1, -1, DIR_RIGHT, DIR_UP);
											}
											else if (newDir == DIR_LEFT) {
												TryStraightMove(dist, -1, 0);
											}
											else if (newDir == DIR_RIGHT) {
												TryStraightMove(dist, 1, 0);
											}
											else if (newDir == DIR_UP) {
												TryStraightMove(dist, 0, -1);
											}
											else if (newDir == DIR_DOWN) {
												TryStraightMove(dist, 0, 1);
											}
								} /////////////ENDLESS LOOPING/HANGING HERE
								
				  // now animate the character
				/*animFrame += 0.1;
					int animFrameInt = FloatToInt(animFrame);
					if (animFrameInt > 4) animFrameInt = 0;*/
					// TODO: set the player loop and frame depending on Direct and animFrameInt
				}
				else {
					// TODO: set the standing frame based on oldDirect
				}
				player.x = FloatToInt(characterX);
				player.y = FloatToInt(characterY);
				oldDirect = Direct;

} //end if paused


I'm getting an error when I test the game and hit a direction key that basically tells me the code is looping over and over at the line I've marked above.

Kweepa

#11
[EDIT] THIS IS THE WORKING CODE

Ok, I see the problem I think...
Direct is being set to DIR_NONE which isn't handled by the loop.
What is the difference between DIR_NONE and DIR_STOP?
If you don't know, just remove all references to DIR_NONE and replace them with DIR_STOP.
Or, change the line

if (Direct != DIR_STOP)

to

if (Direct != DIR_STOP || Direct != DIR_NONE)

I'm not near an AGS enabled pc at the moment.
If you'd rather I just got it working later and then got back to you I can do that.

Cheers,
Steve

EDIT - tested and working.

Code: ags

#define DIR_DOWN_LEFT 1
#define DIR_DOWN 2
#define DIR_DOWN_RIGHT 3
#define DIR_LEFT 4
#define DIR_STOP 5
#define DIR_RIGHT 6
#define DIR_UP_LEFT 7
#define DIR_UP 8
#define DIR_UP_RIGHT 9

int oldDirect = DIR_STOP;
int newDirect;
float characterX;
float characterY;
float animFrame = 0.0;

#define MAX_NEARBY_CHARS 8
int gNearbyChars[MAX_NEARBY_CHARS];
int gNumNearbyChars;

// unfortunately I don't see any way to find the character width (help?)
// should probably set up the blocking widths for characters before hand
int GetBlockingWidth(int candidateIndex)
{
Ã,  int blockingWidth = 16;
Ã,  Character *candidate = character[candidateIndex];
Ã,  if (candidate.BlockingWidth != 0)
Ã,  {
Ã,  Ã,  blockingWidth = candidate.BlockingWidth;
Ã,  }
Ã,  return blockingWidth;
}

int GetBlockingHeight(int candidateIndex)
{
Ã,  int blockingHeight = 8;
Ã,  Character *candidate = character[candidateIndex];
Ã,  if (candidate.BlockingHeight != 0)
Ã,  {
Ã,  Ã,  blockingHeight = candidate.BlockingHeight;
Ã,  }
Ã,  return blockingHeight;
}

// replace GetWalkableAreaAt with this
bool PlayerCanMoveHere(int x, int y)
{
Ã,  bool canMoveHere = false;
Ã,  if (GetWalkableAreaAt(x - GetViewportX(), y - GetViewportY()) > 0)
Ã,  {
Ã,  Ã,  canMoveHere = true;
Ã,  Ã,  int candidateIndexIndex = 0;
Ã,  Ã,  while (candidateIndexIndex < gNumNearbyChars && canMoveHere)
Ã,  Ã,  {
Ã,  Ã,  Ã,  int candidateIndex = gNearbyChars[candidateIndexIndex];
Ã,  Ã,  Ã,  Character *candidate = character[candidateIndex];
Ã,  Ã,  Ã,  int dxo = candidate.x - player.x;
Ã,  Ã,  Ã,  int dyo = candidate.y - player.y;
Ã,  Ã,  Ã,  int dxn = candidate.x - x;
Ã,  Ã,  Ã,  int dyn = candidate.y - y;
Ã,  Ã,  Ã,  int cdx = player.BlockingWidth/2 + GetBlockingWidth(candidateIndex)/2;
Ã,  Ã,  Ã,  int cdy = player.BlockingHeight/2 + GetBlockingHeight(candidateIndex)/2;
Ã,  Ã,  Ã,  // overlapping AND getting closer
Ã,  Ã,  Ã,  if ((dxn*dxn < cdx*cdx && dyn*dyn <Ã,  cdy*cdy)
Ã,  Ã,  Ã,  Ã,  Ã, && ((dxn*dxn + dyn*dyn) < (dxo*dxo + dyo*dyo)))
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  canMoveHere = false;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  candidateIndexIndex++;
Ã,  Ã,  }
Ã,  }
Ã,  return canMoveHere;
}

float TryDiagonalMove(float dist, int dx, int dy, int slideDirX, int slideDirY)
{
Ã,  // test that direction
Ã,  float delta = 0.7071*dist;
Ã,  float dxf = IntToFloat(dx);
Ã,  float dyf = IntToFloat(dy);
Ã,  float newCharacterX = characterX + dxf*delta;
Ã,  float newCharacterY = characterY + dyf*delta;
Ã,  int pixelMoveX = FloatToInt(newCharacterX) - FloatToInt(characterX);
Ã,  int pixelMoveY = FloatToInt(newCharacterY) - FloatToInt(characterY);
Ã,  if (pixelMoveX != 0 || pixelMoveY != 0)
Ã,  {
Ã,  Ã,  // check the diagonal move
Ã,  Ã,  if (PlayerCanMoveHere(player.x + dx, player.y + dy))
Ã,  Ã,  {
Ã,  Ã,  Ã,  // can move that way
Ã,  Ã,  Ã,  float maxDelta = delta;
Ã,  Ã,  Ã,  if (maxDelta > 1.0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  maxDelta = 1.0;
Ã,  Ã,  Ã,  Ã,  dist -= 1.414;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  characterX += dxf*maxDelta;
Ã,  Ã,  Ã,  characterY += dyf*maxDelta;
Ã,  Ã,  }
Ã,  Ã,  else
Ã,  Ã,  {
Ã,  Ã,  Ã,  if (PlayerCanMoveHere(player.x + dx, player.y))
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  // round off the position
Ã,  Ã,  Ã,  Ã,  characterY = IntToFloat(FloatToInt(characterY)) + 0.5;
Ã,  Ã,  Ã,  Ã,  newDirect = slideDirX;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  // round off the position
Ã,  Ã,  Ã,  Ã,  characterX = IntToFloat(FloatToInt(characterX)) + 0.5;
Ã,  Ã,  Ã,  Ã,  newDirect = slideDirY;
Ã,  Ã,  Ã,  }
Ã,  Ã,  }
Ã,  }
Ã,  else
Ã,  {
Ã,  Ã,  // pixelMove == 0
Ã,  Ã,  // didn't change pixel so just move within the pixel
Ã,  Ã,  characterX = newCharacterX;
Ã,  Ã,  characterY = newCharacterY;
Ã,  Ã,  // stop
Ã,  Ã,  dist = 0.0;
Ã,  }

Ã,  return dist;
}

float TryStraightMove(float dist, int dx, int dy)
{
Ã,  float delta = dist;
Ã,  float dxf = IntToFloat(dx);
Ã,  float dyf = IntToFloat(dy);
Ã,  float newCharacterX = characterX + dxf*delta;
Ã,  float newCharacterY = characterY + dyf*delta;
Ã,  int pixelMoveX = FloatToInt(newCharacterX) - FloatToInt(characterX);
Ã,  int pixelMoveY = FloatToInt(newCharacterY) - FloatToInt(characterY);
Ã,  if (pixelMoveX != 0 || pixelMoveY != 0)
Ã,  {
Ã,  Ã,  // check the move
Ã,  Ã,  if (PlayerCanMoveHere(player.x + dx, player.y + dy))
Ã,  Ã,  {
Ã,  Ã,  Ã,  // can move that way
Ã,  Ã,  Ã,  float maxDelta = delta;
Ã,  Ã,  Ã,  if (maxDelta > 1.0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  maxDelta = 1.0;
Ã,  Ã,  Ã,  Ã,  dist -= 1.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  else
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  characterX += dxf*maxDelta;
Ã,  Ã,  Ã,  characterY += dyf*maxDelta;
Ã,  Ã,  }
Ã,  Ã,  else
Ã,  Ã,  {
Ã,  Ã,  Ã,  // round off the position
Ã,  Ã,  Ã,  characterX = IntToFloat(player.x) + 0.5;
Ã,  Ã,  Ã,  characterY = IntToFloat(player.y) + 0.5;
Ã,  Ã,  Ã,  // stop moving
Ã,  Ã,  Ã,  dist = 0.0;
Ã,  Ã,  }
Ã,  }
Ã,  else
Ã,  {
Ã,  Ã,  // pixelMove == 0
Ã,  Ã,  // didn't change pixel so just move within the pixel
Ã,  Ã,  characterX = newCharacterX;
Ã,  Ã,  characterY = newCharacterY;
Ã,  Ã,  // stop
Ã,  Ã,  dist = 0.0;
Ã,  }

Ã,  return dist;
}

function DirectionFromKeyPresses()
{
Ã,  int Direct = DIR_STOP;
Ã,  if ((IsKeyPressed (371) > 0) || (IsKeyPressed (55) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_UP_LEFT;
Ã,  else if ((IsKeyPressed (373) > 0) || (IsKeyPressed (57) > 0) || ((IsKeyPressed (372) > 0) && (IsKeyPressed (377) > 0))) Direct = DIR_UP_RIGHT;
Ã,  else if ((IsKeyPressed (379) > 0) || (IsKeyPressed (49) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (375) > 0))) Direct = DIR_DOWN_LEFT;
Ã,  else if ((IsKeyPressed (381) > 0) || (IsKeyPressed (51) > 0) || ((IsKeyPressed (380) > 0) && (IsKeyPressed (377) > 0))) Direct =DIR_DOWN_RIGHT;
Ã,  else if ((IsKeyPressed (372) > 0) || (IsKeyPressed (56) > 0)) Direct = DIR_UP;
Ã,  else if ((IsKeyPressed (375) > 0) || (IsKeyPressed (52) > 0)) Direct = DIR_LEFT;
Ã,  else if ((IsKeyPressed (376) > 0) || (IsKeyPressed (53) > 0)) Direct = DIR_STOP;
Ã,  else if ((IsKeyPressed (377) > 0) || (IsKeyPressed (54) > 0)) Direct = DIR_RIGHT;
Ã,  else if ((IsKeyPressed (380) > 0) || (IsKeyPressed (50) > 0)) Direct = DIR_DOWN;

Ã,  return Direct;
}



#sectionstart game_startÃ,  // DO NOT EDIT OR REMOVE THIS LINE
function game_start() {
Ã,  characterX = IntToFloat(player.x) + 0.5;
Ã,  characterY = IntToFloat(player.y) + 0.5;
}
#sectionend game_startÃ,  // DO NOT EDIT OR REMOVE THIS LINE


#sectionstart repeatedly_executeÃ,  // DO NOT EDIT OR REMOVE THIS LINE
function repeatedly_execute() {
Ã,  // put anything you want to happen every game cycle here
Ã,  if (IsGamePaused()==0) {

Ã,  Ã,  int Direct = DirectionFromKeyPresses(); // as before

Ã,  Ã,  if (Direct != oldDirect) {
Ã,  Ã,  Ã,  // changing direction, so reset these
Ã,  Ã,  Ã,  characterX = IntToFloat(player.x) + 0.5;
Ã,  Ã,  Ã,  characterY = IntToFloat(player.y) + 0.5;
Ã,  Ã,  }
Ã,  Ã,  if (Direct != DIR_STOP) {
Ã,  Ã,  Ã,  float dist = 0.75;
Ã,  Ã,  Ã,  if (IsKeyPressed(65)) // 'A'
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  dist = 1.5;
Ã,  Ã,  Ã,  }

Ã,  Ã,  Ã,  // before the movement in rep_ex
Ã,  Ã,  Ã,  gNumNearbyChars = 0;
Ã,  Ã,  Ã,  int candidateIndex = 0;
Ã,  Ã,  Ã,  int numCharacters = GetGameParameter(GP_NUMCHARACTERS, 0, 0, 0);
Ã,  Ã,  Ã,  if (player.BlockingWidth == 0) player.BlockingWidth = 16;
Ã,  Ã,  Ã,  if (player.BlockingHeight == 0) player.BlockingHeight = 8;
Ã,  Ã,  Ã,  while (candidateIndex < numCharacters && gNumNearbyChars < MAX_NEARBY_CHARS)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  Character *candidate = character[candidateIndex];
Ã,  Ã,  Ã,  Ã,  if (candidate != player && candidate.Solid && candidate.Room == player.Room)
Ã,  Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  Ã,  // check proximity to player
Ã,  Ã,  Ã,  Ã,  Ã,  int dx = player.x - candidate.x;
Ã,  Ã,  Ã,  Ã,  Ã,  // 10 is a made up number but I doubt the player will move more than 10 pix/frame
Ã,  Ã,  Ã,  Ã,  Ã,  int cdx = player.BlockingWidth/2 + GetBlockingWidth(candidateIndex)/2 + 10;
Ã,  Ã,  Ã,  Ã,  Ã,  int dy = player.y - candidate.y;
Ã,  Ã,  Ã,  Ã,  Ã,  int cdy = player.BlockingHeight/2 + GetBlockingHeight(candidateIndex)/2 + 10;
Ã,  Ã,  Ã,  Ã,  Ã,  if ((dx*dx < cdx*cdx) && (dy*dy < cdy*cdy))
Ã,  Ã,  Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  Ã,  Ã,  gNearbyChars[gNumNearbyChars] = candidateIndex;
Ã,  Ã,  Ã,  Ã,  Ã,  Ã,  gNumNearbyChars++;
Ã,  Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  candidateIndex++;
Ã,  Ã,  Ã,  }

Ã,  Ã,  Ã,  newDirect = Direct;
Ã,  Ã,  Ã,  while (dist > 0.0) {
Ã,  Ã,  Ã,  Ã,  if (newDirect == DIR_DOWN_LEFT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryDiagonalMove(dist, -1, 1, DIR_LEFT, DIR_DOWN);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_DOWN_RIGHT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryDiagonalMove(dist, 1, 1, DIR_RIGHT, DIR_DOWN);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_UP_LEFT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryDiagonalMove(dist, -1, -1, DIR_LEFT, DIR_UP);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_UP_RIGHT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryDiagonalMove(dist, 1, -1, DIR_RIGHT, DIR_UP);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_LEFT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryStraightMove(dist, -1, 0);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_RIGHT) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryStraightMove(dist, 1, 0);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_UP) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryStraightMove(dist, 0, -1);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  else if (newDirect == DIR_DOWN) {
Ã,  Ã,  Ã,  Ã,  Ã,  dist = TryStraightMove(dist, 0, 1);
Ã,  Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  Ã,  player.x = FloatToInt(characterX);
Ã,  Ã,  Ã,  Ã,  player.y = FloatToInt(characterY);
Ã,  Ã,  Ã,  }
Ã,  Ã,  }
Ã,  Ã,  oldDirect = Direct;

Ã,  Ã,  // now animate the character
Ã,  Ã,  if (Direct != DIR_STOP)
Ã,  Ã,  {
Ã,  Ã,  Ã,  animFrame += 0.1;
Ã,  Ã,  Ã,  // based on Roger who has 10 frames in the walk loop
Ã,  Ã,  Ã,  if (animFrame >= 10.0)
Ã,  Ã,  Ã,  {
Ã,  Ã,  Ã,  Ã,  // 9 of which are the walk cycle
Ã,  Ã,  Ã,  Ã,  animFrame -= 9.0;
Ã,  Ã,  Ã,  }
Ã,  Ã,  Ã,  // change these if you only have four directions
Ã,  Ã,  Ã,  if (newDirect == DIR_DOWN_LEFT) player.Loop = 6;
Ã,  Ã,  Ã,  else if (newDirect == DIR_DOWN_RIGHT) player.Loop = 4;
Ã,  Ã,  Ã,  else if (newDirect == DIR_UP_LEFT) player.Loop = 7;
Ã,  Ã,  Ã,  else if (newDirect == DIR_UP_RIGHT) player.Loop = 5;
Ã,  Ã,  Ã,  else if (newDirect == DIR_LEFT) player.Loop = 1;
Ã,  Ã,  Ã,  else if (newDirect == DIR_RIGHT) player.Loop = 2;
Ã,  Ã,  Ã,  else if (newDirect == DIR_UP) player.Loop = 3;
Ã,  Ã,  Ã,  else if (newDirect == DIR_DOWN) player.Loop = 0;
Ã,  Ã,  Ã,  player.Frame = FloatToInt(animFrame);
Ã,  Ã,  }
Ã,  Ã,  else {
Ã,  Ã,  Ã,  // set the standing frame and reset the walk cycle
Ã,  Ã,  Ã,  player.Frame = 0;
Ã,  Ã,  Ã,  animFrame = 1.0;
Ã,  Ã,  }

Ã,  } //end if paused
Ã,  
}
#sectionend repeatedly_executeÃ,  // DO NOT EDIT OR REMOVE THIS LINE


[EDIT] Changed back from cEgo to player, restored running code.
[EDIT2] Added GetViewPort* adjustments for GetWalkableAreaAt() calls.
[EDIT3] Changed round off in diagonal move to only round the required axis.
[EDIT4] Added animation code.
[EDIT5] Fixed occasional getting stuck in a wall.
[EDIT6] Updated to reset the walk cycle and add comments about the number of walk frames.
[EDIT7] Updated to collide with characters.
[EDIT8] Added room check to characters.
Still waiting for Purity of the Surf II

Kinoko

Well, I tried this one out and I get an error with the line: float TryDiagonalMove(float dist, int dx, int dy, int slideDirX, int slideDirY) {

saying it has the wrong number of parameters. I thought you must have meant to write "function" instead of "float" so I changed it and I still get the same message.

monkey0506

#13
It is supposed to be float, but the problem would be with the function being declared (imported into a header) differently than the definition (what you are having problems with).

Edit:  I don't see a script header, so this may not be the problem.  But that's what it would appear to be with saying the function has the wrong number of parameters when trying to declare it.

Edit:  Also, just for the record, "function" is defined as "int".  By typing "float" instead of function you state that the return type is "float" (as apposed to "int" if you had written "function").  You can define the return type of all your functions by doing this (using return type in place of the word "function" (and it's safer because if you try to return a "float" from a "function", it's liable to either a) convert the float or b) fail when called...o_0)).  Just FYI.

Kweepa

#14
Check you haven't left some old code in there:

If you search for TryDiagonalMove, do you find two definitions of the function?
There should be one definition and four uses.
If there are more, remove the ones that don't correspond to the latest code posted here.

[EDIT] And trying to return a float from a "function" won't compile -- it says the return type is wrong, which by a staggering coincidence, it is.
Still waiting for Purity of the Surf II

monkey0506

Yeah, trying to define it twice would have the same effect I suppose.  And I figured the thing with not being able to return a float from a "function".

Kinoko

You were right, monkey. I hadn't changed the script header.

Well, the game compiles without any problems now, but when I test the game, my character won't move. The walkable areas are all still there.

Kweepa

Is it a scrolling room?
GetWalkableAreaAt expects screen coordinates, not room coordinates, so you have to adjust the coordinates to take account of that in the GetWalkableAreaAt() calls, like so:

GetWalkableAreaAt(player.x + dx - GetViewportX(), player.y + dy - GetViewportY())

I forgot about that, sorry.
If it's not, I'm not sure what the problem is.

We'll get there!
Still waiting for Purity of the Surf II

Kinoko

It's a scrolling room. Given that I just had this exact same problem a couple of days ago, this should have occured to me.

Okay!! It's working for left and right, but not up and down (ie. Not when the character runs into a blockage above or below)

Kweepa

#19
So, what happens up and down?
[EDIT] Did you get all three occurrences of GetWalkableAreaAt()?
Still waiting for Purity of the Surf II

SMF spam blocked by CleanTalk