Game Development Community

dev|Pro Game Development Curriculum

Torque DB - Journal System Example

by Matthew Langley · 04/14/2005 (11:29 pm) · 65 comments

Torque DB: Journal System Example

Here is the zip file

This is a completely TorqueScript Database, included is a bunch of pics and description of it, the best advice I could give is to go into the database scripts... I have included some good commenting and good descriptions before most functions :)

Note: this also includes a full Journal System example... for this example I modified guiMessageVectorCtrl.cc and guiMessageVectorCtrl.h

here I will detail my modifications and how to quickly add those to yours... (I didn't add much and I didn't take away anything)...

note: the book graphic included was done using an academic version of Photoshop, it is for non-commercial use only

Steps to implement this resource:

First - either back up your entire Torque folder or at least back up your guiMessageVectorCtrl.cc and guiMessageVectorCtrl.h (Torque\SDK\engine\gui by default)

1. Modify the source code

first go to "guiMessageVectorCtrl.h"
search for this line of code

//   void onMouseMove(const GuiEvent &event);

and uncomment it... like this

void onMouseMove(const GuiEvent &event);

thats all for the .h file ;) We're just adding that function back in that was commented out

second go to "guiMessageVectorCtrl.cc"


now scroll down all the way to the bottom of the file, you should see this set of code

mMouseDown      = false;
   mMouseSpecialLine = -1;
   mMouseSpecialRef  = -1;

add this before it

//---------------------------------------------
   //Matthew "King BoB" Langley - Resource: Journal System...
   Con::executef(this, 2, "clickLineCallback", Con::getIntArg( mMouseSpecialLine ));
   //---------------------------------------------

this is simply providing a callback that passes the line you click on

now add this function to the end of the file

//---------------------------------------------
//Matthew "King BoB" Langley - Resource: Journal System...


void GuiMessageVectorCtrl::onMouseMove(const GuiEvent& event)
{
	//Parent::onMouseMove(event);

	S32 currLine;
   	S32 currRef;
   	findSpecialFromCoord(globalToLocalCoord(event.mousePoint), &currLine, &currRef);

   	Con::executef(this, 2, "moveLineCallback", Con::getIntArg( currLine ));

}

//---------------------------------------------


and that (as you've probably guessed) is simply passing the line you move over... note: this is not optimized, this probably could be cut down a lot, I'm simply using an existing function, I could make my own function (based off of the "findSpecialFromCoord" that only returns the line; however, I presently don't have the time to do so =/ This seems to not affect things much though, have run into little FPS loss (if any)... I look forward to hearing results...


2. Script end of it

ok, well the script end of it is very easy, simply take the included zip file... unzip it and it should contain three folders a .gui file and a .png image, grab the two folders database and journal, drop these in Torque\SDK\example\starter.fps\client (or if you decide to put them elsewhere)... and be sure to include these exec statements (easy to put them in Torque\SDK\example\starter.fps\client\init.cs)

exec("./scripts/journal/journal.cs");
exec("./scripts/database/TorqueDB.cs");

now grab the .gui file and .png
journal.gui
journalbook.png

then put them in Torque\SDK\example\starter.fps\client\ui\

and be sure to add an exec for

exec("./ui/journal.gui");

best place is to put it in init.cs as well with the other .gui exec statements


make sure you change

$torqueDBPath = "starter.fps/data/journalData/";

if needed (its at the top of file.cs)

that will point to where you want to put your journalData folder... this holds the config and output text file... note I have two Dump commands in file.cs... one for plain text and the regular one will encrypt... later I will include some instructions on generating new encryptions... (its a very basic encryption)

ok, now if you recompile for the C++ changes, then simply fire up Torque... bring up the console and type
journalDebug();

you should see the book come up with your journal entries, try selecting and clicking... note the first menu takes you a level "above" "entry"... after that you cannot get back to entry (unless you add your own way to)... I'm hoping to add more functionality to an overal view, though right now wanted to at least release this.




now here is some more information about the rest of this, hopefully I can create some step by step instruction, but again just wanted to get this out there at least :)



here is the layout
www.razedskyz.com/games/torque/plan/journalDiagram1(small).JPGwww.razedskyz.com/games/torque/plan/journalDiagram2(small).JPG

here is the journal system in action
www.razedskyz.com/games/torque/plan/journalpic1.JPGwww.razedskyz.com/games/torque/plan/journalpic2.JPG
first here is the config file... a basic text file.

www.razedskyz.com/games/torque/plan/journalconfig.JPG
as you can see it starts off with

this begins what you can think of a new journal class or type... you don't even really have to think of it as journal specific... as you can see down a few lines I have ... which works perfectly well.

after the "<>" tag (this starts the type)...
you see text starting with a "-"... this denotes a property... so we now assign name, text, and item to the "entry" type.

and name, value, power to the "item" type.

the next file you can see how we use it.

www.razedskyz.com/games/torque/plan/journaltext.JPG
here we see an .. (note: for simplicity I have it started the same way with an html style <> tag)
then the sub property -name is being assigned "BoB"... then values are assigned to text and item... in this case you could think of it as if the player talked to "BoB" and was assigned the quest with the reward (which you could easily change item to reward in the config file) by accomplishing "Go here and do that."
Next entry I clip off some entries for testing... and at the end i have an Item... beginning with the same tag and ending with the tag...(this all keeps it extremeley simple to write and edit, though when I'm finished you should never have to manually edit journal.txt... even though as you can see its simple enough

ok, so I've shown you a couple of text file, how exciting, lol... ok let me show you some debug info output from loading these file through my parsers...

www.razedskyz.com/games/torque/plan/console.JPG
all of these variables are scriptobjects used as arrays, minus $journalMainTags... which was left as a standard single dimension array... I used scripts objects due to the need of having multi-dimension arrays (even got up to a 3 dimensional array =/)... plus as I saw in the resource I beleive these can then be passed (though still have to test)... which adds a large ammount of usability if one wanted to change them to local variables instead.

ok... first off
$journalMainTags...
this (as you can probably see) holds each different type loaded from the config file... right now simply two... entry and item...
more importantly these arrays run together, so you grab the array value of entry or item and then get full access to all the values under the main array.
(as you can see I like seperating things for organization and usability)...

$journalSubTags contains the properties of those main taqgs listed in $journalMainTags...

as you can see [0][0] is name
and [1][0] is name...
reinforcing my method of keeping them aligned with eachother.

$mainValueArray holds the instance (or what you could think of as an instance for you OO programmers out there) of each type loaded from journal.txt
[0] - entry
[0] - entry
[0] - entry
[1] - item
following the journal.txt format of three entries and an item... this is good for quick navigation and later when I implement searching abilities, optimizing the access of information without looping through the entire main array.

now we get to the big boy, the 3-dimensional array... $subValueArray... the first array value denotes the same as the other arrays... the type... 0 is for the first type loaded, entry... 1 is for the second type loaded, item. Again as you have seen this is all dynamically loaded through the config file. It then loads an instance of each of those... first an instance of the "entry"..
[0][0] denoting the first instance (the second 0)... of the first class/type (the first 0)... then it defines [0][0][0] = BoB
this takes the first intance of the first class and adds its first property (again using OO programming principals) and sets it to BoB... this would be the name...
note it loads these in order of the config file... not the journal file...
if you put
name
item
text

it will load
[0] - name
[1] - item
[2] - text

even if the journal is written
item
name
text

NOTE this was added , lol this is a bit dated writeup (at least part of this is a bit dated, though a good description) - ignore the "which I plan on adding"
... now with the right array writing functions (which I plan on adding, as well as an easy template to write your own for customization) the order should always be correct, but figured I'd specify that it loads the config file, then loads the journal and comparesthe first config entry until it gets the journal entry matching, it doesnt simply load it, hoping it all is in proper order. This added for some interesting loops and code, but was well worth it for the control it grants you.

after the first entry it then addes
[0][1][0] - BoB2

first [0] means first class/type (still entry) the [1] meaning the second 'instance' of this class/type... and [0] being the first property... which can be referenced by the class/type number
[0] and the property number [0]...
to
$journalSubTags.contents[0].contents[0] - name
to find the property name.

then it adds a third instance (as many instances as you want can be loaded)... then it goes to the new class/type... [1]... which can be referenced using the same number with
$journalMainTags[1] - item

so it loads item data in
[1][0][0] - rod of death
[1][0][1] - 500
[1][0][2] - death


according to the properties in
$journalSubTags.contents[1].contents[0] - name
$journalSubTags.contents[1].contents[1] - value
$journalSubTags.contents[1].contents[2] - power


[image][/image]



------------------- ARRAYS --------------------------
$journalMainTags
------------------------------------------------------------------
$journalMainTags[0] - entry
$journalMainTags[1] - item
------------------------------------------------------------------
$journalSubTags
------------------------------------------------------------------
$journalSubTags.contents[0].contents[0] - name
$journalSubTags.contents[0].contents[1] - text
$journalSubTags.contents[0].contents[2] - item
$journalSubTags.contents[0].contents[3] - image
$journalSubTags.contents[1].contents[0] - name
$journalSubTags.contents[1].contents[1] - value
$journalSubTags.contents[1].contents[2] - power
------------------------------------------------------------------
$mainValueArray
------------------------------------------------------------------
$mainValueArray.contents[0] - 3
$mainValueArray.contents[1] - 1
------------------------------------------------------------------
$subValueArray
------------------------------------------------------------------
$subValueArray.contents[0].contents[0].contents[0] - Quest given by: BoB
$subValueArray.contents[0].contents[0].contents[1] - Quest text: Go here and do that
$subValueArray.contents[0].contents[0].contents[2] - Quest item: sword of fire
$subValueArray.contents[0].contents[0].contents[3] - Dark.Game/client/ui/GarageGames.jpg
$subValueArray.contents[0].contents[1].contents[0] - BoB2
$subValueArray.contents[0].contents[1].contents[1] - slay the beast and return its head
$subValueArray.contents[0].contents[1].contents[2] - rod of death
$subValueArray.contents[0].contents[1].contents[3] - Dark.Game/client/ui/background.jpg
$subValueArray.contents[0].contents[2].contents[0] - BoB3
$subValueArray.contents[0].contents[2].contents[1] - Go here and do that3
$subValueArray.contents[0].contents[2].contents[2] - shield of valor
$subValueArray.contents[1].contents[0].contents[0] - rod of death
$subValueArray.contents[1].contents[0].contents[1] - 500
$subValueArray.contents[1].contents[0].contents[2] - death
------------------------------------------------------------------

---------------------- VARIABLES -----------------------
$journalArrayCount contents: 22
$mainTagCount contents: 2
$subTagCount[0] contents: 4
$subTagCount[1] contents: 3
$sub2TagCount contents: 0
"



Functions for the journal system

--------------------- File loading and unloading Functions---------------------------

.dumpFile()

This function takes all of the existing information in the arrays and dumps it to a config (setup file)

as well as a journal file that holds all of the entries, data, etc.


.loadFile()

This function is the heap of loops that parses the config file to gain access to the values it will parse

the journal file with. It loads all the information into the arrays as well as the counters that keep track of

everything.

--------------------- Data Manipulation Functions (what you will most likely use the most

of)---------------------------

------Delete functions

.deleteClass()

This function will delete a class if you pass it the class you want deleted... for example
say I wanted to delete the "entry" class...
TorqueDB.deleteProperty("entry");
This will delete that class, as well as all of the instances of that class, updating the arrays
and counters properly


.deleteProperty()

This function will delete a property if you pass it the class and the property you want deleted... for

example
say I wanted to delete the "name" property of the "entry" class...
TorqueDB.deleteProperty("entry", "name");
This will delete that property for the class, and in all of the instances of that class, updating the arrays
and counters properly


.deleteInstance()

This function will delete an instance if you pass it the class, identifying property and value(instance)

of that
property... for example... say I want to delete the "entry"(class) with the "name" (property) of "BoB2"

(instance)
TorqueDB.deleteInstance("entry", "name", "BoB2");
this successfully deleted that instance and correctly updates the arrays and counters

------Write functions

.writeClass()

This will add a new class to the data that will eventually be dumped to the config file
Important to remember you must write the class with this function then populate its
properties with the following functions... You must include one property and value
so it will add one instance on creation, further instances can be added via the
TorqueDB.addInstance function as used inside this function... for example
TorqueDB.writeClass("spell", "name", "ball of fire");
this will add the spell class, which in turn adds "" and "" to the config


.writeProperty()

This will add a new property to the specified class that will eventually be dumped to
the config file... for example
TorqueDB.writeProperty("spell", "damage");
this will add the spell class, which in turn adds "" and "" to the config

------Write functions - to the data (journal in my example)

.writePropertyValue()

This function will write a value to a property. For example... say I wanted to change the name of my

"rod of death"
under the "item" class. I would do this
TorqueDB.writePropertyvalue("item", "name", "rod of death", "name", "new rod of death");
As you can see I pass all the information needed to identify the rod of death and to change its property


.addInstance()

This function will add an instance... Key things to remember, the class must exist and the
property must exist already... using the above functions this is accomplished...
you must specify at least one starting value to reference the instance, can be any that you
created or that are already there
for example... say you wanted to add an instance of the "item" class, thats called
"Sword of Fire"
TorqueDB.addInstance("item", "name", "Sword of Fire");
now the instance of item with the name property of "Sword of Fire" has been added
from here you can use TorqueDB.writePropertyValue to add values to the existing properties
of the "item" class or the TorqueDB.writeProperty to add more properties to the "item"
class
NOTE: the following is a bit more advanced, knowledge of Script Object arrays is needed
you do not NEED to use them, the above specifies normal use, this is additional use
Now I also added support for passing it ScriptObject arrays... though you could just loop
this function and create all sorts of instances I figured I'd make this resource as robust
as possible... There are two types of ScriptObject arrays that it will take... "single" and
"multi"... to pass a ScriptObject array you would call do a call like
TorqueDB.addInstance("entry", "single", $testArray);
note the first value is the same, the class, the second specifies "single" or "multi" and
the last is the ScriptObject array, the code will automatically detect if its a ScriptObject
array and process it accordingly... for example

Example of "single"

$testArray = new ScriptObject();
$testArray.contents[0] = new ScriptObject();
$testArray.contents[1] = new ScriptObject();

$testArray.contents[0].contents[0] = "name";
$testArray.contents[0].contents[1] = "person1";
$testArray.contents[0].contents[2] = "text";
$testArray.contents[0].contents[3] = "BLAH BLAH BLAH";
$testArray.contents[0].contents[4] = "item";
$testArray.contents[0].contents[5] = "Sword of Fire";
$testArray.contents[1].contents[0] = "name";
$testArray.contents[1].contents[1] = "person2";
$testArray.contents[1].contents[2] = "text";
$testArray.contents[1].contents[3] = "blah blah blah";
$testArray.contents[1].contents[4] = "item";
$testArray.contents[1].contents[5] = "Sword of Death";

Example of "multi"

$testArray = new ScriptObject();
$testArray.contents[0] = new ScriptObject();
$testArray.contents[1] = new ScriptObject();

$testArray.contents[0].contents[0] = "name";
$testArray.contents[0].contents[1] = "person1";
$testArray.contents[0].contents[2] = "person2";
$testArray.contents[0].contents[3] = "person3";
$testArray.contents[0].contents[4] = "person4";
$testArray.contents[1].contents[0] = "item";
$testArray.contents[1].contents[1] = "sword1";
$testArray.contents[1].contents[2] = "sword2";
$testArray.contents[1].contents[3] = "sword3";
$testArray.contents[1].contents[4] = "sword4";


Single is set up like this
Array[0][0] - property
Array[0][1] - value
Array[0][2] - property
Array[0][3] - value
Array[1][0] - property
Array[1][1] - value
Array[1][2] - property
Array[1][3] - value
etc etc... this will create two new instances, the first array number denotes the seperation
of instances....

Multi is set up like this
Array[0][0] - property
Array[0][1] - value
Array[0][2] - value
Array[0][3] - value
Array[1][0] - property
Array[1][1] - value
Array[1][2] - value
Array[1][3] - value
Now this is quite a bit different, six different instances will be created, basically multi
is a good way to spam a bunch of simple instances, while single adds instances with multiple
properties


------Search functions

.searchByClass()

search by passing the class and returning the entire class contents
for example... I have "entry" and "item" classes... If I want to
return all entry types I would use
$entries = TorqueDB.searchByClass("entry");
then I could use
echo($entries.contents[0].contents[0]);
and the first property of the first "entry" should display in console
this will be usefull if you use this system as inventory, dialog, etc,
like a database, so you can return only the type you want


.searchByInstanceWithClass()

search by passing the class, property, and instance and returning the instance
for example... I want to get the item by the property "name" of "rod of death" and I know
its of class "item"... I would use
$testInstance = TorqueDB.searchByInstanceWithClass("item", "name", "rod of death");
then I could use
echo($testInstance.contents[0]);
and the first property of "rod of death" would be displayed in console, which would be
"rod of death"
the name we searched by...
echo($testInstance.contents[1]);
would reveal "500"


.searchByInstance()

search by passing the property, and instance and returning the instance, without passing class
note: its recommended to use the previous function, specifying the class will increase efficiency
especially if you have a lot of classes
for example... I want to get the item by the property "name" of "rod of death" without knowing
the class... note: the property instance must be unique! or it will simply return the first
match it finds
$testInstance = TorqueDB.searchByInstance("name", "rod of death");
then I could use
echo($testInstance.contents[0]);
and the first property of "rod of death" would be displayed in console, which would be
"rod of death"
the name we searched by...
echo($testInstance.contents[1]);
would reveal "500"


.searchByPropertyWithClass()

search by passing the class, property, instance, and return property and returning the
property value you specified in %returnProperty
for example... I want to get the item by the property "name" of "rod of death" and I know
its of class "item" and I want to get its "value" I would use
$testProperty = TorqueDB.searchByPropertyWithClass("item", "name", "rod of death", "value");
then I could use
echo($testProperty);
and it would reveal the "value" of "500"


.searchByProperty()

search by passing the property, instance, and return property and returning the
property value you specified in %returnProperty
for example... I want to get the item by the property "name" of "rod of death" and I know
its of class "item" and I want to get its "value" I would use
$testProperty = TorqueDB.searchByProperty("name", "rod of death", "value");
then I could use
echo($testProperty);
and it would reveal the "value" of "500"


-----------------------------------------------------------
Using these search functions... heres an example
-----------------------------------------------------------

Say you have the quest for the "rod of death"... the user wants to know its value...so you do
$testProperty = TorqueDB.searchByPropertyWithClass("item", "name", "rod of death", "value");
The value returns "500"... they like this... so they then want to see what the quest entails..
to get the text associated with the journal entry for this, you would do
$testProperty = TorqueDB.searchByPropertyWithClass("entry", "item", "rod of death", "text");
and it returns "slay the beast and return its head"
then the user asks, who do I return it to... you would get this info by
$testProperty = TorqueDB.searchByPropertyWithClass("entry", "item", "rod of death", "name");
which would return "BoB2"
this is just an example of the many ways to manipulate these functions... note I did include
search functions 'without' the class specification, though you can tell this will decrease
search efficiency

Another great use for this will be setting up a user searchable journal, item list
really its more like a database that can be used for anything... Quite nifty...
In all truth with the functions given it isnt too hard, though it won't be very smart...
(unless you add a bit of a smartness to it, like estimating what the user means, etc)
I may include GUIs and such for this by the time I release this resource, lol if you see it
included then I decided to go ahead and do that, if not then you shouldn't have much trouble
doing it yourself
I may even add functionality to search through the text of each entry, though adding searching
by item, name, or any 'property' or 'class' should be a snap with these functions



------Return functions

.returnClass()

This is for internal uses, it will return the class number used in the data arrays
by passing it the class name ... for example
$classNum = TorqueDB.returnClass("entry");
will return "0"... which then could be identified by using it in the arrays, like
echo($journalMainTags[TorqueDB.returnClass("entry")]);
should display "entry"


.returnProperty()

This is for internal uses, it will return the property number used in the data arrays
by passing a class, and property... for example
$propertyNum = TorqueDB.returnProperty("item","name");
this will return the array number


.returnInstance()

This is for internal uses, it will return the instance number used in the data arrays
by passing a class, property, and instance... for example
$instanceNum = TorqueDB.returnInstance("item","name","rod of death");
this will return the array number


.returnClassName()

This function is used if you have the internal array class Number and want to get the
class name... After some thought I decided to use the above return commands passing the
text name... like "entry" rather than having you convert it to the 0 it is in the internal
array... so I added these two function to help if you already have the number and need the
name, whether for the above functions or for another use... for example
$className = TorqueDB.returnClassName(0);
echo($className);
this should display "entry" in the console


.returnPropertyName()

Heres the second number to name function... pass it the class "text" and the property
number an it will return the property name, look above for reasons you might use this
... for example
$propertyName = TorqueDB.returnPropertyName("entry", 0);
echo($propertyName);
this should print "name" in the console







--------------------- Debug Functions---------------------------

.debug()
and
.debugInfo()

I included a couple debug functions, one that loads the file and displays the journal ... the other is

called from the debug command but can be run seperately also, debugInfo is very usefull it will display all the

contents of the arrays and counters, correctly labeled.


--------------------- String Helper Functions---------------------------


.getTagName()

Here is an internal command that is very usefull... it will get the name out of a tag... for example...

you pass it "" and it gets "entry" out of it... just a helper string function

.returnStringAfter()

Here is another internal helper string function... pass it .returnStringAfter("name=bob", "=") and it

will return "bob"...

.returnStringBefore()

Same as .returnStringAfter except it returns the string before the character

--------------------- Display Functions---------------------------

.displaySub(%lastMenu, %obj)

This function will display a sub object of the journal overlayed over the journal (as well as an image in

the right pane if you have one under that sub... In a journal example this displays the journal entry, the text,

the name, items associated with it, etc... Basically this displays an "instance"

.displayType(%lastMenu)

This will display all of the types or "classes"... so if you have an "entry" class and an "item" class

then it will display entry and item... so then you can browse down further

.displayMain(%lastMenu, %obj)

This will display the contents of a type or "class"... so if you want to display all the journal entries

this would be the function that would... note: the first property of an entry is what is display when listing

all the entries, so you can choose to make the first entry a name, a title, or whatever you choose.

About the author

Was a GG Associate and then joined GG in 2005. Lead tool dev for T2D and T3D. In 2011 joined mobile company ngmoco/DeNA and spent about 4 years working game and server tech. 2014 joined startup Merigo Games developing server technology.

Page«First 1 2 3 4 Next»
#61
08/08/2007 (10:06 pm)
I would love to get this to work with TGB 1.5...
I got TGB recompiled with the small code changes.
I got the scripts added into my project and executed.
When the journal gui gets pushed the guiscrollcontrol::onwake is failing an assert in the c++ code. It says "Failed to create bitmap" and it has to do with this line 109 in guiscrollctrl.cc

bool result = mProfile->constructBitmapArray() >= BmpStates * BmpCount;

Any ideas how to fix this error, or what the underlying problem is?

Also, when i try to load the journal gui in the gui editor I get the message "This gui was created with a previous version. It was loaded but to display it you must select it in the content drop down."

Any ideas what is causing this? I perused the journal.gui file for any "archaic" code and tried commenting out various things but couldn't assertain how it "knew" this was an old gui.
#62
08/14/2007 (9:56 am)
hmm, thanks for posting the problems you ran into James... once I get a moment I will give this a shot and examine this further. I would like to get this working again, it's been quite a while since I created this resource though so I don't know how efficient is.
#63
08/15/2007 (4:50 pm)
I have gone a different route to deal with managing my game data. (Using Simgroups with .save and exec)

But I think this resource probably still works fine, as far as the scripts go, its probably just the journal gui that is causing errors.
#64
12/05/2007 (7:30 am)
Pictures broken = shit.
#65
12/05/2007 (8:01 pm)
Completely agreed... I did this well over 2 years ago (before I was at GG lol), though if I get some time I'll try and re-create the pictures and host them elsewhere.
Page«First 1 2 3 4 Next»