Game Development Community

dev|Pro Game Development Curriculum

Object-Oriented Programming in T2D 1.0.0

by Bryan Edds · 03/04/2005 (7:36 am) · 33 comments

Download Code File

THIS RESOURCE IS DEPRECATED! Find the new 3.0 version HERE.

Well, here goes. Hopefully this will show exactly how to implement OO in T2D 1.0.0 without giving anyone problems.

It is advised that you back up all the files that require modification in this resource before making any changes. Try to follow the instructions as accurately as possible.

For easier copying and pasting of the code in this resource, do not copy and paste from this web page. Instead, use the attached OOInT2DScriptInstructions.txt file.

Here's the same instructions on this webpage for your quick veiwing conveniance.

In console/compileEval.cc starting on line 1047, replace this code -
if(ns)
                  nsEntry = ns->lookup(fnName);
               else
                  nsEntry = NULL;
with this code -
/////////////////////////////////////
               // CODE MODIFIED BRYAN EDDS
               /////////////////////////////////////
               /*if(ns)
                  nsEntry = ns->lookup(fnName);
               else
                  nsEntry = NULL;*/
               if(ns)
               {
                  nsEntry = ns->lookup(fnName);

                  // while we have not found a function entry...
                  while (!nsEntry)
                  {
                     // while there is a parent, set ns = to it, and try 
                     // looking up the function again
                     if (ns->mParent)
                     {
                        ns = ns->mParent;
                        nsEntry = ns->lookup(fnName);
                     }
                     // if there are no more parents to search, the function
                     // cannot be found
                     else
                     {
                        nsEntry = NULL;
                        break;
                     }
                  }
               }
               else
               {
                  nsEntry = NULL;
               }
               /////////////////////////////////////
               // END CODE MODIFIED BRYAN EDDS
               /////////////////////////////////////

In console/simBase.cc directly after this line of code on line 201 -
mFieldDictionary = NULL;
insert this code on a new line -
/////////////////////////////////////
   // CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
   mClassName = "";
   mSuperClassName = "";
   /////////////////////////////////////
   // END CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
In the same file after this line of code on line 803 -
mNameSpace = getClassRep()->getNameSpace();
insert this code on a new line -
/////////////////////////////////////
   // CODE INSERTED BY BRYAN EDDS 
   // AND HAROLD "LABRAT" BROWN
   /////////////////////////////////////

   // parent = C++ Class StringTableEntry
   StringTableEntry parent = getClassRep()->getNameSpace()->mName;

   // objectName = name of instance
   StringTableEntry objectName = getName();

   // here we'll make sure that the instance name is not the same as
   // the classname, superclassname, or parent name. If it is, we'll con-
   // catenate the word "Instance" to the end of the name.
   if(objectName)
   {
      if(mClassName)
      {
         if(dStricmp(mClassName, objectName) == 0)
         {
            int chars = 0;
            while(objectName[chars] != 0) ++chars;

            char * tempStr = new char[chars + 10];
            dStrcpy(tempStr, objectName);
            tempStr = dStrcat(tempStr, "Instance");
            objectName = StringTable->insert(tempStr);

            delete [] tempStr;
         }
      }

      if(mSuperClassName)
      {
         if(dStricmp(mSuperClassName, objectName) == 0)
         {
            int chars = 0;
            while(objectName[chars] != 0) ++chars;

            char * tempStr = new char[chars + 10];
            dStrcpy(tempStr, objectName);
            tempStr = dStrcat(tempStr, "Instance");
            objectName = StringTable->insert(tempStr);

            delete [] tempStr;
         }
      }

      if(parent)
      {
         if(dStricmp(parent, objectName) == 0)
         {
            int chars = 0;
            while(objectName[chars] != 0) ++chars;

            char * tempStr = new char[chars + 10];
            dStrcpy(tempStr, objectName);
            tempStr = dStrcat(tempStr, "Instance");
            objectName = StringTable->insert(tempStr);

            delete [] tempStr;
         }
      }
   }
   
   // it's possible that all the namespace links can fail if
   // multiple objects are named the same thing with different script
   // hierarchies.
   // linkNamespaces will now return false and echo an error message
   // rather than asserting.
   
   // if superClass was used then change parent to the proper NameSpace
   // else leave parent = "SimObject"
   if(mSuperClassName && mSuperClassName[0])
   {  
      // walk = SuperClass's NameSpace
      Namespace *walk = Con::lookupNamespace(mSuperClassName);

      // prnt = "SimObject" NameSpace
      Namespace *prnt = Con::lookupNamespace(parent);

      // insert the SuperClass Namespace into the StringTable
      StringTableEntry mName = StringTable->insert(mSuperClassName);

      // walk = its parent if walk has a parent and it is SuperClass 
      while(walk->mParent && walk->mParent->mName == mName)
      walk = walk->mParent;

      // if walk has a parent, and its parent is not "SimObject",
      // then parent = "SuperClass" StringTableEntry
      if(walk->mParent && walk->mParent != prnt)
         parent = mSuperClassName;

      // if walk doesn't have a parent or its parent is not SimObject
      // link parent and SuperClass StringTableEntries
      // if link is successful, parent = SuperClass StringTableEntry
      else {
         if(Con::linkNamespaces(parent, mSuperClassName))
            parent = mSuperClassName;
      }
   }
      
   // className -> superClassName

   // if class was used then change parent to the proper NameSpace
   // else leave parent = "SimObject"
   if (mClassName && mClassName[0])
   {
      // walk = Class NameSpace
      Namespace *walk = Con::lookupNamespace(mClassName);

      // prnt = parent NameSpace
      Namespace *prnt = Con::lookupNamespace(parent);

      // mName = Class StringTable
      StringTableEntry mName = StringTable->insert(mClassName);

      // walk = its parent while
      // it has a parent and its parent's name = Class StringTable
      while(walk->mParent && walk->mParent->mName == mName)
         walk = walk->mParent;

      // if walk has a parent and it is not the parent NameSpace
      // then parent = Class StringTableEntry
      if(walk->mParent && walk->mParent != prnt)
         parent=mClassName;
      // else if walk has no parent or parent is the parent NameSpace
      // then link the parent and Class StringTableEntries
      // if the link was successful, parent = Class StringTableEntry
      else {
         if(Con::linkNamespaces(parent, mClassName))
            parent = mClassName;
      }
   }

   // objectName -> className

   // if name of this instance exists
   // link parent and objectName StringTableEntries
   // if link is success, parent = objectName
   if (objectName && objectName[0])
      if(Con::linkNamespaces(parent, objectName))
         parent = objectName;

   // Store our namespace
   mNameSpace = Con::lookupNamespace(parent);   

   /////////////////////////////////////
   // END CODE INSERTED BY BRYAN EDDS 
   // AND HAROLD "LABRAT" BROWN
   /////////////////////////////////////
In the same file after this line of code SOMEWHERE AROUND line 1103 (sorry, I lost track)-
Parent::initPersistFields();
insert this code on a new line -
/////////////////////////////////////
   // CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
   addGroup("Classes", "Sim objects have the ability to inherit and have class information.");
   addField("className", TypeString, Offset(mClassName, SimObject), "Class of object.");
   addField("superClassName", TypeString, Offset(mSuperClassName, SimObject), "Superclass of object.");

   // members "class" and "superClass" are retained to be backward compatible
   // ScriptObject. Rather icky, but better than the alternative...
   addField("class", TypeString, Offset(mClassName, SimObject), "Class of object.");
   addField("superClass", TypeString, Offset(mSuperClassName, SimObject), "Superclass of object.");
   
   endGroup("Classes");
   /////////////////////////////////////
   // END CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
In the same file, on the very last line, insert this code -
/////////////////////////////////////
// CODE INSERTED BY BRYAN EDDS
/////////////////////////////////////
ConsoleFunction(link, void, 4, 4, "(string class, string superClass, string parent)")
{
   argc;

   StringTableEntry className;
   StringTableEntry superClassName;
   StringTableEntry parent;

   if(argv[1] && argv[1][0])
      className = StringTable->insert(argv[1]);

   if(argv[2] && argv[2][0])
      superClassName = StringTable->insert(argv[2]);

   if(argv[3] && argv[3][0])
      parent = StringTable->insert(argv[3]);
   
   // parent = C++ Class StringTableEntry
   //StringTableEntry parent = getClassRep()->getNameSpace()->mName;
   
   // it's possible that all the namespace links can fail, if
   // multiple objects are named the same thing with different script
   // hierarchies.
   // linkNamespaces will now return false and echo an error message
   // rather than asserting.
   
   // if superClass was used then change parent to the proper NameSpace
   // else leave parent = "SimObject"
   if(superClassName && superClassName[0])
   {  
      // walk = SuperClass's NameSpace
      Namespace *walk = Con::lookupNamespace(superClassName);

      // prnt = "SimObject" NameSpace
      Namespace *prnt = Con::lookupNamespace(parent);

      // mName = superClassName
      StringTableEntry mName = superClassName;

      // walk = its parent if walk has a parent and it is SuperClass 
      while(walk->mParent && walk->mParent->mName == mName)
      walk = walk->mParent;

      // if walk has a parent, and its parent is not "SimObject",
      // then parent = "SuperClass" StringTableEntry
      if(walk->mParent && walk->mParent != prnt)
         parent = superClassName;

      // if walk doesn't have a parent or its parent is not SimObject
      // link parent and SuperClass StringTableEntries
      // if link is successful, parent = SuperClass StringTableEntry
      else {
         if(Con::linkNamespaces(parent, superClassName))
            parent = superClassName;
      }
   }
      
   // className -> superClassName

   // if class was used then change parent to the proper NameSpace
   // else leave parent = "SimObject"
   if (className && className[0])
   {
      // walk = Class NameSpace
      Namespace *walk = Con::lookupNamespace(className);

      // prnt = parent NameSpace
      Namespace *prnt = Con::lookupNamespace(parent);

      // mName = Class StringTable
      StringTableEntry mName = className;

      // walk = its parent while
      // it has a parent and its parent's name = Class StringTable
      while(walk->mParent && walk->mParent->mName == mName)
         walk = walk->mParent;

      // if walk has a parent and it is not the parent NameSpace
      // then parent = Class StringTableEntry
      if(walk->mParent && walk->mParent != prnt)
         parent=className;
      // else if walk has no parent or parent is the parent NameSpace
      // then link the parent and Class StringTableEntries
      // if the link was successful, parent = Class StringTableEntry
      else {
         if(Con::linkNamespaces(parent, className))
            parent = className;
      }
   }
}
/////////////////////////////////////
// END CODE INSERTED BY BRYAN EDDS
/////////////////////////////////////
In the file console/scriptObject.cc starting on line 17, replace this code -
StringTableEntry mClassName;
   StringTableEntry mSuperClassName;
with this code -
/////////////////////////////////////
   // CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
   /*StringTableEntry mClassName;
   StringTableEntry mSuperClassName;*/
   /////////////////////////////////////
   // END CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
In the same file on line 26, replace this line of code -
ScriptObject();
with this code -
/////////////////////////////////////
   // CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
   /*ScriptObject();*/
   /////////////////////////////////////
   // END CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
In the same file starting on line 45, replace this code -
addGroup("Classes", "Script objects have the ability to inherit and have class information.");
   addField("class", TypeString, Offset(mClassName, ScriptObject), "Class of object.");
   addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject), "Superclass of object.");
   endGroup("Classes");
with this code -
/////////////////////////////////////
   // CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
   /*addGroup("Classes", "Script objects have the ability to inherit and have class information.");
   addField("class", TypeString, Offset(mClassName, ScriptObject), "Class of object.");
   addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject), "Superclass of object.");
   endGroup("Classes");*/
   Parent::initPersistFields();
   /////////////////////////////////////
   // END CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
In the same file starting on line 58, replace this code -
ScriptObject::ScriptObject()
{
   mClassName = "";
   mSuperClassName = "";
}
with this code -
/////////////////////////////////////
// CODE MODIFIED BY BRYAN EDDS
/////////////////////////////////////
/*ScriptObject::ScriptObject()
{
   mClassName = "";
   mSuperClassName = "";
}*/
/////////////////////////////////////
// END CODE MODIFIED BY BRYAN EDDS
/////////////////////////////////////
In the same file starting on line 75, replace this code -
// it's possible that all the namespace links can fail, if
   // multiple objects are named the same thing with different script
   // hierarchies.
   // linkNamespaces will now return false and echo an error message
   // rather than asserting.
     
   // superClassName -> ScriptObject
   StringTableEntry parent = StringTable->insert("ScriptObject");
   if(mSuperClassName[0])
   {
      if(Con::linkNamespaces(parent, mSuperClassName))
         parent = mSuperClassName;
   }
      
   // className -> superClassName
   if (mClassName[0])
   {
      if(Con::linkNamespaces(parent, mClassName))
         parent = mClassName;
   }

   // objectName -> className
   StringTableEntry objectName = getName();
   if (objectName && objectName[0])
   {
      if(Con::linkNamespaces(parent, objectName))
         parent = objectName;
   }

   // Store our namespace
   mNameSpace = Con::lookupNamespace(parent);
with this code -
/////////////////////////////////////
   // CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
   /*// it's possible that all the namespace links can fail, if
   // multiple objects are named the same thing with different script
   // hierarchies.
   // linkNamespaces will now return false and echo an error message
   // rather than asserting.
     
   // superClassName -> ScriptObject
   StringTableEntry parent = StringTable->insert("ScriptObject");
   if(mSuperClassName[0])
   {
      if(Con::linkNamespaces(parent, mSuperClassName))
         parent = mSuperClassName;
   }
      
   // className -> superClassName
   if (mClassName[0])
   {
      if(Con::linkNamespaces(parent, mClassName))
         parent = mClassName;
   }

   // objectName -> className
   StringTableEntry objectName = getName();
   if (objectName && objectName[0])
   {
      if(Con::linkNamespaces(parent, objectName))
         parent = objectName;
   }

   // Store our namespace
   mNameSpace = Con::lookupNamespace(parent);*/
   /////////////////////////////////////
   // END CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
In the file console/simBase.h after this line of code on line 449 -
SimObject*       nextIdObject;
insert this code on a new line -
/////////////////////////////////////
   // CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
   StringTableEntry mClassName;
   StringTableEntry mSuperClassName;
   /////////////////////////////////////
   // END CODE INSERTED BY BRYAN EDDS
   /////////////////////////////////////
In the file gui/guiControl.cc starting on line 56, replace this code -
const char *name = getName();
   if(name && name[0] && getClassRep())
   {    
      Namespace *parent = getClassRep()->getNameSpace();
      if(Con::linkNamespaces(parent->mName, name))
         mNameSpace = Con::lookupNamespace(name);
   }
with this code -
/////////////////////////////////////
   // CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////
   /*const char *name = getName();
   if(name && name[0] && getClassRep())
   {    
      Namespace *parent = getClassRep()->getNameSpace();
      if(Con::linkNamespaces(parent->mName, name))
         mNameSpace = Con::lookupNamespace(name);
   }*/
   /////////////////////////////////////
   // END CODE MODIFIED BY BRYAN EDDS
   /////////////////////////////////////

Now compile.

It is critical that you read and study the file that is attached to this resource call tsoo.cs. This is a script file that can be executed from the console which shows and explains many of the features of OO in T2D script in an example. Once you absorb the knowledge in that document, you'll be on your way to making object-oriented T2D classes and datablocks in no time.

Final Note: If anyone has any problems or I have gotten any line numbers wrong, please don't hesitate to tell me by e-mailing me at the e-mail address specified in my profile.

Thanks for trying out my OO-in-T2D-script extension resource.
Page«First 1 2 Next»
#21
03/19/2005 (2:27 pm)
w007~!~!

Good to hear :)
#22
03/25/2005 (12:15 pm)
hello, I saw the included files and templates. They are very useful. Can you show an example of an object with associated datablocks, or subclassing datablocks.
#23
03/25/2005 (2:57 pm)
Here is a clip from one of my projects that shows a base class with a datablock (I edited to just what deals with the datablock). This is only a case of associated datablock, no subclassing.

// class baseVehicle
link(baseVehicle, "", ScriptObject);

// construction
function baseVehicle::create(%objectName, %size, %image, %classtype) {
  %this = new ScriptObject(%objectName) {
    superClassName = "";
    className = baseVehicle;
    };
  baseVehicle::protInit(%this, %size, %image, %classtype);
  return %this;
  }

// protected initializer
function baseVehicle::protInit(%this, %image) {
  // private members
  %this._name = %image;
  %this._imagemap = %image @ "ImageMap";
  %this._imagepath = "~/client/images/car";
  %this._imagemapdatablock = new fxImageMapDatablock2D(%this._imagemap) {
    mode = full;
    textureName = %this._imagepath @ %this._name;
    };

  }

// destructor
function baseVehicle::delete(%this) {
  %this._imagemapdatablock.safeDelete();
  /// call the parent class's delete method as well
  Parent::delete(%this);
  }

Now, I'm new to Torque in general so I may not be doing things "just right". I'm not messing with defining my own datablocks: instead I have a set of classes that are instantiated one time and then referenced from other classes. I suppose this is really what datablocks should be used for, but I'm still stuck in C++ thinking.
#24
03/26/2005 (1:10 am)
I'm having a problem: I added a class and it isn't inheriting from its base class. I've done lots of testing with this and I'm really confused now.

Setup: I have a client.cs file which exec's several other files. These include a WeaponClass.cs file and a VehicleClass.cs file. The original classes (which work wonderfully!) are based on ScriptObject. Well and good, but I need to have some objects inherit from fxStaticSprite2D and here is where the problem starts.

I added the classes (which have to do with weapons) to the bottom of the WeaponClass.cs file. No good. I can manually type in definitions using the console which are identical in all but class name and they work. Huh?

I can put the definition in a separate file and it does not work unless I give the class a different name. Huh?

I put a bare-bones class definition in the WeaponClass.cs file (at the end) and that didn't work, but I copied and pasted it from a file which I manually exec'd and it worked.

So cutting and pasting the class definition into another file does not work, but if I change the class name in that other file it does work.

I put the definition in the middle of the file (before a spot where a previous class gets instantiated) and it worked. So I moved the class instantiation to the end of the file and it worked -- once. So I moved the fxStaticSprite2D class to the beginning of the file and it is working -- for now.

So it appears that 1) the class name makes a difference as to whether or not it inherits from the base object and 2) it makes a difference where in a file the class definition is put (preceding classes it references seems to be okay, I still haven't figured out what is making the class not inherit).

Am I going crazy?
#25
03/26/2005 (12:44 pm)
Tim, showing me the non-working code may be necessary to resolve your problem...

Without the code to look at, I'll just guess at the problem.

Classes must initially be linked in the order they are inherited in. For example, you'd have to link a class before you link any class derived from it. For example, since the Base script object derives from the ScriptObject engine object, your class that derives from FXStaticSprite2D engine object canNOT derive from the Base script object.

Apologies for the weird wording :) Just remember that ScriptObject is an engine object, NOT a script object, even if the name is similar :/

Also, all inheritance chains must be consistant in what engine class they inherit from. If a class inherits from the ScriptObject engine object, so also must all its sub classes.

I have made lots of classes that derive from Melv's 2D fx classes, so believe me, that's not the problem. It sounds like something is inconsistantly chained or simply out of the proper linking order.
#26
03/26/2005 (1:08 pm)
Here's an example of using a datablock with a class derived from FXStaticSprite2D... The %imageMap parameter is the datablock used to set the sprite's image.

// class
   link( StaticSpriteSubClass,
         "",
         FXStaticSprite2D);
   
   // construction
   
   // object StaticSpriteSubClass
   function StaticSpriteSubClass::create(%objectName, %sceneGraph, %imageMap)
   /// instance returned
   {
      %this = new FXStaticSprite2D(%objectName)
      {
         superClassName = "";
         className = StaticSpriteSubClass;
         sceneGraph = %sceneGraph;
      };
      
      StaticSpriteSubClass::protInit(%this, %imageMap);
      
      return %this;
   }
   
   function StaticSpriteSubClass::protInit(%this, %imageMap)
   {
      // public members
      // private members
      // initialization
      
      %this.setImageMap(%imageMap);
   }

   function StaticSpriteSubClass::delete(%this)
   {   Parent::delete(%this);}
   
   // public functions
   
// end class StaticSpriteSubClass

As for subclassing datablocks, I have not yet done this in T2D (since I haven't foundn a need) so I don't have a handy template. But I can whip up an example of how you might want to organize one -

// datablock class
   link( CharacterSpriteData,
         "",
      	FXImageMapDatablock2D);
   
	function CharacterSpriteData::protInit(%this)
   {
      /// Parent::protInit(%this);  /// this isn't used here since there's no
                                    /// super class
      // public members
      
      // number
      %this.weight = 160;
      
      // private members
      
      // number
      %this._height = 6.0;
   }
   
   // public functions
   
	function CharacterSpriteData::onDeath(%this)
   {  %this._whine();}
   
   // private functions
   
	function CharacterSpriteData::_whine(%this)
   {  echo("AH POO I DIED!!!" SPC %this.weight SPC %this._height);}
   
// end datablock class CharacterSpriteData
   
   
// datablock AllySpriteData
   
   // construction
   
	if(!$AllySpriteData::_init){
   
	datablock FXImageMapDatablock2D(AllySpriteData)
   {
   	superClassName = "";
   	className = CharacterSpriteData;
      
      // initialization
      
      /// here's where you would put stuff that initializes the datablocks
      /// fields.
   	mode = full;
   	textureName = "data/images/allySprite";
   };
   
	CharacterSpriteData::protInit(AllySpriteData);
   
   }  $AllySpriteData::_init = true;
   
// end datablock AllySpriteData
   
   
// datablock EnemySpriteData

   // construction
   
	if(!$EnemySpriteData::_init){
   
	datablock FXImageMapDatablock2D(EnemySpriteData)
   {
   	superClassName = "";
   	className = CharacterSpriteData;
      
      // initialization
      
      /// here's where you would put stuff that initializes the datablocks
      /// fields. EG - 
   	mode = full;
   	textureName = "data/images/enemySprite";
   };
   
	CharacterSpriteData::protInit(EnemySpriteData);
   
   }  $EnemySpriteData::_init = true;
   
// end datablock EnemySpriteData
#27
03/27/2005 (9:59 am)
As you may have noticed it was rather late/early when I posted last and I was between frustration and fatigue. I have had time to look at things again and I don't understand the problem any better (the trivial code block not working based on position in other working code), but I am getting things more organized (in development the file I was working in had grown a little large making it clumsy to work in so I've split it up. While that hasn't helped identify what was breaking the inheritance it has side-stepped it.

If I get the time I'll create a minimal case for breaking inheritance and post that.

Tim Doty
#28
03/27/2005 (9:14 pm)
ATTENTION: There will be a big update for this resource, mostly for the included templates. Other additions will be the inclusion of a cast function to let you know whether or not an object is the same class or a derivitive of an arbitray class, as well as a fix for ScriptGroups.

Isn't code maintenence FUN?

Stay detuned...
#29
03/28/2005 (12:55 am)
Okay, looks like the update isn't going to be anywhere near as big as I initially thought - and that is a good thing for all of us, believe me!

I'm just going to make some cool additions when I have the time, the most useful of them is the cast function which works a lot like C++'s dynamic_cast, but has no significant speed overhead at all and is soooo easy to use.

One of the upcoming changes will be the change from overriding the delete() function to instead overriding onRemove(). The latter is in all ways superior, so be prepared to do a little wrenching with your code. Don't worry, it won't take long at all.

Finally, I implemented reference counting memory management in the Singleton Class Template. This should help a lot.
#30
04/15/2005 (5:58 am)
Arg! I REALLY need to update this thing! I really wish I had time! Lots of fixes and a couple new features as soon as possible, I promise :)
#31
05/05/2005 (4:14 pm)
The new OOP In Torque script v 2.0 is up here at -

www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7762

Make sure to use that one instead of this from now on! And make sure not to apply it on top of this resource :)
#32
05/10/2005 (1:10 am)
why i can't download?
#33
07/11/2005 (7:50 am)
THIS RESOURCE IS DEPRECATED! Find the new 3.0 version HERE.
Page«First 1 2 Next»