logo logo

 Back to main page

The NWNX Community Forum

 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
 
Getting started with SQLite on an existing PW

 
Post new topic   Reply to topic    nwnx.org Forum Index -> Database related
View previous topic :: View next topic  
Author Message
chunkymonky



Joined: 20 Feb 2005
Posts: 31

PostPosted: Thu Nov 23, 2006 4:00    Post subject: Getting started with SQLite on an existing PW Reply with quote

I recently updated my mod to use NWNx, and I want to change over to the SQLite DB. Please, feel free to view me as a complete DB moron, as I am. I understand that the SQlite db was created on the very first startup of the mod, and I can see it in my parent directory. I want to start using that DB to store player info and the like. I have working scripts in place for player info saving taken from the PWHelper scripts of EPOlsen, and I use CNR on my mod. Also, DMFI and Shayans Subrace engine. I know I need to do some work in the scripts, but I am not sure how to direct them to write to this new DB.
Back to top
View user's profile Send private message
FunkySwerve



Joined: 02 Jun 2005
Posts: 377

PostPosted: Thu Nov 23, 2006 6:36    Post subject: Reply with quote

All the functions you need are in the aps_include script. You can just replace out Campaign variable calls with those Persistent variable calls, and you'll be using the new database, which is significantly faster and won't blow up on you down the road. If you want to be able to take advantage of SQL's more powerful features, however, you'd need to post more detailed explanations of what it is that you are trying to do.
Funky
Back to top
View user's profile Send private message
chunkymonky



Joined: 20 Feb 2005
Posts: 31

PostPosted: Thu Dec 07, 2006 23:56    Post subject: Reply with quote

Funky...thanks for the reply. I will start with the replacement of the variable calls and then get a handle on what I need to do from there.
Back to top
View user's profile Send private message
chunkymonky



Joined: 20 Feb 2005
Posts: 31

PostPosted: Sat Jan 13, 2007 14:42    Post subject: Reply with quote

OK, after searching around to the best of my ability, I cannot find the answer to a question I am having. I want to use multiple tables in my sqlite database, currently I only have pwdata which was set up by the SIMTools test mod. I want to create new tables for persistent location storage (pwloc) and questing variables (pwquest), for example, but I am hitting a "database locked" error when I try to create the tables. Do I need to shut the mod down to create them using an external db utility?
Back to top
View user's profile Send private message
FunkySwerve



Joined: 02 Jun 2005
Posts: 377

PostPosted: Sat Jan 13, 2007 19:03    Post subject: Reply with quote

You should be able to add them in the fashion that the SIMTools mod does - look at the CREATE call in the script that adds the database in the SIMTools_Test mod. Here it is, reproduced:
Code:

#include "aps_include"

void main()
{
SQLExecDirect("DROP TABLE pwdata");
SendMessageToPC(GetLastUsedBy(), "Table 'pwdata' deleted.");

// For SQLite
SendMessageToPC(GetLastUsedBy(), "Creating Table 'pwdata' for SQLite...");
SQLExecDirect("CREATE TABLE pwdata (" +
"player varchar(64) NOT NULL default '~'," +
"tag varchar(64) NOT NULL default '~'," +
"name varchar(64) NOT NULL default '~'," +
"val text," +
"expire int(11) default NULL," +
"last timestamp NOT NULL default current_timestamp," +
"PRIMARY KEY (player,tag,name)" +
")");

// For MySQL
/*
SendMessageToPC(GetLastUsedBy(), "Creating Table 'pwdata' for MySQL...");
SQLExecDirect("CREATE TABLE pwdata (" +
"player varchar(64) NOT NULL default '~'," +
"tag varchar(64) NOT NULL default '~'," +
"name varchar(64) NOT NULL default '~'," +
"val text," +
"expire int(11) default NULL," +
"last timestamp NOT NULL default CURRENT_TIMESTAMP," +
"PRIMARY KEY (player,tag,name)" +
") ENGINE=MyISAM DEFAULT CHARSET=latin1;");
*/

// For Access
/*
SendMessageToPC(GetLastUsedBy(), "Creating Table 'pwdata' for Access...");
SQLExecDirect("CREATE TABLE pwdata (" +
"player text(64)," +
"tag text(64)," +
"name text(64)," +
"val memo," +
"expire text(4)," +
"last date)");
*/

SendMessageToPC(GetLastUsedBy(), "Table 'pwdata' created.");
}



The top, uncommented version is for SQLite.
Funky
Back to top
View user's profile Send private message
chunkymonky



Joined: 20 Feb 2005
Posts: 31

PostPosted: Sat Jan 13, 2007 23:47    Post subject: Reply with quote

Thats what I was looking for. I assume that this can be adjusted for any table name I want? (replace pwdata with, say, pw_loc)

Or is that a terrible assumption? I want to separate the tables a little so not all my data is getting crammed into one table in the db.

*waits to get smacked for lack of knowledge*

Oh, by the way...thanks for all the help Funky. I am learning, albeit slowly, but I am learning.
Back to top
View user's profile Send private message
FunkySwerve



Joined: 02 Jun 2005
Posts: 377

PostPosted: Sun Jan 14, 2007 9:45    Post subject: Reply with quote

You can rename it, and do pretty much whatever you want. Unless you are familar with database indexing and data types, however, I wouldn't tinker too much. Here's a same with the name changed only:

Code:

SQLExecDirect("CREATE TABLE pwloc (" +
"player varchar(64) NOT NULL default '~'," +
"tag varchar(64) NOT NULL default '~'," +
"name varchar(64) NOT NULL default '~'," +
"val text," +
"expire int(11) default NULL," +
"last timestamp NOT NULL default current_timestamp," +
"PRIMARY KEY (player,tag,name)" +
")");


You could use this table with the standard aps_include functions like GetPersistentString:

string GetPersistentString(object oObject, string sVarName, string sTable="pwdata");

simply by adding in the name of you database after the stardard object and value (if nothing is entered it defaults to pwdata).

Here's an example of a different table setup, one I use with our login tracking system, which uses custom SQL calls rather than the Get/SetPersistent aps_include functions:

Code:

void CreateTable(object oPC)
{
    SQLExecDirect("CREATE TABLE loggedin (" +
                    "server VARCHAR(64) default NULL," +
                    "pcname VARCHAR(64) default NULL," +
                    "charname VARCHAR(64) default NULL," +
                    "level VARCHAR(64) default NULL," +
                    "KEY idx (server,pcname)" +
                    ")" );

    SendMessageToPC(oPC, "Table 'loggedin' created.");
}


When a character logs in, we check to see if they are logged in elsewhere (to prevent exploits like duping with doubled logins of the same character on different servers). This should give you an idea how you can take more full advantage of the power of SQL than aps_include allows, using custom calls:
Code:

int MarkCharacterEntry(object oPlayer)
{
    int nBoot = FALSE;
    //object oPlayer = GetEnteringObject();
    string sServer;
    sServer = GetLocalString(OBJECT_SELF, "ServerNumber");//returns either 111 or 112
    string sPlayer;
    string sUnencoded = GetPCPlayerName(oPlayer);
    sPlayer = SQLEncodeSpecialChars(sUnencoded);
    string sName = SQLEncodeSpecialChars(GetName(oPlayer));
    int nLevel;
    nLevel = GetHitDice(oPlayer);
    int nLootable;
    nLootable = GetLootable(oPlayer);
    if (nLootable > 40) nLevel = nLootable;
    string sLevel = IntToString(nLevel);
    string sIP = GetPCIPAddress(oPlayer);
    string sCD = GetPCPublicCDKey(oPlayer);
    string sSQL = "SELECT pcname, charname, cdk, server  FROM loggedin WHERE pcname='" + sPlayer + "'";
    SQLExecDirect(sSQL);
    if (SQLFetch() == SQL_SUCCESS)
    {

        // row exists
        string sSQLPName = SQLGetData(1);
        string sSQLCName = SQLGetData(2);
        string sSQLCD = SQLGetData(3);
        string sSQLServer = SQLGetData(4);
        if ((sPlayer == sSQLPName) && (sName == sSQLCName) && (sCD != sSQLCD) && (sServer != sSQLServer)) //same p, c, diff cd, showing logged into diff server than this
        {
            nBoot = TRUE;//boot em, attempting double login
            WriteTimestampedLogEntry("Double Login Attempt Detected! Player: " + sUnencoded + ".");
        }
        else //they aren't attempting double login, server prolly crashed
        {
            sSQL = "UPDATE loggedin SET server='" + sServer +
                   "',charname='" + sName + "',level='" + sLevel + "',cdk='" + sCD + "',cip='" + sIP + "' WHERE pcname='"+ sPlayer + "'";
            SQLExecDirect(sSQL);
        }
    }
    else  //not foud on another server
    {
        // row doesn't exist
        sSQL = "INSERT INTO loggedin (server,pcname,charname,level,cdk,cip) VALUES" +
               "('" + sServer + "','" + sPlayer + "','" + sName + "','" + sLevel + "','" + sCD + "','" + sIP + "')";
        SQLExecDirect(sSQL);
    }
    return nBoot;
}


The logged in table is wiped for that server when it restarts, because it wouldn't clear in the event of a crash or reset with characters still logged in (they are removed from the table only on clientexit, which doesnt fire if the server crashes, for example):
Code:

    sSQL2 = "DELETE FROM loggedin WHERE server='" + sR3ServerIP + "'";
    SQLExecDirect(sSQL2);


Here's the clientexit:
Code:

void MarkCharacterExit(string sPlayer)
{
    string sPlayername;
    sPlayername = SQLEncodeSpecialChars(sPlayer);
    string sSQL = "DELETE FROM loggedin WHERE pcname='" + sPlayername + "'";
    SQLExecDirect(sSQL);
}


Aside from exploit prevention, that lets us do stuff like cross-server player lists:
Code:

void main()
{
    object oPC = GetPCSpeaker();
    string sServer1List, sServer2List, sServer3List, sServer4List, sServer5List, sS1Header, sS2Header, sS3Header, sS4Header, sS5Header;
    string sMessage, sPCName, sCharName, sLevel, sListServer;
    string sServer = GetLocalString(GetModule(), "ServerNumber");
    string SQL;
    int nCount;
    SQL = "SELECT pcname, charname, level, server FROM loggedin WHERE server<>'"+ sServer +"' ORDER BY level";
    sMessage = "<c2>You are on server </c><c>216.144.214."+ sServer +"</c><c2>. Here are the players playing on the other servers:</c>\n<c2>Key: L = Level, P = Player, C = Character</c>\n";
    SQLExecDirect(SQL);
    while(SQLFetch() != SQL_ERROR) //lists 112 and 113
    {
        sPCName = SQLDecodeSpecialChars(SQLGetData(1));
        sCharName = SQLDecodeSpecialChars(SQLGetData(2));
        sLevel = SQLGetData(3);
        sListServer = SQLGetData(4);
        if (sListServer == "111") sServer1List += "<c>L: </c><c >" + sLevel + "</c><c>, P: </c><c>" + sPCName + "</c><c>, C: </c><c>" + sCharName + "</c>\n";
        else if (sListServer == "112") sServer2List += "<c>L: </c><c >" + sLevel + "</c><c>, P: </c><c>" + sPCName + "</c><c>, C: </c><c>" + sCharName + "</c>\n";
        else if (sListServer == "113") sServer3List += "<c>L: </c><c >" + sLevel + "</c><c>, P: </c><c>" + sPCName + "</c><c>, C: </c><c>" + sCharName + "</c>\n";
        else if (sListServer == "114") sServer4List += "<c>L: </c><c >" + sLevel + "</c><c>, P: </c><c>" + sPCName + "</c><c>, C: </c><c>" + sCharName + "</c>\n";
        else sServer5List += "<c>L: </c><c >" + sLevel + "</c><c>, P: </c><c>" + sPCName + "</c><c>, C: </c><c>" + sCharName + "</c>\n";
        nCount++;
    }
    if (sServer1List != "") sS1Header = "<c2>=====Server 111=====</c>\n";
    if (sServer2List != "") sS2Header = "<c2>=====Server 112=====</c>\n";
    if (sServer3List != "") sS3Header = "<c2>=====Server 113=====</c>\n";
    if (sServer4List != "") sS4Header = "<c2>=====Server 114=====</c>\n";
    if (sServer5List != "") sS5Header = "<c2>=====Server 115=====</c>\n";
    sMessage += sS1Header + sServer1List + sS2Header + sServer2List + sS3Header + sServer3List + sS4Header + sServer4List + sS5Header + sServer5List;
    sMessage += "<c2>There are " + IntToString(nCount) + " players on other servers.</c>";
    SendMessageToPC(oPC, sMessage);
}

(pardon the redundant color codes) Razz

Here's a third, used in our market, again using custom sql calls rather than the aps functions:

Code:

SQLExecDirect("CREATE TABLE market (" +
                    "server TINYINT UNSIGNED default NULL," + 
                    "market VARCHAR(64) default NULL," +       
                    "playername VARCHAR(64) default NULL," +
                    "cdkey VARCHAR(64) default NULL," +
                    "resref VARCHAR(64) default NULL," +
                    "name VARCHAR(64) default NULL," +
                    "price INT UNSIGNED default NULL," +
                    "charges TINYINT UNSIGNED default NULL," +
                    "bidder VARCHAR(64) default NULL," +
                    "bidding DATETIME default NULL," +
                    "expires DATETIME default NULL," +
                    "KEY (server,market,cdkey)" +
                    ")" );


The choice of datatypes is dependant on speed, indexability, and what you need to store. I was fortunate enough to have an SQL expert to consult when working on my systems. You can also take a look at this reference manual:
http://dev.mysql.com/doc/refman/5.0/en/index.html

Hopefully this will show you some of the possibilities SQL provides once you start tinkering. Razz

Funky[/code]
Back to top
View user's profile Send private message
chunkymonky



Joined: 20 Feb 2005
Posts: 31

PostPosted: Sun Jan 14, 2007 15:40    Post subject: Reply with quote

Wow, thanks Funky. I will have a project to tinker with now (aside from brewing beer in my basement) that uses my brain some.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    nwnx.org Forum Index -> Database related All times are GMT + 2 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group