Developer Store
Support
Member Forums

Screenshots
FAQ
Documentation
License
Known Issues
Downloads

MMOWorkshop.com Store Opened!
Torque MMO Kit - Open Sourced!
Torque MMO Kit - 1.5.2 Port Alpha Test
Torque MMO Kit - OSX Status

GarageGames.com irc.prairiegames.com
#mmoworkshop

PyTorque
TGB Web Browser


Boaal

hallsofvalhalla - After a long epiphany
Leathel - FoHO pre-Alpha 2.42
OldRod - More Musings on the MMO Industry
xapken - nice
J.C. Smith - 0.0.4.1 Build Notes
Wolf Dreamer - Pointless blog of pointless things
AthlonJedi2 - Server Nuked !!!!!
gamer_goof - New character model GIRL1 available only $4
... MORE BLOGS!

Lets talk skills.
Mob chase range?
Auction House + Internal Mail Sy...
Crafting Wiki
Every Day a new question... :)
Web Host
Seeking experienced programmer
Integrating Green-Ear SDK (paid)
where character information is s...
Spells Problem...

This is more of an information entry than a step by step how to replicate what I added. After taking all of this in you should be able to do just about anything besides create a new database :-) (my upcoming AH project). Again, I didn't write this code and I believe I have a strong understanding now on what the hell is going on, but I could be wrong and something could go horribly wrong, so backup your stuff before you implement the information you learn. Thanks again.

--Xerves

The Situation

I am adding into my game something called a Skill Link. It is a kind of passive skill that can be linked together to affect spells/stats/combat/etc. I used a bit of the macro code to get the GUI part working since I wanted it to work properly offline and online. That part wasn't horribly complicated, but once I dugg a little more into it you then understand that now you have a new table that is linked to the player and to the class skill table for reference.

Anyway, I am going to go over everything needed here from start to finish. Some detailed pieces of code for some sections and some sections more of a general overview of what is needed.

The GUI Window

This part is fairly straight forward. Create the new .gui file. I used the Macro gui file as a template. Changed mainly the names (MACRO to SKILLLINK) and changed the number scheme slight. Had to replace the 3 commands inside the gui file

command = "Py::OnSLButtonClick(5,0);";
altCommand = "Py::OnSLButtonAltClick(5,0);";
rightClickCommand = "ToggleItemInfoWnd();";

This tells the game the player clicked on the 6th row (arrays start with 0) and this is used mainly to tell the game it was the 6th player. The 0 is the slot of the button (first one, again 0). Right Click then pulls open the Item Information Window.

Finally you will need this at the bottom.

//--- OBJECT WRITE END ---

PyExec("mud/client/gui/skillLinkWnd.py");

If you forgot to change this or add it your code will not work.

Adding the Window to the Tome

You can add a key command and/or a button on the tome. In order to add to the tome you can simply modify TomeGui? offline or through the GUI editor. You need to add a new GuiBitmapButtonCtrl? to the end and extend the whole window about 20 pixels or so to fit the button in. My ButtonCtrl? looked like this

      new GuiBitmapButtonCtrl() {
         profile = "MenuButtonProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "353 29";
         extent = "22 22";
         minExtent = "8 2";
         visible = "1";
         command = "ToggleSkillLinkWnd();";
         toolTip = "Skill Links";
         mouseOver = "0";
         hotKey = "-1";
         toggleLocked = "0";
         pulseRed = "0";
         pulseGreen = "0";
         groupNum = "-1";
         buttonType = "PushButton";
         bitmap = "~/data/ui/elements/tomebuttons";
         number = "-1";
         hasStateBitmaps = "0";
         u0 = "0.21";
         u1 = "0.2";
         v0 = "0.82";
         v1 = "0.2";
      };

You will notice it is using a bitmap and then it pulls a button out of it (the u0/u1/v0/v1 is the coordinates). This is the coordinates of the 2nd button on that last row. Also, a command of ToggleSkillLinkWnd?() is called when it is pushed. That code needs to be placed in the GUI file at game.mmo/client/ui/TomeGui.gui. It looks like this

function ToggleSkillLinkWnd()
{
  if (SkillLinkWnd.isAwake())
     canvas.popDialog(SkillLinkWnd);
  else
     canvas.pushDialog(SkillLinkWnd);
     
  Tome_CommandText.makeFirstResponder(true);

}

Adding the GUI Window

* A few things I have missed in the past here. In mud/client/gui/macro.py it has some window location code. If you don't add your window here it doesn't get saved to the client and you have to reopen it and move it :-(. You should notice a handful of windows at the top of this file. Need to add a default position in DEFAULT_WINDOW_POSITIONS, a few lines down there is an active array, you add it here and I set it to 1 (not sure if that is needed or not). A few lines down you have CheckWindowPositions? and another list to add it to. Even further down there is SaveWindowSettings? and two places (windows and awindows). After that just save since the rest is Macro Code.

* Also you need to add it to game.mmo\client\init.cs.

Adding the Server Side Code

A few references to this is made soon, so I am going to throw this in now. We need to actually create a few values on the character to store this information. So in mud/world/character.py add the following class/sql object.

class SkillLinkInfo(Persistent):
    character = ForeignKey('Character')
    skillname = StringCol(default="")
    slot = IntCol(default = 0)
    
    def destroySelf(self):
        Persistent.destroySelf(self)

This is the new table for Skill Link Information. It points back to the character, has a skillname, and a slot (button). The destroySelf overload is really not needed I don't believe, I just left it in.

In the Character class you need to add your reference to SkillLinkInfo? along with the skill link slots available

    skillLinkInfo = MultipleJoin('SkillLinkInfo')
    skillLinks = IntCol(default = 2)

This will join the two tables together and allow you to loop through skillLinkInfo. It also sets the default amount of slots to 2.

Adding the GUI Client Side Code

Well the Client GUI piece is pretty much done. You should have a window and you can open it via the tome. Now you actually need some code to control it. I am going to post bits of the code and explain what is going on. Please remember this is part of the RPG logic that runs on the client and will make calls back to the server for information/commands (or get information already pushed).

# Copyright (C) 2004-2007 Prairie Games, Inc
# Please see LICENSE.TXT for details

from tgenative import *
from mud.tgepython.console import TGEExport

SKILLLINKWND = None

class SkillLinkWnd:
    def __init__(self):
        self.charPages = {}
        
        self.window = TGEObject("SkillLinkWnd_Window")
        
        self.charButtons  = {}
        self.slButtons = {}

        for x in xrange(0,6):
            self.charButtons[x]  = TGEObject("SKILLLINKWND_CHAR%i"%x)
            self.slButtons[x] = dict((y,TGEObject("SKILLLINKWND_SL_%i_%i"%(x,y))) for y in xrange(0,10)) 

This is the beginning of the file. Typically you will see a global value for the window in caps, same here and it is set to None for the time being. It is commonly called in the other server gui functions to pull data from. Then comes the declare of the class and the init (creation). Some values are set such as the window and the buttons are assigned to the TGE names in the GUI.

Now, something needs to happen with this GUI. code/mud/client/playermind.py contains a lot of calls to refresh certain parts of the GUI. If you find this code in the file

            #LEADERWND.tick()  # don't even call instead of just returning
            BUFFWND.tick()
            gui.macroWnd.SetFromCharacterInfos(self.charInfos)

I placed a new call to my GUI code

            SKILLLINKWND.SetFromCharacterInfos(self.charInfos)

I need to import the SKILLLINKWND value at the very top of the file otherwise it won't like that one bit. This simply tells the client to refresh the window with the SetFromCharacterInfos? function. That function is listed below...

    def SetFromCharacterInfos(self,cinfos):
        numc = len(cinfos)
        self.window.extent = '418 %i'%(34+34*numc)
        for x in xrange(0,6):
            self.charButtons[x].visible = False
            for y in xrange(0,10):
                self.slButtons[x][y].visible = False   
                self.slButtons[x][y].setBitmap("")  
       
        for cindex,cinfo in cinfos.iteritems():
            picCtrl = self.charButtons[cindex]
            
            for y in xrange(0, cinfo.SKILLLINKS):
                self.slButtons[cindex][y].visible = True
                
            for slot in cinfo.SKILLLINKINFO.iterkeys():
                skillname = cinfo.SKILLLINKINFO[slot]
                if cinfo.NSKILLS.has_key(skillname):
                    icon = cinfo.NSKILLS[skillname].ICON
            
                    if icon.startswith("SPELLICON_"):
                        split = icon.split("_")
                        index=int(split[2])
                        u0=(float(index%6)*40.0)/256.0
                        v0=(float(index/6)*40.0)/256.0
                        u1=(40.0/256.0)
                        v1=(40.0/256.0)
                        
                        self.slButtons[cindex][slot].setBitmapUV("~/data/ui/icons/spells0%s"%split[1],u0,v0,u1,v1)
                    else:
                        self.slButtons[cindex][slot].setBitmap("~/data/ui/icons/%s"%icon)
                else:
                    print "SkillLinkWnd Error: %s has an invalid skill of %s in a skill link slot"%(cinfo.NAME, skillname)
            
            picCtrl.visible = True    
            if cinfo.DEAD:
                picCtrl.setBitmap("~/data/ui/icons/dead")
            else:
                picCtrl.setBitmap("~/data/ui/charportraits/%s"%cinfo.PORTRAITPIC)    

A lot is happening here. First off it is setting the size of the window based on the size of the parth and then it is setting all of the buttons invisible so they don't show up (the player can have 2-10 buttons visible at any time depending on how many skill link slots are open for that player. It then will start to loop through all the players in the party and set the picture of the player up and then start to make the buttons visible. Then it is looping through some client information about skills and skill links the player has setup (the skill links part will be shown later). Once it finds everything it maps the button's icon based on the skill's icon.

Next...the button click code

def OnSLButtonClick(args):
    from partyWnd import PARTYWND
    from mud.client.gui.macro import CURSORMACROTYPE,CURSORMACROINFO
    cindex = int(args[1])
    mindex = int(args[2])    
    if CURSORMACROTYPE == 'SKILL':
        PARTYWND.mind.perspective.callRemote("PlayerAvatar","assignSkillLink",cindex, CURSORMACROINFO, mindex)
    
    TGEObject("SKILLLINKWND_SL_%i_%i"%(cindex,mindex)).SetValue(0)
    cursor = TGEObject("DefaultCursor")
    cursor.bitmapName = ""
    cursor.u0=cursor.v0 = 0
    cursor.u1=cursor.v1 = 1
    cursor.sizeX =-1
    cursor.sizeY=-1
    cursor.cursorControl = ""
    return

This imports some cursor information from the macro code (to make sure the cursor has a skill and its information). The args are parsed for a character and slot value and then the cursor is checked to make sure it has a skill. If it does then a call is made to the server to assign it.

The next part of the code sets the button value backed to unpressed and clears the cursor back to normal.

Finally we have the Alt Click to remove it.

def OnSLButtonAltClick(args):
    from partyWnd import PARTYWND
    cindex = int(args[1])
    mindex = int(args[2])
    PARTYWND.mind.perspective.callRemote("PlayerAvatar","removeSkillLink",cindex, mindex)    
    return

Same bit of information here. A call is made to the server to remove it.

Ok the last final bit here

def PyExec():
    global SKILLLINKWND
    SKILLLINKWND = SkillLinkWnd()
    
    TGEExport(OnSLButtonClick,"Py","OnSLButtonClick","desc",3,3)
    TGEExport(OnSLButtonAltClick,"Py","OnSLButtonAltClick","desc",3,3)

This exports to TGE the commands so they work properly and sets the global SKILLLINKWND to the class. Leaving this out will make the GUI not function.

Finally, you might want to add the code to the init code here:

code/mud/client/gui/init.py

Adding the GUI Client Skill Link Cached Information

This part adds the information for Skill Links to the Client (cinfo.SKILLLINKINFO). This allows the client to know what skill links are assigned and what the name of the skill is. In /mud/world/shared/playdata.py in the class CharacterInfo? you need to add two chunks of code. In getStateToCacheAndObserveFor you add the following

        state['SKILLLINKS'] = character.skillLinks
       
        skilllinkinfo = {}
        for s in character.skillLinkInfo:
            skilllinkinfo[s.slot] = s.skillname
        state['SKILLLINKINFO'] = skilllinkinfo

This is the number of skill links the character has and the 2nd part is the actual skill links attached to the player curently. The slot is the button and it contains the skillname.

A bit further down there is the Refresh and this needs to be added

        skilllinkinfo = {}
        for s in character.skillLinkInfo:
            skilllinkinfo[s.slot] = s.skillname
        if skilllinkinfo != state['SKILLLINKINFO']:
            changed['SKILLLINKINFO'] = state['SKILLLINKINFO'] = skilllinkinfo    

        if state['SKILLLINKS']!=character.skillLinks:
            state['SKILLLINKS']=changed['SKILLLINKS']=character.skillLinks

When a change is made triggering a refresh all of the cached values are compared to see if a chance is made. If you don't do this the GUI will never have refreshed data. Mainly what happens here is the changed array is sent to the client and then merged into the cached data so only the changed data is sent.

Adding the Information Window Code

This has some of the code I have released for viewing skill information in the Information Window. In order to make it work you need to find the following in itemInfoWnd.py

        if ghost:
            if isSpell:
                self.setSpell(ghost)

Before it add

        if not found:
            skilllinkwnd = TGEObject("SkillLinkWnd")
            if int(skilllinkwnd.isAwake()):
                from skillLinkWnd import SKILLLINKWND
                for cindex,buttons in SKILLLINKWND.slButtons.iteritems():
                    if cindex >= len(PARTYWND.charInfos):
                        continue
                    for slot, button in buttons.iteritems():
                        ncinfo = PARTYWND.charInfos[cindex]
                        if int(button.mouseOver) and ncinfo.SKILLLINKINFO.has_key(slot):
                            found = True
                            ghost = ncinfo.NSKILLS[ncinfo.SKILLLINKINFO[slot]]
                            isSkill = True
                            break

This checks to see if the window is open and then it loops throught he buttons to see if the player has the mouse over the button (also checks to make sure the button has valid information in it). If so it then sets the window up with the information for that skill with the SKILLLINKINFO (which has the name of the skill).

Adding the client to server calls

PARTYWND.mind.perspective.callRemote("PlayerAvatar","removeSkillLink",cindex, mindex)  
 

This is a client to server call. The client is sending a command to remove a skill link. It is sending it to PlayerAvatar? (the player container that contains all of the characters). So we need to add this call. In mud/world/playeravatar.py find this function

def perspective_repairParty(self,cindex):

Above it add these two

    def perspective_assignSkillLink(self,cindex,skill,slot):
        if cindex > len(self.player.party.members) - 1:
            return
        char = self.player.party.members[cindex]
        char.assignSkillLink(skill,slot)
        
    def perspective_removeSkillLink(self,cindex,slot):
        if cindex > len(self.player.party.members) - 1:
            return
        char = self.player.party.members[cindex]
        char.removeSkillLink(slot)

These are the perspective calls and contain the player avatar and the player/party information. You will notice a sanity check to make sure a valid character is called and if it is then the actual character is pulled from the party and then a call is made on the character. To add those calls you need to add it to /mud/world/character.py to the Character class.

    def assignSkillLink(self, skillname, slot):
        for skill in self.skills:
            if skill.skillname == skillname:
                sfnd = 0
                for s in self.skillLinkInfo:
                    if (slot == s.slot):
                        sfnd = 1
                        s.skillname = skillname
                        self.mob.updateClassStats()
                if (sfnd == 0):
                    SkillLinkInfo(character = self, skillname = skillname, slot = slot)
                    self.mob.updateClassStats()
                self.player.sendGameText(RPG_MSG_GAME_GAINED,"%s added <a:Skill%s>%s</a> as a Skill Link!\\n"%(self.name,GetTWikiName(skillname),skillname))
                return
                
        self.player.sendGameText(RPG_MSG_GAME_DENIED,"%s does not possess <a:Skill%s>%s</a> or it does not exist in the game!\\n"%(self.name,GetTWikiName(skillname),skillname))
        return
        
    def removeSkillLink(self, slot):
        for s in self.skillLinkInfo:
            if (slot == s.slot):
                skillname = s.skillname
                self.player.sendGameText(RPG_MSG_GAME_GAINED,"%s removed <a:Skill%s>%s</a> as a Skill Link!\\n" (self.name,GetTWikiName(skillname),skillname))
                s.destroySelf();
                self.mob.updateClassStats()
                return;

Thie first function assigns the skill link to the button. You will notice if one isn't found a SkillLinkInfo? is called creating one (this is a new row created in the DB and also a mapping to the player). You will also notice updateClassStats is called, this will update stats and kick off a server to client refresh of player data. Lastly a message is sent to the player.

The 2nd function removes the Skill Link by destroying it since it isn't needed.

Adding new SkillLinkInfo? table to Genesis

Every time you compile a baseline database is created and it is used later on to compare the current databases to make syncs. If you forget to do this things simply won't work. You will find genesis.py in the root of your /tmmokit directory. It might not be in your IDE. You need to make to changes.

First in the table imports add the new class we just created to the end of the mud.world.character import.

from mud.world.character import Character,CharacterSpell,StartingGear,CharacterSkill,CharacterAdvancement,CharacterDialogChoice,CharacterVaultItem,CharacterFaction,SkillLinkInfo

This imports the SkillLinkInfo? class.

Next we add in the table to the end of table array in CreateTables?. This will drop the table and create it for the baseline. Once this is done your table will start working. A bit down you will see some more code for the DBDict part of the code if you were going that route (we aren't here).

Adding the offline update code

This next part takes care of the offline (single player) piece of the database. There is a separate piece for online play and this part will only fix the offline part. What is going on here is when the game is compiled a database update is forced on the first time the player logs in. It will call WorldUpdate and it will start to compare the working database with the new baseline database just created and look for "differences". If you don't update this depending on how it is linked (by name or id) it could cause a variety of errors and typically will bug out the client. So it is important this is done.

Now on to /mud/world/worldupdate.py.

Need to add SkillLinkInfo? to the character import near the top of the file. Also need to import ClassSkill? now.

from mud.world.skill import ClassSkill

I changed the CharacterSkillCopier? over to this to fix skills that get deleted.

class CharacterSkillCopier:
    def __init__(self,cur,id):
        self.dbAttr = {}
        self.skillname = ""
        t = mixedToUnder("CharacterSkill")
        cur.execute("SELECT * from %s WHERE id = %i;"%(t,id))
        for name,value in zip(WSCHEMA["CharacterSkill"],cur.fetchall()[0]):
            if name == "skillname":
                skillname = value
            self.dbAttr[str(name)]=value
            
        self.skillname = skillname
            
    def install(self,char):
        try:
            sp = list(ClassSkill.selectBy(skillname="%s"%self.skillname))
            if (len(sp) < 1):
                print "CharacterSkill: %s no longer exists"%self.skillname
                return
        except:
            print "CharacterSkill: %s no longer exists"%self.skillname
            return
            
        FilterColumns(CharacterSkill,self.dbAttr)
        self.dbAttr["characterID"]=char.id            
        CharacterSkill(**self.dbAttr)

This will go through all of the character skills and compare them to the class skills. If the skill is not found it isn't added back into the game for the character.

Now with that fix, we add a very similiar function right below it for the Skill Links.

class SkillLinkInfoCopier:
    def __init__(self,cur,id):
        self.dbAttr = {}
        t = mixedToUnder("SkillLinkInfo")
        cur.execute("SELECT * from %s WHERE id = %i;"%(t,id))
        for name,value in zip(WSCHEMA["SkillLinkInfo"],cur.fetchall()[0]):
            if name == "skillname":
                skillname = value
            self.dbAttr[str(name)]=value
            
        self.skillname = skillname
            
    def install(self,char):
        try:
            sp = list(ClassSkill.selectBy(skillname="%s"%self.skillname))
            if (len(sp) < 1):
                print "SkillLinkInfo: %s no longer exists"%self.skillname
                return
        except:
            print "SkillLinkInfo: %s no longer exists"%self.skillname
            return
        FilterColumns(SkillLinkInfo,self.dbAttr)
        self.dbAttr["characterID"]=char.id            
        SkillLinkInfo(**self.dbAttr)

This pretty much does the same thing to make sure or Skill Link is still a valid skill. Since this is stored on the server (macros are stored on the client) we need to do this.

I bit further down the copier code is ran...find this

        for r in cur.fetchall():
            f = CharacterSkillCopier(cur,r[0])
            skills.append(f)

Need to add the new skill link copier

        #SkillLinks
        skilllinks = self.skillLinkInfo = []
        cur.execute("select id from skill_link_info where character_id = %i;"%id)
        for r in cur.fetchall():
            f = SkillLinkInfoCopier(cur,r[0])
            skilllinks.append(f)

A bit further down finally the good skill links are installed. Find this

         for s in self.skills:
             s.install(char)

Add the following afterwards

        #skilllinks
        for s in self.skillLinkInfo:
            s.install(char)

Adding the online updates

Ok this took me forever since I didn't really understand it very well. I know more than I want to know about it now :-(. What is going on here is that with the offline database the characters are simply stored in the world database so unless you delete a character it never leaves that database. However, online you have to deal with the buffer system setup. Instead of leaving the characters in the database it dumps the information into a compressed buffer and then does a lot of converting when it is pushed/pulled from the buffer to the active database. I would imagine this is to conserve bandwidth, database processing, and size. It is however very confusing at first and took me awhile to figure out what the hell and where it was going on. No worry though, you have this document now :-).

First we need to add some deletion code in mud/world/character.py. You really need this for offline too, I just chose this point for the example...

Find the following

        for o in self.vaultItems:
            o.destroySelf()

After it add

        for o in self.skillLinkInfo:
            o.destroySelf()

This will delete the skillLinkInfo from the database when the character is deleted. This is important because when the character is swapped out of the active online world database to the buffer table, it is deleted :-). If you forget this you will get duplicates since the active database will just keep collecting these and it will be assigning it to players that it should not.

Once this is done, time to move on to the actual updating bits.

In mud/characterserver/convertdb.py add our new table to CHARACTER_TABLES since there is some referenced character information. With my SkillLinkInfo? it looks like skill_link_info.

A bit further down you will find the following

        cursor.execute("DELETE from character where id = %i;"%cid)
        cursor.execute("DELETE from character_spell where character_id = %i;"%cid)
        cursor.execute("DELETE from character_skill where character_id = %i;"%cid)

After it add

        cursor.execute("DELETE from skill_link_info where character_id = %i;"%cid)

Now on to /mud/worldserver/charutil.py. Need to add skill_link_info to CHARACTER_TABLES. A bit further down there are 3 references to an array called ctables. skill_link_info needs to be added to all 3.

Ok now on to the big moving force.../mud/characterserver/upgradedb.py. This is the file you run to keep the tiem synced up properly with Genesis. This also is the script that does most of the checking to make sure the player buffers are current and don't have items/skills/spells/etc that no longer exist. A handful of changes are needed here and some were missing for skills so I added them in and they are in the example here.

Find thing you need to do is add SkillLinkInfo? to the character import statement at the top. Next there is CHARACTER_TABLES. Need to add skill_link_info here. Next is TLOOKUP with the matchup of the table to the class name. Add "skill_link_info":SkillLinkInfo? there.

Next find the following

TRANS_SPELLPROTO = {}
TRANS_FACTION = {}
TRANS_ADVANCEMENTPROTO = {}

Below it add

TRANS_CLASSSKILL = {}

Next a few lines down find this

TTRANS['spell_proto']=TRANS_SPELLPROTO
TTRANS['faction']=TRANS_FACTION
TTRANS['advancement_proto']=TRANS_ADVANCEMENTPROTO

After it add this

TTRANS['class_skill']=TRANS_CLASSSKILL

This will be used to update the skills. Now to setup the buffer Translation. Next in DoTranslation? add this global declare before nindexes = NINDEXES['%s'%table]

    global NEWCONN

A bit further down find the following

        except KeyError:
            #traceback.print_exc()
            
            ncursor.execute("DELETE FROM %s WHERE id = %i"%(table,TTRANS[table][values[0]]))
            if nindexes.has_key("name"):
                print "Removing named row %s from %s"%(values[1],table),values
            else:
                print "Removing anonmyous row from %s"%table,values

After it add this code to check for skills and skill links that no longer exist

    if (table == 'character_skill' or table == 'skill_link_info'):
        ncursor.execute("SELECT skillname,id from %s;"%table)
        for name,id in ncursor.fetchall():    
            nncursor = NEWCONN.cursor()
            try:
                nncursor.execute('SELECT id from class_skill WHERE skillname = "%s";'%name)
                nid = nncursor.fetchone()[0]
            except:
                print "Skill %s no longer exists.  Removing from table %s"%(name, table)
                ncursor.execute('DELETE FROM %s WHERE skillname = "%s";'%(table,name))

This is being run on the decompressed character and will delete the state information. Before this you will find some much more nifty code that takes care of characters and spells. This is a bit more bruteish, but it works.

Quite a bit further down find this

    cursor.execute("SELECT name,id from spell_proto;")
    for name,oid in cursor.fetchall():
        try:
            ncursor.execute('SELECT id from spell_proto WHERE name = "%s";'%name)
            nid = ncursor.fetchone()[0]
            TRANS_SPELLPROTO[oid]=nid
        except:
            print "SpellProto %s no longer exists"%name

After it add a bit for skills

    cursor.execute("SELECT skillname,id from class_skill;")
    for name,oid in cursor.fetchall():
        try:
            ncursor.execute('SELECT id from class_skill WHERE skillname = "%s";'%name)
            nid = ncursor.fetchone()[0]
            TRANS_CLASSSKILL[oid]=nid
        except:
            print "ClassSkill %s no longer exists"%name

This will delete any stale skills.

Thats a Wrap

Well that is everything. Hope that helps someone.