unit CComFunc;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, DXPLay, DirectX, StdCtrls;

const
  GAME_DATAMSG = 20; //for  use in TGAME_DATA.dwType

type
  //This record is provided for message data storage
  TGAME_DATA = record
    dwType: DWORD;
    GameFlagIs: Integer;
    GameDataIs: array[0..255] of Char;
  end;

type
  TPLAYERGAME_DATA = record //each player get his own record
    PlayerIDIs: LongInt;
    PlayerGameFlagIs: Integer;
    PlayerGameDataIs: string;
    pgdata: array[0..9] of string; //10 slots provided for parsed data
  end;

type
  PlayerDataRecArray = array[0..24] of TPLAYERGAME_DATA; // 25 player slots

type
  TCCom1 = class(TForm)
    DXPlay1: TDXPlay;
    procedure DXPlay1AddPlayer(Sender: TObject; Player: TDXPlayPlayer);
    procedure DXPlay1DeletePlayer(Sender: TObject; Player: TDXPlayPlayer);
    procedure DXPlay1Message(Sender: TObject; From: TDXPlayPlayer;
      Data: Pointer; DataSize: Integer);
    procedure DXPlay1Open(Sender: TObject);
  private
    function NumToken(aString, SepChar: string): Byte;
    procedure ParseTheDataString(aString, SepChar: string; TokenNum: Byte; PlayerIndexNumIs: Integer);
  public
    procedure Send_GameDataMsg(SendGameFlagIs: Integer; SendGameDataIs: string; SendToAddressIs: TDPID);
    procedure Parse_GameData(PlayerIndexNumIs: Integer);
    function FindPlayerGameDataRec(From: TDXPlayPlayer): Integer;
    function ClearPlayerGameData(From: TDXPlayPlayer): Boolean;
    procedure InitPlayerGameDataArray;
    function CreatePlayerGameDataRec(From: TDXPlayPlayer): Integer;
  end;

var
  CCom1: TCCom1;
  Msg: ^TGAME_DATA;
  MyPlayerDataRecArray: PlayerDataRecArray;
  MsgSize: Integer;
  MyPlayerShortNameIs: string;
  MyPlayerNumIs: string;
  ClientIsUp: Boolean;
  PlayerIDSelected: TDPID;
  ServerIDIs: TDPID;
  TheGameNameIs: string;
  TheGamePortIs: Integer;
  TheGameHostIs: string;

implementation

uses Main, CChatMod;

{$R *.DFM}

function TCCom1.CreatePlayerGameDataRec(From: TDXPlayPlayer): Integer;
var
  xloop, temgprecis: Integer;
begin
{Use this function to create a player record in the
 MyPlayerDataRecArray record array.  If record creation was
 successful than the array index number will for that player
 will be returned, otherwise a -1 will be returned.
}
  temgprecis := -1;
  for xloop := 0 to 24 do
  begin
    if MyPlayerDataRecArray[xloop].PlayerIDIs = -1 then
    begin
      MyPlayerDataRecArray[xloop].PlayerIDIs := From.ID;
      temgprecis := xloop;
      if temgprecis <> -1 then Break;
    end;
  end;
  Result := temgprecis;
end;

function TCCom1.FindPlayerGameDataRec(From: TDXPlayPlayer): Integer;
var
  xloop, temgprecis: Integer;
begin
{ Use this function to find an individual record in the
  MyPlayerDataRecArray record array. If the players record is found
  than the returned result will be the array index number of the
  record found.  If no record is found it will return -1.
}
  temgprecis := -1;
  for xloop := 0 to 24 do
  begin
    if MyPlayerDataRecArray[xloop].PlayerIDIs = From.ID then
    begin
      temgprecis := xloop;
      if temgprecis <> -1 then Break;
    end;
  end;
  Result := temgprecis;
end;

procedure TCCom1.InitPlayerGameDataArray;
var
  xloop, yloop: Integer;
begin
{Initializes the MyPlayerDataRecArray record array.
}
  for xloop := 0 to 24 do
  begin
    MyPlayerDataRecArray[xloop].PlayerIDIs := -1;
    MyPlayerDataRecArray[xloop].PlayerGameFlagIs := 0;
    MyPlayerDataRecArray[xloop].PlayerGameDataIs := '';
    for yloop := 0 to 9 do
    begin
      MyPlayerDataRecArray[xloop].pgdata[yloop] := '';
    end;
  end;
end;

function TCCom1.ClearPlayerGameData(From: TDXPlayPlayer): Boolean;
var
  xloop, yloop: Integer;
begin
{Use this function to clear out the received game data for this player
 Returns False if no player record was found and True if the player
 record was found.
}

  Result := False;
  for xloop := 0 to 24 do
  begin
    if MyPlayerDataRecArray[xloop].PlayerIDIs = From.ID then
    begin
      Result := True;
      MyPlayerDataRecArray[xloop].PlayerIDIs := -1;
      MyPlayerDataRecArray[xloop].PlayerGameFlagIs := 0;
      MyPlayerDataRecArray[xloop].PlayerGameDataIs := '';
      for yloop := 0 to 9 do
      begin
        MyPlayerDataRecArray[xloop].pgdata[yloop] := '';
      end;
    end;
  end;
end;

procedure TCCom1.ParseTheDataString(aString, SepChar: string; TokenNum: Byte; PlayerIndexNumIs: Integer);
var
  Token: string;
  StrLen: Byte;
  TNum: Byte;
  TEnd: Byte;
  xloop: Byte;
begin
{ Parse the received data string and place into MYGPParsedDataRecArray
  record for that sending player. This procedure will break the received
  data string into a max of 10 sub-strings and place each sub-string, in
  the order found in the received data string, into the
  MYGPParsedDataRecArray.pgdata array. This will only occur if the
  number of separation characters (ie; "~") found was greater than zero.
  When the data is placed into the pgdata array it will be in string
  format and minus the separation character.  Example "Apple~" sub-string
  will be stored in the pgdata array as string "Apple". If no sub-strings
  are found the entire received data string will be placed into the
  pgdata[0] array element in string format.
}
  StrLen := Length(aString);
  TNum := 1;
  TEnd := StrLen;
  if TokenNum = 0 then
  begin
    MyPlayerDataRecArray[PlayerIndexNumIs].pgdata[0] := aString;
  end;
  if TokenNum > 0 then
  begin
    for xloop := 0 to TokenNum - 1 do
    begin
      TEnd := Pos(SepChar, aString);
      if TEnd <> 0 then
      begin
        Token := Copy(aString, 1, TEnd - 1);
        MyPlayerDataRecArray[PlayerIndexNumIs].pgdata[xloop] := Token;
        Delete(aString, 1, TEnd);
      end;
    end;
  end;
end;

function TCCom1.NumToken(aString, SepChar: string): Byte;
var
  StrLen: Byte;
  TNum: Byte;
  xloop: Byte;
  SepCharStr: string;

begin
{Here we are determining how many sub-strings exists in the received
 data string. When sending sub-strings in the data string ensure that
 EACH sub-string, including the first and last ones, has a "~" character
 appended to the end of it. You do not have to add a "~" character if
 no sub-strings exists.
}
  StrLen := Length(aString);
  TNum := 0;
  for xloop := 0 to StrLen - 1 do
  begin
    SepCharStr := '';
    SepCharStr := Copy(aString, xloop + 1, 1);
    if SepCharStr = '~' then Inc(TNum);
  end;
  Result := TNum;
end;

procedure TCCom1.Parse_GameData(PlayerIndexNumIs: Integer);
var
  pgd: Byte;
  SepChar: string;
begin
{ Here we do three things:
  1- Establish the separation charcter for sub-strings, in this case a
     "~" character.
  2- Find out how many sub-strings are contained in the data message
     string using the NUMTOKEN function.
  3- Parse the data string using the ParseTheDataString procedure.
}
  SepChar := '~';
  pgd := NumToken(MyPlayerDataRecArray[PlayerIndexNumIs].PlayerGameDataIs, SepChar);
  ParseTheDataString(MyPlayerDataRecArray[PlayerIndexNumIs].PlayerGameDataIs, SepChar, pgd, PlayerIndexNumIs);
end;

procedure TCCom1.Send_GameDataMsg(SendGameFlagIs: Integer; SendGameDataIs: string; SendToAddressIs: TDPID);
begin
 {Send a game data message to a player of your choice or the Game server.
  DPSEND_GUARANTEED option flag is being used automatically in the
  DxPlay unit so message is guaranteed to arrive at the designated
  location if the player's client or the server is online. When
  DPSEND_GUARANTEED is in use DirectX DirectPlay waits for a confirmation
  that the client or server has received the message and the message was
  not corrupted.  DirectPlay will resend messages in cases of corruption
  or non-receipt.  The downside of this is that network latency factors
  in the game will be doubled or tripled as a result but you can at least
  be sure that the information got there.   SendGameFlagIs is any integer
  number and SendGameDataIs is any string up to 254 characters in length.
  If you want to send a message to all players than make
  SendToAddressIs := DPID_ALLPLAYERS.
  }
  if SendGameFlagIs > 0 then
  begin
    MsgSize := SizeOf(TGAME_DATA);
    GetMem(Msg, MsgSize);
    Msg.dwType := GAME_DATAMSG;
    Msg.GameFlagIs := SendGameFlagIs;
    StrPCopy(Msg.GameDataIs, SendGameDataIs);
    try
      DxPlay1.SendMessage(SendToAddressIs, Msg, MsgSize);
    finally
      FreeMem(Msg);
    end;
  end;
end;

procedure TCCom1.DXPlay1AddPlayer(Sender: TObject; Player: TDXPlayPlayer);
begin
 {When a new player joins the game DirectPlay generates this event
  automatically to notify you of this and provides information on
  that player.  Information is provided in the following record
  structure:

  Player.ID: Players Unique identification number (LongInt)
  Player.Name: Players name (String)
  Player.RemotePlayer: Is this a remote player client instead
                        of the server itself? (Boolean)
}

  MainForm.Memo1.Lines.Add(Format('%s entered a room.', [Player.Name]));
end;

procedure TCCom1.DXPlay1DeletePlayer(Sender: TObject;
  Player: TDXPlayPlayer);
begin
 {When a player or the game server goes offline for whatever reason
  DirectPlay generates this event automatically to notify you of this
  and provides information on that player.  Information is provided
  in the following record structure:

  Player.ID: Players Unique identification number (LongInt)
  Player.Name: Players name (String)
  Player.RemotePlayer: Is this a remote player client instead
                        of the server itself? (Boolean)
}
  MainForm.Memo1.Lines.Add(Format('%s left a room.', [Player.Name]));
end;

procedure TCCom1.DXPlay1Message(Sender: TObject; From: TDXPlayPlayer;
  Data: Pointer; DataSize: Integer);
var
  PlayerIndexNumIs: Integer;
  tempGameDataIs: string;
  tempGameFlagIs: Integer;
begin

{ This is where the main action is in the game client. Think of this
  procedure as the telephone operator for the Game Server who receives
  the calls and routes them where they need to go. Any game data message
  sent to the server will end up here. Upon arrival of the message it
  will be decoded by the Read_GameDataMsg procedure and then the case
  statement will route it where ever it needs to go. Game Flags can be
  any integer from 0 to whatever.  We suggest that you reserve number
  999 for chat messages. When DirectPlay generates this event it also
  provides you information on where the message came from in the
  following record structure:

  From.ID: Players Unique identification number (LongInt)
  From.Name: Players name (String)
  From.RemotePlayer: Is this a remote player client instead
                        of the server itself? (Boolean)
}
  case DXPlayMessageType(Data) of
    GAME_DATAMSG: begin
        tempGameFlagIs := TGAME_DATA(Data^).GameFlagIs;
        MyPlayerDataRecArray[0].PlayerGameFlagIs := tempGameFlagIs;
        tempGameDataIs := StrPas(TGAME_DATA(Data^).GameDataIs);
        MyPlayerDataRecArray[0].PlayerGameDataIs := trim(tempGameDataIs);
        Parse_GameData(0);
        PlayerIndexNumIs := 0; //Client will always have the 0 record
        case MyPlayerDataRecArray[PlayerIndexNumIs].PlayerGameFlagIs of
          1: ; //Player Moving North
          2: ; //Player Moving East
          3: ; //Player Moving South
          4: ; //Player Moving West
          5: begin //Server ok's the move
              MainForm.ExecuteGameMove;
            end;
          6: ; //Server disapproves the move
          7: begin //You have joined a game - start moving
              ServerIDIs := From.ID;
              MainForm.BeginTheGame;
            end;
          999: begin //A chat message has been received
              MainForm.Memo1.Lines.Add(From.Name + ' wants to chat with you.');
              ChatModForm1.Memo1.Lines.Add(From.Name + '>>' + MyPlayerDataRecArray[PlayerIndexNumIs].PlayerGameDataIs);
            end;
        end;
      end;
  end;
end;

procedure TCCom1.DXPlay1Open(Sender: TObject);
var
  i: Integer;
begin
{This event Enumerates all the players currently online}
  for i := 0 to DxPlay1.Players.Count - 1 do
    if DxPlay1.Players[i].RemotePlayer then
      MainForm.Memo1.Lines.Add(Format('%s is entering a room.', [DxPlay1.Players[i].Name]));
end;

end.

